Choices on duplicate icon pack

This commit is contained in:
MrLetsplay 2023-10-01 23:23:50 +02:00
parent d25799719e
commit 596bd7bc08
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
9 changed files with 214 additions and 32 deletions

View File

@ -19,6 +19,7 @@ import androidx.core.util.Consumer;
import androidx.fragment.app.Fragment;
import com.cringe_studios.code_guard.databinding.ActivityMainBinding;
import com.cringe_studios.code_guard.databinding.DialogIconPackExistsBinding;
import com.cringe_studios.code_guard.databinding.DialogInputCodeChoiceBinding;
import com.cringe_studios.code_guard.fragment.AboutFragment;
import com.cringe_studios.code_guard.fragment.EditOTPFragment;
@ -26,6 +27,7 @@ import com.cringe_studios.code_guard.fragment.GroupFragment;
import com.cringe_studios.code_guard.fragment.HomeFragment;
import com.cringe_studios.code_guard.fragment.NamedFragment;
import com.cringe_studios.code_guard.fragment.SettingsFragment;
import com.cringe_studios.code_guard.icon.IconPack;
import com.cringe_studios.code_guard.icon.IconPackException;
import com.cringe_studios.code_guard.icon.IconPackMetadata;
import com.cringe_studios.code_guard.icon.IconUtil;
@ -146,7 +148,9 @@ public class MainActivity extends BaseActivity {
try {
if(doc == null) return;
IconPackMetadata meta = IconUtil.importIconPack(this, doc);
IconPackMetadata meta = IconUtil.loadPackMetadata(this, doc);
IconPack existingIconPack = IconUtil.loadIconPack(this, meta.getUuid());
if(!meta.validate()) {
DialogUtil.showErrorDialog(this, getString(R.string.error_icon_pack_invalid));
@ -158,7 +162,57 @@ public class MainActivity extends BaseActivity {
return;
}
DialogUtil.showErrorDialog(this, "Icon pack contains " + meta.getIcons().length + " icons");
if(existingIconPack != null) {
DialogIconPackExistsBinding binding = DialogIconPackExistsBinding.inflate(getLayoutInflater());
binding.iconPackExistsText.setText(getString(R.string.error_icon_pack_exists, meta.getName(), meta.getVersion(), existingIconPack.getMetadata().getName(), existingIconPack.getMetadata().getVersion()));
binding.iconPackExistsChoices.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.error_icon_pack_exists_choices)));
AlertDialog dialog = new StyledDialogBuilder(this)
.setTitle("Icon pack already exists")
.setView(binding.getRoot())
.setNeutralButton(R.string.cancel, (d, which) -> {})
.create();
binding.iconPackExistsChoices.setOnItemClickListener((parent, view, position, id) -> {
switch(position) {
case 0: // Override
try {
IconUtil.importIconPack(this, doc);
Toast.makeText(this, getString(R.string.icon_pack_imported, meta.getIcons().length), Toast.LENGTH_LONG).show();
} catch (IconPackException e) {
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
}
break;
case 1: // Rename existing
try {
IconUtil.renameIconPack(this, existingIconPack, existingIconPack.getMetadata().getName() + " (" + existingIconPack.getMetadata().getVersion() + ")", UUID.randomUUID().toString());
IconUtil.importIconPack(this, doc);
Toast.makeText(this, getString(R.string.icon_pack_imported, meta.getIcons().length), Toast.LENGTH_LONG).show();
} catch (IconPackException e) {
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
}
break;
case 2: // Rename imported
try {
IconUtil.importIconPack(this, doc, meta.getName() + "(" + meta.getVersion() + ")", UUID.randomUUID().toString());
Toast.makeText(this, getString(R.string.icon_pack_imported, meta.getIcons().length), Toast.LENGTH_LONG).show();
} catch (IconPackException e) {
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
}
break;
}
dialog.dismiss();
});
dialog.show();
return;
}
IconUtil.importIconPack(this, doc);
Toast.makeText(this, getString(R.string.icon_pack_imported, meta.getIcons().length), Toast.LENGTH_LONG).show();
} catch (IconPackException e) {
DialogUtil.showErrorDialog(this, "Failed to import icon pack", e);
}

View File

