ImportKeys: Add expanding card for list's item (WIP)

This commit is contained in:
Andrea Torlaschi
2016-07-18 23:17:53 +02:00
parent 83673adeb9
commit 68b015122b
4 changed files with 178 additions and 202 deletions

View File

@@ -31,7 +31,6 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver; import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState; import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState; import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
@@ -51,7 +50,6 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
public class ImportKeysActivity extends BaseActivity implements ImportKeysListener { public class ImportKeysActivity extends BaseActivity implements ImportKeysListener {
@@ -83,7 +81,6 @@ public class ImportKeysActivity extends BaseActivity implements ImportKeysListen
public static final String TAG_FRAG_TOP = "frag_top"; public static final String TAG_FRAG_TOP = "frag_top";
private boolean mFreshIntent; private boolean mFreshIntent;
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
@Override @Override
@@ -370,8 +367,9 @@ public class ImportKeysActivity extends BaseActivity implements ImportKeysListen
keyserver = ((CloudLoaderState) loaderState).mCloudPrefs.keyserver; keyserver = ((CloudLoaderState) loaderState).mCloudPrefs.keyserver;
} }
ImportKeysOperationCallback callback = new ImportKeysOperationCallback(this, keyserver, keyList); ImportKeysOperationCallback cb = new ImportKeysOperationCallback(this, keyserver, keyList);
new CryptoOperationHelper(1, this, callback, R.string.progress_importing).cryptoOperation(); mOperationHelper = new CryptoOperationHelper(1, this, cb, R.string.progress_importing);
mOperationHelper.cryptoOperation();
} }
@Override @Override

View File

