From 9ea36286dca4086942818f68e04bf22721ca43c1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 19:26:09 +0200 Subject: [PATCH] use FlexibleAdapter for new KeyChoiceAdapter --- .../AppSettingsAllowedKeysListFragment.java | 134 +++------- .../ui/SelectSignKeyIdListFragment.java | 3 - .../ui/dialog/RemoteDeduplicateActivity.java | 34 +-- .../ui/dialog/RemoteDeduplicatePresenter.java | 64 ++--- .../keychain/ui/adapter/KeyChoiceAdapter.java | 234 +++++++++++++----- .../res/layout/api_remote_deduplicate.xml | 2 +- ...icate_key_item.xml => key_choice_item.xml} | 34 ++- 7 files changed, 247 insertions(+), 258 deletions(-) rename OpenKeychain/src/main/res/layout/{duplicate_key_item.xml => key_choice_item.xml} (57%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index b08d701ca..ea2a03c17 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -18,45 +18,36 @@ package org.sufficientlysecure.keychain.remote.ui; +import java.util.List; import java.util.Set; -import android.database.Cursor; -import android.net.Uri; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; +import android.support.v7.widget.LinearLayoutManager; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; -import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter; -import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; +import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; -public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { +public class AppSettingsAllowedKeysListFragment extends RecyclerFragment { private static final String ARG_PACKAGE_NAME = "package_name"; - private KeySelectableAdapter mAdapter; - private ApiAppDao mApiAppDao; + private KeyChoiceAdapter keyChoiceAdapter; + private ApiAppDao apiAppDao; private String packageName; - /** - * Creates new instance of this fragment - */ public static AppSettingsAllowedKeysListFragment newInstance(String packageName) { AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment(); + Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, packageName); - frag.setArguments(args); return frag; @@ -66,34 +57,9 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mApiAppDao = ApiAppDao.getInstance(getActivity()); + apiAppDao = ApiAppDao.getInstance(getActivity()); } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View layout = super.onCreateView(inflater, container, savedInstanceState); - ListView lv = layout.findViewById(android.R.id.list); - ViewGroup parent = (ViewGroup) lv.getParent(); - - /* - * http://stackoverflow.com/a/15880684 - * Remove ListView and add FixedListView in its place. - * This is done here programatically to be still able to use the progressBar of ListFragment. - * - * We want FixedListView to be able to put this ListFragment inside a ScrollView - */ - int lvIndex = parent.indexOfChild(lv); - parent.removeViewAt(lvIndex); - FixedListView newLv = new FixedListView(getActivity()); - newLv.setId(android.R.id.list); - parent.addView(newLv, lvIndex, lv.getLayoutParams()); - return layout; - } - - /** - * Define Adapter and Loader on create of Activity - */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -104,70 +70,36 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - Set checked = mApiAppDao.getAllowedKeyIdsForApp(packageName); - mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked); - setListAdapter(mAdapter); - getListView().setOnItemClickListener(mAdapter); + getRecyclerView().setLayoutManager(new LinearLayoutManager(requireContext())); // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); + hideList(false); + KeyRepository keyRepository = KeyRepository.create(requireContext()); + GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + LiveData> liveData = + viewModel.getGenericLiveData(requireContext(), keyRepository::getAllUnifiedKeyInfoWithSecret); + liveData.observe(this, this::onLoadUnifiedKeyData); } - /** Returns all selected master key ids. */ - public Set getSelectedMasterKeyIds() { - return mAdapter.getSelectedMasterKeyIds(); - } - - /** Returns all selected user ids. - public String[] getSelectedUserIds() { - Vector userIds = new Vector<>(); - for (int i = 0; i < getListView().getCount(); ++i) { - if (getListView().isItemChecked(i)) { - userIds.add(spinnerAdapter.getUserId(i)); - } - } - - // make empty array to not return null - String userIdArray[] = new String[0]; - return userIds.toArray(userIdArray); - } */ public void saveAllowedKeys() { - mApiAppDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds()); + Set longs = keyChoiceAdapter.getSelectionIds(); + apiAppDao.saveAllowedKeyIdsForApp(packageName, longs); } - @Override - public Loader onCreateLoader(int loaderId, Bundle data) { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); - String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; - - return new CursorLoader(getActivity(), baseUri, KeyAdapter.PROJECTION, where, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.swapCursor(data); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); + public void onLoadUnifiedKeyData(List data) { + if (keyChoiceAdapter == null) { + keyChoiceAdapter = new KeyChoiceAdapter(true, data); + setAdapter(keyChoiceAdapter); } else { - setListShownNoAnimation(true); + keyChoiceAdapter.setUnifiedKeyInfoItems(data); } - } - @Override - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - mAdapter.swapCursor(null); + Set checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); + keyChoiceAdapter.setSelectionByIds(checkedIds); + + boolean animateShowList = !isResumed(); + showList(animateShowList); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 3f8c4b905..38110e624 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -52,9 +52,6 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment data) { - keyChoiceAdapter.setData(data); - } - - @Override - public void setActiveItem(Integer position) { - keyChoiceAdapter.setActiveItem(position); - } - - @Override - public void setEnableSelectButton(boolean enabled) { - buttonSelect.setEnabled(enabled); + public void setKeyListAdapter(Adapter adapter) { + keyChoiceList.setAdapter(adapter); } }; } @@ -252,8 +236,6 @@ public class RemoteDeduplicateActivity extends FragmentActivity { private void setupListenersForPresenter() { buttonSelect.setOnClickListener(view -> presenter.onClickSelect()); buttonCancel.setOnClickListener(view -> presenter.onClickCancel()); - keyChoiceList.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), - (view, position) -> presenter.onKeyItemClick(position))); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index fd8a7ebae..d51a5b0a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -22,19 +22,15 @@ import java.util.List; import android.arch.lifecycle.LifecycleOwner; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView.Adapter; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.remote.AutocryptInteractor; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicateActivity.DeduplicateViewModel; -import timber.log.Timber; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; class RemoteDeduplicatePresenter { - private final PackageManager packageManager; private final Context context; private final LifecycleOwner lifecycleOwner; @@ -43,15 +39,12 @@ class RemoteDeduplicatePresenter { private DeduplicateViewModel viewModel; private RemoteDeduplicateView view; - private Integer selectedItem; - private List keyInfoData; + private KeyChoiceAdapter keyChoiceAdapter; RemoteDeduplicatePresenter(Context context, LifecycleOwner lifecycleOwner) { this.context = context; this.lifecycleOwner = lifecycleOwner; - - packageManager = context.getPackageManager(); } public void setView(RemoteDeduplicateView view) { @@ -62,42 +55,27 @@ class RemoteDeduplicatePresenter { this.viewModel = viewModel; this.autocryptInteractor = AutocryptInteractor.getInstance(context, viewModel.getPackageName()); - try { - setPackageInfo(viewModel.getPackageName()); - } catch (NameNotFoundException e) { - Timber.e("Unable to find info of calling app!"); - view.finishAsCancelled(); - return; - } - view.setAddressText(viewModel.getDuplicateAddress()); viewModel.getKeyInfoLiveData(context).observe(lifecycleOwner, this::onLoadKeyInfos); } - private void setPackageInfo(String packageName) throws NameNotFoundException { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); - Drawable appIcon = packageManager.getApplicationIcon(applicationInfo); - - view.setTitleClientIcon(appIcon); - } - private void onLoadKeyInfos(List data) { - this.keyInfoData = data; - view.setKeyListData(data); + if (keyChoiceAdapter == null) { + keyChoiceAdapter = new KeyChoiceAdapter(false, data); + view.setKeyListAdapter(keyChoiceAdapter); + } else { + keyChoiceAdapter.setUnifiedKeyInfoItems(data); + } } void onClickSelect() { - if (keyInfoData == null) { - Timber.e("got click on select with no data…?"); + UnifiedKeyInfo activeItem = keyChoiceAdapter.getActiveItem(); + if (activeItem == null) { + view.showNoSelectionError(); return; } - if (selectedItem == null) { - Timber.e("got click on select with no selection…?"); - return; - } - - long masterKeyId = keyInfoData.get(selectedItem).master_key_id(); + long masterKeyId = activeItem.master_key_id(); autocryptInteractor.updateKeyGossipFromDedup(viewModel.getDuplicateAddress(), masterKeyId); view.finish(); @@ -111,25 +89,13 @@ class RemoteDeduplicatePresenter { view.finishAsCancelled(); } - void onKeyItemClick(int position) { - if (selectedItem != null && position == selectedItem) { - selectedItem = null; - } else { - selectedItem = position; - } - view.setActiveItem(selectedItem); - view.setEnableSelectButton(selectedItem != null); - } - interface RemoteDeduplicateView { + void showNoSelectionError(); void finish(); void finishAsCancelled(); void setAddressText(String text); - void setTitleClientIcon(Drawable drawable); - void setKeyListData(List data); - void setActiveItem(Integer position); - void setEnableSelectButton(boolean enabled); + void setKeyListAdapter(Adapter adapter); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 1448e9f31..d495cb4e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -1,81 +1,66 @@ package org.sufficientlysecure.keychain.ui.adapter; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; 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.annotation.Nullable; 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.CheckBox; +import android.widget.RadioButton; import android.widget.TextView; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceViewHolder; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceItem; -public class KeyChoiceAdapter extends Adapter { - private final LayoutInflater layoutInflater; - private final Resources resources; - private List data; - private Drawable iconUnselected; - private Drawable iconSelected; +public class KeyChoiceAdapter extends FlexibleAdapter { private Integer activeItem; - public KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { - this.layoutInflater = layoutInflater; - this.resources = resources; + public KeyChoiceAdapter(boolean isMultiChoice, List items) { + super(getKeyChoiceItems(items)); + setMode(isMultiChoice ? Mode.MULTI : Mode.SINGLE); + addListener((OnItemClickListener) (view, position) -> onClickItem(position)); } - @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 data) { - this.data = data; - notifyDataSetChanged(); - } - - public void setSelectionDrawable(Drawable drawable) { - ConstantState constantState = drawable.getConstantState(); - if (constantState == null) { - return; + @Nullable + private static ArrayList getKeyChoiceItems(@Nullable List items) { + if (items == null) { + return null; } + ArrayList choiceItems = new ArrayList<>(); + for (UnifiedKeyInfo keyInfo : items) { + KeyChoiceItem keyChoiceItem = new KeyChoiceItem(keyInfo); + choiceItems.add(keyChoiceItem); + } + return choiceItems; + } - iconSelected = constantState.newDrawable(resources); - - iconUnselected = constantState.newDrawable(resources); - DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); - - notifyDataSetChanged(); + private boolean onClickItem(int position) { + if (getMode() == Mode.MULTI) { + toggleSelection(position); + notifyItemChanged(position); + } else { + setActiveItem(position); + } + return true; } public void setActiveItem(Integer newActiveItem) { + if (getMode() != Mode.SINGLE) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + Integer prevActiveItem = this.activeItem; this.activeItem = newActiveItem; @@ -87,20 +72,117 @@ public class KeyChoiceAdapter extends Adapter { } } - 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); + public UnifiedKeyInfo getActiveItem() { + if (getMode() != Mode.SINGLE) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + if (activeItem == null) { + return null; } - void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { + KeyChoiceItem item = getItem(activeItem); + return item == null ? null : item.keyInfo; + } + + public void setUnifiedKeyInfoItems(List keyInfos) { + List keyChoiceItems = getKeyChoiceItems(keyInfos); + updateDataSet(keyChoiceItems); + } + + @Override + public long getItemId(int position) { + KeyChoiceItem item = getItem(position); + if (item == null) { + return RecyclerView.NO_ID; + } + return item.getMasterKeyId(); + } + + public void setSelectionByIds(Set checkedIds) { + if (getMode() != Mode.MULTI) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + + clearSelection(); + for (int position = 0; position < getItemCount(); position++) { + long itemId = getItemId(position); + if (checkedIds.contains(itemId)) { + addSelection(position); + } + } + } + + public Set getSelectionIds() { + if (getMode() != Mode.MULTI) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + + Set result = new HashSet<>(); + for (int position : getSelectedPositions()) { + long itemId = getItemId(position); + result.add(itemId); + } + return result; + } + + public static class KeyChoiceItem extends AbstractFlexibleItem { + private UnifiedKeyInfo keyInfo; + + KeyChoiceItem(UnifiedKeyInfo keyInfo) { + this.keyInfo = keyInfo; + setSelectable(true); + } + + @Override + public int getLayoutRes() { + return R.layout.key_choice_item; + } + + @Override + public KeyChoiceViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new KeyChoiceViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, KeyChoiceViewHolder holder, int position, + List payloads) { + boolean isActive = adapter.isSelected(position); + holder.bind(keyInfo, adapter.getMode(), isActive); + } + + @Override + public boolean equals(Object o) { + return (o instanceof KeyChoiceItem) && + ((KeyChoiceItem) o).keyInfo.master_key_id() == keyInfo.master_key_id(); + } + + @Override + public int hashCode() { + long masterKeyId = keyInfo.master_key_id(); + return (int) (masterKeyId ^ (masterKeyId >>> 32)); + } + + public long getMasterKeyId() { + return keyInfo.master_key_id(); + } + } + + public static class KeyChoiceViewHolder extends FlexibleViewHolder { + private final TextView vName; + private final TextView vCreation; + private final CheckBox vCheckbox; + private final RadioButton vRadio; + + KeyChoiceViewHolder(View itemView, FlexibleAdapter adapter) { + super(itemView, adapter); + + vName = itemView.findViewById(R.id.text_keychoice_name); + vCreation = itemView.findViewById(R.id.text_keychoice_creation); + vCheckbox = itemView.findViewById(R.id.checkbox_keychoice); + vRadio = itemView.findViewById(R.id.radio_keychoice); + } + + void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive) { vName.setText(keyInfo.name()); Context context = vCreation.getContext(); @@ -109,7 +191,25 @@ public class KeyChoiceAdapter extends Adapter { DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); - vIcon.setImageDrawable(selectionIcon); + switch (choiceMode) { + case Mode.IDLE: { + vRadio.setVisibility(View.GONE); + vCheckbox.setVisibility(View.GONE); + break; + } + case Mode.SINGLE: { + vRadio.setVisibility(View.VISIBLE); + vRadio.setChecked(isActive); + vCheckbox.setVisibility(View.GONE); + break; + } + case Mode.MULTI: { + vCheckbox.setVisibility(View.VISIBLE); + vCheckbox.setChecked(isActive); + vRadio.setVisibility(View.GONE); + break; + } + } } } } diff --git a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml index 3621f5c5d..d2b1461b4 100644 --- a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml +++ b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml @@ -101,7 +101,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/duplicate_key_list" - tools:listitem="@layout/duplicate_key_item" + tools:listitem="@layout/key_choice_item" tools:layout_height="100dp" /> diff --git a/OpenKeychain/src/main/res/layout/duplicate_key_item.xml b/OpenKeychain/src/main/res/layout/key_choice_item.xml similarity index 57% rename from OpenKeychain/src/main/res/layout/duplicate_key_item.xml rename to OpenKeychain/src/main/res/layout/key_choice_item.xml index 5e8ed1477..f808b08ab 100644 --- a/OpenKeychain/src/main/res/layout/duplicate_key_item.xml +++ b/OpenKeychain/src/main/res/layout/key_choice_item.xml @@ -8,30 +8,42 @@ android:gravity="center_vertical" android:orientation="horizontal"> - + + + android:orientation="vertical" + android:layout_marginStart="12dp" + android:layout_marginLeft="12dp" + >