diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index 4d4219f56..f10fb6ef2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -18,10 +18,6 @@ package org.sufficientlysecure.keychain.ui; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - import android.Manifest; import android.app.Activity; import android.content.ContentResolver; @@ -31,15 +27,17 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; -import android.support.v4.app.ListFragment; +import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.support.v4.util.LongSparseArray; -import android.view.MotionEvent; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnTouchListener; -import android.widget.ListView; +import android.view.ViewGroup; +import android.widget.ProgressBar; import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; @@ -58,7 +56,11 @@ import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; -public class ImportKeysListFragment extends ListFragment implements +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class ImportKeysListFragment extends Fragment implements LoaderManager.LoaderCallbacks>> { private static final String ARG_DATA_URI = "uri"; @@ -70,16 +72,18 @@ public class ImportKeysListFragment extends ListFragment implements private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private Activity mActivity; - private ImportKeysAdapter mAdapter; private ParcelableProxy mParcelableProxy; + private ProgressBar mProgressBar; + private RecyclerView mRecyclerView; + private ImportKeysAdapter mAdapter; + private LoaderState mLoaderState; private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_CLOUD = 1; private LongSparseArray mCachedKeyData; - private boolean mNonInteractive; private boolean mShowingOrbotDialog; @@ -206,39 +210,26 @@ public class ImportKeysListFragment extends ListFragment implements } } - /** - * Define Adapter and Loader on create of Activity - */ @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.import_keys_list_fragment, container, false); mActivity = getActivity(); - // Give some text to display if there is no data. - setEmptyText(mActivity.getString(R.string.error_nothing_import)); - - // Create an empty adapter we will use to display the loaded data. - mAdapter = new ImportKeysAdapter(mActivity); - setListAdapter(mAdapter); - Bundle args = getArguments(); Uri dataUri = args.getParcelable(ARG_DATA_URI); byte[] bytes = args.getByteArray(ARG_BYTES); String query = args.getString(ARG_SERVER_QUERY); - mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); + boolean nonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); - getListView().setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (!mAdapter.isEmpty()) { - mActivity.onTouchEvent(event); - } - return false; - } - }); + mProgressBar = (ProgressBar) view.findViewById(R.id.progress_view); + mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); + RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(mActivity); + mRecyclerView.setLayoutManager(mLayoutManager); - getListView().setFastScrollEnabled(true); + // Create an empty adapter we will use to display the loaded data. + mAdapter = new ImportKeysAdapter(mActivity, nonInteractive); + mRecyclerView.setAdapter(mAdapter); if (dataUri != null || bytes != null) { mLoaderState = new BytesLoaderState(bytes, dataUri); @@ -252,23 +243,25 @@ public class ImportKeysListFragment extends ListFragment implements mLoaderState = new CloudLoaderState(query, cloudSearchPrefs); } - if (dataUri != null && ! checkAndRequestReadPermission(dataUri)) { - return; + if (dataUri != null && !checkAndRequestReadPermission(dataUri)) { + return view; } restartLoaders(); + + return view; } /** * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. - * + *

* This method returns true on Android < 6, or if permission is already granted. It * requests the permission and returns false otherwise. - * + *

* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ private boolean checkAndRequestReadPermission(final Uri uri) { - if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return true; } @@ -312,30 +305,13 @@ public class ImportKeysListFragment extends ListFragment implements } } - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - - if (mNonInteractive) { - return; - } - - // Select checkbox! - // Update underlying data and notify adapter of change. The adapter will - // update the view automatically. - - ImportKeysListEntry entry = mAdapter.getItem(position); - entry.setSelected(!entry.isSelected()); - mAdapter.notifyDataSetChanged(); - } - public void loadNew(LoaderState loaderState) { mLoaderState = loaderState; if (mLoaderState instanceof BytesLoaderState) { BytesLoaderState ls = (BytesLoaderState) mLoaderState; - if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { + if (ls.mDataUri != null && !checkAndRequestReadPermission(ls.mDataUri)) { return; } } @@ -344,69 +320,67 @@ public class ImportKeysListFragment extends ListFragment implements } public void destroyLoader() { - if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) { - getLoaderManager().destroyLoader(LOADER_ID_BYTES); + LoaderManager loaderManager = getLoaderManager(); + + if (loaderManager.getLoader(LOADER_ID_BYTES) != null) { + loaderManager.destroyLoader(LOADER_ID_BYTES); } - if (getLoaderManager().getLoader(LOADER_ID_CLOUD) != null) { - getLoaderManager().destroyLoader(LOADER_ID_CLOUD); - } - if (getView() != null) { - setListShown(true); + if (loaderManager.getLoader(LOADER_ID_CLOUD) != null) { + loaderManager.destroyLoader(LOADER_ID_CLOUD); } } private void restartLoaders() { + LoaderManager loaderManager = getLoaderManager(); + if (mLoaderState instanceof BytesLoaderState) { - // Start out with a progress indicator. - setListShown(false); - - getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this); + loaderManager.restartLoader(LOADER_ID_BYTES, null, this); } else if (mLoaderState instanceof CloudLoaderState) { - // Start out with a progress indicator. - setListShown(false); - - getLoaderManager().restartLoader(LOADER_ID_CLOUD, null, this); + loaderManager.restartLoader(LOADER_ID_CLOUD, null, this); } } @Override - public Loader>> - onCreateLoader(int id, Bundle args) { + public Loader>> onCreateLoader( + int id, + Bundle args + ) { + + Loader>> loader; switch (id) { case LOADER_ID_BYTES: { - return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); + loader = new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); + break; } case LOADER_ID_CLOUD: { CloudLoaderState ls = (CloudLoaderState) mLoaderState; - return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs, - mParcelableProxy); + loader = new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, + ls.mCloudPrefs, mParcelableProxy); + break; } - default: - return null; + loader = null; } + + if (loader != null) { + mRecyclerView.setVisibility(View.GONE); + mProgressBar.setVisibility(View.VISIBLE); + } + + return loader; } @Override - public void onLoadFinished(Loader>> loader, - AsyncTaskResultWrapper> data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) + public void onLoadFinished( + Loader>> loader, + AsyncTaskResultWrapper> data + ) { - Log.d(Constants.TAG, "data: " + data.getResult()); - - // swap in the real data! mAdapter.setData(data.getResult()); mAdapter.notifyDataSetChanged(); - setListAdapter(mAdapter); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } + mRecyclerView.setVisibility(View.VISIBLE); + mProgressBar.setVisibility(View.GONE); // free old cached key data mCachedKeyData = null; @@ -414,7 +388,6 @@ public class ImportKeysListFragment extends ListFragment implements GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult(); switch (loader.getId()) { case LOADER_ID_BYTES: - if (getKeyResult.success()) { // No error mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); @@ -424,7 +397,6 @@ public class ImportKeysListFragment extends ListFragment implements break; case LOADER_ID_CLOUD: - if (getKeyResult.success()) { // No error } else if (getKeyResult.isPending()) { @@ -488,11 +460,11 @@ public class ImportKeysListFragment extends ListFragment implements switch (loader.getId()) { case LOADER_ID_BYTES: // Clear the data in the adapter. - mAdapter.clear(); + mAdapter.clearData(); break; case LOADER_ID_CLOUD: // Clear the data in the adapter. - mAdapter.clear(); + mAdapter.clearData(); break; default: break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index a6d311134..360f3782f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -17,16 +17,14 @@ package org.sufficientlysecure.keychain.ui.adapter; -import android.annotation.TargetApi; -import android.app.Activity; import android.content.Context; import android.graphics.Color; -import android.os.Build; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -47,13 +45,18 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -public class ImportKeysAdapter extends ArrayAdapter { - protected LayoutInflater mInflater; - protected Activity mActivity; +public class ImportKeysAdapter extends RecyclerView.Adapter { - protected List mData; + private Context mContext; + private boolean mNonInteractive; + private List mData; - static class ViewHolder { + public ImportKeysAdapter(Context mContext, boolean mNonInteractive) { + this.mContext = mContext; + this.mNonInteractive = mNonInteractive; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { public TextView mainUserId; public TextView mainUserIdRest; public TextView keyId; @@ -63,38 +66,29 @@ public class ImportKeysAdapter extends ArrayAdapter { public View userIdsDivider; public LinearLayout userIdsList; public CheckBox checkBox; - } - public ImportKeysAdapter(Activity activity) { - super(activity, -1); - mActivity = activity; - mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void setData(List data) { - - clear(); - if (data != null) { - this.mData = data; - - // add data to extended ArrayAdapter - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - addAll(data); - } else { - for (ImportKeysListEntry entry : data) { - add(entry); - } - } + public ViewHolder(View itemView) { + super(itemView); } } + public void clearData() { + mData = null; + notifyDataSetChanged(); + } + + public void setData(List data) { + this.mData = data; + } + public List getData() { return mData; } - /** This method returns a list of all selected entries, with public keys sorted + /** + * This method returns a list of all selected entries, with public keys sorted * before secret keys, see ImportOperation for specifics. + * * @see ImportOperation */ public ArrayList getSelectedEntries() { @@ -115,30 +109,27 @@ public class ImportKeysAdapter extends ArrayAdapter { } @Override - public boolean hasStableIds() { - return true; + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.import_keys_list_item, parent, false); + + ViewHolder vh = new ViewHolder(v); + vh.mainUserId = (TextView) v.findViewById(R.id.import_item_user_id); + vh.mainUserIdRest = (TextView) v.findViewById(R.id.import_item_user_id_email); + vh.keyId = (TextView) v.findViewById(R.id.import_item_key_id); + vh.fingerprint = (TextView) v.findViewById(R.id.import_item_fingerprint); + vh.algorithm = (TextView) v.findViewById(R.id.import_item_algorithm); + vh.status = (ImageView) v.findViewById(R.id.import_item_status); + vh.userIdsDivider = v.findViewById(R.id.import_item_status_divider); + vh.userIdsList = (LinearLayout) v.findViewById(R.id.import_item_user_ids_list); + vh.checkBox = (CheckBox) v.findViewById(R.id.import_item_selected); + + return vh; } - public View getView(int position, View convertView, ViewGroup parent) { - ImportKeysListEntry entry = mData.get(position); - Highlighter highlighter = new Highlighter(mActivity, entry.getQuery()); - ViewHolder holder; - if (convertView == null) { - holder = new ViewHolder(); - convertView = mInflater.inflate(R.layout.import_keys_list_item, null); - holder.mainUserId = (TextView) convertView.findViewById(R.id.import_item_user_id); - holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.import_item_user_id_email); - holder.keyId = (TextView) convertView.findViewById(R.id.import_item_key_id); - holder.fingerprint = (TextView) convertView.findViewById(R.id.import_item_fingerprint); - holder.algorithm = (TextView) convertView.findViewById(R.id.import_item_algorithm); - holder.status = (ImageView) convertView.findViewById(R.id.import_item_status); - holder.userIdsDivider = convertView.findViewById(R.id.import_item_status_divider); - holder.userIdsList = (LinearLayout) convertView.findViewById(R.id.import_item_user_ids_list); - holder.checkBox = (CheckBox) convertView.findViewById(R.id.import_item_selected); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - } + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + final ImportKeysListEntry entry = mData.get(position); + Highlighter highlighter = new Highlighter(mContext, entry.getQuery()); // main user id String userId = entry.getUserIds().get(0); @@ -148,8 +139,7 @@ public class ImportKeysAdapter extends ArrayAdapter { if (userIdSplit.name != null) { // show red user id if it is a secret key if (entry.isSecretKey()) { - holder.mainUserId.setText(mActivity.getString(R.string.secret_key) - + " " + userIdSplit.name); + holder.mainUserId.setText(mContext.getString(R.string.secret_key) + " " + userIdSplit.name); } else { holder.mainUserId.setText(highlighter.highlight(userIdSplit.name)); } @@ -165,7 +155,7 @@ public class ImportKeysAdapter extends ArrayAdapter { holder.mainUserIdRest.setVisibility(View.GONE); } - holder.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(getContext(), entry.getKeyIdHex())); + holder.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(mContext, entry.getKeyIdHex())); // don't show full fingerprint on key import holder.fingerprint.setVisibility(View.GONE); @@ -178,9 +168,9 @@ public class ImportKeysAdapter extends ArrayAdapter { } if (entry.isRevoked()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.key_flag_gray); + KeyFormattingUtils.setStatusImage(mContext, holder.status, null, State.REVOKED, R.color.key_flag_gray); } else if (entry.isExpired()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray); + KeyFormattingUtils.setStatusImage(mContext, holder.status, null, State.EXPIRED, R.color.key_flag_gray); } if (entry.isRevoked() || entry.isExpired()) { @@ -189,9 +179,9 @@ public class ImportKeysAdapter extends ArrayAdapter { // no more space for algorithm display holder.algorithm.setVisibility(View.GONE); - holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); - holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); - holder.keyId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); + holder.mainUserId.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); + holder.mainUserIdRest.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); + holder.keyId.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); } else { holder.status.setVisibility(View.GONE); holder.algorithm.setVisibility(View.VISIBLE); @@ -199,11 +189,11 @@ public class ImportKeysAdapter extends ArrayAdapter { if (entry.isSecretKey()) { holder.mainUserId.setTextColor(Color.RED); } else { - holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); + holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); } - holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); - holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); + holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); + holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); } if (entry.getUserIds().size() == 1) { @@ -237,31 +227,33 @@ public class ImportKeysAdapter extends ArrayAdapter { String cUserId = pair.getKey(); HashSet cEmails = pair.getValue(); - TextView uidView = (TextView) mInflater.inflate( + LayoutInflater inflater = LayoutInflater.from(mContext); + + TextView uidView = (TextView) inflater.inflate( R.layout.import_keys_list_entry_user_id, null); uidView.setText(highlighter.highlight(cUserId)); - uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0); + uidView.setPadding(0, 0, FormattingUtils.dpToPx(mContext, 8), 0); if (entry.isRevoked() || entry.isExpired()) { - uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); + uidView.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); } else { - uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); + uidView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); } holder.userIdsList.addView(uidView); for (String email : cEmails) { - TextView emailView = (TextView) mInflater.inflate( + TextView emailView = (TextView) inflater.inflate( R.layout.import_keys_list_entry_user_id, null); emailView.setPadding( - FormattingUtils.dpToPx(getContext(), 16), 0, - FormattingUtils.dpToPx(getContext(), 8), 0); + FormattingUtils.dpToPx(mContext, 16), 0, + FormattingUtils.dpToPx(mContext, 8), 0); emailView.setText(highlighter.highlight(email)); if (entry.isRevoked() || entry.isExpired()) { - emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); + emailView.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); } else { - emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); + emailView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); } holder.userIdsList.addView(emailView); @@ -270,8 +262,19 @@ public class ImportKeysAdapter extends ArrayAdapter { } holder.checkBox.setChecked(entry.isSelected()); + holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (!mNonInteractive) { + entry.setSelected(isChecked); + } + } + }); + } - return convertView; + @Override + public int getItemCount() { + return mData != null ? mData.size() : 0; } } diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_list_fragment.xml new file mode 100644 index 000000000..2f8bad47c --- /dev/null +++ b/OpenKeychain/src/main/res/layout/import_keys_list_fragment.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_item.xml b/OpenKeychain/src/main/res/layout/import_keys_list_item.xml index e0b19b608..76b68ecea 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_list_item.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_list_item.xml @@ -1,26 +1,21 @@ + android:singleLine="true"> - + android:paddingRight="8dp" + android:paddingTop="2dp" /> + android:padding="16dp" + android:src="@drawable/status_signature_revoked_cutout_24dp" /> + android:background="?android:attr/listDivider" + android:gravity="right" /> + android:textAppearance="?android:attr/textAppearanceSmall" + android:typeface="monospace" />