From 1a6f85ccb65d3586b4182176983bbdf58ca6f906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4ttgen?= Date: Thu, 31 Aug 2023 21:56:05 +0200 Subject: [PATCH] Add ability to assign icons More progress Open IconPicker dialog on click Add ability to reset Fix changing icons Cleanup Add ability to assign icons after import PR fixes --- app/src/main/AndroidManifest.xml | 2 + .../aegis/AssignIconsActivity.java | 278 ++++++++++++++++++ .../aegis/icons/IconPackManager.java | 4 + .../aegis/ui/EditEntryActivity.java | 4 +- .../aegis/ui/ImportEntriesActivity.java | 27 +- .../aegis/ui/MainActivity.java | 41 +++ .../aegis/ui/dialogs/IconPickerDialog.java | 6 +- .../aegis/ui/models/AssignIconEntry.java | 40 +++ .../aegis/ui/views/AssignIconAdapter.java | 55 ++++ .../aegis/ui/views/AssignIconHolder.java | 91 ++++++ .../aegis/ui/views/EntryHolder.java | 2 +- .../aegis/ui/views/IconAdapter.java | 10 +- .../res/drawable/baseline_arrow_right_24.xml | 5 + .../main/res/drawable/ic_icon_unselected.xml | 5 + app/src/main/res/drawable/ic_reset_image.xml | 9 + .../main/res/drawable/rounded_background.xml | 6 + .../main/res/layout/activity_assign_icons.xml | 27 ++ .../res/layout/card_assign_icon_entry.xml | 98 ++++++ app/src/main/res/menu/menu_action_mode.xml | 17 +- app/src/main/res/values/strings.xml | 5 + 20 files changed, 717 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/AssignIconsActivity.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/models/AssignIconEntry.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconAdapter.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java create mode 100644 app/src/main/res/drawable/baseline_arrow_right_24.xml create mode 100644 app/src/main/res/drawable/ic_icon_unselected.xml create mode 100644 app/src/main/res/drawable/ic_reset_image.xml create mode 100644 app/src/main/res/drawable/rounded_background.xml create mode 100644 app/src/main/res/layout/activity_assign_icons.xml create mode 100644 app/src/main/res/layout/card_assign_icon_entry.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 724e935c..215c62b0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,6 +82,8 @@ + _entries = new ArrayList<>(); + private RecyclerView _entriesView; + private AssignIconsActivity.BackPressHandler _backPressHandler; + private ViewPreloadSizeProvider _preloadSizeProvider; + private IconPack _favoriteIconPack; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } + + setContentView(R.layout.activity_assign_icons); + setSupportActionBar(findViewById(R.id.toolbar)); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + } + + ArrayList assignIconEntriesIds = (ArrayList) getIntent().getSerializableExtra("entries"); + for (UUID entryId: assignIconEntriesIds) { + VaultEntry vaultEntry = _vaultManager.getVault().getEntryByUUID(entryId); + _entries.add(new AssignIconEntry(vaultEntry)); + } + + _backPressHandler = new AssignIconsActivity.BackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _backPressHandler); + + IconPreloadProvider modelProvider1 = new IconPreloadProvider(); + EntryIconPreloadProvider modelProvider2 = new EntryIconPreloadProvider(); + _preloadSizeProvider = new ViewPreloadSizeProvider<>(); + RecyclerViewPreloader preloader1 = new RecyclerViewPreloader(this, modelProvider1, _preloadSizeProvider, 10); + RecyclerViewPreloader preloader2 = new RecyclerViewPreloader(this, modelProvider2, _preloadSizeProvider, 10); + + _adapter = new AssignIconAdapter(this); + _entriesView = findViewById(R.id.list_assign_icons); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + _entriesView.setLayoutManager(layoutManager); + _entriesView.setAdapter(_adapter); + _entriesView.setNestedScrollingEnabled(false); + _entriesView.addItemDecoration(new SpacesItemDecoration(8)); + _entriesView.addOnScrollListener(preloader1); + _entriesView.addOnScrollListener(preloader2); + + Optional favoriteIconPack = _iconPackManager.getIconPacks().stream() + .sorted(Comparator.comparing(IconPack::getName)) + .findFirst(); + + if (!favoriteIconPack.isPresent()) { + throw new RuntimeException(String.format("Started %s without any icon packs present", AssignIconsActivity.class.getName())); + } + + _favoriteIconPack = favoriteIconPack.get(); + + for (AssignIconEntry entry : _entries) { + IconPack.Icon suggestedIcon = findSuggestedIcon(entry); + if (suggestedIcon != null) { + entry.setNewIcon(suggestedIcon); + } + } + + _adapter.addEntries(_entries); + } + + private IconPack.Icon findSuggestedIcon(AssignIconEntry entry) { + List suggestedIcons = _favoriteIconPack.getSuggestedIcons(entry.getEntry().getIssuer()); + if (suggestedIcons.size() > 0) { + return suggestedIcons.get(0); + } + + return null; + } + + private void saveAndFinish() throws IOException { + ArrayList uuids = new ArrayList<>(); + for (AssignIconEntry selectedEntry : _entries) { + VaultEntry entry = selectedEntry.getEntry(); + if(selectedEntry.getNewIcon() != null) { + byte[] iconBytes; + try (FileInputStream inStream = new FileInputStream(selectedEntry.getNewIcon().getFile())){ + iconBytes = IOUtils.readFile(inStream); + } + + entry.setIcon(iconBytes, selectedEntry.getNewIcon().getIconType()); + uuids.add(entry.getUUID()); + + _vaultManager.getVault().replaceEntry(entry); + } + } + + Intent intent = new Intent(); + intent.putExtra("entryUUIDs", uuids); + + if (saveAndBackupVault()) { + setResult(RESULT_OK, intent); + finish(); + } + } + + private void discardAndFinish() { + Dialogs.showDiscardDialog(this, + (dialog, which) -> { + try { + saveAndFinish(); + } catch (IOException e) { + Toast.makeText(this, R.string.saving_assign_icons_error, Toast.LENGTH_SHORT).show(); + } + }, + (dialog, which) -> finish()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_groups, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + discardAndFinish(); + break; + case R.id.action_save: + try { + saveAndFinish(); + } catch (IOException e) { + Toast.makeText(this, R.string.saving_assign_icons_error, Toast.LENGTH_SHORT).show(); + } + break; + default: + return super.onOptionsItemSelected(item); + } + + return true; + } + + @Override + public void onAssignIconEntryClick(AssignIconEntry entry) { + BottomSheetDialog dialog = IconPickerDialog.create(this, Collections.singletonList(_favoriteIconPack), entry.getEntry().getIssuer(), false, new IconAdapter.Listener() { + @Override + public void onIconSelected(IconPack.Icon icon) { + entry.setNewIcon(icon); + } + + @Override + public void onCustomSelected() { } + }); + Dialogs.showSecureDialog(dialog); + } + + @Override + public void onSetPreloadView(View view) { + _preloadSizeProvider.setView(view); + } + + private class BackPressHandler extends OnBackPressedCallback { + public BackPressHandler() { + super(false); + } + + @Override + public void handleOnBackPressed() { + discardAndFinish(); + } + } + + private class EntryIconPreloadProvider implements ListPreloader.PreloadModelProvider { + @NonNull + @Override + public List getPreloadItems(int position) { + VaultEntry entry = _entries.get(position).getEntry(); + if (entry.hasIcon()) { + return Collections.singletonList(entry); + } + return Collections.emptyList(); + } + + @Nullable + @Override + public RequestBuilder getPreloadRequestBuilder(@NonNull VaultEntry entry) { + return Glide.with(AssignIconsActivity.this) + .asDrawable() + .load(entry) + .set(IconLoader.ICON_TYPE, entry.getIconType()) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(false); + } + } + + private class IconPreloadProvider implements ListPreloader.PreloadModelProvider { + @NonNull + @Override + public List getPreloadItems(int position) { + AssignIconEntry entry = _entries.get(position); + if (entry.getNewIcon() != null) { + return Collections.singletonList(entry.getNewIcon()); + } + return Collections.emptyList(); + } + + @Nullable + @Override + public RequestBuilder getPreloadRequestBuilder(@NonNull IconPack.Icon icon) { + return Glide.with(AssignIconsActivity.this) + .asDrawable() + .load(icon.getFile()) + .set(IconLoader.ICON_TYPE, icon.getIconType()) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(false); + } + } + + private class SpacesItemDecoration extends RecyclerView.ItemDecoration { + private final int _space; + + public SpacesItemDecoration(int dpSpace) { + + this._space = MetricsHelper.convertDpToPixels(AssignIconsActivity.this, dpSpace); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.left = _space; + outRect.right = _space; + outRect.bottom = _space; + + if (parent.getChildLayoutPosition(view) == 0) { + outRect.top = _space; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/beemdevelopment/aegis/icons/IconPackManager.java b/app/src/main/java/com/beemdevelopment/aegis/icons/IconPackManager.java index 05b2cf29..7aa9a292 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/icons/IconPackManager.java +++ b/app/src/main/java/com/beemdevelopment/aegis/icons/IconPackManager.java @@ -42,6 +42,10 @@ public class IconPackManager { return packs.get(0); } + public boolean hasIconPack() { + return _iconPacks.size() > 0; + } + public List getIconPacks() { return new ArrayList<>(_iconPacks); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index 95de25e2..1c6a9b3f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -216,7 +216,7 @@ public class EditEntryActivity extends AegisActivity { IconViewHelper.setLayerType(_iconView, _origEntry.getIconType()); Glide.with(this) .asDrawable() - .load(_origEntry) + .load(_origEntry.getIcon()) .set(IconLoader.ICON_TYPE, _origEntry.getIconType()) .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(false) @@ -500,7 +500,7 @@ public class EditEntryActivity extends AegisActivity { return; } - BottomSheetDialog dialog = IconPickerDialog.create(this, iconPacks, _textIssuer.getText().toString(), new IconAdapter.Listener() { + BottomSheetDialog dialog = IconPickerDialog.create(this, iconPacks, _textIssuer.getText().toString(), true, new IconAdapter.Listener() { @Override public void onIconSelected(IconPack.Icon icon) { selectIcon(icon); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java index 8e126189..570d6ce7 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -1,5 +1,6 @@ package com.beemdevelopment.aegis.ui; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.Menu; @@ -14,12 +15,14 @@ import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.beemdevelopment.aegis.AssignIconsActivity; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.helpers.FabScrollHelper; import com.beemdevelopment.aegis.importers.DatabaseImporter; import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException; import com.beemdevelopment.aegis.importers.DatabaseImporterException; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; +import com.beemdevelopment.aegis.ui.models.AssignIconEntry; import com.beemdevelopment.aegis.ui.models.ImportEntry; import com.beemdevelopment.aegis.ui.tasks.RootShellTask; import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter; @@ -244,8 +247,30 @@ public class ImportEntriesActivity extends AegisActivity { String toastMessage = getResources().getQuantityString(R.plurals.imported_entries_count, selectedEntries.size(), selectedEntries.size()); Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show(); + setResult(RESULT_OK, null); - finish(); + + if (_iconPackManager.hasIconPack()) { + ArrayList assignIconEntriesIds = new ArrayList<>(); + Intent assignIconIntent = new Intent(getBaseContext(), AssignIconsActivity.class); + for (ImportEntry entry : selectedEntries) { + assignIconEntriesIds.add(entry.getEntry().getUUID()); + } + + assignIconIntent.putExtra("entries", assignIconEntriesIds); + + Dialogs.showSecureDialog(new AlertDialog.Builder(this) + .setTitle(R.string.import_assign_icons_dialog_title) + .setMessage(R.string.import_assign_icons_dialog_text) + .setPositiveButton(android.R.string.yes, (dialog, which) -> { + startActivity(assignIconIntent); + finish(); + }) + .setNegativeButton(android.R.string.no, ((dialogInterface, i) -> finish())) + .create()); + } else { + finish(); + } } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index a43b1643..58face0c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -35,6 +35,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.SearchView; +import com.beemdevelopment.aegis.AssignIconsActivity; import com.beemdevelopment.aegis.CopyBehavior; import com.beemdevelopment.aegis.AccountNamePosition; import com.beemdevelopment.aegis.Preferences; @@ -76,6 +77,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private static final int CODE_DECRYPT = 4; private static final int CODE_PREFERENCES = 5; private static final int CODE_SCAN_IMAGE = 6; + private static final int CODE_ASSIGN_ICONS = 7; + // Permission request codes private static final int CODE_PERM_CAMERA = 0; @@ -232,6 +235,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene break; case CODE_SCAN_IMAGE: onScanImageResult(data); + break; + case CODE_ASSIGN_ICONS: + onAssignEntriesResult(data); + break; } super.onActivityResult(requestCode, resultCode, data); @@ -317,6 +324,17 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene startActivityForResult(intent, requestCode); } + private void startAssignIconsActivity(int requestCode, List entries) { + ArrayList assignIconEntriesIds = new ArrayList<>(); + Intent assignIconIntent = new Intent(getBaseContext(), AssignIconsActivity.class); + for (VaultEntry entry : entries) { + assignIconEntriesIds.add(entry.getUUID()); + } + + assignIconIntent.putExtra("entries", assignIconEntriesIds); + startActivityForResult(assignIconIntent, requestCode); + } + private void startIntroActivity() { if (!_isDoingIntro) { Intent intro = new Intent(this, IntroActivity.class); @@ -353,6 +371,17 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene } } + private void onAssignEntriesResult(Intent data) { + if (_loaded) { + ArrayList entryUUIDs = (ArrayList) data.getSerializableExtra("entryUUIDs"); + + for (UUID entryUUID: entryUUIDs) { + VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID); + _entryListView.replaceEntry(entryUUID, entry); + } + } + } + private void onScanImageResult(Intent intent) { if (intent.getData() != null) { startDecodeQrCodeImages(Collections.singletonList(intent.getData())); @@ -916,6 +945,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _actionMode.getMenu().findItem(R.id.action_copy).setVisible(!multipleSelected); } + private void setAssignIconsMenuItemVisibility() { + MenuItem assignIconsMenuItem = _actionMode.getMenu().findItem(R.id.action_assign_icons); + assignIconsMenuItem.setVisible(_iconPackManager.hasIconPack()); + } + private void setFavoriteMenuItemVisiblity() { MenuItem toggleFavoriteMenuItem = _actionMode.getMenu().findItem(R.id.action_toggle_favorite); @@ -948,6 +982,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _actionMode = startSupportActionMode(_actionModeCallbacks); _actionModeBackPressHandler.setEnabled(true); setFavoriteMenuItemVisiblity(); + setAssignIconsMenuItemVisibility(); } @Override @@ -1142,6 +1177,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene mode.finish(); }); return true; + + case R.id.action_assign_icons: + startAssignIconsActivity(CODE_ASSIGN_ICONS, _selectedEntries); + mode.finish(); + return true; + default: return false; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java b/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java index fbcd148d..ae9ecaa2 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java @@ -40,7 +40,7 @@ public class IconPickerDialog { } - public static BottomSheetDialog create(Activity activity, List iconPacks, String issuer, IconAdapter.Listener listener) { + public static BottomSheetDialog create(Activity activity, List iconPacks, String issuer, boolean showAddCustom, IconAdapter.Listener listener) { View view = LayoutInflater.from(activity).inflate(R.layout.dialog_icon_picker, null); TextView textIconPack = view.findViewById(R.id.text_icon_pack); @@ -128,7 +128,7 @@ public class IconPickerDialog { recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); recyclerView.addOnScrollListener(preloader); - adapter.loadIcons(iconPacks.get(0)); + adapter.loadIcons(iconPacks.get(0), showAddCustom); textIconPack.setText(iconPacks.get(0).getName()); view.findViewById(R.id.btn_icon_pack).setOnClickListener(v -> { @@ -139,7 +139,7 @@ public class IconPickerDialog { PopupMenu popupMenu = new PopupMenu(activity, v); popupMenu.setOnMenuItemClickListener(item -> { IconPack pack = iconPacks.get(iconPackNames.indexOf(item.getTitle().toString())); - adapter.loadIcons(pack); + adapter.loadIcons(pack, showAddCustom); String query = iconSearch.getText().toString(); if (!query.isEmpty()) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/models/AssignIconEntry.java b/app/src/main/java/com/beemdevelopment/aegis/ui/models/AssignIconEntry.java new file mode 100644 index 00000000..04e74a4b --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/models/AssignIconEntry.java @@ -0,0 +1,40 @@ +package com.beemdevelopment.aegis.ui.models; + +import com.beemdevelopment.aegis.icons.IconPack; +import com.beemdevelopment.aegis.vault.VaultEntry; + +import java.io.Serializable; + +public class AssignIconEntry implements Serializable { + private final VaultEntry _entry; + + private IconPack.Icon _newIcon; + + private transient AssignIconEntry.Listener _listener; + + public void setOnResetListener(AssignIconEntry.Listener listener) { + _listener = listener; + } + + public AssignIconEntry(VaultEntry entry) { + _entry = entry; + } + + public VaultEntry getEntry() { + return _entry; + } + + public IconPack.Icon getNewIcon() { return _newIcon; } + + public void setNewIcon(IconPack.Icon icon) { + _newIcon = icon; + + if (_listener != null) { + _listener.onNewIconChanged(); + } + } + + public interface Listener { + void onNewIconChanged(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconAdapter.java new file mode 100644 index 00000000..40e935a1 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconAdapter.java @@ -0,0 +1,55 @@ +package com.beemdevelopment.aegis.ui.views; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.ui.models.AssignIconEntry; + +import java.util.ArrayList; +import java.util.Collection; + +public class AssignIconAdapter extends RecyclerView.Adapter { + private AssignIconAdapter.Listener _listener; + private ArrayList _entries; + + public AssignIconAdapter(AssignIconAdapter.Listener listener) { + _listener = listener; + _entries = new ArrayList<>(); + } + + public void addEntries(Collection entries) { + _entries.addAll(entries); + } + + @Override + public AssignIconHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_assign_icon_entry, parent, false); + AssignIconHolder holder = new AssignIconHolder(view); + // NOTE: This assumes that the old and new icon views are the same size + _listener.onSetPreloadView(holder.getOldIconView()); + return holder; + } + + @Override + public void onBindViewHolder(AssignIconHolder holder, int position) { + holder.setData(_entries.get(position)); + holder.itemView.setOnClickListener(view -> { + _listener.onAssignIconEntryClick(_entries.get(position)); + }); + _entries.get(position).setOnResetListener(holder); + } + + @Override + public int getItemCount() { + return _entries.size(); + } + + public interface Listener { + void onAssignIconEntryClick(AssignIconEntry entry); + void onSetPreloadView(View view); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java new file mode 100644 index 00000000..658eb152 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java @@ -0,0 +1,91 @@ +package com.beemdevelopment.aegis.ui.views; + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.amulyakhare.textdrawable.TextDrawable; +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.helpers.TextDrawableHelper; +import com.beemdevelopment.aegis.icons.IconType; +import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.models.AssignIconEntry; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; + +public class AssignIconHolder extends RecyclerView.ViewHolder implements AssignIconEntry.Listener { + private View _view; + + private AssignIconEntry _entry; + private TextView _issuer; + private TextView _accountName; + private ImageView _oldIcon; + private ImageView _newIcon; + private ImageView _btnReset; + + public AssignIconHolder(final View view) { + super(view); + + _view = view.findViewById(R.id.rlCardEntry); + + _issuer = view.findViewById(R.id.tvIssuer); + _accountName = view.findViewById(R.id.tvAccountName); + _oldIcon = view.findViewById(R.id.ivOldImage); + _newIcon = view.findViewById(R.id.ivNewImage); + _btnReset = view.findViewById(R.id.btnReset); + _btnReset.setOnClickListener(l -> _entry.setNewIcon(null)); + } + + public void setData(AssignIconEntry entry) { + _entry = entry; + _issuer.setText(entry.getEntry().getIssuer()); + _accountName.setText(entry.getEntry().getName()); + + if(!entry.getEntry().hasIcon()) { + TextDrawable drawable = TextDrawableHelper.generate(entry.getEntry().getIssuer(), entry.getEntry().getName(), _oldIcon); + _oldIcon.setImageDrawable(drawable); + } else { + Glide.with(_view.getContext()) + .asDrawable() + .load(entry.getEntry()) + .set(IconLoader.ICON_TYPE, entry.getEntry().getIconType()) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .into(_oldIcon); + } + + setNewIcon(); + } + + private void setNewIcon() { + if (_entry.getNewIcon() != null) { + Glide.with(_view.getContext()) + .asDrawable() + .load(_entry.getNewIcon().getFile()) + .set(IconLoader.ICON_TYPE, _entry.getNewIcon().getIconType()) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(false) + .into(_newIcon); + } else { + Glide.with(_view.getContext()) + .asDrawable() + .load(R.drawable.ic_icon_unselected) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(false) + .into(_newIcon); + } + + _btnReset.setVisibility(_entry.getNewIcon() != null ? View.VISIBLE : View.INVISIBLE); + } + + public View getOldIconView() { + return _oldIcon; + } + + @Override + public void onNewIconChanged() { + setNewIcon(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java index 25bdf2ab..0bb2e457 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java @@ -200,7 +200,7 @@ public class EntryHolder extends RecyclerView.ViewHolder { IconViewHelper.setLayerType(_profileDrawable, _entry.getIconType()); Glide.with(fragment) .asDrawable() - .load(_entry) + .load(_entry.getIcon()) .set(IconLoader.ICON_TYPE, _entry.getIconType()) .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(false) diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconAdapter.java index a35e7a01..671c4501 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconAdapter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconAdapter.java @@ -40,7 +40,7 @@ public class IconAdapter extends RecyclerView.Adapter { /** * Loads all icons from the given icon pack into this adapter. Any icons added before this call will be overwritten. */ - public void loadIcons(IconPack pack) { + public void loadIcons(IconPack pack, boolean showAddCustom) { _pack = pack; _query = null; _icons = new ArrayList<>(_pack.getIcons()); @@ -60,7 +60,11 @@ public class IconAdapter extends RecyclerView.Adapter { .count(); List suggested = pack.getSuggestedIcons(_issuer); - suggested.add(0, new DummyIcon(_context.getString(R.string.icon_custom))); + + if (showAddCustom) { + suggested.add(0, new DummyIcon(_context.getString(R.string.icon_custom))); + } + if (suggested.size() > 0) { CategoryHeader category = new CategoryHeader(_context.getString(R.string.suggested)); category.setIsCollapsed(false); @@ -90,7 +94,7 @@ public class IconAdapter extends RecyclerView.Adapter { _query = query; if (_query == null) { - loadIcons(_pack); + loadIcons(_pack, false); } else { _icons = _pack.getIcons().stream() .filter(i -> i.isSuggestedFor(query)) diff --git a/app/src/main/res/drawable/baseline_arrow_right_24.xml b/app/src/main/res/drawable/baseline_arrow_right_24.xml new file mode 100644 index 00000000..e3b8f37b --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_right_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_icon_unselected.xml b/app/src/main/res/drawable/ic_icon_unselected.xml new file mode 100644 index 00000000..d60f1680 --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_unselected.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_reset_image.xml b/app/src/main/res/drawable/ic_reset_image.xml new file mode 100644 index 00000000..c42849c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_reset_image.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/rounded_background.xml b/app/src/main/res/drawable/rounded_background.xml new file mode 100644 index 00000000..e2a884d8 --- /dev/null +++ b/app/src/main/res/drawable/rounded_background.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_assign_icons.xml b/app/src/main/res/layout/activity_assign_icons.xml new file mode 100644 index 00000000..3cef9268 --- /dev/null +++ b/app/src/main/res/layout/activity_assign_icons.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_assign_icon_entry.xml b/app/src/main/res/layout/card_assign_icon_entry.xml new file mode 100644 index 00000000..a9232419 --- /dev/null +++ b/app/src/main/res/layout/card_assign_icon_entry.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_action_mode.xml b/app/src/main/res/menu/menu_action_mode.xml index bea4f31b..057988ab 100644 --- a/app/src/main/res/menu/menu_action_mode.xml +++ b/app/src/main/res/menu/menu_action_mode.xml @@ -22,18 +22,25 @@ app:showAsAction="always" /> + app:showAsAction="never"/> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65204e97..86b71839 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -171,6 +171,7 @@ Set up biometric unlock Copy Edit + Assign icons Favorite Unfavorite ERROR @@ -205,6 +206,7 @@ Discard changes? Your changes have not been saved Error saving profile + Error assigning icons Welcome Aegis is a free, secure and open source 2FA app Setup completed @@ -461,6 +463,7 @@ Scan a QR code Manage key slots Import entries + Assign icons Wipe entries Your vault already contains entries. Do you want to remove these entries before importing this file?\n\nIn doing so, you will permanently lose access to the existing entries in the vault. Wipe vault contents @@ -491,6 +494,8 @@ Steam v3.0 and newer are not supported. Supply a copy of /data/data/com.valvesoftware.android.steam.community/files/Steamguard-*.json, located in the internal storage directory of Steam. Supply a TOTP Authenticator export file. Supply a WinAuth export file. + Assign icons + Do you want to assign icons to the imported entries? Encrypted entry was skipped: %s