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