diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 0c0c338..981f604 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -3,7 +3,20 @@ - + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/cringe_studios/code_guard/fragment/SettingsFragment.java b/app/src/main/java/com/cringe_studios/code_guard/fragment/SettingsFragment.java index ff32d67..f091b18 100644 --- a/app/src/main/java/com/cringe_studios/code_guard/fragment/SettingsFragment.java +++ b/app/src/main/java/com/cringe_studios/code_guard/fragment/SettingsFragment.java @@ -22,8 +22,11 @@ import com.cringe_studios.code_guard.crypto.BiometricKey; import com.cringe_studios.code_guard.crypto.Crypto; import com.cringe_studios.code_guard.crypto.CryptoException; import com.cringe_studios.code_guard.crypto.CryptoParameters; +import com.cringe_studios.code_guard.databinding.DialogDownloadIconPacksBinding; import com.cringe_studios.code_guard.databinding.DialogManageIconPacksBinding; import com.cringe_studios.code_guard.databinding.FragmentSettingsBinding; +import com.cringe_studios.code_guard.icon.DownloadIconPackListAdapter; +import com.cringe_studios.code_guard.icon.DownloadableIconPack; import com.cringe_studios.code_guard.icon.IconPack; import com.cringe_studios.code_guard.icon.IconPackListAdapter; import com.cringe_studios.code_guard.icon.IconUtil; @@ -275,6 +278,19 @@ public class SettingsFragment extends NamedFragment { binding.settingsLoadIconPack.setOnClickListener(v -> ((MainActivity) requireActivity()).promptPickIconPackFile()); + binding.settingsDownloadIconPacks.setOnClickListener(v -> { + DialogDownloadIconPacksBinding binding = DialogDownloadIconPacksBinding.inflate(getLayoutInflater()); + + binding.downloadIconPacksList.setLayoutManager(new LinearLayoutManager(requireContext())); + binding.downloadIconPacksList.setAdapter(new DownloadIconPackListAdapter(requireContext(), Arrays.asList(DownloadableIconPack.values()))); + + new StyledDialogBuilder(requireContext()) + .setTitle(R.string.download_icon_packs_title) + .setView(binding.getRoot()) + .setPositiveButton(R.string.ok, (d, which) -> {}) + .show(); + }); + binding.settingsManageIconPacks.setOnClickListener(v -> { List brokenPacks = new ArrayList<>(); List packs = IconUtil.loadAllIconPacks(requireContext(), brokenPacks::add); diff --git a/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadIconPackItem.java b/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadIconPackItem.java new file mode 100644 index 0000000..9603e17 --- /dev/null +++ b/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadIconPackItem.java @@ -0,0 +1,31 @@ +package com.cringe_studios.code_guard.icon; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.cringe_studios.code_guard.databinding.DialogDownloadIconPacksItemBinding; + +public class DownloadIconPackItem extends RecyclerView.ViewHolder { + + private final DialogDownloadIconPacksItemBinding binding; + + private DownloadableIconPack pack; + + public DownloadIconPackItem(@NonNull DialogDownloadIconPacksItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public DialogDownloadIconPacksItemBinding getBinding() { + return binding; + } + + public void setPack(DownloadableIconPack pack) { + this.pack = pack; + } + + public DownloadableIconPack getPack() { + return pack; + } + +} diff --git a/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadIconPackListAdapter.java b/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadIconPackListAdapter.java new file mode 100644 index 0000000..ccc5c29 --- /dev/null +++ b/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadIconPackListAdapter.java @@ -0,0 +1,109 @@ +package com.cringe_studios.code_guard.icon; + +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.RecyclerView; + +import com.cringe_studios.code_guard.R; +import com.cringe_studios.code_guard.databinding.DialogDownloadIconPacksItemBinding; +import com.cringe_studios.code_guard.util.DialogUtil; +import com.cringe_studios.code_guard.util.StyledDialogBuilder; + +import java.io.File; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class DownloadIconPackListAdapter extends RecyclerView.Adapter { + + private final Context context; + + private final LayoutInflater inflater; + + private final List packs; + + private final Handler handler; + + public DownloadIconPackListAdapter(Context context, List packs) { + this.context = context; + this.inflater = LayoutInflater.from(context); + this.packs = packs; + this.handler = new Handler(Looper.getMainLooper()); + } + + @NonNull + @Override + public DownloadIconPackItem onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new DownloadIconPackItem(DialogDownloadIconPacksItemBinding.inflate(inflater, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull DownloadIconPackItem holder, int position) { + DownloadableIconPack pack = packs.get(position); + holder.setPack(pack); + + holder.getBinding().iconPackName.setText(pack.getName()); + holder.getBinding().iconPackCredit.setText(pack.getCredit()); + + IconPack installedPack = null; + try { + installedPack = IconUtil.loadIconPack(context, pack.getId()); + } catch (IconPackException ignored) { /* ignored, the user can just download the icon pack again */ } + + if(installedPack != null) { + holder.getBinding().iconPackDownload.setImageResource(R.drawable.baseline_refresh_24); + holder.getBinding().iconPackInstalled.setText(context.getString(R.string.icon_pack_version, installedPack.getMetadata().getVersion())); + }else { + holder.getBinding().iconPackInstalled.setText(R.string.icon_pack_not_installed); + } + + holder.getBinding().iconPackDownload.setOnClickListener(view -> { + AlertDialog dialog = new StyledDialogBuilder(context) + .setTitle(R.string.icon_pack_downloading_title) + .setMessage(R.string.icon_pack_downloading_message) + .setCancelable(false) + .show(); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(() -> { + executor.shutdown(); + + File file = null; + try { + file = File.createTempFile("iconpack", ".zip", context.getCacheDir()); + pack.download(file); + + IconPackMetadata meta = IconUtil.importIconPack(context, Uri.fromFile(file)); + + handler.post(() -> { + dialog.dismiss(); + notifyItemChanged(position); + Toast.makeText(context, context.getString(R.string.icon_pack_imported, meta.getIcons().length), Toast.LENGTH_LONG).show(); + }); + }catch(Exception e) { + handler.post(() -> { + dialog.dismiss(); + DialogUtil.showErrorDialog(context, context.getString(R.string.error_import_icon_pack), e); + }); + } finally { + if(file != null) file.delete(); + } + + return null; + }); + }); + } + + @Override + public int getItemCount() { + return packs.size(); + } +} diff --git a/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadableIconPack.java b/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadableIconPack.java new file mode 100644 index 0000000..4bf1309 --- /dev/null +++ b/app/src/main/java/com/cringe_studios/code_guard/icon/DownloadableIconPack.java @@ -0,0 +1,74 @@ +package com.cringe_studios.code_guard.icon; + +import android.util.Log; + +import com.cringe_studios.code_guard.util.DownloadUtil; +import com.cringe_studios.code_guard.util.SettingsUtil; +import com.google.gson.JsonObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public enum DownloadableIconPack { + + AEGIS_ICONS("c1018b93-4e8c-490a-b575-30dde62a833e", "aegis-icons", "https://aegis-icons.github.io/", "https://github.com/aegis-icons/aegis-icons/releases/latest/download/aegis-icons.zip"), + AEGIS_SIMPLE_ICONS("6a371ea0-1178-4677-ae93-cda7a7a5b378", "aegis-simple-icons", "https://github.com/alexbakker/aegis-simple-icons", () -> { + String apiURL = "https://api.github.com/repos/alexbakker/aegis-simple-icons/releases/latest"; + JsonObject object = SettingsUtil.GSON.fromJson(new String(DownloadUtil.downloadURL(apiURL), StandardCharsets.UTF_8), JsonObject.class); + return object.get("assets").getAsJsonArray() + .get(0).getAsJsonObject() + .get("browser_download_url").getAsString(); + }), + ; + + private final String id; + private final String name; + private final String credit; + private final URLLoader url; + + DownloadableIconPack(String id, String name, String credit, URLLoader url) { + this.id = id; + this.name = name; + this.credit = credit; + this.url = url; + } + + DownloadableIconPack(String id, String name, String credit, String url) { + this(id, name, credit, () -> url); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getCredit() { + return credit; + } + + public void download(File destinationFile) throws IOException { + String downloadURL = url.load(); + Log.d("Download", "Downloading from " + downloadURL); + try(InputStream in = DownloadUtil.openURL(downloadURL); + FileOutputStream fOut = new FileOutputStream(destinationFile)) { + byte[] buf = new byte[1024]; + int len; + while((len = in.read(buf)) != -1) { + fOut.write(buf, 0, len); + } + } + } + + private interface URLLoader { + + String load() throws IOException; + + } + +} diff --git a/app/src/main/java/com/cringe_studios/code_guard/icon/IconMetadata.java b/app/src/main/java/com/cringe_studios/code_guard/icon/IconMetadata.java index 1144898..653fad5 100644 --- a/app/src/main/java/com/cringe_studios/code_guard/icon/IconMetadata.java +++ b/app/src/main/java/com/cringe_studios/code_guard/icon/IconMetadata.java @@ -24,7 +24,7 @@ public class IconMetadata { } public String getCategory() { - return category; + return category == null ? "No category" : category; } public String[] getIssuer() { @@ -32,7 +32,7 @@ public class IconMetadata { } public boolean validate() { - return filename != null && category != null && issuer != null; + return filename != null && issuer != null; } } diff --git a/app/src/main/java/com/cringe_studios/code_guard/icon/IconUtil.java b/app/src/main/java/com/cringe_studios/code_guard/icon/IconUtil.java index eab64c5..2a37aab 100644 --- a/app/src/main/java/com/cringe_studios/code_guard/icon/IconUtil.java +++ b/app/src/main/java/com/cringe_studios/code_guard/icon/IconUtil.java @@ -82,7 +82,16 @@ public class IconUtil { return iconPacksDir; } - public static void importIconPack(Context context, Uri uri) throws IconPackException { + public static List getIconPackIds(Context context) { + File iconPacksDir = getIconPacksDir(context); + + String[] packIDs = iconPacksDir.list(); + if(packIDs == null) return Collections.emptyList(); + + return Arrays.asList(packIDs); + } + + public static IconPackMetadata importIconPack(Context context, Uri uri) throws IconPackException { IconPackMetadata meta = loadPackMetadata(context, uri); File iconPackFile = new File(getIconPacksDir(context), meta.getUuid()); @@ -98,6 +107,8 @@ public class IconUtil { byte[] bytes = IOUtil.readBytes(in); out.write(bytes); } + + return meta; }catch(IOException e) { throw new IconPackException("Failed to import icon pack", e); } diff --git a/app/src/main/java/com/cringe_studios/code_guard/util/DownloadUtil.java b/app/src/main/java/com/cringe_studios/code_guard/util/DownloadUtil.java new file mode 100644 index 0000000..3fc78da --- /dev/null +++ b/app/src/main/java/com/cringe_studios/code_guard/util/DownloadUtil.java @@ -0,0 +1,26 @@ +package com.cringe_studios.code_guard.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class DownloadUtil { + + public static InputStream openURL(String url) throws IOException { + return new URL(url).openStream(); + } + + public static byte[] downloadURL(String url) throws IOException { + try(InputStream in = openURL(url)) { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) != -1) { + bOut.write(buf, 0, len); + } + return bOut.toByteArray(); + } + } + +} diff --git a/app/src/main/res/drawable/baseline_download_24.xml b/app/src/main/res/drawable/baseline_download_24.xml new file mode 100644 index 0000000..6297dd2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_download_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/dialog_download_icon_packs.xml b/app/src/main/res/layout/dialog_download_icon_packs.xml new file mode 100644 index 0000000..f7ed719 --- /dev/null +++ b/app/src/main/res/layout/dialog_download_icon_packs.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_download_icon_packs_item.xml b/app/src/main/res/layout/dialog_download_icon_packs_item.xml new file mode 100644 index 0000000..e626436 --- /dev/null +++ b/app/src/main/res/layout/dialog_download_icon_packs_item.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 461088e..29b27b5 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -197,6 +197,15 @@ android:text="@string/settings_icon_packs_import" android:textAllCaps="false" /> + + Extra-Cringe-Symbol verwenden Kopieren des OTP-Codes fehlgeschlagen OTP-Code in die Zwischenablage kopiert + Symbolpakete herunterladen + Symbolpakete herunterladen + Nicht installiert + Installiert (version %d) + Symbolpaket wird heruntergeladen + Bitte habe einen Moment Geduld… \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f8ed495..7d90286 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -162,4 +162,10 @@ Utiliser l\'icône de l\'humour supplémentaire Échec de la copie de l\'OTP dans le presse-papiers OTP copié dans le presse-papiers + Télécharger les packs d\'icônes + Télécharger les packs d\'icônes + Non installé + Installé (version %d) + Téléchargement du pack d\'icônes + Veuillez patienter un instant… \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8cd0307..20638d6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -162,4 +162,10 @@ Użyj dodatkowej ikony cringe Nie udało się skopiować OTP do schowka OTP skopiowany do schowka + Pobierz pakiety ikon + Pobierz pakiety ikon + Nie zainstalowano + Zainstalowano (wersja %d) + Pobieranie pakietu ikon + Poczekaj chwilę… \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 109251f..1f8eae3 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -162,4 +162,10 @@ Використовуйте додаткову піктограму обтиснення Не вдалося скопіювати OTP до буфера обміну OTP скопійовано в буфер обміну + Завантажити пакети іконок + Завантажити пакети іконок + Не встановлено + Встановлено (версія %d) + Завантаження пакета іконок + Будь ласка, зачекайте хвилинку… \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c67410..9020597 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -125,7 +125,7 @@ https://git.cringe-studios.com/CringeStudios/Code-Guard The icon pack doesn\'t contain any icons Pack contains invalid metadata. Make sure you selected the correct file - The icon pack you\'re trying to import already exists.\n\nImported: %s (version %d)\nExisting: %s (version %d) What do you want to do? + 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? Broken icon packs Some icon packs failed to load.\n\nDo you want to delete the broken icon packs? Icon pack with %d icon(s) imported @@ -183,7 +183,7 @@ Reset to default image - Override + Overwrite Rename existing Rename imported @@ -193,4 +193,10 @@ I like hamburgers 🍔 Use extra cringe icon OTP copied to clipboard + Download icon packs + Download icon packs + Not installed + Installed (version %d) + Downloading icon pack + Please wait a moment… \ No newline at end of file