Add option to download icon packs to settings

This commit is contained in:
MrLetsplay 2024-04-21 15:12:17 +02:00
parent b30b0df5b4
commit baac0ccec2
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
17 changed files with 391 additions and 6 deletions

View File

@ -3,7 +3,20 @@
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State />
<State>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_2_API_34.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2024-04-21T10:28:14.063607866Z" />
</State>
</entry>
</value>
</component>

View File

@ -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<String> brokenPacks = new ArrayList<>();
List<IconPack> packs = IconUtil.loadAllIconPacks(requireContext(), brokenPacks::add);

View File

@ -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;
}
}

View File

@ -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<DownloadIconPackItem> {
private final Context context;
private final LayoutInflater inflater;
private final List<DownloadableIconPack> packs;
private final Handler handler;
public DownloadIconPackListAdapter(Context context, List<DownloadableIconPack> 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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -82,7 +82,16 @@ public class IconUtil {
return iconPacksDir;
}
public static void importIconPack(Context context, Uri uri) throws IconPackException {
public static List<String> 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);
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:attr/textColor" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
</vector>

View File

@ -0,0 +1,13 @@
<?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:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_icon_packs_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,48 @@
<?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="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<LinearLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/icon_pack_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|center"
android:textStyle="bold"
tools:text="My Icon Pack" />
<TextView
android:id="@+id/icon_pack_credit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|center"
android:autoLink="web"
tools:text="https://cringe-studios.com/my-icon-pack" />
<TextView
android:id="@+id/icon_pack_installed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|center"
android:autoLink="web"
tools:text="Not installed" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/icon_pack_download"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
android:src="@drawable/baseline_download_24"
android:layout_gravity="center"/>
</LinearLayout>

View File

@ -197,6 +197,15 @@
android:text="@string/settings_icon_packs_import"
android:textAllCaps="false" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/settings_download_icon_packs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="?attr/buttonBackground"
android:text="@string/settings_icon_packs_download"
android:textAllCaps="false" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/settings_manage_icon_packs"
android:layout_width="match_parent"

View File

@ -176,4 +176,10 @@
<string name="settings_cringe_icon">Extra-Cringe-Symbol verwenden</string>
<string name="otp_copy_error">Kopieren des OTP-Codes fehlgeschlagen</string>
<string name="otp_copied">OTP-Code in die Zwischenablage kopiert</string>
<string name="settings_icon_packs_download">Symbolpakete herunterladen</string>
<string name="download_icon_packs_title">Symbolpakete herunterladen</string>
<string name="icon_pack_not_installed">Nicht installiert</string>
<string name="icon_pack_version">Installiert (version %d)</string>
<string name="icon_pack_downloading_title">Symbolpaket wird heruntergeladen</string>
<string name="icon_pack_downloading_message">Bitte habe einen Moment Geduld…</string>
</resources>

View File

@ -162,4 +162,10 @@
<string name="settings_cringe_icon">Utiliser l\'icône de l\'humour supplémentaire</string>
<string name="otp_copy_error">Échec de la copie de l\'OTP dans le presse-papiers</string>
<string name="otp_copied">OTP copié dans le presse-papiers</string>
<string name="settings_icon_packs_download">Télécharger les packs d\'icônes</string>
<string name="download_icon_packs_title">Télécharger les packs d\'icônes</string>
<string name="icon_pack_not_installed">Non installé</string>
<string name="icon_pack_version">Installé (version %d)</string>
<string name="icon_pack_downloading_title">Téléchargement du pack d\'icônes</string>
<string name="icon_pack_downloading_message">Veuillez patienter un instant…</string>
</resources>

View File

@ -162,4 +162,10 @@
<string name="settings_cringe_icon">Użyj dodatkowej ikony cringe</string>
<string name="otp_copy_error">Nie udało się skopiować OTP do schowka</string>
<string name="otp_copied">OTP skopiowany do schowka</string>
<string name="settings_icon_packs_download">Pobierz pakiety ikon</string>
<string name="download_icon_packs_title">Pobierz pakiety ikon</string>
<string name="icon_pack_not_installed">Nie zainstalowano</string>
<string name="icon_pack_version">Zainstalowano (wersja %d)</string>
<string name="icon_pack_downloading_title">Pobieranie pakietu ikon</string>
<string name="icon_pack_downloading_message">Poczekaj chwilę…</string>
</resources>

View File

@ -162,4 +162,10 @@
<string name="settings_cringe_icon">Використовуйте додаткову піктограму обтиснення</string>
<string name="otp_copy_error">Не вдалося скопіювати OTP до буфера обміну</string>
<string name="otp_copied">OTP скопійовано в буфер обміну</string>
<string name="settings_icon_packs_download">Завантажити пакети іконок</string>
<string name="download_icon_packs_title">Завантажити пакети іконок</string>
<string name="icon_pack_not_installed">Не встановлено</string>
<string name="icon_pack_version">Встановлено (версія %d)</string>
<string name="icon_pack_downloading_title">Завантаження пакета іконок</string>
<string name="icon_pack_downloading_message">Будь ласка, зачекайте хвилинку…</string>
</resources>

View File

@ -125,7 +125,7 @@
<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) What do you want to do?</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>
@ -183,7 +183,7 @@
<item>Reset to default image</item>
</string-array>
<string-array name="error_icon_pack_exists_choices">
<item>Override</item>
<item>Overwrite</item>
<item>Rename existing</item>
<item>Rename imported</item>
</string-array>
@ -193,4 +193,10 @@
<string name="settings_hamburger_mode">I like hamburgers 🍔</string>
<string name="settings_cringe_icon">Use extra cringe icon</string>
<string name="otp_copied">OTP copied to clipboard</string>
<string name="settings_icon_packs_download">Download icon packs</string>
<string name="download_icon_packs_title">Download icon packs</string>
<string name="icon_pack_not_installed">Not installed</string>
<string name="icon_pack_version">Installed (version %d)</string>
<string name="icon_pack_downloading_title">Downloading icon pack</string>
<string name="icon_pack_downloading_message">Please wait a moment…</string>
</resources>