@@ -18,18 +18,18 @@
package org.sufficientlysecure.keychain.ui.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.databinding.DataBindingUtil;
import android.graphics.Color; import android.graphics.Color;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState; import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
@@ -60,28 +60,17 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
private List<ImportKeysListEntry> mData; private List<ImportKeysListEntry> mData;
public ImportKeysAdapter(Context mContext, ImportKeysListener listener, boolean mNonInteractive) { public ImportKeysAdapter(Context mContext, ImportKeysListener listener, boolean mNonInteractive) {
this.mContext = mContext; this.mContext = mContext;
this.mListener = listener; this.mListener = listener;
this.mNonInteractive = mNonInteractive; this.mNonInteractive = mNonInteractive;
} }
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
public View container; public ImportKeysListItemBinding binding;
public TextView mainUserId; public ViewHolder(View view) {
public TextView mainUserIdRest; super(view);
public TextView keyId; binding = DataBindingUtil.bind(view);
public TextView fingerprint;
public TextView algorithm;
public ImageView status;
public View userIdsDivider;
public LinearLayout userIdsList;
public Button importButton;
public ViewHolder(View container) {
super(container);
this.container = container;
} }
} }
@@ -122,99 +111,69 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
@Override @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.import_keys_list_item, parent, false); LayoutInflater inflater = LayoutInflater.from(mContext);
View v = inflater.inflate(R.layout.import_keys_list_item, parent, false);
ViewHolder vh = new ViewHolder(v); 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.importButton = (Button) v.findViewById(R.id.import_item_button);
return vh; return vh;
} }
@Override @Override
public void onBindViewHolder(ViewHolder holder, int position) { public void onBindViewHolder(final ViewHolder holder, int position) {
final ImportKeysListItemBinding b = holder.binding;
final ImportKeysListEntry entry = mData.get(position); final ImportKeysListEntry entry = mData.get(position);
Resources resources = mContext.getResources();
Highlighter highlighter = new Highlighter(mContext, entry.getQuery());
b.setStandardColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
b.setRevokedExpiredColor(resources.getColor(R.color.key_flag_gray));
b.setSecretColor(Color.RED);
b.setHighlighter(highlighter);
b.setSecret(entry.isSecretKey());
b.setExpired(entry.isExpired());
b.setRevoked(entry.isRevoked());
String userId = entry.getUserIds().get(0); // main user id String userId = entry.getUserIds().get(0); // main user id
OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId);
Highlighter highlighter = new Highlighter(mContext, entry.getQuery()); b.setAlgorithm(entry.getAlgorithm());
// name b.setUserId(userIdSplit.name);
if (userIdSplit.name != null) { b.setUserIdEmail(userIdSplit.email);
// show red user id if it is a secret key b.setKeyId(KeyFormattingUtils.beautifyKeyIdWithPrefix(mContext, entry.getKeyIdHex()));
if (entry.isSecretKey()) {
holder.mainUserId.setText(mContext.getString(R.string.secret_key) + " " + userIdSplit.name);
} else {
holder.mainUserId.setText(highlighter.highlight(userIdSplit.name));
}
} else {
holder.mainUserId.setText(R.string.user_id_no_name);
}
// email
if (userIdSplit.email != null) {
holder.mainUserIdRest.setVisibility(View.VISIBLE);
holder.mainUserIdRest.setText(highlighter.highlight(userIdSplit.email));
} else {
holder.mainUserIdRest.setVisibility(View.GONE);
}
holder.keyId.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix(mContext, entry.getKeyIdHex()));
// don't show full fingerprint on key import
holder.fingerprint.setVisibility(View.GONE);
if (entry.getAlgorithm() != null) {
holder.algorithm.setText(entry.getAlgorithm());
holder.algorithm.setVisibility(View.VISIBLE);
} else {
holder.algorithm.setVisibility(View.GONE);
}
if (entry.isRevoked()) { if (entry.isRevoked()) {
KeyFormattingUtils.setStatusImage(mContext, holder.status, null, State.REVOKED, R.color.key_flag_gray); KeyFormattingUtils.setStatusImage(mContext, b.status, null, State.REVOKED, R.color.key_flag_gray);
} else if (entry.isExpired()) { } else if (entry.isExpired()) {
KeyFormattingUtils.setStatusImage(mContext, holder.status, null, State.EXPIRED, R.color.key_flag_gray); KeyFormattingUtils.setStatusImage(mContext, b.status, null, State.EXPIRED, R.color.key_flag_gray);
} }
if (entry.isRevoked() || entry.isExpired()) { b.importKey.setOnClickListener(new View.OnClickListener() {
holder.status.setVisibility(View.VISIBLE); @Override
public void onClick(View v) {
// no more space for algorithm display if (mLoaderState instanceof BytesLoaderState) {
holder.algorithm.setVisibility(View.GONE); mListener.importKey(new ParcelableKeyRing(entry.getEncodedRing()));
} else if (mLoaderState instanceof CloudLoaderState) {
holder.mainUserId.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); mListener.importKey(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(),
holder.mainUserIdRest.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); entry.getKeybaseName(), entry.getFbUsername()));
holder.keyId.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray)); }
} else {
holder.status.setVisibility(View.GONE);
holder.algorithm.setVisibility(View.VISIBLE);
if (entry.isSecretKey()) {
holder.mainUserId.setTextColor(Color.RED);
} else {
holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
} }
});
holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); b.expand.setOnClickListener(new View.OnClickListener() {
holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); @Override
} public void onClick(View v) {
boolean hidden = b.extraContainer.getVisibility() == View.GONE;
b.extraContainer.setVisibility(hidden ? View.VISIBLE : View.GONE);
b.expand.animate().rotation(hidden ? 180 : 0).start();
}
});
if (entry.getUserIds().size() == 1) { if (entry.getUserIds().size() == 1) {
holder.userIdsList.setVisibility(View.GONE); b.userIdsList.setVisibility(View.GONE);
holder.userIdsDivider.setVisibility(View.GONE);
} else { } else {
holder.userIdsList.setVisibility(View.VISIBLE); b.userIdsList.setVisibility(View.VISIBLE);
holder.userIdsDivider.setVisibility(View.VISIBLE);
// destroyLoader view from holder // destroyLoader view from holder
holder.userIdsList.removeAllViews(); b.userIdsList.removeAllViews();
// we want conventional gpg UserIDs first, then Keybase ”proofs” // we want conventional gpg UserIDs first, then Keybase ”proofs”
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds(); HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
@@ -250,7 +209,7 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
uidView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); uidView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
} }
holder.userIdsList.addView(uidView); b.userIdsList.addView(uidView);
for (String email : cEmails) { for (String email : cEmails) {
TextView emailView = (TextView) inflater.inflate( TextView emailView = (TextView) inflater.inflate(
@@ -266,22 +225,10 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
emailView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText)); emailView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
} }
holder.userIdsList.addView(emailView); b.userIdsList.addView(emailView);
} }
} }
} }
holder.importButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mLoaderState instanceof BytesLoaderState) {
mListener.importKey(new ParcelableKeyRing(entry.getEncodedRing()));
} else if (mLoaderState instanceof CloudLoaderState) {
mListener.importKey(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(),
entry.getKeybaseName(), entry.getFbUsername()));
}
}
});
} }
@Override @Override

