mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-22 23:09:13 +00:00
Use the hash of entry icons as keys for Glide caching
This is mostly a cleanup of the way we do Glide in-memory caching. It also fixes a few minor issues along the way: - Entry icon cache keys were based on entry UUID's. This could cause problems when changing an entry's icon. - A TextDrawable could get replaced by the icon of a different entry when scrolling through the entry list quickly.
This commit is contained in:
parent
566bcac3e0
commit
f1c9c6c5fc
15 changed files with 327 additions and 204 deletions
|
@ -1,26 +0,0 @@
|
|||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.os.Build;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.beemdevelopment.aegis.icons.IconType;
|
||||
|
||||
public class IconViewHelper {
|
||||
private IconViewHelper() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the layer type of the given ImageView based on the given IconType. If the
|
||||
* icon type is SVG and SDK <= 27, the layer type is set to software. Otherwise, it
|
||||
* is set to hardware.
|
||||
*/
|
||||
public static void setLayerType(ImageView view, IconType iconType) {
|
||||
if (iconType == IconType.SVG && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
|
||||
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
|
||||
return;
|
||||
}
|
||||
|
||||
view.setLayerType(ImageView.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ 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;
|
||||
|
@ -19,19 +20,20 @@ import com.beemdevelopment.aegis.helpers.MetricsHelper;
|
|||
import com.beemdevelopment.aegis.icons.IconPack;
|
||||
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.glide.GlideHelper;
|
||||
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.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||
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;
|
||||
|
@ -121,13 +123,14 @@ public class AssignIconsActivity extends AegisActivity implements AssignIconAdap
|
|||
ArrayList<UUID> uuids = new ArrayList<>();
|
||||
for (AssignIconEntry selectedEntry : _entries) {
|
||||
VaultEntry entry = selectedEntry.getEntry();
|
||||
if(selectedEntry.getNewIcon() != null) {
|
||||
if (selectedEntry.getNewIcon() != null) {
|
||||
byte[] iconBytes;
|
||||
try (FileInputStream inStream = new FileInputStream(selectedEntry.getNewIcon().getFile())){
|
||||
iconBytes = IOUtils.readFile(inStream);
|
||||
}
|
||||
|
||||
entry.setIcon(iconBytes, selectedEntry.getNewIcon().getIconType());
|
||||
VaultEntryIcon icon = new VaultEntryIcon(iconBytes, selectedEntry.getNewIcon().getIconType());
|
||||
entry.setIcon(icon);
|
||||
uuids.add(entry.getUUID());
|
||||
|
||||
_vaultManager.getVault().replaceEntry(entry);
|
||||
|
@ -223,12 +226,9 @@ public class AssignIconsActivity extends AegisActivity implements AssignIconAdap
|
|||
@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);
|
||||
RequestBuilder<Drawable> rb = Glide.with(AssignIconsActivity.this)
|
||||
.load(entry.getIcon());
|
||||
return GlideHelper.setCommonOptions(rb, entry.getIcon().getType());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,12 +246,9 @@ public class AssignIconsActivity extends AegisActivity implements AssignIconAdap
|
|||
@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);
|
||||
RequestBuilder<Drawable> rb = Glide.with(AssignIconsActivity.this)
|
||||
.load(icon.getFile());
|
||||
return GlideHelper.setCommonOptions(rb, icon.getIconType());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ import com.beemdevelopment.aegis.encoding.Hex;
|
|||
import com.beemdevelopment.aegis.helpers.AnimationsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.IconViewHelper;
|
||||
import com.beemdevelopment.aegis.helpers.SafHelper;
|
||||
import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener;
|
||||
import com.beemdevelopment.aegis.helpers.SimpleTextWatcher;
|
||||
|
@ -54,13 +53,14 @@ import com.beemdevelopment.aegis.otp.TotpInfo;
|
|||
import com.beemdevelopment.aegis.otp.YandexInfo;
|
||||
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.glide.GlideHelper;
|
||||
import com.beemdevelopment.aegis.ui.models.VaultGroupModel;
|
||||
import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
||||
import com.beemdevelopment.aegis.ui.views.IconAdapter;
|
||||
import com.beemdevelopment.aegis.util.Cloner;
|
||||
import com.beemdevelopment.aegis.util.IOUtils;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||
import com.beemdevelopment.aegis.vault.VaultGroup;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
import com.bumptech.glide.Glide;
|
||||
|
@ -239,19 +239,9 @@ public class EditEntryActivity extends AegisActivity {
|
|||
_advancedSettings = findViewById(R.id.expandableLayout);
|
||||
|
||||
// fill the fields with values if possible
|
||||
GlideHelper.loadEntryIcon(Glide.with(this), _origEntry, _iconView);
|
||||
if (_origEntry.hasIcon()) {
|
||||
IconViewHelper.setLayerType(_iconView, _origEntry.getIconType());
|
||||
Glide.with(this)
|
||||
.asDrawable()
|
||||
.load(_origEntry.getIcon())
|
||||
.set(IconLoader.ICON_TYPE, _origEntry.getIconType())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_iconView);
|
||||
_hasCustomIcon = true;
|
||||
} else {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
|
||||
_iconView.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
_textName.setText(_origEntry.getName());
|
||||
|
@ -548,14 +538,7 @@ public class EditEntryActivity extends AegisActivity {
|
|||
_hasCustomIcon = true;
|
||||
_hasChangedIcon = true;
|
||||
|
||||
IconViewHelper.setLayerType(_iconView, icon.getIconType());
|
||||
Glide.with(EditEntryActivity.this)
|
||||
.asDrawable()
|
||||
.load(icon.getFile())
|
||||
.set(IconLoader.ICON_TYPE, icon.getIconType())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_iconView);
|
||||
GlideHelper.loadIcon(Glide.with(EditEntryActivity.this), icon, _iconView);
|
||||
}
|
||||
|
||||
private void startEditingIcon(Uri data) {
|
||||
|
@ -743,13 +726,14 @@ public class EditEntryActivity extends AegisActivity {
|
|||
|
||||
if (_hasChangedIcon) {
|
||||
if (_hasCustomIcon) {
|
||||
VaultEntryIcon icon;
|
||||
if (_selectedIcon == null) {
|
||||
Bitmap bitmap = ((BitmapDrawable) _iconView.getDrawable()).getBitmap();
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
// the quality parameter is ignored for PNG
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||
byte[] data = stream.toByteArray();
|
||||
entry.setIcon(data, IconType.PNG);
|
||||
icon = new VaultEntryIcon(data, IconType.PNG);
|
||||
} else {
|
||||
byte[] iconBytes;
|
||||
try (FileInputStream inStream = new FileInputStream(_selectedIcon.getFile())){
|
||||
|
@ -757,11 +741,12 @@ public class EditEntryActivity extends AegisActivity {
|
|||
} catch (IOException e) {
|
||||
throw new ParseException(e.getMessage());
|
||||
}
|
||||
|
||||
entry.setIcon(iconBytes, _selectedIcon.getIconType());
|
||||
icon = new VaultEntryIcon(iconBytes, _selectedIcon.getIconType());
|
||||
}
|
||||
|
||||
entry.setIcon(icon);
|
||||
} else {
|
||||
entry.setIcon(null, IconType.INVALID);
|
||||
entry.setIcon(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,14 +18,13 @@ import androidx.recyclerview.widget.GridLayoutManager;
|
|||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.icons.IconPack;
|
||||
import com.beemdevelopment.aegis.ui.glide.IconLoader;
|
||||
import com.beemdevelopment.aegis.ui.glide.GlideHelper;
|
||||
import com.beemdevelopment.aegis.ui.views.IconAdapter;
|
||||
import com.beemdevelopment.aegis.ui.views.IconRecyclerView;
|
||||
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.BottomSheetBehavior;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
@ -76,12 +75,9 @@ public class IconPickerDialog {
|
|||
@Nullable
|
||||
@Override
|
||||
public RequestBuilder<Drawable> getPreloadRequestBuilder(@NonNull IconPack.Icon icon) {
|
||||
return Glide.with(dialog.getContext())
|
||||
.asDrawable()
|
||||
.load(icon.getFile())
|
||||
.set(IconLoader.ICON_TYPE, icon.getIconType())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false);
|
||||
RequestBuilder<Drawable> rb = Glide.with(dialog.getContext())
|
||||
.load(icon.getFile());
|
||||
return GlideHelper.setCommonOptions(rb, icon.getIconType());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.graphics.drawable.PictureDrawable;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.Registry;
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
|
@ -19,7 +20,7 @@ import java.nio.ByteBuffer;
|
|||
public class AegisGlideModule extends AppGlideModule {
|
||||
@Override
|
||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||
registry.prepend(VaultEntry.class, ByteBuffer.class, new IconLoader.Factory());
|
||||
registry.prepend(VaultEntryIcon.class, ByteBuffer.class, new VaultEntryIconLoader.Factory());
|
||||
registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
|
||||
.append(InputStream.class, SVG.class, new SvgDecoder())
|
||||
.append(ByteBuffer.class, SVG.class, new SvgBytesDecoder());
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package com.beemdevelopment.aegis.ui.glide;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RawRes;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
||||
import com.beemdevelopment.aegis.icons.IconPack;
|
||||
import com.beemdevelopment.aegis.icons.IconType;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.DrawableImageViewTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class GlideHelper {
|
||||
private GlideHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static void loadIconFile(RequestManager rm, File file, IconType iconType, ImageView targetView) {
|
||||
load(rm.load(file), iconType, targetView);
|
||||
}
|
||||
|
||||
public static void loadIcon(RequestManager rm, IconPack.Icon icon, ImageView targetView) {
|
||||
loadIconFile(rm, icon.getFile(), icon.getIconType(), targetView);
|
||||
}
|
||||
|
||||
public static void loadResource(RequestManager rm, @RawRes @DrawableRes @Nullable Integer resourceId, ImageView targetView) {
|
||||
loadResource(rm, resourceId, null, targetView);
|
||||
}
|
||||
|
||||
public static void loadResource(RequestManager rm, @RawRes @DrawableRes @Nullable Integer resourceId, @Nullable Integer tint, ImageView targetView) {
|
||||
setCommonOptions(rm.load(resourceId), null)
|
||||
.listener(new ViewReadyListener<>(view -> {
|
||||
if (tint != null) {
|
||||
view.setColorFilter(tint);
|
||||
}
|
||||
setLayerType(targetView, IconType.INVALID);
|
||||
}))
|
||||
.into(targetView);
|
||||
}
|
||||
|
||||
public static void loadEntryIcon(RequestManager rm, VaultEntry entry, ImageView targetView) {
|
||||
if (entry.hasIcon()) {
|
||||
setCommonOptions(rm.load(entry.getIcon()), entry.getIcon().getType()).into(targetView);
|
||||
} else {
|
||||
// Clear any pending loads for targetView, so that the TextDrawable
|
||||
// we're about to display doesn't get overwritten when that pending load finishes
|
||||
rm.clear(targetView);
|
||||
|
||||
setLayerType(targetView, IconType.INVALID);
|
||||
TextDrawable drawable = TextDrawableHelper.generate(entry.getIssuer(), entry.getName(), targetView);
|
||||
targetView.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void load(RequestBuilder<Drawable> rb, IconType iconType, ImageView targetView) {
|
||||
setCommonOptions(rb, iconType).into(targetView);
|
||||
}
|
||||
|
||||
public static RequestBuilder<Drawable> setCommonOptions(RequestBuilder<Drawable> rb, IconType iconType) {
|
||||
if (iconType != null) {
|
||||
rb = rb.set(VaultEntryIconLoader.ICON_TYPE, iconType)
|
||||
.listener(new ViewReadyListener<>(targetView -> {
|
||||
targetView.setImageTintList(null);
|
||||
setLayerType(targetView, iconType);
|
||||
}));
|
||||
}
|
||||
|
||||
return rb.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the layer type of the given ImageView based on the given IconType. If the
|
||||
* icon type is SVG and SDK <= 27, the layer type is set to software. Otherwise, it
|
||||
* is set to hardware.
|
||||
*/
|
||||
private static void setLayerType(ImageView view, IconType iconType) {
|
||||
if (iconType == IconType.SVG && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
|
||||
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
|
||||
return;
|
||||
}
|
||||
|
||||
view.setLayerType(ImageView.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
|
||||
private static class ViewReadyListener<T> implements RequestListener<T> {
|
||||
private final Listener<T> _listener;
|
||||
|
||||
public ViewReadyListener(Listener<T> listener) {
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target<T> target, boolean isFirstResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(@NonNull T resource, @NonNull Object model, Target<T> target, @NonNull DataSource dataSource, boolean isFirstResource) {
|
||||
if (target instanceof DrawableImageViewTarget) {
|
||||
DrawableImageViewTarget viewTarget = (DrawableImageViewTarget) target;
|
||||
if (_listener != null) {
|
||||
_listener.onConfigureImageView(viewTarget.getView());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface Listener<T> {
|
||||
void onConfigureImageView(ImageView targetView);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
|
|||
|
||||
@Override
|
||||
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
|
||||
return options.get(IconLoader.ICON_TYPE) == IconType.SVG;
|
||||
return options.get(VaultEntryIconLoader.ICON_TYPE) == IconType.SVG;
|
||||
}
|
||||
|
||||
public Resource<SVG> decode(
|
||||
|
|
|
@ -2,30 +2,30 @@ package com.beemdevelopment.aegis.ui.glide;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||
import com.bumptech.glide.load.Key;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDKey implements Key {
|
||||
private UUID _uuid;
|
||||
public class VaultEntryIconKey implements Key {
|
||||
private final VaultEntryIcon _icon;
|
||||
|
||||
public UUIDKey(UUID uuid) {
|
||||
_uuid = uuid;
|
||||
public VaultEntryIconKey(VaultEntryIcon icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
||||
messageDigest.update(_uuid.toString().getBytes(CHARSET));
|
||||
messageDigest.update(_icon.getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return _uuid.equals(o);
|
||||
return _icon.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return _uuid.hashCode();
|
||||
return _icon.hashCode();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package com.beemdevelopment.aegis.ui.glide;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.beemdevelopment.aegis.icons.IconType;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.Option;
|
||||
|
@ -15,29 +15,29 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class IconLoader implements ModelLoader<VaultEntry, ByteBuffer> {
|
||||
public class VaultEntryIconLoader implements ModelLoader<VaultEntryIcon, ByteBuffer> {
|
||||
public static final Option<IconType> ICON_TYPE = Option.memory("ICON_TYPE", IconType.INVALID);
|
||||
|
||||
@Override
|
||||
public LoadData<ByteBuffer> buildLoadData(@NonNull VaultEntry model, int width, int height, @NonNull Options options) {
|
||||
return new LoadData<>(new UUIDKey(model.getUUID()), new Fetcher(model));
|
||||
public LoadData<ByteBuffer> buildLoadData(@NonNull VaultEntryIcon icon, int width, int height, @NonNull Options options) {
|
||||
return new LoadData<>(new VaultEntryIconKey(icon), new Fetcher(icon));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handles(@NonNull VaultEntry model) {
|
||||
public boolean handles(@NonNull VaultEntryIcon icon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Fetcher implements DataFetcher<ByteBuffer> {
|
||||
private final VaultEntry _model;
|
||||
private final VaultEntryIcon _icon;
|
||||
|
||||
private Fetcher(VaultEntry model) {
|
||||
_model = model;
|
||||
private Fetcher(VaultEntryIcon icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
|
||||
byte[] bytes = _model.getIcon();
|
||||
byte[] bytes = _icon.getBytes();
|
||||
ByteBuffer buf = ByteBuffer.wrap(bytes);
|
||||
callback.onDataReady(buf);
|
||||
}
|
||||
|
@ -65,11 +65,11 @@ public class IconLoader implements ModelLoader<VaultEntry, ByteBuffer> {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<VaultEntry, ByteBuffer> {
|
||||
public static class Factory implements ModelLoaderFactory<VaultEntryIcon, ByteBuffer> {
|
||||
@NonNull
|
||||
@Override
|
||||
public ModelLoader<VaultEntry, ByteBuffer> build(@NonNull MultiModelLoaderFactory unused) {
|
||||
return new IconLoader();
|
||||
public ModelLoader<VaultEntryIcon, ByteBuffer> build(@NonNull MultiModelLoaderFactory unused) {
|
||||
return new VaultEntryIconLoader();
|
||||
}
|
||||
|
||||
@Override
|
|
@ -6,15 +6,10 @@ 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.glide.GlideHelper;
|
||||
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;
|
||||
|
@ -44,37 +39,15 @@ public class AssignIconHolder extends RecyclerView.ViewHolder implements AssignI
|
|||
_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);
|
||||
}
|
||||
|
||||
GlideHelper.loadEntryIcon(Glide.with(_view.getContext()), _entry.getEntry(), _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);
|
||||
GlideHelper.loadIcon(Glide.with(_view.getContext()), _entry.getNewIcon(), _newIcon);
|
||||
} else {
|
||||
Glide.with(_view.getContext())
|
||||
.asDrawable()
|
||||
.load(R.drawable.ic_icon_unselected)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_newIcon);
|
||||
GlideHelper.loadResource(Glide.with(_view.getContext()), R.drawable.ic_icon_unselected, _newIcon);
|
||||
}
|
||||
|
||||
_btnReset.setVisibility(_entry.getNewIcon() != null ? View.VISIBLE : View.INVISIBLE);
|
||||
|
|
|
@ -11,15 +11,12 @@ import android.widget.TextView;
|
|||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.beemdevelopment.aegis.AccountNamePosition;
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.ViewMode;
|
||||
import com.beemdevelopment.aegis.helpers.AnimationsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.IconViewHelper;
|
||||
import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener;
|
||||
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
||||
import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
||||
import com.beemdevelopment.aegis.helpers.UiRefresher;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
|
@ -28,10 +25,9 @@ import com.beemdevelopment.aegis.otp.OtpInfoException;
|
|||
import com.beemdevelopment.aegis.otp.SteamInfo;
|
||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.YandexInfo;
|
||||
import com.beemdevelopment.aegis.ui.glide.IconLoader;
|
||||
import com.beemdevelopment.aegis.ui.glide.GlideHelper;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
private static final float DEFAULT_ALPHA = 1.0f;
|
||||
|
@ -197,19 +193,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
public void loadIcon(Fragment fragment) {
|
||||
if (_entry.hasIcon()) {
|
||||
IconViewHelper.setLayerType(_profileDrawable, _entry.getIconType());
|
||||
Glide.with(fragment)
|
||||
.asDrawable()
|
||||
.load(_entry.getIcon())
|
||||
.set(IconLoader.ICON_TYPE, _entry.getIconType())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_profileDrawable);
|
||||
} else {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _profileDrawable);
|
||||
_profileDrawable.setImageDrawable(drawable);
|
||||
}
|
||||
GlideHelper.loadEntryIcon(Glide.with(fragment), _entry, _profileDrawable);
|
||||
}
|
||||
|
||||
public ImageView getIconView() {
|
||||
|
|
|
@ -23,8 +23,8 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
|||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.CopyBehavior;
|
||||
import com.beemdevelopment.aegis.AccountNamePosition;
|
||||
import com.beemdevelopment.aegis.CopyBehavior;
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.SortCategory;
|
||||
|
@ -36,7 +36,7 @@ import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
|||
import com.beemdevelopment.aegis.helpers.UiRefresher;
|
||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||
import com.beemdevelopment.aegis.ui.glide.IconLoader;
|
||||
import com.beemdevelopment.aegis.ui.glide.GlideHelper;
|
||||
import com.beemdevelopment.aegis.ui.models.VaultGroupModel;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
|
@ -45,7 +45,6 @@ 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 com.google.android.material.chip.Chip;
|
||||
|
@ -739,18 +738,16 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
if (!entry.hasIcon()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.singletonList(entry);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RequestBuilder<Drawable> getPreloadRequestBuilder(@NonNull VaultEntry entry) {
|
||||
return Glide.with(EntryListView.this)
|
||||
.asDrawable()
|
||||
.load(entry)
|
||||
.set(IconLoader.ICON_TYPE, entry.getIconType())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false);
|
||||
RequestBuilder<Drawable> rb = Glide.with(EntryListView.this)
|
||||
.load(entry.getIcon());
|
||||
return GlideHelper.setCommonOptions(rb, entry.getIcon().getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,11 @@ import android.widget.TextView;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.helpers.IconViewHelper;
|
||||
import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
||||
import com.beemdevelopment.aegis.icons.IconPack;
|
||||
import com.beemdevelopment.aegis.icons.IconType;
|
||||
import com.beemdevelopment.aegis.ui.glide.IconLoader;
|
||||
import com.beemdevelopment.aegis.ui.glide.GlideHelper;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
@ -42,18 +40,9 @@ public class IconHolder extends RecyclerView.ViewHolder {
|
|||
public void loadIcon(Context context) {
|
||||
if (_isCustom) {
|
||||
int tint = ThemeHelper.getThemeColor(R.attr.iconColorPrimary, context.getTheme());
|
||||
_imageView.setColorFilter(tint);
|
||||
_imageView.setImageResource(R.drawable.ic_plus_black_24dp);
|
||||
GlideHelper.loadResource(Glide.with(context), R.drawable.ic_plus_black_24dp, tint, _imageView);
|
||||
} else {
|
||||
_imageView.setImageTintList(null);
|
||||
IconViewHelper.setLayerType(_imageView, _iconType);
|
||||
Glide.with(context)
|
||||
.asDrawable()
|
||||
.load(_iconFile)
|
||||
.set(IconLoader.ICON_TYPE, _iconType)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_imageView);
|
||||
GlideHelper.loadIconFile(Glide.with(context), _iconFile, _iconType, _imageView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.icons.IconType;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
|
@ -14,7 +11,7 @@ import org.json.JSONArray;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
@ -23,8 +20,7 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
private String _name = "";
|
||||
private String _issuer = "";
|
||||
private OtpInfo _info;
|
||||
private byte[] _icon;
|
||||
private IconType _iconType = IconType.INVALID;
|
||||
private VaultEntryIcon _icon;
|
||||
private boolean _isFavorite;
|
||||
private int _usageCount;
|
||||
private String _note = "";
|
||||
|
@ -61,8 +57,7 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
obj.put("issuer", _issuer);
|
||||
obj.put("note", _note);
|
||||
obj.put("favorite", _isFavorite);
|
||||
obj.put("icon", _icon == null ? JSONObject.NULL : Base64.encode(_icon));
|
||||
obj.put("icon_mime", _icon == null ? null : _iconType.toMimeType());
|
||||
VaultEntryIcon.toJson(_icon, obj);
|
||||
obj.put("info", _info.toJson());
|
||||
|
||||
JSONArray groupUuids = new JSONArray();
|
||||
|
@ -107,21 +102,11 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
entry.setOldGroup(JsonUtils.optString(obj, "group"));
|
||||
}
|
||||
|
||||
Object icon = obj.get("icon");
|
||||
if (icon != JSONObject.NULL) {
|
||||
String mime = JsonUtils.optString(obj, "icon_mime");
|
||||
|
||||
IconType iconType = mime == null ? IconType.JPEG : IconType.fromMimeType(mime);
|
||||
if (iconType == IconType.INVALID) {
|
||||
throw new VaultEntryException(String.format("Bad icon MIME type: %s", mime));
|
||||
}
|
||||
|
||||
byte[] iconBytes = Base64.decode((String) icon);
|
||||
entry.setIcon(iconBytes, iconType);
|
||||
}
|
||||
VaultEntryIcon icon = VaultEntryIcon.fromJson(obj);
|
||||
entry.setIcon(icon);
|
||||
|
||||
return entry;
|
||||
} catch (OtpInfoException | JSONException | EncodingException e) {
|
||||
} catch (OtpInfoException | JSONException e) {
|
||||
throw new VaultEntryException(e);
|
||||
}
|
||||
}
|
||||
|
@ -138,14 +123,10 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
return _groups;
|
||||
}
|
||||
|
||||
public byte[] getIcon() {
|
||||
public VaultEntryIcon getIcon() {
|
||||
return _icon;
|
||||
}
|
||||
|
||||
public IconType getIconType() {
|
||||
return _iconType;
|
||||
}
|
||||
|
||||
public OtpInfo getInfo() {
|
||||
return _info;
|
||||
}
|
||||
|
@ -154,9 +135,15 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
return _usageCount;
|
||||
}
|
||||
|
||||
public String getNote() { return _note; }
|
||||
public String getNote() {
|
||||
return _note;
|
||||
}
|
||||
|
||||
public boolean isFavorite() { return _isFavorite; };
|
||||
public boolean isFavorite() {
|
||||
return _isFavorite;
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
public void setName(String name) {
|
||||
_name = name;
|
||||
|
@ -188,20 +175,25 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
_info = info;
|
||||
}
|
||||
|
||||
public void setIcon(byte[] icon, IconType iconType) {
|
||||
public void setIcon(VaultEntryIcon icon) {
|
||||
_icon = icon;
|
||||
_iconType = iconType;
|
||||
}
|
||||
|
||||
public boolean hasIcon() {
|
||||
return _icon != null;
|
||||
}
|
||||
|
||||
public void setUsageCount(int usageCount) { _usageCount = usageCount; }
|
||||
public void setUsageCount(int usageCount) {
|
||||
_usageCount = usageCount;
|
||||
}
|
||||
|
||||
public void setNote(String note) { _note = note; }
|
||||
public void setNote(String note) {
|
||||
_note = note;
|
||||
}
|
||||
|
||||
public void setIsFavorite(boolean isFavorite) { _isFavorite = isFavorite; }
|
||||
public void setIsFavorite(boolean isFavorite) {
|
||||
_isFavorite = isFavorite;
|
||||
}
|
||||
|
||||
void setOldGroup(String oldGroup) {
|
||||
_oldGroup = oldGroup;
|
||||
|
@ -230,8 +222,7 @@ public class VaultEntry extends UUIDMap.Value {
|
|||
return getName().equals(entry.getName())
|
||||
&& getIssuer().equals(entry.getIssuer())
|
||||
&& getInfo().equals(entry.getInfo())
|
||||
&& Arrays.equals(getIcon(), entry.getIcon())
|
||||
&& getIconType().equals(entry.getIconType())
|
||||
&& Objects.equals(getIcon(), entry.getIcon())
|
||||
&& getNote().equals(entry.getNote())
|
||||
&& isFavorite() == entry.isFavorite()
|
||||
&& getGroups().equals(entry.getGroups());
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.encoding.Hex;
|
||||
import com.beemdevelopment.aegis.icons.IconType;
|
||||
import com.beemdevelopment.aegis.util.JsonUtils;
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class VaultEntryIcon implements Serializable {
|
||||
private final byte[] _bytes;
|
||||
private final byte[] _hash;
|
||||
private final IconType _type;
|
||||
|
||||
public VaultEntryIcon(byte @NonNull [] bytes, @NonNull IconType type) {
|
||||
this(bytes, type, generateHash(bytes, type));
|
||||
}
|
||||
|
||||
VaultEntryIcon(byte @NonNull [] bytes, @NonNull IconType type, byte @NonNull [] hash) {
|
||||
_bytes = bytes;
|
||||
_hash = hash;
|
||||
_type = type;
|
||||
}
|
||||
|
||||
public byte @NonNull [] getBytes() {
|
||||
return _bytes;
|
||||
}
|
||||
|
||||
public byte @NonNull [] getHash() {
|
||||
return _hash;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public IconType getType() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof VaultEntryIcon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VaultEntryIcon entry = (VaultEntryIcon) o;
|
||||
return Arrays.equals(getHash(), entry.getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCode.fromBytes(_hash).asInt();
|
||||
}
|
||||
|
||||
static void toJson(@Nullable VaultEntryIcon icon, @NonNull JSONObject obj) throws JSONException {
|
||||
obj.put("icon", icon == null ? JSONObject.NULL : Base64.encode(icon.getBytes()));
|
||||
if (icon != null) {
|
||||
obj.put("icon_mime", icon.getType().toMimeType());
|
||||
obj.put("icon_hash", Hex.encode(icon.getHash()));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static VaultEntryIcon fromJson(@NonNull JSONObject obj) throws VaultEntryException {
|
||||
try {
|
||||
Object icon = obj.get("icon");
|
||||
if (icon == JSONObject.NULL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String mime = JsonUtils.optString(obj, "icon_mime");
|
||||
IconType iconType = mime == null ? IconType.JPEG : IconType.fromMimeType(mime);
|
||||
if (iconType == IconType.INVALID) {
|
||||
throw new VaultEntryException(String.format("Bad icon MIME type: %s", mime));
|
||||
}
|
||||
|
||||
byte[] iconBytes = Base64.decode((String) icon);
|
||||
String iconHashStr = JsonUtils.optString(obj, "icon_hash");
|
||||
if (iconHashStr != null) {
|
||||
byte[] iconHash = Hex.decode(iconHashStr);
|
||||
return new VaultEntryIcon(iconBytes, iconType, iconHash);
|
||||
}
|
||||
|
||||
return new VaultEntryIcon(iconBytes, iconType);
|
||||
} catch (JSONException | EncodingException e) {
|
||||
throw new VaultEntryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte @NonNull [] generateHash(byte @NonNull [] bytes, @NonNull IconType type) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(type.toMimeType().getBytes(StandardCharsets.UTF_8));
|
||||
return md.digest(bytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue