Implement icon packs (WIP), Add FLAG_SECURE to dialogs
This commit is contained in:
parent
80ffea7a10
commit
1ee9a6fabe
@ -96,4 +96,6 @@ dependencies {
|
|||||||
implementation 'com.google.protobuf:protobuf-javalite:3.24.3'
|
implementation 'com.google.protobuf:protobuf-javalite:3.24.3'
|
||||||
|
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
|
|
||||||
|
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ import com.cringe_studios.cringe_authenticator.fragment.GroupFragment;
|
|||||||
import com.cringe_studios.cringe_authenticator.fragment.HomeFragment;
|
import com.cringe_studios.cringe_authenticator.fragment.HomeFragment;
|
||||||
import com.cringe_studios.cringe_authenticator.fragment.NamedFragment;
|
import com.cringe_studios.cringe_authenticator.fragment.NamedFragment;
|
||||||
import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment;
|
import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment;
|
||||||
|
import com.cringe_studios.cringe_authenticator.icon.IconPackException;
|
||||||
|
import com.cringe_studios.cringe_authenticator.icon.IconPackMetadata;
|
||||||
|
import com.cringe_studios.cringe_authenticator.icon.IconUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
import com.cringe_studios.cringe_authenticator.scanner.QRScanner;
|
import com.cringe_studios.cringe_authenticator.scanner.QRScanner;
|
||||||
import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract;
|
import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract;
|
||||||
@ -56,6 +59,8 @@ public class MainActivity extends BaseActivity {
|
|||||||
|
|
||||||
private Consumer<Uri> pickBackupFileLoadCallback;
|
private Consumer<Uri> pickBackupFileLoadCallback;
|
||||||
|
|
||||||
|
private ActivityResultLauncher<String[]> pickIconPackFileLoad;
|
||||||
|
|
||||||
private QRScanner qrScanner;
|
private QRScanner qrScanner;
|
||||||
|
|
||||||
private boolean fullyLaunched;
|
private boolean fullyLaunched;
|
||||||
@ -131,6 +136,16 @@ public class MainActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pickIconPackFileLoad = registerForActivityResult(new ActivityResultContracts.OpenDocument(), doc -> {
|
||||||
|
try {
|
||||||
|
if(doc == null) return;
|
||||||
|
IconPackMetadata meta = IconUtil.importIconPack(this, doc);
|
||||||
|
DialogUtil.showErrorDialog(this, "Icon pack contains " + meta.getIcons().length + " icons");
|
||||||
|
} catch (IconPackException e) {
|
||||||
|
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if(SettingsUtil.isFirstLaunch(this) && SettingsUtil.getGroups(this).isEmpty()) {
|
if(SettingsUtil.isFirstLaunch(this) && SettingsUtil.getGroups(this).isEmpty()) {
|
||||||
SettingsUtil.addGroup(this, UUID.randomUUID().toString(), "My Codes");
|
SettingsUtil.addGroup(this, UUID.randomUUID().toString(), "My Codes");
|
||||||
SettingsUtil.setFirstLaunch(this, false);
|
SettingsUtil.setFirstLaunch(this, false);
|
||||||
@ -339,6 +354,11 @@ public class MainActivity extends BaseActivity {
|
|||||||
pickBackupFileLoad.launch(new String[]{"application/json", "*/*"});
|
pickBackupFileLoad.launch(new String[]{"application/json", "*/*"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void promptPickIconPackLoad() {
|
||||||
|
this.lockOnStop = false;
|
||||||
|
pickIconPackFileLoad.launch(new String[]{"application/zip", "*/*"});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recreate() {
|
public void recreate() {
|
||||||
lockOnStop = false;
|
lockOnStop = false;
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
public class Icon {
|
||||||
|
|
||||||
|
private IconMetadata metadata;
|
||||||
|
private byte[] bytes;
|
||||||
|
|
||||||
|
public Icon(IconMetadata metadata, byte[] bytes) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
this.bytes = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes() {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
public class IconMetadata {
|
||||||
|
|
||||||
|
private String filename;
|
||||||
|
private String category;
|
||||||
|
private String[] issuer;
|
||||||
|
|
||||||
|
private IconMetadata() {}
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getIssuer() {
|
||||||
|
return issuer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
public class IconPack {
|
||||||
|
|
||||||
|
private IconPackMetadata metadata;
|
||||||
|
private Icon[] icons;
|
||||||
|
|
||||||
|
public IconPack(IconPackMetadata metadata, Icon[] icons) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
this.icons = icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPackMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Icon[] getIcons() {
|
||||||
|
return icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Icon findIconForIssuer(String issuer) {
|
||||||
|
for(Icon icon : icons) {
|
||||||
|
for(String i : icon.getMetadata().getIssuer()) {
|
||||||
|
if(issuer.equals(i)) {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
public class IconPackException extends Exception {
|
||||||
|
public IconPackException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPackException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPackException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconPackException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
public class IconPackMetadata {
|
||||||
|
|
||||||
|
private String uuid;
|
||||||
|
private String name;
|
||||||
|
private int version;
|
||||||
|
private IconMetadata[] icons;
|
||||||
|
|
||||||
|
private IconPackMetadata() {}
|
||||||
|
|
||||||
|
public String getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconMetadata[] getIcons() {
|
||||||
|
return icons;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.icon;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.IOUtil;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
public class IconUtil {
|
||||||
|
|
||||||
|
// Source: https://sashamaps.net/docs/resources/20-colors/
|
||||||
|
private static final List<Integer> DISTINCT_COLORS = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
Color.parseColor("#e6194B"), // Red
|
||||||
|
Color.parseColor("#f58231"), // Orange
|
||||||
|
// Color.parseColor("#ffe119"), // Yellow
|
||||||
|
// Color.parseColor("#bfef45"), // Lime
|
||||||
|
Color.parseColor("#3cb44b"), // Green
|
||||||
|
// Color.parseColor("#42d4f4"), // Cyan
|
||||||
|
Color.parseColor("#4363d8"), // Blue
|
||||||
|
Color.parseColor("#911eb4"), // Purple
|
||||||
|
Color.parseColor("#f032e6"), // Magenta
|
||||||
|
// Color.parseColor("#a9a9a9"), // Grey
|
||||||
|
Color.parseColor("#800000"), // Maroon
|
||||||
|
Color.parseColor("#9A6324"), // Brown
|
||||||
|
Color.parseColor("#808000"), // Olive
|
||||||
|
Color.parseColor("#469990"), // Teal
|
||||||
|
Color.parseColor("#000075") // Navy
|
||||||
|
// Color.parseColor("#000000"), // Black
|
||||||
|
// Color.parseColor("#fabed4"), // Pink
|
||||||
|
// Color.parseColor("#ffd8b1"), // Apricot
|
||||||
|
// Color.parseColor("#fffac8"), // Beige
|
||||||
|
// Color.parseColor("#aaffc3"), // Mint
|
||||||
|
// Color.parseColor("#dcbeff"), // Lavender
|
||||||
|
// Color.parseColor("#ffffff") // White
|
||||||
|
));
|
||||||
|
|
||||||
|
private static Map<String, IconPack> loadedPacks = new HashMap<>();
|
||||||
|
|
||||||
|
private static File getIconPacksDir(Context context) {
|
||||||
|
File iconPacksDir = new File(context.getFilesDir(), "iconpacks");
|
||||||
|
if(!iconPacksDir.exists()) {
|
||||||
|
iconPacksDir.mkdirs();
|
||||||
|
}
|
||||||
|
return iconPacksDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IconPackMetadata importIconPack(Context context, Uri uri) throws IconPackException {
|
||||||
|
IconPackMetadata meta = loadPackMetadata(context, uri);
|
||||||
|
|
||||||
|
// TODO: check for existing icon pack
|
||||||
|
File iconPackFile = new File(getIconPacksDir(context), meta.getUuid());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!iconPackFile.exists()) {
|
||||||
|
iconPackFile.createNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (OutputStream out = new FileOutputStream(iconPackFile);
|
||||||
|
InputStream in = context.getContentResolver().openInputStream(uri)) {
|
||||||
|
if(in == null) throw new IconPackException("Failed to read icon pack");
|
||||||
|
byte[] bytes = IOUtil.readBytes(in);
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
}catch(IOException e) {
|
||||||
|
throw new IconPackException("Failed to import icon pack", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IconPackMetadata loadPackMetadata(Context context, Uri uri) throws IconPackException {
|
||||||
|
try(InputStream in = context.getContentResolver().openInputStream(uri)) {
|
||||||
|
if(in == null) throw new IconPackException("Failed to read icon pack");
|
||||||
|
try(ZipInputStream zIn = new ZipInputStream(in)) {
|
||||||
|
ZipEntry en;
|
||||||
|
while((en = zIn.getNextEntry()) != null) {
|
||||||
|
if(en.getName().equals("pack.json")) {
|
||||||
|
byte[] entryBytes = readEntry(zIn, en);
|
||||||
|
return SettingsUtil.GSON.fromJson(new String(entryBytes, StandardCharsets.UTF_8), IconPackMetadata.class); // TODO: validate metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(IOException e) {
|
||||||
|
throw new IconPackException("Failed to read icon pack", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IconPackException("No pack.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<IconPack> loadAllIconPacks(Context context) {
|
||||||
|
File iconPacksDir = getIconPacksDir(context);
|
||||||
|
|
||||||
|
String[] packIDs = iconPacksDir.list();
|
||||||
|
if(packIDs == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<IconPack> packs = new ArrayList<>();
|
||||||
|
for(String pack : packIDs) {
|
||||||
|
try {
|
||||||
|
packs.add(loadIconPack(context, pack));
|
||||||
|
}catch(IconPackException e) {
|
||||||
|
DialogUtil.showErrorDialog(context, "An icon pack failed to load", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IconPack loadIconPack(Context context, String uuid) throws IconPackException {
|
||||||
|
if(loadedPacks.containsKey(uuid)) return loadedPacks.get(uuid);
|
||||||
|
|
||||||
|
IconPack p = loadIconPack(new File(getIconPacksDir(context), uuid));
|
||||||
|
if(p == null) return null;
|
||||||
|
|
||||||
|
loadedPacks.put(uuid, p);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IconPack loadIconPack(File file) throws IconPackException {
|
||||||
|
if(!file.exists()) return null;
|
||||||
|
|
||||||
|
try(ZipInputStream in = new ZipInputStream(new FileInputStream(file))) {
|
||||||
|
IconPackMetadata metadata = null;
|
||||||
|
Map<String, byte[]> files = new HashMap<>();
|
||||||
|
|
||||||
|
ZipEntry en;
|
||||||
|
while((en = in.getNextEntry()) != null) {
|
||||||
|
byte[] entryBytes = readEntry(in, en);
|
||||||
|
|
||||||
|
if(en.getName().equals("pack.json")) {
|
||||||
|
metadata = SettingsUtil.GSON.fromJson(new String(entryBytes, StandardCharsets.UTF_8), IconPackMetadata.class); // TODO: validate metadata
|
||||||
|
}else {
|
||||||
|
files.put(en.getName(), entryBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(metadata == null) throw new IconPackException("Missing icon pack metadata");
|
||||||
|
|
||||||
|
Icon[] icons = new Icon[metadata.getIcons().length];
|
||||||
|
int iconCount = 0;
|
||||||
|
for(IconMetadata m : metadata.getIcons()) {
|
||||||
|
byte[] bytes = files.get(m.getFilename());
|
||||||
|
if(bytes == null) continue;
|
||||||
|
icons[iconCount++] = new Icon(m, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon[] workingIcons = new Icon[iconCount];
|
||||||
|
System.arraycopy(icons, 0, workingIcons, 0, iconCount);
|
||||||
|
|
||||||
|
return new IconPack(metadata, workingIcons);
|
||||||
|
}catch(IOException e) {
|
||||||
|
throw new IconPackException("Failed to read icon pack", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readEntry(ZipInputStream in, ZipEntry en) throws IOException {
|
||||||
|
if (en.getSize() < 0 || en.getSize() > Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Invalid ZIP entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] entryBytes = new byte[(int) en.getSize()];
|
||||||
|
|
||||||
|
int totalRead = 0;
|
||||||
|
while (totalRead < entryBytes.length) {
|
||||||
|
totalRead += in.read(entryBytes, totalRead, entryBytes.length - totalRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entryBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap generateCodeImage(String issuer) {
|
||||||
|
int imageSize = 128;
|
||||||
|
|
||||||
|
Bitmap b = Bitmap.createBitmap(imageSize, imageSize, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas c = new Canvas(b);
|
||||||
|
|
||||||
|
Paint p = new Paint();
|
||||||
|
p.setColor(DISTINCT_COLORS.get(Math.abs(issuer.hashCode()) % DISTINCT_COLORS.size()));
|
||||||
|
p.setStyle(Paint.Style.FILL);
|
||||||
|
c.drawCircle(imageSize / 2, imageSize / 2, imageSize / 2, p);
|
||||||
|
|
||||||
|
p.setColor(Color.WHITE);
|
||||||
|
p.setAntiAlias(true);
|
||||||
|
p.setTextSize(64);
|
||||||
|
|
||||||
|
String text = issuer.substring(0, 1);
|
||||||
|
Rect r = new Rect();
|
||||||
|
p.getTextBounds(text, 0, text.length(), r);
|
||||||
|
c.drawText(text, imageSize / 2 - r.exactCenterX(), imageSize / 2 - r.exactCenterY(), p);
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,6 +9,8 @@ import java.io.Serializable;
|
|||||||
|
|
||||||
public class OTPData implements Serializable {
|
public class OTPData implements Serializable {
|
||||||
|
|
||||||
|
public static final String IMAGE_DATA_NONE = "none";
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private String issuer;
|
private String issuer;
|
||||||
private OTPType type;
|
private OTPType type;
|
||||||
@ -18,6 +20,7 @@ public class OTPData implements Serializable {
|
|||||||
private int period;
|
private int period;
|
||||||
private long counter;
|
private long counter;
|
||||||
private boolean checksum;
|
private boolean checksum;
|
||||||
|
private String imageData;
|
||||||
|
|
||||||
// Cached
|
// Cached
|
||||||
private transient OTP otp;
|
private transient OTP otp;
|
||||||
@ -70,6 +73,14 @@ public class OTPData implements Serializable {
|
|||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setImageData(String imageData) {
|
||||||
|
this.imageData = imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageData() {
|
||||||
|
return imageData;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPin() throws OTPException {
|
public String getPin() throws OTPException {
|
||||||
return getOTP().getPin();
|
return getOTP().getPin();
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package com.cringe_studios.cringe_authenticator.otplist;
|
package com.cringe_studios.cringe_authenticator.otplist;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.util.Base64;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -12,14 +15,20 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.caverock.androidsvg.SVG;
|
||||||
|
import com.caverock.androidsvg.SVGParseException;
|
||||||
import com.cringe_studios.cringe_authenticator.BaseActivity;
|
import com.cringe_studios.cringe_authenticator.BaseActivity;
|
||||||
import com.cringe_studios.cringe_authenticator.R;
|
import com.cringe_studios.cringe_authenticator.R;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.icon.Icon;
|
||||||
|
import com.cringe_studios.cringe_authenticator.icon.IconPack;
|
||||||
|
import com.cringe_studios.cringe_authenticator.icon.IconUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||||
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||||
import com.cringe_studios.cringe_authenticator_library.OTPException;
|
import com.cringe_studios.cringe_authenticator_library.OTPException;
|
||||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -75,6 +84,46 @@ public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
|
|||||||
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.GONE);
|
holder.getBinding().progress.setVisibility(data.getType() == OTPType.TOTP ? View.VISIBLE : View.GONE);
|
||||||
holder.getBinding().refresh.setVisibility(data.getType() == OTPType.HOTP ? View.VISIBLE : View.GONE);
|
holder.getBinding().refresh.setVisibility(data.getType() == OTPType.HOTP ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
List<IconPack> packs = IconUtil.loadAllIconPacks(context);
|
||||||
|
|
||||||
|
byte[] imageBytes = null;
|
||||||
|
String imageData = holder.getOTPData().getImageData();
|
||||||
|
if(!OTPData.IMAGE_DATA_NONE.equals(imageData)) {
|
||||||
|
if (imageData == null) {
|
||||||
|
for (IconPack pack : packs) {
|
||||||
|
Icon ic = pack.findIconForIssuer(holder.getOTPData().getIssuer());
|
||||||
|
if (ic != null) {
|
||||||
|
imageBytes = ic.getBytes();
|
||||||
|
// TODO: save new icon
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
imageBytes = Base64.decode(imageData, Base64.DEFAULT);
|
||||||
|
}catch(IllegalArgumentException ignored) {
|
||||||
|
// Just use default icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(imageBytes == null) {
|
||||||
|
String issuer = data.getIssuer() != null ? data.getIssuer() : data.getName();
|
||||||
|
holder.getBinding().otpCodeIcon.setImageBitmap(IconUtil.generateCodeImage(issuer));
|
||||||
|
}else {
|
||||||
|
Bitmap bm = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
||||||
|
if(bm != null) {
|
||||||
|
holder.getBinding().otpCodeIcon.setImageBitmap(bm);
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
SVG svg = SVG.getFromInputStream(new ByteArrayInputStream(imageBytes));
|
||||||
|
holder.getBinding().otpCodeIcon.setSVG(svg);
|
||||||
|
}catch(SVGParseException e) {
|
||||||
|
holder.getBinding().otpCodeIcon.setImageBitmap(IconUtil.generateCodeImage(data.getIssuer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
holder.getBinding().refresh.setOnClickListener(view -> {
|
holder.getBinding().refresh.setOnClickListener(view -> {
|
||||||
if (data.getType() != OTPType.HOTP) return;
|
if (data.getType() != OTPType.HOTP) return;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package com.cringe_studios.cringe_authenticator.util;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.util.Log;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
@ -27,10 +27,11 @@ public class StyledDialogBuilder extends AlertDialog.Builder {
|
|||||||
|
|
||||||
TypedArray arr = dialog.getContext().obtainStyledAttributes(new int[] {R.attr.dialogBackground});
|
TypedArray arr = dialog.getContext().obtainStyledAttributes(new int[] {R.attr.dialogBackground});
|
||||||
try {
|
try {
|
||||||
Log.d("WINDOW", dialog.getWindow().getClass().toString());
|
|
||||||
dialog.getWindow().setBackgroundDrawable(arr.getDrawable(0));
|
dialog.getWindow().setBackgroundDrawable(arr.getDrawable(0));
|
||||||
//dialog.getWindow().getContext().getResources().obtainAttributes().getDrawable()
|
|
||||||
//R.attr.dialogBackground;
|
if(SettingsUtil.isScreenSecurity(getContext())) {
|
||||||
|
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
}
|
||||||
}finally {
|
}finally {
|
||||||
arr.close();
|
arr.close();
|
||||||
}
|
}
|
||||||
|
@ -60,5 +60,13 @@
|
|||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:text="Developed by Cringe Studios and JG-Cody"
|
android:text="Developed by Cringe Studios and JG-Cody"
|
||||||
android:textAlignment="center" />
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="76dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/about_text" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView 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"
|
|
||||||
tools:context=".fragment.HomeFragment">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<!--<LinearLayout
|
|
||||||
android:id="@+id/menuItems"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:layout_editor_absoluteY="16dp">
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
android:id="@+id/editSwitch"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Edit" />
|
|
||||||
</LinearLayout>-->
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/menu_items"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="76dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/menu_items" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".fragment.HomeFragment">
|
tools:context=".fragment.HomeFragment">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -130,5 +131,38 @@
|
|||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:background="@color/background_light_grey"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginVertical="10dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Icon Packs" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/settings_load_icon_pack"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/buttonBackground"
|
||||||
|
android:text="Import icon pack"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/settings_manage_icon_packs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/buttonBackground"
|
||||||
|
android:text="Manage icon packs"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="76dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
@ -18,14 +18,14 @@
|
|||||||
android:paddingBottom="5dp"
|
android:paddingBottom="5dp"
|
||||||
android:background="?attr/buttonBackground">
|
android:background="?attr/buttonBackground">
|
||||||
|
|
||||||
<ImageView
|
<com.caverock.androidsvg.SVGImageView
|
||||||
android:id="@+id/imageView5"
|
android:id="@+id/otp_code_icon"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingLeft="10dp"
|
android:paddingLeft="10dp"
|
||||||
android:paddingEnd="10dp"
|
android:paddingEnd="10dp"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
app:srcCompat="@drawable/cringeauth_white" />
|
android:src="@drawable/cringeauth_white" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
Loading…
Reference in New Issue
Block a user