aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java')
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java211
1 files changed, 169 insertions, 42 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
index ea37677d1..c4a694bba 100644
--- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -19,11 +19,14 @@ package org.sufficientlysecure.keychain.ui;
import java.util.HashMap;
import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
+import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@@ -53,13 +56,21 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.Button;
@@ -73,24 +84,37 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
* StickyListHeaders library which does not extend upon ListView.
*/
-public class KeyListFragment extends Fragment implements AdapterView.OnItemClickListener,
+public class KeyListFragment extends Fragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
private KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList;
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ boolean mListShown;
+ View mProgressContainer;
+ View mListContainer;
+
+ private String mCurQuery;
+ private SearchView mSearchView;
// empty list layout
private BootstrapButton mButtonEmptyCreate;
private BootstrapButton mButtonEmptyImport;
+
/**
* Load custom layout with StickyListView from library
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.key_list_fragment, container, false);
+ View root = inflater.inflate(R.layout.key_list_fragment, container, false);
+
+ mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list);
+ mStickyList.setOnItemClickListener(this);
- mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create);
+
+ // empty view
+ mButtonEmptyCreate = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_create);
mButtonEmptyCreate.setOnClickListener(new OnClickListener() {
@Override
@@ -102,8 +126,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
startActivityForResult(intent, 0);
}
});
-
- mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import);
+ mButtonEmptyImport = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_import);
mButtonEmptyImport.setOnClickListener(new OnClickListener() {
@Override
@@ -114,7 +137,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
}
});
- return view;
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ mListContainer = root.findViewById(R.id.key_list_list_container);
+ mProgressContainer = root.findViewById(R.id.key_list_progress_container);
+ mListShown = true;
+
+ return root;
}
/**
@@ -125,8 +153,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
-
mStickyList.setOnItemClickListener(this);
mStickyList.setAreHeadersSticky(true);
mStickyList.setDrawingListUnderStickyHeader(false);
@@ -137,7 +163,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
}
// this view is made visible if no data is available
- mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
+ mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
/*
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
@@ -147,8 +173,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() {
- private int count = 0;
-
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
android.view.MenuInflater inflater = getActivity().getMenuInflater();
@@ -174,7 +198,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
break;
}
case R.id.menu_key_list_multi_delete: {
- ids = mAdapter.getCurrentSelectedItemIds();
+ ids = mStickyList.getWrappedList().getCheckedItemIds();
showDeleteKeyDialog(mode, ids);
break;
}
@@ -184,7 +208,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
@Override
public void onDestroyActionMode(ActionMode mode) {
- count = 0;
mAdapter.clearSelection();
}
@@ -192,13 +215,11 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
if (checked) {
- count++;
mAdapter.setNewSelection(position, checked);
} else {
- count--;
mAdapter.removeSelection(position);
}
-
+ int count = mStickyList.getCheckedItemCount();
String keysSelected = getResources().getQuantityString(
R.plurals.key_list_selected_keys, count, count);
mode.setTitle(keysSelected);
@@ -207,9 +228,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
});
}
- // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // NOTE: Not supported by StickyListHeader, but reimplemented here
// Start out with a progress indicator.
- // setListShown(false);
+ setListShown(false);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new KeyListAdapter(getActivity(), null, Id.type.public_key);
@@ -238,27 +262,33 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
-
+ String where = null;
+ String whereArgs[] = null;
+ if (mCurQuery != null) {
+ where = KeychainContract.UserIds.USER_ID + " LIKE ?";
+ whereArgs = new String[]{"%" + mCurQuery + "%"};
+ }
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
- return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER);
+ return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, SORT_ORDER);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
+ mAdapter.setSearchQuery(mCurQuery);
mAdapter.swapCursor(data);
mStickyList.setAdapter(mAdapter);
- // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading
+ // NOTE: Not supported by StickyListHeader, but reimplemented here
// The list should now be shown.
- // if (isResumed()) {
- // setListShown(true);
- // } else {
- // setListShownNoAnimation(true);
- // }
+ if (isResumed()) {
+ setListShown(true);
+ } else {
+ setListShownNoAnimation(true);
+ }
}
@Override
@@ -338,6 +368,87 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
}
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ // Get the searchview
+ MenuItem searchItem = menu.findItem(R.id.menu_key_list_search);
+ mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+
+ // Execute this when searching
+ mSearchView.setOnQueryTextListener(this);
+
+ // Erase search result without focus
+ MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ mCurQuery = null;
+ mSearchView.setQuery("", true);
+ getLoaderManager().restartLoader(0, null, KeyListFragment.this);
+ return true;
+ }
+ });
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String s) {
+ // Called when the action bar search text has changed. Update
+ // the search filter, and restart the loader to do a new query
+ // with this filter.
+ mCurQuery = !TextUtils.isEmpty(s) ? s : null;
+ getLoaderManager().restartLoader(0, null, this);
+ return true;
+ }
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ public void setListShown(boolean shown, boolean animate) {
+ if (mListShown == shown) {
+ return;
+ }
+ mListShown = shown;
+ if (shown) {
+ if (animate) {
+ mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_out));
+ mListContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_in));
+ }
+ mProgressContainer.setVisibility(View.GONE);
+ mListContainer.setVisibility(View.VISIBLE);
+ } else {
+ if (animate) {
+ mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_in));
+ mListContainer.startAnimation(AnimationUtils.loadAnimation(
+ getActivity(), android.R.anim.fade_out));
+ }
+ mProgressContainer.setVisibility(View.VISIBLE);
+ mListContainer.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ public void setListShown(boolean shown) {
+ setListShown(shown, true);
+ }
+
+ // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
+ public void setListShownNoAnimation(boolean shown) {
+ setListShown(shown, false);
+ }
+
/**
* Implements StickyListHeadersAdapter from library
*/
@@ -347,6 +458,8 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
private int mIndexIsRevoked;
private int mMasterKeyId;
+ private String mCurQuery;
+
@SuppressLint("UseSparseArrays")
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
@@ -394,12 +507,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
String userId = cursor.getString(mIndexUserId);
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
if (userIdSplit[0] != null) {
- mainUserId.setText(userIdSplit[0]);
+ mainUserId.setText(highlightSearchQuery(userIdSplit[0]));
} else {
mainUserId.setText(R.string.user_id_no_name);
}
if (userIdSplit[1] != null) {
- mainUserIdRest.setText(userIdSplit[1]);
+ mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
mainUserIdRest.setVisibility(View.VISIBLE);
} else {
mainUserIdRest.setVisibility(View.GONE);
@@ -558,20 +671,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
notifyDataSetChanged();
}
- public boolean isPositionChecked(int position) {
- Boolean result = mSelection.get(position);
- return result == null ? false : result;
- }
-
- public long[] getCurrentSelectedItemIds() {
- long[] ids = new long[mSelection.size()];
- int i = 0;
- // get master key ids
- for (int pos : mSelection.keySet())
- ids[i++] = mAdapter.getItemId(pos);
- return ids;
- }
-
public long[] getCurrentSelectedMasterKeyIds() {
long[] ids = new long[mSelection.size()];
int i = 0;
@@ -608,6 +707,34 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
return v;
}
+ // search highlight methods
+
+ public void setSearchQuery(String searchQuery) {
+ mCurQuery = searchQuery;
+ }
+
+ public String getSearchQuery() {
+ return mCurQuery;
+ }
+
+ protected Spannable highlightSearchQuery(String text) {
+ Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
+
+ if (mCurQuery != null) {
+ Pattern pattern = Pattern.compile("(?i)" + mCurQuery);
+ Matcher matcher = pattern.matcher(text);
+ if (matcher.find()) {
+ highlight.setSpan(
+ new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
+ matcher.start(),
+ matcher.end(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ return highlight;
+ } else {
+ return highlight;
+ }
+ }
}
}