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
This commit is contained in:
Michael Schättgen 2023-08-31 21:56:05 +02:00
parent b916697391
commit 1a6f85ccb6
20 changed files with 717 additions and 15 deletions

View File

@ -82,6 +82,8 @@
<activity
android:name=".ui.GroupManagerActivity"
android:label="@string/title_activity_manage_groups" />
<activity android:name=".AssignIconsActivity"
android:label="@string/title_activity_assign_icons"/>
<activity
android:name=".ui.PanicResponderActivity"
android:exported="true"

View File

@ -0,0 +1,278 @@
package com.beemdevelopment.aegis;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.helpers.MetricsHelper;
import com.beemdevelopment.aegis.icons.IconPack;
import com.beemdevelopment.aegis.ui.AegisActivity;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.ui.dialogs.IconPickerDialog;
import com.beemdevelopment.aegis.ui.glide.IconLoader;
import com.beemdevelopment.aegis.ui.models.AssignIconEntry;
import com.beemdevelopment.aegis.ui.views.AssignIconAdapter;
import com.beemdevelopment.aegis.ui.views.IconAdapter;
import com.beemdevelopment.aegis.util.IOUtils;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.util.ViewPreloadSizeProvider;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class AssignIconsActivity extends AegisActivity implements AssignIconAdapter.Listener {
private AssignIconAdapter _adapter;
private ArrayList<AssignIconEntry> _entries = new ArrayList<>();
private RecyclerView _entriesView;
private AssignIconsActivity.BackPressHandler _backPressHandler;
private ViewPreloadSizeProvider<AssignIconEntry> _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<UUID> assignIconEntriesIds = (ArrayList<UUID>) 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<IconPack.Icon> preloader1 = new RecyclerViewPreloader(this, modelProvider1, _preloadSizeProvider, 10);
RecyclerViewPreloader<VaultEntry> 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<IconPack> 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<IconPack.Icon> suggestedIcons = _favoriteIconPack.getSuggestedIcons(entry.getEntry().getIssuer());
if (suggestedIcons.size() > 0) {
return suggestedIcons.get(0);
}
return null;
}
private void saveAndFinish() throws IOException {
ArrayList<UUID> 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<VaultEntry> {
@NonNull
@Override
public List<VaultEntry> getPreloadItems(int position) {
VaultEntry entry = _entries.get(position).getEntry();
if (entry.hasIcon()) {
return Collections.singletonList(entry);
}
return Collections.emptyList();
}
@Nullable
@Override
public RequestBuilder<Drawable> 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<IconPack.Icon> {
@NonNull
@Override
public List<IconPack.Icon> getPreloadItems(int position) {
AssignIconEntry entry = _entries.get(position);
if (entry.getNewIcon() != null) {
return Collections.singletonList(entry.getNewIcon());
}
return Collections.emptyList();
}
@Nullable
@Override
public RequestBuilder<Drawable> 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;
}
}
}
}

View File

@ -42,6 +42,10 @@ public class IconPackManager {
return packs.get(0);
}
public boolean hasIconPack() {
return _iconPacks.size() > 0;
}
public List<IconPack> getIconPacks() {
return new ArrayList<>(_iconPacks);
}

View File

@ -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);

View File

@ -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<UUID> 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();
}
}
}

View File

@ -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<VaultEntry> entries) {
ArrayList<UUID> 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<UUID> entryUUIDs = (ArrayList<UUID>) 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;
}

View File

@ -40,7 +40,7 @@ public class IconPickerDialog {
}
public static BottomSheetDialog create(Activity activity, List<IconPack> iconPacks, String issuer, IconAdapter.Listener listener) {
public static BottomSheetDialog create(Activity activity, List<IconPack> 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()) {

View File

@ -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();
}
}

View File

@ -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<AssignIconHolder> {
private AssignIconAdapter.Listener _listener;
private ArrayList<AssignIconEntry> _entries;
public AssignIconAdapter(AssignIconAdapter.Listener listener) {
_listener = listener;
_entries = new ArrayList<>();
}
public void addEntries(Collection<AssignIconEntry> 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);
}
}

View File

@ -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();
}
}

View File

@ -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)

View File