@ -37,6 +37,7 @@ import com.cringe_studios.code_guard.util.SettingsUtil;
import com.cringe_studios.code_guard.util.StyledDialogBuilder;
import com.cringe_studios.code_guard.util.Theme;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@ -264,7 +265,17 @@ public class SettingsFragment extends NamedFragment {
binding.settingsLoadIconPack.setOnClickListener(v -> ((MainActivity) requireActivity()).promptPickIconPackFile());
binding.settingsManageIconPacks.setOnClickListener(v -> {
List<IconPack> packs = IconUtil.loadAllIconPacks(requireContext());
List<String> brokenPacks = new ArrayList<>();
List<IconPack> packs = IconUtil.loadAllIconPacks(requireContext(), brokenPacks::add);
if(!brokenPacks.isEmpty()) {
DialogUtil.showYesNo(requireContext(), R.string.broken_icon_packs_title, R.string.broken_icon_packs_message, () -> {
for(String pack : brokenPacks) {
IconUtil.removeIconPack(requireContext(), pack);
}
}, null);
}
if(packs.isEmpty()) {
Toast.makeText(requireContext(), R.string.no_icon_packs_installed, Toast.LENGTH_LONG).show();
return;

View File

@ -41,7 +41,13 @@ public class IconPackListAdapter extends RecyclerView.Adapter<IconPackItem> {
holder.getBinding().iconPackName.setText(pack.getMetadata().getName());
holder.getBinding().iconPackDelete.setOnClickListener(view -> {
DialogUtil.showYesNo(context, R.string.delete_pack_title, R.string.delete_pack_message, () -> IconUtil.removeIconPack(context, pack.getMetadata().getUuid()), null);
DialogUtil.showYesNo(context, R.string.delete_pack_title, R.string.delete_pack_message, () -> {
IconUtil.removeIconPack(context, pack.getMetadata().getUuid());
int idx = packs.indexOf(pack);
packs.remove(idx);
notifyItemRemoved(idx);
}, null);
});
}

View File

@ -9,10 +9,18 @@ public class IconPackMetadata {
private IconPackMetadata() {}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getUuid() {
return uuid;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}

View File

@ -17,13 +17,15 @@ import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGImageView;
import com.caverock.androidsvg.SVGParseException;
import com.cringe_studios.code_guard.model.OTPData;
import com.cringe_studios.code_guard.util.DialogUtil;
import com.cringe_studios.code_guard.util.IOUtil;
import com.cringe_studios.code_guard.util.SettingsUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -38,6 +40,7 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class IconUtil {
@ -79,10 +82,9 @@ public class IconUtil {
return iconPacksDir;
}
public static IconPackMetadata importIconPack(Context context, Uri uri) throws IconPackException {
public static void 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 {
@ -90,7 +92,7 @@ public class IconUtil {
iconPackFile.createNewFile();
}
try (OutputStream out = new FileOutputStream(iconPackFile);
try (OutputStream out = new BufferedOutputStream(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);
@ -99,8 +101,54 @@ public class IconUtil {
}catch(IOException e) {
throw new IconPackException("Failed to import icon pack", e);
}
}
return meta;
public static void importIconPack(Context context, Uri uri, String newName, String newUUID) throws IconPackException {
IconPackMetadata meta = loadPackMetadata(context, uri);
meta.setName(newName);
meta.setUuid(newUUID);
File iconPackFile = new File(getIconPacksDir(context), meta.getUuid());
try {
if (!iconPackFile.exists()) {
iconPackFile.createNewFile();
}
try (InputStream in = context.getContentResolver().openInputStream(uri)) {
if(in == null) throw new IconPackException("Failed to read icon pack");
writeRenamedPack(in, iconPackFile, meta);
}
}catch(IOException e) {
throw new IconPackException("Failed to import icon pack", e);
}
}
public static void renameIconPack(Context context, IconPack pack, String newName, String newUUID) throws IconPackException {
File packFile = new File(getIconPacksDir(context), pack.getMetadata().getUuid());
if(!packFile.exists()) return;
File newPackFile = new File(getIconPacksDir(context), newUUID);
String oldName = pack.getMetadata().getName();
String oldUUID = pack.getMetadata().getUuid();
loadedPacks.remove(oldUUID);
pack.getMetadata().setName(newName);
pack.getMetadata().setUuid(newUUID);
try {
writeRenamedPack(new BufferedInputStream(new FileInputStream(packFile)), newPackFile, pack.getMetadata());
packFile.delete();
}catch(IconPackException e) {
pack.getMetadata().setName(oldName);
pack.getMetadata().setUuid(oldUUID);
throw e;
} catch (FileNotFoundException e) {
throw new IconPackException(e);
}
}
public static void removeIconPack(Context context, String uuid) {
@ -109,12 +157,14 @@ public class IconUtil {
loadedPacks.remove(uuid);
}
private static IconPackMetadata loadPackMetadata(Context context, Uri uri) throws IconPackException {
public 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.isDirectory()) continue;
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
@ -128,6 +178,28 @@ public class IconUtil {
throw new IconPackException("No pack.json");
}
private static void writeRenamedPack(InputStream oldFile, File newFile, IconPackMetadata meta) throws IconPackException {
try(ZipInputStream in = new ZipInputStream(oldFile);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(newFile)))) {
ZipEntry en;
while((en = in.getNextEntry()) != null) {
if(en.isDirectory()) continue;
byte[] entryBytes = readEntry(in, en);
if(en.getName().equals("pack.json")) {
out.putNextEntry(new ZipEntry("pack.json"));
out.write(SettingsUtil.GSON.toJson(meta).getBytes(StandardCharsets.UTF_8));
continue;
}
out.putNextEntry(new ZipEntry(en.getName()));
out.write(entryBytes);
}
}catch(IOException e) {
throw new IconPackException(e);
}
}
public static Map<String, List<Icon>> loadAllIcons(Context context) {
List<IconPack> packs = loadAllIconPacks(context);
@ -148,7 +220,7 @@ public class IconUtil {
return icons;
}
public static List<IconPack> loadAllIconPacks(Context context) {
public static List<IconPack> loadAllIconPacks(Context context, Consumer<String> brokenPack) {
File iconPacksDir = getIconPacksDir(context);
String[] packIDs = iconPacksDir.list();
@ -157,15 +229,24 @@ public class IconUtil {
List<IconPack> packs = new ArrayList<>();
for(String pack : packIDs) {
try {
packs.add(loadIconPack(context, pack));
IconPack p = loadIconPack(context, pack);
if(p == null) continue;
if(!p.getMetadata().getUuid().equals(pack)) throw new IconPackException("Invalid metadata");
packs.add(p);
}catch(IconPackException e) {
DialogUtil.showErrorDialog(context, "An icon pack failed to load", e);
e.printStackTrace();
if(brokenPack != null) brokenPack.accept(pack);
//DialogUtil.showErrorDialog(context, "An icon pack failed to load", e);
}
}
return packs;
}
public static List<IconPack> loadAllIconPacks(Context context) {
return loadAllIconPacks(context, null);
}
public static IconPack loadIconPack(Context context, String uuid) throws IconPackException {
if(loadedPacks.containsKey(uuid)) return loadedPacks.get(uuid);
@ -179,12 +260,14 @@ public class IconUtil {
private static IconPack loadIconPack(File file) throws IconPackException {
if(!file.exists()) return null;
try(ZipInputStream in = new ZipInputStream(new FileInputStream(file))) {
try(ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)))) {
IconPackMetadata metadata = null;
Map<String, byte[]> files = new HashMap<>();
ZipEntry en;
while((en = in.getNextEntry()) != null) {
if(en.isDirectory()) continue;
byte[] entryBytes = readEntry(in, en);
if(en.getName().equals("pack.json")) {
@ -214,18 +297,11 @@ public class IconUtil {
}
private static byte[] readEntry(ZipInputStream in, ZipEntry en) throws IOException {
if (en.getSize() < 0 || en.getSize() > Integer.MAX_VALUE) {
if (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;
return IOUtil.readBytes(in);
}
public static Bitmap generateCodeImage(String issuer, String name) {

View File

@ -1,9 +1,9 @@
package com.cringe_studios.code_guard.util;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
@ -11,7 +11,7 @@ import java.nio.ByteBuffer;
public class IOUtil {
public static byte[] readBytes(File file) throws IOException {
try(FileInputStream fIn = new FileInputStream(file)) {
try(InputStream fIn = new BufferedInputStream(new FileInputStream(file))) {
ByteBuffer fileBuffer = ByteBuffer.allocate((int) file.length());
byte[] buffer = new byte[1024];
int len;
@ -34,10 +34,4 @@ public class IOUtil {
return bOut.toByteArray();
}
public static void writeBytes(File file, byte[] bytes) throws IOException {
try(FileOutputStream fOut = new FileOutputStream(file)) {
fOut.write(bytes);
}
}
}

View File

@ -11,9 +11,11 @@ import com.cringe_studios.code_guard.model.OTPData;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@ -148,7 +150,7 @@ public class OTPDatabase {
byte[] dbBytes = convertToEncryptedBytes(loadedDatabase, loadedKey, parameters);
try(FileOutputStream fOut = new FileOutputStream(file)) {
try(OutputStream fOut = new BufferedOutputStream(new FileOutputStream(file))) {
fOut.write(dbBytes);
} catch (IOException e) {
throw new OTPDatabaseException(e);

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/icon_pack_exists_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/error_icon_pack_exists" />
<ListView
android:id="@+id/icon_pack_exists_choices"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
tools:listitem="@android:layout/simple_list_item_1"/>
</LinearLayout>

View File

@ -123,4 +123,13 @@
<string name="patreon_link" translatable="false">https://git.cringe-studios.com/CringeStudios/Code-Guard</string>
<string name="error_icon_pack_empty">The icon pack doesn\'t contain any icons</string>
<string name="error_icon_pack_invalid">Pack contains invalid metadata. Make sure you selected the correct file</string>
<string name="error_icon_pack_exists">The icon pack you\'re trying to import already exists.\n\nImported: %s (version %d)\nExisting: %s (version %d)\n\nWhat do you want to do?</string>
<string name="broken_icon_packs_title">Broken icon packs</string>
<string name="broken_icon_packs_message">Some icon packs failed to load.\n\nDo you want to delete the broken icon packs?</string>
<string name="icon_pack_imported">Icon pack with %d icon(s) imported</string>
<string-array name="error_icon_pack_exists_choices">
<item>Override</item>
<item>Rename existing</item>
<item>Rename imported</item>
</string-array>
</resources>