diff options
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.java | 211 |
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; + } + } } } |