diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2f6be48b..be712480 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt
index cc1bb5f9..4bd03aff 100644
--- a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt
+++ b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt
@@ -63,7 +63,8 @@ internal fun UnittoApp() {
// Anything below will not be called if theming mode is still loading from DataStore
themingMode = userPrefs.value.themingMode ?: return,
dynamicThemeEnabled = userPrefs.value.enableDynamicTheme,
- amoledThemeEnabled = userPrefs.value.enableAmoledTheme
+ amoledThemeEnabled = userPrefs.value.enableAmoledTheme,
+ customColor = userPrefs.value.customColor
)
val navController = rememberNavController()
val sysUiController = rememberSystemUiController()
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index 6a7128f4..00000000
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml
deleted file mode 100644
index 9b58398e..00000000
--- a/app/src/main/res/drawable/ic_launcher_monochrome.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 7b32835e..00000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 2bde775e..00000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index e808cbff..00000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 6d592b62..00000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 667a2fce..00000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index d55a909d..00000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/core/base/src/main/ic_launcher_3_green-playstore.png b/core/base/src/main/ic_launcher_3_green-playstore.png
new file mode 100644
index 00000000..da2313ed
Binary files /dev/null and b/core/base/src/main/ic_launcher_3_green-playstore.png differ
diff --git a/core/base/src/main/res/drawable/ic_launcher_1_foreground.xml b/core/base/src/main/res/drawable/ic_launcher_1_foreground.xml
new file mode 100644
index 00000000..49768b8d
--- /dev/null
+++ b/core/base/src/main/res/drawable/ic_launcher_1_foreground.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/core/base/src/main/res/drawable/ic_launcher_2_foreground.xml b/core/base/src/main/res/drawable/ic_launcher_2_foreground.xml
new file mode 100644
index 00000000..8e7f2b97
--- /dev/null
+++ b/core/base/src/main/res/drawable/ic_launcher_2_foreground.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/core/base/src/main/res/drawable/ic_launcher_3_foreground.xml b/core/base/src/main/res/drawable/ic_launcher_3_foreground.xml
new file mode 100644
index 00000000..a85ec69f
--- /dev/null
+++ b/core/base/src/main/res/drawable/ic_launcher_3_foreground.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_green.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_green.xml
new file mode 100644
index 00000000..7ab49ade
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_green.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_orange.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_orange.xml
new file mode 100644
index 00000000..9dbdd14d
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_orange.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_red.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_red.xml
new file mode 100644
index 00000000..f368821a
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_1_red.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_green.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_green.xml
new file mode 100644
index 00000000..65cea040
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_green.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_orange.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_orange.xml
new file mode 100644
index 00000000..96547697
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_orange.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_red.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_red.xml
new file mode 100644
index 00000000..6ed087c9
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_2_red.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_green.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_green.xml
new file mode 100644
index 00000000..d56b3738
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_green.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_orange.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_orange.xml
new file mode 100644
index 00000000..b42d62bc
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_orange.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_red.xml b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_red.xml
new file mode 100644
index 00000000..866e20de
--- /dev/null
+++ b/core/base/src/main/res/mipmap-anydpi-v26/ic_launcher_3_red.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_green.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_green.png
new file mode 100644
index 00000000..2a2737fd
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_green.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_orange.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_orange.png
new file mode 100644
index 00000000..0bf2874c
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_orange.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_red.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_red.png
new file mode 100644
index 00000000..4b9697a2
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_1_red.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_green.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_green.png
new file mode 100644
index 00000000..342ebf24
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_green.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_orange.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_orange.png
new file mode 100644
index 00000000..75160b4f
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_orange.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_red.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_red.png
new file mode 100644
index 00000000..1f502550
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_2_red.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_green.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_green.png
new file mode 100644
index 00000000..e54454f7
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_green.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_orange.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_orange.png
new file mode 100644
index 00000000..8c3f5f55
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_orange.png differ
diff --git a/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_red.png b/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_red.png
new file mode 100644
index 00000000..e07823f3
Binary files /dev/null and b/core/base/src/main/res/mipmap-hdpi/ic_launcher_3_red.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_green.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_green.png
new file mode 100644
index 00000000..6d28a7d5
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_green.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_orange.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_orange.png
new file mode 100644
index 00000000..7c6ecae6
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_orange.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_red.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_red.png
new file mode 100644
index 00000000..88bde280
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_1_red.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_green.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_green.png
new file mode 100644
index 00000000..07cfdad1
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_green.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_orange.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_orange.png
new file mode 100644
index 00000000..43de32e0
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_orange.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_red.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_red.png
new file mode 100644
index 00000000..aa27c24a
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_2_red.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_green.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_green.png
new file mode 100644
index 00000000..c564b0ff
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_green.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_orange.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_orange.png
new file mode 100644
index 00000000..46b86e9b
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_orange.png differ
diff --git a/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_red.png b/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_red.png
new file mode 100644
index 00000000..f5ccaeef
Binary files /dev/null and b/core/base/src/main/res/mipmap-mdpi/ic_launcher_3_red.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_green.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_green.png
new file mode 100644
index 00000000..837bd9f2
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_green.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_orange.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_orange.png
new file mode 100644
index 00000000..477861cf
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_red.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_red.png
new file mode 100644
index 00000000..550a7031
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_1_red.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_green.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_green.png
new file mode 100644
index 00000000..c898a7a2
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_green.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_orange.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_orange.png
new file mode 100644
index 00000000..8e5f90e4
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_red.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_red.png
new file mode 100644
index 00000000..292071f4
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_2_red.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_green.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_green.png
new file mode 100644
index 00000000..6965ba9e
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_green.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_orange.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_orange.png
new file mode 100644
index 00000000..b1ebafad
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_red.png b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_red.png
new file mode 100644
index 00000000..b52c03bb
Binary files /dev/null and b/core/base/src/main/res/mipmap-xhdpi/ic_launcher_3_red.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_green.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_green.png
new file mode 100644
index 00000000..1ff614e7
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_green.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_orange.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_orange.png
new file mode 100644
index 00000000..41c4b560
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_red.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_red.png
new file mode 100644
index 00000000..0d7066c6
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_1_red.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_green.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_green.png
new file mode 100644
index 00000000..4ca55d7d
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_green.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_orange.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_orange.png
new file mode 100644
index 00000000..126b3e38
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_red.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_red.png
new file mode 100644
index 00000000..772ee987
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_2_red.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_green.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_green.png
new file mode 100644
index 00000000..8a6381e4
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_green.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_orange.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_orange.png
new file mode 100644
index 00000000..2025c39c
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_red.png b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_red.png
new file mode 100644
index 00000000..c04b30d9
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxhdpi/ic_launcher_3_red.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_green.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_green.png
new file mode 100644
index 00000000..53c54fa7
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_green.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_orange.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_orange.png
new file mode 100644
index 00000000..c4dece23
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_red.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_red.png
new file mode 100644
index 00000000..f79e98a6
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_1_red.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_green.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_green.png
new file mode 100644
index 00000000..cff15555
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_green.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_orange.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_orange.png
new file mode 100644
index 00000000..33001066
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_red.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_red.png
new file mode 100644
index 00000000..75b5149c
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_2_red.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_green.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_green.png
new file mode 100644
index 00000000..0e30e949
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_green.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_orange.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_orange.png
new file mode 100644
index 00000000..f074609a
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_orange.png differ
diff --git a/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_red.png b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_red.png
new file mode 100644
index 00000000..4adf8b57
Binary files /dev/null and b/core/base/src/main/res/mipmap-xxxhdpi/ic_launcher_3_red.png differ
diff --git a/core/base/src/main/res/values/colors.xml b/core/base/src/main/res/values/colors.xml
new file mode 100644
index 00000000..e00bc481
--- /dev/null
+++ b/core/base/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+
+
+
+ #186C31
+ #FF9800
+ #E91E63
+
\ No newline at end of file
diff --git a/core/base/src/main/res/values/strings.xml b/core/base/src/main/res/values/strings.xml
index ab3740f5..4b9d976a 100644
--- a/core/base/src/main/res/values/strings.xml
+++ b/core/base/src/main/res/values/strings.xml
@@ -1064,9 +1064,9 @@
tf
Millinewton
mN
- attonewton
+ Attonewton
aN
- dyne
+ Dyne
dyn
Joule/meter
J/m
@@ -1317,10 +1317,14 @@
Light
Dark
Color theme
+ Pick a theming mode
AMOLED Dark
Use black background for dark themes
Dynamic colors
Use colors from your wallpaper
+ Color scheme
+ Selected color
+ Selected icon
Loading…
@@ -1328,6 +1332,9 @@
Copied %1$s!
Cancel
OK
+ Apply
+ Warning!
+ This feature is unstable. App will be closed to apply changes. Restart your launcher if nothing changes. Reinstall app from phone settings if things go horribly. You have been warned!
Search units
No results found
Open settings
diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/SegmentedButton.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/SegmentedButton.kt
new file mode 100644
index 00000000..097cd368
--- /dev/null
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/SegmentedButton.kt
@@ -0,0 +1,106 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2023 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.core.ui.common
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.OutlinedCard
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.unit.dp
+
+private val GroupRowContainerHeight = 40.dp
+private val GroupRowItemMinWidth = 58.dp
+
+@Composable
+fun SegmentedButtonRow(
+ modifier: Modifier = Modifier,
+ content: @Composable RowScope.() -> Unit
+) {
+ OutlinedCard(
+ modifier = modifier,
+ shape = CircleShape
+ ) {
+ Row(
+ modifier = Modifier
+ .height(GroupRowContainerHeight)
+ .widthIn(min = GroupRowItemMinWidth),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ content()
+ }
+ }
+}
+
+@Composable
+fun RowScope.SegmentedButton(
+ onClick: () -> Unit,
+ selected: Boolean,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ content: @Composable () -> Unit,
+) {
+ val containerColor =
+ if (selected)
+ MaterialTheme.colorScheme.secondaryContainer
+ else
+ MaterialTheme.colorScheme.surface
+ OutlinedButton(
+ modifier = modifier.weight(1f),
+ onClick = onClick,
+ shape = RectangleShape,
+ colors = ButtonDefaults.outlinedButtonColors(
+ containerColor = containerColor,
+ contentColor = contentColorFor(containerColor)
+ ),
+ enabled = enabled,
+ contentPadding = PaddingValues(horizontal = 12.dp)
+ ) {
+ AnimatedVisibility(visible = selected) {
+ Row {
+ Icon(
+ modifier = Modifier.size(18.dp),
+ imageVector = Icons.Default.Check,
+ contentDescription = null
+ )
+ Spacer(Modifier.width(8.dp))
+ }
+ }
+ content()
+ }
+}
diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt
index 08514aa3..6dcf5ae6 100644
--- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoListItem.kt
@@ -33,9 +33,9 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
-import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -52,7 +52,7 @@ import com.sadellie.unitto.core.ui.R
* Represents one item in list on Settings screen.
*
* @param label Main text.
- * @param supportText Text that is located below label.
+ * @param supportContent Text that is located below label.
* @param switchState Current switch state.
* @param onSwitchChange Action to perform when user clicks on this component or just switch. Gives
* you new value.
@@ -61,7 +61,7 @@ import com.sadellie.unitto.core.ui.R
fun UnittoListItem(
label: String,
leadingContent: @Composable (() -> Unit)?,
- supportText: String? = null,
+ supportContent: String? = null,
switchState: Boolean,
onSwitchChange: (Boolean) -> Unit
) {
@@ -72,8 +72,8 @@ fun UnittoListItem(
indication = rememberRipple(),
onClick = { onSwitchChange(!switchState) }
),
- headlineText = { Text(label) },
- supportingText = { supportText?.let { Text(text = it) } },
+ headlineContent = { Text(label) },
+ supportingContent = { supportContent?.let { Text(text = it) } },
leadingContent = leadingContent,
trailingContent = {
Switch(
@@ -111,8 +111,8 @@ fun UnittoListItem(
)
ListItem(
- headlineText = { Text(label) },
- supportingText = { supportText?.let { Text(text = it) } },
+ headlineContent = { Text(label) },
+ supportingContent = { supportText?.let { Text(text = it) } },
leadingContent = leadingContent,
trailingContent = {
ExposedDropdownMenuBox(
@@ -130,10 +130,10 @@ fun UnittoListItem(
singleLine = true,
enabled = false,
textStyle = MaterialTheme.typography.bodyLarge,
- colors = TextFieldDefaults.outlinedTextFieldColors(
- disabledBorderColor = MaterialTheme.colorScheme.outline,
+ colors = OutlinedTextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
- disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant
+ disabledBorderColor = MaterialTheme.colorScheme.outline,
+ disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
trailingIcon = {
Icon(
diff --git a/data/model/src/main/java/com/sadellie/unitto/data/model/AppIcon.kt b/data/model/src/main/java/com/sadellie/unitto/data/model/AppIcon.kt
new file mode 100644
index 00000000..5d3b108c
--- /dev/null
+++ b/data/model/src/main/java/com/sadellie/unitto/data/model/AppIcon.kt
@@ -0,0 +1,47 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2023 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.data.model
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+private const val unitto = 0xFF186c31
+private const val white = 0xFFFFFFFF
+private const val red = 0xFFE91E63
+private const val orange = 0xFFFF9800
+
+enum class LauncherIcon(
+ val component: String,
+ @StringRes val labelString: Int = R.string.app_name,
+ @DrawableRes val foregroundDrawable: Int,
+ val foregroundColor: Long,
+ val backgroundColor: Long,
+) {
+ MAIN_DEFAULT( "com.sadellie.unitto.MainActivity", R.string.app_name, R.drawable.ic_launcher_1_foreground, white, unitto),
+ MAIN_RED( "com.sadellie.unitto.custom.MainActivity2", R.string.app_name, R.drawable.ic_launcher_1_foreground, white, red),
+ MAIN_ORANGE( "com.sadellie.unitto.custom.MainActivity3", R.string.app_name, R.drawable.ic_launcher_1_foreground, white, orange),
+
+ CALC1_DEFAULT( "com.sadellie.unitto.custom.MainActivity4", R.string.calculator, R.drawable.ic_launcher_2_foreground, white, unitto),
+ CALC1_RED( "com.sadellie.unitto.custom.MainActivity5", R.string.calculator, R.drawable.ic_launcher_2_foreground, white, red),
+ CALC1_ORANGE( "com.sadellie.unitto.custom.MainActivity6", R.string.calculator, R.drawable.ic_launcher_2_foreground, white, orange),
+
+ CALC2_DEFAULT( "com.sadellie.unitto.custom.MainActivity7", R.string.calculator, R.drawable.ic_launcher_3_foreground, white, unitto),
+ CALC2_RED( "com.sadellie.unitto.custom.MainActivity8", R.string.calculator, R.drawable.ic_launcher_3_foreground, white, red),
+ CALC2_ORANGE( "com.sadellie.unitto.custom.MainActivity9", R.string.calculator, R.drawable.ic_launcher_3_foreground, white, orange),
+}
diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt
index 5942b3ff..9b15672b 100644
--- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt
+++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt
@@ -18,18 +18,21 @@
package com.sadellie.unitto.data.userprefs
+import androidx.compose.ui.graphics.Color
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.data.model.ALL_UNIT_GROUPS
import com.sadellie.unitto.data.model.AbstractUnit
+import com.sadellie.unitto.data.model.LauncherIcon
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.units.MyUnitIDS
@@ -47,6 +50,7 @@ import javax.inject.Inject
* still loading.
* @property enableDynamicTheme Use dynamic color scheme
* @property enableAmoledTheme Use amoled color scheme
+ * @property customColor Generate custom color scheme from this color.
* @property digitsPrecision Current [PRECISIONS]. Number of digits in fractional part
* @property separator Current [Separator] that used to separate thousands
* @property outputFormat Current [OutputFormat] that is applied to converted value (not input)
@@ -59,11 +63,13 @@ import javax.inject.Inject
* @property unitConverterFavoritesOnly If true will show only units that are marked as favorite.
* @property unitConverterFormatTime If true will format time to be more human readable.
* @property unitConverterSorting Units list sorting mode.
+ * @property launcherIcon Current [LauncherIcon]/
*/
data class UserPreferences(
val themingMode: ThemingMode? = null,
val enableDynamicTheme: Boolean = false,
val enableAmoledTheme: Boolean = false,
+ val customColor: Color = Color.Unspecified,
val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACES,
val outputFormat: Int = OutputFormat.PLAIN,
@@ -77,6 +83,7 @@ data class UserPreferences(
val unitConverterFavoritesOnly: Boolean = false,
val unitConverterFormatTime: Boolean = false,
val unitConverterSorting: UnitsListSorting = UnitsListSorting.USAGE,
+ val launcherIcon: LauncherIcon = LauncherIcon.MAIN_DEFAULT
)
/**
@@ -90,6 +97,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
+ val CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
@@ -103,6 +111,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val UNIT_CONVERTER_FAVORITES_ONLY = booleanPreferencesKey("UNIT_CONVERTER_FAVORITES_ONLY_PREF_KEY")
val UNIT_CONVERTER_FORMAT_TIME = booleanPreferencesKey("UNIT_CONVERTER_FORMAT_TIME_PREF_KEY")
val UNIT_CONVERTER_SORTING = stringPreferencesKey("UNIT_CONVERTER_SORTING_PREF_KEY")
+ val LAUNCHER_ICON = stringPreferencesKey("LAUNCHER_ICON_PREF_KEY")
}
val userPreferencesFlow: Flow = dataStore.data
@@ -117,20 +126,14 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val themingMode: ThemingMode =
preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) }
?: ThemingMode.AUTO
- val enableDynamicTheme: Boolean =
- preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false
- val enableAmoledTheme: Boolean =
- preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
- val digitsPrecision: Int =
- preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
- val separator: Int =
- preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES
- val outputFormat: Int =
- preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
- val latestLeftSideUnit: String =
- preferences[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
- val latestRightSideUnit: String =
- preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
+ val enableDynamicTheme: Boolean = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false
+ val enableAmoledTheme: Boolean = preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
+ val customColor: Color = preferences[PrefsKeys.CUSTOM_COLOR]?.let { Color(it.toULong()) } ?: Color.Unspecified
+ val digitsPrecision: Int = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
+ val separator: Int = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES
+ val outputFormat: Int = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
+ val latestLeftSideUnit: String = preferences[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
+ val latestRightSideUnit: String = preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
val shownUnitGroups: List =
preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.let { list ->
// Everything is in hidden (nothing in shown)
@@ -150,13 +153,14 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val radianMode: Boolean = preferences[PrefsKeys.RADIAN_MODE] ?: true
val unitConverterFavoritesOnly: Boolean = preferences[PrefsKeys.UNIT_CONVERTER_FAVORITES_ONLY] ?: false
val unitConverterFormatTime: Boolean = preferences[PrefsKeys.UNIT_CONVERTER_FORMAT_TIME] ?: false
- val unitConverterSorting: UnitsListSorting = preferences[PrefsKeys.UNIT_CONVERTER_SORTING]?.let { UnitsListSorting.valueOf(it) }
- ?: UnitsListSorting.USAGE
+ val unitConverterSorting: UnitsListSorting = preferences[PrefsKeys.UNIT_CONVERTER_SORTING]?.let { UnitsListSorting.valueOf(it) } ?: UnitsListSorting.USAGE
+ val launcherIcon: LauncherIcon = preferences[PrefsKeys.LAUNCHER_ICON]?.let { LauncherIcon.valueOf(it) } ?: LauncherIcon.MAIN_DEFAULT
UserPreferences(
themingMode = themingMode,
enableDynamicTheme = enableDynamicTheme,
enableAmoledTheme = enableAmoledTheme,
+ customColor = customColor,
digitsPrecision = digitsPrecision,
separator = separator,
outputFormat = outputFormat,
@@ -169,7 +173,8 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
radianMode = radianMode,
unitConverterFavoritesOnly = unitConverterFavoritesOnly,
unitConverterFormatTime = unitConverterFormatTime,
- unitConverterSorting = unitConverterSorting
+ unitConverterSorting = unitConverterSorting,
+ launcherIcon = launcherIcon
)
}
@@ -253,6 +258,17 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
}
}
+ /**
+ * Update preference on custom color scheme.
+ *
+ * @param color New custom color value.
+ */
+ suspend fun updateCustomColor(color: Color) {
+ dataStore.edit { preferences ->
+ preferences[PrefsKeys.CUSTOM_COLOR] = color.value.toLong()
+ }
+ }
+
/**
* Update preference on starting screen route.
*
@@ -335,4 +351,15 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
preferences[PrefsKeys.UNIT_CONVERTER_SORTING] = sorting.name
}
}
+
+ /**
+ * Update [UserPreferences.launcherIcon].
+ *
+ * @see UserPreferences.launcherIcon
+ */
+ suspend fun updateLauncherIcon(icon: LauncherIcon) {
+ dataStore.edit { preferences ->
+ preferences[PrefsKeys.LAUNCHER_ICON] = icon.name
+ }
+ }
}
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt
index 95d49580..7e9fd9e3 100644
--- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/AboutScreen.kt
@@ -75,7 +75,7 @@ internal fun AboutScreen(
stringResource(R.string.currency_rates_note_setting)
)
},
- headlineText = { Text(stringResource(R.string.currency_rates_note_setting)) },
+ headlineContent = { Text(stringResource(R.string.currency_rates_note_setting)) },
modifier = Modifier.clickable { showDialog = true }
)
}
@@ -89,7 +89,7 @@ internal fun AboutScreen(
stringResource(R.string.terms_and_conditions)
)
},
- headlineText = { Text(stringResource(R.string.terms_and_conditions)) },
+ headlineContent = { Text(stringResource(R.string.terms_and_conditions)) },
modifier = Modifier.clickable {
openLink(
mContext,
@@ -108,7 +108,7 @@ internal fun AboutScreen(
stringResource(R.string.privacy_policy)
)
},
- headlineText = { Text(stringResource(R.string.privacy_policy)) },
+ headlineContent = { Text(stringResource(R.string.privacy_policy)) },
modifier = Modifier.clickable {
openLink(
mContext,
@@ -127,7 +127,7 @@ internal fun AboutScreen(
stringResource(R.string.open_source)
)
},
- headlineText = { Text(stringResource(R.string.open_source)) },
+ headlineContent = { Text(stringResource(R.string.open_source)) },
modifier = Modifier.clickable {
openLink(
mContext,
@@ -146,8 +146,8 @@ internal fun AboutScreen(
stringResource(R.string.translate_app)
)
},
- headlineText = { Text(stringResource(R.string.translate_app)) },
- supportingText = { Text(stringResource(R.string.translate_app_support)) },
+ headlineContent = { Text(stringResource(R.string.translate_app)) },
+ supportingContent = { Text(stringResource(R.string.translate_app_support)) },
modifier = Modifier.clickable {
openLink(
mContext,
@@ -166,7 +166,7 @@ internal fun AboutScreen(
stringResource(R.string.third_party_licenses)
)
},
- headlineText = { Text(stringResource(R.string.third_party_licenses)) },
+ headlineContent = { Text(stringResource(R.string.third_party_licenses)) },
modifier = Modifier.clickable { navigateToThirdParty() }
)
}
@@ -180,8 +180,8 @@ internal fun AboutScreen(
stringResource(R.string.app_version_name_setting)
)
},
- headlineText = { Text(stringResource(R.string.app_version_name_setting)) },
- supportingText = { Text("${BuildConfig.APP_NAME} (${BuildConfig.APP_CODE})") },
+ headlineContent = { Text(stringResource(R.string.app_version_name_setting)) },
+ supportingContent = { Text("${BuildConfig.APP_NAME} (${BuildConfig.APP_CODE})") },
modifier = Modifier.combinedClickable {
if (userPrefs.value.enableToolsExperiment) {
Toast.makeText(mContext, "Tools already enabled!", Toast.LENGTH_LONG).show()
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt
index 4b287ed9..ece6aa4b 100644
--- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsScreen.kt
@@ -88,8 +88,8 @@ internal fun SettingsScreen(
stringResource(R.string.theme_setting),
)
},
- headlineText = { Text(stringResource(R.string.theme_setting)) },
- supportingText = { Text(stringResource(R.string.theme_setting_support)) },
+ headlineContent = { Text(stringResource(R.string.theme_setting)) },
+ supportingContent = { Text(stringResource(R.string.theme_setting_support)) },
modifier = Modifier.clickable { navControllerAction(themesRoute) }
)
}
@@ -103,8 +103,8 @@ internal fun SettingsScreen(
stringResource(R.string.starting_screen_setting),
)
},
- headlineText = { Text(stringResource(R.string.starting_screen_setting)) },
- supportingText = { Text(stringResource(R.string.starting_screen_setting_support)) },
+ headlineContent = { Text(stringResource(R.string.starting_screen_setting)) },
+ supportingContent = { Text(stringResource(R.string.starting_screen_setting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.START_SCREEN }
)
}
@@ -121,8 +121,8 @@ internal fun SettingsScreen(
stringResource(R.string.precision_setting),
)
},
- headlineText = { Text(stringResource(R.string.precision_setting)) },
- supportingText = { Text(stringResource(R.string.precision_setting_support)) },
+ headlineContent = { Text(stringResource(R.string.precision_setting)) },
+ supportingContent = { Text(stringResource(R.string.precision_setting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.PRECISION }
)
}
@@ -130,8 +130,8 @@ internal fun SettingsScreen(
// SEPARATOR
item {
ListItem(
- headlineText = { Text(stringResource(R.string.separator_setting)) },
- supportingText = { Text(stringResource(R.string.separator_setting_support)) },
+ headlineContent = { Text(stringResource(R.string.separator_setting)) },
+ supportingContent = { Text(stringResource(R.string.separator_setting_support)) },
modifier = Modifier
.clickable { dialogState = DialogState.SEPARATOR }
.padding(start = 40.dp)
@@ -141,8 +141,8 @@ internal fun SettingsScreen(
// OUTPUT FORMAT
item {
ListItem(
- headlineText = { Text(stringResource(R.string.output_format_setting)) },
- supportingText = { Text(stringResource(R.string.output_format_setting_support)) },
+ headlineContent = { Text(stringResource(R.string.output_format_setting)) },
+ supportingContent = { Text(stringResource(R.string.output_format_setting_support)) },
modifier = Modifier
.clickable { dialogState = DialogState.OUTPUT_FORMAT }
.padding(start = 40.dp)
@@ -161,8 +161,8 @@ internal fun SettingsScreen(
stringResource(R.string.disable_unit_group_description),
)
},
- headlineText = { Text(stringResource(R.string.unit_groups_setting)) },
- supportingText = { Text(stringResource(R.string.unit_groups_support)) },
+ headlineContent = { Text(stringResource(R.string.unit_groups_setting)) },
+ supportingContent = { Text(stringResource(R.string.unit_groups_support)) },
modifier = Modifier.clickable { navControllerAction(unitsGroupRoute) }
)
}
@@ -176,8 +176,8 @@ internal fun SettingsScreen(
stringResource(R.string.units_sorting)
)
},
- headlineText = { Text(stringResource(R.string.units_sorting)) },
- supportingText = { Text(stringResource(R.string.units_sorting_support)) },
+ headlineContent = { Text(stringResource(R.string.units_sorting)) },
+ supportingContent = { Text(stringResource(R.string.units_sorting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.UNIT_LIST_SORTING }
)
}
@@ -192,7 +192,7 @@ internal fun SettingsScreen(
stringResource(R.string.format_time)
)
},
- supportText = stringResource(R.string.format_time_support),
+ supportContent = stringResource(R.string.format_time_support),
switchState = userPrefs.value.unitConverterFormatTime,
onSwitchChange = viewModel::updateUnitConverterFormatTime
)
@@ -211,7 +211,7 @@ internal fun SettingsScreen(
stringResource(R.string.enable_vibrations)
)
},
- supportText = stringResource(R.string.enable_vibrations_support),
+ supportContent = stringResource(R.string.enable_vibrations_support),
switchState = userPrefs.value.enableVibrations,
onSwitchChange = viewModel::updateVibrations
)
@@ -227,7 +227,7 @@ internal fun SettingsScreen(
stringResource(R.string.rate_this_app),
)
},
- headlineText = { Text(stringResource(R.string.rate_this_app)) },
+ headlineContent = { Text(stringResource(R.string.rate_this_app)) },
modifier = Modifier.clickable { openLink(mContext, BuildConfig.STORE_LINK) }
)
}
@@ -242,8 +242,8 @@ internal fun SettingsScreen(
stringResource(R.string.about_unitto),
)
},
- headlineText = { Text(stringResource(R.string.about_unitto)) },
- supportingText = { Text(stringResource(R.string.about_unitto_support)) },
+ headlineContent = { Text(stringResource(R.string.about_unitto)) },
+ supportingContent = { Text(stringResource(R.string.about_unitto_support)) },
modifier = Modifier.clickable { navControllerAction(aboutRoute) }
)
}
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt
index 6f4ebfc4..c38fc7fa 100644
--- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/SettingsViewModel.kt
@@ -18,12 +18,15 @@
package com.sadellie.unitto.feature.settings
+import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.ui.Formatter
+import com.sadellie.unitto.data.model.LauncherIcon
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.unitgroups.UnitGroupsRepository
+import com.sadellie.unitto.data.userprefs.UserPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.themmo.ThemingMode
@@ -40,10 +43,10 @@ class SettingsViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
private val unitGroupsRepository: UnitGroupsRepository,
) : ViewModel() {
- var userPrefs = userPrefsRepository.userPreferencesFlow
+ val userPrefs = userPrefsRepository.userPreferencesFlow
.onEach { Formatter.setSeparator(it.separator) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000),
- com.sadellie.unitto.data.userprefs.UserPreferences()
+ UserPreferences()
)
val shownUnitGroups = unitGroupsRepository.shownUnitGroups
val hiddenUnitGroups = unitGroupsRepository.hiddenUnitGroups
@@ -75,6 +78,15 @@ class SettingsViewModel @Inject constructor(
}
}
+ /**
+ * @see UserPreferencesRepository.updateCustomColor
+ */
+ fun updateCustomColor(color: Color) {
+ viewModelScope.launch {
+ userPrefsRepository.updateCustomColor(color)
+ }
+ }
+
/**
* @see UserPreferencesRepository.updateDigitsPrecision
*/
@@ -187,6 +199,16 @@ class SettingsViewModel @Inject constructor(
}
}
+ /**
+ * @see UserPreferencesRepository.updateLauncherIcon
+ */
+ fun updateLauncherIcon(icon: LauncherIcon, finished: () -> Unit?) {
+ viewModelScope.launch {
+ userPrefsRepository.updateLauncherIcon(icon)
+ finished()
+ }
+ }
+
/**
* Prevent from dragging over non-draggable items (headers and hidden)
*
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt
index d233f7b1..6e693873 100644
--- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/ThemesScreen.kt
@@ -18,83 +18,228 @@
package com.sadellie.unitto.feature.settings
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
import android.os.Build
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Colorize
import androidx.compose.material.icons.filled.DarkMode
+import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Palette
+import androidx.compose.material.icons.filled.Warning
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.ui.R
+import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton
+import com.sadellie.unitto.core.ui.common.SegmentedButton
+import com.sadellie.unitto.core.ui.common.SegmentedButtonRow
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
+import com.sadellie.unitto.data.model.LauncherIcon
+import com.sadellie.unitto.feature.settings.components.ColorSelector
+import com.sadellie.unitto.feature.settings.components.IconsSelector
import io.github.sadellie.themmo.ThemingMode
import io.github.sadellie.themmo.ThemmoController
+private val colorSchemes: List by lazy {
+ listOf(
+ Color(0xFFE91E63),
+ Color(0xFFFF9800),
+ Color(0xFF4CAF50),
+ Color(0xFF2196F3),
+ Color(0xFF9C27B0),
+ Color(0xFF5C76AA),
+ Color(0xFF756FAA),
+ Color(0xFF9E6C2A),
+ )
+}
+
@Composable
-internal fun ThemesScreen(
+internal fun ThemesRoute(
navigateUpAction: () -> Unit = {},
themmoController: ThemmoController,
viewModel: SettingsViewModel
) {
+ val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
+
+ ThemesScreen(
+ navigateUpAction = navigateUpAction,
+ currentThemingMode = themmoController.currentThemingMode,
+ onThemeChange = {
+ themmoController.setThemingMode(it)
+ viewModel.updateThemingMode(it)
+ },
+ isDynamicThemeEnabled = themmoController.isDynamicThemeEnabled,
+ onDynamicThemeChange = {
+ themmoController.enableDynamicTheme(it)
+ viewModel.updateDynamicTheme(it)
+ },
+ isAmoledThemeEnabled = themmoController.isAmoledThemeEnabled,
+ onAmoledThemeChange = {
+ themmoController.enableAmoledTheme(it)
+ viewModel.updateAmoledTheme(it)
+ },
+ selectedColor = themmoController.currentCustomColor,
+ onColorChange = {
+ themmoController.setCustomColor(it)
+ viewModel.updateCustomColor(it)
+ },
+ currentIcon = userPrefs.value.launcherIcon,
+ onIconChange = { newValue, callback ->
+ viewModel.updateLauncherIcon(newValue, callback)
+ },
+ )
+}
+
+@Composable
+private fun ThemesScreen(
+ navigateUpAction: () -> Unit,
+ currentThemingMode: ThemingMode,
+ onThemeChange: (ThemingMode) -> Unit,
+ isDynamicThemeEnabled: Boolean,
+ onDynamicThemeChange: (Boolean) -> Unit,
+ isAmoledThemeEnabled: Boolean,
+ onAmoledThemeChange: (Boolean) -> Unit,
+ selectedColor: Color,
+ onColorChange: (Color) -> Unit,
+ currentIcon: LauncherIcon,
+ onIconChange: (LauncherIcon, () -> Unit) -> Unit
+) {
+ val mContext = LocalContext.current
+ val themingModes by remember {
+ mutableStateOf(
+ mapOf(
+ ThemingMode.AUTO to R.string.force_auto_mode,
+ ThemingMode.FORCE_LIGHT to R.string.force_light_mode,
+ ThemingMode.FORCE_DARK to R.string.force_dark_mode
+ )
+ )
+ }
+ var showIconChangeWarning: Boolean by rememberSaveable { mutableStateOf(false) }
+
+ var selectedLauncherIcon: LauncherIcon by remember { mutableStateOf(currentIcon) }
+ var selectedLauncherIconColor: Color by remember { mutableStateOf(Color(currentIcon.backgroundColor)) }
+ val selectedLauncherIconColorAnim = animateColorAsState(targetValue = selectedLauncherIconColor)
+ val launcherIcons: List by remember(selectedLauncherIcon) {
+ derivedStateOf {
+ LauncherIcon
+ .values()
+ .toList()
+ .filter { Color(it.backgroundColor) == selectedLauncherIconColor }
+ }
+ }
+
UnittoScreenWithLargeTopBar(
title = stringResource(R.string.theme_setting),
navigationIcon = { NavigateUpButton(navigateUpAction) }
) { paddingValues ->
LazyColumn(contentPadding = paddingValues) {
+
item {
- UnittoListItem(
+ ListItem(
+ headlineContent = { Text(stringResource(R.string.selected_color)) },
+ supportingContent = {
+ ColorSelector(
+ modifier = Modifier.padding(top = 12.dp),
+ selected = selectedLauncherIconColor,
+ onItemClick = {
+ selectedLauncherIconColor = it
+ },
+ colorSchemes = remember {
+ LauncherIcon.values().map { Color(it.backgroundColor) }
+ .distinct()
+ },
+ )
+ },
+ modifier = Modifier.padding(start = 40.dp)
+ )
+ }
+
+ item {
+ ListItem(
+ headlineContent = { Text(stringResource(R.string.selected_launcher_icon)) },
+ supportingContent = {
+ IconsSelector(
+ modifier = Modifier.padding(top = 12.dp),
+ selectedColor = selectedLauncherIconColorAnim.value,
+ icons = launcherIcons,
+ selectedIcon = selectedLauncherIcon,
+ onIconChange = { selectedLauncherIcon = it }
+ )
+ },
+ modifier = Modifier.padding(start = 40.dp)
+ )
+ }
+
+ item {
+ FilledTonalButton(
+ modifier = Modifier.padding(start = 56.dp),
+ enabled = currentIcon != selectedLauncherIcon,
+ onClick = { showIconChangeWarning = true },
+ ) {
+ Text(stringResource(R.string.apply_label))
+ }
+ }
+
+ item {
+ ListItem(
leadingContent = {
Icon(
Icons.Default.Palette,
stringResource(R.string.color_theme),
)
},
- label = stringResource(R.string.color_theme),
- allOptions = mapOf(
- ThemingMode.AUTO to stringResource(R.string.force_auto_mode),
- ThemingMode.FORCE_LIGHT to stringResource(R.string.force_light_mode),
- ThemingMode.FORCE_DARK to stringResource(R.string.force_dark_mode)
- ),
- selected = themmoController.currentThemingMode,
- onSelectedChange = {
- themmoController.setThemingMode(it)
- viewModel.updateThemingMode(it)
- }
+ headlineContent = { Text(stringResource(R.string.color_theme)) },
+ supportingContent = { Text(stringResource(R.string.color_theme_support)) },
)
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
- item {
- UnittoListItem(
- leadingContent = {
- Icon(
- Icons.Default.Colorize,
- stringResource(R.string.enable_dynamic_colors),
- )
- },
- label = stringResource(R.string.enable_dynamic_colors),
- supportText = stringResource(R.string.enable_dynamic_colors_support),
- switchState = themmoController.isDynamicThemeEnabled,
- onSwitchChange = {
- themmoController.enableDynamicTheme(it)
- viewModel.updateDynamicTheme(it)
- }
- )
+ item {
+ SegmentedButtonRow(
+ modifier = Modifier.padding(56.dp, 8.dp, 24.dp, 2.dp)
+ ) {
+ themingModes.forEach { (mode, stringRes) ->
+ SegmentedButton(
+ onClick = { onThemeChange(mode) },
+ selected = currentThemingMode == mode,
+ content = { Text(stringResource(stringRes)) }
+ )
+ }
}
}
item {
AnimatedVisibility(
- visible = (themmoController.currentThemingMode != ThemingMode.FORCE_LIGHT),
+ visible = currentThemingMode != ThemingMode.FORCE_LIGHT,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(),
) {
@@ -106,15 +251,128 @@ internal fun ThemesScreen(
)
},
label = stringResource(R.string.force_amoled_mode),
- supportText = stringResource(R.string.force_amoled_mode_support),
- switchState = themmoController.isAmoledThemeEnabled,
- onSwitchChange = {
- themmoController.enableAmoledTheme(it)
- viewModel.updateAmoledTheme(it)
- }
+ supportContent = stringResource(R.string.force_amoled_mode_support),
+ switchState = isAmoledThemeEnabled,
+ onSwitchChange = onAmoledThemeChange
+ )
+ }
+ }
+
+ item { Header(stringResource(R.string.color_scheme)) }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ item {
+ UnittoListItem(
+ leadingContent = {
+ Icon(
+ Icons.Default.Colorize,
+ stringResource(R.string.enable_dynamic_colors),
+ )
+ },
+ label = stringResource(R.string.enable_dynamic_colors),
+ supportContent = stringResource(R.string.enable_dynamic_colors_support),
+ switchState = isDynamicThemeEnabled,
+ onSwitchChange = onDynamicThemeChange
+ )
+ }
+ }
+
+ item {
+ AnimatedVisibility(
+ visible = !isDynamicThemeEnabled,
+ enter = expandVertically() + fadeIn(),
+ exit = shrinkVertically() + fadeOut(),
+ ) {
+ ListItem(
+ headlineContent = { Text(stringResource(R.string.selected_color)) },
+ supportingContent = {
+ ColorSelector(
+ modifier = Modifier.padding(top = 12.dp),
+ selected = selectedColor,
+ onItemClick = onColorChange,
+ colorSchemes = colorSchemes,
+ defaultColor = Color(0xFF186c31)
+ )
+ },
+ modifier = Modifier.padding(start = 40.dp)
)
}
}
}
}
+
+ if (showIconChangeWarning) {
+ AlertDialog(
+ icon = {
+ Icon(Icons.Default.Warning, stringResource(R.string.warning_label))
+ },
+ title = {
+ Text(stringResource(R.string.warning_label))
+ },
+ text = {
+ Text(
+ stringResource(R.string.icon_change_warning)
+ )
+ },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ onIconChange(selectedLauncherIcon) {
+ mContext.changeIcon(selectedLauncherIcon)
+ (mContext as Activity).finish()
+ }
+ showIconChangeWarning = false
+ }
+ ) {
+ Text(stringResource(R.string.apply_label))
+ }
+ },
+ dismissButton = {
+ TextButton(
+ onClick = { showIconChangeWarning = false }
+ ) {
+ Text(stringResource(R.string.cancel_label))
+ }
+ },
+ onDismissRequest = { showIconChangeWarning = false }
+ )
+ }
+}
+
+private fun Context.changeIcon(newIcon: LauncherIcon) {
+ // Enable new icon
+ packageManager.setComponentEnabledSetting(
+ ComponentName(this, newIcon.component),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ )
+
+ val packages = LauncherIcon.values().toList() - newIcon
+
+ // We make sure that other icons are disabled to avoid bugs.
+ packages.forEach {
+ packageManager.setComponentEnabledSetting(
+ ComponentName(this, it.component),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ ThemesScreen(
+ navigateUpAction = {},
+ currentThemingMode = ThemingMode.AUTO,
+ onThemeChange = {},
+ isDynamicThemeEnabled = false,
+ onDynamicThemeChange = {},
+ isAmoledThemeEnabled = false,
+ onAmoledThemeChange = {},
+ selectedColor = Color.Unspecified,
+ onColorChange = {},
+ currentIcon = LauncherIcon.MAIN_DEFAULT,
+ onIconChange = {_,_->}
+ )
}
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt
index f2834887..ed93cd2e 100644
--- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/UnitGroupsScreen.kt
@@ -100,7 +100,7 @@ internal fun UnitGroupsScreen(
}
ListItem(
- headlineText = { Text(stringResource(item.res)) },
+ headlineContent = { Text(stringResource(item.res)) },
modifier = Modifier
.padding(horizontal = itemPadding)
.clip(CircleShape)
@@ -153,7 +153,7 @@ internal fun UnitGroupsScreen(
.background(MaterialTheme.colorScheme.surface)
.clickable { viewModel.returnUnitGroup(it) }
.animateItemPlacement(),
- headlineText = { Text(stringResource(it.res)) },
+ headlineContent = { Text(stringResource(it.res)) },
trailingContent = {
Icon(
Icons.Default.AddCircleOutline,
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/components/ColorCheckbox.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/components/ColorCheckbox.kt
new file mode 100644
index 00000000..4a38d6d3
--- /dev/null
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/components/ColorCheckbox.kt
@@ -0,0 +1,122 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2023 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.feature.settings.components
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.surfaceColorAtElevation
+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.Color
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.unit.dp
+
+@Composable
+internal fun ColorSelector(
+ modifier: Modifier = Modifier,
+ selected: Color,
+ onItemClick: (Color) -> Unit,
+ colorSchemes: List,
+ defaultColor: Color? = null
+) {
+ LazyRow(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ if (defaultColor != null) {
+ // Default, Unitto colors
+ item {
+ ColorCheckbox(
+ color = defaultColor,
+ selected = Color.Unspecified == selected,
+ onClick = { onItemClick(Color.Unspecified) }
+ )
+ }
+ }
+
+ colorSchemes.forEach {
+ item {
+ ColorCheckbox(
+ color = it,
+ selected = it == selected,
+ onClick = { onItemClick(it) }
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ColorCheckbox(
+ color: Color,
+ selected: Boolean,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .width(72.dp)
+ .clip(RoundedCornerShape(25))
+ .clickable(onClick = onClick)
+ .background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .aspectRatio(1f)
+ .padding(8.dp)
+ .clip(CircleShape)
+ .background(color)
+ .border(1.dp, Color.Black.copy(0.5f), CircleShape),
+ )
+ AnimatedVisibility(
+ visible = selected,
+ enter = fadeIn(tween(250)) + scaleIn(tween(150)),
+ exit = fadeOut(tween(250)) + scaleOut(tween(150)),
+ ) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = null,
+ tint = if (color.luminance() > 0.5) Color.Black else Color.White,
+ )
+ }
+ }
+}
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/components/IconSelector.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/components/IconSelector.kt
new file mode 100644
index 00000000..24744a47
--- /dev/null
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/components/IconSelector.kt
@@ -0,0 +1,144 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2023 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.feature.settings.components
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.surfaceColorAtElevation
+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.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.sadellie.unitto.data.model.LauncherIcon
+
+
+@Composable
+internal fun IconsSelector(
+ modifier: Modifier,
+ selectedColor: Color,
+ icons: List,
+ selectedIcon: LauncherIcon,
+ onIconChange: (LauncherIcon) -> Unit
+) {
+ LazyRow(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ items(icons) {
+ IconCheckbox(
+ iconRes = it.foregroundDrawable,
+ foregroundColor = Color(it.foregroundColor),
+ backGroundColor = selectedColor,
+ selected = it == selectedIcon,
+ onClick = { onIconChange(it) },
+ label = it.labelString
+ )
+ }
+ }
+}
+
+@Composable
+private fun IconCheckbox(
+ @DrawableRes iconRes: Int,
+ foregroundColor: Color,
+ backGroundColor: Color,
+ @StringRes label: Int,
+ selected: Boolean,
+ onClick: () -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .width(72.dp)
+ .clip(RoundedCornerShape(25))
+ .clickable(onClick = onClick)
+ .background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
+ .padding(bottom = 8.dp)
+ ,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .aspectRatio(1f)
+ .padding(8.dp)
+ .clip(CircleShape)
+ .background(backGroundColor)
+ .border(1.dp, Color.Black.copy(0.5f), CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(iconRes),
+ contentDescription = null,
+ tint = foregroundColor,
+ modifier = Modifier.scale(1.5f)
+ )
+
+ // bug in language, need to call it like this
+ androidx.compose.animation.AnimatedVisibility(
+ visible = selected,
+ enter = fadeIn(tween(250)) + scaleIn(tween(150)),
+ exit = fadeOut(tween(250)) + scaleOut(tween(150)),
+ ) {
+ Box(
+ modifier = Modifier
+ .background(MaterialTheme.colorScheme.scrim.copy(0.5f))
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = null,
+ tint = if (backGroundColor.luminance() > 0.5) Color.Black else Color.White,
+ )
+ }
+ }
+ }
+
+ Text(stringResource(label), style = MaterialTheme.typography.labelSmall)
+ }
+}
diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt
index 9949afb9..cc2a874e 100644
--- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt
+++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/navigation/SettingsNavigation.kt
@@ -28,7 +28,7 @@ import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.feature.settings.AboutScreen
import com.sadellie.unitto.feature.settings.SettingsScreen
import com.sadellie.unitto.feature.settings.SettingsViewModel
-import com.sadellie.unitto.feature.settings.ThemesScreen
+import com.sadellie.unitto.feature.settings.ThemesRoute
import com.sadellie.unitto.feature.settings.ThirdPartyLicensesScreen
import com.sadellie.unitto.feature.settings.UnitGroupsScreen
import io.github.sadellie.themmo.ThemmoController
@@ -63,7 +63,7 @@ fun NavGraphBuilder.settingGraph(
}
composable(themesRoute) {
- ThemesScreen(
+ ThemesRoute(
navigateUpAction = { navController.navigateUp() },
themmoController = themmoController,
viewModel = settingsViewModel
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3bf68e0a..0fe6b065 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,31 +2,31 @@
appCode = "19"
appName = "Jazzberry jam"
kotlin = "1.8.10"
-androidxCore = "1.9.0"
-androidGradlePlugin = "7.4.1"
+androidxCore = "1.10.0"
+androidGradlePlugin = "7.4.2"
orgJetbrainsKotlinxCoroutinesTest = "1.6.4"
-androidxCompose = "1.4.0-beta01"
-androidxComposeCompiler = "1.4.2"
-androidxComposeUi = "1.4.0-beta01"
-androidxComposeMaterial3 = "1.1.0-alpha06"
+androidxCompose = "1.5.0-alpha02"
+androidxComposeCompiler = "1.4.4"
+androidxComposeUi = "1.5.0-alpha02"
+androidxComposeMaterial3 = "1.1.0-beta02"
androidxNavigation = "2.5.3"
-androidxLifecycleRuntimeCompose = "2.6.0-beta01"
+androidxLifecycleRuntimeCompose = "2.6.1"
androidxHilt = "1.0.0"
-comGoogleDagger = "2.44.2"
-androidxComposeMaterialIconsExtended = "1.4.0-alpha04"
+comGoogleDagger = "2.45"
+androidxComposeMaterialIconsExtended = "1.5.0-alpha02"
androidxDatastore = "1.0.0"
-comGoogleAccompanist = "0.27.1"
-androidxRoom = "2.4.3"
+comGoogleAccompanist = "0.30.1"
+androidxRoom = "2.5.1"
comSquareupMoshi = "1.14.0"
comSquareupRetrofit2 = "2.9.0"
-comGithubSadellieThemmo = "cf6be7e592"
+comGithubSadellieThemmo = "3e34a0dcfe"
orgBurnoutcrewComposereorderable = "0.9.6"
comGithubSadellieExprk = "e55cba8f41"
mxParser = "5.2.1"
junit = "4.13.2"
androidxTest = "1.5.0"
androidxTestExt = "1.1.4"
-androidDesugarJdkLibs = "2.0.2"
+androidDesugarJdkLibs = "2.0.3"
androidxTestRunner = "1.5.1"
androidxTestRules = "1.5.0"
orgRobolectric = "4.9"