@ -40,7 +40,7 @@ public class IconAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* 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<RecyclerView.ViewHolder> {
.count();
List<IconPack.Icon> 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<RecyclerView.ViewHolder> {
_query = query;
if (_query == null) {
loadIcons(_pack);
loadIcons(_pack, false);
} else {
_icons = _pack.getIcons().stream()
.filter(i -> i.isSuggestedFor(query))

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,17l5,-5 -5,-5v10z"/>
</vector>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M120,360L120,120L200,120L200,254Q250,192 322.5,156Q395,120 480,120Q598,120 690.5,187Q783,254 820,360L733,360Q699,288 632,244Q565,200 480,200Q423,200 372.5,221Q322,242 284,280L360,280L360,360L120,360ZM240,720L720,720L570,520L450,680L360,560L240,720ZM200,880Q167,880 143.5,856.5Q120,833 120,800L120,480L200,480L200,800Q200,800 200,800Q200,800 200,800L760,800Q760,800 760,800Q760,800 760,800L760,480L840,480L840,800Q840,833 816.5,856.5Q793,880 760,880L200,880Z"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/cardBackground"/>
<corners android:radius="4dp"/>
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp"/>
</shape>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/background"
tools:context="com.beemdevelopment.aegis.AssignIconsActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.Aegis.AppBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorAppBar" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_assign_icons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_height="wrap_content"
android:background="@drawable/rounded_background"
android:elevation="4dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:background="?attr/cardBackground"
android:id="@+id/rlCardEntry"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="14dp">
<!-- Title -->
<TextView
android:id="@+id/tvIssuer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textColor="?attr/primaryText"
android:text="Issuer"
android:textStyle="bold"
android:textSize="18sp"/>
<TextView
android:id="@+id/tvAccountName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/secondary_text"
android:text="AccountName"
android:textSize="14sp"
android:layout_marginBottom="8dp"/>
<!-- Row with 3 columns -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivOldImage"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center_vertical" />
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
app:tint="?attr/iconColorPrimary"
android:alpha="0.7"
android:src="@drawable/baseline_arrow_right_24"
android:layout_marginStart="5dp"
android:layout_marginEnd="15dp"
android:layout_gravity="center_vertical"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivNewImage"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center_vertical" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/btnReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_reset_image"
app:tint="?attr/iconColorPrimary"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:paddingTop="12.5dp"
android:paddingBottom="12.5dp"
android:foreground="?android:attr/selectableItemBackground"
android:layout_gravity="center_vertical"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -22,18 +22,25 @@
app:showAsAction="always" />
<item
android:id="@+id/action_delete"
android:title="@string/action_delete"
android:id="@+id/action_assign_icons"
android:title="@string/assign_icons"
android:orderInCategory="100"
android:icon="@drawable/ic_delete_white"
android:tint="?attr/iconColorPrimary"
app:showAsAction="ifRoom"/>
app:showAsAction="never"/>
<item
android:id="@+id/action_share_qr"
android:title="@string/action_transfer"
android:orderInCategory="100"
android:orderInCategory="110"
android:icon="@drawable/ic_qr_code_full"
android:tint="?attr/iconColorPrimary"
app:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:title="@string/action_delete"
android:orderInCategory="400"
android:icon="@drawable/ic_delete_white"
android:tint="?attr/iconColorPrimary"
app:showAsAction="ifRoom"/>
</menu>

View File

@ -171,6 +171,7 @@
<string name="set_up_biometric">Set up biometric unlock</string>
<string name="copy">Copy</string>
<string name="edit">Edit</string>
<string name="assign_icons">Assign icons</string>
<string name="favorite" comment="Verb">Favorite</string>
<string name="unfavorite" comment="Verb">Unfavorite</string>
<string name="error_all_caps">ERROR</string>
@ -205,6 +206,7 @@
<string name="discard_changes">Discard changes?</string>
<string name="discard_changes_description">Your changes have not been saved</string>
<string name="saving_profile_error">Error saving profile</string>
<string name="saving_assign_icons_error">Error assigning icons</string>
<string name="welcome">Welcome</string>
<string name="app_description">Aegis is a free, secure and open source 2FA app</string>
<string name="setup_completed">Setup completed</string>
@ -461,6 +463,7 @@
<string name="title_activity_scan_qr">Scan a QR code</string>
<string name="title_activity_manage_slots">Manage key slots</string>
<string name="title_activity_import_entries">Import entries</string>
<string name="title_activity_assign_icons">Assign icons</string>
<string name="dialog_wipe_entries_title">Wipe entries</string>
<string name="dialog_wipe_entries_message">Your vault already contains entries. Do you want to remove these entries before importing this file?\n\n<b>In doing so, you will permanently lose access to the existing entries in the vault.</b></string>
<string name="dialog_wipe_entries_checkbox">Wipe vault contents</string>
@ -491,6 +494,8 @@
<string name="importer_help_steam"><b>Steam v3.0 and newer are not supported</b>. Supply a copy of <b>/data/data/com.valvesoftware.android.steam.community/files/Steamguard-*.json</b>, located in the internal storage directory of Steam.</string>
<string name="importer_help_totp_authenticator">Supply a TOTP Authenticator export file.</string>
<string name="importer_help_winauth">Supply a WinAuth export file.</string>
<string name="import_assign_icons_dialog_title">Assign icons</string>
<string name="import_assign_icons_dialog_text">Do you want to assign icons to the imported entries?</string>
<string name="importer_encrypted_exception_google_authenticator">Encrypted entry was skipped: %s</string>