use LiveData for signing key spinners
This commit is contained in:
parent
1635c261b8
commit
83d5aafadb
|
@ -25,6 +25,7 @@ import java.util.List;
|
|||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
|
@ -43,6 +44,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
|||
import timber.log.Timber;
|
||||
|
||||
|
||||
@WorkerThread
|
||||
public class KeyRepository extends AbstractDao {
|
||||
final ContentResolver contentResolver;
|
||||
final LocalPublicKeyStorage mLocalPublicKeyStorage;
|
||||
|
|
|
@ -127,7 +127,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||
Vector<String> userIds = new Vector<>();
|
||||
for (int i = 0; i < getListView().getCount(); ++i) {
|
||||
if (getListView().isItemChecked(i)) {
|
||||
userIds.add(mAdapter.getUserId(i));
|
||||
userIds.add(spinnerAdapter.getUserId(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,23 +29,16 @@ import android.arch.lifecycle.ViewModelProviders;
|
|||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Drawable.ConstantState;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.content.res.ResourcesCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
@ -57,6 +50,7 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
|||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
|
||||
import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicatePresenter.RemoteDeduplicateView;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
|
||||
|
@ -263,94 +257,4 @@ public class RemoteDeduplicateActivity extends FragmentActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private static class KeyChoiceAdapter extends Adapter<KeyChoiceViewHolder> {
|
||||
private final LayoutInflater layoutInflater;
|
||||
private final Resources resources;
|
||||
private List<UnifiedKeyInfo> data;
|
||||
private Drawable iconUnselected;
|
||||
private Drawable iconSelected;
|
||||
private Integer activeItem;
|
||||
|
||||
KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) {
|
||||
this.layoutInflater = layoutInflater;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false);
|
||||
return new KeyChoiceViewHolder(keyChoiceItemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) {
|
||||
UnifiedKeyInfo keyInfo = data.get(position);
|
||||
Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected;
|
||||
holder.bind(keyInfo, icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data != null ? data.size() : 0;
|
||||
}
|
||||
|
||||
public void setData(List<UnifiedKeyInfo> data) {
|
||||
this.data = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void setSelectionDrawable(Drawable drawable) {
|
||||
ConstantState constantState = drawable.getConstantState();
|
||||
if (constantState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
iconSelected = constantState.newDrawable(resources);
|
||||
|
||||
iconUnselected = constantState.newDrawable(resources);
|
||||
DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null));
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void setActiveItem(Integer newActiveItem) {
|
||||
Integer prevActiveItem = this.activeItem;
|
||||
this.activeItem = newActiveItem;
|
||||
|
||||
if (prevActiveItem != null) {
|
||||
notifyItemChanged(prevActiveItem);
|
||||
}
|
||||
if (newActiveItem != null) {
|
||||
notifyItemChanged(newActiveItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class KeyChoiceViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView vName;
|
||||
private final TextView vCreation;
|
||||
private final ImageView vIcon;
|
||||
|
||||
KeyChoiceViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
vName = itemView.findViewById(R.id.key_list_item_name);
|
||||
vCreation = itemView.findViewById(R.id.key_list_item_creation);
|
||||
vIcon = itemView.findViewById(R.id.key_list_item_icon);
|
||||
}
|
||||
|
||||
void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) {
|
||||
vName.setText(keyInfo.name());
|
||||
|
||||
Context context = vCreation.getContext();
|
||||
String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(),
|
||||
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME |
|
||||
DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH);
|
||||
vCreation.setText(context.getString(R.string.label_key_created, dateTime));
|
||||
|
||||
vIcon.setImageDrawable(selectionIcon);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,8 +20,11 @@ package org.sufficientlysecure.keychain.ui;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
|
@ -42,9 +45,10 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
|||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
|
@ -53,7 +57,7 @@ public class CertifyKeyFragment
|
|||
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
|
||||
private CertifyKeySpinner mCertifyKeySpinner;
|
||||
private KeySpinner mCertifyKeySpinner;
|
||||
|
||||
private MultiUserIdsFragment mMultiUserIdsFragment;
|
||||
|
||||
|
@ -61,6 +65,13 @@ public class CertifyKeyFragment
|
|||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
|
||||
LiveData<List<UnifiedKeyInfo>> liveData = viewModel.getGenericLiveData(requireContext(), () -> {
|
||||
KeyRepository keyRepository = KeyRepository.create(requireContext());
|
||||
return keyRepository.getAllUnifiedKeyInfoWithSecret();
|
||||
});
|
||||
liveData.observe(this, mCertifyKeySpinner::setData);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// preselect certify key id if given
|
||||
long certifyKeyId = getActivity().getIntent()
|
||||
|
@ -90,6 +101,8 @@ public class CertifyKeyFragment
|
|||
mMultiUserIdsFragment = (MultiUserIdsFragment)
|
||||
getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment);
|
||||
|
||||
mCertifyKeySpinner.setShowNone(R.string.choice_select_cert);
|
||||
|
||||
// make certify image gray, like action icons
|
||||
ImageView vActionCertifyImage =
|
||||
view.findViewById(R.id.certify_key_action_certify_image);
|
||||
|
|
|
@ -22,7 +22,10 @@ import java.util.ArrayList;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -36,12 +39,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
|||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
@ -57,9 +60,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) {
|
||||
EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment();
|
||||
|
||||
|
@ -75,23 +75,21 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
||||
|
||||
mSignKeySpinner = view.findViewById(R.id.sign);
|
||||
mSignKeySpinner = view.findViewById(R.id.sign_key_spinner);
|
||||
mEncryptKeyView = view.findViewById(R.id.recipient_list);
|
||||
mEncryptKeyView.setThreshold(1); // Start working from first character
|
||||
|
||||
final ViewAnimator vSignatureIcon = view.findViewById(R.id.result_signature_icon);
|
||||
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
|
||||
@Override
|
||||
public void onKeyChanged(long masterKeyId) {
|
||||
int child = masterKeyId != Constants.key.none ? 1 : 0;
|
||||
if (vSignatureIcon.getDisplayedChild() != child) {
|
||||
vSignatureIcon.setDisplayedChild(child);
|
||||
}
|
||||
mSignKeySpinner.setOnKeyChangedListener(masterKeyId -> {
|
||||
int child = masterKeyId != Constants.key.none ? 1 : 0;
|
||||
if (vSignatureIcon.getDisplayedChild() != child) {
|
||||
vSignatureIcon.setDisplayedChild(child);
|
||||
}
|
||||
});
|
||||
mSignKeySpinner.setShowNone(R.string.cert_none);
|
||||
|
||||
final ViewAnimator vEncryptionIcon = view.findViewById(R.id.result_encryption_icon);
|
||||
mEncryptKeyView.setTokenListener(new TokenListener<KeyItem>() {
|
||||
|
@ -117,7 +115,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mKeyRepository = KeyRepository.create(getContext());
|
||||
mKeyRepository = KeyRepository.create(requireContext());
|
||||
|
||||
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
|
||||
LiveData<List<UnifiedKeyInfo>> liveData = viewModel.getGenericLiveData(requireContext(),
|
||||
mKeyRepository::getAllUnifiedKeyInfoWithSecret);
|
||||
liveData.observe(this, mSignKeySpinner::setData);
|
||||
|
||||
// preselect keys given, from state or arguments
|
||||
if (savedInstanceState == null) {
|
||||
|
|
|
@ -24,15 +24,12 @@ import java.util.List;
|
|||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.os.CancellationSignal;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
@ -58,7 +55,6 @@ import eu.davidea.flexibleadapter.SelectableAdapter.Mode;
|
|||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.KeySyncOperation;
|
||||
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.KeySyncParcel;
|
||||
|
@ -78,8 +74,8 @@ import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectio
|
|||
import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||
|
@ -101,6 +97,9 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
|||
|
||||
private FloatingActionsMenu mFab;
|
||||
|
||||
private KeyRepository keyRepository;
|
||||
private FlexibleKeyItemFactory flexibleKeyItemFactory;
|
||||
|
||||
private final ActionMode.Callback mActionCallback = new ActionMode.Callback() {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
|
@ -244,36 +243,18 @@ public class KeyListFragment extends RecyclerFragment<FlexibleAdapter<FlexibleKe
|
|||
|
||||
setLayoutManager(new LinearLayoutManager(activity));
|
||||
|
||||
KeyListViewModel keyListViewModel = ViewModelProviders.of(this).get(KeyListViewModel.class);
|
||||
keyListViewModel.getLiveData(getContext()).observe(this, this::onLoadKeyItems);
|
||||
keyRepository = KeyRepository.create(requireContext());
|
||||
flexibleKeyItemFactory = new FlexibleKeyItemFactory(requireContext().getResources());
|
||||
|
||||
GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class);
|
||||
LiveData<List<FlexibleKeyItem>> liveData = viewModel.getGenericLiveData(requireContext(), this::loadFlexibleKeyItems);
|
||||
liveData.observe(this, this::onLoadKeyItems);
|
||||
}
|
||||
|
||||
public static class KeyListViewModel extends ViewModel {
|
||||
LiveData<List<FlexibleKeyItem>> liveData;
|
||||
|
||||
LiveData<List<FlexibleKeyItem>> getLiveData(Context context) {
|
||||
if (liveData == null) {
|
||||
liveData = new KeyListLiveData(context);
|
||||
}
|
||||
return liveData;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyListLiveData extends AsyncTaskLiveData<List<FlexibleKeyItem>> {
|
||||
private final KeyRepository keyRepository;
|
||||
private FlexibleKeyItemFactory flexibleKeyItemFactory;
|
||||
|
||||
KeyListLiveData(@NonNull Context context) {
|
||||
super(context, KeyRings.CONTENT_URI);
|
||||
keyRepository = KeyRepository.create(context.getApplicationContext());
|
||||
flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<FlexibleKeyItem> asyncLoadData() {
|
||||
List<UnifiedKeyInfo> unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo();
|
||||
return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
|
||||
}
|
||||
@WorkerThread
|
||||
private List<FlexibleKeyItem> loadFlexibleKeyItems() {
|
||||
List<UnifiedKeyInfo> unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo();
|
||||
return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo);
|
||||
}
|
||||
|
||||
private void onLoadKeyItems(List<FlexibleKeyItem> flexibleKeyItems) {
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Drawable.ConstantState;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.res.ResourcesCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceViewHolder;
|
||||
|
||||
|
||||
public class KeyChoiceAdapter extends Adapter<KeyChoiceViewHolder> {
|
||||
private final LayoutInflater layoutInflater;
|
||||
private final Resources resources;
|
||||
private List<UnifiedKeyInfo> data;
|
||||
private Drawable iconUnselected;
|
||||
private Drawable iconSelected;
|
||||
private Integer activeItem;
|
||||
|
||||
public KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) {
|
||||
this.layoutInflater = layoutInflater;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false);
|
||||
return new KeyChoiceViewHolder(keyChoiceItemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) {
|
||||
UnifiedKeyInfo keyInfo = data.get(position);
|
||||
Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected;
|
||||
holder.bind(keyInfo, icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data != null ? data.size() : 0;
|
||||
}
|
||||
|
||||
public void setData(List<UnifiedKeyInfo> data) {
|
||||
this.data = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setSelectionDrawable(Drawable drawable) {
|
||||
ConstantState constantState = drawable.getConstantState();
|
||||
if (constantState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
iconSelected = constantState.newDrawable(resources);
|
||||
|
||||
iconUnselected = constantState.newDrawable(resources);
|
||||
DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null));
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setActiveItem(Integer newActiveItem) {
|
||||
Integer prevActiveItem = this.activeItem;
|
||||
this.activeItem = newActiveItem;
|
||||
|
||||
if (prevActiveItem != null) {
|
||||
notifyItemChanged(prevActiveItem);
|
||||
}
|
||||
if (newActiveItem != null) {
|
||||
notifyItemChanged(newActiveItem);
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyChoiceViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView vName;
|
||||
private final TextView vCreation;
|
||||
private final ImageView vIcon;
|
||||
|
||||
KeyChoiceViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
vName = itemView.findViewById(R.id.key_list_item_name);
|
||||
vCreation = itemView.findViewById(R.id.key_list_item_creation);
|
||||
vIcon = itemView.findViewById(R.id.key_list_item_icon);
|
||||
}
|
||||
|
||||
void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) {
|
||||
vName.setText(keyInfo.name());
|
||||
|
||||
Context context = vCreation.getContext();
|
||||
String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(),
|
||||
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME |
|
||||
DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH);
|
||||
vCreation.setText(context.getString(R.string.label_key_created, dateTime));
|
||||
|
||||
vIcon.setImageDrawable(selectionIcon);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.sufficientlysecure.keychain.ui.keyview;
|
||||
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
|
||||
import org.sufficientlysecure.keychain.livedata.GenericLiveData.GenericDataLoader;
|
||||
|
||||
|
||||
/** A simple generic ViewModel that can be used if exactly one field of data needs to be stored. */
|
||||
public class GenericViewModel extends ViewModel {
|
||||
private LiveData genericLiveData;
|
||||
|
||||
public <T> LiveData<T> getGenericLiveData(Context context, GenericDataLoader<T> func) {
|
||||
if (genericLiveData == null) {
|
||||
genericLiveData = new GenericLiveData<>(context, null, func);
|
||||
}
|
||||
// noinspection unchecked
|
||||
return genericLiveData;
|
||||
}
|
||||
}
|
|
@ -19,8 +19,11 @@ package org.sufficientlysecure.keychain.ui.keyview;
|
|||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
|
@ -52,6 +55,7 @@ import org.sufficientlysecure.keychain.linked.UriAttribute;
|
|||
import org.sufficientlysecure.keychain.livedata.CertificationDao;
|
||||
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
|
||||
import org.sufficientlysecure.keychain.model.Certification.CertDetails;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
|
@ -69,7 +73,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
|
|||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
|
@ -118,11 +122,15 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
|
|||
|
||||
isSecret = args.getBoolean(ARG_IS_SECRET);
|
||||
masterKeyId = args.getLong(ARG_MASTER_KEY_ID);
|
||||
}
|
||||
|
||||
IdentityDao identityDao = IdentityDao.getInstance(getContext());
|
||||
GenericLiveData<LinkedIdInfo> uriAttributeLiveData =
|
||||
new GenericLiveData<>(getContext(), null, () -> identityDao.getLinkedIdInfo(masterKeyId, lidRank));
|
||||
uriAttributeLiveData.observe(this, this::onLinkedIdInfoLoaded);
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
|
||||
viewModel.getLinkedIdInfo(requireContext(), masterKeyId, lidRank).observe(this, this::onLinkedIdInfoLoaded);
|
||||
viewModel.getCertifyingKeys(requireContext()).observe(this, viewHolder.vKeySpinner::setData);
|
||||
}
|
||||
|
||||
private void onLinkedIdInfoLoaded(LinkedIdInfo linkedIdInfo) {
|
||||
|
@ -191,7 +199,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
|
|||
|
||||
private ViewAnimator vButtonSwitcher;
|
||||
private CertListWidget vLinkedCerts;
|
||||
private CertifyKeySpinner vKeySpinner;
|
||||
private KeySpinner vKeySpinner;
|
||||
private final View vButtonVerify;
|
||||
private final View vButtonRetry;
|
||||
private final View vButtonConfirm;
|
||||
|
@ -236,6 +244,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
|
|||
if (!isSecret) {
|
||||
showButton(2);
|
||||
if (!vKeySpinner.isSingleEntry()) {
|
||||
vKeySpinner.setShowNone(R.string.choice_select_cert);
|
||||
vKeySpinnerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
|
@ -351,10 +360,8 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
|
|||
viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource());
|
||||
viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying());
|
||||
|
||||
CertificationDao certificationDao = CertificationDao.getInstance(context);
|
||||
LiveData<CertDetails> certDetailsLiveData = new GenericLiveData<>(
|
||||
context, null, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank));
|
||||
certDetailsLiveData.observe(this, this::onLoadCertDetails);
|
||||
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
|
||||
viewModel.getCertDetails(context, masterKeyId, lidRank).observe(this, this::onLoadCertDetails);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
@ -483,4 +490,38 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB
|
|||
return true;
|
||||
}
|
||||
|
||||
public static class LinkedIdViewModel extends ViewModel {
|
||||
LiveData<List<UnifiedKeyInfo>> certifyingKeysLiveData;
|
||||
LiveData<CertDetails> certDetailsLiveData;
|
||||
LiveData<LinkedIdInfo> linkedIfInfoLiveData;
|
||||
|
||||
LiveData<List<UnifiedKeyInfo>> getCertifyingKeys(Context context) {
|
||||
if (certifyingKeysLiveData == null) {
|
||||
certifyingKeysLiveData = new GenericLiveData<>(context, null, () -> {
|
||||
KeyRepository keyRepository = KeyRepository.create(context);
|
||||
return keyRepository.getAllUnifiedKeyInfoWithSecret();
|
||||
});
|
||||
}
|
||||
return certifyingKeysLiveData;
|
||||
}
|
||||
|
||||
LiveData<CertDetails> getCertDetails(Context context, long masterKeyId, int lidRank) {
|
||||
if (certDetailsLiveData == null) {
|
||||
CertificationDao certificationDao = CertificationDao.getInstance(context);
|
||||
certDetailsLiveData = new GenericLiveData<>(context, null,
|
||||
() -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank));
|
||||
}
|
||||
return certDetailsLiveData;
|
||||
}
|
||||
|
||||
public LiveData<LinkedIdInfo> getLinkedIdInfo(Context context, long masterKeyId, int lidRank) {
|
||||
if (linkedIfInfoLiveData == null) {
|
||||
IdentityDao identityDao = IdentityDao.getInstance(context);
|
||||
linkedIfInfoLiveData = new GenericLiveData<>(context, null,
|
||||
() -> identityDao.getLinkedIdInfo(masterKeyId, lidRank));
|
||||
}
|
||||
return linkedIfInfoLiveData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -268,6 +268,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
|||
|
||||
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class);
|
||||
viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L));
|
||||
viewModel.getUnifiedKeyInfoLiveData(getApplicationContext()).observe(this, this::onLoadUnifiedKeyInfo);
|
||||
|
||||
if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) {
|
||||
OperationResult result = intent.getParcelableExtra(EXTRA_DISPLAY_RESULT);
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
|
||||
public class CertifyKeySpinner extends KeySpinner {
|
||||
private long mHiddenMasterKeyId = Constants.key.none;
|
||||
private boolean mIsSingle;
|
||||
|
||||
public CertifyKeySpinner(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CertifyKeySpinner(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CertifyKeySpinner(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void setHiddenMasterKeyId(long hiddenMasterKeyId) {
|
||||
this.mHiddenMasterKeyId = hiddenMasterKeyId;
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||
KeychainContract.KeyRings.HAS_CERTIFY_SECRET,
|
||||
});
|
||||
|
||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
|
||||
+ " != " + mHiddenMasterKeyId;
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
}
|
||||
|
||||
private int mIndexHasCertify;
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
super.onLoadFinished(loader, data);
|
||||
|
||||
if (loader.getId() == LOADER_ID) {
|
||||
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY_SECRET);
|
||||
|
||||
// If:
|
||||
// - no key has been pre-selected (e.g. by SageSlinger)
|
||||
// - there are actually keys (not just "none" entry)
|
||||
// Then:
|
||||
// - select key that is capable of certifying, but only if there is only one key capable of it
|
||||
if (mPreSelectedKeyId == Constants.key.none && mAdapter.getCount() > 1) {
|
||||
// preselect if key can certify
|
||||
int selection = -1;
|
||||
while (data.moveToNext()) {
|
||||
if (!data.isNull(mIndexHasCertify)) {
|
||||
if (selection == -1) {
|
||||
selection = data.getPosition() + 1;
|
||||
mIsSingle = true;
|
||||
} else {
|
||||
// if selection is already set, we have more than one certify key!
|
||||
// get back to "none"!
|
||||
mIsSingle = false;
|
||||
selection = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelection(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSingleEntry() {
|
||||
return mIsSingle && getSelectedItemPosition() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.isNull(mIndexHasCertify)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// valid key
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes int getNoneString() {
|
||||
return R.string.choice_select_cert;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
|
||||
|
||||
class KeyChoiceSpinnerAdapter extends BaseAdapter {
|
||||
private Integer noneItemString;
|
||||
private List<UnifiedKeyInfo> data;
|
||||
private final LayoutInflater layoutInflater;
|
||||
|
||||
KeyChoiceSpinnerAdapter(Context context) {
|
||||
super();
|
||||
|
||||
layoutInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
public void setData(List<UnifiedKeyInfo> data) {
|
||||
this.data = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setNoneItemString(@StringRes Integer noneItemString) {
|
||||
this.noneItemString = noneItemString;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean hasNoneItem() {
|
||||
return noneItemString != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return (data != null ? data.size() : 0) + (noneItemString != null ? 1 : 0);
|
||||
}
|
||||
|
||||
public boolean isSingleEntry() {
|
||||
return data != null && data.size() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnifiedKeyInfo getItem(int position) {
|
||||
if (noneItemString != null) {
|
||||
if (position == 0) {
|
||||
return null;
|
||||
}
|
||||
position -= 1;
|
||||
}
|
||||
return data.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (noneItemString != null) {
|
||||
if (position == 0) {
|
||||
return 0;
|
||||
}
|
||||
position -= 1;
|
||||
}
|
||||
return data != null ? data.get(position).master_key_id() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (noneItemString != null && position == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return super.getItemViewType(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (noneItemString != null) {
|
||||
if (position == 0) {
|
||||
if (convertView != null && convertView.getTag() == null) {
|
||||
return convertView;
|
||||
} else {
|
||||
View view = layoutInflater.inflate(R.layout.keyspinner_item_none, parent, false);
|
||||
view.<TextView>findViewById(R.id.keyspinner_key_name).setText(noneItemString);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
View view;
|
||||
KeyChoiceViewHolder viewHolder;
|
||||
if (convertView == null || !(convertView.getTag() instanceof KeyChoiceViewHolder)) {
|
||||
view = layoutInflater.inflate(R.layout.key_list_item, parent, false);
|
||||
viewHolder = new KeyChoiceViewHolder(view);
|
||||
view.setTag(viewHolder);
|
||||
} else {
|
||||
view = convertView;
|
||||
viewHolder = ((KeyChoiceViewHolder) view.getTag());
|
||||
}
|
||||
|
||||
UnifiedKeyInfo keyInfo = getItem(position);
|
||||
viewHolder.bind(view.getContext(), keyInfo, isEnabled(position));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public static class KeyChoiceViewHolder {
|
||||
private View mView;
|
||||
private TextView mMainUserId;
|
||||
private TextView mMainUserIdRest;
|
||||
private TextView mCreationDate;
|
||||
private ImageView mStatus;
|
||||
|
||||
KeyChoiceViewHolder(View view) {
|
||||
mView = view;
|
||||
mMainUserId = view.findViewById(R.id.key_list_item_name);
|
||||
mMainUserIdRest = view.findViewById(R.id.key_list_item_email);
|
||||
mStatus = view.findViewById(R.id.key_list_item_status_icon);
|
||||
mCreationDate = view.findViewById(R.id.key_list_item_creation);
|
||||
}
|
||||
|
||||
public void bind(Context context, UnifiedKeyInfo keyInfo, boolean enabled) {
|
||||
{ // set name and stuff, common to both key types
|
||||
if (keyInfo.name() != null) {
|
||||
mMainUserId.setText(keyInfo.name());
|
||||
} else {
|
||||
mMainUserId.setText(R.string.user_id_no_name);
|
||||
}
|
||||
if (keyInfo.email() != null) {
|
||||
mMainUserIdRest.setText(keyInfo.email());
|
||||
mMainUserIdRest.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mMainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// sort of a hack: if this item isn't enabled, we make it clickable
|
||||
// to intercept its click events. either way, no listener!
|
||||
mView.setClickable(!enabled);
|
||||
|
||||
{ // set edit button and status, specific by key type
|
||||
|
||||
int textColor;
|
||||
|
||||
// Note: order is important!
|
||||
if (keyInfo.is_revoked()) {
|
||||
KeyFormattingUtils
|
||||
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
textColor = context.getResources().getColor(R.color.key_flag_gray);
|
||||
} else if (keyInfo.is_expired()) {
|
||||
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
textColor = context.getResources().getColor(R.color.key_flag_gray);
|
||||
} else if (!keyInfo.is_secure()) {
|
||||
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
textColor = context.getResources().getColor(R.color.key_flag_gray);
|
||||
} else if (keyInfo.has_any_secret()) {
|
||||
mStatus.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, mStatus, State.VERIFIED);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
}
|
||||
textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText);
|
||||
}
|
||||
|
||||
mMainUserId.setTextColor(textColor);
|
||||
mMainUserIdRest.setTextColor(textColor);
|
||||
|
||||
if (keyInfo.has_duplicate()) {
|
||||
String dateTime = DateUtils.formatDateTime(context,
|
||||
keyInfo.creation() * 1000,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,39 +18,22 @@
|
|||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.AppCompatSpinner;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||
|
||||
|
||||
/**
|
||||
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
|
||||
* Related: http://stackoverflow.com/a/27713090
|
||||
*/
|
||||
public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
|
||||
public class KeySpinner extends AppCompatSpinner {
|
||||
public static final String ARG_SUPER_STATE = "super_state";
|
||||
public static final String ARG_KEY_ID = "key_id";
|
||||
|
||||
|
@ -58,13 +41,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
void onKeyChanged(long masterKeyId);
|
||||
}
|
||||
|
||||
protected long mPreSelectedKeyId = Constants.key.none;
|
||||
protected SelectKeyAdapter mAdapter = new SelectKeyAdapter();
|
||||
protected Long preSelectedKeyId;
|
||||
protected KeyChoiceSpinnerAdapter spinnerAdapter;
|
||||
protected OnKeyChangedListener mListener;
|
||||
|
||||
// this shall note collide with other loaders inside the activity
|
||||
protected int LOADER_ID = 2343;
|
||||
|
||||
public KeySpinner(Context context) {
|
||||
super(context);
|
||||
initView();
|
||||
|
@ -81,7 +61,9 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
}
|
||||
|
||||
private void initView() {
|
||||
setAdapter(mAdapter);
|
||||
spinnerAdapter = new KeyChoiceSpinnerAdapter(getContext());
|
||||
|
||||
setAdapter(spinnerAdapter);
|
||||
super.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
|
@ -100,6 +82,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
});
|
||||
}
|
||||
|
||||
public void setShowNone(@StringRes Integer noneStringRes) {
|
||||
spinnerAdapter.setNoneItemString(noneStringRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
@ -109,32 +95,28 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
reload();
|
||||
public void setData(List<UnifiedKeyInfo> keyInfos) {
|
||||
spinnerAdapter.setData(keyInfos);
|
||||
maybeSelectPreselection(keyInfos);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
if (getContext() instanceof FragmentActivity) {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||
} else {
|
||||
// ignore, this happens during preview! we use fragmentactivities everywhere either way
|
||||
private void maybeSelectPreselection(List<UnifiedKeyInfo> keyInfos) {
|
||||
if (preSelectedKeyId == null) {
|
||||
return;
|
||||
}
|
||||
for (UnifiedKeyInfo keyInfo : keyInfos) {
|
||||
if (keyInfo.master_key_id() == preSelectedKeyId) {
|
||||
int position = keyInfos.indexOf(keyInfo);
|
||||
if (spinnerAdapter.hasNoneItem()) {
|
||||
position += 1;
|
||||
}
|
||||
setSelection(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if (loader.getId() == LOADER_ID) {
|
||||
mAdapter.swapCursor(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (loader.getId() == LOADER_ID) {
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
public boolean isSingleEntry() {
|
||||
return spinnerAdapter.isSingleEntry();
|
||||
}
|
||||
|
||||
public long getSelectedKeyId() {
|
||||
|
@ -143,108 +125,21 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
}
|
||||
|
||||
public long getSelectedKeyId(Object item) {
|
||||
if (item instanceof KeyItem) {
|
||||
return ((KeyItem) item).mKeyId;
|
||||
if (item instanceof UnifiedKeyInfo) {
|
||||
return ((UnifiedKeyInfo) item).master_key_id();
|
||||
}
|
||||
return Constants.key.none;
|
||||
}
|
||||
|
||||
public void setPreSelectedKeyId(long selectedKeyId) {
|
||||
mPreSelectedKeyId = selectedKeyId;
|
||||
}
|
||||
|
||||
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||
private KeyAdapter inner;
|
||||
private int mIndexMasterKeyId;
|
||||
|
||||
public SelectKeyAdapter() {
|
||||
inner = new KeyAdapter(getContext(), null, 0) {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(Cursor cursor) {
|
||||
return KeySpinner.this.isItemEnabled(cursor);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor == null) return inner.swapCursor(null);
|
||||
|
||||
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
|
||||
|
||||
Cursor oldCursor = inner.swapCursor(newCursor);
|
||||
|
||||
// pre-select key if mPreSelectedKeyId is given
|
||||
if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
|
||||
do {
|
||||
if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
|
||||
setSelection(newCursor.getPosition() +1);
|
||||
}
|
||||
} while (newCursor.moveToNext());
|
||||
}
|
||||
return oldCursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return inner.getCount() +1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyItem getItem(int position) {
|
||||
if (position == 0) {
|
||||
return null;
|
||||
}
|
||||
return inner.getItem(position -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (position == 0) {
|
||||
return Constants.key.none;
|
||||
}
|
||||
return inner.getItemId(position -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
|
||||
// Unfortunately, SpinnerAdapter does not support multiple view
|
||||
// types. For this reason, we throw away convertViews of a bad
|
||||
// type. This is sort of a hack, but since the number of elements
|
||||
// we deal with in KeySpinners is usually very small (number of
|
||||
// secret keys), this is the easiest solution. (I'm sorry.)
|
||||
if (convertView != null) {
|
||||
// This assumes that the inner view has non-null tags on its views!
|
||||
boolean isWrongType = (convertView.getTag() == null) != (position == 0);
|
||||
if (isWrongType) {
|
||||
convertView = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (position > 0) {
|
||||
return inner.getView(position -1, convertView, parent);
|
||||
}
|
||||
|
||||
View view = convertView != null ? convertView :
|
||||
LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.keyspinner_item_none, parent, false);
|
||||
((TextView) view.findViewById(R.id.keyspinner_key_name)).setText(getNoneString());
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
return true;
|
||||
preSelectedKeyId = selectedKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
|
||||
mPreSelectedKeyId = bundle.getLong(ARG_KEY_ID);
|
||||
preSelectedKeyId = bundle.getLong(ARG_KEY_ID);
|
||||
|
||||
// restore super state
|
||||
super.onRestoreInstanceState(bundle.getParcelable(ARG_SUPER_STATE));
|
||||
|
@ -262,9 +157,4 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
|||
bundle.putLong(ARG_KEY_ID, getSelectedKeyId());
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public @StringRes int getNoneString() {
|
||||
return R.string.cert_none;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
|
||||
public class SignKeySpinner extends KeySpinner {
|
||||
public SignKeySpinner(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SignKeySpinner(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SignKeySpinner(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int loaderId, Bundle data) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||
KeychainContract.KeyRings.HAS_SIGN_SECRET,
|
||||
});
|
||||
|
||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
}
|
||||
|
||||
private int mIndexHasSign;
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
super.onLoadFinished(loader, data);
|
||||
|
||||
if (loader.getId() == LOADER_ID) {
|
||||
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN_SECRET);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_SECURE) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.isNull(mIndexHasSign)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// valid key
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -49,7 +49,7 @@
|
|||
android:paddingRight="8dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner
|
||||
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
|
||||
android:id="@+id/certify_key_spinner"
|
||||
android:minHeight="56dip"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -95,8 +95,8 @@
|
|||
android:text="@string/label_asymmetric_from"
|
||||
android:paddingRight="8dp"/>
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.SignKeySpinner
|
||||
android:id="@+id/sign"
|
||||
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
|
||||
android:id="@+id/sign_key_spinner"
|
||||
android:minHeight="56dip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="44dip"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="4dp">
|
||||
|
||||
|
|
|
@ -132,11 +132,11 @@
|
|||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/add_keys_my_key" />
|
||||
|
||||
<org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner
|
||||
<org.sufficientlysecure.keychain.ui.widget.KeySpinner
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/cert_key_spinner">
|
||||
</org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner>
|
||||
</org.sufficientlysecure.keychain.ui.widget.KeySpinner>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
Loading…
Reference in a new issue