From da0218b5b4c25645341c97725a6de710e32acd38 Mon Sep 17 00:00:00 2001 From: Tobias Erthal Date: Sun, 25 Sep 2016 15:00:38 +0200 Subject: [PATCH] Removing changes associated to KeyListFragment, they will get their own PR. --- OpenKeychain/build.gradle | 20 +- .../keychain/ui/KeyListFragment.java | 519 ++++++++++++---- .../keychain/ui/adapter/KeyAdapter.java | 23 + .../ui/adapter/KeySectionedListAdapter.java | 553 ------------------ .../res/drawable-v21/list_item_ripple.xml | 12 - .../main/res/drawable/list_item_ripple.xml | 5 - .../src/main/res/layout/key_list_dummy.xml | 47 -- .../src/main/res/layout/key_list_fragment.xml | 215 +++---- .../src/main/res/layout/key_list_header.xml | 27 + .../res/layout/key_list_header_private.xml | 34 -- .../res/layout/key_list_header_public.xml | 22 - .../src/main/res/layout/key_list_item.xml | 67 ++- 12 files changed, 605 insertions(+), 939 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java delete mode 100644 OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml delete mode 100644 OpenKeychain/src/main/res/drawable/list_item_ripple.xml delete mode 100644 OpenKeychain/src/main/res/layout/key_list_dummy.xml create mode 100644 OpenKeychain/src/main/res/layout/key_list_header.xml delete mode 100644 OpenKeychain/src/main/res/layout/key_list_header_private.xml delete mode 100644 OpenKeychain/src/main/res/layout/key_list_header_public.xml diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 885e77205..fc02ec683 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -23,6 +23,7 @@ dependencies { compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final' compile 'com.splitwise:tokenautocomplete:2.0.7@aar' + compile 'se.emilsjolander:stickylistheaders:2.7.0' compile 'org.sufficientlysecure:html-textview:1.8' compile 'org.sufficientlysecure:donations:2.4' compile 'com.nispok:snackbar:2.11.0' @@ -101,6 +102,7 @@ dependencyVerification { 'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13', 'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75', 'com.splitwise:tokenautocomplete:f56239588390f103b270b7c12361d99b06313a5a0410dc7f66e241ac4baf9baa', + 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', 'org.sufficientlysecure:html-textview:206f484fe4178be6c831fe680de558764967e7b56496c4cc7f37f2979a477df6', 'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', @@ -110,7 +112,6 @@ dependencyVerification { 'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633', 'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4', 'com.cocosw:bottomsheet:4af6112a7f4cad4e2b70e5fdf1edc39f51275523a0f53011a012837dc103e597', - 'com.tonicartos:superslim:ca89b5c674660cc6918a8f8fd385065bffeee27983e0d33c7c2f0ad7b34d2d49', 'com.mikepenz:materialdrawer:4169462fdde042e2bb53a7c2b4e2334d569d16b2020781ee05741b50e1a2967d', 'com.mikepenz:fastadapter:1bfc00216d71dfdfe0d8e7a9d92bb97bfaa1794543930e34b1f79d5d7adbddf6', 'com.mikepenz:materialize:575195b2fa5b2414fb14a59470ee21d8a8cd8355b651e0cf52e477e3ff1cd96c', @@ -119,18 +120,19 @@ dependencyVerification { 'com.mikepenz:fontawesome-typeface:033cf3460d8074bd37a1fefc2ff4eac8f2e3db835ec78bf386d46710e4d0827c', 'com.mikepenz:community-material-typeface:382e8446fc08fe03cb1e0f91ee329ffd514c113ad22f8389b88424ac71ed5fbb', 'com.fidesmo:nordpol-android:56f43fe2b1676817bcb4085926de14a08282ef6729c855c198d81aec62b20d65', -// 'open-keychain:openpgp-api-lib:00d6ae7e3c4cc9b9ffbb279ba511732fb30c2fb3c5f408171c74cce9b1dfe67f', -// 'open-keychain:openkeychain-api-lib:842dd4c4469e3e1e76790bd37c8474422d385b09a6cb55d0baeacabfdaf0bc58', -// 'open-keychain.extern.bouncycastle:core:cab27a0fe17ea07a94624c29855ac8bc8f8d55dff277349007152bc778959f03', -// 'open-keychain.extern.bouncycastle:pg:c45a5caf65c5b0e7be2c4b59948fb3717f93fd58d680191b7b20d3f1898cdb9a', -// 'open-keychain.extern.bouncycastle:prov:12f98852e8d3f129816516ea7a244a218eb7f7f0423c66e5084f35421e286c8e', -// 'open-keychain.extern:minidns:f3deaa17cf92e0b1828e6f261cc423ff8a11c4717c25f5a2edf75d53a7a164e0', -// 'open-keychain:KeybaseLib:5ebab9bc7aa43271c80f2de91216c886264ab30289ae4fb5cd08fbde9aa10981', -// 'open-keychain:safeslinger-exchange:a1cda62f799df6b416aca75d0e91d93d42c6171aab7da831086b522bfbef454f', +// 'OpenKeychain:openpgp-api-lib:2c145be0d124d37558f65ed962c47358b7e424dd4c00dc8818aad64598664c3d', +// 'OpenKeychain:openkeychain-api-lib:afff9f8410d8781fcd4023ca247d876ca124a6ee4f718afbc3740fc6078a255d', +// 'OpenKeychain.extern.bouncycastle:core:18876ec7629c427002ee00bb361b016b86159dcd9aac68951aec92f9ca41bb7a', +// 'OpenKeychain.extern.bouncycastle:pg:b6469f69d17ecb43c86c082fb1b5c01ae2f8e7a8ca5fa29e9f199c74dc021be1', +// 'OpenKeychain.extern.bouncycastle:prov:db7c1a72a86d918a25fc1ca8553762d1280274e8e2f02299f344b244bb527708', +// 'OpenKeychain.extern:minidns:8b324812d4b9ea9d639be43183f4292e5f257319fbb6c23faf2ee420f041d279', +// 'OpenKeychain:KeybaseLib:a992fb93c718fa34ab45850e34a294e413c9410e2b00243a43e6f303d85b6147', +// 'OpenKeychain:safeslinger-exchange:99d6ce7745edf5341495932bb26d0d1f8894b1edbfbddffc8514b2cf4149ad9b', 'com.android.support:animated-vector-drawable:4fcd1fc36034a804200ef3e552b0f2f688a0a7a8a007de43201e40bfedda73b3', 'com.android.support:support-vector-drawable:45b1f180b437a750429f6c1457181c167ba211c17fcb992f83cdbefef5eb1519', 'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266', 'com.fidesmo:nordpol-core:3de58e850a00bba5b4d3a604d1399bcd89f695ea191ec0b03a57222e18062d15', + 'com.tonicartos:superslim:ca89b5c674660cc6918a8f8fd385065bffeee27983e0d33c7c2f0ad7b34d2d49', ] } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index 596a715af..77139f5de 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -21,10 +21,12 @@ package org.sufficientlysecure.keychain.ui; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; +import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -43,14 +45,15 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; import android.widget.ViewAnimator; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; -import com.tonicartos.superslim.LayoutManager; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; @@ -67,25 +70,26 @@ import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.adapter.KeySectionedListAdapter; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; -import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; +import se.emilsjolander.stickylistheaders.StickyListHeadersListView; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; +import java.util.HashMap; /** * 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 RecyclerFragment - implements SearchView.OnQueryTextListener, +public class KeyListFragment extends LoaderFragment + implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks, FabContainer, CryptoOperationHelper.Callback { @@ -93,6 +97,9 @@ public class KeyListFragment extends RecyclerFragment private static final int REQUEST_DELETE = 2; private static final int REQUEST_VIEW_KEY = 3; + private KeyListAdapter mAdapter; + private StickyListHeadersListView mStickyList; + // saves the mode object for multiselect, needed for reset at some point private ActionMode mActionMode = null; @@ -110,107 +117,16 @@ public class KeyListFragment extends RecyclerFragment // for ConsolidateOperation private CryptoOperationHelper mConsolidateOpHelper; - // Callbacks related to listview and menu events - private final ActionMode.Callback mActionCallback - = new ActionMode.Callback() { - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - getActivity().getMenuInflater().inflate(R.menu.key_list_multi, menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_key_list_multi_encrypt: { - long[] keyIds = getAdapter().getSelectedMasterKeyIds(); - Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); - intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); - intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, keyIds); - - startActivityForResult(intent, REQUEST_ACTION); - mode.finish(); - break; - } - - case R.id.menu_key_list_multi_delete: { - long[] keyIds = getAdapter().getSelectedMasterKeyIds(); - boolean hasSecret = getAdapter().isAnySecretKeySelected(); - Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); - intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, keyIds); - intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); - if (hasSecret) { - intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, - Preferences.getPreferences(getActivity()).getPreferredKeyserver()); - } - - startActivityForResult(intent, REQUEST_DELETE); - break; - } - } - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - if(getAdapter() != null) { - getAdapter().finishSelection(); - } - } - }; - - private final KeySectionedListAdapter.KeyListListener mKeyListener - = new KeySectionedListAdapter.KeyListListener() { - @Override - public void onKeyDummyItemClicked() { - createKey(); - } - - @Override - public void onKeyItemClicked(long masterKeyId) { - Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); - viewIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - startActivityForResult(viewIntent, REQUEST_VIEW_KEY); - } - - @Override - public void onSlingerButtonClicked(long masterKeyId) { - Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); - startActivityForResult(safeSlingerIntent, REQUEST_ACTION); - } - - @Override - public void onSelectionStateChanged(int selectedCount) { - if(selectedCount < 1) { - if(mActionMode != null) { - mActionMode.finish(); - } - } else { - if(mActionMode == null) { - mActionMode = getActivity().startActionMode(mActionCallback); - } - - String keysSelected = getResources().getQuantityString( - R.plurals.key_list_selected_keys, selectedCount, selectedCount); - mActionMode.setTitle(keysSelected); - } - } - }; - - /** * 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); + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, superContainer, savedInstanceState); + View view = inflater.inflate(R.layout.key_list_fragment, getContainer()); + + mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list); + mStickyList.setOnItemClickListener(this); mFab = (FloatingActionsMenu) view.findViewById(R.id.fab_main); @@ -241,7 +157,7 @@ public class KeyListFragment extends RecyclerFragment }); - return view; + return root; } /** @@ -254,13 +170,102 @@ public class KeyListFragment extends RecyclerFragment // show app name instead of "keys" from nav drawer final FragmentActivity activity = getActivity(); + activity.setTitle(R.string.app_name); + mStickyList.setOnItemClickListener(this); + mStickyList.setAreHeadersSticky(true); + mStickyList.setDrawingListUnderStickyHeader(false); + mStickyList.setFastScrollEnabled(true); + + // Adds an empty footer view so that the Floating Action Button won't block content + // in last few rows. + View footer = new View(activity); + + int spacing = (int) android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() + ); + + android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( + android.widget.AbsListView.LayoutParams.MATCH_PARENT, + spacing + ); + + footer.setLayoutParams(params); + mStickyList.addFooterView(footer, null, false); + + /* + * Multi-selection + */ + mStickyList.setFastScrollAlwaysVisible(true); + + mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + android.view.MenuInflater inflater = activity.getMenuInflater(); + inflater.inflate(R.menu.key_list_multi, menu); + mActionMode = mode; + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + + // get IDs for checked positions as long array + long[] ids; + + switch (item.getItemId()) { + case R.id.menu_key_list_multi_encrypt: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + encrypt(mode, ids); + break; + } + case R.id.menu_key_list_multi_delete: { + ids = mAdapter.getCurrentSelectedMasterKeyIds(); + showDeleteKeyDialog(ids, mAdapter.isAnySecretSelected()); + break; + } + } + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + mAdapter.clearSelection(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + if (checked) { + mAdapter.setNewSelection(position, true); + } else { + mAdapter.removeSelection(position); + } + int count = mStickyList.getCheckedItemCount(); + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, count, count); + mode.setTitle(keysSelected); + } + + }); + // We have a menu item to show in action bar. setHasOptionsMenu(true); // Start out with a progress indicator. - hideList(false); + setContentShown(false); + + // this view is made visible if no data is available + mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty)); // click on search button (in empty view) starts query for search string vSearchContainer = (ViewAnimator) activity.findViewById(R.id.search_container); @@ -273,13 +278,8 @@ public class KeyListFragment extends RecyclerFragment }); // Create an empty adapter we will use to display the loaded data. - //mAdapter = new KeyListAdapter(activity, null, 0); - - KeySectionedListAdapter adapter = new KeySectionedListAdapter(getContext(), null); - adapter.setKeyListener(mKeyListener); - - setAdapter(adapter); - setLayoutManager(new LayoutManager(getActivity())); + mAdapter = new KeyListAdapter(activity, null, 0); + mStickyList.setAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, // or start a new one. @@ -298,6 +298,9 @@ public class KeyListFragment extends RecyclerFragment startActivity(searchIntent); } + static final String ORDER = + KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC"; + @Override public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This @@ -311,17 +314,31 @@ public class KeyListFragment extends RecyclerFragment // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), uri, - KeySectionedListAdapter.KeyListCursor.PROJECTION, null, null, - KeySectionedListAdapter.KeyListCursor.ORDER); + return new CursorLoader(getActivity(), uri, KeyListAdapter.PROJECTION, null, null, ORDER); } @Override public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - getAdapter().setSearchQuery(mQuery); - getAdapter().swapCursor(KeySectionedListAdapter.KeyListCursor.wrap(data)); + mAdapter.setSearchQuery(mQuery); + + if (data != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { + boolean isSecret = data.moveToFirst() && data.getInt(KeyListAdapter.INDEX_HAS_ANY_SECRET) != 0; + if (!isSecret) { + MatrixCursor headerCursor = new MatrixCursor(KeyListAdapter.PROJECTION); + Long[] row = new Long[KeyListAdapter.PROJECTION.length]; + row[KeyListAdapter.INDEX_HAS_ANY_SECRET] = 1L; + row[KeyListAdapter.INDEX_MASTER_KEY_ID] = 0L; + headerCursor.addRow(row); + + Cursor dataCursor = data; + data = new MergeCursor(new Cursor[] { + headerCursor, dataCursor + }); + } + } + mAdapter.swapCursor(data); // end action mode, if any if (mActionMode != null) { @@ -330,9 +347,9 @@ public class KeyListFragment extends RecyclerFragment // The list should now be shown. if (isResumed()) { - showList(true); + setContentShown(true); } else { - showList(false); + setContentShownNoAnimation(true); } } @@ -341,9 +358,47 @@ public class KeyListFragment extends RecyclerFragment // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. - getAdapter().swapCursor(null); + mAdapter.swapCursor(null); } + /** + * On click on item, start key view activity + */ + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); + viewIntent.setData( + KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position))); + startActivityForResult(viewIntent, REQUEST_VIEW_KEY); + } + + protected void encrypt(ActionMode mode, long[] masterKeyIds) { + Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); + intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); + intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds); + // used instead of startActivity set actionbar based on callingPackage + startActivityForResult(intent, REQUEST_ACTION); + + mode.finish(); + } + + /** + * Show dialog to delete key + * + * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not + */ + public void showDeleteKeyDialog(long[] masterKeyIds, boolean hasSecret) { + Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); + if (hasSecret) { + intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(getActivity()).getPreferredKeyserver()); + } + startActivityForResult(intent, REQUEST_DELETE); + } + + @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.key_list, menu); @@ -358,6 +413,7 @@ public class KeyListFragment extends RecyclerFragment // Get the searchview MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); + SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Execute this when searching @@ -502,6 +558,7 @@ public class KeyListFragment extends RecyclerFragment } ProviderHelper providerHelper = new ProviderHelper(activity); + Cursor cursor = providerHelper.getContentResolver().query( KeyRings.buildUnifiedKeyRingsUri(), new String[]{ KeyRings.FINGERPRINT @@ -516,7 +573,7 @@ public class KeyListFragment extends RecyclerFragment ArrayList keyList = new ArrayList<>(); try { while (cursor.moveToNext()) { - byte[] blob = cursor.getBlob(0); //fingerprint column is 0 + byte[] blob = cursor.getBlob(0);//fingerprint column is 0 String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); keyList.add(keyEntry); @@ -535,6 +592,7 @@ public class KeyListFragment extends RecyclerFragment } private void consolidate() { + CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @@ -564,11 +622,14 @@ public class KeyListFragment extends RecyclerFragment } }; - mConsolidateOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); + mConsolidateOpHelper = + new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); + mConsolidateOpHelper.cryptoOperation(); } private void benchmark() { + CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @@ -598,7 +659,9 @@ public class KeyListFragment extends RecyclerFragment } }; - CryptoOperationHelper opHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); + CryptoOperationHelper opHelper = + new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); + opHelper.cryptoOperation(); } @@ -617,7 +680,6 @@ public class KeyListFragment extends RecyclerFragment if (mActionMode != null) { mActionMode.finish(); } - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(getActivity()).show(); @@ -689,4 +751,209 @@ public class KeyListFragment extends RecyclerFragment public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } + + public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter { + + private HashMap mSelection = new HashMap<>(); + + private Context mContext; + + public KeyListAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + mContext = context; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = super.newView(context, cursor, parent); + + final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag(); + + holder.mSlinger.setVisibility(View.VISIBLE); + + ContentDescriptionHint.setup(holder.mSlingerButton); + holder.mSlingerButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (holder.mMasterKeyId != null) { + Intent safeSlingerIntent = new Intent(mContext, SafeSlingerActivity.class); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, holder.mMasterKeyId); + startActivityForResult(safeSlingerIntent, REQUEST_ACTION); + } + } + }); + + return view; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + // let the adapter handle setting up the row views + View v = super.getView(position, convertView, parent); + + int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); + + if (mSelection.get(position) != null) { + // selected position color + v.setBackgroundColor(colorEmphasis); + } else { + // default color + v.setBackgroundColor(Color.TRANSPARENT); + } + + return v; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; + long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); + if (isSecret && masterKeyId == 0L) { + + // sort of a hack: if this item isn't enabled, we make it clickable + // to intercept its click events + view.setClickable(true); + + KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); + h.setDummy(new OnClickListener() { + @Override + public void onClick(View v) { + createKey(); + } + }); + return; + } + + super.bindView(view, context, cursor); + } + + private class HeaderViewHolder { + TextView mText; + TextView mCount; + } + + /** + * Creates a new header view and binds the section headers to it. It uses the ViewHolder + * pattern. Most functionality is similar to getView() from Android's CursorAdapter. + *

+ * NOTE: The variables mDataValid and mCursor are available due to the super class + * CursorAdapter. + */ + @Override + public View getHeaderView(int position, View convertView, ViewGroup parent) { + HeaderViewHolder holder; + if (convertView == null) { + holder = new HeaderViewHolder(); + convertView = mInflater.inflate(R.layout.key_list_header, parent, false); + holder.mText = (TextView) convertView.findViewById(R.id.stickylist_header_text); + holder.mCount = (TextView) convertView.findViewById(R.id.contacts_num); + convertView.setTag(holder); + } else { + holder = (HeaderViewHolder) convertView.getTag(); + } + + if (!mDataValid) { + // no data available at this point + Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); + return convertView; + } + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) { + { // set contact count + int num = mCursor.getCount(); + // If this is a dummy secret key, subtract one + if (mCursor.getLong(INDEX_MASTER_KEY_ID) == 0L) { + num -= 1; + } + String contactsTotal = mContext.getResources().getQuantityString(R.plurals.n_keys, num, num); + holder.mCount.setText(contactsTotal); + holder.mCount.setVisibility(View.VISIBLE); + } + + holder.mText.setText(convertView.getResources().getString(R.string.my_keys)); + return convertView; + } + + // set header text as first char in user id + String userId = mCursor.getString(INDEX_USER_ID); + String headerText = convertView.getResources().getString(R.string.user_id_no_name); + if (userId != null && userId.length() > 0) { + headerText = "" + userId.charAt(0); + } + holder.mText.setText(headerText); + holder.mCount.setVisibility(View.GONE); + return convertView; + } + + /** + * Header IDs should be static, position=1 should always return the same Id that is. + */ + @Override + public long getHeaderId(int position) { + if (!mDataValid) { + // no data available at this point + Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); + return -1; + } + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + // early breakout: all secret keys are assigned id 0 + if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) { + return 1L; + } + // otherwise, return the first character of the name as ID + String userId = mCursor.getString(INDEX_USER_ID); + if (userId != null && userId.length() > 0) { + return Character.toUpperCase(userId.charAt(0)); + } else { + return Long.MAX_VALUE; + } + } + + /** + * -------------------------- MULTI-SELECTION METHODS -------------- + */ + public void setNewSelection(int position, boolean value) { + mSelection.put(position, value); + notifyDataSetChanged(); + } + + public boolean isAnySecretSelected() { + for (int pos : mSelection.keySet()) { + if (isSecretAvailable(pos)) { + return true; + } + } + return false; + } + + public long[] getCurrentSelectedMasterKeyIds() { + long[] ids = new long[mSelection.size()]; + int i = 0; + // get master key ids + for (int pos : mSelection.keySet()) { + ids[i++] = getMasterKeyId(pos); + } + return ids; + } + + public void removeSelection(int position) { + mSelection.remove(position); + notifyDataSetChanged(); + } + + public void clearSelection() { + mSelection.clear(); + notifyDataSetChanged(); + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index cb02d4b6b..56dd15a8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -94,6 +94,7 @@ public class KeyAdapter extends CursorAdapter { public static class KeyItemViewHolder { public View mView; + public View mLayoutDummy; public View mLayoutData; public Long mMasterKeyId; public TextView mMainUserId; @@ -108,6 +109,7 @@ public class KeyAdapter extends CursorAdapter { public KeyItemViewHolder(View view) { mView = view; mLayoutData = view.findViewById(R.id.key_list_item_data); + mLayoutDummy = view.findViewById(R.id.key_list_item_dummy); mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name); mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email); mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); @@ -117,6 +119,10 @@ public class KeyAdapter extends CursorAdapter { } public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) { + + mLayoutData.setVisibility(View.VISIBLE); + mLayoutDummy.setVisibility(View.GONE); + mDisplayedItem = item; { // set name and stuff, common to both key types @@ -201,8 +207,25 @@ public class KeyAdapter extends CursorAdapter { } else { mCreationDate.setVisibility(View.GONE); } + } + } + + /** Shows the "you have no keys yet" dummy view, and sets an OnClickListener. */ + public void setDummy(OnClickListener listener) { + + // just reset everything to display the dummy layout + mLayoutDummy.setVisibility(View.VISIBLE); + mLayoutData.setVisibility(View.GONE); + mSlinger.setVisibility(View.GONE); + mStatus.setVisibility(View.GONE); + mView.setClickable(false); + + mLayoutDummy.setOnClickListener(listener); + + } + } public boolean isEnabled(Cursor cursor) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java deleted file mode 100644 index 1ae5ae61a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java +++ /dev/null @@ -1,553 +0,0 @@ -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import android.graphics.PorterDuff; -import android.support.v4.content.ContextCompat; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.adapter.*; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class KeySectionedListAdapter extends SectionCursorAdapter { - - private static final short VIEW_ITEM_TYPE_KEY = 0x0; - private static final short VIEW_ITEM_TYPE_DUMMY = 0x1; - - private static final short VIEW_SECTION_TYPE_PRIVATE = 0x0; - private static final short VIEW_SECTION_TYPE_PUBLIC = 0x1; - - private String mQuery; - private List mSelected; - private KeyListListener mListener; - - private boolean mHasDummy = false; - - public KeySectionedListAdapter(Context context, Cursor cursor) { - super(context, KeyListCursor.wrap(cursor, KeyListCursor.class), 0); - - mQuery = ""; - mSelected = new ArrayList<>(); - } - - public void setSearchQuery(String query) { - mQuery = query; - } - - - @Override - public void onContentChanged() { - mHasDummy = false; - mSelected.clear(); - - if(mListener != null) { - mListener.onSelectionStateChanged(0); - } - - super.onContentChanged(); - } - - @Override - public KeyListCursor swapCursor(KeyListCursor cursor) { - if (cursor != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { - boolean isSecret = cursor.moveToFirst() && cursor.isSecret(); - - if (!isSecret) { - MatrixCursor headerCursor = new MatrixCursor(KeyListCursor.PROJECTION); - Long[] row = new Long[KeyListCursor.PROJECTION.length]; - row[cursor.getColumnIndex(KeychainContract.KeyRings.HAS_ANY_SECRET)] = 1L; - row[cursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID)] = 0L; - headerCursor.addRow(row); - - Cursor[] toMerge = { - headerCursor, - cursor.getWrappedCursor() - }; - - cursor = KeyListCursor.wrap(new MergeCursor(toMerge)); - } - } - - return super.swapCursor(cursor); - } - - public void setKeyListener(KeyListListener listener) { - mListener = listener; - } - - private int getSelectedCount() { - return mSelected.size(); - } - - private void selectPosition(int position) { - mSelected.add(position); - notifyItemChanged(position); - } - - private void deselectPosition(int position) { - mSelected.remove(Integer.valueOf(position)); - notifyItemChanged(position); - } - - private boolean isSelected(int position) { - return mSelected.contains(position); - } - - public long[] getSelectedMasterKeyIds() { - long[] keys = new long[mSelected.size()]; - for(int i = 0; i < keys.length; i++) { - int index = getCursorPositionWithoutSections(mSelected.get(i)); - if(!moveCursor(index)) { - return keys; - } - - keys[i] = getIdFromCursor(getCursor()); - } - - return keys; - } - - public boolean isAnySecretKeySelected() { - for(int i = 0; i < mSelected.size(); i++) { - int index = getCursorPositionWithoutSections(mSelected.get(i)); - if(!moveCursor(index)) { - return false; - } - - if(getCursor().isSecret()) { - return true; - } - } - - return false; - } - - - /** - * Returns the number of database entries displayed. - * @return The item count - */ - public int getCount() { - if (getCursor() != null) { - return getCursor().getCount() - (mHasDummy ? 1 : 0); - } else { - return 0; - } - } - - @Override - public long getIdFromCursor(KeyListCursor cursor) { - return cursor.getKeyId(); - } - - @Override - protected Character getSectionFromCursor(KeyListCursor cursor) throws IllegalStateException { - if (cursor.isSecret()) { - if (cursor.getKeyId() == 0L) { - mHasDummy = true; - } - - return '#'; - } else { - String userId = cursor.getRawUserId(); - if(TextUtils.isEmpty(userId)) { - return '?'; - } else { - return Character.toUpperCase(userId.charAt(0)); - } - } - } - - @Override - protected short getSectionHeaderViewType(int sectionIndex) { - return (sectionIndex < 1) ? - VIEW_SECTION_TYPE_PRIVATE : - VIEW_SECTION_TYPE_PUBLIC; - } - - @Override - protected short getSectionItemViewType(int position) { - if (moveCursor(position)) { - KeyListCursor c = getCursor(); - - if (c.isSecret() && c.getKeyId() == 0L) { - return VIEW_ITEM_TYPE_DUMMY; - } - } else { - Log.w(Constants.TAG, "Unable to determine key view type. " - + "Reason: Could not move cursor over dataset."); - } - - return VIEW_ITEM_TYPE_KEY; - } - - @Override - protected KeyHeaderViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_SECTION_TYPE_PUBLIC: - return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_header_public, parent, false)); - - case VIEW_SECTION_TYPE_PRIVATE: - return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_header_private, parent, false)); - - default: - return null; - } - } - - @Override - protected ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_ITEM_TYPE_KEY: - return new KeyItemViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_item, parent, false)); - - case VIEW_ITEM_TYPE_DUMMY: - return new KeyDummyViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_dummy, parent, false)); - - default: - return null; - } - - } - - @Override - protected void onBindSectionViewHolder(KeyHeaderViewHolder holder, Character section) { - switch (holder.getItemViewTypeWithoutSections()) { - case VIEW_SECTION_TYPE_PUBLIC: { - String title = section.equals('?') ? - getContext().getString(R.string.user_id_no_name) : - String.valueOf(section); - - holder.bind(title); - break; - } - - case VIEW_SECTION_TYPE_PRIVATE: { - int count = getCount(); - String title = getContext().getResources() - .getQuantityString(R.plurals.n_keys, count, count); - holder.bind(title); - break; - } - - } - } - - @Override - protected void onBindItemViewHolder(ViewHolder holder, KeyListCursor cursor) { - if (holder.getItemViewTypeWithoutSections() == VIEW_ITEM_TYPE_KEY) { - Highlighter highlighter = new Highlighter(getContext(), mQuery); - ((KeyItemViewHolder) holder).bindKey(cursor, highlighter); - } - } - - public void finishSelection() { - Integer[] selected = mSelected.toArray( - new Integer[mSelected.size()] - ); - - mSelected.clear(); - - for(int i = 0; i < selected.length; i++) { - notifyItemChanged(selected[i]); - } - } - - private class KeyDummyViewHolder extends SectionCursorAdapter.ViewHolder - implements View.OnClickListener{ - - KeyDummyViewHolder(View itemView) { - super(itemView); - - itemView.setClickable(true); - itemView.setOnClickListener(this); - itemView.setEnabled(getSelectedCount() == 0); - } - - @Override - public void onClick(View view) { - if(mListener != null) { - mListener.onKeyDummyItemClicked(); - } - } - } - - public class KeyItemViewHolder extends SectionCursorAdapter.ViewHolder - implements View.OnClickListener, View.OnLongClickListener { - - private TextView mMainUserId; - private TextView mMainUserIdRest; - private TextView mCreationDate; - private ImageView mStatus; - private View mSlinger; - private ImageButton mSlingerButton; - - KeyItemViewHolder(View itemView) { - super(itemView); - - mMainUserId = (TextView) itemView.findViewById(R.id.key_list_item_name); - mMainUserIdRest = (TextView) itemView.findViewById(R.id.key_list_item_email); - mStatus = (ImageView) itemView.findViewById(R.id.key_list_item_status_icon); - mSlinger = itemView.findViewById(R.id.key_list_item_slinger_view); - mSlingerButton = (ImageButton) itemView.findViewById(R.id.key_list_item_slinger_button); - mCreationDate = (TextView) itemView.findViewById(R.id.key_list_item_creation); - - itemView.setClickable(true); - itemView.setLongClickable(true); - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); - - mSlingerButton.setClickable(true); - mSlingerButton.setOnClickListener(this); - } - - void bindKey(KeyListCursor keyItem, Highlighter highlighter) { - itemView.setSelected(isSelected(getAdapterPosition())); - Context context = itemView.getContext(); - - { // set name and stuff, common to both key types - OpenPgpUtils.UserId userIdSplit = keyItem.getUserId(); - if (userIdSplit.name != null) { - mMainUserId.setText(highlighter.highlight(userIdSplit.name)); - } else { - mMainUserId.setText(R.string.user_id_no_name); - } - if (userIdSplit.email != null) { - mMainUserIdRest.setText(highlighter.highlight(userIdSplit.email)); - mMainUserIdRest.setVisibility(View.VISIBLE); - } else { - mMainUserIdRest.setVisibility(View.GONE); - } - } - - { // set edit button and status, specific by key type. Note: order is important! - int textColor; - if (keyItem.isRevoked()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - null, - KeyFormattingUtils.State.REVOKED, - R.color.key_flag_gray - ); - - mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (keyItem.isExpired()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - null, - KeyFormattingUtils.State.EXPIRED, - R.color.key_flag_gray - ); - - mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (keyItem.isSecret()) { - mStatus.setVisibility(View.GONE); - if (mSlingerButton.hasOnClickListeners()) { - mSlingerButton.setColorFilter( - FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText), - PorterDuff.Mode.SRC_IN - ); - - mSlinger.setVisibility(View.VISIBLE); - } else { - mSlinger.setVisibility(View.GONE); - } - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } else { - // this is a public key - show if it's verified - if (keyItem.isVerified()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - KeyFormattingUtils.State.VERIFIED - ); - - mStatus.setVisibility(View.VISIBLE); - } else { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - KeyFormattingUtils.State.UNVERIFIED - ); - - mStatus.setVisibility(View.VISIBLE); - } - mSlinger.setVisibility(View.GONE); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - mMainUserId.setTextColor(textColor); - mMainUserIdRest.setTextColor(textColor); - - if (keyItem.hasDuplicate()) { - String dateTime = DateUtils.formatDateTime(context, - keyItem.getCreationTime(), - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - mCreationDate.setText(context.getString(R.string.label_key_created, - dateTime)); - mCreationDate.setTextColor(textColor); - mCreationDate.setVisibility(View.VISIBLE); - } else { - mCreationDate.setVisibility(View.GONE); - } - - } - } - - @Override - public void onClick(View v) { - int pos = getAdapterPosition(); - switch (v.getId()) { - case R.id.key_list_item_slinger_button: - if (mListener != null) { - mListener.onSlingerButtonClicked(getItemId()); - } - break; - - default: - if (getSelectedCount() == 0) { - if (mListener != null) { - mListener.onKeyItemClicked(getItemId()); - } - } else { - if (isSelected(pos)) { - deselectPosition(pos); - } else { - selectPosition(pos); - } - - if (mListener != null) { - mListener.onSelectionStateChanged(getSelectedCount()); - } - } - break; - } - - } - - @Override - public boolean onLongClick(View v) { - System.out.println("Long Click!"); - if (getSelectedCount() == 0) { - selectPosition(getAdapterPosition()); - - if (mListener != null) { - mListener.onSelectionStateChanged(getSelectedCount()); - } - return true; - } - - return false; - } - } - - static class KeyHeaderViewHolder extends SectionCursorAdapter.ViewHolder { - private TextView mText1; - - public KeyHeaderViewHolder(View itemView) { - super(itemView); - mText1 = (TextView) itemView.findViewById(android.R.id.text1); - } - - public void bind(String title) { - mText1.setText(title); - } - } - - public static class KeyListCursor extends CursorAdapter.KeyCursor { - public static final String ORDER = KeychainContract.KeyRings.HAS_ANY_SECRET - + " DESC, " + KeychainContract.KeyRings.USER_ID + " COLLATE NOCASE ASC"; - - public static final String[] PROJECTION; - - static { - ArrayList arr = new ArrayList<>(); - arr.addAll(Arrays.asList(KeyCursor.PROJECTION)); - arr.addAll(Arrays.asList( - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.HAS_ENCRYPT - )); - - PROJECTION = arr.toArray(new String[arr.size()]); - } - - public static KeyListCursor wrap(Cursor cursor) { - if (cursor != null) { - return new KeyListCursor(cursor); - } else { - return null; - } - } - - private KeyListCursor(Cursor cursor) { - super(cursor); - } - - public boolean hasEncrypt() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT); - return getInt(index) != 0; - } - - public byte[] getRawFingerprint() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.FINGERPRINT); - return getBlob(index); - } - - public String getFingerprint() { - return KeyFormattingUtils.convertFingerprintToHex(getRawFingerprint()); - } - - public boolean isSecret() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ANY_SECRET); - return getInt(index) != 0; - } - - public boolean isVerified() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.VERIFIED); - return getInt(index) > 0; - } - } - - public interface KeyListListener { - void onKeyDummyItemClicked(); - void onKeyItemClicked(long masterKeyId); - void onSlingerButtonClicked(long masterKeyId); - void onSelectionStateChanged(int selectedCount); - } -} diff --git a/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml b/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml deleted file mode 100644 index 32d726ac1..000000000 --- a/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/drawable/list_item_ripple.xml b/OpenKeychain/src/main/res/drawable/list_item_ripple.xml deleted file mode 100644 index ae8972031..000000000 --- a/OpenKeychain/src/main/res/drawable/list_item_ripple.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/key_list_dummy.xml b/OpenKeychain/src/main/res/layout/key_list_dummy.xml deleted file mode 100644 index afdd88f0c..000000000 --- a/OpenKeychain/src/main/res/layout/key_list_dummy.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/key_list_fragment.xml b/OpenKeychain/src/main/res/layout/key_list_fragment.xml index 05a32efaf..6aaf5be25 100644 --- a/OpenKeychain/src/main/res/layout/key_list_fragment.xml +++ b/OpenKeychain/src/main/res/layout/key_list_fragment.xml @@ -1,144 +1,117 @@ - - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - + + android:layout_height="match_parent"> - - - - - - - - + android:layout_height="match_parent" + android:drawSelectorOnTop="true" + android:fastScrollEnabled="true" + android:paddingLeft="16dp" + android:paddingRight="32dp" + android:scrollbarStyle="outsideOverlay" /> - + - + android:text="@string/key_list_empty_text1" + android:textAppearance="?android:attr/textAppearanceLarge" /> - + + + +