diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_add.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_add.xml
new file mode 100644
index 0000000..61b2c48
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_add.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_arrow_back.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_arrow_back.xml
new file mode 100644
index 0000000..c10a4c8
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_arrow_back.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_bell.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_bell.xml
new file mode 100644
index 0000000..1cba642
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_bell.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_bug.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_bug.xml
new file mode 100644
index 0000000..e060d64
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_bug.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_check.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_check.xml
new file mode 100644
index 0000000..e1f16f1
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_chevron_right.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_chevron_right.xml
new file mode 100644
index 0000000..04eca79
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_chevron_right.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_close.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_close.xml
new file mode 100644
index 0000000..22e27a4
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_close.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_cloud_off.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_cloud_off.xml
new file mode 100644
index 0000000..b824bc6
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_cloud_off.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_code.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_code.xml
new file mode 100644
index 0000000..dcaff89
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_code.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_delete.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_delete.xml
new file mode 100644
index 0000000..b1c5e2d
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_delete.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_document.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_document.xml
new file mode 100644
index 0000000..c736b25
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_document.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_download.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_download.xml
new file mode 100644
index 0000000..8562fe6
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_download.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_file.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_file.xml
new file mode 100644
index 0000000..c736b25
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_file.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_folder.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_folder.xml
new file mode 100644
index 0000000..fa1259b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_folder.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_globe.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_globe.xml
new file mode 100644
index 0000000..bcac5e0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_globe.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_hand.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_hand.xml
new file mode 100644
index 0000000..e3fd4dc
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_hand.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_info.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_info.xml
new file mode 100644
index 0000000..9b956e0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_info.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_lock.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_lock.xml
new file mode 100644
index 0000000..091b21a
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_lock.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_megaphone.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_megaphone.xml
new file mode 100644
index 0000000..4dac2e2
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_megaphone.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_moon.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_moon.xml
new file mode 100644
index 0000000..1cca55f
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_moon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_nfc.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_nfc.xml
new file mode 100644
index 0000000..ae9b0d5
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_nfc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_qr_code.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_qr_code.xml
new file mode 100644
index 0000000..9b46d9f
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_qr_code.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_radio.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_radio.xml
new file mode 100644
index 0000000..9ac616d
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_radio.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_scan.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_scan.xml
new file mode 100644
index 0000000..c92ee7e
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_scan.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_send.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_send.xml
new file mode 100644
index 0000000..769733a
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_send.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_settings.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_settings.xml
new file mode 100644
index 0000000..f8fd090
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_shield.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_shield.xml
new file mode 100644
index 0000000..ca3fbc0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_shield.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_shield_check.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_shield_check.xml
new file mode 100644
index 0000000..3c32152
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_shield_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_storage.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_storage.xml
new file mode 100644
index 0000000..23ba7a7
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_storage.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_sun.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_sun.xml
new file mode 100644
index 0000000..1cca505
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_sun.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_sync.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_sync.xml
new file mode 100644
index 0000000..1b90650
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_sync.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_system_theme.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_system_theme.xml
new file mode 100644
index 0000000..d209553
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_system_theme.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_temporary.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_temporary.xml
new file mode 100644
index 0000000..44dd937
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_temporary.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_total_storage.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_total_storage.xml
new file mode 100644
index 0000000..ecf52bf
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_total_storage.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_transfer_data.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_transfer_data.xml
new file mode 100644
index 0000000..addbb87
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_transfer_data.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_user.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_user.xml
new file mode 100644
index 0000000..5b39e0b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_user.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_fluent_user_off.xml b/shared/src/commonMain/composeResources/drawable/icon_fluent_user_off.xml
new file mode 100644
index 0000000..317e5b0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_fluent_user_off.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_add.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_add.xml
new file mode 100644
index 0000000..0c6f7be
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_add.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_arrow_back.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_arrow_back.xml
new file mode 100644
index 0000000..1dd2b59
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_arrow_back.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_bell.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_bell.xml
new file mode 100644
index 0000000..89d76b2
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_bell.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_bug.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_bug.xml
new file mode 100644
index 0000000..e2c2f2e
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_bug.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_check.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_check.xml
new file mode 100644
index 0000000..707183b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_chevron_right.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_chevron_right.xml
new file mode 100644
index 0000000..a724d1a
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_chevron_right.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_close.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_close.xml
new file mode 100644
index 0000000..1ab69a9
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_close.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_cloud_off.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_cloud_off.xml
new file mode 100644
index 0000000..457645b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_cloud_off.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_code.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_code.xml
new file mode 100644
index 0000000..833b065
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_code.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_delete.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_delete.xml
new file mode 100644
index 0000000..6f1b771
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_delete.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_document.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_document.xml
new file mode 100644
index 0000000..84663f2
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_document.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_download.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_download.xml
new file mode 100644
index 0000000..f71a77a
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_download.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_file.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_file.xml
new file mode 100644
index 0000000..67685f8
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_file.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_folder.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_folder.xml
new file mode 100644
index 0000000..810dfe9
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_folder.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_globe.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_globe.xml
new file mode 100644
index 0000000..24dd351
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_globe.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_hand.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_hand.xml
new file mode 100644
index 0000000..c293104
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_hand.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_info.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_info.xml
new file mode 100644
index 0000000..1451d17
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_info.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_lock.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_lock.xml
new file mode 100644
index 0000000..081de7a
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_lock.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_megaphone.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_megaphone.xml
new file mode 100644
index 0000000..9a20c21
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_megaphone.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_moon.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_moon.xml
new file mode 100644
index 0000000..6c65c9f
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_moon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_nfc.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_nfc.xml
new file mode 100644
index 0000000..143c12b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_nfc.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_qr_code.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_qr_code.xml
new file mode 100644
index 0000000..8eea8d5
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_qr_code.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_radio.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_radio.xml
new file mode 100644
index 0000000..538476b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_radio.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_scan.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_scan.xml
new file mode 100644
index 0000000..b9f760c
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_scan.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_send.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_send.xml
new file mode 100644
index 0000000..ef14e75
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_send.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_settings.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_settings.xml
new file mode 100644
index 0000000..6c8c431
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_shield.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_shield.xml
new file mode 100644
index 0000000..f526199
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_shield.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_shield_check.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_shield_check.xml
new file mode 100644
index 0000000..c232a16
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_shield_check.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_storage.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_storage.xml
new file mode 100644
index 0000000..eaf4c2c
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_storage.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_sun.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_sun.xml
new file mode 100644
index 0000000..a5604c2
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_sun.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_sync.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_sync.xml
new file mode 100644
index 0000000..30f8c32
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_sync.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_system_theme.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_system_theme.xml
new file mode 100644
index 0000000..ea91232
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_system_theme.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_temporary.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_temporary.xml
new file mode 100644
index 0000000..27dfac7
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_temporary.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_total_storage.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_total_storage.xml
new file mode 100644
index 0000000..181b977
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_total_storage.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_transfer_data.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_transfer_data.xml
new file mode 100644
index 0000000..da03d38
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_transfer_data.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_user.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_user.xml
new file mode 100644
index 0000000..a27914e
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_user.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_lucide_user_off.xml b/shared/src/commonMain/composeResources/drawable/icon_lucide_user_off.xml
new file mode 100644
index 0000000..014683d
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_lucide_user_off.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_add.xml b/shared/src/commonMain/composeResources/drawable/icon_material_add.xml
new file mode 100644
index 0000000..8ca5093
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_add.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_arrow_back.xml b/shared/src/commonMain/composeResources/drawable/icon_material_arrow_back.xml
new file mode 100644
index 0000000..59d0467
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_arrow_back.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_bell.xml b/shared/src/commonMain/composeResources/drawable/icon_material_bell.xml
new file mode 100644
index 0000000..5b216f3
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_bell.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_bug.xml b/shared/src/commonMain/composeResources/drawable/icon_material_bug.xml
new file mode 100644
index 0000000..335943b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_bug.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_check.xml b/shared/src/commonMain/composeResources/drawable/icon_material_check.xml
new file mode 100644
index 0000000..c2778d2
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_chevron_right.xml b/shared/src/commonMain/composeResources/drawable/icon_material_chevron_right.xml
new file mode 100644
index 0000000..9d207e1
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_chevron_right.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_close.xml b/shared/src/commonMain/composeResources/drawable/icon_material_close.xml
new file mode 100644
index 0000000..186a472
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_close.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_cloud_off.xml b/shared/src/commonMain/composeResources/drawable/icon_material_cloud_off.xml
new file mode 100644
index 0000000..7192332
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_cloud_off.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_code.xml b/shared/src/commonMain/composeResources/drawable/icon_material_code.xml
new file mode 100644
index 0000000..a96a122
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_code.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_delete.xml b/shared/src/commonMain/composeResources/drawable/icon_material_delete.xml
new file mode 100644
index 0000000..81d832d
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_delete.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_document.xml b/shared/src/commonMain/composeResources/drawable/icon_material_document.xml
new file mode 100644
index 0000000..f6ad658
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_document.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_download.xml b/shared/src/commonMain/composeResources/drawable/icon_material_download.xml
new file mode 100644
index 0000000..4f3c7e2
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_download.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_file.xml b/shared/src/commonMain/composeResources/drawable/icon_material_file.xml
new file mode 100644
index 0000000..4e8c130
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_file.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_folder.xml b/shared/src/commonMain/composeResources/drawable/icon_material_folder.xml
new file mode 100644
index 0000000..93f6cd0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_folder.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_globe.xml b/shared/src/commonMain/composeResources/drawable/icon_material_globe.xml
new file mode 100644
index 0000000..6b82b9e
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_globe.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_hand.xml b/shared/src/commonMain/composeResources/drawable/icon_material_hand.xml
new file mode 100644
index 0000000..85ec083
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_hand.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_info.xml b/shared/src/commonMain/composeResources/drawable/icon_material_info.xml
new file mode 100644
index 0000000..f1ed433
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_info.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_lock.xml b/shared/src/commonMain/composeResources/drawable/icon_material_lock.xml
new file mode 100644
index 0000000..98bc921
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_lock.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_megaphone.xml b/shared/src/commonMain/composeResources/drawable/icon_material_megaphone.xml
new file mode 100644
index 0000000..b795f50
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_megaphone.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_moon.xml b/shared/src/commonMain/composeResources/drawable/icon_material_moon.xml
new file mode 100644
index 0000000..aa9e4bd
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_moon.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_nfc.xml b/shared/src/commonMain/composeResources/drawable/icon_material_nfc.xml
new file mode 100644
index 0000000..a5325e0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_nfc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_qr_code.xml b/shared/src/commonMain/composeResources/drawable/icon_material_qr_code.xml
new file mode 100644
index 0000000..402a891
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_qr_code.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_radio.xml b/shared/src/commonMain/composeResources/drawable/icon_material_radio.xml
new file mode 100644
index 0000000..e97dd63
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_radio.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_scan.xml b/shared/src/commonMain/composeResources/drawable/icon_material_scan.xml
new file mode 100644
index 0000000..e6a56dd
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_scan.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_send.xml b/shared/src/commonMain/composeResources/drawable/icon_material_send.xml
new file mode 100644
index 0000000..4ece80a
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_send.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_settings.xml b/shared/src/commonMain/composeResources/drawable/icon_material_settings.xml
new file mode 100644
index 0000000..0165abd
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_shield.xml b/shared/src/commonMain/composeResources/drawable/icon_material_shield.xml
new file mode 100644
index 0000000..bece701
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_shield.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_shield_check.xml b/shared/src/commonMain/composeResources/drawable/icon_material_shield_check.xml
new file mode 100644
index 0000000..0a4fb51
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_shield_check.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_storage.xml b/shared/src/commonMain/composeResources/drawable/icon_material_storage.xml
new file mode 100644
index 0000000..22211dd
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_storage.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_sun.xml b/shared/src/commonMain/composeResources/drawable/icon_material_sun.xml
new file mode 100644
index 0000000..0d05059
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_sun.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_sync.xml b/shared/src/commonMain/composeResources/drawable/icon_material_sync.xml
new file mode 100644
index 0000000..046806b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_sync.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_system_theme.xml b/shared/src/commonMain/composeResources/drawable/icon_material_system_theme.xml
new file mode 100644
index 0000000..f75f2f0
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_system_theme.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_temporary.xml b/shared/src/commonMain/composeResources/drawable/icon_material_temporary.xml
new file mode 100644
index 0000000..b0b6785
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_temporary.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_total_storage.xml b/shared/src/commonMain/composeResources/drawable/icon_material_total_storage.xml
new file mode 100644
index 0000000..0eb972e
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_total_storage.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_transfer_data.xml b/shared/src/commonMain/composeResources/drawable/icon_material_transfer_data.xml
new file mode 100644
index 0000000..fc32254
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_transfer_data.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_user.xml b/shared/src/commonMain/composeResources/drawable/icon_material_user.xml
new file mode 100644
index 0000000..e5dfa98
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_user.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/drawable/icon_material_user_off.xml b/shared/src/commonMain/composeResources/drawable/icon_material_user_off.xml
new file mode 100644
index 0000000..34bca1f
--- /dev/null
+++ b/shared/src/commonMain/composeResources/drawable/icon_material_user_off.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/src/commonMain/composeResources/files/icon-licenses/fluent-system-icons-mit.txt b/shared/src/commonMain/composeResources/files/icon-licenses/fluent-system-icons-mit.txt
new file mode 100644
index 0000000..bc9c36b
--- /dev/null
+++ b/shared/src/commonMain/composeResources/files/icon-licenses/fluent-system-icons-mit.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/shared/src/commonMain/composeResources/files/icon-licenses/lucide-isc.txt b/shared/src/commonMain/composeResources/files/icon-licenses/lucide-isc.txt
new file mode 100644
index 0000000..718bb3f
--- /dev/null
+++ b/shared/src/commonMain/composeResources/files/icon-licenses/lucide-isc.txt
@@ -0,0 +1,43 @@
+ISC License
+
+Copyright (c) 2026 Lucide Icons and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+---
+
+The following Lucide icons are derived from the Feather project:
+
+airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
+
+The MIT License (MIT) (for the icons listed above)
+
+Copyright (c) 2013-present Cole Bemis
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/shared/src/commonMain/composeResources/files/icon-licenses/material-symbols-apache-2.0.txt b/shared/src/commonMain/composeResources/files/icon-licenses/material-symbols-apache-2.0.txt
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/shared/src/commonMain/composeResources/files/icon-licenses/material-symbols-apache-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/approvals/ApprovalModalHost.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/approvals/ApprovalModalHost.kt
index 4d097f1..182e7e5 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/approvals/ApprovalModalHost.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/approvals/ApprovalModalHost.kt
@@ -12,21 +12,20 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.SecondaryButton
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
@@ -66,8 +65,8 @@ fun ApprovalModalHost(
) {
Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
Surface(shape = RoundedCornerShape(14.dp), color = colors.backgroundSelection) {
- Icon(
- ApprovalIcon,
+ PlatformIcon(
+ AppIcon.ShieldCheck,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier.padding(11.dp).size(24.dp),
@@ -116,18 +115,3 @@ fun ApprovalModalHost(
}
}
}
-
-private val ApprovalIcon: ImageVector = ImageVector.Builder(
- name = "Approval",
- defaultWidth = 24.dp,
- defaultHeight = 24.dp,
- viewportWidth = 24f,
- viewportHeight = 24f,
-).apply {
- path {
- moveTo(12f, 2f); lineTo(20f, 5.5f); verticalLineTo(11f)
- curveTo(20f, 16.1f, 16.6f, 20.7f, 12f, 22f)
- curveTo(7.4f, 20.7f, 4f, 16.1f, 4f, 11f); verticalLineTo(5.5f); close()
- moveTo(8.2f, 11.8f); lineTo(10.7f, 14.3f); lineTo(15.9f, 9.1f)
- }
-}.build()
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/receive/ReceiveScreen.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/receive/ReceiveScreen.kt
index dd82fb5..e7f7dc0 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/receive/ReceiveScreen.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/receive/ReceiveScreen.kt
@@ -21,7 +21,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FloatingActionButton
-import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -30,13 +29,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.PathFillType
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.StrokeJoin
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.PathBuilder
-import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -55,9 +47,10 @@ import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow
import com.vnidrop.app.ui.components.SecondaryButton
import com.vnidrop.app.ui.feedback.UiText
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.platform.LocalUiPlatform
import com.vnidrop.app.ui.platform.usesMobilePresentation
-import com.vnidrop.app.ui.navigation.VniDropIcons
import com.vnidrop.app.ui.state.WindowClass
import com.vnidrop.app.ui.state.displayNameForStatus
import com.vnidrop.app.ui.state.formatBytes
@@ -73,7 +66,7 @@ fun ReceiveFloatingAction(onClick: () -> Unit, modifier: Modifier = Modifier) {
modifier = modifier,
containerColor = LocalVniDropColors.current.brandButton,
contentColor = Color.White,
- ) { Icon(ReceiveIcons.Download, stringResource(Res.string.button_receive_files)) }
+ ) { PlatformIcon(AppIcon.Download, stringResource(Res.string.button_receive_files)) }
}
@Composable
@@ -180,8 +173,8 @@ private fun ReceiveEmptyState(onOpen: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
- Icon(
- imageVector = VniDropIcons.Receive,
+ PlatformIcon(
+ icon = AppIcon.Download,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier
@@ -210,15 +203,15 @@ private fun ReceiveMethodPanel(
Text(stringResource(Res.string.receive_choose_method_title), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
Text(stringResource(Res.string.receive_choose_method_body), color = LocalVniDropColors.current.foregroundLighter)
ReceiveMethodRow(
- ReceiveIcons.File, stringResource(Res.string.receive_method_file), stringResource(Res.string.receive_method_file_description),
+ AppIcon.File, stringResource(Res.string.receive_method_file), stringResource(Res.string.receive_method_file_description),
actions.fileAvailability,
) { actions.pickInvitation { onResult(ReceiveMethod.InvitationFile, it) } }
if (actions.qrAvailability != ReceiveMethodAvailability.Hidden) ReceiveMethodRow(
- ReceiveIcons.Scan, stringResource(Res.string.receive_method_scan), stringResource(Res.string.receive_method_scan_description),
+ AppIcon.Scan, stringResource(Res.string.receive_method_scan), stringResource(Res.string.receive_method_scan_description),
actions.qrAvailability,
) { actions.scanQrCode { onResult(ReceiveMethod.QrCode, it) } }
if (actions.nfcAvailability != ReceiveMethodAvailability.Hidden) ReceiveMethodRow(
- ReceiveIcons.Nfc,
+ AppIcon.Nfc,
if (isWaitingForNfc) stringResource(Res.string.receive_nfc_waiting) else stringResource(Res.string.receive_method_nfc),
stringResource(Res.string.receive_method_nfc_description),
if (isWaitingForNfc) ReceiveMethodAvailability.Unavailable else actions.nfcAvailability,
@@ -230,7 +223,7 @@ private fun ReceiveMethodPanel(
}
@Composable
-private fun ReceiveMethodRow(icon: ImageVector, title: String, description: String, availability: ReceiveMethodAvailability, onClick: () -> Unit) {
+private fun ReceiveMethodRow(icon: AppIcon, title: String, description: String, availability: ReceiveMethodAvailability, onClick: () -> Unit) {
val enabled = availability == ReceiveMethodAvailability.Available
Surface(
modifier = Modifier.fillMaxWidth().clickable(enabled = enabled, onClick = onClick),
@@ -238,7 +231,7 @@ private fun ReceiveMethodRow(icon: ImageVector, title: String, description: Stri
color = LocalVniDropColors.current.backgroundSurface200,
) {
Row(Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
- Icon(icon, null, tint = if (enabled) LocalVniDropColors.current.brandLink else LocalVniDropColors.current.foregroundLighter, modifier = Modifier.size(24.dp))
+ PlatformIcon(icon, null, tint = if (enabled) LocalVniDropColors.current.brandLink else LocalVniDropColors.current.foregroundLighter, modifier = Modifier.size(24.dp))
Spacer(Modifier.width(14.dp))
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(title, fontWeight = FontWeight.SemiBold)
@@ -320,7 +313,7 @@ private fun ReceiveTransferRow(
Surface(Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp), color = LocalVniDropColors.current.backgroundSurface200) {
Row(Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(44.dp).background(LocalVniDropColors.current.backgroundSurface300, RoundedCornerShape(10.dp)), contentAlignment = Alignment.Center) {
- Icon(ReceiveIcons.File, null, tint = LocalVniDropColors.current.foregroundLighter)
+ PlatformIcon(AppIcon.File, null, tint = LocalVniDropColors.current.foregroundLighter)
}
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
@@ -332,7 +325,7 @@ private fun ReceiveTransferRow(
}
if (transfer.status.isTerminalReceiveHistory()) {
IconButton(onClick = onDelete) {
- Icon(ReceiveIcons.Trash, stringResource(Res.string.receive_delete_history_item), tint = LocalVniDropColors.current.destructiveDefault)
+ PlatformIcon(AppIcon.Delete, stringResource(Res.string.receive_delete_history_item), tint = LocalVniDropColors.current.destructiveDefault)
}
}
}
@@ -369,15 +362,3 @@ private fun ReceiveHistoryDeletePanel(
}
}
}
-
-private object ReceiveIcons {
- val Download = lineIcon("Download") { moveTo(12f, 3f); lineTo(12f, 15f); moveTo(7f, 10f); lineTo(12f, 15f); lineTo(17f, 10f); moveTo(4f, 20f); lineTo(20f, 20f) }
- val File = lineIcon("File") { moveTo(14f, 2f); lineTo(6f, 2f); lineTo(6f, 22f); lineTo(18f, 22f); lineTo(18f, 6f); close(); moveTo(14f, 2f); lineTo(14f, 6f); lineTo(18f, 6f) }
- val Scan = lineIcon("Scan") { moveTo(3f, 8f); lineTo(3f, 3f); lineTo(8f, 3f); moveTo(16f, 3f); lineTo(21f, 3f); lineTo(21f, 8f); moveTo(21f, 16f); lineTo(21f, 21f); lineTo(16f, 21f); moveTo(8f, 21f); lineTo(3f, 21f); lineTo(3f, 16f); moveTo(7f, 12f); lineTo(17f, 12f) }
- val Nfc = lineIcon("Nfc") { moveTo(6f, 8f); curveTo(10f, 12f, 10f, 12f, 6f, 16f); moveTo(10f, 5f); curveTo(17f, 12f, 17f, 12f, 10f, 19f); moveTo(14f, 2f); curveTo(24f, 12f, 24f, 12f, 14f, 22f) }
- val Trash = lineIcon("Delete") { moveTo(4f, 7f); lineTo(20f, 7f); moveTo(9f, 3f); lineTo(15f, 3f); lineTo(16f, 7f); moveTo(7f, 7f); lineTo(8f, 21f); lineTo(16f, 21f); lineTo(17f, 7f); moveTo(10f, 11f); lineTo(10f, 17f); moveTo(14f, 11f); lineTo(14f, 17f) }
-}
-
-private fun lineIcon(name: String, block: PathBuilder.() -> Unit) = ImageVector.Builder(name, 24.dp, 24.dp, 24f, 24f).apply {
- path(fill = SolidColor(Color.Transparent), stroke = SolidColor(Color.Black), strokeLineWidth = 2f, strokeLineCap = StrokeCap.Round, strokeLineJoin = StrokeJoin.Round, pathFillType = PathFillType.NonZero, pathBuilder = block)
-}.build()
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendCatalog.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendCatalog.kt
index 065d2c5..8a153a1 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendCatalog.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendCatalog.kt
@@ -19,7 +19,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.FloatingActionButton
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -42,9 +41,10 @@ import com.vnidrop.app.ui.components.PillTone
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow
import com.vnidrop.app.ui.components.StatusPill
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.platform.LocalUiPlatform
import com.vnidrop.app.ui.platform.usesMobilePresentation
-import com.vnidrop.app.ui.navigation.VniDropIcons
import com.vnidrop.app.ui.state.TransferProgress
import com.vnidrop.app.ui.state.WindowClass
import com.vnidrop.app.ui.state.activeSendProgress
@@ -71,7 +71,7 @@ internal fun SendFloatingAction(onClick: () -> Unit, modifier: Modifier = Modifi
containerColor = LocalVniDropColors.current.brandButton,
contentColor = Color.White,
) {
- Icon(SendIcons.Plus, contentDescription = stringResource(Res.string.send_new_transfer_description))
+ PlatformIcon(AppIcon.Add, contentDescription = stringResource(Res.string.send_new_transfer_description))
}
}
@@ -144,8 +144,8 @@ private fun SendEmptyState(onOpenComposer: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
- Icon(
- imageVector = VniDropIcons.Send,
+ PlatformIcon(
+ icon = AppIcon.Send,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier
@@ -216,7 +216,7 @@ private fun TransferListItem(
}
}
Spacer(Modifier.width(8.dp))
- Icon(SendIcons.ChevronRight, contentDescription = null, tint = colors.foregroundLighter, modifier = Modifier.size(18.dp))
+ PlatformIcon(AppIcon.ChevronRight, contentDescription = null, tint = colors.foregroundLighter, modifier = Modifier.size(18.dp))
}
}
}
@@ -233,7 +233,7 @@ internal fun FileArtwork(thumbnailBytes: ByteArray?, modifier: Modifier = Modifi
)
} else {
Box(modifier, contentAlignment = Alignment.Center) {
- Icon(SendIcons.File, contentDescription = null, tint = LocalVniDropColors.current.foregroundLight, modifier = Modifier.size(22.dp))
+ PlatformIcon(AppIcon.File, contentDescription = null, tint = LocalVniDropColors.current.foregroundLight, modifier = Modifier.size(22.dp))
}
}
}
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendIcons.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendIcons.kt
deleted file mode 100644
index 89089a3..0000000
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/SendIcons.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.vnidrop.app.feature.send
-
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.PathFillType
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.StrokeJoin
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.PathBuilder
-import androidx.compose.ui.graphics.vector.path
-import androidx.compose.ui.unit.dp
-
-internal object SendIcons {
- val Plus = lineIcon("Plus") {
- moveTo(12f, 5f)
- lineTo(12f, 19f)
- moveTo(5f, 12f)
- lineTo(19f, 12f)
- }
- val File = lineIcon("File") {
- moveTo(14f, 2f)
- lineTo(6f, 2f)
- lineTo(6f, 22f)
- lineTo(18f, 22f)
- lineTo(18f, 6f)
- close()
- moveTo(14f, 2f)
- lineTo(14f, 6f)
- lineTo(18f, 6f)
- }
- val Back = lineIcon("Back") {
- moveTo(19f, 12f)
- lineTo(5f, 12f)
- moveTo(12f, 19f)
- lineTo(5f, 12f)
- lineTo(12f, 5f)
- }
- val Delete = lineIcon("Delete") {
- moveTo(4f, 7f); lineTo(20f, 7f)
- moveTo(9f, 7f); lineTo(9f, 4f); lineTo(15f, 4f); lineTo(15f, 7f)
- moveTo(6f, 7f); lineTo(7f, 21f); lineTo(17f, 21f); lineTo(18f, 7f)
- moveTo(10f, 11f); lineTo(10f, 17f)
- moveTo(14f, 11f); lineTo(14f, 17f)
- }
- val ChevronRight = lineIcon("ChevronRight") {
- moveTo(9f, 18f)
- lineTo(15f, 12f)
- lineTo(9f, 6f)
- }
- val Shield = lineIcon("Shield") {
- moveTo(12f, 2f)
- lineTo(20f, 6f)
- lineTo(20f, 12f)
- arcTo(9f, 9f, 0f, false, true, 12f, 22f)
- arcTo(9f, 9f, 0f, false, true, 4f, 12f)
- lineTo(4f, 6f)
- close()
- }
- val Globe = lineIcon("Globe") {
- moveTo(21f, 12f)
- arcTo(9f, 9f, 0f, true, true, 3f, 12f)
- arcTo(9f, 9f, 0f, true, true, 21f, 12f)
- moveTo(3f, 12f)
- lineTo(21f, 12f)
- moveTo(12f, 3f)
- arcTo(14f, 14f, 0f, false, true, 12f, 21f)
- arcTo(14f, 14f, 0f, false, true, 12f, 3f)
- }
-}
-
-private fun lineIcon(name: String, block: PathBuilder.() -> Unit): ImageVector =
- ImageVector.Builder(name, 24.dp, 24.dp, 24f, 24f).apply {
- path(
- fill = SolidColor(Color.Transparent),
- stroke = SolidColor(Color.Black),
- strokeLineWidth = 2f,
- strokeLineCap = StrokeCap.Round,
- strokeLineJoin = StrokeJoin.Round,
- pathFillType = PathFillType.NonZero,
- pathBuilder = block,
- )
- }.build()
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferComposer.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferComposer.kt
index 5a3bedb..6ff956d 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferComposer.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferComposer.kt
@@ -15,7 +15,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
@@ -25,7 +24,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -35,6 +33,8 @@ import com.vnidrop.app.core.ShareAccessPolicy
import com.vnidrop.app.ui.components.Field
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.QuietButton
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.state.WindowClass
import com.vnidrop.app.ui.state.formatBytes
import com.vnidrop.app.ui.theme.LocalVniDropColors
@@ -114,7 +114,7 @@ private fun ChooseFileStep(onSelectFile: () -> Unit, onSelectFolder: () -> Unit)
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(14.dp),
) {
- Icon(SendIcons.File, contentDescription = null, tint = LocalVniDropColors.current.brandLink, modifier = Modifier.size(32.dp))
+ PlatformIcon(AppIcon.File, contentDescription = null, tint = LocalVniDropColors.current.brandLink, modifier = Modifier.size(32.dp))
PrimaryButton(stringResource(Res.string.button_choose_files), onClick = onSelectFile)
QuietButton(stringResource(Res.string.button_choose_folder), onClick = onSelectFolder)
}
@@ -154,14 +154,14 @@ private fun ReviewFileStep(
Field(state.senderName, onSenderNameChanged, stringResource(Res.string.field_sender_name))
Text(stringResource(Res.string.send_access_title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
PolicyOption(
- icon = SendIcons.Shield,
+ icon = AppIcon.Shield,
title = stringResource(Res.string.send_access_approval),
description = stringResource(Res.string.send_access_approval_description),
selected = state.accessPolicy == ShareAccessPolicy.RequireApproval,
onClick = { onAccessPolicyChanged(ShareAccessPolicy.RequireApproval) },
)
PolicyOption(
- icon = SendIcons.Globe,
+ icon = AppIcon.Globe,
title = stringResource(Res.string.send_access_anyone),
description = stringResource(Res.string.send_access_anyone_description),
selected = state.accessPolicy == ShareAccessPolicy.AnyoneWithTransfer,
@@ -226,7 +226,7 @@ private fun SelectedFileCard(
}
if (canRemove) {
IconButton(onClick = onRemove) {
- Icon(SendIcons.Delete, stringResource(Res.string.button_remove_file), tint = LocalVniDropColors.current.destructiveDefault)
+ PlatformIcon(AppIcon.Delete, stringResource(Res.string.button_remove_file), tint = LocalVniDropColors.current.destructiveDefault)
}
}
}
@@ -235,7 +235,7 @@ private fun SelectedFileCard(
@Composable
private fun PolicyOption(
- icon: ImageVector,
+ icon: AppIcon,
title: String,
description: String,
selected: Boolean,
@@ -252,7 +252,7 @@ private fun PolicyOption(
.padding(14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Icon(icon, contentDescription = null, tint = if (selected) colors.brandLink else colors.foregroundLight, modifier = Modifier.size(22.dp))
+ PlatformIcon(icon, contentDescription = null, tint = if (selected) colors.brandLink else colors.foregroundLight, modifier = Modifier.size(22.dp))
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(title, fontWeight = FontWeight.SemiBold)
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferDetails.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferDetails.kt
index 0d772b5..d6d3d75 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferDetails.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/send/TransferDetails.kt
@@ -23,7 +23,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -52,6 +51,8 @@ import com.vnidrop.app.ui.components.DestructiveButton
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow
import com.vnidrop.app.ui.components.SecondaryButton
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.state.TransferProgress
import com.vnidrop.app.ui.state.displayNameForStatus
import com.vnidrop.app.ui.state.formatBytes
@@ -84,7 +85,7 @@ internal fun TransferDetails(
) {
item {
Row(verticalAlignment = Alignment.CenterVertically) {
- IconButton(onClick = onBack) { Icon(SendIcons.Back, stringResource(Res.string.button_back)) }
+ IconButton(onClick = onBack) { PlatformIcon(AppIcon.ArrowBack, stringResource(Res.string.button_back)) }
Text(
stringResource(Res.string.send_transfer_details_title),
modifier = Modifier.weight(1f),
@@ -92,7 +93,7 @@ internal fun TransferDetails(
fontWeight = FontWeight.Bold,
)
IconButton(onClick = onDelete) {
- Icon(SendIcons.Delete, stringResource(Res.string.button_delete_transfer), tint = LocalVniDropColors.current.destructiveDefault)
+ PlatformIcon(AppIcon.Delete, stringResource(Res.string.button_delete_transfer), tint = LocalVniDropColors.current.destructiveDefault)
}
}
}
@@ -156,7 +157,7 @@ private fun DetailDestination(title: String, description: String, count: Int? =
Text(count.toString(), modifier = Modifier.background(LocalVniDropColors.current.backgroundSelection, RoundedCornerShape(20.dp)).padding(horizontal = 9.dp, vertical = 3.dp))
Spacer(Modifier.width(8.dp))
}
- Icon(SendIcons.ChevronRight, null, tint = LocalVniDropColors.current.foregroundLighter, modifier = Modifier.size(18.dp))
+ PlatformIcon(AppIcon.ChevronRight, null, tint = LocalVniDropColors.current.foregroundLighter, modifier = Modifier.size(18.dp))
}
}
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AboutSettings.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AboutSettings.kt
index e2f3d65..3c4562a 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AboutSettings.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AboutSettings.kt
@@ -9,18 +9,18 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.vnidrop.app.diagnostics.DiagnosticsBuildConfig
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
@@ -87,28 +87,28 @@ internal fun AboutSettings(
AboutSection(
title = stringResource(Res.string.about_is_title),
points = listOf(
- SettingsIcons.PaperPlane to stringResource(Res.string.about_is_direct),
- SettingsIcons.AccountOff to stringResource(Res.string.about_is_no_account),
- SettingsIcons.ShieldCheck to stringResource(Res.string.about_is_in_control),
- SettingsIcons.Lock to stringResource(Res.string.about_is_encrypted),
- SettingsIcons.Code to stringResource(Res.string.about_is_open),
+ AppIcon.Send to stringResource(Res.string.about_is_direct),
+ AppIcon.UserOff to stringResource(Res.string.about_is_no_account),
+ AppIcon.ShieldCheck to stringResource(Res.string.about_is_in_control),
+ AppIcon.Lock to stringResource(Res.string.about_is_encrypted),
+ AppIcon.Code to stringResource(Res.string.about_is_open),
),
)
AboutSection(
title = stringResource(Res.string.about_isnt_title),
points = listOf(
- SettingsIcons.CloudOff to stringResource(Res.string.about_isnt_cloud),
- SettingsIcons.Sync to stringResource(Res.string.about_isnt_sync),
- SettingsIcons.Megaphone to stringResource(Res.string.about_isnt_public),
+ AppIcon.CloudOff to stringResource(Res.string.about_isnt_cloud),
+ AppIcon.Sync to stringResource(Res.string.about_isnt_sync),
+ AppIcon.Megaphone to stringResource(Res.string.about_isnt_public),
),
)
AboutSection(
title = stringResource(Res.string.about_privacy_title),
points = listOf(
- SettingsIcons.QrCode to stringResource(Res.string.about_privacy_capability),
- SettingsIcons.Hand to stringResource(Res.string.about_privacy_deny),
- SettingsIcons.Radio to stringResource(Res.string.about_privacy_relay),
- SettingsIcons.Drive to stringResource(Res.string.about_privacy_local),
+ AppIcon.QrCode to stringResource(Res.string.about_privacy_capability),
+ AppIcon.Hand to stringResource(Res.string.about_privacy_deny),
+ AppIcon.Radio to stringResource(Res.string.about_privacy_relay),
+ AppIcon.Storage to stringResource(Res.string.about_privacy_local),
),
)
@@ -122,7 +122,7 @@ internal fun AboutSettings(
AboutInfoItem(stringResource(Res.string.about_license_label), "Apache 2.0")
SettingsDivider()
SettingsRow(
- icon = SettingsIcons.Shield,
+ icon = AppIcon.Shield,
title = stringResource(Res.string.about_privacy_policy_label),
iconTone = SettingsIconTone.Brand,
onClick = { uriHandler.openUri(PrivacyPolicyUrl) },
@@ -132,7 +132,7 @@ internal fun AboutSettings(
SettingsGroup {
if (DiagnosticsBuildConfig.INCLUDED) {
SettingsToggleRow(
- icon = SettingsIcons.Info,
+ icon = AppIcon.Info,
title = stringResource(Res.string.diagnostics_title),
description = stringResource(Res.string.diagnostics_description),
checked = state.diagnosticsEnabled,
@@ -142,7 +142,7 @@ internal fun AboutSettings(
SettingsDivider()
}
SettingsRow(
- icon = SettingsIcons.Bug,
+ icon = AppIcon.Bug,
title = stringResource(Res.string.about_bug_report),
iconTone = SettingsIconTone.Neutral,
onClick = onReportBug,
@@ -154,7 +154,7 @@ internal fun AboutSettings(
@Composable
private fun AboutSection(
title: String,
- points: List>,
+ points: List>,
) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
@@ -173,7 +173,7 @@ private fun AboutSection(
}
@Composable
-private fun AboutPoint(icon: ImageVector, text: String) {
+private fun AboutPoint(icon: AppIcon, text: String) {
val colors = LocalVniDropColors.current
Row(
modifier = Modifier
@@ -182,7 +182,7 @@ private fun AboutPoint(icon: ImageVector, text: String) {
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Icon(icon, contentDescription = null, tint = colors.brandLink, modifier = Modifier.size(24.dp))
+ PlatformIcon(icon, contentDescription = null, tint = colors.brandLink, modifier = Modifier.size(24.dp))
Spacer(Modifier.width(16.dp))
Text(
text,
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AppearanceSettings.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AppearanceSettings.kt
index 8144651..16fe354 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AppearanceSettings.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/AppearanceSettings.kt
@@ -3,15 +3,15 @@ package com.vnidrop.app.feature.settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
-import com.vnidrop.app.ui.theme.ThemeMode
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
+import com.vnidrop.app.ui.theme.ThemeMode
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
import vnidrop.shared.generated.resources.appearance_auto_description
@@ -30,15 +30,15 @@ internal fun AppearanceSettings(
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
SettingsTopBar(stringResource(Res.string.appearance_title), onBack, showBack)
SettingsGroup {
- ThemeSettingsRow(SettingsIcons.Device, stringResource(Res.string.appearance_system_mode), mode == ThemeMode.System) {
+ ThemeSettingsRow(AppIcon.SystemTheme, stringResource(Res.string.appearance_system_mode), mode == ThemeMode.System) {
onModeChanged(ThemeMode.System)
}
SettingsDivider()
- ThemeSettingsRow(SettingsIcons.Moon, stringResource(Res.string.appearance_dark_mode), mode == ThemeMode.Dark) {
+ ThemeSettingsRow(AppIcon.Moon, stringResource(Res.string.appearance_dark_mode), mode == ThemeMode.Dark) {
onModeChanged(ThemeMode.Dark)
}
SettingsDivider()
- ThemeSettingsRow(SettingsIcons.Sun, stringResource(Res.string.appearance_light_mode), mode == ThemeMode.Light) {
+ ThemeSettingsRow(AppIcon.Sun, stringResource(Res.string.appearance_light_mode), mode == ThemeMode.Light) {
onModeChanged(ThemeMode.Light)
}
}
@@ -53,7 +53,7 @@ internal fun AppearanceSettings(
}
@Composable
-private fun ThemeSettingsRow(icon: ImageVector, title: String, selected: Boolean, onClick: () -> Unit) {
+private fun ThemeSettingsRow(icon: AppIcon, title: String, selected: Boolean, onClick: () -> Unit) {
SettingsRow(
icon = icon,
title = title,
@@ -62,8 +62,8 @@ private fun ThemeSettingsRow(icon: ImageVector, title: String, selected: Boolean
showsDisclosure = false,
trailing = if (selected) {
{
- Icon(
- SettingsIcons.Check,
+ PlatformIcon(
+ AppIcon.Check,
contentDescription = null,
tint = LocalVniDropColors.current.brandLink,
modifier = Modifier.size(20.dp),
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/BugReportSettings.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/BugReportSettings.kt
index 0e50b72..52c7402 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/BugReportSettings.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/BugReportSettings.kt
@@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vnidrop.app.ui.components.Field
import com.vnidrop.app.ui.components.PrimaryButton
+import com.vnidrop.app.ui.icons.AppIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
@@ -82,7 +83,7 @@ internal fun BugReportSettings(
)
SettingsGroup {
SettingsToggleRow(
- icon = SettingsIcons.Document,
+ icon = AppIcon.Document,
title = stringResource(Res.string.bug_report_include_logs),
description = stringResource(Res.string.bug_report_include_logs_description),
checked = state.bugIncludeLogs,
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/NotificationSettings.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/NotificationSettings.kt
index 8916d91..95bb9ff 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/NotificationSettings.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/NotificationSettings.kt
@@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vnidrop.app.notifications.NotificationPermission
import com.vnidrop.app.ui.components.SecondaryButton
+import com.vnidrop.app.ui.icons.AppIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
@@ -32,7 +33,7 @@ internal fun NotificationSettings(
SettingsTopBar(stringResource(Res.string.notifications_title), onBack, showBack)
SettingsGroup {
SettingsToggleRow(
- icon = SettingsIcons.Bell,
+ icon = AppIcon.Bell,
title = stringResource(Res.string.notifications_local_title),
description = stringResource(Res.string.notifications_description),
checked = state.notificationsEnabled,
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/PreferencesSettings.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/PreferencesSettings.kt
index 315d527..3f086a2 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/PreferencesSettings.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/PreferencesSettings.kt
@@ -9,6 +9,7 @@ import com.vnidrop.app.core.FolderAccessStatus
import com.vnidrop.app.ui.components.Field
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.SecondaryButton
+import com.vnidrop.app.ui.icons.AppIcon
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
import vnidrop.shared.generated.resources.button_choose_folder
@@ -35,14 +36,14 @@ internal fun PreferencesSettings(
Field(state.username, onUsernameChanged, stringResource(Res.string.field_username))
SettingsGroup {
SettingsRow(
- icon = SettingsIcons.Folder,
+ icon = AppIcon.Folder,
title = stringResource(Res.string.preferences_receive_folder_title),
value = state.receiveFolder?.displayName?.ifBlank { state.receiveFolder.value },
iconTone = SettingsIconTone.Neutral,
)
SettingsDivider()
SettingsRow(
- icon = SettingsIcons.Check,
+ icon = AppIcon.Check,
title = state.folderStatusLabel(),
iconTone = SettingsIconTone.Neutral,
)
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsComponents.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsComponents.kt
index 9b8b838..60d9ff6 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsComponents.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsComponents.kt
@@ -19,7 +19,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
@@ -30,12 +29,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
@@ -52,8 +52,8 @@ internal fun SettingsTopBar(title: String, onBack: () -> Unit, showBack: Boolean
) {
if (showBack) {
IconButton(onClick = onBack) {
- Icon(
- SettingsIcons.Back,
+ PlatformIcon(
+ AppIcon.ArrowBack,
contentDescription = stringResource(Res.string.button_back),
tint = LocalVniDropColors.current.foregroundDefault,
)
@@ -78,7 +78,7 @@ internal fun SettingsGroup(content: @Composable ColumnScope.() -> Unit) {
@Composable
internal fun SettingsRow(
- icon: ImageVector,
+ icon: AppIcon,
title: String,
value: String? = null,
subtitle: String? = null,
@@ -135,7 +135,7 @@ internal fun SettingsRow(
}
onClick != null && showsDisclosure -> {
Spacer(Modifier.width(8.dp))
- Icon(SettingsIcons.ChevronRight, contentDescription = null, tint = colors.foregroundLighter, modifier = Modifier.size(18.dp))
+ PlatformIcon(AppIcon.ChevronRight, contentDescription = null, tint = colors.foregroundLighter, modifier = Modifier.size(18.dp))
}
}
}
@@ -143,7 +143,7 @@ internal fun SettingsRow(
@Composable
internal fun SettingsToggleRow(
- icon: ImageVector,
+ icon: AppIcon,
title: String,
description: String,
checked: Boolean,
@@ -203,7 +203,7 @@ internal fun InfoItem(title: String, value: String) {
}
@Composable
-private fun SettingsLeadingIcon(icon: ImageVector, tone: SettingsIconTone) {
+private fun SettingsLeadingIcon(icon: AppIcon, tone: SettingsIconTone) {
val colors = LocalVniDropColors.current
val foreground = if (tone == SettingsIconTone.Brand) colors.brandLink else colors.foregroundLight
val background = if (tone == SettingsIconTone.Brand) colors.brandLink.copy(alpha = 0.13f) else colors.backgroundSurface300
@@ -211,6 +211,6 @@ private fun SettingsLeadingIcon(icon: ImageVector, tone: SettingsIconTone) {
modifier = Modifier.size(34.dp).background(background, RoundedCornerShape(10.dp)),
contentAlignment = Alignment.Center,
) {
- Icon(icon, contentDescription = null, tint = foreground, modifier = Modifier.size(19.dp))
+ PlatformIcon(icon, contentDescription = null, tint = foreground, modifier = Modifier.size(19.dp))
}
}
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsIcons.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsIcons.kt
deleted file mode 100644
index 97cbe79..0000000
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsIcons.kt
+++ /dev/null
@@ -1,328 +0,0 @@
-package com.vnidrop.app.feature.settings
-
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.PathFillType
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.StrokeJoin
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.PathBuilder
-import androidx.compose.ui.graphics.vector.path
-import androidx.compose.ui.unit.dp
-
-internal object SettingsIcons {
- val ChevronRight = lineIcon("ChevronRight") {
- moveTo(9f, 18f)
- lineTo(15f, 12f)
- lineTo(9f, 6f)
- }
- val Back = lineIcon("Back") {
- moveTo(19f, 12f)
- lineTo(5f, 12f)
- moveTo(12f, 19f)
- lineTo(5f, 12f)
- lineTo(12f, 5f)
- }
- val Check = lineIcon("Check") {
- moveTo(20f, 6f)
- lineTo(9f, 17f)
- lineTo(4f, 12f)
- }
- val PaperPlane = lineIcon("PaperPlane") {
- moveTo(22f, 2f)
- lineTo(15f, 22f)
- lineTo(11f, 13f)
- lineTo(2f, 9f)
- close()
- moveTo(22f, 2f)
- lineTo(11f, 13f)
- }
- val AccountOff = lineIcon("AccountOff") {
- circle(9f, 8f, 4f)
- moveTo(2f, 21f)
- curveTo(2.8f, 16.8f, 5f, 15f, 9f, 15f)
- curveTo(11.1f, 15f, 12.7f, 15.5f, 14f, 16.5f)
- circle(18f, 18f, 4f)
- moveTo(16.6f, 16.6f)
- lineTo(19.4f, 19.4f)
- moveTo(19.4f, 16.6f)
- lineTo(16.6f, 19.4f)
- }
- val ShieldCheck = lineIcon("ShieldCheck") {
- moveTo(12f, 22f)
- curveTo(17f, 19.5f, 20f, 16.5f, 20f, 11f)
- lineTo(20f, 5f)
- lineTo(12f, 2f)
- lineTo(4f, 5f)
- lineTo(4f, 11f)
- curveTo(4f, 16.5f, 7f, 19.5f, 12f, 22f)
- moveTo(8f, 12f)
- lineTo(11f, 15f)
- lineTo(16f, 9f)
- }
- val Shield = lineIcon("Shield") {
- moveTo(12f, 22f)
- curveTo(17f, 19.5f, 20f, 16.5f, 20f, 11f)
- lineTo(20f, 5f)
- lineTo(12f, 2f)
- lineTo(4f, 5f)
- lineTo(4f, 11f)
- curveTo(4f, 16.5f, 7f, 19.5f, 12f, 22f)
- }
- val Lock = lineIcon("Lock") {
- roundRect(5f, 10f, 14f, 11f, 2f)
- moveTo(8f, 10f)
- lineTo(8f, 7f)
- arcTo(4f, 4f, 0f, false, true, 16f, 7f)
- lineTo(16f, 10f)
- }
- val Code = lineIcon("Code") {
- moveTo(8f, 9f)
- lineTo(3f, 14f)
- lineTo(8f, 19f)
- moveTo(16f, 9f)
- lineTo(21f, 14f)
- lineTo(16f, 19f)
- moveTo(14f, 4f)
- lineTo(10f, 22f)
- }
- val CloudOff = lineIcon("CloudOff") {
- moveTo(5.5f, 5.5f)
- lineTo(18.5f, 18.5f)
- moveTo(7f, 18f)
- lineTo(6f, 18f)
- curveTo(2.7f, 18f, 1f, 16.2f, 1f, 13.5f)
- curveTo(1f, 10.7f, 3.1f, 8.5f, 6f, 8.1f)
- curveTo(7.5f, 4.9f, 10.1f, 3f, 13.5f, 3f)
- curveTo(18f, 3f, 21f, 6.5f, 21f, 11f)
- curveTo(22.3f, 12f, 23f, 13.4f, 23f, 15f)
- curveTo(23f, 16.1f, 22.7f, 17f, 22f, 18f)
- }
- val Sync = lineIcon("Sync") {
- moveTo(20f, 7f)
- lineTo(20f, 3f)
- lineTo(16f, 3f)
- moveTo(20f, 3f)
- curveTo(17.7f, 1.4f, 14.8f, 1f, 12f, 2f)
- curveTo(9.6f, 2.8f, 7.7f, 4.6f, 7f, 7f)
- moveTo(4f, 17f)
- lineTo(4f, 21f)
- lineTo(8f, 21f)
- moveTo(4f, 21f)
- curveTo(6.3f, 22.6f, 9.2f, 23f, 12f, 22f)
- curveTo(14.4f, 21.2f, 16.3f, 19.4f, 17f, 17f)
- }
- val Megaphone = lineIcon("Megaphone") {
- moveTo(3f, 11f)
- lineTo(3f, 15f)
- lineTo(7f, 15f)
- lineTo(18f, 20f)
- lineTo(18f, 6f)
- lineTo(7f, 11f)
- close()
- moveTo(7f, 15f)
- lineTo(9f, 21f)
- lineTo(13f, 21f)
- lineTo(11.5f, 17f)
- moveTo(21f, 10f)
- lineTo(21f, 16f)
- }
- val QrCode = lineIcon("QrCode") {
- moveTo(3f, 9f)
- lineTo(3f, 3f)
- lineTo(9f, 3f)
- moveTo(15f, 3f)
- lineTo(21f, 3f)
- lineTo(21f, 9f)
- moveTo(3f, 15f)
- lineTo(3f, 21f)
- lineTo(9f, 21f)
- moveTo(15f, 21f)
- lineTo(15f, 15f)
- lineTo(21f, 15f)
- moveTo(7f, 7f)
- lineTo(7.01f, 7f)
- moveTo(17f, 7f)
- lineTo(17.01f, 7f)
- moveTo(7f, 17f)
- lineTo(7.01f, 17f)
- moveTo(20f, 20f)
- lineTo(20.01f, 20f)
- }
- val Hand = lineIcon("Hand", strokeWidth = 1.6f) {
- moveTo(4f, 14f)
- lineTo(4f, 10f)
- curveTo(4f, 8.9f, 4.9f, 8f, 6f, 8f)
- curveTo(7.1f, 8f, 8f, 8.9f, 8f, 10f)
- lineTo(8f, 12f)
- lineTo(8.5f, 12f)
- lineTo(8.5f, 6f)
- curveTo(8.5f, 4.9f, 9.4f, 4f, 10.5f, 4f)
- curveTo(11.6f, 4f, 12.5f, 4.9f, 12.5f, 6f)
- lineTo(12.5f, 11f)
- lineTo(13f, 11f)
- lineTo(13f, 4f)
- curveTo(13f, 2.9f, 13.9f, 2f, 15f, 2f)
- curveTo(16.1f, 2f, 17f, 2.9f, 17f, 4f)
- lineTo(17f, 12f)
- lineTo(17.5f, 12f)
- lineTo(17.5f, 7f)
- curveTo(17.5f, 5.9f, 18.4f, 5f, 19.5f, 5f)
- curveTo(20.6f, 5f, 21.5f, 5.9f, 21.5f, 7f)
- lineTo(21.5f, 14f)
- lineTo(22f, 13.5f)
- curveTo(22.4f, 13.1f, 23f, 13.2f, 23.4f, 13.7f)
- curveTo(23.9f, 14.4f, 23.7f, 15.3f, 23.1f, 16f)
- lineTo(18.8f, 21f)
- curveTo(17.7f, 22.3f, 16.1f, 23f, 14.3f, 23f)
- lineTo(12f, 23f)
- curveTo(7.6f, 23f, 4f, 19.4f, 4f, 15f)
- close()
- }
- val Radio = lineIcon("Radio") {
- moveTo(12f, 12f)
- lineTo(12f, 22f)
- moveTo(9f, 22f)
- lineTo(15f, 22f)
- moveTo(9f, 9f)
- curveTo(7.5f, 10.7f, 7.5f, 13.3f, 9f, 15f)
- moveTo(15f, 9f)
- curveTo(16.5f, 10.7f, 16.5f, 13.3f, 15f, 15f)
- moveTo(6f, 6f)
- curveTo(2.7f, 9.3f, 2.7f, 14.7f, 6f, 18f)
- moveTo(18f, 6f)
- curveTo(21.3f, 9.3f, 21.3f, 14.7f, 18f, 18f)
- circle(12f, 12f, 1f)
- }
- val Drive = lineIcon("Drive") {
- roundRect(2f, 6f, 20f, 12f, 3f)
- moveTo(2f, 14f)
- lineTo(22f, 14f)
- moveTo(17f, 16f)
- lineTo(17.01f, 16f)
- moveTo(20f, 16f)
- lineTo(20.01f, 16f)
- }
- val Sun = lineIcon("Sun") {
- moveTo(12f, 4f)
- lineTo(12f, 2f)
- moveTo(12f, 22f)
- lineTo(12f, 20f)
- moveTo(4.93f, 4.93f)
- lineTo(6.34f, 6.34f)
- moveTo(17.66f, 17.66f)
- lineTo(19.07f, 19.07f)
- moveTo(2f, 12f)
- lineTo(4f, 12f)
- moveTo(20f, 12f)
- lineTo(22f, 12f)
- moveTo(4.93f, 19.07f)
- lineTo(6.34f, 17.66f)
- moveTo(17.66f, 6.34f)
- lineTo(19.07f, 4.93f)
- moveTo(16f, 12f)
- arcTo(4f, 4f, 0f, true, true, 8f, 12f)
- arcTo(4f, 4f, 0f, true, true, 16f, 12f)
- }
- val Moon = lineIcon("Moon") {
- moveTo(21f, 12.79f)
- arcTo(9f, 9f, 0f, true, true, 11.21f, 3f)
- arcTo(7f, 7f, 0f, false, false, 21f, 12.79f)
- }
- val Device = lineIcon("Device") {
- roundRect(7f, 2f, 10f, 20f, 2.5f)
- moveTo(11f, 18f)
- lineTo(13f, 18f)
- }
- val Folder = lineIcon("Folder") {
- moveTo(3f, 7f)
- lineTo(9f, 7f)
- lineTo(11f, 9f)
- lineTo(21f, 9f)
- lineTo(21f, 19f)
- lineTo(3f, 19f)
- close()
- }
- val Info = lineIcon("Info") {
- moveTo(12f, 16f)
- lineTo(12f, 12f)
- moveTo(12f, 8f)
- lineTo(12.01f, 8f)
- moveTo(21f, 12f)
- arcTo(9f, 9f, 0f, true, true, 3f, 12f)
- arcTo(9f, 9f, 0f, true, true, 21f, 12f)
- }
- val Bell = lineIcon("Bell") {
- moveTo(18f, 8f)
- arcTo(6f, 6f, 0f, false, false, 6f, 8f)
- lineTo(6f, 13f)
- lineTo(4f, 17f)
- lineTo(20f, 17f)
- lineTo(18f, 13f)
- close()
- moveTo(10f, 21f)
- arcTo(2f, 2f, 0f, false, false, 14f, 21f)
- }
- val Document = lineIcon("Document") {
- moveTo(14f, 2f)
- lineTo(6f, 2f)
- arcTo(2f, 2f, 0f, false, false, 4f, 4f)
- lineTo(4f, 20f)
- arcTo(2f, 2f, 0f, false, false, 6f, 22f)
- lineTo(18f, 22f)
- arcTo(2f, 2f, 0f, false, false, 20f, 20f)
- lineTo(20f, 8f)
- lineTo(14f, 2f)
- moveTo(14f, 2f)
- lineTo(14f, 8f)
- lineTo(20f, 8f)
- }
- val Bug = lineIcon("Bug") {
- roundRect(7f, 6f, 10f, 14f, 5f)
- moveTo(3f, 10f)
- lineTo(7f, 10f)
- moveTo(17f, 10f)
- lineTo(21f, 10f)
- moveTo(3f, 16f)
- lineTo(7f, 16f)
- moveTo(17f, 16f)
- lineTo(21f, 16f)
- moveTo(12f, 6f)
- lineTo(12f, 20f)
- }
-}
-
-private fun lineIcon(
- name: String,
- strokeWidth: Float = 2f,
- block: PathBuilder.() -> Unit,
-): ImageVector =
- ImageVector.Builder(name, 24.dp, 24.dp, 24f, 24f).apply {
- path(
- fill = SolidColor(Color.Transparent),
- stroke = SolidColor(Color.Black),
- strokeLineWidth = strokeWidth,
- strokeLineCap = StrokeCap.Round,
- strokeLineJoin = StrokeJoin.Round,
- pathFillType = PathFillType.NonZero,
- pathBuilder = block,
- )
- }.build()
-
-private fun PathBuilder.roundRect(x: Float, y: Float, width: Float, height: Float, radius: Float) {
- moveTo(x + radius, y)
- lineTo(x + width - radius, y)
- arcTo(radius, radius, 0f, false, true, x + width, y + radius)
- lineTo(x + width, y + height - radius)
- arcTo(radius, radius, 0f, false, true, x + width - radius, y + height)
- lineTo(x + radius, y + height)
- arcTo(radius, radius, 0f, false, true, x, y + height - radius)
- lineTo(x, y + radius)
- arcTo(radius, radius, 0f, false, true, x + radius, y)
-}
-
-private fun PathBuilder.circle(centerX: Float, centerY: Float, radius: Float) {
- moveTo(centerX + radius, centerY)
- arcTo(radius, radius, 0f, true, true, centerX - radius, centerY)
- arcTo(radius, radius, 0f, true, true, centerX + radius, centerY)
-}
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsOverview.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsOverview.kt
index 0f7ecbf..5d292c4 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsOverview.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/SettingsOverview.kt
@@ -7,6 +7,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
+import com.vnidrop.app.ui.icons.AppIcon
import com.vnidrop.app.ui.theme.ThemeMode
import org.jetbrains.compose.resources.stringResource
import vnidrop.shared.generated.resources.Res
@@ -34,14 +35,14 @@ internal fun SettingsOverview(
)
SettingsGroup {
SettingsRow(
- icon = SettingsIcons.Drive,
+ icon = AppIcon.Storage,
title = stringResource(Res.string.storage_title),
selected = state.selectedSection == SettingsSection.Storage,
onClick = { onSectionSelected(SettingsSection.Storage) },
)
SettingsDivider()
SettingsRow(
- icon = SettingsIcons.Device,
+ icon = AppIcon.User,
title = stringResource(Res.string.preferences_title),
value = state.username,
selected = state.selectedSection == SettingsSection.Preferences,
@@ -49,7 +50,7 @@ internal fun SettingsOverview(
)
SettingsDivider()
SettingsRow(
- icon = SettingsIcons.Sun,
+ icon = AppIcon.Sun,
title = stringResource(Res.string.appearance_title),
value = themeModeLabel(state.themeMode),
selected = state.selectedSection == SettingsSection.Appearance,
@@ -58,14 +59,14 @@ internal fun SettingsOverview(
}
SettingsGroup {
SettingsRow(
- icon = SettingsIcons.Bell,
+ icon = AppIcon.Bell,
title = stringResource(Res.string.notifications_title),
selected = state.selectedSection == SettingsSection.Notifications,
onClick = { onSectionSelected(SettingsSection.Notifications) },
)
SettingsDivider()
SettingsRow(
- icon = SettingsIcons.Info,
+ icon = AppIcon.Info,
title = stringResource(Res.string.about_title),
selected = state.selectedSection == SettingsSection.About,
iconTone = SettingsIconTone.Neutral,
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/StorageSettings.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/StorageSettings.kt
index 07b1b48..f5c4c75 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/StorageSettings.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/feature/settings/StorageSettings.kt
@@ -9,6 +9,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
+import com.vnidrop.app.ui.icons.AppIcon
import com.vnidrop.app.ui.state.formatBytes
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.stringResource
@@ -37,26 +38,26 @@ internal fun StorageSettings(
if (storage == null || state.isCalculatingStorage) {
SettingsGroup {
SettingsRow(
- icon = SettingsIcons.Drive,
+ icon = AppIcon.Storage,
title = stringResource(Res.string.storage_calculating),
trailing = { CircularProgressIndicator() },
)
}
} else {
SettingsGroup {
- StorageRow(stringResource(Res.string.storage_transfer_data), storage.transferCacheBytes)
+ StorageRow(AppIcon.TransferData, stringResource(Res.string.storage_transfer_data), storage.transferCacheBytes)
SettingsDivider()
- StorageRow(stringResource(Res.string.storage_app_data), storage.appDataBytes)
+ StorageRow(AppIcon.Storage, stringResource(Res.string.storage_app_data), storage.appDataBytes)
SettingsDivider()
- StorageRow(stringResource(Res.string.storage_temporary), storage.temporaryBytes)
+ StorageRow(AppIcon.Temporary, stringResource(Res.string.storage_temporary), storage.temporaryBytes)
SettingsDivider()
SettingsRow(
- icon = SettingsIcons.Folder,
+ icon = AppIcon.Folder,
title = stringResource(Res.string.storage_received_files),
value = formatBytes(storage.receivedBytes),
)
SettingsDivider()
- StorageRow(stringResource(Res.string.storage_total), storage.deviceImpactBytes)
+ StorageRow(AppIcon.TotalStorage, stringResource(Res.string.storage_total), storage.deviceImpactBytes)
}
}
Button(
@@ -75,6 +76,6 @@ internal fun StorageSettings(
}
@Composable
-private fun StorageRow(title: String, bytes: ULong) {
- SettingsRow(icon = SettingsIcons.Drive, title = title, value = formatBytes(bytes))
+private fun StorageRow(icon: AppIcon, title: String, bytes: ULong) {
+ SettingsRow(icon = icon, title = title, value = formatBytes(bytes))
}
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/components/AdaptiveDrawer.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/components/AdaptiveDrawer.kt
index 56e9ee3..c8f162b 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/components/AdaptiveDrawer.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/components/AdaptiveDrawer.kt
@@ -8,22 +8,18 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.vnidrop.app.isDesktop
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.platform.LocalUiPlatform
import com.vnidrop.app.ui.platform.usesMobilePresentation
import com.vnidrop.app.ui.state.WindowClass
@@ -75,14 +71,7 @@ private fun ClosableModalContent(
onClick = onClose,
modifier = Modifier.align(androidx.compose.ui.Alignment.TopEnd).padding(8.dp).size(40.dp),
) {
- Icon(CloseIcon, stringResource(Res.string.button_close), tint = LocalVniDropColors.current.foregroundLight)
+ PlatformIcon(AppIcon.Close, stringResource(Res.string.button_close), tint = LocalVniDropColors.current.foregroundLight)
}
}
}
-
-private val CloseIcon = ImageVector.Builder("Close", 24.dp, 24.dp, 24f, 24f).apply {
- path(fill = SolidColor(Color.Transparent), stroke = SolidColor(Color.Black), strokeLineWidth = 2f, strokeLineCap = StrokeCap.Round) {
- moveTo(6f, 6f); lineTo(18f, 18f)
- moveTo(18f, 6f); lineTo(6f, 18f)
- }
-}.build()
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/feedback/VniDropSnackbarHost.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/feedback/VniDropSnackbarHost.kt
index d3454bb..7e8ce53 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/feedback/VniDropSnackbarHost.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/feedback/VniDropSnackbarHost.kt
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarData
@@ -28,12 +27,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.PathFillType
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
+import com.vnidrop.app.ui.icons.AppIcon
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
@@ -131,8 +127,8 @@ private fun SnackbarMessage(message: String, modifier: Modifier = Modifier) {
@Composable
private fun DismissButton(onClick: () -> Unit) {
IconButton(onClick = onClick, modifier = Modifier.size(40.dp)) {
- Icon(
- imageVector = CloseIcon,
+ PlatformIcon(
+ icon = AppIcon.Close,
contentDescription = stringResource(Res.string.snackbar_dismiss),
tint = LocalVniDropColors.current.foregroundLighter,
modifier = Modifier.size(18.dp),
@@ -140,21 +136,6 @@ private fun DismissButton(onClick: () -> Unit) {
}
}
-private val CloseIcon = ImageVector.Builder("Close", 24.dp, 24.dp, 24f, 24f).apply {
- path(
- fill = SolidColor(Color.Transparent),
- stroke = SolidColor(Color.Black),
- strokeLineWidth = 2f,
- strokeLineCap = StrokeCap.Round,
- pathFillType = PathFillType.NonZero,
- ) {
- moveTo(6f, 6f)
- lineTo(18f, 18f)
- moveTo(18f, 6f)
- lineTo(6f, 18f)
- }
-}.build()
-
private suspend fun UiText.resolve(): String = when (this) {
is UiText.Dynamic -> value
is UiText.Resource -> getString(resource)
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/icons/PlatformIcons.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/icons/PlatformIcons.kt
new file mode 100644
index 0000000..bd7e6a0
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/icons/PlatformIcons.kt
@@ -0,0 +1,115 @@
+package com.vnidrop.app.ui.icons
+
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.vnidrop.app.UiPlatform
+import com.vnidrop.app.ui.platform.LocalUiPlatform
+import org.jetbrains.compose.resources.DrawableResource
+import org.jetbrains.compose.resources.painterResource
+import vnidrop.shared.generated.resources.*
+
+internal enum class AppIcon(
+ val material: DrawableResource,
+ val fluent: DrawableResource,
+ val lucide: DrawableResource,
+) {
+ Add(Res.drawable.icon_material_add, Res.drawable.icon_fluent_add, Res.drawable.icon_lucide_add),
+ ArrowBack(Res.drawable.icon_material_arrow_back, Res.drawable.icon_fluent_arrow_back, Res.drawable.icon_lucide_arrow_back),
+ Bell(Res.drawable.icon_material_bell, Res.drawable.icon_fluent_bell, Res.drawable.icon_lucide_bell),
+ Bug(Res.drawable.icon_material_bug, Res.drawable.icon_fluent_bug, Res.drawable.icon_lucide_bug),
+ Check(Res.drawable.icon_material_check, Res.drawable.icon_fluent_check, Res.drawable.icon_lucide_check),
+ ChevronRight(
+ Res.drawable.icon_material_chevron_right,
+ Res.drawable.icon_fluent_chevron_right,
+ Res.drawable.icon_lucide_chevron_right,
+ ),
+ Close(Res.drawable.icon_material_close, Res.drawable.icon_fluent_close, Res.drawable.icon_lucide_close),
+ Code(Res.drawable.icon_material_code, Res.drawable.icon_fluent_code, Res.drawable.icon_lucide_code),
+ CloudOff(Res.drawable.icon_material_cloud_off, Res.drawable.icon_fluent_cloud_off, Res.drawable.icon_lucide_cloud_off),
+ Delete(Res.drawable.icon_material_delete, Res.drawable.icon_fluent_delete, Res.drawable.icon_lucide_delete),
+ Document(Res.drawable.icon_material_document, Res.drawable.icon_fluent_document, Res.drawable.icon_lucide_document),
+ Download(Res.drawable.icon_material_download, Res.drawable.icon_fluent_download, Res.drawable.icon_lucide_download),
+ File(Res.drawable.icon_material_file, Res.drawable.icon_fluent_file, Res.drawable.icon_lucide_file),
+ Folder(Res.drawable.icon_material_folder, Res.drawable.icon_fluent_folder, Res.drawable.icon_lucide_folder),
+ Globe(Res.drawable.icon_material_globe, Res.drawable.icon_fluent_globe, Res.drawable.icon_lucide_globe),
+ Hand(Res.drawable.icon_material_hand, Res.drawable.icon_fluent_hand, Res.drawable.icon_lucide_hand),
+ Info(Res.drawable.icon_material_info, Res.drawable.icon_fluent_info, Res.drawable.icon_lucide_info),
+ Lock(Res.drawable.icon_material_lock, Res.drawable.icon_fluent_lock, Res.drawable.icon_lucide_lock),
+ Megaphone(Res.drawable.icon_material_megaphone, Res.drawable.icon_fluent_megaphone, Res.drawable.icon_lucide_megaphone),
+ Moon(Res.drawable.icon_material_moon, Res.drawable.icon_fluent_moon, Res.drawable.icon_lucide_moon),
+ Nfc(Res.drawable.icon_material_nfc, Res.drawable.icon_fluent_nfc, Res.drawable.icon_lucide_nfc),
+ QrCode(Res.drawable.icon_material_qr_code, Res.drawable.icon_fluent_qr_code, Res.drawable.icon_lucide_qr_code),
+ Radio(Res.drawable.icon_material_radio, Res.drawable.icon_fluent_radio, Res.drawable.icon_lucide_radio),
+ Scan(Res.drawable.icon_material_scan, Res.drawable.icon_fluent_scan, Res.drawable.icon_lucide_scan),
+ Send(Res.drawable.icon_material_send, Res.drawable.icon_fluent_send, Res.drawable.icon_lucide_send),
+ Settings(Res.drawable.icon_material_settings, Res.drawable.icon_fluent_settings, Res.drawable.icon_lucide_settings),
+ Shield(Res.drawable.icon_material_shield, Res.drawable.icon_fluent_shield, Res.drawable.icon_lucide_shield),
+ ShieldCheck(
+ Res.drawable.icon_material_shield_check,
+ Res.drawable.icon_fluent_shield_check,
+ Res.drawable.icon_lucide_shield_check,
+ ),
+ Storage(Res.drawable.icon_material_storage, Res.drawable.icon_fluent_storage, Res.drawable.icon_lucide_storage),
+ Sun(Res.drawable.icon_material_sun, Res.drawable.icon_fluent_sun, Res.drawable.icon_lucide_sun),
+ Sync(Res.drawable.icon_material_sync, Res.drawable.icon_fluent_sync, Res.drawable.icon_lucide_sync),
+ SystemTheme(
+ Res.drawable.icon_material_system_theme,
+ Res.drawable.icon_fluent_system_theme,
+ Res.drawable.icon_lucide_system_theme,
+ ),
+ Temporary(
+ Res.drawable.icon_material_temporary,
+ Res.drawable.icon_fluent_temporary,
+ Res.drawable.icon_lucide_temporary,
+ ),
+ TotalStorage(
+ Res.drawable.icon_material_total_storage,
+ Res.drawable.icon_fluent_total_storage,
+ Res.drawable.icon_lucide_total_storage,
+ ),
+ TransferData(
+ Res.drawable.icon_material_transfer_data,
+ Res.drawable.icon_fluent_transfer_data,
+ Res.drawable.icon_lucide_transfer_data,
+ ),
+ User(Res.drawable.icon_material_user, Res.drawable.icon_fluent_user, Res.drawable.icon_lucide_user),
+ UserOff(Res.drawable.icon_material_user_off, Res.drawable.icon_fluent_user_off, Res.drawable.icon_lucide_user_off),
+}
+
+internal enum class IconFamily {
+ Material,
+ Fluent,
+ Lucide,
+}
+
+internal fun iconFamilyFor(uiPlatform: UiPlatform): IconFamily = when (uiPlatform) {
+ UiPlatform.Android -> IconFamily.Material
+ UiPlatform.Windows -> IconFamily.Fluent
+ UiPlatform.Linux,
+ UiPlatform.Desktop,
+ -> IconFamily.Lucide
+}
+
+internal fun AppIcon.resourceFor(uiPlatform: UiPlatform): DrawableResource = when (iconFamilyFor(uiPlatform)) {
+ IconFamily.Material -> material
+ IconFamily.Fluent -> fluent
+ IconFamily.Lucide -> lucide
+}
+
+@Composable
+internal fun PlatformIcon(
+ icon: AppIcon,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current,
+) {
+ Icon(
+ painter = painterResource(icon.resourceFor(LocalUiPlatform.current)),
+ contentDescription = contentDescription,
+ modifier = modifier,
+ tint = tint,
+ )
+}
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppDestination.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppDestination.kt
index cfa21cf..a2bc095 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppDestination.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppDestination.kt
@@ -1,6 +1,6 @@
package com.vnidrop.app.ui.navigation
-import androidx.compose.ui.graphics.vector.ImageVector
+import com.vnidrop.app.ui.icons.AppIcon
import org.jetbrains.compose.resources.StringResource
import vnidrop.shared.generated.resources.Res
import vnidrop.shared.generated.resources.nav_receive
@@ -13,17 +13,17 @@ enum class AppDestination {
Settings,
}
-data class NavigationItem(
+internal data class NavigationItem(
val destination: AppDestination,
val label: StringResource,
- val icon: ImageVector,
+ val icon: AppIcon,
)
// The route list is intentionally tiny for this phase. Activity, receiver
// requests, and diagnostics remain available inside screens instead of being
// promoted to top-level navigation.
-val primaryNavigationItems = listOf(
- NavigationItem(AppDestination.Send, Res.string.nav_send, VniDropIcons.Send),
- NavigationItem(AppDestination.Receive, Res.string.nav_receive, VniDropIcons.Receive),
- NavigationItem(AppDestination.Settings, Res.string.nav_settings, VniDropIcons.Settings),
+internal val primaryNavigationItems = listOf(
+ NavigationItem(AppDestination.Send, Res.string.nav_send, AppIcon.Send),
+ NavigationItem(AppDestination.Receive, Res.string.nav_receive, AppIcon.Download),
+ NavigationItem(AppDestination.Settings, Res.string.nav_settings, AppIcon.Settings),
)
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppNavigation.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppNavigation.kt
index 37c55f2..1a1b416 100644
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppNavigation.kt
+++ b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/AppNavigation.kt
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
@@ -32,6 +31,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vnidrop.app.UiPlatform
import com.vnidrop.app.isDesktop
+import com.vnidrop.app.ui.icons.PlatformIcon
import com.vnidrop.app.ui.platform.DesktopNavigationWidthDp
import com.vnidrop.app.ui.state.WindowClass
import com.vnidrop.app.ui.theme.LocalVniDropColors
@@ -86,7 +86,7 @@ private fun AndroidNavigationRail(
NavigationRailItem(
selected = item.destination == selected,
onClick = { onDestinationSelected(item.destination) },
- icon = { Icon(item.icon, contentDescription = label) },
+ icon = { PlatformIcon(item.icon, contentDescription = label) },
label = { Text(label, maxLines = 1, overflow = TextOverflow.Ellipsis) },
colors = NavigationRailItemDefaults.colors(
selectedIconColor = colors.brandLink,
@@ -152,7 +152,7 @@ private fun DesktopNavigationItem(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
- Icon(item.icon, contentDescription = label, tint = if (selected) colors.brandLink else foreground, modifier = Modifier.size(20.dp))
+ PlatformIcon(item.icon, contentDescription = label, tint = if (selected) colors.brandLink else foreground, modifier = Modifier.size(20.dp))
Text(
text = label,
color = foreground,
@@ -180,7 +180,7 @@ fun AppBottomNavigation(
NavigationBarItem(
selected = item.destination == selected,
onClick = { onDestinationSelected(item.destination) },
- icon = { Icon(item.icon, contentDescription = label, modifier = Modifier.size(24.dp)) },
+ icon = { PlatformIcon(item.icon, contentDescription = label, modifier = Modifier.size(24.dp)) },
label = { Text(label, maxLines = 1, overflow = TextOverflow.Ellipsis) },
colors = NavigationBarItemDefaults.colors(
selectedIconColor = colors.brandLink,
diff --git a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/VniDropIcons.kt b/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/VniDropIcons.kt
deleted file mode 100644
index eddae76..0000000
--- a/shared/src/commonMain/kotlin/com/vnidrop/app/ui/navigation/VniDropIcons.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.vnidrop.app.ui.navigation
-
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.PathFillType
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.StrokeJoin
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.path
-import androidx.compose.ui.unit.dp
-
-object VniDropIcons {
- val Send: ImageVector by lazy {
- ImageVector.Builder("Send", 24.dp, 24.dp, 24f, 24f).apply {
- path(
- fill = SolidColor(Color.Transparent),
- stroke = SolidColor(Color.Black),
- strokeLineWidth = 2f,
- strokeLineCap = StrokeCap.Round,
- strokeLineJoin = StrokeJoin.Round,
- pathFillType = PathFillType.NonZero,
- ) {
- moveTo(22f, 2f)
- lineTo(11f, 13f)
- moveTo(22f, 2f)
- lineTo(15f, 22f)
- lineTo(11f, 13f)
- lineTo(2f, 9f)
- lineTo(22f, 2f)
- }
- }.build()
- }
-
- val Receive: ImageVector by lazy {
- ImageVector.Builder("Receive", 24.dp, 24.dp, 24f, 24f).apply {
- path(
- fill = SolidColor(Color.Transparent),
- stroke = SolidColor(Color.Black),
- strokeLineWidth = 2f,
- strokeLineCap = StrokeCap.Round,
- strokeLineJoin = StrokeJoin.Round,
- pathFillType = PathFillType.NonZero,
- ) {
- moveTo(12f, 17f)
- lineTo(12f, 3f)
- moveTo(6f, 11f)
- lineTo(12f, 17f)
- lineTo(18f, 11f)
- moveTo(19f, 21f)
- lineTo(5f, 21f)
- }
- }.build()
- }
-
- val Settings: ImageVector by lazy {
- ImageVector.Builder("Settings", 24.dp, 24.dp, 24f, 24f).apply {
- path(
- fill = SolidColor(Color.Transparent),
- stroke = SolidColor(Color.Black),
- strokeLineWidth = 2f,
- strokeLineCap = StrokeCap.Round,
- strokeLineJoin = StrokeJoin.Round,
- pathFillType = PathFillType.NonZero,
- ) {
- moveTo(9.671f, 4.136f)
- arcToRelative(2.34f, 2.34f, 0f, false, true, 4.659f, 0f)
- arcToRelative(2.34f, 2.34f, 0f, false, false, 3.319f, 1.915f)
- arcToRelative(2.34f, 2.34f, 0f, false, true, 2.33f, 4.033f)
- arcToRelative(2.34f, 2.34f, 0f, false, false, 0f, 3.831f)
- arcToRelative(2.34f, 2.34f, 0f, false, true, -2.33f, 4.033f)
- arcToRelative(2.34f, 2.34f, 0f, false, false, -3.319f, 1.915f)
- arcToRelative(2.34f, 2.34f, 0f, false, true, -4.659f, 0f)
- arcToRelative(2.34f, 2.34f, 0f, false, false, -3.32f, -1.915f)
- arcToRelative(2.34f, 2.34f, 0f, false, true, -2.33f, -4.033f)
- arcToRelative(2.34f, 2.34f, 0f, false, false, 0f, -3.831f)
- arcTo(2.34f, 2.34f, 0f, false, true, 6.35f, 6.051f)
- arcToRelative(2.34f, 2.34f, 0f, false, false, 3.319f, -1.915f)
- moveTo(12f, 15f)
- arcTo(3f, 3f, 0f, false, true, 12f, 9f)
- arcTo(3f, 3f, 0f, false, true, 12f, 15f)
- }
- }.build()
- }
-}
diff --git a/shared/src/commonTest/kotlin/com/vnidrop/app/ui/icons/PlatformIconsTest.kt b/shared/src/commonTest/kotlin/com/vnidrop/app/ui/icons/PlatformIconsTest.kt
new file mode 100644
index 0000000..a56ca81
--- /dev/null
+++ b/shared/src/commonTest/kotlin/com/vnidrop/app/ui/icons/PlatformIconsTest.kt
@@ -0,0 +1,24 @@
+package com.vnidrop.app.ui.icons
+
+import com.vnidrop.app.UiPlatform
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class PlatformIconsTest {
+ @Test
+ fun iconFamiliesFollowTheRuntimePlatform() {
+ assertEquals(IconFamily.Material, iconFamilyFor(UiPlatform.Android))
+ assertEquals(IconFamily.Fluent, iconFamilyFor(UiPlatform.Windows))
+ assertEquals(IconFamily.Lucide, iconFamilyFor(UiPlatform.Linux))
+ assertEquals(IconFamily.Lucide, iconFamilyFor(UiPlatform.Desktop))
+ }
+
+ @Test
+ fun everySemanticIconResolvesToItsPlatformResource() {
+ AppIcon.entries.forEach { icon ->
+ assertEquals(icon.material, icon.resourceFor(UiPlatform.Android), icon.name)
+ assertEquals(icon.fluent, icon.resourceFor(UiPlatform.Windows), icon.name)
+ assertEquals(icon.lucide, icon.resourceFor(UiPlatform.Linux), icon.name)
+ }
+ }
+}
diff --git a/shared/src/jvmTest/kotlin/com/vnidrop/app/ui/icons/PlatformIconsComposeTest.kt b/shared/src/jvmTest/kotlin/com/vnidrop/app/ui/icons/PlatformIconsComposeTest.kt
new file mode 100644
index 0000000..0e2cd40
--- /dev/null
+++ b/shared/src/jvmTest/kotlin/com/vnidrop/app/ui/icons/PlatformIconsComposeTest.kt
@@ -0,0 +1,52 @@
+package com.vnidrop.app.ui.icons
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.v2.runComposeUiTest
+import androidx.compose.ui.unit.dp
+import com.vnidrop.app.UiPlatform
+import com.vnidrop.app.ui.platform.LocalUiPlatform
+import com.vnidrop.app.ui.theme.VniDropTheme
+import kotlin.test.Test
+
+@OptIn(ExperimentalTestApi::class)
+class PlatformIconsComposeTest {
+ @Test
+ fun everyPlatformIconResourceRenders() = runComposeUiTest {
+ val platforms = listOf(UiPlatform.Android, UiPlatform.Windows, UiPlatform.Linux)
+ setContent {
+ VniDropTheme(isDarkTheme = false) {
+ Column {
+ platforms.forEach { platform ->
+ CompositionLocalProvider(LocalUiPlatform provides platform) {
+ AppIcon.entries.chunked(10).forEach { icons ->
+ Row {
+ icons.forEach { icon ->
+ PlatformIcon(
+ icon = icon,
+ contentDescription = null,
+ modifier = Modifier.size(24.dp).testTag("${platform.name}-${icon.name}"),
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ platforms.forEach { platform ->
+ AppIcon.entries.forEach { icon ->
+ onNodeWithTag("${platform.name}-${icon.name}").assertIsDisplayed()
+ }
+ }
+ }
+}
diff --git a/shared/tools/import_platform_icons.py b/shared/tools/import_platform_icons.py
new file mode 100644
index 0000000..4dc0978
--- /dev/null
+++ b/shared/tools/import_platform_icons.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+"""Import the curated platform icon set as Compose vector resources."""
+
+from __future__ import annotations
+
+import re
+import subprocess
+import urllib.parse
+import xml.etree.ElementTree as ET
+from dataclasses import dataclass
+from pathlib import Path
+
+
+MATERIAL_REVISION = "abd7f5c0e179c83f068c770650bd14ebac5d5a09"
+FLUENT_REVISION = "3cc5907ff2b187df22c6c711e8d0a96da41debf8"
+LUCIDE_REVISION = "4e2efb9350fac7dbcb94caae9ccac5654bdcc050"
+
+ANDROID_NS = "http://schemas.android.com/apk/res/android"
+ET.register_namespace("android", ANDROID_NS)
+
+
+@dataclass(frozen=True)
+class IconSource:
+ name: str
+ material: str
+ fluent_directory: str
+ fluent_file: str
+ lucide: str
+
+
+ICONS = (
+ IconSource("add", "add", "Add", "add", "plus"),
+ IconSource("arrow_back", "arrow_back", "Arrow Left", "arrow_left", "arrow-left"),
+ IconSource("bell", "notifications", "Alert", "alert", "bell"),
+ IconSource("bug", "bug_report", "Bug", "bug", "bug"),
+ IconSource("check", "check", "Checkmark", "checkmark", "check"),
+ IconSource("chevron_right", "chevron_right", "Chevron Right", "chevron_right", "chevron-right"),
+ IconSource("close", "close", "Dismiss", "dismiss", "x"),
+ IconSource("code", "code", "Code", "code", "code-xml"),
+ IconSource("cloud_off", "cloud_off", "Cloud Off", "cloud_off", "cloud-off"),
+ IconSource("delete", "delete", "Delete", "delete", "trash-2"),
+ IconSource("document", "description", "Document", "document", "file-text"),
+ IconSource("download", "download", "Arrow Download", "arrow_download", "download"),
+ IconSource("file", "draft", "Document", "document", "file"),
+ IconSource("folder", "folder", "Folder", "folder", "folder"),
+ IconSource("globe", "language", "Globe", "globe", "globe"),
+ IconSource("hand", "back_hand", "Hand Right", "hand_right", "hand"),
+ IconSource("info", "info", "Info", "info", "info"),
+ IconSource("lock", "lock", "Lock Closed", "lock_closed", "lock"),
+ IconSource("megaphone", "campaign", "Megaphone", "megaphone", "megaphone"),
+ IconSource("moon", "dark_mode", "Weather Moon", "weather_moon", "moon"),
+ IconSource("nfc", "nfc", "Tap Double", "tap_double", "nfc"),
+ IconSource("qr_code", "qr_code_scanner", "QR Code", "qr_code", "qr-code"),
+ IconSource("radio", "cell_tower", "Cellular Data 1", "cellular_data_1", "radio-tower"),
+ IconSource("scan", "document_scanner", "Scan Type", "scan_type", "scan-line"),
+ IconSource("send", "send", "Send", "send", "send"),
+ IconSource("settings", "settings", "Settings", "settings", "settings"),
+ IconSource("shield", "shield", "Shield", "shield", "shield"),
+ IconSource("shield_check", "verified_user", "Shield Checkmark", "shield_checkmark", "shield-check"),
+ IconSource("storage", "database", "Database", "database", "database"),
+ IconSource("sun", "light_mode", "Weather Sunny", "weather_sunny", "sun"),
+ IconSource("sync", "sync", "Arrow Sync", "arrow_sync", "refresh-cw"),
+ IconSource("system_theme", "devices", "Desktop", "desktop", "monitor-cog"),
+ IconSource("temporary", "schedule", "Clock", "clock", "timer-reset"),
+ IconSource("total_storage", "data_usage", "Data Pie", "data_pie", "chart-pie"),
+ IconSource("transfer_data", "inventory_2", "Archive", "archive", "package"),
+ IconSource("user", "account_circle", "Person Circle", "person_circle", "circle-user-round"),
+ IconSource("user_off", "person_off", "Person Prohibited", "person_prohibited", "user-round-x"),
+)
+
+ROOT = Path(__file__).resolve().parents[1]
+DRAWABLES = ROOT / "src/commonMain/composeResources/drawable"
+LICENSES = ROOT / "src/commonMain/composeResources/files/icon-licenses"
+
+
+def fetch(url: str) -> bytes:
+ return subprocess.run(
+ ["curl", "--fail", "--location", "--silent", "--show-error", url],
+ check=True,
+ capture_output=True,
+ ).stdout
+
+
+def android_attr(name: str) -> str:
+ return f"{{{ANDROID_NS}}}{name}"
+
+
+def write_vector(name: str, family: str, vector: ET.Element) -> None:
+ vector.insert(0, ET.Comment(f" {family} icon imported by shared/tools/import_platform_icons.py. "))
+ ET.indent(vector, space=" ")
+ data = ET.tostring(vector, encoding="unicode", xml_declaration=True)
+ (DRAWABLES / f"icon_{family}_{name}.xml").write_text(data + "\n", encoding="utf-8")
+
+
+def import_material(icon: IconSource) -> None:
+ url = (
+ "https://raw.githubusercontent.com/google/material-design-icons/"
+ f"{MATERIAL_REVISION}/symbols/android/{icon.material}/materialsymbolsrounded/"
+ f"{icon.material}_24px.xml"
+ )
+ vector = ET.fromstring(fetch(url))
+ vector.attrib.pop(android_attr("tint"), None)
+ for path in vector.iter("path"):
+ path.set(android_attr("fillColor"), "#FF000000")
+ write_vector(icon.name, "material", vector)
+
+
+def import_fluent(icon: IconSource) -> None:
+ directory = urllib.parse.quote(icon.fluent_directory)
+ url = (
+ "https://raw.githubusercontent.com/microsoft/fluentui-system-icons/"
+ f"{FLUENT_REVISION}/assets/{directory}/SVG/"
+ f"ic_fluent_{icon.fluent_file}_24_regular.svg"
+ )
+ svg = ET.fromstring(fetch(url))
+ view_box = [float(value) for value in svg.attrib["viewBox"].split()]
+ vector = vector_root(view_box[2], view_box[3])
+ for element in svg.iter():
+ if element.tag.rsplit("}", 1)[-1] != "path":
+ continue
+ path = ET.SubElement(vector, "path")
+ path.set(android_attr("pathData"), element.attrib["d"])
+ path.set(android_attr("fillColor"), "#FF000000")
+ if element.attrib.get("fill-rule") == "evenodd":
+ path.set(android_attr("fillType"), "evenOdd")
+ write_vector(icon.name, "fluent", vector)
+
+
+def clean_number(value: str | float) -> str:
+ value = float(value)
+ return str(int(value)) if value.is_integer() else str(value)
+
+
+def points_path(points: str, close: bool) -> str:
+ numbers = re.findall(r"-?\d+(?:\.\d+)?", points)
+ pairs = list(zip(numbers[0::2], numbers[1::2]))
+ commands = [f"M {x} {y}" if index == 0 else f"L {x} {y}" for index, (x, y) in enumerate(pairs)]
+ if close:
+ commands.append("Z")
+ return " ".join(commands)
+
+
+def primitive_path(element: ET.Element) -> str | None:
+ tag = element.tag.rsplit("}", 1)[-1]
+ attributes = element.attrib
+ if tag == "path":
+ return attributes["d"]
+ if tag == "line":
+ return f"M {attributes['x1']} {attributes['y1']} L {attributes['x2']} {attributes['y2']}"
+ if tag == "polyline":
+ return points_path(attributes["points"], close=False)
+ if tag == "polygon":
+ return points_path(attributes["points"], close=True)
+ if tag in {"circle", "ellipse"}:
+ cx = float(attributes["cx"])
+ cy = float(attributes["cy"])
+ rx = float(attributes.get("r", attributes.get("rx", "0")))
+ ry = float(attributes.get("r", attributes.get("ry", "0")))
+ return (
+ f"M {clean_number(cx + rx)} {clean_number(cy)} "
+ f"A {clean_number(rx)} {clean_number(ry)} 0 1 0 {clean_number(cx - rx)} {clean_number(cy)} "
+ f"A {clean_number(rx)} {clean_number(ry)} 0 1 0 {clean_number(cx + rx)} {clean_number(cy)}"
+ )
+ if tag == "rect":
+ x = float(attributes.get("x", "0"))
+ y = float(attributes.get("y", "0"))
+ width = float(attributes["width"])
+ height = float(attributes["height"])
+ radius = float(attributes.get("rx", "0"))
+ if radius == 0:
+ return f"M {clean_number(x)} {clean_number(y)} h {clean_number(width)} v {clean_number(height)} h -{clean_number(width)} Z"
+ return (
+ f"M {clean_number(x + radius)} {clean_number(y)} "
+ f"H {clean_number(x + width - radius)} A {clean_number(radius)} {clean_number(radius)} 0 0 1 {clean_number(x + width)} {clean_number(y + radius)} "
+ f"V {clean_number(y + height - radius)} A {clean_number(radius)} {clean_number(radius)} 0 0 1 {clean_number(x + width - radius)} {clean_number(y + height)} "
+ f"H {clean_number(x + radius)} A {clean_number(radius)} {clean_number(radius)} 0 0 1 {clean_number(x)} {clean_number(y + height - radius)} "
+ f"V {clean_number(y + radius)} A {clean_number(radius)} {clean_number(radius)} 0 0 1 {clean_number(x + radius)} {clean_number(y)} Z"
+ )
+ return None
+
+
+def import_lucide(icon: IconSource) -> None:
+ url = (
+ "https://raw.githubusercontent.com/lucide-icons/lucide/"
+ f"{LUCIDE_REVISION}/icons/{icon.lucide}.svg"
+ )
+ svg = ET.fromstring(fetch(url))
+ view_box = [float(value) for value in svg.attrib["viewBox"].split()]
+ vector = vector_root(view_box[2], view_box[3])
+ for element in svg.iter():
+ path_data = primitive_path(element)
+ if path_data is None:
+ continue
+ path = ET.SubElement(vector, "path")
+ path.set(android_attr("pathData"), path_data)
+ path.set(android_attr("fillColor"), "#00000000")
+ path.set(android_attr("strokeColor"), "#FF000000")
+ path.set(android_attr("strokeWidth"), svg.attrib.get("stroke-width", "2"))
+ path.set(android_attr("strokeLineCap"), svg.attrib.get("stroke-linecap", "round"))
+ path.set(android_attr("strokeLineJoin"), svg.attrib.get("stroke-linejoin", "round"))
+ write_vector(icon.name, "lucide", vector)
+
+
+def vector_root(width: float, height: float) -> ET.Element:
+ return ET.Element(
+ "vector",
+ {
+ android_attr("width"): "24dp",
+ android_attr("height"): "24dp",
+ android_attr("viewportWidth"): clean_number(width),
+ android_attr("viewportHeight"): clean_number(height),
+ },
+ )
+
+
+def import_licenses() -> None:
+ licenses = {
+ "material-symbols-apache-2.0.txt": (
+ "google/material-design-icons",
+ MATERIAL_REVISION,
+ "LICENSE",
+ ),
+ "fluent-system-icons-mit.txt": (
+ "microsoft/fluentui-system-icons",
+ FLUENT_REVISION,
+ "LICENSE",
+ ),
+ "lucide-isc.txt": ("lucide-icons/lucide", LUCIDE_REVISION, "LICENSE"),
+ }
+ LICENSES.mkdir(parents=True, exist_ok=True)
+ for filename, (repository, revision, source_file) in licenses.items():
+ url = f"https://raw.githubusercontent.com/{repository}/{revision}/{source_file}"
+ LICENSES.joinpath(filename).write_bytes(fetch(url))
+
+
+def main() -> None:
+ DRAWABLES.mkdir(parents=True, exist_ok=True)
+ for icon in ICONS:
+ import_material(icon)
+ import_fluent(icon)
+ import_lucide(icon)
+ import_licenses()
+
+
+if __name__ == "__main__":
+ main()