View File

@@ -19,6 +19,9 @@ public class Highlighter {
} }
public Spannable highlight(String text) { public Spannable highlight(String text) {
if (text == null)
return null;
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text); Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
if (mQuery == null) { if (mQuery == null) {

View File

@@ -1,121 +1,149 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
card_view:cardCornerRadius="2dp">
<LinearLayout <data>
<import type="android.view.View" />
<import type="org.sufficientlysecure.keychain.ui.util.Highlighter" />
<variable name="standardColor" type="int" />
<variable name="revokedExpiredColor" type="int" />
<variable name="secretColor" type="int" />
<variable name="highlighter" type="Highlighter" />
<variable name="secret" type="boolean" />
<variable name="revoked" type="boolean" />
<variable name="expired" type="boolean" />
<variable name="algorithm" type="String" />
<variable name="userId" type="String" />
<variable name="userIdEmail" type="String" />
<variable name="keyId" type="String" />
</data>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
card_view:cardCornerRadius="2dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical">
android:paddingBottom="16dp"
android:paddingLeft="16dp" <LinearLayout
android:paddingRight="16dp" android:layout_width="match_parent"
android:paddingTop="24dp"> android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="24dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/status"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{algorithm}"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{secret ? @string/secret_key + " " + (userId ?? @string/user_id_no_name) : highlighter.highlight(userId ?? @string/user_id_no_name)}'
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@{revoked || expired ? revokedExpiredColor : (secret ? secretColor : standardColor)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{highlighter.highlight(userIdEmail)}"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@{revoked || expired ? revokedExpiredColor : standardColor}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{userIdEmail != null ? "Key ID: " + userIdEmail : ""}'
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@{revoked || expired ? revokedExpiredColor : standardColor}" />
</LinearLayout>
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
</RelativeLayout>
</LinearLayout>
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:padding="8dp">
<LinearLayout <Button
android:id="@+id/import_item_header_container" android:id="@+id/import_key"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/import_item_status" android:background="?android:attr/selectableItemBackground"
android:orientation="vertical"> android:minHeight="0dp"
android:minWidth="0dp"
android:padding="8dp"
android:text="@string/btn_import" />
<TextView <ImageButton
android:id="@+id/import_item_user_id" android:id="@+id/expand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Alice"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/import_item_user_id_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="alice@example.com"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/import_item_key_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Key ID: abcd abcd abcd abcd"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:id="@+id/import_item_status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:src="@drawable/status_signature_revoked_cutout_24dp" /> android:background="?android:attr/selectableItemBackground"
android:minHeight="0dp"
android:minWidth="0dp"
android:padding="8dp"
android:src="@drawable/ic_expand_more_black_24dp" />
</RelativeLayout> </RelativeLayout>
<View
android:id="@+id/import_item_status_divider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<LinearLayout <LinearLayout
android:id="@+id/import_item_user_ids_list" android:id="@+id/extra_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<TextView
android:id="@+id/import_item_fingerprint"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="0000 0000 0000 0000 0000\n0000 0000 0000 0000 0000" android:animateLayoutChanges="true"
android:textAppearance="?android:attr/textAppearanceSmall" android:orientation="vertical"
android:typeface="monospace" /> android:paddingBottom="24dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:visibility="gone">
<TextView <LinearLayout
android:id="@+id/import_item_algorithm" android:id="@+id/user_ids_list"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginTop="8dp" android:orientation="vertical" />
android:text="RSA" </LinearLayout>
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout> </LinearLayout>
<LinearLayout </android.support.v7.widget.CardView>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<Button </layout>
android:id="@+id/import_item_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:minHeight="0dp"
android:minWidth="0dp"
android:padding="8dp"
android:text="@string/btn_import" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>