Split up FlexibleKeyItem, re-add support for dummy item if user has no secret keys
This commit is contained in:
parent
377bf55b70
commit
b4ac6cd337
|
@ -453,12 +453,28 @@ public class KeychainDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) {
|
private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) {
|
||||||
db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;");
|
try {
|
||||||
db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;");
|
db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;");
|
||||||
|
db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (Constants.DEBUG) {
|
||||||
|
Timber.e(e, "Ignoring migration exception, this probably happened before");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) {
|
private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) {
|
||||||
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;");
|
try {
|
||||||
|
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (Constants.DEBUG) {
|
||||||
|
Timber.e(e, "Ignoring migration exception, this probably happened before");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
|
|
@ -54,7 +54,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener;
|
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener;
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener;
|
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener;
|
||||||
import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
|
import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
|
||||||
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||||
|
@ -67,8 +66,11 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
|
import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader;
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem;
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory;
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
||||||
|
@ -76,7 +78,6 @@ import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
|
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
|
|
||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
@ -163,7 +164,7 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
||||||
List<Integer> selectedPositions = adapter.getSelectedPositions();
|
List<Integer> selectedPositions = adapter.getSelectedPositions();
|
||||||
long[] keyIds = new long[selectedPositions.size()];
|
long[] keyIds = new long[selectedPositions.size()];
|
||||||
for (int i = 0; i < selectedPositions.size(); i++) {
|
for (int i = 0; i < selectedPositions.size(); i++) {
|
||||||
FlexibleKeyItem selectedItem = adapter.getItem(selectedPositions.get(i));
|
FlexibleKeyDetailsItem selectedItem = adapter.getItem(selectedPositions.get(i), FlexibleKeyDetailsItem.class);
|
||||||
if (selectedItem != null) {
|
if (selectedItem != null) {
|
||||||
keyIds[i] = selectedItem.keyInfo.master_key_id();
|
keyIds[i] = selectedItem.keyInfo.master_key_id();
|
||||||
}
|
}
|
||||||
|
@ -174,7 +175,7 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
||||||
private boolean isAnySecretKeySelected() {
|
private boolean isAnySecretKeySelected() {
|
||||||
FlexibleAdapter<FlexibleKeyItem> adapter = getAdapter();
|
FlexibleAdapter<FlexibleKeyItem> adapter = getAdapter();
|
||||||
for (int position : adapter.getSelectedPositions()) {
|
for (int position : adapter.getSelectedPositions()) {
|
||||||
FlexibleKeyItem item = adapter.getItem(position);
|
FlexibleKeyDetailsItem item = adapter.getItem(position, FlexibleKeyDetailsItem.class);
|
||||||
if (item != null && item.keyInfo.has_any_secret()) {
|
if (item != null && item.keyInfo.has_any_secret()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -296,8 +297,14 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
FlexibleKeyHeader header = item.getHeader();
|
if (item instanceof FlexibleSectionableKeyItem) {
|
||||||
return header.getSectionTitle();
|
FlexibleKeyHeader header = ((FlexibleSectionableKeyItem) item).getHeader();
|
||||||
|
return header.getSectionTitle();
|
||||||
|
}
|
||||||
|
if (item instanceof FlexibleKeyHeader) {
|
||||||
|
return ((FlexibleKeyHeader) item).getSectionTitle();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -404,12 +411,21 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item instanceof FlexibleKeyDummyItem) {
|
||||||
|
createKey();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(item instanceof FlexibleKeyDetailsItem)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (mActionMode != null && position != RecyclerView.NO_POSITION) {
|
if (mActionMode != null && position != RecyclerView.NO_POSITION) {
|
||||||
toggleSelection(position);
|
toggleSelection(position);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
long masterKeyId = item.keyInfo.master_key_id();
|
long masterKeyId = ((FlexibleKeyDetailsItem) item).keyInfo.master_key_id();
|
||||||
Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
|
Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
|
||||||
viewIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
viewIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||||
startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
|
startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
|
||||||
|
@ -418,13 +434,15 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemLongClick(int position) {
|
public void onItemLongClick(int position) {
|
||||||
if (mActionMode == null) {
|
if (getAdapter().getItem(position) instanceof FlexibleKeyDetailsItem) {
|
||||||
FragmentActivity activity = getActivity();
|
if (mActionMode == null) {
|
||||||
if (activity != null) {
|
FragmentActivity activity = getActivity();
|
||||||
mActionMode = activity.startActionMode(mActionCallback);
|
if (activity != null) {
|
||||||
|
mActionMode = activity.startActionMode(mActionCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
toggleSelection(position);
|
||||||
}
|
}
|
||||||
toggleSelection(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleSelection(int position) {
|
private void toggleSelection(int position) {
|
||||||
|
|
|
@ -0,0 +1,237 @@
|
||||||
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
|
import eu.davidea.flexibleadapter.items.IFilterable;
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible;
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem.FlexibleKeyItemViewHolder;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem;
|
||||||
|
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.PackageIconGetter;
|
||||||
|
|
||||||
|
|
||||||
|
public class FlexibleKeyDetailsItem extends FlexibleSectionableKeyItem<FlexibleKeyItemViewHolder>
|
||||||
|
implements IFilterable<String> {
|
||||||
|
public final UnifiedKeyInfo keyInfo;
|
||||||
|
|
||||||
|
FlexibleKeyDetailsItem(UnifiedKeyInfo keyInfo, FlexibleKeyHeader header) {
|
||||||
|
super(header);
|
||||||
|
this.keyInfo = keyInfo;
|
||||||
|
|
||||||
|
setSelectable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutRes() {
|
||||||
|
return R.layout.key_list_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlexibleKeyItemViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
|
||||||
|
return new FlexibleKeyItemViewHolder(view, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindViewHolder(
|
||||||
|
FlexibleAdapter<IFlexible> adapter, FlexibleKeyItemViewHolder holder, int position, List<Object> payloads) {
|
||||||
|
String highlightString = adapter.getFilter(String.class);
|
||||||
|
holder.bind(keyInfo, highlightString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem) {
|
||||||
|
org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem
|
||||||
|
other = (org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem) o;
|
||||||
|
return keyInfo.master_key_id() == other.keyInfo.master_key_id();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
long masterKeyId = keyInfo.master_key_id();
|
||||||
|
return (int) (masterKeyId ^ (masterKeyId >>> 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean filter(String constraint) {
|
||||||
|
String uidList = keyInfo.user_id_list();
|
||||||
|
return constraint == null || (uidList != null && uidList.contains(constraint));
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlexibleKeyItemViewHolder extends FlexibleViewHolder {
|
||||||
|
private static final long JUST_NOW_THRESHOLD = DateUtils.MINUTE_IN_MILLIS * 5;
|
||||||
|
|
||||||
|
private final TextView vMainUserId;
|
||||||
|
private final TextView vMainUserIdRest;
|
||||||
|
private final TextView vCreationDate;
|
||||||
|
private final ImageView vStatusIcon;
|
||||||
|
private final ImageView vTrustIdIcon;
|
||||||
|
|
||||||
|
FlexibleKeyItemViewHolder(View itemView, FlexibleAdapter adapter) {
|
||||||
|
super(itemView, adapter);
|
||||||
|
|
||||||
|
vMainUserId = itemView.findViewById(R.id.key_list_item_name);
|
||||||
|
vMainUserIdRest = itemView.findViewById(R.id.key_list_item_email);
|
||||||
|
vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon);
|
||||||
|
vCreationDate = itemView.findViewById(R.id.key_list_item_creation);
|
||||||
|
vTrustIdIcon = itemView.findViewById(R.id.key_list_item_tid_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(UnifiedKeyInfo keyInfo, String highlightString) {
|
||||||
|
setEnabled(true);
|
||||||
|
|
||||||
|
Context context = itemView.getContext();
|
||||||
|
Highlighter highlighter = new Highlighter(context, highlightString);
|
||||||
|
|
||||||
|
{ // set name and stuff, common to both key types
|
||||||
|
if (keyInfo.name() == null) {
|
||||||
|
if (keyInfo.email() != null) {
|
||||||
|
vMainUserId.setText(highlighter.highlight(keyInfo.email()));
|
||||||
|
vMainUserIdRest.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
vMainUserId.setText(R.string.user_id_no_name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vMainUserId.setText(highlighter.highlight(keyInfo.name()));
|
||||||
|
// for some reason, this hangs for me
|
||||||
|
// FlexibleUtils.highlightText(vMainUserId, keyInfo.name(), highlightString);
|
||||||
|
if (keyInfo.email() != null) {
|
||||||
|
vMainUserIdRest.setText(highlighter.highlight(keyInfo.email()));
|
||||||
|
vMainUserIdRest.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
vMainUserIdRest.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // set edit button and status, specific by key type. Note: order is important!
|
||||||
|
int textColor;
|
||||||
|
if (keyInfo.is_revoked()) {
|
||||||
|
KeyFormattingUtils.setStatusImage(
|
||||||
|
context,
|
||||||
|
vStatusIcon,
|
||||||
|
null,
|
||||||
|
KeyFormattingUtils.State.REVOKED,
|
||||||
|
R.color.key_flag_gray
|
||||||
|
);
|
||||||
|
|
||||||
|
vStatusIcon.setVisibility(View.VISIBLE);
|
||||||
|
textColor = ContextCompat.getColor(context, R.color.key_flag_gray);
|
||||||
|
} else if (keyInfo.is_expired()) {
|
||||||
|
KeyFormattingUtils.setStatusImage(
|
||||||
|
context,
|
||||||
|
vStatusIcon,
|
||||||
|
null,
|
||||||
|
KeyFormattingUtils.State.EXPIRED,
|
||||||
|
R.color.key_flag_gray
|
||||||
|
);
|
||||||
|
|
||||||
|
vStatusIcon.setVisibility(View.VISIBLE);
|
||||||
|
textColor = ContextCompat.getColor(context, R.color.key_flag_gray);
|
||||||
|
} else if (!keyInfo.is_secure()) {
|
||||||
|
KeyFormattingUtils.setStatusImage(
|
||||||
|
context,
|
||||||
|
vStatusIcon,
|
||||||
|
null,
|
||||||
|
KeyFormattingUtils.State.INSECURE,
|
||||||
|
R.color.key_flag_gray
|
||||||
|
);
|
||||||
|
|
||||||
|
vStatusIcon.setVisibility(View.VISIBLE);
|
||||||
|
textColor = ContextCompat.getColor(context, R.color.key_flag_gray);
|
||||||
|
} else if (keyInfo.has_any_secret()) {
|
||||||
|
vStatusIcon.setVisibility(View.GONE);
|
||||||
|
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
|
||||||
|
} else {
|
||||||
|
// this is a public key - show if it's verified
|
||||||
|
if (keyInfo.is_verified()) {
|
||||||
|
KeyFormattingUtils.setStatusImage(
|
||||||
|
context,
|
||||||
|
vStatusIcon,
|
||||||
|
KeyFormattingUtils.State.VERIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
vStatusIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
KeyFormattingUtils.setStatusImage(
|
||||||
|
context,
|
||||||
|
vStatusIcon,
|
||||||
|
KeyFormattingUtils.State.UNVERIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
vStatusIcon.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
vMainUserId.setTextColor(textColor);
|
||||||
|
vMainUserIdRest.setTextColor(textColor);
|
||||||
|
|
||||||
|
if (keyInfo.has_duplicate() || keyInfo.has_any_secret()) {
|
||||||
|
vCreationDate.setText(getSecretKeyReadableTime(context, keyInfo));
|
||||||
|
vCreationDate.setTextColor(textColor);
|
||||||
|
vCreationDate.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
vCreationDate.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // set icons
|
||||||
|
|
||||||
|
if (!keyInfo.has_any_secret() && !keyInfo.autocrypt_package_names().isEmpty()) {
|
||||||
|
String packageName = keyInfo.autocrypt_package_names().get(0);
|
||||||
|
Drawable drawable = PackageIconGetter.getInstance(context).getDrawableForPackageName(packageName);
|
||||||
|
if (drawable != null) {
|
||||||
|
vTrustIdIcon.setImageDrawable(drawable);
|
||||||
|
vTrustIdIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
vTrustIdIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vTrustIdIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String getSecretKeyReadableTime(Context context, UnifiedKeyInfo keyInfo) {
|
||||||
|
long creationMillis = keyInfo.creation() * 1000;
|
||||||
|
|
||||||
|
boolean allowRelativeTimestamp = keyInfo.has_duplicate();
|
||||||
|
if (allowRelativeTimestamp) {
|
||||||
|
long creationAgeMillis = System.currentTimeMillis() - creationMillis;
|
||||||
|
if (creationAgeMillis < JUST_NOW_THRESHOLD) {
|
||||||
|
return context.getString(R.string.label_key_created_just_now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String dateTime = DateUtils.formatDateTime(context,
|
||||||
|
creationMillis,
|
||||||
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
|
| DateUtils.FORMAT_SHOW_TIME
|
||||||
|
| DateUtils.FORMAT_SHOW_YEAR
|
||||||
|
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||||
|
return context.getString(R.string.label_key_created, dateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible;
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem.FlexibleKeyDummyViewHolder;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem;
|
||||||
|
|
||||||
|
|
||||||
|
public class FlexibleKeyDummyItem extends FlexibleSectionableKeyItem<FlexibleKeyDummyViewHolder> {
|
||||||
|
FlexibleKeyDummyItem(FlexibleKeyHeader header) {
|
||||||
|
super(header);
|
||||||
|
|
||||||
|
setSelectable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutRes() {
|
||||||
|
return R.layout.key_list_dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlexibleKeyDummyViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
|
||||||
|
return new FlexibleKeyDummyViewHolder(view, adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindViewHolder(FlexibleAdapter<IFlexible> adapter, FlexibleKeyDummyViewHolder holder,
|
||||||
|
int position, List<Object> payloads) {
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlexibleKeyDummyViewHolder extends FlexibleViewHolder {
|
||||||
|
private FlexibleKeyDummyViewHolder(View view, FlexibleAdapter adapter) {
|
||||||
|
super(view, adapter, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,18 +9,20 @@ import android.widget.TextView;
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem;
|
import eu.davidea.flexibleadapter.items.AbstractHeaderItem;
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible;
|
import eu.davidea.flexibleadapter.items.IFlexible;
|
||||||
|
import eu.davidea.flexibleadapter.items.IHeader;
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder;
|
import eu.davidea.viewholders.FlexibleViewHolder;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader.FlexibleHeaderViewHolder;
|
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader.FlexibleHeaderViewHolder;
|
||||||
|
|
||||||
|
|
||||||
public class FlexibleKeyHeader extends AbstractHeaderItem<FlexibleHeaderViewHolder> {
|
public class FlexibleKeyHeader extends FlexibleKeyItem<FlexibleHeaderViewHolder>
|
||||||
|
implements IHeader<FlexibleHeaderViewHolder> {
|
||||||
private final String sectionTitle;
|
private final String sectionTitle;
|
||||||
|
|
||||||
FlexibleKeyHeader(String sectionTitle) {
|
FlexibleKeyHeader(String sectionTitle) {
|
||||||
|
super();
|
||||||
this.sectionTitle = sectionTitle;
|
this.sectionTitle = sectionTitle;
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
setSelectable(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,236 +1,30 @@
|
||||||
package org.sufficientlysecure.keychain.ui.adapter;
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
import android.content.Context;
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
|
||||||
import android.graphics.drawable.Drawable;
|
import eu.davidea.flexibleadapter.items.ISectionable;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem;
|
|
||||||
import eu.davidea.flexibleadapter.items.IFilterable;
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible;
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
|
||||||
import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo;
|
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleKeyItemViewHolder;
|
|
||||||
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.PackageIconGetter;
|
|
||||||
|
|
||||||
|
|
||||||
public class FlexibleKeyItem extends AbstractSectionableItem<FlexibleKeyItemViewHolder, FlexibleKeyHeader>
|
public abstract class FlexibleKeyItem<VH extends RecyclerView.ViewHolder> extends AbstractFlexibleItem<VH> {
|
||||||
implements IFilterable<String> {
|
|
||||||
public final UnifiedKeyInfo keyInfo;
|
|
||||||
|
|
||||||
FlexibleKeyItem(UnifiedKeyInfo keyInfo, FlexibleKeyHeader header) {
|
public static abstract class FlexibleSectionableKeyItem<VH extends RecyclerView.ViewHolder>
|
||||||
super(header);
|
extends FlexibleKeyItem<VH> implements ISectionable<VH, FlexibleKeyHeader> {
|
||||||
this.keyInfo = keyInfo;
|
FlexibleKeyHeader header;
|
||||||
|
|
||||||
setSelectable(true);
|
FlexibleSectionableKeyItem(FlexibleKeyHeader header) {
|
||||||
}
|
this.header = header;
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLayoutRes() {
|
|
||||||
return R.layout.key_list_item;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FlexibleKeyItemViewHolder createViewHolder(View view, FlexibleAdapter<IFlexible> adapter) {
|
|
||||||
return new FlexibleKeyItemViewHolder(view, adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bindViewHolder(
|
|
||||||
FlexibleAdapter<IFlexible> adapter, FlexibleKeyItemViewHolder holder, int position, List<Object> payloads) {
|
|
||||||
String highlightString = adapter.getFilter(String.class);
|
|
||||||
holder.bind(keyInfo, highlightString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o instanceof FlexibleKeyItem) {
|
|
||||||
FlexibleKeyItem other = (FlexibleKeyItem) o;
|
|
||||||
return keyInfo.master_key_id() == other.keyInfo.master_key_id();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
long masterKeyId = keyInfo.master_key_id();
|
|
||||||
return (int) (masterKeyId ^ (masterKeyId >>> 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean filter(String constraint) {
|
|
||||||
String uidList = keyInfo.user_id_list();
|
|
||||||
return constraint == null || (uidList != null && uidList.contains(constraint));
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FlexibleKeyItemViewHolder extends FlexibleViewHolder {
|
|
||||||
private static final long JUST_NOW_THRESHOLD = DateUtils.MINUTE_IN_MILLIS * 5;
|
|
||||||
|
|
||||||
private final TextView vMainUserId;
|
|
||||||
private final TextView vMainUserIdRest;
|
|
||||||
private final TextView vCreationDate;
|
|
||||||
private final ImageView vStatusIcon;
|
|
||||||
private final ImageView vTrustIdIcon;
|
|
||||||
|
|
||||||
FlexibleKeyItemViewHolder(View itemView, FlexibleAdapter adapter) {
|
|
||||||
super(itemView, adapter);
|
|
||||||
|
|
||||||
vMainUserId = itemView.findViewById(R.id.key_list_item_name);
|
|
||||||
vMainUserIdRest = itemView.findViewById(R.id.key_list_item_email);
|
|
||||||
vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon);
|
|
||||||
vCreationDate = itemView.findViewById(R.id.key_list_item_creation);
|
|
||||||
vTrustIdIcon = itemView.findViewById(R.id.key_list_item_tid_icon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(UnifiedKeyInfo keyInfo, String highlightString) {
|
@Override
|
||||||
setEnabled(true);
|
public FlexibleKeyHeader getHeader() {
|
||||||
|
return header;
|
||||||
Context context = itemView.getContext();
|
|
||||||
Highlighter highlighter = new Highlighter(context, highlightString);
|
|
||||||
|
|
||||||
{ // set name and stuff, common to both key types
|
|
||||||
if (keyInfo.name() == null) {
|
|
||||||
if (keyInfo.email() != null) {
|
|
||||||
vMainUserId.setText(highlighter.highlight(keyInfo.email()));
|
|
||||||
vMainUserIdRest.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
vMainUserId.setText(R.string.user_id_no_name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vMainUserId.setText(highlighter.highlight(keyInfo.name()));
|
|
||||||
// for some reason, this hangs for me
|
|
||||||
// FlexibleUtils.highlightText(vMainUserId, keyInfo.name(), highlightString);
|
|
||||||
if (keyInfo.email() != null) {
|
|
||||||
vMainUserIdRest.setText(highlighter.highlight(keyInfo.email()));
|
|
||||||
vMainUserIdRest.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
vMainUserIdRest.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // set edit button and status, specific by key type. Note: order is important!
|
|
||||||
int textColor;
|
|
||||||
if (keyInfo.is_revoked()) {
|
|
||||||
KeyFormattingUtils.setStatusImage(
|
|
||||||
context,
|
|
||||||
vStatusIcon,
|
|
||||||
null,
|
|
||||||
KeyFormattingUtils.State.REVOKED,
|
|
||||||
R.color.key_flag_gray
|
|
||||||
);
|
|
||||||
|
|
||||||
vStatusIcon.setVisibility(View.VISIBLE);
|
|
||||||
textColor = ContextCompat.getColor(context, R.color.key_flag_gray);
|
|
||||||
} else if (keyInfo.is_expired()) {
|
|
||||||
KeyFormattingUtils.setStatusImage(
|
|
||||||
context,
|
|
||||||
vStatusIcon,
|
|
||||||
null,
|
|
||||||
KeyFormattingUtils.State.EXPIRED,
|
|
||||||
R.color.key_flag_gray
|
|
||||||
);
|
|
||||||
|
|
||||||
vStatusIcon.setVisibility(View.VISIBLE);
|
|
||||||
textColor = ContextCompat.getColor(context, R.color.key_flag_gray);
|
|
||||||
} else if (!keyInfo.is_secure()) {
|
|
||||||
KeyFormattingUtils.setStatusImage(
|
|
||||||
context,
|
|
||||||
vStatusIcon,
|
|
||||||
null,
|
|
||||||
KeyFormattingUtils.State.INSECURE,
|
|
||||||
R.color.key_flag_gray
|
|
||||||
);
|
|
||||||
|
|
||||||
vStatusIcon.setVisibility(View.VISIBLE);
|
|
||||||
textColor = ContextCompat.getColor(context, R.color.key_flag_gray);
|
|
||||||
} else if (keyInfo.has_any_secret()) {
|
|
||||||
vStatusIcon.setVisibility(View.GONE);
|
|
||||||
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
|
|
||||||
} else {
|
|
||||||
// this is a public key - show if it's verified
|
|
||||||
if (keyInfo.is_verified()) {
|
|
||||||
KeyFormattingUtils.setStatusImage(
|
|
||||||
context,
|
|
||||||
vStatusIcon,
|
|
||||||
KeyFormattingUtils.State.VERIFIED
|
|
||||||
);
|
|
||||||
|
|
||||||
vStatusIcon.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
KeyFormattingUtils.setStatusImage(
|
|
||||||
context,
|
|
||||||
vStatusIcon,
|
|
||||||
KeyFormattingUtils.State.UNVERIFIED
|
|
||||||
);
|
|
||||||
|
|
||||||
vStatusIcon.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
|
|
||||||
}
|
|
||||||
|
|
||||||
vMainUserId.setTextColor(textColor);
|
|
||||||
vMainUserIdRest.setTextColor(textColor);
|
|
||||||
|
|
||||||
if (keyInfo.has_duplicate() || keyInfo.has_any_secret()) {
|
|
||||||
vCreationDate.setText(getSecretKeyReadableTime(context, keyInfo));
|
|
||||||
vCreationDate.setTextColor(textColor);
|
|
||||||
vCreationDate.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
vCreationDate.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // set icons
|
|
||||||
|
|
||||||
if (!keyInfo.has_any_secret() && !keyInfo.autocrypt_package_names().isEmpty()) {
|
|
||||||
String packageName = keyInfo.autocrypt_package_names().get(0);
|
|
||||||
Drawable drawable = PackageIconGetter.getInstance(context).getDrawableForPackageName(packageName);
|
|
||||||
if (drawable != null) {
|
|
||||||
vTrustIdIcon.setImageDrawable(drawable);
|
|
||||||
vTrustIdIcon.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
vTrustIdIcon.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vTrustIdIcon.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@NonNull
|
public void setHeader(FlexibleKeyHeader header) {
|
||||||
private String getSecretKeyReadableTime(Context context, UnifiedKeyInfo keyInfo) {
|
this.header = header;
|
||||||
long creationMillis = keyInfo.creation() * 1000;
|
|
||||||
|
|
||||||
boolean allowRelativeTimestamp = keyInfo.has_duplicate();
|
|
||||||
if (allowRelativeTimestamp) {
|
|
||||||
long creationAgeMillis = System.currentTimeMillis() - creationMillis;
|
|
||||||
if (creationAgeMillis < JUST_NOW_THRESHOLD) {
|
|
||||||
return context.getString(R.string.label_key_created_just_now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String dateTime = DateUtils.formatDateTime(context,
|
|
||||||
creationMillis,
|
|
||||||
DateUtils.FORMAT_SHOW_DATE
|
|
||||||
| DateUtils.FORMAT_SHOW_TIME
|
|
||||||
| DateUtils.FORMAT_SHOW_YEAR
|
|
||||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
|
||||||
return context.getString(R.string.label_key_created, dateTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,12 @@ public class FlexibleKeyItemFactory {
|
||||||
if (unifiedKeyInfos == null) {
|
if (unifiedKeyInfos == null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
if (unifiedKeyInfos.isEmpty() || !unifiedKeyInfos.get(0).has_any_secret()) {
|
||||||
|
result.add(new FlexibleKeyDummyItem(myKeysHeader));
|
||||||
|
}
|
||||||
for (UnifiedKeyInfo unifiedKeyInfo : unifiedKeyInfos) {
|
for (UnifiedKeyInfo unifiedKeyInfo : unifiedKeyInfos) {
|
||||||
FlexibleKeyHeader header = getFlexibleKeyHeader(unifiedKeyInfo);
|
FlexibleKeyHeader header = getFlexibleKeyHeader(unifiedKeyInfo);
|
||||||
FlexibleKeyItem flexibleKeyItem = new FlexibleKeyItem(unifiedKeyInfo, header);
|
FlexibleKeyItem flexibleKeyItem = new FlexibleKeyDetailsItem(unifiedKeyInfo, header);
|
||||||
result.add(flexibleKeyItem);
|
result.add(flexibleKeyItem);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:focusable="true"
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
android:background="?android:selectableItemBackground">
|
android:background="?android:selectableItemBackground">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -8,9 +8,7 @@
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:descendantFocusability="blocksDescendants"
|
|
||||||
android:background="@drawable/list_item_ripple"
|
android:background="@drawable/list_item_ripple"
|
||||||
android:focusable="false"
|
|
||||||
android:paddingLeft="12dp"
|
android:paddingLeft="12dp"
|
||||||
android:paddingRight="12dp"
|
android:paddingRight="12dp"
|
||||||
tools:layout_marginTop="30dp">
|
tools:layout_marginTop="30dp">
|
||||||
|
|
Loading…
Reference in a new issue