Compare commits
147 Commits
v0.4.4
...
v0.5.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65595076ee | ||
|
|
bf1563ae78 | ||
|
|
d111771e06 | ||
|
|
00ce01e102 | ||
|
|
15b7022204 | ||
|
|
759b7f0033 | ||
|
|
73c3777529 | ||
|
|
a365e76383 | ||
|
|
7c4747d451 | ||
|
|
12be43e100 | ||
|
|
207cf244e6 | ||
|
|
6f45cf8456 | ||
|
|
5b73de5700 | ||
|
|
25c94e8270 | ||
|
|
0cd703427a | ||
|
|
3871978abc | ||
|
|
a9502be471 | ||
|
|
a5f2844488 | ||
|
|
dd67497154 | ||
|
|
01c7512c01 | ||
|
|
fe37ea914c | ||
|
|
fa768c0e2a | ||
|
|
bcd3d1719b | ||
|
|
55f881d8a0 | ||
|
|
cd817dec93 | ||
|
|
cdf8bc2825 | ||
|
|
62d07b0142 | ||
|
|
f50cd2de58 | ||
|
|
396a47cde9 | ||
|
|
51e67c7293 | ||
|
|
0bc64c6253 | ||
|
|
f571013fe5 | ||
|
|
5f7ca53458 | ||
|
|
0cacb9a671 | ||
|
|
30d36b7541 | ||
|
|
3ece15f516 | ||
|
|
603fab1831 | ||
|
|
90838012ad | ||
|
|
e366c94d55 | ||
|
|
a0ffa3feb1 | ||
|
|
ecbf5c4e66 | ||
|
|
023645646c | ||
|
|
553251d3c1 | ||
|
|
92ce04d5d9 | ||
|
|
9930eb1bca | ||
|
|
47b2d81f72 | ||
|
|
e6d6260d72 | ||
|
|
dd02948a00 | ||
|
|
4a12b3ded0 | ||
|
|
6acfa7f349 | ||
|
|
d3e2dda165 | ||
|
|
a5ec6a10b3 | ||
|
|
0a8260b7fa | ||
|
|
4cddc9b305 | ||
|
|
b4835b869a | ||
|
|
11c6dc8a0d | ||
|
|
71a2baf402 | ||
|
|
94bfd39a9b | ||
|
|
d473343ca3 | ||
|
|
5101db7169 | ||
|
|
08e10639d1 | ||
|
|
741dd82003 | ||
|
|
4c2bfc5dab | ||
|
|
ce3cb4427a | ||
|
|
463e3a3588 | ||
|
|
da611d3077 | ||
|
|
d0afe023a6 | ||
|
|
00f01b59db | ||
|
|
7a1cc211f3 | ||
|
|
1e29204d9c | ||
|
|
d541eed655 | ||
|
|
aaf7e62ed9 | ||
|
|
29cbc4f373 | ||
|
|
791f3d2a69 | ||
|
|
6595aa4d1a | ||
|
|
815ea0845a | ||
|
|
37c5256769 | ||
|
|
b85703db36 | ||
|
|
bc1bc8fece | ||
|
|
b3c6589326 | ||
|
|
8f44d04a6f | ||
|
|
349758991a | ||
|
|
fed979dec9 | ||
|
|
d6f8555bdf | ||
|
|
5afd6bd722 | ||
|
|
0716420697 | ||
|
|
b923a5bf65 | ||
|
|
3c2b2ae473 | ||
|
|
1e1881e6ea | ||
|
|
3acda3745a | ||
|
|
2a8c67c4f4 | ||
|
|
0d572fc1ae | ||
|
|
7810d8034c | ||
|
|
6b9fc555f0 | ||
|
|
f352c768f4 | ||
|
|
24d6667b52 | ||
|
|
d29cfff7d4 | ||
|
|
babf58bf53 | ||
|
|
701088c842 | ||
|
|
4fdbb7c235 | ||
|
|
5831bd84ad | ||
|
|
dd97ff09b6 | ||
|
|
a5407502f3 | ||
|
|
def05ef3a7 | ||
|
|
8ec03c09cc | ||
|
|
a298687f6e | ||
|
|
2488614d22 | ||
|
|
ca8ca65533 | ||
|
|
8e134cc15f | ||
|
|
17860815b6 | ||
|
|
bb502b5f8c | ||
|
|
0e51f25bb0 | ||
|
|
c1e50c9684 | ||
|
|
c377cd9ed7 | ||
|
|
1fc3b536d9 | ||
|
|
ae645fa327 | ||
|
|
bfd20055fc | ||
|
|
15aa6a6bb3 | ||
|
|
e4ef14e684 | ||
|
|
39d7ea4fc0 | ||
|
|
6d330f807a | ||
|
|
e6bc0657f6 | ||
|
|
4686234ba2 | ||
|
|
57e4765ce7 | ||
|
|
aaf26a7a4b | ||
|
|
f8f6756b84 | ||
|
|
35fd70ce6d | ||
|
|
a0ebd62f9a | ||
|
|
3dacd3c2d0 | ||
|
|
c73b8dda0b | ||
|
|
5353a01226 | ||
|
|
34f8fec2f6 | ||
|
|
7199fcdf12 | ||
|
|
8319f563b9 | ||
|
|
45efe52159 | ||
|
|
7a286b932b | ||
|
|
df3f84e96d | ||
|
|
4cc2216940 | ||
|
|
21fdd31c88 | ||
|
|
53c6cb52ca | ||
|
|
9b12213b3e | ||
|
|
659a5062ab | ||
|
|
086e5f7782 | ||
|
|
ae73e64cd2 | ||
|
|
9f5e8cddd5 | ||
|
|
3724495d4f | ||
|
|
15b5dd9e3e |
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
|
||||
import java.io.ByteArrayOutputStream
|
||||
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.application)
|
||||
@@ -23,7 +23,6 @@ plugins {
|
||||
alias(libs.plugins.kotlin.plugin.compose)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.mannodermaus.android.junit5)
|
||||
alias(libs.plugins.mikepenz.aboutlibraries)
|
||||
}
|
||||
|
||||
@@ -52,6 +51,7 @@ android {
|
||||
freeCompilerArgs = listOf(
|
||||
"-opt-in=kotlin.contracts.ExperimentalContracts",
|
||||
"-Xjvm-default=all-compatibility",
|
||||
"-Xwhen-guards",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -202,16 +202,11 @@ dependencies {
|
||||
implementation(libs.patrickgold.jetpref.material.ui)
|
||||
|
||||
implementation(project(":lib:android"))
|
||||
implementation(project(":lib:color"))
|
||||
implementation(project(":lib:kotlin"))
|
||||
implementation(project(":lib:native"))
|
||||
implementation(project(":lib:snygg"))
|
||||
|
||||
testImplementation(libs.equalsverifier)
|
||||
testImplementation(libs.kotest.assertions.core)
|
||||
testImplementation(libs.kotest.extensions.roboelectric)
|
||||
testImplementation(libs.kotest.property)
|
||||
testImplementation(libs.kotest.runner.junit5)
|
||||
|
||||
androidTestImplementation(libs.androidx.test.ext)
|
||||
androidTestImplementation(libs.androidx.test.espresso.core)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
android:name="dev.patrickgold.florisboard.lib.crashutility.CrashDialogActivity"
|
||||
android:icon="@mipmap/floris_app_icon"
|
||||
android:label="@string/crash_dialog__title"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/CrashDialogTheme"/>
|
||||
|
||||
<!-- Copy to Clipboard Activity -->
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
"authors": [ "iamrasel" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "hindi_in",
|
||||
"label": "हिंदी",
|
||||
"authors": [ "npnpatidar" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "bepo",
|
||||
"label": "BÉPO",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
[
|
||||
{ "$": "case_selector", "lower": { "code": 2335, "label": "ट" }, "upper": { "code": 2336, "label": "ठ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2337, "label": "ड" }, "upper": { "code": 2338, "label": "ढ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2375, "label": "े" }, "upper": { "code": 2376, "label": "ै" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2352, "label": "र" }, "upper": { "code": 2371, "label": "ृ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2340, "label": "त" }, "upper": { "code": 2341, "label": "थ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2351, "label": "य" }, "upper": { "code": 2399, "label": "य़" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2369, "label": "ु" }, "upper": { "code": 2370, "label": "ू" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2367, "label": "ि" }, "upper": { "code": 2368, "label": "ी" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2379, "label": "ो" }, "upper": { "code": 2380, "label": "ौ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2346, "label": "प" }, "upper": { "code": 2398, "label": "फ़" } }
|
||||
],
|
||||
[
|
||||
{ "$": "case_selector", "lower": { "code": 2366, "label": "ा" }, "upper": { "code": 2309, "label": "अ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2360, "label": "स" }, "upper": { "code": 2358, "label": "श" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2342, "label": "द" }, "upper": { "code": 2343, "label": "ध" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2347, "label": "फ" }, "upper": { "code": 2364, "label": " ़" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2327, "label": "ग" }, "upper": { "code": 2328, "label": "घ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2361, "label": "ह" }, "upper": { "code": 2307, "label": "ः" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2332, "label": "ज" }, "upper": { "code": 2333, "label": "झ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2325, "label": "क" }, "upper": { "code": 2326, "label": "ख" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2354, "label": "ल" }, "upper": { "code": 2355, "label": "ळ" } }
|
||||
],
|
||||
[
|
||||
{
|
||||
"$": "case_selector",
|
||||
"lower": { "$": "multi_text_key", "codePoints": [2332, 2381, 2334], "label": "ज्ञ" },
|
||||
"upper": { "code": 2395, "label": "ज़" }
|
||||
},
|
||||
{
|
||||
"$": "case_selector",
|
||||
"lower": { "$": "multi_text_key", "codePoints": [2325, 2381, 2359], "label": "क्ष" },
|
||||
"upper": { "code": 2359, "label": "ष" }
|
||||
},
|
||||
{ "$": "case_selector", "lower": { "code": 2330, "label": "च" }, "upper": { "code": 2331, "label": "छ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2357, "label": "व" }, "upper": { "code": 2381, "label": "्" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2348, "label": "ब" }, "upper": { "code": 2349, "label": "भ" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2344, "label": "न" }, "upper": { "code": 2339, "label": "ण" } },
|
||||
{ "$": "case_selector", "lower": { "code": 2350, "label": "म" }, "upper": { "code": 2306, "label": "ं" } }
|
||||
]
|
||||
]
|
||||
@@ -48,6 +48,7 @@
|
||||
{ "id": "fi", "authors": [ "patrickgold" ] },
|
||||
{ "id": "fo", "authors": [ "BinFlush" ] },
|
||||
{ "id": "fr", "authors": [ "patrickgold" ] },
|
||||
{ "id": "hi-IN", "authors": [ "npnpatidar" ] },
|
||||
{ "id": "hr", "authors": [ "hedidnothingwrong" ] },
|
||||
{ "id": "hu", "authors": [ "zoli111, gabik65" ] },
|
||||
{ "id": "hy", "authors": [ "PJTSearch" ] },
|
||||
@@ -695,6 +696,16 @@
|
||||
"numericAdvanced": "org.florisboard.layouts:bengali"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "hi-IN",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
"currencySet": "org.florisboard.currencysets:indian_rupee",
|
||||
"popupMapping": "org.florisboard.localization:hi-IN",
|
||||
"preferred": {
|
||||
"characters": "org.florisboard.layouts:hindi_in",
|
||||
"numericRow": "org.florisboard.layouts:devanagari"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "ast",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"all": {
|
||||
"क": {
|
||||
"main": { "$": "auto_text_key", "code": 2392, "label": "क़" },
|
||||
"relevant": [{ "$": "auto_text_key", "code": 2393, "label": "ख़" }]
|
||||
},
|
||||
"ग": {
|
||||
"main": { "$": "auto_text_key", "code": 2394, "label": "ग़" },
|
||||
"relevant": [{ "$": "auto_text_key", "code": 2427, "label": "ॻ" }]
|
||||
},
|
||||
"च": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2385, "label": " ॑" },
|
||||
{ "$": "auto_text_key", "code": 2386, "label": " ॒" }
|
||||
]
|
||||
},
|
||||
"ज": {
|
||||
"main": { "$": "auto_text_key", "code": 2395, "label": "ज़" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2428, "label": "ॼ" },
|
||||
{ "$": "auto_text_key", "code": 2425, "label": "ॹ" }
|
||||
]
|
||||
},
|
||||
"ड": {
|
||||
"main": { "$": "auto_text_key", "code": 2396, "label": "ड़" },
|
||||
"relevant": [{ "$": "auto_text_key", "code": 2397, "label": "ढ़" }]
|
||||
},
|
||||
"त": {
|
||||
"main": { "$": "multi_text_key", "codePoints": [2340, 2381, 2352], "label": "त्र" }
|
||||
},
|
||||
"द": {
|
||||
"main": { "$": "auto_text_key", "code": 2396, "label": "ड़" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2430, "label": "ॾ" },
|
||||
{ "$": "auto_text_key", "code": 2397, "label": "ढ़" },
|
||||
{ "$": "auto_text_key", "code": 2424, "label": "ॸ" }
|
||||
]
|
||||
},
|
||||
"न": {
|
||||
"main": { "$": "auto_text_key", "code": 2329, "label": "ङ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2345, "label": "ऩ" },
|
||||
{ "$": "auto_text_key", "code": 2334, "label": "ञ" }
|
||||
]
|
||||
},
|
||||
"फ": {
|
||||
"main": { "$": "auto_text_key", "code": 2398, "label": "फ़" }
|
||||
},
|
||||
"ब": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2431, "label": "ॿ" },
|
||||
{ "$": "auto_text_key", "code": 2365, "label": "ऽ" },
|
||||
{ "$": "auto_text_key", "code": 2416, "label": "॰" }
|
||||
]
|
||||
},
|
||||
"म": {
|
||||
"main": { "$": "auto_text_key", "code": 2305, "label": "ँ" },
|
||||
"relevant": [{ "$": "auto_text_key", "code": 2304, "label": "ऀ" }]
|
||||
},
|
||||
"य": {
|
||||
"main": { "$": "auto_text_key", "code": 2426, "label": "ॺ" }
|
||||
},
|
||||
"र": {
|
||||
"main": { "$": "auto_text_key", "code": 2315, "label": "ऋ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2400, "label": "ॠ" },
|
||||
{ "$": "auto_text_key", "code": 2372, "label": "ॄ" },
|
||||
{ "$": "auto_text_key", "code": 2353, "label": "ऱ" }
|
||||
]
|
||||
},
|
||||
"ल": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2402, "label": "ॢ" },
|
||||
{ "$": "auto_text_key", "code": 2403, "label": "ॣ" },
|
||||
{ "$": "auto_text_key", "code": 2316, "label": "ऌ" },
|
||||
{ "$": "auto_text_key", "code": 2401, "label": "ॡ" },
|
||||
{ "$": "auto_text_key", "code": 2356, "label": "ऴ" }
|
||||
]
|
||||
},
|
||||
"व": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2387, "label": " ॓" },
|
||||
{ "$": "auto_text_key", "code": 2388, "label": " ॔" }
|
||||
]
|
||||
},
|
||||
"स": {
|
||||
"main": { "$": "multi_text_key", "codePoints": [2358, 2381, 2352], "label": "श्र" },
|
||||
"relevant": [{ "$": "auto_text_key", "code": 2359, "label": "ष" }]
|
||||
},
|
||||
"ा": {
|
||||
"main": { "$": "auto_text_key", "code": 2310, "label": "आ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2373, "label": "ॅ" },
|
||||
{ "$": "auto_text_key", "code": 2418, "label": "ॲ" },
|
||||
{ "$": "auto_text_key", "code": 2308, "label": "ऄ" }
|
||||
]
|
||||
},
|
||||
"ि": {
|
||||
"main": { "$": "auto_text_key", "code": 2311, "label": "इ" },
|
||||
"relevant": [{ "$": "auto_text_key", "code": 2312, "label": "ई" }]
|
||||
},
|
||||
"ु": {
|
||||
"main": { "$": "auto_text_key", "code": 2313, "label": "उ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2422, "label": "ॶ" },
|
||||
{ "$": "auto_text_key", "code": 2423, "label": "ॷ" },
|
||||
{ "$": "auto_text_key", "code": 2390, "label": " ॖ" },
|
||||
{ "$": "auto_text_key", "code": 2314, "label": "ऊ" },
|
||||
{ "$": "auto_text_key", "code": 2391, "label": " ॗ" }
|
||||
]
|
||||
},
|
||||
"े": {
|
||||
"main": { "$": "auto_text_key", "code": 2319, "label": "ए" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2317, "label": "ऍ" },
|
||||
{ "$": "auto_text_key", "code": 2318, "label": "ऎ" },
|
||||
{ "$": "auto_text_key", "code": 2374, "label": " ॆ" },
|
||||
{ "$": "auto_text_key", "code": 2320, "label": "ऐ" },
|
||||
{ "$": "auto_text_key", "code": 2382, "label": " ॎ" },
|
||||
{ "$": "auto_text_key", "code": 2389, "label": " ॕ" }
|
||||
]
|
||||
},
|
||||
"ो": {
|
||||
"main": { "$": "auto_text_key", "code": 2323, "label": "ओ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 2322, "label": "ऒ" },
|
||||
{ "$": "auto_text_key", "code": 2378, "label": " ॊ" },
|
||||
{ "$": "auto_text_key", "code": 2383, "label": " ॏ" },
|
||||
{ "$": "auto_text_key", "code": 2421, "label": "ॵ" },
|
||||
{ "$": "auto_text_key", "code": 2384, "label": "ॐ" },
|
||||
{ "$": "auto_text_key", "code": 2377, "label": "ॉ" },
|
||||
{ "$": "auto_text_key", "code": 2419, "label": "ॳ" },
|
||||
{ "$": "auto_text_key", "code": 2420, "label": "ॴ" },
|
||||
{ "$": "auto_text_key", "code": 2362, "label": " ऺ" },
|
||||
{ "$": "auto_text_key", "code": 2363, "label": " ऻ" },
|
||||
{ "$": "auto_text_key", "code": 2324, "label": "औ" },
|
||||
{ "$": "auto_text_key", "code": 2321, "label": "ऑ" }
|
||||
]
|
||||
},
|
||||
|
||||
"~right": {
|
||||
"main": { "code": 2404, "label": "।" },
|
||||
"relevant": [
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "$": "layout_direction_selector", "ltr": { "code": 40, "label": "(" }, "rtl": { "code": 41, "label": "(" } },
|
||||
{ "$": "layout_direction_selector", "ltr": { "code": 41, "label": ")" }, "rtl": { "code": 40, "label": ")" } },
|
||||
{ "code": 8205, "label": ">⁞<" },
|
||||
{ "code": 8204, "label": "<⁞>" },
|
||||
{ "code": 2417, "label": "ॱ" },
|
||||
{ "code": 2429, "label": "ॽ" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" },
|
||||
{ "code": 2405, "label": "॥" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" },
|
||||
{ "code": -255, "label": ".in" },
|
||||
{ "code": -255, "label": ".co.in" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "gboard_day",
|
||||
"label": "Gboard Day",
|
||||
"authors": [ "patrickgold", "itskareem" ],
|
||||
"isNightTheme": false,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#0479ed",
|
||||
"colorPrimaryDark": "#0467c9",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "true",
|
||||
"semiTransparentColor": "#20000000",
|
||||
"textColor": "#000000"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#D1D6DC"
|
||||
},
|
||||
"key": {
|
||||
"background": "#FCFFFF",
|
||||
"backgroundPressed": "#F5F5F5",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "true"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#757575"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "@keyboard/background",
|
||||
"foreground": "#424242"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#EEEEEE",
|
||||
"backgroundActive": "#BDBDBD",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "@keyboard/background",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#8A8A8A"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "@key/background",
|
||||
"foreground": "@key/foreground"
|
||||
},
|
||||
"glideTrail": {"foreground": "#200479ed"}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "gboard_night",
|
||||
"label": "Gboard Night",
|
||||
"authors": [ "Netscaping" ],
|
||||
"isNightTheme": true,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#5e97f6",
|
||||
"colorPrimaryDark": "#4285f4",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "false",
|
||||
"semiTransparentColor": "#20FFFFFF",
|
||||
"textColor": "#FFFFFF"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#292e33"
|
||||
},
|
||||
"key": {
|
||||
"background": "#484c4f",
|
||||
"backgroundPressed": "#5e5e60",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "true"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#BDBDBD"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "#373c41",
|
||||
"foreground": "#9b9da0"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#373c41",
|
||||
"backgroundActive": "#5a5e60",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "transparent",
|
||||
"foreground": "#d4d5d6",
|
||||
"foregroundAlt": "#73FFFFFF"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "#FFFFFF",
|
||||
"foreground": "#686868"
|
||||
},
|
||||
"glideTrail": {"foreground": "#205e97f6"}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
"$": "ime.extension.theme",
|
||||
"meta": {
|
||||
"id": "org.florisboard.themes",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"title": "FlorisBoard default themes",
|
||||
"description": "Default themes (both day and night) for the keyboard UI",
|
||||
"maintainers": [ "patrickgold <patrick@patrickgold.dev>" ],
|
||||
"maintainers": [ "patrickgold <patrick@patrickgold.dev>", "lm41 <lm41@lm41.xyz>" ],
|
||||
"license": "apache-2.0"
|
||||
},
|
||||
"themes": [
|
||||
@@ -13,49 +13,37 @@
|
||||
"id": "floris_day",
|
||||
"label": "Floris Day",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNight": false,
|
||||
"isBorderless": false,
|
||||
"isMaterialYouAware": false
|
||||
"isNight": false
|
||||
},
|
||||
{
|
||||
"id": "floris_day_borderless",
|
||||
"label": "Floris Day (Borderless)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNight": false,
|
||||
"isBorderless": true,
|
||||
"isMaterialYouAware": false
|
||||
"isNight": false
|
||||
},
|
||||
{
|
||||
"id": "floris_night",
|
||||
"label": "Floris Night",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNight": true,
|
||||
"isBorderless": false,
|
||||
"isMaterialYouAware": false
|
||||
"isNight": true
|
||||
},
|
||||
{
|
||||
"id": "floris_night_borderless",
|
||||
"label": "Floris Night (Borderless)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNight": true,
|
||||
"isBorderless": true,
|
||||
"isMaterialYouAware": false
|
||||
"isNight": true
|
||||
},
|
||||
{
|
||||
"id": "floris_pure_night",
|
||||
"label": "Floris Pure Night",
|
||||
"authors": [ "serebit" ],
|
||||
"isNight": true,
|
||||
"isBorderless": false,
|
||||
"isMaterialYouAware": false
|
||||
"isNight": true
|
||||
},
|
||||
{
|
||||
"id": "floris_pure_night_borderless",
|
||||
"label": "Floris Pure Night (Borderless)",
|
||||
"authors": [ "serebit" ],
|
||||
"isNight": true,
|
||||
"isBorderless": true,
|
||||
"isMaterialYouAware": false
|
||||
"isNight": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
|
||||
"@defines": {
|
||||
"--primary": "#4caf50",
|
||||
"--primary-variant": "#388e3c",
|
||||
@@ -8,7 +9,20 @@
|
||||
"--surface": "#ffffff",
|
||||
"--surface-variant": "#f5f5f5",
|
||||
|
||||
"--popup-surface": "#eeeeee",
|
||||
"--focused-popup-surface": "#bdbdbd",
|
||||
|
||||
"--drag-marker": "rgb(255,0,0)",
|
||||
"--spacer-color": "rgba(0 ,0, 0, 0.25)",
|
||||
|
||||
"--one-hand-background": "#e8f5e9",
|
||||
"--one-hand-foreground": "#424242",
|
||||
|
||||
"--incognito-icon-color": "#00000011",
|
||||
|
||||
"--on-primary": "#f0f0f0",
|
||||
"--on-background": "#121212",
|
||||
"--on-background-disabled": "rgb(175,175,175)",
|
||||
"--on-surface": "#000000",
|
||||
"--on-surface-variant": "#5f5f5f",
|
||||
|
||||
@@ -16,8 +30,10 @@
|
||||
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
|
||||
},
|
||||
|
||||
"keyboard": {
|
||||
"background": "var(--background)"
|
||||
"window": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"clip": "no"
|
||||
},
|
||||
|
||||
"key": {
|
||||
@@ -25,48 +41,69 @@
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shadow-elevation": "2dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key:pressed": {
|
||||
"background": "var(--surface-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]": {
|
||||
"key[code=10]": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]:pressed": {
|
||||
"key[code=10]:pressed": {
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
"key[code=32]": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"key[code=-201,-202,-203]": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"key[code=-204,-205]": {
|
||||
"font-size": "12sp"
|
||||
},
|
||||
"key[code=-205]": {
|
||||
"text-max-lines": "2"
|
||||
},
|
||||
"key[code=-11][shiftstate=`caps_lock`]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key-hint": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp"
|
||||
"font-family": "monospace",
|
||||
"font-size": "12sp",
|
||||
"padding": "0dp 1dp 1dp 0dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key-popup": {
|
||||
"background": "#eeeeee",
|
||||
"key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"key-popup:focus": {
|
||||
"background": "#bdbdbd",
|
||||
"foreground": "var(--on-surface)"
|
||||
"key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"key-popup-extended-indicator": {
|
||||
"font-size": "16sp"
|
||||
},
|
||||
|
||||
"smartbar": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "6dp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
@@ -78,117 +115,180 @@
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
"font-size": "14sp",
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "2",
|
||||
"text-align": "center",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "circle()"
|
||||
},
|
||||
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
"foreground": "var(--secondary)",
|
||||
"font-size": "16sp",
|
||||
"font-weight": "bold",
|
||||
"padding": "12dp 16dp 12dp 8dp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile-grid": {
|
||||
"margin": "4dp 0dp"
|
||||
},
|
||||
"smartbar-actions-editor-tile": {
|
||||
"margin": "4dp",
|
||||
"padding": "8dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "2",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-999]": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-991]": {
|
||||
"foreground": "var(--drag-marker)"
|
||||
},
|
||||
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rectangle()"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rectangle()",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-word:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-background)"
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-candidate-clip": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-clip:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-background)"
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-candidate-spacer": {
|
||||
"foreground": "var(--surface)"
|
||||
"foreground": "var(--spacer-color)"
|
||||
},
|
||||
|
||||
"clipboard-header": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"clipboard-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"clipboard-header-button:disabled": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"clipboard-header-text": {
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"clipboard-subheader": {
|
||||
"font-size": "14sp",
|
||||
"margin": "6dp"
|
||||
},
|
||||
"clipboard-content": {
|
||||
"padding": "10dp"
|
||||
},
|
||||
"clipboard-item": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"clipboard-item-popup": {
|
||||
"background": "var(--surface-variant)",
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
|
||||
"emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"clipboard-item-popup-action": {
|
||||
"font-size": "16sp",
|
||||
"padding": "12dp"
|
||||
},
|
||||
"emoji-key:pressed": {
|
||||
"clipboard-clear-all-dialog": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"emoji-key-popup": {
|
||||
"background": "#eeeeee",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "1dp"
|
||||
},
|
||||
"emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
"clipboard-clear-all-dialog-message": {
|
||||
"padding": "16dp"
|
||||
},
|
||||
"emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
"clipboard-clear-all-dialog-buttons": {
|
||||
"padding": "4dp"
|
||||
},
|
||||
"clipboard-clear-all-dialog-button": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape-variant)"
|
||||
},
|
||||
"clipboard-history-disabled-title": {
|
||||
"font-weight": "bold"
|
||||
},
|
||||
"clipboard-history-disabled-message": {
|
||||
"padding": "0dp 4dp 0dp 8dp"
|
||||
},
|
||||
"clipboard-history-disabled-button": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-primary)",
|
||||
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
|
||||
},
|
||||
"clipboard-history-locked-title": {
|
||||
"font-weight": "bold",
|
||||
"text-align": "center"
|
||||
},
|
||||
"clipboard-history-locked-message": {
|
||||
"padding": "0dp 4dp 0dp 0dp",
|
||||
"text-align": "center"
|
||||
},
|
||||
|
||||
"extracted-landscape-input-layout": {
|
||||
@@ -213,15 +313,70 @@
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#00000011"
|
||||
"foreground": "var(--incognito-icon-color)"
|
||||
},
|
||||
|
||||
"inline-autofill-chip": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"media-emoji-subheader": {
|
||||
"font-weight": "bold",
|
||||
"margin": "4dp"
|
||||
},
|
||||
"media-emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"media-emoji-key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"media-emoji-key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
},
|
||||
"media-emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
"media-bottom-row-button": {
|
||||
"padding": "16dp 0dp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key-popup-extended-indicator": {
|
||||
"foreground": "inherit"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#e8f5e9",
|
||||
"foreground": "#424242"
|
||||
"background": "var(--one-hand-background)",
|
||||
"foreground": "var(--one-hand-foreground)"
|
||||
},
|
||||
|
||||
"system-nav-bar": {
|
||||
"background": "var(--background)"
|
||||
"subtype-panel": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"subtype-panel-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "18sp",
|
||||
"padding": "12dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
|
||||
"@defines": {
|
||||
"--primary": "#4caf50",
|
||||
"--primary-variant": "#388e3c",
|
||||
@@ -8,7 +9,20 @@
|
||||
"--surface": "#f0f0f0",
|
||||
"--surface-variant": "#ffffff",
|
||||
|
||||
"--popup-surface": "#eeeeee",
|
||||
"--focused-popup-surface": "#bdbdbd",
|
||||
|
||||
"--drag-marker": "rgb(255,0,0)",
|
||||
"--spacer-color": "rgba(0 ,0, 0, 0.25)",
|
||||
|
||||
"--one-hand-background": "#e8f5e9",
|
||||
"--one-hand-foreground": "#424242",
|
||||
|
||||
"--incognito-icon-color": "#00000011",
|
||||
|
||||
"--on-primary": "#f0f0f0",
|
||||
"--on-background": "#121212",
|
||||
"--on-background-disabled": "#12121248",
|
||||
"--on-surface": "#000000",
|
||||
"--on-surface-variant": "#5f5f5f",
|
||||
|
||||
@@ -16,56 +30,82 @@
|
||||
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
|
||||
},
|
||||
|
||||
"keyboard": {
|
||||
"background": "var(--background)"
|
||||
"window": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"clip": "no"
|
||||
},
|
||||
|
||||
"key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]": {
|
||||
"key[code=10]": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)"
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "0dp 6dp"
|
||||
},
|
||||
"key[code={c:enter}]:pressed": {
|
||||
"key[code=10]:pressed": {
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
"key[code=32]": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp",
|
||||
"margin": "0dp 6dp",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"key[code=-201,-202,-203]": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"key[code=-204,-205]": {
|
||||
"font-size": "12sp"
|
||||
},
|
||||
"key[code=-205]": {
|
||||
"text-max-lines": "2"
|
||||
},
|
||||
"key[code=-11][shiftstate=`caps_lock`]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key-hint": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp"
|
||||
"font-size": "12sp",
|
||||
"font-family": "monospace",
|
||||
"padding": "0dp 1dp 1dp 0dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key-popup": {
|
||||
"background": "#eeeeee",
|
||||
"key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"key-popup:focus": {
|
||||
"background": "#bdbdbd",
|
||||
"foreground": "var(--on-surface)"
|
||||
"key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"key-popup-extended-indicator": {
|
||||
"font-size": "16sp"
|
||||
},
|
||||
|
||||
"smartbar": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "6dp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
@@ -77,7 +117,6 @@
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-key:pressed": {
|
||||
@@ -85,48 +124,80 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
"font-size": "14sp",
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "2",
|
||||
"text-align": "center",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"font-weight": "bold",
|
||||
"padding": "12dp 16dp 12dp 8dp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile-grid": {
|
||||
"margin": "4dp 0dp"
|
||||
},
|
||||
"smartbar-actions-editor-tile": {
|
||||
"margin": "4dp",
|
||||
"padding": "8dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "2",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-999]": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-991]": {
|
||||
"foreground": "var(--drag-marker)"
|
||||
},
|
||||
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rectangle()"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rectangle()",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-word:pressed": {
|
||||
"background": "var(--surface)",
|
||||
@@ -136,7 +207,11 @@
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-clip:pressed": {
|
||||
"background": "var(--surface)",
|
||||
@@ -151,43 +226,80 @@
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"clipboard-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"clipboard-header-button:disabled": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"clipboard-header-text": {
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"clipboard-subheader": {
|
||||
"font-size": "14sp",
|
||||
"margin": "6dp"
|
||||
},
|
||||
"clipboard-content": {
|
||||
"padding": "10dp"
|
||||
},
|
||||
"clipboard-item": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"clipboard-item-popup": {
|
||||
"background": "var(--surface-variant)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
|
||||
"emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"clipboard-item-popup-action": {
|
||||
"font-size": "16sp",
|
||||
"padding": "12dp"
|
||||
},
|
||||
"emoji-key:pressed": {
|
||||
"clipboard-clear-all-dialog": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"emoji-key-popup": {
|
||||
"background": "#eeeeee",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "1dp"
|
||||
},
|
||||
"emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
"clipboard-clear-all-dialog-message": {
|
||||
"padding": "16dp"
|
||||
},
|
||||
"emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
"clipboard-clear-all-dialog-buttons": {
|
||||
"padding": "4dp"
|
||||
},
|
||||
"clipboard-clear-all-dialog-button": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape-variant)"
|
||||
},
|
||||
"clipboard-history-disabled-title": {
|
||||
"font-weight": "bold"
|
||||
},
|
||||
"clipboard-history-disabled-message": {
|
||||
"padding": "0dp 4dp 0dp 8dp"
|
||||
},
|
||||
"clipboard-history-disabled-button": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-primary)",
|
||||
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
|
||||
},
|
||||
"clipboard-history-locked-title": {
|
||||
"font-weight": "bold",
|
||||
"text-align": "center"
|
||||
},
|
||||
"clipboard-history-locked-message": {
|
||||
"padding": "0dp 4dp 0dp 0dp",
|
||||
"text-align": "center"
|
||||
},
|
||||
|
||||
"extracted-landscape-input-layout": {
|
||||
@@ -212,15 +324,71 @@
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#00000011"
|
||||
"foreground": "var(--incognito-icon-color)"
|
||||
},
|
||||
|
||||
"inline-autofill-chip": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"media-emoji-subheader": {
|
||||
"font-weight": "bold",
|
||||
"margin": "4dp"
|
||||
},
|
||||
"media-emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"media-emoji-key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"media-emoji-key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
},
|
||||
"media-emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
"media-bottom-row-button": {
|
||||
"padding": "16dp 0dp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key-popup-extended-indicator": {
|
||||
"foreground": "inherit"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#e8f5e9",
|
||||
"foreground": "#424242"
|
||||
"background": "var(--one-hand-background)",
|
||||
"foreground": "var(--one-hand-foreground)"
|
||||
},
|
||||
|
||||
"system-nav-bar": {
|
||||
"background": "var(--background)"
|
||||
"subtype-panel": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"subtype-panel-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "18sp",
|
||||
"padding": "12dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
|
||||
"@defines": {
|
||||
"--primary": "#4caf50",
|
||||
"--primary-variant": "#388e3c",
|
||||
@@ -7,8 +8,16 @@
|
||||
"--background": "#212121",
|
||||
"--surface": "#424242",
|
||||
"--surface-variant": "#616161",
|
||||
|
||||
"--popup-surface": "#757575",
|
||||
"--focused-popup-surface": "#bdbdbd",
|
||||
"--drag-marker": "rgb(255,0,0)",
|
||||
"--spacer-color": "rgba(255, 255, 255, 0.25)",
|
||||
"--one-hand-background": "#1b5e20",
|
||||
"--one-hand-foreground": "#eeeeee",
|
||||
"--incognito-icon-color": "#ffffff11",
|
||||
"--on-primary": "#f0f0f0",
|
||||
"--on-background": "#dcdcdc",
|
||||
"--on-background-disabled": "#dcdcdc48",
|
||||
"--on-surface": "#ffffff",
|
||||
"--on-surface-variant": "#a0a0a0",
|
||||
|
||||
@@ -16,8 +25,10 @@
|
||||
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
|
||||
},
|
||||
|
||||
"keyboard": {
|
||||
"background": "var(--background)"
|
||||
"window": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"clip": "no"
|
||||
},
|
||||
|
||||
"key": {
|
||||
@@ -25,48 +36,68 @@
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shadow-elevation": "2dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key:pressed": {
|
||||
"background": "var(--surface-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]": {
|
||||
"key[code=10]": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]:pressed": {
|
||||
"key[code=10]:pressed": {
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
"key[code=32]": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"key[code=-201,-202,-203]": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"key[code=-204,-205]": {
|
||||
"font-size": "12sp"
|
||||
},
|
||||
"key[code=-205]": {
|
||||
"text-max-lines": "2"
|
||||
},
|
||||
"key[code=-11][shiftstate=`caps_lock`]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key-hint": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp"
|
||||
"font-family": "monospace",
|
||||
"font-size": "12sp",
|
||||
"padding": "0dp 1dp 1dp 0dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key-popup": {
|
||||
"background": "#757575",
|
||||
"key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"key-popup:focus": {
|
||||
"background": "#bdbdbd",
|
||||
"foreground": "var(--on-surface)"
|
||||
"key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"key-popup-extended-indicator": {
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "6dp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
@@ -78,117 +109,180 @@
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
"font-size": "14sp",
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "2",
|
||||
"text-align": "center",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "circle()"
|
||||
},
|
||||
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
"foreground": "var(--secondary)",
|
||||
"font-size": "16sp",
|
||||
"font-weight": "bold",
|
||||
"padding": "12dp 16dp 12dp 8dp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile-grid": {
|
||||
"margin": "4dp 0dp"
|
||||
},
|
||||
"smartbar-actions-editor-tile": {
|
||||
"margin": "4dp",
|
||||
"padding": "8dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "2",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-999]": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-991]": {
|
||||
"foreground": "var(--drag-marker)"
|
||||
},
|
||||
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rectangle()"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rectangle()",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-word:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-background)"
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-candidate-clip": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-clip:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-background)"
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-candidate-spacer": {
|
||||
"foreground": "var(--surface)"
|
||||
"foreground": "var(--spacer-color)"
|
||||
},
|
||||
|
||||
"clipboard-header": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"clipboard-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"clipboard-header-button:disabled": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"clipboard-header-text": {
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"clipboard-subheader": {
|
||||
"font-size": "14sp",
|
||||
"margin": "6dp"
|
||||
},
|
||||
"clipboard-content": {
|
||||
"padding": "10dp"
|
||||
},
|
||||
"clipboard-item": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"clipboard-item-popup": {
|
||||
"background": "var(--surface-variant)",
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
|
||||
"emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"clipboard-item-popup-action": {
|
||||
"font-size": "16sp",
|
||||
"padding": "12dp"
|
||||
},
|
||||
"emoji-key:pressed": {
|
||||
"clipboard-clear-all-dialog": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"emoji-key-popup": {
|
||||
"background": "#757575",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "1dp"
|
||||
},
|
||||
"emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
"clipboard-clear-all-dialog-message": {
|
||||
"padding": "16dp"
|
||||
},
|
||||
"emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
"clipboard-clear-all-dialog-buttons": {
|
||||
"padding": "4dp"
|
||||
},
|
||||
"clipboard-clear-all-dialog-button": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape-variant)"
|
||||
},
|
||||
"clipboard-history-disabled-title": {
|
||||
"font-weight": "bold"
|
||||
},
|
||||
"clipboard-history-disabled-message": {
|
||||
"padding": "0dp 4dp 0dp 8dp"
|
||||
},
|
||||
"clipboard-history-disabled-button": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-primary)",
|
||||
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
|
||||
},
|
||||
"clipboard-history-locked-title": {
|
||||
"font-weight": "bold",
|
||||
"text-align": "center"
|
||||
},
|
||||
"clipboard-history-locked-message": {
|
||||
"padding": "0dp 4dp 0dp 0dp",
|
||||
"text-align": "center"
|
||||
},
|
||||
|
||||
"extracted-landscape-input-layout": {
|
||||
@@ -213,15 +307,69 @@
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
"foreground": "var(--incognito-icon-color)"
|
||||
},
|
||||
|
||||
"inline-autofill-chip": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"media-emoji-subheader": {
|
||||
"font-weight": "bold",
|
||||
"margin": "4dp"
|
||||
},
|
||||
"media-emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"media-emoji-key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"media-emoji-key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
},
|
||||
"media-emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
"media-bottom-row-button": {
|
||||
"padding": "16dp 0dp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key-popup-extended-indicator": {
|
||||
"foreground": "inherit"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#1b5e20",
|
||||
"foreground": "#eeeeee"
|
||||
"background": "var(--one-hand-background)",
|
||||
"foreground": "var(--one-hand-foreground)"
|
||||
},
|
||||
|
||||
"system-nav-bar": {
|
||||
"background": "var(--background)"
|
||||
"subtype-panel": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"subtype-panel-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "18sp",
|
||||
"padding": "12dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
|
||||
"@defines": {
|
||||
"--primary": "#4caf50",
|
||||
"--primary-variant": "#388e3c",
|
||||
@@ -7,8 +8,16 @@
|
||||
"--background": "#212121",
|
||||
"--surface": "#424242",
|
||||
"--surface-variant": "#616161",
|
||||
|
||||
"--popup-surface": "#757575",
|
||||
"--focused-popup-surface": "#bdbdbd",
|
||||
"--drag-marker": "rgb(255,0,0)",
|
||||
"--spacer-color": "rgba(255, 255, 255, 0.25)",
|
||||
"--one-hand-background": "#1b5e20",
|
||||
"--one-hand-foreground": "#eeeeee",
|
||||
"--incognito-icon-color": "#ffffff11",
|
||||
"--on-primary": "#f0f0f0",
|
||||
"--on-background": "#dcdcdc",
|
||||
"--on-background-disabled": "#dcdcdc48",
|
||||
"--on-surface": "#ffffff",
|
||||
"--on-surface-variant": "#a0a0a0",
|
||||
|
||||
@@ -16,56 +25,81 @@
|
||||
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
|
||||
},
|
||||
|
||||
"keyboard": {
|
||||
"background": "var(--background)"
|
||||
"window": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"clip": "no"
|
||||
},
|
||||
|
||||
"key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]": {
|
||||
"key[code=10]": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)"
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "0dp 6dp"
|
||||
},
|
||||
"key[code={c:enter}]:pressed": {
|
||||
"key[code=10]:pressed": {
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
"key[code=32]": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp",
|
||||
"margin": "0dp 6dp",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"key[code=-201,-202,-203]": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"key[code=-204,-205]": {
|
||||
"font-size": "12sp"
|
||||
},
|
||||
"key[code=-205]": {
|
||||
"text-max-lines": "2"
|
||||
},
|
||||
"key[code=-11][shiftstate=`caps_lock`]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key-hint": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp"
|
||||
"font-size": "12sp",
|
||||
"font-family": "monospace",
|
||||
"padding": "0dp 1dp 1dp 0dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key-popup": {
|
||||
"background": "#757575",
|
||||
"key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"key-popup:focus": {
|
||||
"background": "#bdbdbd",
|
||||
"foreground": "var(--on-surface)"
|
||||
"key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"key-popup-extended-indicator": {
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "6dp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
@@ -77,7 +111,6 @@
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-key:pressed": {
|
||||
@@ -85,48 +118,80 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
"font-size": "14sp",
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "2",
|
||||
"text-align": "center",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"font-weight": "bold",
|
||||
"padding": "12dp 16dp 12dp 8dp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile-grid": {
|
||||
"margin": "4dp 0dp"
|
||||
},
|
||||
"smartbar-actions-editor-tile": {
|
||||
"margin": "4dp",
|
||||
"padding": "8dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "2",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-999]": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-991]": {
|
||||
"foreground": "var(--drag-marker)"
|
||||
},
|
||||
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rectangle()"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rectangle()",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-word:pressed": {
|
||||
"background": "var(--surface)",
|
||||
@@ -136,7 +201,11 @@
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-clip:pressed": {
|
||||
"background": "var(--surface)",
|
||||
@@ -151,43 +220,80 @@
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"clipboard-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"clipboard-header-button:disabled": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"clipboard-header-text": {
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"clipboard-subheader": {
|
||||
"font-size": "14sp",
|
||||
"margin": "6dp"
|
||||
},
|
||||
"clipboard-content": {
|
||||
"padding": "10dp"
|
||||
},
|
||||
"clipboard-item": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"clipboard-item-popup": {
|
||||
"background": "var(--surface-variant)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
|
||||
"emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"clipboard-item-popup-action": {
|
||||
"font-size": "16sp",
|
||||
"padding": "12dp"
|
||||
},
|
||||
"emoji-key:pressed": {
|
||||
"clipboard-clear-all-dialog": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"emoji-key-popup": {
|
||||
"background": "#757575",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "1dp"
|
||||
},
|
||||
"emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
"clipboard-clear-all-dialog-message": {
|
||||
"padding": "16dp"
|
||||
},
|
||||
"emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
"clipboard-clear-all-dialog-buttons": {
|
||||
"padding": "4dp"
|
||||
},
|
||||
"clipboard-clear-all-dialog-button": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape-variant)"
|
||||
},
|
||||
"clipboard-history-disabled-title": {
|
||||
"font-weight": "bold"
|
||||
},
|
||||
"clipboard-history-disabled-message": {
|
||||
"padding": "0dp 4dp 0dp 8dp"
|
||||
},
|
||||
"clipboard-history-disabled-button": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-primary)",
|
||||
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
|
||||
},
|
||||
"clipboard-history-locked-title": {
|
||||
"font-weight": "bold",
|
||||
"text-align": "center"
|
||||
},
|
||||
"clipboard-history-locked-message": {
|
||||
"padding": "0dp 4dp 0dp 0dp",
|
||||
"text-align": "center"
|
||||
},
|
||||
|
||||
"extracted-landscape-input-layout": {
|
||||
@@ -212,15 +318,69 @@
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
"foreground": "var(--incognito-icon-color)"
|
||||
},
|
||||
|
||||
"inline-autofill-chip": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"media-emoji-subheader": {
|
||||
"font-weight": "bold",
|
||||
"margin": "4dp"
|
||||
},
|
||||
"media-emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"media-emoji-key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"media-emoji-key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
},
|
||||
"media-emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
"media-bottom-row-button": {
|
||||
"padding": "16dp 0dp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key-popup-extended-indicator": {
|
||||
"foreground": "inherit"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#1b5e20",
|
||||
"foreground": "#eeeeee"
|
||||
"background": "var(--one-hand-background)",
|
||||
"foreground": "var(--one-hand-foreground)"
|
||||
},
|
||||
|
||||
"system-nav-bar": {
|
||||
"background": "var(--background)"
|
||||
"subtype-panel": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"subtype-panel-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "18sp",
|
||||
"padding": "12dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
|
||||
"@defines": {
|
||||
"--primary": "#388e3c",
|
||||
"--primary-variant": "#306d32",
|
||||
@@ -7,7 +8,15 @@
|
||||
"--background": "#000000",
|
||||
"--surface": "#212121",
|
||||
"--surface-variant": "#3d3d3d",
|
||||
|
||||
"--popup-surface": "#424242",
|
||||
"--focused-popup-surface": "#707070",
|
||||
"--drag-marker": "rgb(255,0,0)",
|
||||
"--spacer-color": "rgba(255, 255, 255, 0.25)",
|
||||
"--one-hand-background": "#1b5e20",
|
||||
"--one-hand-foreground": "#eeeeee",
|
||||
"--incognito-icon-color": "#ffffff11",
|
||||
"--on-primary": "#f0f0f0",
|
||||
"--on-background-disabled": "#dcdcdc48",
|
||||
"--on-background": "#eeeeee",
|
||||
"--on-surface": "#eeeeee",
|
||||
"--on-surface-variant": "#ffffff73",
|
||||
@@ -16,8 +25,10 @@
|
||||
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
|
||||
},
|
||||
|
||||
"keyboard": {
|
||||
"background": "var(--background)"
|
||||
"window": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"clip": "no"
|
||||
},
|
||||
|
||||
"key": {
|
||||
@@ -25,48 +36,68 @@
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shadow-elevation": "2dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key:pressed": {
|
||||
"background": "var(--surface-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]": {
|
||||
"key[code=10]": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]:pressed": {
|
||||
"key[code=10]:pressed": {
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
"key[code=32]": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"key[code=-201,-202,-203]": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"key[code=-204,-205]": {
|
||||
"font-size": "12sp"
|
||||
},
|
||||
"key[code=-205]": {
|
||||
"text-max-lines": "2"
|
||||
},
|
||||
"key[code=-11][shiftstate=`caps_lock`]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key-hint": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp"
|
||||
"font-family": "monospace",
|
||||
"font-size": "12sp",
|
||||
"padding": "0dp 1dp 1dp 0dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key-popup": {
|
||||
"background": "#424242",
|
||||
"key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"key-popup:focus": {
|
||||
"background": "#707070",
|
||||
"foreground": "var(--on-surface)"
|
||||
"key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"key-popup-extended-indicator": {
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "6dp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
@@ -78,7 +109,6 @@
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-key:pressed": {
|
||||
@@ -86,109 +116,177 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
"font-size": "14sp",
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "2",
|
||||
"text-align": "center",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "circle()"
|
||||
},
|
||||
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
"foreground": "var(--secondary)",
|
||||
"font-size": "16sp",
|
||||
"font-weight": "bold",
|
||||
"padding": "12dp 16dp 12dp 8dp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile-grid": {
|
||||
"margin": "4dp 0dp"
|
||||
},
|
||||
"smartbar-actions-editor-tile": {
|
||||
"margin": "4dp",
|
||||
"padding": "8dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "2",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-999]": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-991]": {
|
||||
"foreground": "var(--drag-marker)"
|
||||
},
|
||||
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rectangle()"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rectangle()",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-word:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-background)"
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-candidate-clip": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-clip:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-background)"
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-candidate-spacer": {
|
||||
"foreground": "var(--surface)"
|
||||
"foreground": "var(--spacer-color)"
|
||||
},
|
||||
|
||||
"clipboard-header": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"clipboard-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"clipboard-header-button:disabled": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"clipboard-header-text": {
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"clipboard-subheader": {
|
||||
"font-size": "14sp",
|
||||
"margin": "6dp"
|
||||
},
|
||||
"clipboard-content": {
|
||||
"padding": "10dp"
|
||||
},
|
||||
"clipboard-item": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"clipboard-item-popup": {
|
||||
"background": "var(--surface-variant)",
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
|
||||
"emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"clipboard-item-popup-action": {
|
||||
"font-size": "16sp",
|
||||
"padding": "12dp"
|
||||
},
|
||||
"emoji-key:pressed": {
|
||||
"clipboard-clear-all-dialog": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"emoji-key-popup": {
|
||||
"background": "#757575",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "1dp"
|
||||
},
|
||||
"emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
"clipboard-clear-all-dialog-message": {
|
||||
"padding": "16dp"
|
||||
},
|
||||
"emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
"clipboard-clear-all-dialog-buttons": {
|
||||
"padding": "4dp"
|
||||
},
|
||||
"clipboard-clear-all-dialog-button": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape-variant)"
|
||||
},
|
||||
"clipboard-history-disabled-title": {
|
||||
"font-weight": "bold"
|
||||
},
|
||||
"clipboard-history-disabled-message": {
|
||||
"padding": "0dp 4dp 0dp 8dp"
|
||||
},
|
||||
"clipboard-history-disabled-button": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-primary)",
|
||||
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
|
||||
},
|
||||
"clipboard-history-locked-title": {
|
||||
"font-weight": "bold",
|
||||
"text-align": "center"
|
||||
},
|
||||
"clipboard-history-locked-message": {
|
||||
"padding": "0dp 4dp 0dp 0dp",
|
||||
"text-align": "center"
|
||||
},
|
||||
|
||||
"extracted-landscape-input-layout": {
|
||||
@@ -199,29 +297,83 @@
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp",
|
||||
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
|
||||
"border-color": "var(--secondary-variant)",
|
||||
"border-width": "1dp"
|
||||
"border-color": "var(--secondary)",
|
||||
"border-width": "2dp"
|
||||
},
|
||||
"extracted-landscape-input-action": {
|
||||
"background": "var(--surface)",
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "rounded-corner(4dp, 4dp, 4dp, 4dp)"
|
||||
},
|
||||
|
||||
"glide-trail": {
|
||||
"foreground": "var(--primary-variant)"
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
"foreground": "var(--incognito-icon-color)"
|
||||
},
|
||||
|
||||
"inline-autofill-chip": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"media-emoji-subheader": {
|
||||
"font-weight": "bold",
|
||||
"margin": "4dp"
|
||||
},
|
||||
"media-emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"media-emoji-key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"media-emoji-key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
},
|
||||
"media-emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
"media-bottom-row-button": {
|
||||
"padding": "16dp 0dp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key-popup-extended-indicator": {
|
||||
"foreground": "inherit"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#000000",
|
||||
"foreground": "#eeeeee"
|
||||
"background": "var(--one-hand-background)",
|
||||
"foreground": "var(--one-hand-foreground)"
|
||||
},
|
||||
|
||||
"system-nav-bar": {
|
||||
"background": "var(--background)"
|
||||
"subtype-panel": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"subtype-panel-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "18sp",
|
||||
"padding": "12dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
|
||||
"@defines": {
|
||||
"--primary": "#388e3c",
|
||||
"--primary-variant": "#306d32",
|
||||
@@ -7,7 +8,15 @@
|
||||
"--background": "#000000",
|
||||
"--surface": "#212121",
|
||||
"--surface-variant": "#3d3d3d",
|
||||
|
||||
"--popup-surface": "#424242",
|
||||
"--focused-popup-surface": "#707070",
|
||||
"--drag-marker": "rgb(255,0,0)",
|
||||
"--spacer-color": "rgba(255, 255, 255, 0.25)",
|
||||
"--one-hand-background": "#1b5e20",
|
||||
"--one-hand-foreground": "#eeeeee",
|
||||
"--incognito-icon-color": "#ffffff11",
|
||||
"--on-primary": "#f0f0f0",
|
||||
"--on-background-disabled": "#dcdcdc48",
|
||||
"--on-background": "#eeeeee",
|
||||
"--on-surface": "#eeeeee",
|
||||
"--on-surface-variant": "#ffffff73",
|
||||
@@ -16,56 +25,81 @@
|
||||
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
|
||||
},
|
||||
|
||||
"keyboard": {
|
||||
"background": "var(--background)"
|
||||
"window": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"clip": "no"
|
||||
},
|
||||
|
||||
"key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key:pressed": {
|
||||
"background": "#6161617f",
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:enter}]": {
|
||||
"key[code=10]": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)"
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "0dp 6dp"
|
||||
},
|
||||
"key[code={c:enter}]:pressed": {
|
||||
"key[code=10]:pressed": {
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
"background": "#61616146",
|
||||
"key[code=32]": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp",
|
||||
"margin": "0dp 6dp",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"key[code=-201,-202,-203]": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
"key[code=-204,-205]": {
|
||||
"font-size": "12sp"
|
||||
},
|
||||
"key[code=-205]": {
|
||||
"text-max-lines": "2"
|
||||
},
|
||||
"key[code=-11][shiftstate=`caps_lock`]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key-hint": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"font-size": "12sp"
|
||||
"font-size": "12sp",
|
||||
"font-family": "monospace",
|
||||
"padding": "0dp 1dp 1dp 0dp",
|
||||
"text-max-lines": "1"
|
||||
},
|
||||
"key-popup": {
|
||||
"background": "#363636",
|
||||
"key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"key-popup:focus": {
|
||||
"background": "#5F5F5F",
|
||||
"foreground": "var(--on-surface)"
|
||||
"key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"key-popup-extended-indicator": {
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar": {
|
||||
"font-size": "18sp"
|
||||
},
|
||||
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"margin": "6dp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
@@ -77,7 +111,6 @@
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-key:pressed": {
|
||||
@@ -85,48 +118,80 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
"font-size": "14sp",
|
||||
"shape": "var(--shape)",
|
||||
"text-max-lines": "2",
|
||||
"text-align": "center",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
"font-size": "16sp",
|
||||
"font-weight": "bold",
|
||||
"padding": "12dp 16dp 12dp 8dp",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile-grid": {
|
||||
"margin": "4dp 0dp"
|
||||
},
|
||||
"smartbar-actions-editor-tile": {
|
||||
"margin": "4dp",
|
||||
"padding": "8dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "2",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-999]": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"smartbar-actions-editor-tile[code=-991]": {
|
||||
"foreground": "var(--drag-marker)"
|
||||
},
|
||||
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rectangle()"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rectangle()",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-word:pressed": {
|
||||
"background": "var(--surface)",
|
||||
@@ -136,7 +201,11 @@
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "14sp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
|
||||
"margin": "4dp",
|
||||
"padding": "8dp 0dp",
|
||||
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"smartbar-candidate-clip:pressed": {
|
||||
"background": "var(--surface)",
|
||||
@@ -151,43 +220,80 @@
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"clipboard-header-button": {
|
||||
"margin": "4dp",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"clipboard-header-button:disabled": {
|
||||
"foreground": "var(--on-background-disabled)"
|
||||
},
|
||||
"clipboard-header-text": {
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
},
|
||||
"clipboard-subheader": {
|
||||
"font-size": "14sp",
|
||||
"margin": "6dp"
|
||||
},
|
||||
"clipboard-content": {
|
||||
"padding": "10dp"
|
||||
},
|
||||
"clipboard-item": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"clipboard-item-popup": {
|
||||
"background": "var(--surface-variant)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"margin": "4dp",
|
||||
"padding": "12dp 8dp",
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
|
||||
"emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
"clipboard-item-popup-action": {
|
||||
"font-size": "16sp",
|
||||
"padding": "12dp"
|
||||
},
|
||||
"emoji-key:pressed": {
|
||||
"clipboard-clear-all-dialog": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"emoji-key-popup": {
|
||||
"background": "#757575",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
"shape": "var(--shape-variant)",
|
||||
"shadow-elevation": "1dp"
|
||||
},
|
||||
"emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
"clipboard-clear-all-dialog-message": {
|
||||
"padding": "16dp"
|
||||
},
|
||||
"emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
"clipboard-clear-all-dialog-buttons": {
|
||||
"padding": "4dp"
|
||||
},
|
||||
"clipboard-clear-all-dialog-button": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "var(--shape-variant)"
|
||||
},
|
||||
"clipboard-history-disabled-title": {
|
||||
"font-weight": "bold"
|
||||
},
|
||||
"clipboard-history-disabled-message": {
|
||||
"padding": "0dp 4dp 0dp 8dp"
|
||||
},
|
||||
"clipboard-history-disabled-button": {
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-primary)",
|
||||
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
|
||||
},
|
||||
"clipboard-history-locked-title": {
|
||||
"font-weight": "bold",
|
||||
"text-align": "center"
|
||||
},
|
||||
"clipboard-history-locked-message": {
|
||||
"padding": "0dp 4dp 0dp 0dp",
|
||||
"text-align": "center"
|
||||
},
|
||||
|
||||
"extracted-landscape-input-layout": {
|
||||
@@ -198,29 +304,83 @@
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp",
|
||||
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
|
||||
"border-color": "var(--secondary-variant)",
|
||||
"border-width": "1dp"
|
||||
"border-color": "var(--secondary)",
|
||||
"border-width": "2dp"
|
||||
},
|
||||
"extracted-landscape-input-action": {
|
||||
"background": "var(--surface)",
|
||||
"background": "var(--primary)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "rounded-corner(4dp, 4dp, 4dp, 4dp)"
|
||||
},
|
||||
|
||||
"glide-trail": {
|
||||
"foreground": "var(--primary-variant)"
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
"foreground": "var(--incognito-icon-color)"
|
||||
},
|
||||
|
||||
"inline-autofill-chip": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"media-emoji-subheader": {
|
||||
"font-weight": "bold",
|
||||
"margin": "4dp"
|
||||
},
|
||||
"media-emoji-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"media-emoji-key-popup-box": {
|
||||
"background": "var(--popup-surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "22sp",
|
||||
"shape": "var(--shape)",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"media-emoji-key-popup-element:focus": {
|
||||
"background": "var(--focused-popup-surface)",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-tab": {
|
||||
"foreground": "var(--on-background)"
|
||||
},
|
||||
"media-emoji-tab:focus": {
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
"media-bottom-row-button": {
|
||||
"padding": "16dp 0dp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"media-emoji-key-popup-extended-indicator": {
|
||||
"foreground": "inherit"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#000000",
|
||||
"foreground": "#eeeeee"
|
||||
"background": "var(--one-hand-background)",
|
||||
"foreground": "var(--one-hand-foreground)"
|
||||
},
|
||||
|
||||
"system-nav-bar": {
|
||||
"background": "var(--background)"
|
||||
"subtype-panel": {
|
||||
"background": "var(--background)",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"subtype-panel-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "18sp",
|
||||
"padding": "12dp",
|
||||
"text-align": "center",
|
||||
"text-max-lines": "1",
|
||||
"text-overflow": "ellipsis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyboardManager
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.FlorisEmojiCompat
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpManager
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.crashutility.CrashUtility
|
||||
@@ -61,7 +60,6 @@ class FlorisApplication : Application() {
|
||||
System.loadLibrary("fl_native")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
FlorisImeTheme.init()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package dev.patrickgold.florisboard
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.inputmethodservice.ExtractEditText
|
||||
import android.os.Build
|
||||
@@ -48,10 +49,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -79,6 +79,8 @@ import dev.patrickgold.florisboard.app.devtools.DevtoolsOverlay
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.ImeUiMode
|
||||
import dev.patrickgold.florisboard.ime.clipboard.ClipboardInputLayout
|
||||
import dev.patrickgold.florisboard.ime.core.SelectSubtypePanel
|
||||
import dev.patrickgold.florisboard.ime.core.isSubtypeSelectionShowing
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorRange
|
||||
import dev.patrickgold.florisboard.ime.editor.FlorisEditorInfo
|
||||
import dev.patrickgold.florisboard.ime.input.InputFeedbackController
|
||||
@@ -99,7 +101,7 @@ import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsEditorPa
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputLayout
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButton
|
||||
import dev.patrickgold.florisboard.ime.theme.WallpaperChangeReceiver
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import dev.patrickgold.florisboard.lib.compose.SystemUiIme
|
||||
import dev.patrickgold.florisboard.lib.devtools.LogTopic
|
||||
@@ -111,6 +113,7 @@ import dev.patrickgold.florisboard.lib.util.ViewUtils
|
||||
import dev.patrickgold.florisboard.lib.util.debugSummarize
|
||||
import dev.patrickgold.florisboard.lib.util.launchActivity
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import java.lang.ref.WeakReference
|
||||
import org.florisboard.lib.android.AndroidInternalR
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.isOrientationLandscape
|
||||
@@ -118,14 +121,11 @@ import org.florisboard.lib.android.isOrientationPortrait
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemServiceOrNull
|
||||
import org.florisboard.lib.kotlin.collectLatestIn
|
||||
import org.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import org.florisboard.lib.snygg.ui.shape
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import java.lang.ref.WeakReference
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
|
||||
/**
|
||||
* Global weak reference for the [FlorisImeService] class. This is needed as certain actions (request hide, switch to
|
||||
@@ -232,10 +232,10 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
val imm = ims.systemServiceOrNull(InputMethodManager::class) ?: return false
|
||||
val list: List<InputMethodInfo> = imm.enabledInputMethodList
|
||||
for (el in list) {
|
||||
for (i in 0 until el.subtypeCount){
|
||||
for (i in 0 until el.subtypeCount) {
|
||||
if (el.getSubtypeAt(i).mode != "voice") continue
|
||||
if (AndroidVersion.ATLEAST_API28_P) {
|
||||
ims.switchInputMethod(el.id)
|
||||
ims.switchInputMethod(el.id, el.getSubtypeAt(i))
|
||||
return true
|
||||
} else {
|
||||
ims.window.window?.let { window ->
|
||||
@@ -267,6 +267,8 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
private var isExtractUiShown by mutableStateOf(false)
|
||||
private var resourcesContext by mutableStateOf(this as Context)
|
||||
|
||||
private val wallpaperChangeReceiver = WallpaperChangeReceiver()
|
||||
|
||||
init {
|
||||
setTheme(R.style.FlorisImeTheme)
|
||||
}
|
||||
@@ -277,9 +279,20 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window.window!!, false)
|
||||
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
|
||||
val config = Configuration(resources.configuration)
|
||||
config.setLocale(subtype.primaryLocale.base)
|
||||
if (prefs.localization.displayKeyboardLabelsInSubtypeLanguage.get()) {
|
||||
config.setLocale(subtype.primaryLocale.base)
|
||||
}
|
||||
resourcesContext = createConfigurationContext(config)
|
||||
}
|
||||
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.observeForever { shouldSync ->
|
||||
val config = Configuration(resources.configuration)
|
||||
if (shouldSync) {
|
||||
config.setLocale(subtypeManager.activeSubtype.primaryLocale.base)
|
||||
}
|
||||
resourcesContext = createConfigurationContext(config)
|
||||
}
|
||||
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
|
||||
registerReceiver(wallpaperChangeReceiver, IntentFilter(Intent.ACTION_WALLPAPER_CHANGED))
|
||||
}
|
||||
|
||||
override fun onCreateInputView(): View {
|
||||
@@ -318,6 +331,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unregisterReceiver(wallpaperChangeReceiver)
|
||||
FlorisImeServiceReference = WeakReference(null)
|
||||
inputWindowView = null
|
||||
}
|
||||
@@ -448,6 +462,10 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
flogInfo(LogTopic.IMS_EVENTS) { "Creating inline suggestions request" }
|
||||
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
|
||||
if (stylesBundle == null) {
|
||||
flogWarning(LogTopic.IMS_EVENTS) { "Failed to retrieve inline suggestions style bundle" }
|
||||
return null
|
||||
}
|
||||
val spec = InlinePresentationSpec.Builder(
|
||||
InlineSuggestionUiSmallestSize,
|
||||
InlineSuggestionUiBiggestSize,
|
||||
@@ -495,7 +513,9 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
outInsets.visibleTopInsets = visibleTopY
|
||||
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION
|
||||
val left = 0
|
||||
val top = if (keyboardManager.activeState.isBottomSheetShowing()) { 0 } else {
|
||||
val top = if (keyboardManager.activeState.isBottomSheetShowing() || keyboardManager.activeState.isSubtypeSelectionShowing()) {
|
||||
0
|
||||
} else {
|
||||
visibleTopY - if (needAdditionalOverlay) FlorisImeSizing.Static.smartbarHeightPx else 0
|
||||
}
|
||||
val right = inputViewSize.width
|
||||
@@ -508,7 +528,8 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
*/
|
||||
private fun updateSoftInputWindowLayoutParameters() {
|
||||
val w = window?.window ?: return
|
||||
WindowCompat.setDecorFitsSystemWindows(w, true)
|
||||
// TODO: Verify that this doesn't give us a padding problem
|
||||
WindowCompat.setDecorFitsSystemWindows(w, false)
|
||||
ViewUtils.updateLayoutHeightOf(w, WindowManager.LayoutParams.MATCH_PARENT)
|
||||
val layoutHeight = if (isFullscreenUiMode) {
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
@@ -545,19 +566,18 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
ProvideKeyboardRowBaseHeight {
|
||||
CompositionLocalProvider(LocalInputFeedbackController provides inputFeedbackController) {
|
||||
FlorisImeTheme {
|
||||
// Do not apply system bar padding here yet, we want to draw it ourselves
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
if (!(isFullscreenUiMode && isExtractUiShown)) {
|
||||
Box(
|
||||
DevtoolsOverlay(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
) {
|
||||
DevtoolsOverlay(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
)
|
||||
}
|
||||
ImeUi()
|
||||
SystemUiIme()
|
||||
}
|
||||
SystemUiIme()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,25 +588,22 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
@Composable
|
||||
private fun ImeUi() {
|
||||
val state by keyboardManager.activeState.collectAsState()
|
||||
val keyboardStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.Keyboard,
|
||||
mode = state.inputShiftState.value,
|
||||
)
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
SideEffect {
|
||||
if (keyboardManager.activeState.layoutDirection != layoutDirection) {
|
||||
keyboardManager.activeState.layoutDirection = layoutDirection
|
||||
}
|
||||
LaunchedEffect(layoutDirection) {
|
||||
keyboardManager.activeState.layoutDirection = layoutDirection
|
||||
}
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
SnyggSurface(
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.Window.elementName,
|
||||
attributes = mapOf(FlorisImeUi.Attr.ShiftState to state.inputShiftState.attrName()),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.onGloballyPositioned { coords -> inputViewSize = coords.size }
|
||||
.onGloballyPositioned { coords -> inputViewSize = coords.size },
|
||||
clickAndSemanticsModifier = Modifier
|
||||
// Do not remove below line or touch input may get stuck
|
||||
.pointerInteropFilter { false },
|
||||
style = keyboardStyle,
|
||||
supportsBackgroundImage = true,
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val bottomOffset by if (configuration.isOrientationPortrait()) {
|
||||
@@ -598,18 +615,18 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
// Apply system bars padding here (we already drew our keyboard background)
|
||||
.safeDrawingPadding()
|
||||
// FIXME: removing this fixes the Smartbar sizing but breaks one-handed-mode
|
||||
//.height(IntrinsicSize.Min)
|
||||
.padding(bottom = bottomOffset),
|
||||
) {
|
||||
val oneHandedMode by prefs.keyboard.oneHandedMode.observeAsState()
|
||||
val oneHandedModeEnabled by prefs.keyboard.oneHandedModeEnabled.observeAsState()
|
||||
val oneHandedModeScaleFactor by prefs.keyboard.oneHandedModeScaleFactor.observeAsState()
|
||||
val keyboardWeight = when {
|
||||
oneHandedMode == OneHandedMode.OFF || configuration.isOrientationLandscape() -> 1f
|
||||
!oneHandedModeEnabled || configuration.isOrientationLandscape() -> 1f
|
||||
else -> oneHandedModeScaleFactor / 100f
|
||||
}
|
||||
if (oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
|
||||
if (oneHandedModeEnabled && oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
|
||||
OneHandedPanel(
|
||||
panelSide = OneHandedMode.START,
|
||||
weight = 1f - keyboardWeight,
|
||||
@@ -628,7 +645,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
|
||||
if (oneHandedModeEnabled && oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
|
||||
OneHandedPanel(
|
||||
panelSide = OneHandedMode.END,
|
||||
weight = 1f - keyboardWeight,
|
||||
@@ -680,12 +697,22 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
|
||||
FlorisImeTheme {
|
||||
BottomSheetHostUi(
|
||||
isShowing = state.isBottomSheetShowing(),
|
||||
isShowing = state.isBottomSheetShowing() || state.isSubtypeSelectionShowing(),
|
||||
onHide = {
|
||||
keyboardManager.activeState.isActionsEditorVisible = false
|
||||
if (state.isBottomSheetShowing()) {
|
||||
keyboardManager.activeState.isActionsEditorVisible = false
|
||||
}
|
||||
if (state.isSubtypeSelectionShowing()) {
|
||||
keyboardManager.activeState.isSubtypeSelectionVisible = false
|
||||
}
|
||||
},
|
||||
) {
|
||||
QuickActionsEditorPanel()
|
||||
if (state.isBottomSheetShowing()) {
|
||||
QuickActionsEditorPanel()
|
||||
}
|
||||
if (state.isSubtypeSelectionShowing()) {
|
||||
SelectSubtypePanel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,44 +746,38 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
@Composable
|
||||
fun Content() {
|
||||
val context = LocalContext.current
|
||||
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
|
||||
FlorisImeTheme {
|
||||
val layoutStyle = FlorisImeTheme.style.get(FlorisImeUi.ExtractedLandscapeInputLayout)
|
||||
val fieldStyle = FlorisImeTheme.style.get(FlorisImeUi.ExtractedLandscapeInputField)
|
||||
val actionStyle = FlorisImeTheme.style.get(FlorisImeUi.ExtractedLandscapeInputAction)
|
||||
val activeEditorInfo by editorInstance.activeInfoFlow.collectAsState()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.snyggBackground(context, layoutStyle, FlorisImeTheme.fallbackSurfaceColor()),
|
||||
) {
|
||||
Row(
|
||||
SnyggBox(FlorisImeUi.ExtractedLandscapeInputLayout.elementName) {
|
||||
SnyggRow(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val fieldColor = fieldStyle.foreground.solidColor(context, FlorisImeTheme.fallbackContentColor())
|
||||
AndroidView(
|
||||
SnyggBox(FlorisImeUi.ExtractedLandscapeInputLayout.elementName,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.snyggShadow(fieldStyle)
|
||||
.snyggBorder(context, fieldStyle)
|
||||
.snyggBackground(context, fieldStyle),
|
||||
factory = { extractEditText },
|
||||
update = { view ->
|
||||
view.background = null
|
||||
view.backgroundTintList = null
|
||||
view.foregroundTintList = null
|
||||
view.setTextColor(fieldColor.toArgb())
|
||||
view.setHintTextColor(fieldColor.copy(fieldColor.alpha * 0.6f).toArgb())
|
||||
view.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_SP,
|
||||
fieldStyle.fontSize.spSize(default = 16.sp).value,
|
||||
)
|
||||
},
|
||||
)
|
||||
FlorisButton(
|
||||
.weight(1f),
|
||||
) {
|
||||
val fieldStyle = rememberSnyggThemeQuery(FlorisImeUi.ExtractedLandscapeInputField.elementName)
|
||||
val foreground = fieldStyle.foreground()
|
||||
AndroidView(
|
||||
factory = { extractEditText },
|
||||
update = { view ->
|
||||
view.background = null
|
||||
view.backgroundTintList = null
|
||||
view.foregroundTintList = null
|
||||
view.setTextColor(foreground.toArgb())
|
||||
view.setHintTextColor(foreground.copy(foreground.alpha * 0.6f).toArgb())
|
||||
view.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_SP,
|
||||
fieldStyle.fontSize(default = 16.sp).value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
SnyggButton(
|
||||
FlorisImeUi.ExtractedLandscapeInputAction.elementName,
|
||||
onClick = {
|
||||
if (activeEditorInfo.extractedActionId != 0) {
|
||||
currentInputConnection?.performEditorAction(activeEditorInfo.extractedActionId)
|
||||
@@ -765,15 +786,13 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
text = activeEditorInfo.extractedActionLabel
|
||||
?: getTextForImeAction(activeEditorInfo.imeOptions.action.toInt())
|
||||
?: "ACTION",
|
||||
shape = actionStyle.shape.shape(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = actionStyle.background.solidColor(context, FlorisImeTheme.fallbackContentColor()),
|
||||
contentColor = actionStyle.foreground.solidColor(context, FlorisImeTheme.fallbackSurfaceColor()),
|
||||
),
|
||||
)
|
||||
) {
|
||||
SnyggText(
|
||||
text = activeEditorInfo.extractedActionLabel
|
||||
?: getTextForImeAction(activeEditorInfo.imeOptions.action.toInt())
|
||||
?: "ACTION",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ package dev.patrickgold.florisboard.app
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
|
||||
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
|
||||
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
@@ -40,58 +41,34 @@ import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickAction
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionJsonConfig
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
|
||||
import dev.patrickgold.florisboard.lib.compose.ColorPreferenceSerializer
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
import dev.patrickgold.florisboard.lib.util.VersionName
|
||||
import dev.patrickgold.jetpref.datastore.JetPref
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceType
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import kotlinx.serialization.encodeToString
|
||||
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.isOrientationPortrait
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import org.florisboard.lib.color.DEFAULT_GREEN
|
||||
|
||||
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
|
||||
|
||||
class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
val advanced = Advanced()
|
||||
inner class Advanced {
|
||||
val settingsTheme = enum(
|
||||
key = "advanced__settings_theme",
|
||||
default = AppTheme.AUTO,
|
||||
)
|
||||
val useMaterialYou = boolean(
|
||||
key = "advanced__use_material_you",
|
||||
default = true,
|
||||
)
|
||||
val settingsLanguage = string(
|
||||
key = "advanced__settings_language",
|
||||
default = "auto",
|
||||
)
|
||||
val showAppIcon = boolean(
|
||||
key = "advanced__show_app_icon",
|
||||
default = true,
|
||||
)
|
||||
val incognitoMode = enum(
|
||||
key = "advanced__incognito_mode",
|
||||
default = IncognitoMode.DYNAMIC_ON_OFF,
|
||||
)
|
||||
// Internal pref
|
||||
val forceIncognitoModeFromDynamic = boolean(
|
||||
key = "advanced__force_incognito_mode_from_dynamic",
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
||||
val clipboard = Clipboard()
|
||||
inner class Clipboard {
|
||||
val useInternalClipboard = boolean(
|
||||
@@ -138,6 +115,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "clipboard__clear_primary_clip_deletes_last_item",
|
||||
default = true,
|
||||
)
|
||||
val suggestionEnabled = boolean(
|
||||
key = "clipboard__suggestion_enabled",
|
||||
default = true,
|
||||
)
|
||||
val suggestionTimeout = int(
|
||||
key = "clipboard__suggestion_timeout",
|
||||
default = 60,
|
||||
)
|
||||
}
|
||||
|
||||
val correction = Correction()
|
||||
@@ -166,10 +151,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "devtools__enabled",
|
||||
default = false,
|
||||
)
|
||||
val showLastLayoutComputation = boolean(
|
||||
key = "devtools__show_last_layout_computation",
|
||||
default = false,
|
||||
)
|
||||
val showPrimaryClip = boolean(
|
||||
key = "devtools__show_primary_clip",
|
||||
default = false,
|
||||
@@ -502,7 +483,11 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
)
|
||||
val oneHandedMode = enum(
|
||||
key = "keyboard__one_handed_mode",
|
||||
default = OneHandedMode.OFF,
|
||||
default = OneHandedMode.END,
|
||||
)
|
||||
val oneHandedModeEnabled = boolean(
|
||||
key = "keyboard__one_handed_mode_enabled",
|
||||
default = false,
|
||||
)
|
||||
val oneHandedModeScaleFactor = int(
|
||||
key = "keyboard__one_handed_mode_scale_factor",
|
||||
@@ -574,14 +559,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
@Composable
|
||||
fun fontSizeMultiplier(): Float {
|
||||
val configuration = LocalConfiguration.current
|
||||
val oneHandedMode by oneHandedMode.observeAsState()
|
||||
val oneHandedModeEnabled by oneHandedModeEnabled.observeAsState()
|
||||
val oneHandedModeFactor by oneHandedModeScaleFactor.observeAsTransformingState { it / 100.0f }
|
||||
val fontSizeMultiplierBase by if (configuration.isOrientationPortrait()) {
|
||||
fontSizeMultiplierPortrait
|
||||
} else {
|
||||
fontSizeMultiplierLandscape
|
||||
}.observeAsTransformingState { it / 100.0f }
|
||||
val fontSizeMultiplier = fontSizeMultiplierBase * if (oneHandedMode != OneHandedMode.OFF && configuration.isOrientationPortrait()) {
|
||||
val fontSizeMultiplier = fontSizeMultiplierBase * if (oneHandedModeEnabled && configuration.isOrientationPortrait()) {
|
||||
oneHandedModeFactor
|
||||
} else {
|
||||
1.0f
|
||||
@@ -596,6 +581,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "localization__display_language_names_in",
|
||||
default = DisplayLanguageNamesIn.SYSTEM_LOCALE,
|
||||
)
|
||||
val displayKeyboardLabelsInSubtypeLanguage = boolean(
|
||||
key = "localization__display_keyboard_labels_in_subtype_language",
|
||||
default = false,
|
||||
)
|
||||
val activeSubtypeId = long(
|
||||
key = "localization__active_subtype_id",
|
||||
default = Subtype.DEFAULT.id,
|
||||
@@ -606,6 +595,30 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
)
|
||||
}
|
||||
|
||||
val other = Other()
|
||||
inner class Other {
|
||||
val settingsTheme = enum(
|
||||
key = "other__settings_theme",
|
||||
default = AppTheme.AUTO,
|
||||
)
|
||||
val accentColor = custom(
|
||||
key = "other__accent_color",
|
||||
default = when (AndroidVersion.ATLEAST_API31_S) {
|
||||
true -> Color.Unspecified
|
||||
false -> DEFAULT_GREEN
|
||||
},
|
||||
serializer = ColorPreferenceSerializer,
|
||||
)
|
||||
val settingsLanguage = string(
|
||||
key = "other__settings_language",
|
||||
default = "auto",
|
||||
)
|
||||
val showAppIcon = boolean(
|
||||
key = "other__show_app_icon",
|
||||
default = true,
|
||||
)
|
||||
}
|
||||
|
||||
val smartbar = Smartbar()
|
||||
inner class Smartbar {
|
||||
val enabled = boolean(
|
||||
@@ -682,13 +695,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "suggestion__block_possibly_offensive",
|
||||
default = true,
|
||||
)
|
||||
val clipboardContentEnabled = boolean(
|
||||
key = "suggestion__clipboard_content_enabled",
|
||||
default = true,
|
||||
val incognitoMode = enum(
|
||||
key = "suggestion__incognito_mode",
|
||||
default = IncognitoMode.DYNAMIC_ON_OFF,
|
||||
)
|
||||
val clipboardContentTimeout = int(
|
||||
key = "suggestion__clipboard_content_timeout",
|
||||
default = 60,
|
||||
// Internal pref
|
||||
val forceIncognitoModeFromDynamic = boolean(
|
||||
key = "suggestion__force_incognito_mode_from_dynamic",
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -708,6 +722,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
default = extCoreTheme("floris_night"),
|
||||
serializer = ExtensionComponentName.Serializer,
|
||||
)
|
||||
val accentColor = custom(
|
||||
key = "theme__accent_color",
|
||||
default = when (AndroidVersion.ATLEAST_API31_S) {
|
||||
true -> Color.Unspecified
|
||||
false -> DEFAULT_GREEN
|
||||
},
|
||||
serializer = ColorPreferenceSerializer,
|
||||
)
|
||||
//val sunriseTime = localTime(
|
||||
// key = "theme__sunrise_time",
|
||||
// default = LocalTime.of(6, 0),
|
||||
@@ -716,9 +738,9 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
// key = "theme__sunset_time",
|
||||
// default = LocalTime.of(18, 0),
|
||||
//)
|
||||
val editorDisplayColorsAs = enum(
|
||||
key = "theme__editor_display_colors_as",
|
||||
default = DisplayColorsAs.HEX8,
|
||||
val editorColorRepresentation = enum(
|
||||
key = "theme__editor_color_representation",
|
||||
default = ColorRepresentation.HEX,
|
||||
)
|
||||
val editorDisplayKbdAfterDialogs = enum(
|
||||
key = "theme__editor_display_kbd_after_dialogs",
|
||||
@@ -732,34 +754,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
|
||||
override fun migrate(entry: PreferenceMigrationEntry): PreferenceMigrationEntry {
|
||||
return when (entry.key) {
|
||||
// Migrate enums from their lowercase to uppercase representation
|
||||
// Keep migration rule until: 0.5 dev cycle
|
||||
"advanced__settings_theme", "gestures__swipe_up", "gestures__swipe_down", "gestures__swipe_left",
|
||||
"gestures__swipe_right", "gestures__space_bar_swipe_up", "gestures__space_bar_swipe_left",
|
||||
"gestures__space_bar_swipe_right", "gestures__space_bar_long_press", "gestures__delete_key_swipe_left",
|
||||
"gestures__delete_key_long_press", "keyboard__hinted_number_row_mode", "keyboard__hinted_symbols_mode",
|
||||
"keyboard__utility_key_action", "keyboard__one_handed_mode", "keyboard__landscape_input_ui_mode",
|
||||
"localization__display_language_names_in", "smartbar__primary_actions_row_type",
|
||||
"smartbar__secondary_actions_placement", "smartbar__secondary_actions_row_type", "spelling__language_mode",
|
||||
"suggestion__display_mode", "theme__mode", "theme__editor_display_colors_as",
|
||||
"theme__editor_display_kbd_after_dialogs", "theme__editor_level",
|
||||
-> {
|
||||
entry.transform(rawValue = entry.rawValue.uppercase())
|
||||
}
|
||||
|
||||
// Migrate old private mode force flag as this is a sensitive preference
|
||||
// Keep migration rule until: 0.5 dev cycle
|
||||
"advanced__force_private_mode" -> {
|
||||
if (entry.rawValue.toBoolean()) {
|
||||
entry.transform(
|
||||
type = PreferenceType.string(),
|
||||
key = "advanced__incognito_mode",
|
||||
rawValue = IncognitoMode.FORCE_ON.toString(),
|
||||
)
|
||||
} else {
|
||||
entry.reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate media prefs to emoji prefs
|
||||
// Keep migration rule until: 0.6 dev cycle
|
||||
@@ -774,19 +768,80 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
"media__emoji_recently_used_max_size" -> {
|
||||
entry.transform(key = "emoji__history_recent_max_size")
|
||||
}
|
||||
"media__emoji_preferred_skin_tone" -> {
|
||||
|
||||
// Migrate advanced prefs to other prefs
|
||||
// Keep migration rules until: 0.7 dev cycle
|
||||
"advanced__settings_theme" -> {
|
||||
entry.transform(key = "other__settings_theme")
|
||||
}
|
||||
"advanced__accent_color" -> {
|
||||
entry.transform(key = "other__accent_color")
|
||||
}
|
||||
"advanced__settings_language" -> {
|
||||
entry.transform(key = "other__settings_language")
|
||||
}
|
||||
"advanced__show_app_icon" -> {
|
||||
entry.transform(key = "other__show_app_icon")
|
||||
}
|
||||
"advanced__incognito_mode" -> {
|
||||
entry.transform(key = "suggestion__incognito_mode")
|
||||
}
|
||||
"advanced__force_incognito_mode_from_dynamic" -> {
|
||||
entry.transform(key = "suggestion__force_incognito_mode_from_dynamic")
|
||||
}
|
||||
// Migrate clipboard suggestion prefs to clipboard
|
||||
// Keep migration rules until: 0.7 dev cycle
|
||||
"suggestion__clipboard_content_enabled" -> {
|
||||
entry.transform(key = "clipboard__suggestion_enabled")
|
||||
}
|
||||
"suggestion__clipboard_content_timeout" -> {
|
||||
entry.transform(key = "clipboard__suggestion_timeout")
|
||||
}
|
||||
|
||||
//Migrate one hand mode prefs keep until: 0.7 dev cycle
|
||||
"keyboard__one_handed_mode" -> {
|
||||
if (entry.rawValue != "OFF") {
|
||||
val prefs by florisPreferenceModel()
|
||||
prefs.keyboard.oneHandedModeEnabled.set(true)
|
||||
entry.keepAsIs()
|
||||
} else {
|
||||
entry.reset()
|
||||
}
|
||||
}
|
||||
"smartbar__action_arrangement" -> {
|
||||
val arrangement = QuickActionJsonConfig.decodeFromString<QuickActionArrangement>(entry.rawValue)
|
||||
var newArrangement = arrangement.copy(
|
||||
dynamicActions = arrangement.dynamicActions.map { action ->
|
||||
if (action is QuickAction.InsertKey && action.data.code == KeyCode.COMPACT_LAYOUT_TO_RIGHT) {
|
||||
action.copy(TextKeyData.TOGGLE_COMPACT_LAYOUT)
|
||||
} else {
|
||||
action
|
||||
}
|
||||
}
|
||||
)
|
||||
if (QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH) !in newArrangement) {
|
||||
newArrangement = newArrangement.copy(
|
||||
dynamicActions = newArrangement.dynamicActions.plus(QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH))
|
||||
)
|
||||
}
|
||||
val json = QuickActionJsonConfig.encodeToString(newArrangement)
|
||||
entry.transform(rawValue = json)
|
||||
}
|
||||
|
||||
// Migrate theme editor fine-tuning
|
||||
// Keep migration rule until: 0.6 dev cycle
|
||||
"theme__editor_display_colors_as" -> {
|
||||
val colorRepresentation = when (entry.rawValue) {
|
||||
"RGBA" -> ColorRepresentation.RGB
|
||||
else -> ColorRepresentation.HEX
|
||||
}
|
||||
entry.transform(
|
||||
key = "emoji__preferred_skin_tone",
|
||||
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
|
||||
)
|
||||
}
|
||||
"media__emoji_preferred_hair_style" -> {
|
||||
entry.transform(
|
||||
key = "emoji__preferred_hair_style",
|
||||
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
|
||||
key = "theme__editor_color_representation",
|
||||
rawValue = colorRepresentation.name,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Default: keep entry
|
||||
else -> entry.keepAsIs()
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ package dev.patrickgold.florisboard.app
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
|
||||
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
|
||||
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
|
||||
@@ -41,9 +41,9 @@ import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreferenceEntry
|
||||
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
|
||||
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@@ -58,19 +58,19 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
)
|
||||
entry(
|
||||
key = AppTheme.AUTO_AMOLED,
|
||||
label = stringRes(R.string.pref__advanced__settings_theme__auto_amoled),
|
||||
label = stringRes(R.string.pref__other__settings_theme__auto_amoled),
|
||||
)
|
||||
entry(
|
||||
key = AppTheme.LIGHT,
|
||||
label = stringRes(R.string.pref__advanced__settings_theme__light),
|
||||
label = stringRes(R.string.pref__other__settings_theme__light),
|
||||
)
|
||||
entry(
|
||||
key = AppTheme.DARK,
|
||||
label = stringRes(R.string.pref__advanced__settings_theme__dark),
|
||||
label = stringRes(R.string.pref__other__settings_theme__dark),
|
||||
)
|
||||
entry(
|
||||
key = AppTheme.AMOLED_DARK,
|
||||
label = stringRes(R.string.pref__advanced__settings_theme__amoled_dark),
|
||||
label = stringRes(R.string.pref__other__settings_theme__amoled_dark),
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -102,18 +102,24 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
)
|
||||
}
|
||||
},
|
||||
DisplayColorsAs::class to DEFAULT to {
|
||||
ColorRepresentation::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
key = DisplayColorsAs.HEX8,
|
||||
label = stringRes(R.string.enum__display_colors_as__hex8),
|
||||
key = ColorRepresentation.HEX,
|
||||
label = stringRes(R.string.enum__color_representation__hex),
|
||||
description = stringRes(R.string.general__example_given).curlyFormat("example" to "#4caf50ff"),
|
||||
showDescriptionOnlyIfSelected = true,
|
||||
)
|
||||
entry(
|
||||
key = DisplayColorsAs.RGBA,
|
||||
label = stringRes(R.string.enum__display_colors_as__rgba),
|
||||
description = stringRes(R.string.general__example_given).curlyFormat("example" to "rgba(76,175,80,1.0)"),
|
||||
key = ColorRepresentation.RGB,
|
||||
label = stringRes(R.string.enum__color_representation__rgb),
|
||||
description = stringRes(R.string.general__example_given).curlyFormat("example" to "rgba(76, 175, 80, 1.0)"),
|
||||
showDescriptionOnlyIfSelected = true,
|
||||
)
|
||||
entry(
|
||||
key = ColorRepresentation.HSV,
|
||||
label = stringRes(R.string.enum__color_representation__hsv),
|
||||
description = stringRes(R.string.general__example_given).curlyFormat("example" to "hsva(122, 56, 68, 1.0)"),
|
||||
showDescriptionOnlyIfSelected = true,
|
||||
)
|
||||
}
|
||||
@@ -376,10 +382,6 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
},
|
||||
OneHandedMode::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
key = OneHandedMode.OFF,
|
||||
label = stringRes(R.string.enum__one_handed_mode__off),
|
||||
)
|
||||
entry(
|
||||
key = OneHandedMode.START,
|
||||
label = stringRes(R.string.enum__one_handed_mode__start),
|
||||
@@ -542,6 +544,10 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
key = SwipeAction.SHOW_INPUT_METHOD_PICKER,
|
||||
label = stringRes(R.string.enum__swipe_action__show_input_method_picker),
|
||||
)
|
||||
entry(
|
||||
key = SwipeAction.SHOW_SUBTYPE_PICKER,
|
||||
label = "Show subtype picker"
|
||||
)
|
||||
entry(
|
||||
key = SwipeAction.SWITCH_TO_PREV_SUBTYPE,
|
||||
label = stringRes(R.string.enum__swipe_action__switch_to_prev_subtype),
|
||||
|
||||
@@ -88,17 +88,17 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
prefs.advanced.settingsTheme.observe(this) {
|
||||
prefs.other.settingsTheme.observe(this) {
|
||||
appTheme = it
|
||||
}
|
||||
prefs.advanced.settingsLanguage.observe(this) {
|
||||
prefs.other.settingsLanguage.observe(this) {
|
||||
val config = Configuration(resources.configuration)
|
||||
val locale = if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it)
|
||||
config.setLocale(locale.base)
|
||||
resourcesContext = createConfigurationContext(config)
|
||||
}
|
||||
if (AndroidVersion.ATMOST_API28_P) {
|
||||
prefs.advanced.showAppIcon.observe(this) {
|
||||
prefs.other.showAppIcon.observe(this) {
|
||||
showAppIcon = it
|
||||
}
|
||||
}
|
||||
@@ -117,8 +117,7 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
setContent {
|
||||
ProvideLocalizedResources(resourcesContext) {
|
||||
val useMaterialYou by prefs.advanced.useMaterialYou.observeAsState()
|
||||
FlorisAppTheme(theme = appTheme, isMaterialYouAware = useMaterialYou) {
|
||||
FlorisAppTheme(theme = appTheme) {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
AppContent()
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import dev.patrickgold.florisboard.app.settings.HomeScreen
|
||||
import dev.patrickgold.florisboard.app.settings.about.AboutScreen
|
||||
import dev.patrickgold.florisboard.app.settings.about.ProjectLicenseScreen
|
||||
import dev.patrickgold.florisboard.app.settings.about.ThirdPartyLicensesScreen
|
||||
import dev.patrickgold.florisboard.app.settings.advanced.AdvancedScreen
|
||||
import dev.patrickgold.florisboard.app.settings.advanced.OtherScreen
|
||||
import dev.patrickgold.florisboard.app.settings.advanced.BackupScreen
|
||||
import dev.patrickgold.florisboard.app.settings.advanced.RestoreScreen
|
||||
import dev.patrickgold.florisboard.app.settings.clipboard.ClipboardScreen
|
||||
@@ -110,9 +110,9 @@ object Routes {
|
||||
|
||||
const val Media = "settings/media"
|
||||
|
||||
const val Advanced = "settings/advanced"
|
||||
const val Backup = "settings/advanced/backup"
|
||||
const val Restore = "settings/advanced/restore"
|
||||
const val Other = "settings/other"
|
||||
const val Backup = "settings/other/backup"
|
||||
const val Restore = "settings/other/restore"
|
||||
|
||||
const val About = "settings/about"
|
||||
const val ProjectLicense = "settings/about/project-license"
|
||||
@@ -239,7 +239,7 @@ object Routes {
|
||||
|
||||
composableWithDeepLink(Settings.Media) { MediaScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Advanced) { AdvancedScreen() }
|
||||
composableWithDeepLink(Settings.Other) { OtherScreen() }
|
||||
composableWithDeepLink(Settings.Backup) { BackupScreen() }
|
||||
composableWithDeepLink(Settings.Restore) { RestoreScreen() }
|
||||
|
||||
|
||||
@@ -17,19 +17,22 @@
|
||||
package dev.patrickgold.florisboard.app.apptheme
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
import dev.patrickgold.florisboard.app.AppTheme
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.color.ColorMappings
|
||||
|
||||
/*private val AmoledDarkColorPalette = darkColorScheme(
|
||||
primary = Green500,
|
||||
@@ -70,170 +73,66 @@ private val LightColorPalette = lightColorScheme(
|
||||
*/
|
||||
)*/
|
||||
|
||||
private val lightScheme = lightColorScheme(
|
||||
primary = primaryLight,
|
||||
onPrimary = onPrimaryLight,
|
||||
primaryContainer = primaryContainerLight,
|
||||
onPrimaryContainer = onPrimaryContainerLight,
|
||||
secondary = secondaryLight,
|
||||
onSecondary = onSecondaryLight,
|
||||
secondaryContainer = secondaryContainerLight,
|
||||
onSecondaryContainer = onSecondaryContainerLight,
|
||||
tertiary = tertiaryLight,
|
||||
onTertiary = onTertiaryLight,
|
||||
tertiaryContainer = tertiaryContainerLight,
|
||||
onTertiaryContainer = onTertiaryContainerLight,
|
||||
error = errorLight,
|
||||
onError = onErrorLight,
|
||||
errorContainer = errorContainerLight,
|
||||
onErrorContainer = onErrorContainerLight,
|
||||
background = backgroundLight,
|
||||
onBackground = onBackgroundLight,
|
||||
surface = surfaceLight,
|
||||
onSurface = onSurfaceLight,
|
||||
surfaceVariant = surfaceVariantLight,
|
||||
onSurfaceVariant = onSurfaceVariantLight,
|
||||
outline = outlineLight,
|
||||
outlineVariant = outlineVariantLight,
|
||||
scrim = scrimLight,
|
||||
inverseSurface = inverseSurfaceLight,
|
||||
inverseOnSurface = inverseOnSurfaceLight,
|
||||
inversePrimary = inversePrimaryLight,
|
||||
surfaceDim = surfaceDimLight,
|
||||
surfaceBright = surfaceBrightLight,
|
||||
surfaceContainerLowest = surfaceContainerLowestLight,
|
||||
surfaceContainerLow = surfaceContainerLowLight,
|
||||
surfaceContainer = surfaceContainerLight,
|
||||
surfaceContainerHigh = surfaceContainerHighLight,
|
||||
surfaceContainerHighest = surfaceContainerHighestLight,
|
||||
)
|
||||
|
||||
private val darkScheme = darkColorScheme(
|
||||
primary = primaryDark,
|
||||
onPrimary = onPrimaryDark,
|
||||
primaryContainer = primaryContainerDark,
|
||||
onPrimaryContainer = onPrimaryContainerDark,
|
||||
secondary = secondaryDark,
|
||||
onSecondary = onSecondaryDark,
|
||||
secondaryContainer = secondaryContainerDark,
|
||||
onSecondaryContainer = onSecondaryContainerDark,
|
||||
tertiary = tertiaryDark,
|
||||
onTertiary = onTertiaryDark,
|
||||
tertiaryContainer = tertiaryContainerDark,
|
||||
onTertiaryContainer = onTertiaryContainerDark,
|
||||
error = errorDark,
|
||||
onError = onErrorDark,
|
||||
errorContainer = errorContainerDark,
|
||||
onErrorContainer = onErrorContainerDark,
|
||||
background = backgroundDark,
|
||||
onBackground = onBackgroundDark,
|
||||
surface = surfaceDark,
|
||||
onSurface = onSurfaceDark,
|
||||
surfaceVariant = surfaceVariantDark,
|
||||
onSurfaceVariant = onSurfaceVariantDark,
|
||||
outline = outlineDark,
|
||||
outlineVariant = outlineVariantDark,
|
||||
scrim = scrimDark,
|
||||
inverseSurface = inverseSurfaceDark,
|
||||
inverseOnSurface = inverseOnSurfaceDark,
|
||||
inversePrimary = inversePrimaryDark,
|
||||
surfaceDim = surfaceDimDark,
|
||||
surfaceBright = surfaceBrightDark,
|
||||
surfaceContainerLowest = surfaceContainerLowestDark,
|
||||
surfaceContainerLow = surfaceContainerLowDark,
|
||||
surfaceContainer = surfaceContainerDark,
|
||||
surfaceContainerHigh = surfaceContainerHighDark,
|
||||
surfaceContainerHighest = surfaceContainerHighestDark,
|
||||
)
|
||||
@Composable
|
||||
fun getColorScheme(
|
||||
context: Context,
|
||||
theme: AppTheme,
|
||||
): ColorScheme {
|
||||
val prefs by florisPreferenceModel()
|
||||
val accentColor by prefs.other.accentColor.observeAsState()
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
private val amoledScheme = darkScheme.copy(
|
||||
background = amoledDark,
|
||||
surface = amoledDark
|
||||
)
|
||||
return when (theme) {
|
||||
AppTheme.AUTO -> {
|
||||
if (isDark) {
|
||||
ColorMappings.dynamicDarkColorScheme(context, accentColor)
|
||||
} else {
|
||||
ColorMappings.dynamicLightColorScheme(context, accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
AppTheme.DARK -> {
|
||||
ColorMappings.dynamicDarkColorScheme(context, accentColor)
|
||||
}
|
||||
|
||||
AppTheme.LIGHT -> {
|
||||
ColorMappings.dynamicLightColorScheme(context, accentColor)
|
||||
}
|
||||
|
||||
AppTheme.AMOLED_DARK -> {
|
||||
ColorMappings.dynamicDarkColorScheme(context, accentColor).amoled()
|
||||
}
|
||||
|
||||
AppTheme.AUTO_AMOLED -> {
|
||||
if (isDark) {
|
||||
ColorMappings.dynamicDarkColorScheme(context, accentColor).amoled()
|
||||
} else {
|
||||
ColorMappings.dynamicLightColorScheme(context, accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ColorScheme.amoled(): ColorScheme {
|
||||
return this.copy(background = Color.Black, surface = Color.Black)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FlorisAppTheme(
|
||||
theme: AppTheme,
|
||||
isMaterialYouAware: Boolean,
|
||||
content: @Composable () -> Unit
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
|
||||
val colors = if (AndroidVersion.ATLEAST_API31_S) {
|
||||
when (theme) {
|
||||
AppTheme.AUTO -> when {
|
||||
isMaterialYouAware -> when {
|
||||
isSystemInDarkTheme() -> dynamicDarkColorScheme(LocalContext.current)
|
||||
else -> dynamicLightColorScheme(LocalContext.current)
|
||||
}
|
||||
|
||||
else -> {
|
||||
when {
|
||||
isSystemInDarkTheme() -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppTheme.AUTO_AMOLED -> when {
|
||||
isMaterialYouAware -> when {
|
||||
isSystemInDarkTheme() -> dynamicDarkColorScheme(LocalContext.current).copy(
|
||||
background = amoledDark,
|
||||
surface = amoledDark,
|
||||
)
|
||||
|
||||
else -> dynamicLightColorScheme(LocalContext.current)
|
||||
}
|
||||
|
||||
else -> {
|
||||
when {
|
||||
isSystemInDarkTheme() -> amoledScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppTheme.LIGHT -> when {
|
||||
isMaterialYouAware -> dynamicLightColorScheme(LocalContext.current)
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
AppTheme.DARK -> when {
|
||||
isMaterialYouAware -> dynamicDarkColorScheme(LocalContext.current)
|
||||
else -> darkScheme
|
||||
}
|
||||
|
||||
AppTheme.AMOLED_DARK -> when {
|
||||
isMaterialYouAware -> dynamicDarkColorScheme(LocalContext.current).copy(
|
||||
background = amoledDark,
|
||||
surface = amoledDark,
|
||||
)
|
||||
|
||||
else -> amoledScheme
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (theme) {
|
||||
AppTheme.AUTO -> when {
|
||||
isSystemInDarkTheme() -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
AppTheme.AUTO_AMOLED -> when {
|
||||
isSystemInDarkTheme() -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
AppTheme.LIGHT -> lightScheme
|
||||
AppTheme.DARK -> darkScheme
|
||||
AppTheme.AMOLED_DARK -> amoledScheme
|
||||
}
|
||||
}
|
||||
val colors = getColorScheme(
|
||||
context = LocalContext.current,
|
||||
theme = theme,
|
||||
)
|
||||
|
||||
val darkTheme =
|
||||
theme == AppTheme.DARK
|
||||
|| theme == AppTheme.AMOLED_DARK
|
||||
|| (theme == AppTheme.AUTO && isSystemInDarkTheme())
|
||||
|| (theme == AppTheme.AUTO_AMOLED && isSystemInDarkTheme())
|
||||
|| theme == AppTheme.AMOLED_DARK
|
||||
|| (theme == AppTheme.AUTO && isSystemInDarkTheme())
|
||||
|| (theme == AppTheme.AUTO_AMOLED && isSystemInDarkTheme())
|
||||
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -43,13 +44,17 @@ import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.editorInstance
|
||||
import dev.patrickgold.florisboard.ime.keyboard.CachedLayout
|
||||
import dev.patrickgold.florisboard.ime.keyboard.DebugLayoutComputationResult
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.SnyggMissingSchemaException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -61,15 +66,16 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val prefs by florisPreferenceModel()
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val themeManager by context.themeManager()
|
||||
|
||||
val devtoolsEnabled by prefs.devtools.enabled.observeAsState()
|
||||
val showPrimaryClip by prefs.devtools.showPrimaryClip.observeAsState()
|
||||
val showInputStateOverlay by prefs.devtools.showInputStateOverlay.observeAsState()
|
||||
val showLastLayoutComputation by prefs.devtools.showLastLayoutComputation.observeAsState()
|
||||
val showSpellingOverlay by prefs.devtools.showSpellingOverlay.observeAsState()
|
||||
val showInlineAutofillOverlay by prefs.devtools.showInlineAutofillOverlay.observeAsState()
|
||||
|
||||
val debugLayoutResult by keyboardManager.layoutManager.debugLayoutComputationResultFlow.collectAsState()
|
||||
val themeInfo by themeManager.activeThemeInfo.observeAsState()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides Color.White,
|
||||
@@ -82,8 +88,8 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
if (devtoolsEnabled && showInputStateOverlay) {
|
||||
DevtoolsInputStateOverlay()
|
||||
}
|
||||
if (devtoolsEnabled && showLastLayoutComputation || debugLayoutResult?.allLayoutsSuccess() == false) {
|
||||
DevtoolsLastLayoutComputationOverlay()
|
||||
if (debugLayoutResult?.allLayoutsSuccess() == false) {
|
||||
DevtoolsLastLayoutComputationOverlay(debugLayoutResult)
|
||||
}
|
||||
if (devtoolsEnabled && showSpellingOverlay) {
|
||||
DevtoolsSpellingOverlay()
|
||||
@@ -91,6 +97,10 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
if (devtoolsEnabled && showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
|
||||
DevtoolsInlineAutofillOverlay()
|
||||
}
|
||||
val loadFailure = themeInfo?.loadFailure
|
||||
if (loadFailure != null) {
|
||||
DevtoolsStylesheetFailedToLoadOverlay(loadFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,13 +147,9 @@ private fun DevtoolsInputStateOverlay() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DevtoolsLastLayoutComputationOverlay() {
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val debugLayoutResult by keyboardManager.layoutManager.debugLayoutComputationResultFlow.collectAsState()
|
||||
|
||||
private fun DevtoolsLastLayoutComputationOverlay(debugLayoutResult: DebugLayoutComputationResult?) {
|
||||
@Composable
|
||||
fun PrintResult(result: Result<CachedLayout>) {
|
||||
fun PrintResult(result: Result<CachedLayout?>) {
|
||||
if (result.isSuccess) {
|
||||
DevtoolsText(text = "loaded: ${result.getOrNull()?.name}")
|
||||
} else {
|
||||
@@ -229,6 +235,39 @@ private fun DevtoolsInlineAutofillOverlay() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DevtoolsStylesheetFailedToLoadOverlay(loadFailure: ThemeManager.LoadFailure) {
|
||||
DevtoolsOverlayBox(title = "Failed to load stylesheet, fell back to base style") {
|
||||
DevtoolsSubGroup(title = "Extension") {
|
||||
DevtoolsText(text = "id: ${loadFailure.extension.id}")
|
||||
DevtoolsText(text = "title: ${loadFailure.extension.title}")
|
||||
DevtoolsText(text = "version: ${loadFailure.extension.version}")
|
||||
}
|
||||
DevtoolsSubGroup(title = "Component") {
|
||||
DevtoolsText(text = "id: ${loadFailure.component.id}")
|
||||
DevtoolsText(text = "label: ${loadFailure.component.label}")
|
||||
DevtoolsText(text = "path: ${loadFailure.component.stylesheetPath()}")
|
||||
}
|
||||
val cause = loadFailure.cause
|
||||
DevtoolsSubGroup(title = "Cause") {
|
||||
DevtoolsText(text = "${cause.message}")
|
||||
}
|
||||
if (cause is SnyggMissingSchemaException) {
|
||||
DevtoolsSubGroup(title = "Explanation") {
|
||||
DevtoolsText(
|
||||
text = """
|
||||
It appears you’re trying to load a theme designed for FlorisBoard v0.4 (Snygg v1), which isn’t compatible with the latest release using Snygg v2.
|
||||
|
||||
If you are the theme author, please update your theme to support Snygg v2.
|
||||
|
||||
If you’re a user, please update your theme via the Addons Store. If an updated version isn’t available yet, please select one of the built-in themes during this transition period.
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DevtoolsOverlayBox(
|
||||
title: String,
|
||||
|
||||
@@ -73,12 +73,6 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
summary = stringRes(R.string.devtools__show_input_state_overlay__summary),
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.devtools.showLastLayoutComputation,
|
||||
title = "Show last layout computation",
|
||||
summary = "Show the last layout computation in a dialog",
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.devtools.showSpellingOverlay,
|
||||
title = stringRes(R.string.devtools__show_spelling_overlay__label),
|
||||
@@ -128,6 +122,13 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
onClick = { navController.navigate(Routes.Devtools.ExportDebugLog) },
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.glide.enabled,
|
||||
title = "prefs.glide.enabled (debug)",
|
||||
summaryOn = "This impacts your performance and may trigger the all keys invisible bug!",
|
||||
summaryOff = "Recommended to keep this off!",
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.devtools__group_android__title)) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Input
|
||||
import androidx.compose.material.icons.filled.Shop
|
||||
import androidx.compose.material.icons.outlined.FileDownload
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -29,6 +30,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
@@ -41,6 +44,41 @@ import dev.patrickgold.florisboard.lib.ext.generateUpdateUrl
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
|
||||
@Composable
|
||||
fun ImportExtensionBox(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 4.dp),
|
||||
text = stringRes(id = R.string.ext__home__info),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 6.dp),
|
||||
) {
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
context.launchUrl("https://${BuildConfig.FLADDONS_STORE_URL}/")
|
||||
},
|
||||
icon = Icons.Default.Shop,
|
||||
text = stringRes(id = R.string.ext__home__visit_store),
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, null))
|
||||
},
|
||||
icon = Icons.AutoMirrored.Filled.Input,
|
||||
text = stringRes(R.string.action__import),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateBox(extensionIndex: List<Extension>) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -81,14 +81,11 @@ fun ExtensionComponentView(
|
||||
when (component) {
|
||||
is ThemeExtensionComponent -> {
|
||||
val text = remember(
|
||||
component.authors, component.isNightTheme, component.isBorderless,
|
||||
component.isMaterialYouAware, component.stylesheetPath(),
|
||||
component.authors, component.isNightTheme, component.stylesheetPath(),
|
||||
) {
|
||||
buildString {
|
||||
appendLine("authors = ${component.authors}")
|
||||
appendLine("isNightTheme = ${component.isNightTheme}")
|
||||
appendLine("isBorderless = ${component.isBorderless}")
|
||||
appendLine("isMaterialYouAware = ${component.isMaterialYouAware}")
|
||||
append("stylesheetPath = ${component.stylesheetPath()}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.app.ext
|
||||
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Photo
|
||||
import androidx.compose.material.icons.filled.TextFields
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
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.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.MimeTypeFilter
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import org.florisboard.lib.android.query
|
||||
import org.florisboard.lib.android.readToFile
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.kotlin.io.parentDir
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
|
||||
const val FONTS = "fonts"
|
||||
const val IMAGES = "images"
|
||||
|
||||
val MIME_TYPES = mapOf(
|
||||
FONTS to listOf(
|
||||
// Source: https://www.alienfactory.co.uk/articles/mime-types-for-web-fonts-in-bedsheet#mimeTypes
|
||||
"font/*",
|
||||
"application/vnd.ms-fontobject", // .eot
|
||||
"application/font-woff", // .woff
|
||||
"application/x-font-truetype", // .ttf
|
||||
"application/x-font-opentype", // .otf
|
||||
),
|
||||
IMAGES to listOf(
|
||||
"image/*",
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = FlorisScreen {
|
||||
title = stringRes(R.string.ext__editor__files__title)
|
||||
|
||||
fun handleBackPress() {
|
||||
workspace.currentAction = null
|
||||
}
|
||||
|
||||
navigationIcon {
|
||||
FlorisIconButton(
|
||||
onClick = { handleBackPress() },
|
||||
icon = Icons.Default.Close,
|
||||
)
|
||||
}
|
||||
|
||||
content {
|
||||
val context = LocalContext.current
|
||||
var version by rememberSaveable { mutableIntStateOf(0) }
|
||||
val fontFiles = remember(version) {
|
||||
workspace.extDir.subDir(FONTS).listFiles { it.isFile }.orEmpty().asList()
|
||||
}
|
||||
val imageFiles = remember(version) {
|
||||
workspace.extDir.subDir(IMAGES).listFiles { it.isFile }.orEmpty().asList()
|
||||
}
|
||||
|
||||
var currentImportDest by remember { mutableStateOf<String?>(null) }
|
||||
var currentImportResult by remember { mutableStateOf<Result<Pair<File, String>>?>(null) }
|
||||
|
||||
val importLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent(),
|
||||
onResult = { uri ->
|
||||
currentImportResult = runCatching {
|
||||
checkNotNull(uri) { "" }
|
||||
val tempFile = context.cacheDir.subFile("temp_${UUID.randomUUID()}")
|
||||
context.contentResolver.readToFile(uri, tempFile)
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val types = MIME_TYPES[currentImportDest!!]!!
|
||||
checkNotNull(MimeTypeFilter.matches(mimeType, types.toTypedArray())) {
|
||||
"Given file mime type was '$mimeType', expected one of $types"
|
||||
}
|
||||
val fileName = context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME)).use { cursor ->
|
||||
if (cursor == null || !cursor.moveToFirst()) return@use null
|
||||
val name = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor.getString(name)
|
||||
}
|
||||
tempFile to fileName.orEmpty()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(currentImportResult) {
|
||||
val message = currentImportResult?.exceptionOrNull()?.message
|
||||
if (!message.isNullOrBlank()) {
|
||||
context.showLongToast(message)
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
handleBackPress()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FileList(title: String, icon: ImageVector, files: List<File>, onAdd: () -> Unit) {
|
||||
var dialogFile by remember { mutableStateOf<File?>(null) }
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = title,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
leadingContent = {
|
||||
Spacer(modifier = Modifier.width(24.dp))
|
||||
},
|
||||
trailingContent = {
|
||||
IconButton(
|
||||
onClick = onAdd,
|
||||
) {
|
||||
Icon(Icons.Default.Add, null)
|
||||
}
|
||||
},
|
||||
)
|
||||
for (file in files) {
|
||||
Preference(
|
||||
onClick = {
|
||||
dialogFile = file
|
||||
},
|
||||
icon = icon,
|
||||
title = file.name,
|
||||
)
|
||||
}
|
||||
|
||||
dialogFile?.let { file ->
|
||||
var fileNameInput by rememberSaveable { mutableStateOf(file.name) }
|
||||
JetPrefAlertDialog(
|
||||
title = stringRes(R.string.general__properties),
|
||||
confirmLabel = stringRes(R.string.action__apply),
|
||||
dismissLabel = stringRes(R.string.action__cancel),
|
||||
neutralLabel = stringRes(R.string.action__delete),
|
||||
allowOutsideDismissal = true,
|
||||
onNeutral = {
|
||||
if (file.delete()) {
|
||||
context.showShortToast("Successfully deleted")
|
||||
} else {
|
||||
context.showShortToast("Failed to delete")
|
||||
}
|
||||
dialogFile = null
|
||||
version++
|
||||
},
|
||||
onConfirm = {
|
||||
val newFile = file.parentFile!!.subFile(fileNameInput).canonicalFile
|
||||
if (newFile.parentFile != file.canonicalFile.parentFile) {
|
||||
context.showLongToast("Invalid file name!")
|
||||
return@JetPrefAlertDialog
|
||||
}
|
||||
if (newFile.exists()) {
|
||||
context.showShortToast("Filename already exists.")
|
||||
return@JetPrefAlertDialog
|
||||
}
|
||||
val success = file.renameTo(newFile)
|
||||
if (success) {
|
||||
context.showShortToast("Successfully renamed")
|
||||
} else {
|
||||
context.showShortToast("Failed to rename the file.")
|
||||
}
|
||||
dialogFile = null
|
||||
version++
|
||||
},
|
||||
onDismiss = {
|
||||
dialogFile = null
|
||||
},
|
||||
) {
|
||||
JetPrefTextField(
|
||||
labelText = stringRes(R.string.general__file_name),
|
||||
value = fileNameInput,
|
||||
onValueChange = { fileNameInput = it },
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileList(
|
||||
title = stringRes(R.string.ext__editor__files__type_fonts),
|
||||
icon = Icons.Default.TextFields,
|
||||
files = fontFiles,
|
||||
) {
|
||||
currentImportDest = FONTS
|
||||
importLauncher.launch("*/*")
|
||||
}
|
||||
|
||||
FileList(
|
||||
title = stringRes(R.string.ext__editor__files__type_images),
|
||||
icon = Icons.Default.Photo,
|
||||
files = imageFiles,
|
||||
) {
|
||||
currentImportDest = IMAGES
|
||||
importLauncher.launch("*/*")
|
||||
}
|
||||
|
||||
val dest = currentImportDest
|
||||
val result = currentImportResult?.getOrNull()
|
||||
if (dest != null && result != null) {
|
||||
var fileNameInput by rememberSaveable { mutableStateOf(result.second) }
|
||||
JetPrefAlertDialog(
|
||||
title = stringRes(R.string.action__import_file),
|
||||
confirmLabel = stringRes(R.string.action__add),
|
||||
onConfirm = {
|
||||
val fileName = fileNameInput.trim()
|
||||
val dir = workspace.extDir.subDir(dest)
|
||||
dir.mkdirs()
|
||||
val file = dir.subFile(fileName)
|
||||
if (file.parentDir != workspace.extDir.subDir(dest)) {
|
||||
context.showShortToast("Invalid file name")
|
||||
} else if (file.exists()) {
|
||||
context.showShortToast("File already exists")
|
||||
} else {
|
||||
val tempFile = result.first
|
||||
if (!tempFile.renameTo(file)) {
|
||||
context.showShortToast("Failed to rename file")
|
||||
tempFile.delete()
|
||||
}
|
||||
currentImportDest = null
|
||||
currentImportResult = null
|
||||
version++
|
||||
}
|
||||
},
|
||||
dismissLabel = stringRes(R.string.action__cancel),
|
||||
onDismiss = {
|
||||
val tempFile = result.first
|
||||
tempFile.delete()
|
||||
currentImportDest = null
|
||||
currentImportResult = null
|
||||
},
|
||||
) {
|
||||
JetPrefTextField(
|
||||
value = fileNameInput,
|
||||
onValueChange = { fileNameInput = it },
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import androidx.compose.material.icons.automirrored.outlined.LibraryBooks
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -49,6 +48,7 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.settings.advanced.RadioListItem
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DialogProperty
|
||||
import dev.patrickgold.florisboard.app.settings.theme.PrettyPrintConfig
|
||||
import dev.patrickgold.florisboard.app.settings.theme.ThemeEditorScreen
|
||||
import dev.patrickgold.florisboard.cacheManager
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
@@ -64,9 +64,9 @@ import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisInfoCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisUnsavedChangesDialog
|
||||
import dev.patrickgold.florisboard.lib.compose.Validation
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.Extension
|
||||
@@ -83,15 +83,17 @@ import dev.patrickgold.florisboard.lib.ext.validate
|
||||
import dev.patrickgold.florisboard.lib.io.FlorisRef
|
||||
import dev.patrickgold.florisboard.lib.io.ZipUtils
|
||||
import dev.patrickgold.florisboard.lib.rememberValidationResult
|
||||
import org.florisboard.lib.snygg.SnyggStylesheetJsonConfig
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
|
||||
import java.util.*
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import org.florisboard.lib.kotlin.io.writeJson
|
||||
import java.util.UUID
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
private val TextFieldVerticalPadding = 8.dp
|
||||
@@ -197,7 +199,7 @@ private fun ExtensionEditScreenSheetSwitcher(
|
||||
ManageDependenciesScreen(workspace)
|
||||
}
|
||||
is EditorAction.ManageFiles -> {
|
||||
ManageFilesScreen(workspace)
|
||||
ExtensionEditFilesScreen(workspace)
|
||||
}
|
||||
is EditorAction.CreateComponent<*> -> {
|
||||
CreateComponentScreen(workspace, action.type)
|
||||
@@ -261,17 +263,33 @@ private fun EditScreen(
|
||||
return
|
||||
}
|
||||
val manifest = extEditor.build()
|
||||
workspace.saverDir.deleteContentsRecursively()
|
||||
val manifestFile = workspace.saverDir.subFile(ExtensionDefaults.MANIFEST_FILE_NAME)
|
||||
manifestFile.writeJson(manifest, ExtensionJsonConfig)
|
||||
when (extEditor) {
|
||||
is ThemeExtensionEditor -> {
|
||||
// TODO: this is hacky
|
||||
val fonts = workspace.extDir.subDir("fonts")
|
||||
if (fonts.exists()) {
|
||||
fonts.copyRecursively(workspace.saverDir.subDir("fonts"), overwrite = true)
|
||||
}
|
||||
val images = workspace.extDir.subDir("images")
|
||||
if (images.exists()) {
|
||||
images.copyRecursively(workspace.saverDir.subDir("images"), overwrite = true)
|
||||
}
|
||||
for (theme in extEditor.themes) {
|
||||
val stylesheetFile = workspace.saverDir.subFile(theme.stylesheetPath())
|
||||
stylesheetFile.parentFile?.mkdirs()
|
||||
val stylesheetEditor = theme.stylesheetEditor
|
||||
if (stylesheetEditor != null) {
|
||||
val stylesheet = stylesheetEditor.build()
|
||||
stylesheetFile.writeJson(stylesheet, SnyggStylesheetJsonConfig)
|
||||
runCatching {
|
||||
val stylesheet = stylesheetEditor.build().toJson(PrettyPrintConfig).getOrThrow()
|
||||
stylesheetFile.writeText(stylesheet)
|
||||
}.onFailure {
|
||||
// TODO: better error handling
|
||||
context.showLongToast(it.message.toString())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
val unmodifiedStylesheetFile = workspace.extDir.subFile(theme.stylesheetPath())
|
||||
if (unmodifiedStylesheetFile.exists()) {
|
||||
@@ -582,35 +600,6 @@ private fun ManageDependenciesScreen(workspace: CacheManager.ExtEditorWorkspace<
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManageFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = FlorisScreen {
|
||||
title = stringRes(R.string.ext__editor__files__title)
|
||||
|
||||
fun handleBackPress() {
|
||||
workspace.currentAction = null
|
||||
}
|
||||
|
||||
navigationIcon {
|
||||
FlorisIconButton(
|
||||
onClick = { handleBackPress() },
|
||||
icon = Icons.Default.Close,
|
||||
)
|
||||
}
|
||||
|
||||
content {
|
||||
BackHandler {
|
||||
handleBackPress()
|
||||
}
|
||||
|
||||
FlorisInfoCard(
|
||||
modifier = Modifier.padding(all = 8.dp),
|
||||
text = """
|
||||
Managing archive files is currently not supported.
|
||||
""".trimIndent().replace('\n', ' '),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private enum class CreateFrom {
|
||||
EMPTY,
|
||||
EXISTING;
|
||||
@@ -703,15 +692,14 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
|
||||
val component = editor.themes.find { it.id == componentName.componentId } ?: return
|
||||
val componentEditor = component.let { c ->
|
||||
ThemeExtensionComponentEditor(
|
||||
componentId, c.label, c.authors, c.isNightTheme, c.isBorderless,
|
||||
c.isMaterialYouAware, stylesheetPath = "",
|
||||
componentId, c.label, c.authors, c.isNightTheme, stylesheetPath = "",
|
||||
).also { it.stylesheetEditor = c.stylesheetEditor }
|
||||
}
|
||||
if (componentEditor.stylesheetEditor != null) {
|
||||
val stylesheet = componentEditor.stylesheetEditor!!.build()
|
||||
val stylesheetFile = workspace.extDir.subFile(componentEditor.stylesheetPath())
|
||||
stylesheetFile.parentFile?.mkdirs()
|
||||
stylesheetFile.writeJson(stylesheet, SnyggStylesheetJsonConfig)
|
||||
val stylesheet = componentEditor.stylesheetEditor!!.build().toJson(PrettyPrintConfig).getOrThrow()
|
||||
stylesheetFile.writeText(stylesheet)
|
||||
componentEditor.stylesheetEditor = null
|
||||
} else {
|
||||
val srcStylesheetFile = workspace.extDir.subFile(component.stylesheetPath())
|
||||
@@ -813,36 +801,37 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringRes(R.string.ext__meta__id),
|
||||
) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = newId,
|
||||
onValueChange = { newId = it },
|
||||
singleLine = true,
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = newIdValidation,
|
||||
)
|
||||
Validation(showValidationErrors, newIdValidation)
|
||||
}
|
||||
DialogProperty(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringRes(R.string.ext__meta__label),
|
||||
) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = newLabel,
|
||||
onValueChange = { newLabel = it },
|
||||
singleLine = true,
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = newLabelValidation,
|
||||
)
|
||||
Validation(showValidationErrors, newLabelValidation)
|
||||
|
||||
}
|
||||
DialogProperty(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringRes(R.string.ext__meta__authors),
|
||||
) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = newAuthors,
|
||||
onValueChange = { newAuthors = it },
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = newAuthorsValidation,
|
||||
)
|
||||
Validation(showValidationErrors, newAuthorsValidation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -860,7 +849,6 @@ private fun EditorSheetTextField(
|
||||
showValidationError: Boolean = false,
|
||||
validationResult: ValidationResult? = null,
|
||||
) {
|
||||
val borderColor = MaterialTheme.colorScheme.outline
|
||||
Column(modifier = Modifier.padding(vertical = TextFieldVerticalPadding)) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -880,18 +868,13 @@ private fun EditorSheetTextField(
|
||||
)
|
||||
}
|
||||
}
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
enabled = enabled,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
showValidationError = showValidationError,
|
||||
validationResult = validationResult,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = borderColor,
|
||||
disabledBorderColor = borderColor,
|
||||
)
|
||||
)
|
||||
Validation(showValidationError, validationResult)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,33 +16,18 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.ext
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Input
|
||||
import androidx.compose.material.icons.filled.Keyboard
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.Palette
|
||||
import androidx.compose.material.icons.filled.Shop
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
|
||||
@Composable
|
||||
@@ -56,36 +41,7 @@ fun ExtensionHomeScreen() = FlorisScreen {
|
||||
val extensionIndex = extensionManager.combinedExtensionList()
|
||||
|
||||
content {
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 4.dp),
|
||||
text = stringRes(id = R.string.ext__home__info),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 6.dp),
|
||||
) {
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
context.launchUrl("https://${BuildConfig.FLADDONS_STORE_URL}/")
|
||||
},
|
||||
icon = Icons.Default.Shop,
|
||||
text = stringRes(id = R.string.ext__home__visit_store),
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, null))
|
||||
},
|
||||
icon = Icons.AutoMirrored.Filled.Input,
|
||||
text = stringRes(R.string.action__import),
|
||||
)
|
||||
}
|
||||
}
|
||||
ImportExtensionBox(navController)
|
||||
|
||||
UpdateBox(extensionIndex = extensionIndex)
|
||||
|
||||
|
||||
@@ -113,6 +113,9 @@ fun ExtensionListScreen(type: ExtensionListScreenType, showUpdate: Boolean) = Fl
|
||||
contentPadding = PaddingValues(bottom = fabHeightDp),
|
||||
) {
|
||||
if (showUpdate) {
|
||||
item {
|
||||
ImportExtensionBox(navController)
|
||||
}
|
||||
item {
|
||||
UpdateBox(extensionIndex = extensionIndex)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package dev.patrickgold.florisboard.app.settings
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Assignment
|
||||
import androidx.compose.material.icons.filled.Adb
|
||||
import androidx.compose.material.icons.filled.Extension
|
||||
import androidx.compose.material.icons.filled.Gesture
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
@@ -152,8 +153,8 @@ fun HomeScreen() = FlorisScreen {
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Outlined.Build,
|
||||
title = stringRes(R.string.settings__advanced__title),
|
||||
onClick = { navController.navigate(Routes.Settings.Advanced) },
|
||||
title = stringRes(R.string.settings__other__title),
|
||||
onClick = { navController.navigate(Routes.Settings.Other) },
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Outlined.Info,
|
||||
|
||||
@@ -19,58 +19,71 @@ package dev.patrickgold.florisboard.app.settings.advanced
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Adb
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
import androidx.compose.material.icons.filled.FormatPaint
|
||||
import androidx.compose.material.icons.filled.FormatColorFill
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.Palette
|
||||
import androidx.compose.material.icons.filled.Preview
|
||||
import androidx.compose.material.icons.filled.SettingsBackupRestore
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.AppTheme
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ColorPickerPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
|
||||
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.color.ColorMappings
|
||||
|
||||
|
||||
@Composable
|
||||
fun AdvancedScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__advanced__title)
|
||||
fun OtherScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__other__title)
|
||||
previewFieldVisible = false
|
||||
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
|
||||
content {
|
||||
ListPreference(
|
||||
prefs.advanced.settingsTheme,
|
||||
prefs.other.settingsTheme,
|
||||
icon = Icons.Default.Palette,
|
||||
title = stringRes(R.string.pref__advanced__settings_theme__label),
|
||||
title = stringRes(R.string.pref__other__settings_theme__label),
|
||||
entries = enumDisplayEntriesOf(AppTheme::class),
|
||||
)
|
||||
SwitchPreference(
|
||||
pref = prefs.advanced.useMaterialYou,
|
||||
icon = Icons.Default.FormatPaint,
|
||||
title = stringRes(R.string.pref__advanced__settings_material_you__label),
|
||||
visibleIf = {
|
||||
AndroidVersion.ATLEAST_API31_S
|
||||
},
|
||||
ColorPickerPreference(
|
||||
pref = prefs.other.accentColor,
|
||||
title = stringRes(R.string.pref__other__settings_accent_color__label),
|
||||
defaultValueLabel = stringRes(R.string.action__default),
|
||||
icon = Icons.Default.FormatColorFill,
|
||||
defaultColors = ColorMappings.colors,
|
||||
showAlphaSlider = false,
|
||||
enableAdvancedLayout = false,
|
||||
colorOverride = {
|
||||
if (it.isMaterialYou(context)) {
|
||||
Color.Unspecified
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
)
|
||||
ListPreference(
|
||||
prefs.advanced.settingsLanguage,
|
||||
prefs.other.settingsLanguage,
|
||||
icon = Icons.Default.Language,
|
||||
title = stringRes(R.string.pref__advanced__settings_language__label),
|
||||
title = stringRes(R.string.pref__other__settings_language__label),
|
||||
entries = listPrefEntries {
|
||||
listOf(
|
||||
"auto",
|
||||
@@ -132,21 +145,15 @@ fun AdvancedScreen() = FlorisScreen {
|
||||
}
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.advanced.showAppIcon,
|
||||
prefs.other.showAppIcon,
|
||||
icon = Icons.Default.Preview,
|
||||
title = stringRes(R.string.pref__advanced__show_app_icon__label),
|
||||
title = stringRes(R.string.pref__other__show_app_icon__label),
|
||||
summary = when {
|
||||
AndroidVersion.ATLEAST_API29_Q -> stringRes(R.string.pref__advanced__show_app_icon__summary_atleast_q)
|
||||
AndroidVersion.ATLEAST_API29_Q -> stringRes(R.string.pref__other__show_app_icon__summary_atleast_q)
|
||||
else -> null
|
||||
},
|
||||
enabledIf = { AndroidVersion.ATMOST_API28_P },
|
||||
)
|
||||
ListPreference(
|
||||
prefs.advanced.incognitoMode,
|
||||
icon = vectorResource(id = R.drawable.ic_incognito),
|
||||
title = stringRes(R.string.pref__advanced__incognito_mode__label),
|
||||
entries = enumDisplayEntriesOf(IncognitoMode::class),
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Default.Adb,
|
||||
title = stringRes(R.string.devtools__title),
|
||||
@@ -52,6 +52,23 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
|
||||
)
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__clipboard__group_clipboard_suggestion__label)) {
|
||||
SwitchPreference(
|
||||
prefs.clipboard.suggestionEnabled,
|
||||
title = stringRes(R.string.pref__clipboard__suggestion_enabled__label),
|
||||
summary = stringRes(R.string.pref__clipboard__suggestion_enabled__summary),
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.suggestionTimeout,
|
||||
title = stringRes(R.string.pref__clipboard__suggestion_timeout__label),
|
||||
valueLabel = { stringRes(R.string.pref__clipboard__suggestion_timeout__summary, "v" to it) },
|
||||
min = 30,
|
||||
max = 300,
|
||||
stepIncrement = 5,
|
||||
enabledIf = { prefs.clipboard.suggestionEnabled isEqualTo true },
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__clipboard__group_clipboard_history__label)) {
|
||||
SwitchPreference(
|
||||
prefs.clipboard.historyEnabled,
|
||||
|
||||
@@ -56,14 +56,15 @@ import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryEntry
|
||||
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryValidation
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.Validation
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.rememberValidationResult
|
||||
import dev.patrickgold.florisboard.lib.util.launchActivity
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
@@ -366,39 +367,35 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
|
||||
) {
|
||||
Column {
|
||||
DialogProperty(text = stringRes(R.string.settings__udm__dialog__word_label)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = word,
|
||||
onValueChange = { word = it },
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = wordValidation,
|
||||
)
|
||||
Validation(showValidationErrors, wordValidation)
|
||||
}
|
||||
DialogProperty(text = stringRes(
|
||||
R.string.settings__udm__dialog__freq_label,
|
||||
"f_min" to FREQUENCY_MIN, "f_max" to FREQUENCY_MAX,
|
||||
)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = freq,
|
||||
onValueChange = { freq = it },
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = freqValidation,
|
||||
)
|
||||
Validation(showValidationErrors, freqValidation)
|
||||
}
|
||||
DialogProperty(text = stringRes(R.string.settings__udm__dialog__shortcut_label)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = shortcut,
|
||||
onValueChange = { shortcut = it },
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = shortcutValidation,
|
||||
)
|
||||
Validation(showValidationErrors, shortcutValidation)
|
||||
}
|
||||
DialogProperty(text = stringRes(R.string.settings__udm__dialog__locale_label)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = locale,
|
||||
onValueChange = { locale = it },
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = localeValidation,
|
||||
)
|
||||
Validation(showValidationErrors, localeValidation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +107,10 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
PreferenceGroup(title = stringRes(R.string.pref__keyboard__group_layout__label)) {
|
||||
ListPreference(
|
||||
prefs.keyboard.oneHandedMode,
|
||||
prefs.keyboard.oneHandedModeEnabled,
|
||||
title = stringRes(R.string.pref__keyboard__one_handed_mode__label),
|
||||
entries = enumDisplayEntriesOf(OneHandedMode::class),
|
||||
summarySwitchDisabled = stringRes(R.string.state__disabled),
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.keyboard.oneHandedModeScaleFactor,
|
||||
@@ -117,7 +119,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
min = 70,
|
||||
max = 90,
|
||||
stepIncrement = 1,
|
||||
enabledIf = { prefs.keyboard.oneHandedMode isNotEqualTo OneHandedMode.OFF },
|
||||
enabledIf = { prefs.keyboard.oneHandedModeEnabled.isTrue() },
|
||||
)
|
||||
ListPreference(
|
||||
prefs.keyboard.landscapeInputUiMode,
|
||||
|
||||
@@ -53,6 +53,7 @@ import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -103,6 +104,10 @@ fun LocalizationScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__localization__display_language_names_in__label),
|
||||
entries = enumDisplayEntriesOf(DisplayLanguageNamesIn::class),
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.localization.displayKeyboardLabelsInSubtypeLanguage,
|
||||
title = stringRes(R.string.settings__localization__display_keyboard_labels_in_subtype_language),
|
||||
)
|
||||
Preference(
|
||||
title = stringRes(R.string.settings__localization__language_pack_title),
|
||||
summary = stringRes(R.string.settings__localization__language_pack_summary),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -48,8 +48,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -59,7 +59,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
@@ -82,30 +81,33 @@ import dev.patrickgold.florisboard.ime.keyboard.computeImageVector
|
||||
import dev.patrickgold.florisboard.ime.keyboard.computeLabel
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUiSpec
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.NATIVE_NULLPTR
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisChip
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisDropdownMenu
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisHyperlinkText
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import org.florisboard.lib.snygg.SnyggRule
|
||||
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefDropdown
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextFieldDefaults
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import org.florisboard.lib.kotlin.getKeyByValue
|
||||
import org.florisboard.lib.snygg.SnyggAnnotationRule
|
||||
import org.florisboard.lib.snygg.SnyggElementRule
|
||||
import org.florisboard.lib.snygg.SnyggRule
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.NonNullSaver
|
||||
|
||||
private val TransparentTextSelectionColors = TextSelectionColors(
|
||||
handleColor = Color.Transparent,
|
||||
backgroundColor = Color.Transparent,
|
||||
)
|
||||
internal val SnyggEmptyRuleForAdding = SnyggRule(element = "- select -")
|
||||
internal val SnyggEmptyRuleForAdding = SnyggElementRule(elementName = "--select--")
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@@ -116,69 +118,69 @@ internal fun EditRuleDialog(
|
||||
onDeleteRule: (rule: SnyggRule) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val isAddRuleDialog = initRule == SnyggEmptyRuleForAdding
|
||||
var showSelectAsError by rememberSaveable { mutableStateOf(false) }
|
||||
var showAlreadyExistsError by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val possibleElementNames = remember {
|
||||
listOf(SnyggEmptyRuleForAdding.element) + FlorisImeUiSpec.elements.keys
|
||||
val possibleRuleTemplates = remember {
|
||||
buildList {
|
||||
add(SnyggEmptyRuleForAdding)
|
||||
add(SnyggAnnotationRule.Font(fontName = ""))
|
||||
FlorisImeUi.elementNames.forEach { name ->
|
||||
add(SnyggElementRule(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
val possibleRuleLabels = possibleRuleTemplates.map { rule ->
|
||||
val elementName = when (rule) {
|
||||
is SnyggElementRule -> rule.elementName
|
||||
else -> rule.decl().name
|
||||
}
|
||||
context.translateElementName(elementName, level) ?: rule
|
||||
}
|
||||
val possibleElementLabels = possibleElementNames.map { translateElementName(it, level) ?: it }
|
||||
var elementsExpanded by remember { mutableStateOf(false) }
|
||||
var elementsSelectedIndex by rememberSaveable {
|
||||
val index = possibleElementNames.indexOf(initRule.element).coerceIn(possibleElementNames.indices)
|
||||
val index = possibleRuleTemplates
|
||||
.indexOfFirst { rule ->
|
||||
val elementName = when (rule) {
|
||||
is SnyggElementRule -> rule.elementName
|
||||
else -> rule.decl().name
|
||||
}
|
||||
val initElementName = when (initRule) {
|
||||
is SnyggElementRule -> initRule.elementName
|
||||
else -> initRule.decl().name
|
||||
}
|
||||
elementName == initElementName
|
||||
}
|
||||
.coerceIn(possibleRuleTemplates.indices)
|
||||
mutableIntStateOf(index)
|
||||
}
|
||||
|
||||
val codes = rememberSaveable(saver = IntListSaver) { initRule.codes.toMutableStateList() }
|
||||
var editCodeDialogValue by rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
val groups = rememberSaveable(saver = IntListSaver) { initRule.groups.toMutableStateList() }
|
||||
var shiftStateUnshifted by rememberSaveable {
|
||||
mutableStateOf(initRule.shiftStates.contains(InputShiftState.UNSHIFTED.value))
|
||||
var currentRule by rememberSaveable(elementsSelectedIndex, stateSaver = SnyggRule.NonNullSaver) {
|
||||
mutableStateOf(
|
||||
if (isAddRuleDialog) possibleRuleTemplates[elementsSelectedIndex] else initRule
|
||||
)
|
||||
}
|
||||
var shiftStateShiftedManual by rememberSaveable {
|
||||
mutableStateOf(initRule.shiftStates.contains(InputShiftState.SHIFTED_MANUAL.value))
|
||||
}
|
||||
var shiftStateShiftedAutomatic by rememberSaveable {
|
||||
mutableStateOf(initRule.shiftStates.contains(InputShiftState.SHIFTED_AUTOMATIC.value))
|
||||
}
|
||||
var shiftStateCapsLock by rememberSaveable {
|
||||
mutableStateOf(initRule.shiftStates.contains(InputShiftState.CAPS_LOCK.value))
|
||||
}
|
||||
var pressedSelector by rememberSaveable { mutableStateOf(initRule.pressedSelector) }
|
||||
var focusSelector by rememberSaveable { mutableStateOf(initRule.focusSelector) }
|
||||
var disabledSelector by rememberSaveable { mutableStateOf(initRule.disabledSelector) }
|
||||
|
||||
JetPrefAlertDialog(
|
||||
title = stringRes(if (isAddRuleDialog) {
|
||||
R.string.settings__theme_editor__add_rule
|
||||
} else {
|
||||
R.string.settings__theme_editor__edit_rule
|
||||
}),
|
||||
confirmLabel = stringRes(if (isAddRuleDialog) {
|
||||
R.string.action__add
|
||||
} else {
|
||||
R.string.action__apply
|
||||
}),
|
||||
title = stringRes(
|
||||
if (isAddRuleDialog) {
|
||||
R.string.settings__theme_editor__add_rule
|
||||
} else {
|
||||
R.string.settings__theme_editor__edit_rule
|
||||
}
|
||||
),
|
||||
confirmLabel = stringRes(
|
||||
if (isAddRuleDialog) {
|
||||
R.string.action__add
|
||||
} else {
|
||||
R.string.action__apply
|
||||
}
|
||||
),
|
||||
onConfirm = {
|
||||
if (isAddRuleDialog && elementsSelectedIndex == 0) {
|
||||
showSelectAsError = true
|
||||
} else {
|
||||
val newRule = SnyggRule(
|
||||
element = possibleElementNames[elementsSelectedIndex],
|
||||
codes = codes.toList(),
|
||||
groups = groups.toList(),
|
||||
shiftStates = buildList {
|
||||
if (shiftStateUnshifted) { add(InputShiftState.UNSHIFTED.value) }
|
||||
if (shiftStateShiftedManual) { add(InputShiftState.SHIFTED_MANUAL.value) }
|
||||
if (shiftStateShiftedAutomatic) { add(InputShiftState.SHIFTED_AUTOMATIC.value) }
|
||||
if (shiftStateCapsLock) { add(InputShiftState.CAPS_LOCK.value) }
|
||||
},
|
||||
pressedSelector = pressedSelector,
|
||||
focusSelector = focusSelector,
|
||||
disabledSelector = disabledSelector,
|
||||
)
|
||||
if (!onConfirmRule(initRule, newRule)) {
|
||||
if (!onConfirmRule(initRule, currentRule)) {
|
||||
showAlreadyExistsError = true
|
||||
}
|
||||
}
|
||||
@@ -202,154 +204,228 @@ internal fun EditRuleDialog(
|
||||
)
|
||||
}
|
||||
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_element)) {
|
||||
FlorisDropdownMenu(
|
||||
items = possibleElementLabels,
|
||||
expanded = elementsExpanded,
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_name)) {
|
||||
JetPrefDropdown(
|
||||
options = possibleRuleLabels,
|
||||
selectedOptionIndex = elementsSelectedIndex,
|
||||
onSelectOption = { elementsSelectedIndex = it },
|
||||
enabled = isAddRuleDialog,
|
||||
selectedIndex = elementsSelectedIndex,
|
||||
isError = showSelectAsError && elementsSelectedIndex == 0,
|
||||
onSelectItem = { elementsSelectedIndex = it },
|
||||
onExpandRequest = { elementsExpanded = true },
|
||||
onDismissRequest = { elementsExpanded = false },
|
||||
)
|
||||
}
|
||||
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_selectors)) {
|
||||
Row(modifier = Modifier.florisHorizontalScroll()) {
|
||||
FlorisChip(
|
||||
onClick = { pressedSelector = !pressedSelector },
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggRule.PRESSED_SELECTOR
|
||||
else -> stringRes(R.string.snygg__rule_selector__pressed)
|
||||
(currentRule as? SnyggAnnotationRule.Font)?.apply {
|
||||
DialogProperty(text = stringRes(R.string.snygg__rule_annotation__font_name)) {
|
||||
JetPrefTextField(
|
||||
modifier = Modifier,
|
||||
value = fontName,
|
||||
onValueChange = {
|
||||
currentRule = copy(fontName = it)
|
||||
},
|
||||
selected = pressedSelector,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { focusSelector = !focusSelector },
|
||||
modifier = Modifier.padding( end = 4.dp),
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggRule.FOCUS_SELECTOR
|
||||
else -> stringRes(R.string.snygg__rule_selector__focus)
|
||||
},
|
||||
selected = focusSelector,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { disabledSelector = !disabledSelector },
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggRule.DISABLED_SELECTOR
|
||||
else -> stringRes(R.string.snygg__rule_selector__disabled)
|
||||
},
|
||||
selected = disabledSelector,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DialogProperty(
|
||||
text = stringRes(R.string.settings__theme_editor__rule_codes),
|
||||
trailingIconTitle = {
|
||||
FlorisIconButton(
|
||||
onClick = { editCodeDialogValue = NATIVE_NULLPTR.toInt() },
|
||||
modifier = Modifier.offset(x = 12.dp),
|
||||
icon = Icons.Default.Add,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
text = stringRes(if (codes.isEmpty()) {
|
||||
R.string.settings__theme_editor__no_codes_defined
|
||||
// TODO: Move to toplevel @Composable function
|
||||
(currentRule as? SnyggElementRule)?.apply {
|
||||
if (elementName == SnyggEmptyRuleForAdding.elementName) {
|
||||
return@apply
|
||||
}
|
||||
fun updateCurrentRule(newSelector: SnyggSelector) {
|
||||
currentRule = if (selector == newSelector) {
|
||||
copy(selector = SnyggSelector.NONE)
|
||||
} else {
|
||||
R.string.settings__theme_editor__codes_defined
|
||||
}),
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
FlowRow {
|
||||
for (code in codes) {
|
||||
copy(selector = newSelector)
|
||||
}
|
||||
}
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_selectors)) {
|
||||
Row(modifier = Modifier.florisHorizontalScroll()) {
|
||||
//TODO: LazyRow
|
||||
FlorisChip(
|
||||
onClick = { editCodeDialogValue = code },
|
||||
text = code.toString(),
|
||||
selected = editCodeDialogValue == code,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
onClick = { updateCurrentRule(SnyggSelector.PRESSED) },
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.PRESSED.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__pressed)
|
||||
},
|
||||
selected = selector == SnyggSelector.PRESSED,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { updateCurrentRule(SnyggSelector.FOCUS) },
|
||||
modifier = Modifier.padding( end = 4.dp),
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.FOCUS.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__focus)
|
||||
},
|
||||
selected = selector == SnyggSelector.FOCUS,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { updateCurrentRule(SnyggSelector.HOVER) },
|
||||
modifier = Modifier.padding( end = 4.dp),
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.HOVER.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__hover)
|
||||
},
|
||||
selected = selector == SnyggSelector.HOVER,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { updateCurrentRule(SnyggSelector.DISABLED) },
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.DISABLED.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__disabled)
|
||||
},
|
||||
selected = selector == SnyggSelector.DISABLED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val codes = remember(currentRule) {
|
||||
attributes[FlorisImeUi.Attr.Code] ?: emptyList()
|
||||
}
|
||||
var editCodeDialogValue by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val initCodeValue = editCodeDialogValue
|
||||
if (initCodeValue != null) {
|
||||
EditCodeValueDialog(
|
||||
codeValue = initCodeValue,
|
||||
checkExisting = { codes.contains(it) },
|
||||
onAdd = {
|
||||
currentRule = copy(
|
||||
attributes = attributes.including(FlorisImeUi.Attr.Code to it)
|
||||
)
|
||||
},
|
||||
onDelete = {
|
||||
currentRule = copy(
|
||||
attributes = attributes.excluding(FlorisImeUi.Attr.Code to it)
|
||||
)
|
||||
},
|
||||
onDismiss = { editCodeDialogValue = null },
|
||||
)
|
||||
}
|
||||
DialogProperty(
|
||||
text = stringRes(R.string.settings__theme_editor__rule_codes),
|
||||
trailingIconTitle = {
|
||||
FlorisIconButton(
|
||||
onClick = { editCodeDialogValue = KeyCode.UNSPECIFIED.toString() },
|
||||
modifier = Modifier.offset(x = 12.dp),
|
||||
icon = Icons.Default.Add,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
text = stringRes(
|
||||
if (codes.isEmpty()) {
|
||||
R.string.settings__theme_editor__no_codes_defined
|
||||
} else {
|
||||
R.string.settings__theme_editor__codes_defined
|
||||
}
|
||||
),
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
FlowRow {
|
||||
for (code in codes) {
|
||||
FlorisChip(
|
||||
onClick = { editCodeDialogValue = code },
|
||||
text = code.toString(),
|
||||
selected = editCodeDialogValue == code,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val shiftStateUnshifted = remember(currentRule) {
|
||||
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.UNSHIFTED.attrName()) == true
|
||||
}
|
||||
val shiftStateShiftedManual = remember(currentRule) {
|
||||
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.SHIFTED_MANUAL.attrName()) == true
|
||||
}
|
||||
val shiftStateShiftedAutomatic = remember(currentRule) {
|
||||
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.SHIFTED_AUTOMATIC.attrName()) == true
|
||||
}
|
||||
val shiftStateCapsLock = remember(currentRule) {
|
||||
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.CAPS_LOCK.attrName()) == true
|
||||
}
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_shift_states)) {
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
FlorisChip(
|
||||
onClick = {
|
||||
currentRule = copy(
|
||||
attributes = attributes.toggling(
|
||||
FlorisImeUi.Attr.ShiftState to InputShiftState.UNSHIFTED.attrName()
|
||||
)
|
||||
)
|
||||
},
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> InputShiftState.UNSHIFTED.attrName()
|
||||
else -> stringRes(R.string.enum__input_shift_state__unshifted)
|
||||
},
|
||||
selected = shiftStateUnshifted,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = {
|
||||
currentRule = copy(
|
||||
attributes = attributes.toggling(
|
||||
FlorisImeUi.Attr.ShiftState to InputShiftState.SHIFTED_MANUAL.attrName()
|
||||
)
|
||||
)
|
||||
},
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> InputShiftState.SHIFTED_MANUAL.attrName()
|
||||
else -> stringRes(R.string.enum__input_shift_state__shifted_manual)
|
||||
},
|
||||
selected = shiftStateShiftedManual,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = {
|
||||
currentRule = copy(
|
||||
attributes = attributes.toggling(
|
||||
FlorisImeUi.Attr.ShiftState to InputShiftState.SHIFTED_AUTOMATIC.attrName()
|
||||
)
|
||||
)
|
||||
},
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> InputShiftState.SHIFTED_AUTOMATIC.attrName()
|
||||
else -> stringRes(R.string.enum__input_shift_state__shifted_automatic)
|
||||
},
|
||||
selected = shiftStateShiftedAutomatic,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = {
|
||||
currentRule = copy(
|
||||
attributes = attributes.toggling(
|
||||
FlorisImeUi.Attr.ShiftState to InputShiftState.CAPS_LOCK.attrName()
|
||||
)
|
||||
)
|
||||
},
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> InputShiftState.CAPS_LOCK.attrName()
|
||||
else -> stringRes(R.string.enum__input_shift_state__caps_lock)
|
||||
},
|
||||
selected = shiftStateCapsLock,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_shift_states)) {
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
FlorisChip(
|
||||
onClick = { shiftStateUnshifted = !shiftStateUnshifted },
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> remember {
|
||||
SnyggRule.Placeholders.getKeyByValue(InputShiftState.UNSHIFTED.value)
|
||||
}
|
||||
else -> stringRes(R.string.enum__input_shift_state__unshifted)
|
||||
},
|
||||
selected = shiftStateUnshifted,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { shiftStateShiftedManual = !shiftStateShiftedManual },
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> remember {
|
||||
SnyggRule.Placeholders.getKeyByValue(InputShiftState.SHIFTED_MANUAL.value)
|
||||
}
|
||||
else -> stringRes(R.string.enum__input_shift_state__shifted_manual)
|
||||
},
|
||||
selected = shiftStateShiftedManual,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { shiftStateShiftedAutomatic = !shiftStateShiftedAutomatic },
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> remember {
|
||||
SnyggRule.Placeholders.getKeyByValue(InputShiftState.SHIFTED_AUTOMATIC.value)
|
||||
}
|
||||
else -> stringRes(R.string.enum__input_shift_state__shifted_automatic)
|
||||
},
|
||||
selected = shiftStateShiftedAutomatic,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { shiftStateCapsLock = !shiftStateCapsLock },
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> remember {
|
||||
SnyggRule.Placeholders.getKeyByValue(InputShiftState.CAPS_LOCK.value)
|
||||
}
|
||||
else -> stringRes(R.string.enum__input_shift_state__caps_lock)
|
||||
},
|
||||
selected = shiftStateCapsLock,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val initCodeValue = editCodeDialogValue
|
||||
if (initCodeValue != null) {
|
||||
EditCodeValueDialog(
|
||||
codeValue = initCodeValue,
|
||||
checkExisting = { codes.contains(it) },
|
||||
onAdd = { codes.add(it) },
|
||||
onDelete = { codes.remove(it) },
|
||||
onDismiss = { editCodeDialogValue = null },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun EditCodeValueDialog(
|
||||
codeValue: Int,
|
||||
checkExisting: (Int) -> Boolean,
|
||||
onAdd: (Int) -> Unit,
|
||||
onDelete: (Int) -> Unit,
|
||||
codeValue: String,
|
||||
checkExisting: (String) -> Boolean,
|
||||
onAdd: (String) -> Unit,
|
||||
onDelete: (String) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
|
||||
var inputCodeString by rememberSaveable(codeValue) {
|
||||
val str = if (codeValue == 0) "" else codeValue.toString()
|
||||
val str = if (codeValue == KeyCode.UNSPECIFIED.toString()) "" else codeValue.toString()
|
||||
mutableStateOf(str)
|
||||
}
|
||||
val textKeyData = remember(inputCodeString) {
|
||||
@@ -406,6 +482,7 @@ private fun EditCodeValueDialog(
|
||||
inputCodeString = data.code.toString()
|
||||
isRecordingKey = false
|
||||
}
|
||||
|
||||
override fun onInputKeyRepeat(data: KeyData) = Unit
|
||||
override fun onInputKeyCancel(data: KeyData) = Unit
|
||||
}
|
||||
@@ -423,16 +500,20 @@ private fun EditCodeValueDialog(
|
||||
}
|
||||
|
||||
JetPrefAlertDialog(
|
||||
title = stringRes(if (codeValue == NATIVE_NULLPTR.toInt()) {
|
||||
R.string.settings__theme_editor__add_code
|
||||
} else {
|
||||
R.string.settings__theme_editor__edit_code
|
||||
}),
|
||||
confirmLabel = stringRes(if (codeValue == NATIVE_NULLPTR.toInt()) {
|
||||
R.string.action__add
|
||||
} else {
|
||||
R.string.action__apply
|
||||
}),
|
||||
title = stringRes(
|
||||
if (codeValue == KeyCode.UNSPECIFIED.toString()) {
|
||||
R.string.settings__theme_editor__add_code
|
||||
} else {
|
||||
R.string.settings__theme_editor__edit_code
|
||||
}
|
||||
),
|
||||
confirmLabel = stringRes(
|
||||
if (codeValue == KeyCode.UNSPECIFIED.toString()) {
|
||||
R.string.action__add
|
||||
} else {
|
||||
R.string.action__apply
|
||||
}
|
||||
),
|
||||
onConfirm = {
|
||||
val code = inputCodeString.trim().toIntOrNull(radix = 10)
|
||||
when {
|
||||
@@ -440,25 +521,28 @@ private fun EditCodeValueDialog(
|
||||
errorId = R.string.settings__theme_editor__code_invalid
|
||||
showError = true
|
||||
}
|
||||
code == codeValue -> {
|
||||
|
||||
code.toString() == codeValue -> {
|
||||
onDismiss()
|
||||
}
|
||||
checkExisting(code) -> {
|
||||
|
||||
checkExisting(code.toString()) -> {
|
||||
errorId = R.string.settings__theme_editor__code_already_exists
|
||||
showError = true
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (codeValue != NATIVE_NULLPTR.toInt()) {
|
||||
if (codeValue != KeyCode.UNSPECIFIED.toString()) {
|
||||
onDelete(codeValue)
|
||||
}
|
||||
onAdd(code)
|
||||
onAdd(code.toString())
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissLabel = stringRes(R.string.action__cancel),
|
||||
onDismiss = onDismiss,
|
||||
neutralLabel = if (codeValue != NATIVE_NULLPTR.toInt()) {
|
||||
neutralLabel = if (codeValue != KeyCode.UNSPECIFIED.toString()) {
|
||||
stringRes(R.string.action__delete)
|
||||
} else {
|
||||
null
|
||||
@@ -505,7 +589,7 @@ private fun EditCodeValueDialog(
|
||||
LocalTextSelectionColors.current
|
||||
}
|
||||
CompositionLocalProvider(LocalTextSelectionColors provides textSelectionColors) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.weight(1f),
|
||||
@@ -514,7 +598,7 @@ private fun EditCodeValueDialog(
|
||||
inputCodeString = v
|
||||
showError = false
|
||||
},
|
||||
placeholder = when {
|
||||
placeholderText = when {
|
||||
isRecordingKey -> {
|
||||
stringRes(R.string.settings__theme_editor__code_recording_placeholder)
|
||||
}
|
||||
@@ -527,21 +611,25 @@ private fun EditCodeValueDialog(
|
||||
},
|
||||
isError = showError,
|
||||
singleLine = true,
|
||||
colors = if (isRecordingKey) {
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedTextColor = Color.Transparent,
|
||||
cursorColor = Color.Transparent,
|
||||
appearance = JetPrefTextFieldDefaults.filled(
|
||||
colors = if (isRecordingKey) {
|
||||
TextFieldDefaults.colors(
|
||||
focusedTextColor = Color.Transparent,
|
||||
cursorColor = Color.Transparent,
|
||||
)
|
||||
} else {
|
||||
TextFieldDefaults.colors()
|
||||
}
|
||||
),
|
||||
trailingIcon = {
|
||||
FlorisIconButton(
|
||||
onClick = { requestStartRecording() },
|
||||
icon = Icons.Default.Pageview,
|
||||
iconColor = recordingKeyColor,
|
||||
)
|
||||
} else {
|
||||
OutlinedTextFieldDefaults.colors()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
FlorisIconButton(
|
||||
onClick = { requestStartRecording() },
|
||||
icon = Icons.Default.Pageview,
|
||||
iconColor = recordingKeyColor,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = showError) {
|
||||
Text(
|
||||
@@ -573,9 +661,12 @@ private fun TextKeyDataPreviewBox(
|
||||
override val mode = KeyboardMode.NUMERIC_ADVANCED
|
||||
override fun getKeyForPos(pointerX: Float, pointerY: Float) = error("not implemented")
|
||||
override fun keys() = error("not implemented")
|
||||
override fun layout(keyboardWidth: Float, keyboardHeight: Float, desiredKey: Key,
|
||||
extendTouchBoundariesDownwards: Boolean) = error("not implemented")
|
||||
override fun layout(
|
||||
keyboardWidth: Float, keyboardHeight: Float, desiredKey: Key,
|
||||
extendTouchBoundariesDownwards: Boolean,
|
||||
) = error("not implemented")
|
||||
}
|
||||
|
||||
override fun context() = context
|
||||
}
|
||||
}
|
||||
@@ -603,20 +694,19 @@ private fun TextKeyDataPreviewBox(
|
||||
.align(Alignment.CenterVertically),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (label != null) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 16.sp,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
modifier = Modifier.requiredSize(24.dp),
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
)
|
||||
} else if (label != null) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 16.sp,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
|
||||
@@ -23,9 +23,9 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
|
||||
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
|
||||
private val FineTuneContentPadding = PaddingValues(horizontal = 8.dp)
|
||||
@@ -44,9 +44,9 @@ fun FineTuneDialog(onDismiss: () -> Unit) {
|
||||
entries = enumDisplayEntriesOf(SnyggLevel::class),
|
||||
)
|
||||
ListPreference(
|
||||
listPref = prefs.theme.editorDisplayColorsAs,
|
||||
title = stringRes(R.string.settings__theme_editor__fine_tune__display_colors_as),
|
||||
entries = enumDisplayEntriesOf(DisplayColorsAs::class),
|
||||
listPref = prefs.theme.editorColorRepresentation,
|
||||
title = stringRes(R.string.settings__theme_editor__fine_tune__color_representation),
|
||||
entries = enumDisplayEntriesOf(ColorRepresentation::class),
|
||||
)
|
||||
ListPreference(
|
||||
listPref = prefs.theme.editorDisplayKbdAfterDialogs,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.florisboard.lib.snygg
|
||||
package dev.patrickgold.florisboard.app.settings.theme
|
||||
|
||||
/**
|
||||
* SnyggLevel indicates if a rule property is intended to be edited by all users (BASIC) or only by advanced users
|
||||
@@ -26,29 +26,64 @@ import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.FormatAlignLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.FormatAlignRight
|
||||
import androidx.compose.material.icons.automirrored.filled.WrapText
|
||||
import androidx.compose.material.icons.filled.AttachFile
|
||||
import androidx.compose.material.icons.filled.CheckBox
|
||||
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
|
||||
import androidx.compose.material.icons.filled.FontDownload
|
||||
import androidx.compose.material.icons.filled.FormatAlignCenter
|
||||
import androidx.compose.material.icons.filled.FormatAlignJustify
|
||||
import androidx.compose.material.icons.filled.FormatBold
|
||||
import androidx.compose.material.icons.filled.FormatItalic
|
||||
import androidx.compose.material.icons.filled.FormatSize
|
||||
import androidx.compose.material.icons.filled.FormatStrikethrough
|
||||
import androidx.compose.material.icons.filled.FormatUnderlined
|
||||
import androidx.compose.material.icons.filled.Link
|
||||
import androidx.compose.material.icons.filled.OpenInFull
|
||||
import androidx.compose.material.icons.filled.Padding
|
||||
import androidx.compose.material.icons.filled.Straighten
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.snygg.value.SnyggCutCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDefinedVarValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDpSizeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggMaterialYouValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRoundedCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSolidColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggStaticColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSpSizeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggValue
|
||||
import dev.patrickgold.jetpref.material.ui.checkeredBackground
|
||||
import org.florisboard.lib.color.ColorMappings
|
||||
import org.florisboard.lib.color.getColor
|
||||
import org.florisboard.lib.snygg.value.SnyggContentScaleValue
|
||||
import org.florisboard.lib.snygg.value.SnyggCustomFontFamilyValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDynamicDarkColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDynamicLightColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggFontStyleValue
|
||||
import org.florisboard.lib.snygg.value.SnyggFontWeightValue
|
||||
import org.florisboard.lib.snygg.value.SnyggGenericFontFamilyValue
|
||||
import org.florisboard.lib.snygg.value.SnyggNoValue
|
||||
import org.florisboard.lib.snygg.value.SnyggPaddingValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextAlignValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextDecorationLineValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextOverflowValue
|
||||
import org.florisboard.lib.snygg.value.SnyggUriValue
|
||||
import org.florisboard.lib.snygg.value.SnyggYesValue
|
||||
|
||||
object SnyggValueIcon {
|
||||
interface Spec {
|
||||
@@ -86,13 +121,51 @@ internal fun SnyggValueIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
spec: SnyggValueIcon.Spec = SnyggValueIcon.Normal,
|
||||
) {
|
||||
val prefs by florisPreferenceModel()
|
||||
val context = LocalContext.current
|
||||
val accentColor by prefs.theme.accentColor.observeAsState()
|
||||
|
||||
when (value) {
|
||||
is SnyggSolidColorValue -> {
|
||||
is SnyggStaticColorValue -> {
|
||||
SnyggValueColorBox(modifier = modifier, spec = spec, backgroundColor = value.color)
|
||||
}
|
||||
is SnyggDynamicLightColorValue -> {
|
||||
val colorScheme = ColorMappings.dynamicLightColorScheme(context, accentColor)
|
||||
SnyggValueColorBox(modifier = modifier, spec = spec, backgroundColor = colorScheme.getColor(value.colorName))
|
||||
}
|
||||
is SnyggDynamicDarkColorValue -> {
|
||||
val colorScheme = ColorMappings.dynamicDarkColorScheme(context, accentColor)
|
||||
SnyggValueColorBox(modifier = modifier, spec = spec, backgroundColor = colorScheme.getColor(value.colorName))
|
||||
}
|
||||
|
||||
is SnyggMaterialYouValue -> {
|
||||
SnyggValueColorBox(modifier = modifier, spec = spec, backgroundColor = value.loadColor(LocalContext.current))
|
||||
is SnyggGenericFontFamilyValue, is SnyggCustomFontFamilyValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.FontDownload,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is SnyggFontStyleValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.FormatItalic,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is SnyggFontWeightValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.FormatBold,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
is SnyggPaddingValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.Padding,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
is SnyggShapeValue -> {
|
||||
@@ -102,6 +175,7 @@ internal fun SnyggValueIcon(
|
||||
.border(spec.borderWith, MaterialTheme.colorScheme.onBackground, value.alwaysPercentShape())
|
||||
)
|
||||
}
|
||||
|
||||
is SnyggDpSizeValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
@@ -116,6 +190,37 @@ internal fun SnyggValueIcon(
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
is SnyggTextAlignValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = when (value.textAlign) {
|
||||
TextAlign.Left, TextAlign.Start -> Icons.AutoMirrored.Default.FormatAlignLeft
|
||||
TextAlign.Right, TextAlign.End -> Icons.AutoMirrored.Default.FormatAlignRight
|
||||
TextAlign.Justify -> Icons.Default.FormatAlignJustify
|
||||
else -> Icons.Default.FormatAlignCenter
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is SnyggTextDecorationLineValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = when (value.textDecoration) {
|
||||
TextDecoration.LineThrough -> Icons.Default.FormatStrikethrough
|
||||
else -> Icons.Default.FormatUnderlined
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is SnyggTextOverflowValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.AutoMirrored.Default.WrapText,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
is SnyggDefinedVarValue -> {
|
||||
val realValue = definedVariables[value.key]
|
||||
if (realValue == null) {
|
||||
@@ -150,6 +255,37 @@ internal fun SnyggValueIcon(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SnyggUriValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.AttachFile,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is SnyggContentScaleValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.OpenInFull,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
is SnyggYesValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.FormatBold,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
is SnyggNoValue -> {
|
||||
Icon(
|
||||
modifier = modifier.requiredSize(spec.iconSize),
|
||||
imageVector = Icons.Default.CheckBoxOutlineBlank,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Render nothing
|
||||
}
|
||||
|
||||
@@ -36,11 +36,16 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.Tune
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
@@ -53,11 +58,8 @@ import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -71,17 +73,18 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.apptheme.Shapes
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionComponentView
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUiSpec
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionEditor
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.ime.theme.extPreviewTheme
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.PreviewKeyboardField
|
||||
import dev.patrickgold.florisboard.lib.compose.Validation
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.rememberPreviewFieldController
|
||||
@@ -93,26 +96,46 @@ import dev.patrickgold.florisboard.themeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.kotlin.io.readJson
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import org.florisboard.lib.snygg.SnyggPropertySetEditor
|
||||
import org.florisboard.lib.snygg.SnyggPropertySetSpec
|
||||
import org.florisboard.lib.snygg.SnyggAnnotationRule
|
||||
import org.florisboard.lib.snygg.SnyggElementRule
|
||||
import org.florisboard.lib.snygg.SnyggJsonConfiguration
|
||||
import org.florisboard.lib.snygg.SnyggMultiplePropertySetsEditor
|
||||
import org.florisboard.lib.snygg.SnyggRule
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.SnyggSinglePropertySetEditor
|
||||
import org.florisboard.lib.snygg.SnyggSpec
|
||||
import org.florisboard.lib.snygg.SnyggSpecDecl
|
||||
import org.florisboard.lib.snygg.SnyggStylesheet
|
||||
import org.florisboard.lib.snygg.SnyggStylesheetEditor
|
||||
import org.florisboard.lib.snygg.SnyggStylesheetJsonConfig
|
||||
import org.florisboard.lib.snygg.definedVariablesRule
|
||||
import org.florisboard.lib.snygg.isDefinedVariablesRule
|
||||
import org.florisboard.lib.snygg.ui.Saver
|
||||
import kotlin.Boolean
|
||||
import kotlin.String
|
||||
|
||||
internal val IntListSaver = Saver<SnapshotStateList<Int>, ArrayList<Int>>(
|
||||
save = { ArrayList(it) },
|
||||
restore = { it.toMutableStateList() },
|
||||
internal val PrettyPrintConfig = SnyggJsonConfiguration.of(
|
||||
prettyPrint = true,
|
||||
prettyPrintIndent = " ",
|
||||
)
|
||||
|
||||
private val LenientConfig = SnyggJsonConfiguration.of(
|
||||
ignoreMissingSchema = true,
|
||||
ignoreInvalidSchema = true,
|
||||
ignoreUnsupportedSchema = true,
|
||||
ignoreInvalidRules = true,
|
||||
ignoreInvalidProperties = true,
|
||||
ignoreInvalidValues = true,
|
||||
)
|
||||
|
||||
private enum class StylesheetLoadingStrategy {
|
||||
TRY_LOAD_OR_ASK_ON_CONFLICT, // default state
|
||||
TRY_LOAD_OR_EMPTY, // user chose to not auto-fix errors
|
||||
TRY_LOAD_OR_PARSE_LENIENT; // user chose to auto-fix errors
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ThemeEditorScreen(
|
||||
@@ -130,35 +153,66 @@ fun ThemeEditorScreen(
|
||||
val scope = rememberCoroutineScope()
|
||||
val previewFieldController = rememberPreviewFieldController().also { it.isVisible = true }
|
||||
|
||||
val stylesheetEditor = remember {
|
||||
var stylesheetLoadingStrategy by rememberSaveable {
|
||||
mutableStateOf(StylesheetLoadingStrategy.TRY_LOAD_OR_ASK_ON_CONFLICT)
|
||||
}
|
||||
var stylesheetEditorFailure by remember { mutableStateOf<Throwable?>(null) }
|
||||
val stylesheetEditor = remember(stylesheetLoadingStrategy) {
|
||||
editor.stylesheetEditor ?: run {
|
||||
stylesheetEditorFailure = null
|
||||
val stylesheetPath = editor.stylesheetPath()
|
||||
editor.stylesheetPathOnLoad = stylesheetPath
|
||||
val stylesheetFile = workspace.extDir.subFile(stylesheetPath)
|
||||
val stylesheetEditor = if (stylesheetFile.exists()) {
|
||||
try {
|
||||
stylesheetFile.readJson<SnyggStylesheet>(SnyggStylesheetJsonConfig).edit()
|
||||
} catch (e: Throwable) {
|
||||
SnyggStylesheetEditor()
|
||||
val stylesheetJson = stylesheetFile.readText()
|
||||
val config = when (stylesheetLoadingStrategy) {
|
||||
StylesheetLoadingStrategy.TRY_LOAD_OR_PARSE_LENIENT -> LenientConfig
|
||||
else -> PrettyPrintConfig
|
||||
}
|
||||
SnyggStylesheet.fromJson(stylesheetJson, config).getOrThrow().edit(CustomRuleComparator)
|
||||
} catch (error: Throwable) {
|
||||
stylesheetEditorFailure = when (stylesheetLoadingStrategy) {
|
||||
StylesheetLoadingStrategy.TRY_LOAD_OR_ASK_ON_CONFLICT -> error
|
||||
else -> null
|
||||
}
|
||||
SnyggStylesheetEditor(SnyggStylesheet.SCHEMA_V2, comparator = CustomRuleComparator)
|
||||
}
|
||||
} else {
|
||||
SnyggStylesheetEditor()
|
||||
}
|
||||
if (stylesheetEditor.rules.none { (rule, _) -> rule.isDefinedVariablesRule() }) {
|
||||
stylesheetEditor.rules[SnyggRule.definedVariablesRule()] = SnyggPropertySetEditor()
|
||||
SnyggStylesheetEditor(SnyggStylesheet.SCHEMA_V2, comparator = CustomRuleComparator)
|
||||
}
|
||||
stylesheetEditor.rules.putIfAbsent(SnyggAnnotationRule.Defines, SnyggSinglePropertySetEditor())
|
||||
stylesheetEditor
|
||||
}.also { editor.stylesheetEditor = it }
|
||||
}
|
||||
|
||||
val definedVariables = remember(stylesheetEditor.rules) {
|
||||
stylesheetEditor.rules.firstNotNullOfOrNull { (rule, propertySet) ->
|
||||
if (rule is SnyggAnnotationRule.Defines && propertySet is SnyggSinglePropertySetEditor) {
|
||||
propertySet.properties
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyMap()
|
||||
}
|
||||
|
||||
val fontNames = remember(stylesheetEditor.rules) {
|
||||
stylesheetEditor.rules.mapNotNull { (rule, _) ->
|
||||
if (rule is SnyggAnnotationRule.Font) {
|
||||
rule.fontName
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val snyggLevel by prefs.theme.editorLevel.observeAsState()
|
||||
val displayColorsAs by prefs.theme.editorDisplayColorsAs.observeAsState()
|
||||
val colorRepresentation by prefs.theme.editorColorRepresentation.observeAsState()
|
||||
val displayKbdAfterDialogs by prefs.theme.editorDisplayKbdAfterDialogs.observeAsState()
|
||||
var oldFocusState by remember { mutableStateOf(false) }
|
||||
var snyggRuleToEdit by rememberSaveable(stateSaver = SnyggRule.Saver) { mutableStateOf(null) }
|
||||
var snyggPropertyToEdit by remember { mutableStateOf<PropertyInfo?>(null) }
|
||||
var snyggPropertySetForEditing = remember<SnyggPropertySetEditor?> { null }
|
||||
var snyggPropertySetSpecForEditing = remember<SnyggPropertySetSpec?> { null }
|
||||
var snyggPropertySetForEditing = remember<SnyggSinglePropertySetEditor?> { null }
|
||||
var showEditComponentMetaDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showFineTuneDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
@@ -198,6 +252,33 @@ fun ThemeEditorScreen(
|
||||
}
|
||||
|
||||
content {
|
||||
stylesheetEditorFailure?.let { failure ->
|
||||
JetPrefAlertDialog(
|
||||
title = stringRes(R.string.settings__theme_editor__stylesheet_error_title),
|
||||
confirmLabel = stringRes(R.string.action__yes),
|
||||
onConfirm = {
|
||||
editor.stylesheetEditor = null
|
||||
stylesheetLoadingStrategy = StylesheetLoadingStrategy.TRY_LOAD_OR_PARSE_LENIENT
|
||||
},
|
||||
dismissLabel = stringRes(R.string.action__no),
|
||||
onDismiss = {
|
||||
editor.stylesheetEditor = null
|
||||
stylesheetLoadingStrategy = StylesheetLoadingStrategy.TRY_LOAD_OR_EMPTY
|
||||
},
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
text = failure.message.toString(),
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.settings__theme_editor__stylesheet_error_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
handleBackPress()
|
||||
}
|
||||
@@ -229,23 +310,15 @@ fun ThemeEditorScreen(
|
||||
|
||||
DisposableEffect(workspace.version) {
|
||||
themeManager.previewThemeInfo = ThemeManager.ThemeInfo.DEFAULT.copy(
|
||||
stylesheet = stylesheetEditor.build().compileToFullyQualified(FlorisImeUiSpec),
|
||||
name = extPreviewTheme(System.currentTimeMillis().toString()),
|
||||
stylesheet = stylesheetEditor.build(),
|
||||
loadedDir = workspace.extDir,
|
||||
)
|
||||
onDispose {
|
||||
themeManager.previewThemeInfo = null
|
||||
}
|
||||
}
|
||||
|
||||
val definedVariables = remember(stylesheetEditor.rules) {
|
||||
stylesheetEditor.rules.firstNotNullOfOrNull { (rule, propertySet) ->
|
||||
if (rule.isDefinedVariablesRule()) {
|
||||
propertySet.properties
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyMap()
|
||||
}
|
||||
|
||||
// TODO: (priority = low)
|
||||
// Floris scrollbar does not like lazy lists with non-constant item heights.
|
||||
// Consider building a custom scrollbar tailored for this list specifically.
|
||||
@@ -263,7 +336,7 @@ fun ThemeEditorScreen(
|
||||
onEditBtnClick = { showEditComponentMetaDialog = true },
|
||||
)
|
||||
if (stylesheetEditor.rules.isEmpty() ||
|
||||
(stylesheetEditor.rules.size == 1 && stylesheetEditor.rules.keys.all { it.isDefinedVariablesRule() })
|
||||
(stylesheetEditor.rules.size == 1 && stylesheetEditor.rules.all { (rule, _) -> rule == SnyggAnnotationRule.Defines })
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
@@ -274,9 +347,9 @@ fun ThemeEditorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
items(stylesheetEditor.rules.entries.toList()) { (rule, propertySet) -> key(rule) {
|
||||
val isVariablesRule = rule.isDefinedVariablesRule()
|
||||
val propertySetSpec = FlorisImeUiSpec.propertySetSpec(rule.element)
|
||||
items(stylesheetEditor.rules.toList()) { (rule, propertySet) -> key(rule) {
|
||||
val propertySetSpec = SnyggSpec.propertySetSpecOf(rule)
|
||||
val isVariablesRule = rule == SnyggAnnotationRule.Defines
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
@@ -291,33 +364,129 @@ fun ThemeEditorScreen(
|
||||
snyggRuleToEdit = rule
|
||||
},
|
||||
onAddPropertyBtnClick = {
|
||||
snyggPropertySetForEditing = propertySet
|
||||
snyggPropertySetSpecForEditing = propertySetSpec
|
||||
snyggPropertyToEdit = SnyggEmptyPropertyInfoForAdding
|
||||
when(propertySet) {
|
||||
is SnyggMultiplePropertySetsEditor -> {
|
||||
workspace.update {
|
||||
propertySet.sets.add(SnyggSinglePropertySetEditor())
|
||||
}
|
||||
}
|
||||
is SnyggSinglePropertySetEditor -> {
|
||||
snyggPropertySetForEditing = propertySet
|
||||
snyggPropertyToEdit = SnyggEmptyPropertyInfoForAdding.copy(
|
||||
rule = rule,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
if (isVariablesRule) {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 8.dp, start = 16.dp, end = 16.dp),
|
||||
text = stringRes(R.string.snygg__rule_element__defines_description),
|
||||
text = stringRes(R.string.snygg__rule_annotation__defines_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
}
|
||||
for ((propertyName, propertyValue) in propertySet.properties) {
|
||||
val propertySpec = propertySetSpec?.propertySpec(propertyName)
|
||||
if (propertySpec != null && propertySpec.level <= snyggLevel || isVariablesRule) {
|
||||
JetPrefListItem(
|
||||
modifier = Modifier.rippleClickable {
|
||||
snyggPropertySetForEditing = propertySet
|
||||
snyggPropertySetSpecForEditing = propertySetSpec
|
||||
snyggPropertyToEdit = PropertyInfo(propertyName, propertyValue)
|
||||
},
|
||||
text = translatePropertyName(propertyName, snyggLevel),
|
||||
secondaryText = translatePropertyValue(propertyValue, snyggLevel, displayColorsAs),
|
||||
singleLineSecondaryText = true,
|
||||
trailing = { SnyggValueIcon(propertyValue, definedVariables) },
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SinglePropertySetEditor(
|
||||
propertySet: SnyggSinglePropertySetEditor,
|
||||
) {
|
||||
for ((propertyName, propertySpec) in propertySetSpec?.properties.orEmpty()) {
|
||||
if (propertySpec.required && !propertySet.properties.containsKey(propertyName)) {
|
||||
FlorisOutlinedBox(title = "Errors", modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp),
|
||||
text = "Required property '$propertyName' does not exist",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((propertyName, propertyValue) in propertySet.properties) {
|
||||
if (true /*propertySpec != null && propertySpec.level <= snyggLevel*/ || isVariablesRule) {
|
||||
JetPrefListItem(
|
||||
modifier = Modifier.rippleClickable {
|
||||
snyggPropertySetForEditing = propertySet
|
||||
snyggPropertyToEdit = PropertyInfo(rule, propertyName, propertyValue)
|
||||
},
|
||||
text = context.translatePropertyName(propertyName, snyggLevel),
|
||||
secondaryText = context.translatePropertyValue(propertyValue, snyggLevel, colorRepresentation),
|
||||
singleLineSecondaryText = true,
|
||||
trailing = { SnyggValueIcon(propertyValue, definedVariables) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (propertySet) {
|
||||
is SnyggSinglePropertySetEditor -> {
|
||||
SinglePropertySetEditor(propertySet)
|
||||
}
|
||||
is SnyggMultiplePropertySetsEditor -> {
|
||||
val sets = propertySet.sets
|
||||
sets.forEachIndexed { propertySetIndex, propertySet ->
|
||||
key(propertySet.uuid) {
|
||||
FlorisOutlinedBox(Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp)) {
|
||||
Row {
|
||||
Text("Source set", Modifier
|
||||
.padding(start = 16.dp)
|
||||
.align(Alignment.CenterVertically))
|
||||
Spacer(Modifier.weight(1f))
|
||||
FlorisIconButton(
|
||||
onClick = {
|
||||
workspace.update {
|
||||
if (propertySetIndex > 0) {
|
||||
val set = sets.removeAt(propertySetIndex)
|
||||
sets.add(propertySetIndex - 1, set)
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = Icons.Default.KeyboardArrowUp,
|
||||
iconColor = MaterialTheme.colorScheme.primary,
|
||||
iconModifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
enabled = propertySetIndex > 0,
|
||||
)
|
||||
FlorisIconButton(
|
||||
onClick = {
|
||||
workspace.update {
|
||||
if (propertySetIndex + 1 < sets.size) {
|
||||
val set = sets.removeAt(propertySetIndex)
|
||||
sets.add(propertySetIndex + 1, set)
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = Icons.Default.KeyboardArrowDown,
|
||||
iconColor = MaterialTheme.colorScheme.primary,
|
||||
iconModifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
enabled = propertySetIndex + 1 < sets.size,
|
||||
)
|
||||
FlorisIconButton(
|
||||
onClick = {
|
||||
workspace.update {
|
||||
sets.removeAt(propertySetIndex)
|
||||
}
|
||||
},
|
||||
icon = Icons.Default.Delete,
|
||||
iconColor = MaterialTheme.colorScheme.primary,
|
||||
iconModifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
)
|
||||
FlorisIconButton(
|
||||
onClick = {
|
||||
snyggPropertySetForEditing = propertySet
|
||||
snyggPropertyToEdit = SnyggEmptyPropertyInfoForAdding.copy(
|
||||
rule = rule,
|
||||
)
|
||||
},
|
||||
icon = Icons.Default.Add,
|
||||
iconColor = MaterialTheme.colorScheme.primary,
|
||||
iconModifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
)
|
||||
}
|
||||
SinglePropertySetEditor(propertySet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,7 +538,14 @@ fun ThemeEditorScreen(
|
||||
true
|
||||
}
|
||||
oldRule == SnyggEmptyRuleForAdding -> {
|
||||
rules[newRule] = SnyggPropertySetEditor()
|
||||
when (SnyggSpec.propertySetSpecOf(newRule)!!.type) {
|
||||
SnyggSpecDecl.PropertySet.Type.SINGLE_SET -> {
|
||||
rules[newRule] = SnyggSinglePropertySetEditor()
|
||||
}
|
||||
SnyggSpecDecl.PropertySet.Type.MULTIPLE_SETS -> {
|
||||
rules[newRule] = SnyggMultiplePropertySetsEditor()
|
||||
}
|
||||
}
|
||||
snyggRuleToEdit = null
|
||||
scope.launch {
|
||||
lazyListState.animateScrollToItem(index = rules.keys.indexOf(newRule))
|
||||
@@ -396,11 +572,12 @@ fun ThemeEditorScreen(
|
||||
val propertyToEdit = snyggPropertyToEdit
|
||||
if (propertyToEdit != null) {
|
||||
EditPropertyDialog(
|
||||
propertySetSpec = snyggPropertySetSpecForEditing,
|
||||
initProperty = propertyToEdit,
|
||||
level = snyggLevel,
|
||||
displayColorsAs = displayColorsAs,
|
||||
colorRepresentation = colorRepresentation,
|
||||
definedVariables = definedVariables,
|
||||
fontNames = fontNames,
|
||||
workspace = workspace,
|
||||
onConfirmNewValue = { name, value ->
|
||||
val properties = snyggPropertySetForEditing?.properties ?: return@EditPropertyDialog false
|
||||
if (propertyToEdit == SnyggEmptyPropertyInfoForAdding && properties.containsKey(name)) {
|
||||
@@ -441,8 +618,6 @@ private fun ComponentMetaEditorDialog(
|
||||
var authors by rememberSaveable { mutableStateOf(editor.authors.joinToString("\n")) }
|
||||
val authorsValidation = rememberValidationResult(ExtensionValidation.ComponentAuthors, authors)
|
||||
var isNightTheme by rememberSaveable { mutableStateOf(editor.isNightTheme) }
|
||||
var isBorderless by rememberSaveable { mutableStateOf(editor.isBorderless) }
|
||||
val isMaterialYouAware by rememberSaveable { mutableStateOf(editor.isMaterialYouAware) }
|
||||
var stylesheetPath by rememberSaveable { mutableStateOf(editor.stylesheetPath) }
|
||||
val stylesheetPathValidation = rememberValidationResult(ExtensionValidation.ThemeComponentStylesheetPath, stylesheetPath)
|
||||
|
||||
@@ -464,8 +639,6 @@ private fun ComponentMetaEditorDialog(
|
||||
editor.label = label.trim()
|
||||
editor.authors = authors.lines().map { it.trim() }.filter { it.isNotBlank() }
|
||||
editor.isNightTheme = isNightTheme
|
||||
editor.isBorderless = isBorderless
|
||||
editor.isMaterialYouAware = isMaterialYouAware
|
||||
editor.stylesheetPath = stylesheetPath.trim()
|
||||
}
|
||||
onConfirm()
|
||||
@@ -477,31 +650,28 @@ private fun ComponentMetaEditorDialog(
|
||||
) {
|
||||
Column {
|
||||
DialogProperty(text = stringRes(R.string.ext__meta__id)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = id,
|
||||
onValueChange = { id = it },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii),
|
||||
singleLine = true,
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = idValidation,
|
||||
)
|
||||
Validation(showValidationErrors, idValidation)
|
||||
}
|
||||
DialogProperty(text = stringRes(R.string.ext__meta__label)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = label,
|
||||
onValueChange = { label = it },
|
||||
singleLine = true,
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = labelValidation,
|
||||
)
|
||||
Validation(showValidationErrors, labelValidation)
|
||||
}
|
||||
DialogProperty(text = stringRes(R.string.ext__meta__authors)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = authors,
|
||||
onValueChange = { authors = it },
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = authorsValidation,
|
||||
)
|
||||
Validation(showValidationErrors, authorsValidation)
|
||||
}
|
||||
JetPrefListItem(
|
||||
modifier = Modifier.toggleable(isNightTheme) { isNightTheme = it },
|
||||
@@ -509,28 +679,21 @@ private fun ComponentMetaEditorDialog(
|
||||
trailing = {
|
||||
Switch(checked = isNightTheme, onCheckedChange = null)
|
||||
},
|
||||
)
|
||||
JetPrefListItem(
|
||||
modifier = Modifier.toggleable(isBorderless) { isBorderless = it },
|
||||
text = stringRes(R.string.settings__theme_editor__component_meta_is_borderless),
|
||||
trailing = {
|
||||
Switch(checked = isBorderless, onCheckedChange = null)
|
||||
},
|
||||
colors = ListItemDefaults.colors(containerColor = AlertDialogDefaults.containerColor)
|
||||
)
|
||||
DialogProperty(text = stringRes(R.string.settings__theme_editor__component_meta_stylesheet_path)) {
|
||||
FlorisOutlinedTextField(
|
||||
JetPrefTextField(
|
||||
value = stylesheetPath,
|
||||
onValueChange = { stylesheetPath = it },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii),
|
||||
singleLine = true,
|
||||
placeholder = if (stylesheetPath.isEmpty()) {
|
||||
placeholderText = if (stylesheetPath.isEmpty()) {
|
||||
ThemeExtensionComponent.defaultStylesheetPath(id.trim())
|
||||
} else {
|
||||
null
|
||||
},
|
||||
showValidationError = showValidationErrors,
|
||||
validationResult = stylesheetPathValidation,
|
||||
)
|
||||
Validation(showValidationErrors, stylesheetPathValidation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -544,6 +707,8 @@ private fun SnyggRuleRow(
|
||||
onEditRuleBtnClick: () -> Unit,
|
||||
onAddPropertyBtnClick: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@Composable
|
||||
fun Selector(text: String) {
|
||||
Text(
|
||||
@@ -580,38 +745,53 @@ private fun SnyggRuleRow(
|
||||
.weight(1f)
|
||||
.padding(vertical = 8.dp, horizontal = 10.dp),
|
||||
) {
|
||||
Text(
|
||||
text = translateElementName(rule, level),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
if (rule.pressedSelector) {
|
||||
Selector(text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggRule.PRESSED_SELECTOR
|
||||
else -> stringRes(R.string.snygg__rule_selector__pressed)
|
||||
})
|
||||
if (rule is SnyggElementRule) {
|
||||
Text(
|
||||
text = context.translateElementName(rule, level),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
if (rule.selector == SnyggSelector.PRESSED) {
|
||||
Selector(
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.PRESSED.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__pressed)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (rule.selector == SnyggSelector.FOCUS) {
|
||||
Selector(
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.FOCUS.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__focus)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (rule.selector == SnyggSelector.HOVER) {
|
||||
Selector(
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.HOVER.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__hover)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (rule.selector == SnyggSelector.DISABLED) {
|
||||
Selector(
|
||||
text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggSelector.DISABLED.id
|
||||
else -> stringRes(R.string.snygg__rule_selector__disabled)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (rule.focusSelector) {
|
||||
Selector(text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggRule.FOCUS_SELECTOR
|
||||
else -> stringRes(R.string.snygg__rule_selector__focus)
|
||||
})
|
||||
for ((attrKey, attrValue) in rule.attributes) {
|
||||
AttributesList(text = attrKey, list = attrValue.toString())
|
||||
}
|
||||
if (rule.disabledSelector) {
|
||||
Selector(text = when (level) {
|
||||
SnyggLevel.DEVELOPER -> SnyggRule.DISABLED_SELECTOR
|
||||
else -> stringRes(R.string.snygg__rule_selector__disabled)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (rule.codes.isNotEmpty()) {
|
||||
AttributesList(text = "code", list = remember(rule.codes) { rule.codes.toString() })
|
||||
}
|
||||
if (rule.shiftStates.isNotEmpty()) {
|
||||
AttributesList(text = "shiftstate", list = remember(rule.shiftStates) { rule.shiftStates.toString() })
|
||||
} else {
|
||||
Text(text = rule.toString())
|
||||
}
|
||||
}
|
||||
if (showEditBtn) {
|
||||
@@ -645,10 +825,31 @@ internal fun DialogProperty(
|
||||
.weight(1f)
|
||||
.padding(vertical = 8.dp),
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
trailingIconTitle()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
private object CustomRuleComparator : Comparator<SnyggRule> {
|
||||
@Suppress("IfThenToElvis")
|
||||
override fun compare(a: SnyggRule, b: SnyggRule): Int {
|
||||
return if (a !is SnyggElementRule || b !is SnyggElementRule || a.elementName == b.elementName) {
|
||||
a.compareTo(b)
|
||||
} else {
|
||||
val aOrdinal = FlorisImeUi.elementNamesToOrdinals[a.elementName]
|
||||
val bOrdinal = FlorisImeUi.elementNamesToOrdinals[b.elementName]
|
||||
if (aOrdinal == null && bOrdinal == null) {
|
||||
a.elementName.compareTo(b.elementName)
|
||||
} else if (bOrdinal == null) {
|
||||
-1
|
||||
} else if (aOrdinal == null) {
|
||||
1
|
||||
} else {
|
||||
aOrdinal.compareTo(bOrdinal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ package dev.patrickgold.florisboard.app.settings.theme
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BrightnessAuto
|
||||
import androidx.compose.material.icons.filled.ColorLens
|
||||
import androidx.compose.material.icons.filled.DarkMode
|
||||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
@@ -41,8 +43,11 @@ import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ColorPickerPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
|
||||
import org.florisboard.lib.color.ColorMappings
|
||||
|
||||
@Composable
|
||||
fun ThemeScreen() = FlorisScreen {
|
||||
@@ -108,6 +113,22 @@ fun ThemeScreen() = FlorisScreen {
|
||||
navController.navigate(Routes.Settings.ThemeManager(ThemeManagerScreenAction.SELECT_NIGHT))
|
||||
},
|
||||
)
|
||||
ColorPickerPreference(
|
||||
pref = prefs.theme.accentColor,
|
||||
title = stringRes(R.string.pref__theme__theme_accent_color__label),
|
||||
defaultValueLabel = stringRes(R.string.action__default),
|
||||
icon = Icons.Default.ColorLens,
|
||||
defaultColors = ColorMappings.colors,
|
||||
showAlphaSlider = false,
|
||||
enableAdvancedLayout = false,
|
||||
colorOverride = {
|
||||
if (it.isMaterialYou(context)) {
|
||||
Color.Unspecified
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
AddonManagementReferenceBox(type = ExtensionListScreenType.EXT_THEME)
|
||||
}
|
||||
|
||||
@@ -16,134 +16,108 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import android.content.Context
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.UnicodeCtrlChar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
import org.florisboard.lib.kotlin.simpleNameOrEnclosing
|
||||
import org.florisboard.lib.snygg.Snygg
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import org.florisboard.lib.snygg.SnyggRule
|
||||
import org.florisboard.lib.snygg.value.RgbaColor
|
||||
import org.florisboard.lib.snygg.SnyggElementRule
|
||||
import org.florisboard.lib.snygg.value.SnyggCircleShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggCustomFontFamilyValue
|
||||
import org.florisboard.lib.snygg.value.SnyggCutCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggCutCornerPercentShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDefinedVarValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDpSizeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggExplicitInheritValue
|
||||
import org.florisboard.lib.snygg.value.SnyggImplicitInheritValue
|
||||
import org.florisboard.lib.snygg.value.SnyggMaterialYouDarkColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggMaterialYouLightColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDynamicDarkColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDynamicLightColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggFontStyleValue
|
||||
import org.florisboard.lib.snygg.value.SnyggFontWeightValue
|
||||
import org.florisboard.lib.snygg.value.SnyggGenericFontFamilyValue
|
||||
import org.florisboard.lib.snygg.value.SnyggInheritValue
|
||||
import org.florisboard.lib.snygg.value.SnyggNoValue
|
||||
import org.florisboard.lib.snygg.value.SnyggContentScaleValue
|
||||
import org.florisboard.lib.snygg.value.SnyggPaddingValue
|
||||
import org.florisboard.lib.snygg.value.SnyggPercentageSizeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRectangleShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRoundedCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRoundedCornerPercentShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSolidColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSpSizeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggStaticColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextAlignValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextDecorationLineValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextMaxLinesValue
|
||||
import org.florisboard.lib.snygg.value.SnyggTextOverflowValue
|
||||
import org.florisboard.lib.snygg.value.SnyggUndefinedValue
|
||||
import org.florisboard.lib.snygg.value.SnyggUriValue
|
||||
import org.florisboard.lib.snygg.value.SnyggValue
|
||||
import org.florisboard.lib.snygg.value.SnyggValueEncoder
|
||||
import kotlin.math.roundToInt
|
||||
import org.florisboard.lib.snygg.value.SnyggYesValue
|
||||
|
||||
@Composable
|
||||
internal fun translateElementName(rule: SnyggRule, level: SnyggLevel): String {
|
||||
return translateElementName(rule.element, level) ?: remember {
|
||||
buildString {
|
||||
if (rule.isAnnotation) {
|
||||
append(SnyggRule.ANNOTATION_MARKER)
|
||||
}
|
||||
append(rule.element)
|
||||
}
|
||||
internal fun Context.translateElementName(rule: SnyggElementRule, level: SnyggLevel): String {
|
||||
return translateElementName(rule.elementName, level) ?: rule.elementName
|
||||
}
|
||||
|
||||
internal fun Context.translateElementName(element: String, level: SnyggLevel): String? {
|
||||
return when (level) {
|
||||
SnyggLevel.DEVELOPER -> null
|
||||
else -> FlorisImeUi.elementNamesToTranslation[element]?.let { getString(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun translateElementName(element: String, level: SnyggLevel): String? {
|
||||
return when (level) {
|
||||
SnyggLevel.DEVELOPER -> null
|
||||
else -> when (element) {
|
||||
"defines" -> R.string.snygg__rule_element__defines
|
||||
FlorisImeUi.Keyboard -> R.string.snygg__rule_element__keyboard
|
||||
FlorisImeUi.Key -> R.string.snygg__rule_element__key
|
||||
FlorisImeUi.KeyHint -> R.string.snygg__rule_element__key_hint
|
||||
FlorisImeUi.KeyPopup -> R.string.snygg__rule_element__key_popup
|
||||
FlorisImeUi.ClipboardHeader -> R.string.snygg__rule_element__clipboard_header
|
||||
FlorisImeUi.ClipboardItem -> R.string.snygg__rule_element__clipboard_item
|
||||
FlorisImeUi.ClipboardItemPopup -> R.string.snygg__rule_element__clipboard_item_popup
|
||||
FlorisImeUi.EmojiKey -> R.string.snygg__rule_element__emoji_key
|
||||
FlorisImeUi.EmojiKeyPopup -> R.string.snygg__rule_element__emoji_key_popup
|
||||
FlorisImeUi.EmojiTab -> R.string.snygg__rule_element__emoji_key_tab
|
||||
FlorisImeUi.ExtractedLandscapeInputLayout -> R.string.snygg__rule_element__extracted_landscape_input_layout
|
||||
FlorisImeUi.ExtractedLandscapeInputField -> R.string.snygg__rule_element__extracted_landscape_input_field
|
||||
FlorisImeUi.ExtractedLandscapeInputAction -> R.string.snygg__rule_element__extracted_landscape_input_action
|
||||
FlorisImeUi.GlideTrail -> R.string.snygg__rule_element__glide_trail
|
||||
FlorisImeUi.IncognitoModeIndicator -> R.string.snygg__rule_element__incognito_mode_indicator
|
||||
FlorisImeUi.OneHandedPanel -> R.string.snygg__rule_element__one_handed_panel
|
||||
FlorisImeUi.Smartbar -> R.string.snygg__rule_element__smartbar
|
||||
FlorisImeUi.SmartbarSharedActionsRow -> R.string.snygg__rule_element__smartbar_shared_actions_row
|
||||
FlorisImeUi.SmartbarSharedActionsToggle -> R.string.snygg__rule_element__smartbar_shared_actions_toggle
|
||||
FlorisImeUi.SmartbarExtendedActionsRow -> R.string.snygg__rule_element__smartbar_extended_actions_row
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle -> R.string.snygg__rule_element__smartbar_extended_actions_toggle
|
||||
FlorisImeUi.SmartbarActionKey -> R.string.snygg__rule_element__smartbar_action_key
|
||||
FlorisImeUi.SmartbarActionTile -> R.string.snygg__rule_element__smartbar_action_tile
|
||||
FlorisImeUi.SmartbarActionsOverflow -> R.string.snygg__rule_element__smartbar_actions_overflow
|
||||
FlorisImeUi.SmartbarActionsOverflowCustomizeButton -> R.string.snygg__rule_element__smartbar_actions_overflow_customize_button
|
||||
FlorisImeUi.SmartbarActionsEditor -> R.string.snygg__rule_element__smartbar_actions_editor
|
||||
FlorisImeUi.SmartbarActionsEditorHeader -> R.string.snygg__rule_element__smartbar_actions_editor_header
|
||||
FlorisImeUi.SmartbarActionsEditorSubheader -> R.string.snygg__rule_element__smartbar_actions_editor_subheader
|
||||
FlorisImeUi.SmartbarCandidatesRow -> R.string.snygg__rule_element__smartbar_candidates_row
|
||||
FlorisImeUi.SmartbarCandidateWord -> R.string.snygg__rule_element__smartbar_candidate_word
|
||||
FlorisImeUi.SmartbarCandidateClip -> R.string.snygg__rule_element__smartbar_candidate_clip
|
||||
FlorisImeUi.SmartbarCandidateSpacer -> R.string.snygg__rule_element__smartbar_candidate_spacer
|
||||
FlorisImeUi.SystemNavBar -> R.string.snygg__rule_element__system_nav_bar
|
||||
else -> null
|
||||
}
|
||||
}.let { if (it != null) { stringRes(it) } else { null } }
|
||||
}
|
||||
private val PropertyNameMap = mapOf(
|
||||
Snygg.Background to R.string.snygg__property_name__background,
|
||||
Snygg.Foreground to R.string.snygg__property_name__foreground,
|
||||
Snygg.BackgroundImage to R.string.snygg__property_name__background_image,
|
||||
Snygg.ContentScale to R.string.snygg__property_name__content_scale,
|
||||
Snygg.BorderColor to R.string.snygg__property_name__border_color,
|
||||
Snygg.BorderStyle to R.string.snygg__property_name__border_style,
|
||||
Snygg.BorderWidth to R.string.snygg__property_name__border_width,
|
||||
Snygg.FontFamily to R.string.snygg__property_name__font_family,
|
||||
Snygg.FontSize to R.string.snygg__property_name__font_size,
|
||||
Snygg.FontStyle to R.string.snygg__property_name__font_style,
|
||||
Snygg.FontWeight to R.string.snygg__property_name__font_weight,
|
||||
Snygg.LetterSpacing to R.string.snygg__property_name__letter_spacing,
|
||||
Snygg.LineHeight to R.string.snygg__property_name__line_height,
|
||||
Snygg.Margin to R.string.snygg__property_name__margin,
|
||||
Snygg.Padding to R.string.snygg__property_name__padding,
|
||||
Snygg.ShadowColor to R.string.snygg__property_name__shadow_color,
|
||||
Snygg.ShadowElevation to R.string.snygg__property_name__shadow_elevation,
|
||||
Snygg.Shape to R.string.snygg__property_name__shape,
|
||||
Snygg.Clip to R.string.snygg__property_name__clip,
|
||||
Snygg.Src to R.string.snygg__property_name__src,
|
||||
Snygg.TextAlign to R.string.snygg__property_name__text_align,
|
||||
Snygg.TextDecorationLine to R.string.snygg__property_name__text_decoration_line,
|
||||
Snygg.TextMaxLines to R.string.snygg__property_name__text_max_lines,
|
||||
Snygg.TextOverflow to R.string.snygg__property_name__text_overflow,
|
||||
"--primary" to R.string.snygg__property_name__var_primary,
|
||||
"--primary-variant" to R.string.snygg__property_name__var_primary_variant,
|
||||
"--secondary" to R.string.snygg__property_name__var_secondary,
|
||||
"--secondary-variant" to R.string.snygg__property_name__var_secondary_variant,
|
||||
"--background" to R.string.snygg__property_name__var_background,
|
||||
"--surface" to R.string.snygg__property_name__var_surface,
|
||||
"--surface-variant" to R.string.snygg__property_name__var_surface_variant,
|
||||
"--on-primary" to R.string.snygg__property_name__var_on_primary,
|
||||
"--on-secondary" to R.string.snygg__property_name__var_on_secondary,
|
||||
"--on-background" to R.string.snygg__property_name__var_on_background,
|
||||
"--on-surface" to R.string.snygg__property_name__var_on_surface,
|
||||
"--on-surface-variant" to R.string.snygg__property_name__var_on_surface_variant,
|
||||
"--shape" to R.string.snygg__property_name__var_shape,
|
||||
"--shape-variant" to R.string.snygg__property_name__var_shape_variant
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun translatePropertyName(propertyName: String, level: SnyggLevel): String {
|
||||
internal fun Context.translatePropertyName(propertyName: String, level: SnyggLevel): String {
|
||||
return when (level) {
|
||||
SnyggLevel.DEVELOPER -> null
|
||||
else -> when (propertyName) {
|
||||
Snygg.Width -> R.string.snygg__property_name__width
|
||||
Snygg.Height -> R.string.snygg__property_name__height
|
||||
Snygg.Background -> R.string.snygg__property_name__background
|
||||
Snygg.Foreground -> R.string.snygg__property_name__foreground
|
||||
Snygg.BorderColor -> R.string.snygg__property_name__border_color
|
||||
Snygg.BorderStyle -> R.string.snygg__property_name__border_style
|
||||
Snygg.BorderWidth -> R.string.snygg__property_name__border_width
|
||||
Snygg.FontFamily -> R.string.snygg__property_name__font_family
|
||||
Snygg.FontSize -> R.string.snygg__property_name__font_size
|
||||
Snygg.FontStyle -> R.string.snygg__property_name__font_style
|
||||
Snygg.FontVariant -> R.string.snygg__property_name__font_variant
|
||||
Snygg.FontWeight -> R.string.snygg__property_name__font_weight
|
||||
Snygg.ShadowElevation -> R.string.snygg__property_name__shadow_elevation
|
||||
Snygg.Shape -> R.string.snygg__property_name__shape
|
||||
"--primary" -> R.string.snygg__property_name__var_primary
|
||||
"--primary-variant" -> R.string.snygg__property_name__var_primary_variant
|
||||
"--secondary" -> R.string.snygg__property_name__var_secondary
|
||||
"--secondary-variant" -> R.string.snygg__property_name__var_secondary_variant
|
||||
"--background" -> R.string.snygg__property_name__var_background
|
||||
"--surface" -> R.string.snygg__property_name__var_surface
|
||||
"--surface-variant" -> R.string.snygg__property_name__var_surface_variant
|
||||
"--on-primary" -> R.string.snygg__property_name__var_on_primary
|
||||
"--on-secondary" -> R.string.snygg__property_name__var_on_secondary
|
||||
"--on-background" -> R.string.snygg__property_name__var_on_background
|
||||
"--on-surface" -> R.string.snygg__property_name__var_on_surface
|
||||
"--on-surface-variant" -> R.string.snygg__property_name__var_on_surface_variant
|
||||
"--shape" -> R.string.snygg__property_name__var_shape
|
||||
"--shape-variant" -> R.string.snygg__property_name__var_shape_variant
|
||||
else -> null
|
||||
}
|
||||
else -> PropertyNameMap[propertyName]
|
||||
}.let { resId ->
|
||||
when {
|
||||
resId != null -> {
|
||||
stringRes(resId)
|
||||
getString(resId)
|
||||
}
|
||||
propertyName.isBlank() -> {
|
||||
stringRes(R.string.general__select_dropdown_value_placeholder)
|
||||
getString(R.string.general__select_dropdown_value_placeholder)
|
||||
}
|
||||
else -> {
|
||||
propertyName
|
||||
@@ -152,15 +126,14 @@ internal fun translatePropertyName(propertyName: String, level: SnyggLevel): Str
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun translatePropertyValue(
|
||||
internal fun Context.translatePropertyValue(
|
||||
propertyValue: SnyggValue,
|
||||
level: SnyggLevel,
|
||||
displayColorsAs: DisplayColorsAs,
|
||||
colorRepresentation: ColorRepresentation,
|
||||
): String {
|
||||
return when (propertyValue) {
|
||||
is SnyggSolidColorValue -> remember(propertyValue.color, displayColorsAs) {
|
||||
buildColorString(propertyValue.color, displayColorsAs)
|
||||
is SnyggStaticColorValue -> {
|
||||
colorRepresentation.formatColor(propertyValue.color, withAlpha = true)
|
||||
}
|
||||
else -> when (level) {
|
||||
SnyggLevel.DEVELOPER -> null
|
||||
@@ -176,51 +149,38 @@ internal fun translatePropertyValue(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun buildColorString(color: Color, displayColorsAs: DisplayColorsAs): String {
|
||||
return when (displayColorsAs) {
|
||||
DisplayColorsAs.HEX8 -> buildString {
|
||||
append(UnicodeCtrlChar.LeftToRightIsolate)
|
||||
append("#")
|
||||
append((color.red * RgbaColor.RedMax).roundToInt().toString(16).padStart(2, '0'))
|
||||
append((color.green * RgbaColor.GreenMax).roundToInt().toString(16).padStart(2, '0'))
|
||||
append((color.blue * RgbaColor.BlueMax).roundToInt().toString(16).padStart(2, '0'))
|
||||
append((color.alpha * 0xFF).roundToInt().toString(16).padStart(2, '0'))
|
||||
append(UnicodeCtrlChar.PopDirectionalIsolate)
|
||||
}
|
||||
DisplayColorsAs.RGBA -> buildString {
|
||||
append(UnicodeCtrlChar.LeftToRightIsolate)
|
||||
append("rgba(")
|
||||
append((color.red * RgbaColor.RedMax).roundToInt())
|
||||
append(",")
|
||||
append((color.green * RgbaColor.GreenMax).roundToInt())
|
||||
append(",")
|
||||
append((color.blue * RgbaColor.BlueMax).roundToInt())
|
||||
append(",")
|
||||
append(color.alpha)
|
||||
append(")")
|
||||
append(UnicodeCtrlChar.PopDirectionalIsolate)
|
||||
}
|
||||
}
|
||||
}
|
||||
private val PropertyValueEncoderNameMap = mapOf(
|
||||
SnyggUndefinedValue to R.string.general__select_dropdown_value_placeholder,
|
||||
SnyggInheritValue to R.string.snygg__property_value__explicit_inherit,
|
||||
SnyggDefinedVarValue to R.string.snygg__property_value__defined_var,
|
||||
SnyggYesValue to R.string.snygg__property_value__yes,
|
||||
SnyggNoValue to R.string.snygg__property_value__no,
|
||||
SnyggStaticColorValue to R.string.snygg__property_value__solid_color,
|
||||
SnyggDynamicLightColorValue to R.string.snygg__property_value__material_you_light_color,
|
||||
SnyggDynamicDarkColorValue to R.string.snygg__property_value__material_you_dark_color,
|
||||
SnyggGenericFontFamilyValue to R.string.snygg__property_value__font_family_generic,
|
||||
SnyggCustomFontFamilyValue to R.string.snygg__property_value__font_family_custom,
|
||||
SnyggFontStyleValue to R.string.snygg__property_value__font_style,
|
||||
SnyggFontWeightValue to R.string.snygg__property_value__font_weight,
|
||||
SnyggPaddingValue to R.string.snygg__property_value__padding,
|
||||
SnyggRectangleShapeValue to R.string.snygg__property_value__rectangle_shape,
|
||||
SnyggCircleShapeValue to R.string.snygg__property_value__circle_shape,
|
||||
SnyggCutCornerDpShapeValue to R.string.snygg__property_value__cut_corner_shape_dp,
|
||||
SnyggCutCornerPercentShapeValue to R.string.snygg__property_value__cut_corner_shape_percent,
|
||||
SnyggRoundedCornerDpShapeValue to R.string.snygg__property_value__rounded_corner_shape_dp,
|
||||
SnyggRoundedCornerPercentShapeValue to R.string.snygg__property_value__rounded_corner_shape_percent,
|
||||
SnyggDpSizeValue to R.string.snygg__property_value__dp_size,
|
||||
SnyggSpSizeValue to R.string.snygg__property_value__sp_size,
|
||||
SnyggPercentageSizeValue to R.string.snygg__property_value__percentage_size,
|
||||
SnyggContentScaleValue to R.string.snygg__property_value__content_scale,
|
||||
SnyggTextAlignValue to R.string.snygg__property_value__text_align,
|
||||
SnyggTextDecorationLineValue to R.string.snygg__property_value__text_decoration_line,
|
||||
SnyggTextMaxLinesValue to R.string.snygg__property_value__text_max_lines,
|
||||
SnyggTextOverflowValue to R.string.snygg__property_value__text_overflow,
|
||||
SnyggUriValue to R.string.snygg__property_value__uri,
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun translatePropertyValueEncoderName(encoder: SnyggValueEncoder): String {
|
||||
return when (encoder) {
|
||||
SnyggImplicitInheritValue -> R.string.general__select_dropdown_value_placeholder
|
||||
SnyggExplicitInheritValue -> R.string.snygg__property_value__explicit_inherit
|
||||
SnyggSolidColorValue -> R.string.snygg__property_value__solid_color
|
||||
SnyggMaterialYouLightColorValue -> R.string.snygg__property_value__material_you_light_color
|
||||
SnyggMaterialYouDarkColorValue -> R.string.snygg__property_value__material_you_dark_color
|
||||
SnyggRectangleShapeValue -> R.string.snygg__property_value__rectangle_shape
|
||||
SnyggCircleShapeValue -> R.string.snygg__property_value__circle_shape
|
||||
SnyggCutCornerDpShapeValue -> R.string.snygg__property_value__cut_corner_shape_dp
|
||||
SnyggCutCornerPercentShapeValue -> R.string.snygg__property_value__cut_corner_shape_percent
|
||||
SnyggRoundedCornerDpShapeValue -> R.string.snygg__property_value__rounded_corner_shape_dp
|
||||
SnyggRoundedCornerPercentShapeValue -> R.string.snygg__property_value__rounded_corner_shape_percent
|
||||
SnyggDpSizeValue -> R.string.snygg__property_value__dp_size
|
||||
SnyggSpSizeValue -> R.string.snygg__property_value__sp_size
|
||||
SnyggPercentageSizeValue -> R.string.snygg__property_value__percentage_size
|
||||
SnyggDefinedVarValue -> R.string.snygg__property_value__defined_var
|
||||
else -> null
|
||||
}.let { if (it != null) { stringRes(it) } else { encoder::class.simpleName ?: "" } }.toString()
|
||||
internal fun Context.translatePropertyValueEncoderName(encoder: SnyggValueEncoder): String {
|
||||
return PropertyValueEncoderNameMap[encoder]?.let { getString(it) }
|
||||
?: encoder::class.simpleNameOrEnclosing().orEmpty()
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisErrorCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisHyperlinkText
|
||||
@@ -47,6 +48,7 @@ import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@@ -79,28 +81,18 @@ fun TypingScreen() = FlorisScreen {
|
||||
summary = stringRes(R.string.pref__suggestion__block_possibly_offensive__summary),
|
||||
enabledIf = { prefs.suggestion.enabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.suggestion.clipboardContentEnabled,
|
||||
title = stringRes(R.string.pref__suggestion__clipboard_content_enabled__label),
|
||||
summary = stringRes(R.string.pref__suggestion__clipboard_content_enabled__summary),
|
||||
enabledIf = { prefs.suggestion.enabled isEqualTo true },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.suggestion.clipboardContentTimeout,
|
||||
title = stringRes(R.string.pref__suggestion__clipboard_content_timeout__label),
|
||||
valueLabel = { stringRes(R.string.pref__suggestion__clipboard_content_timeout__summary, "v" to it) },
|
||||
min = 30,
|
||||
max = 300,
|
||||
stepIncrement = 5,
|
||||
enabledIf = { prefs.suggestion.enabled isEqualTo true },
|
||||
visibleIf = { prefs.suggestion.clipboardContentEnabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.suggestion.api30InlineSuggestionsEnabled,
|
||||
title = stringRes(R.string.pref__suggestion__api30_inline_suggestions_enabled__label),
|
||||
summary = stringRes(R.string.pref__suggestion__api30_inline_suggestions_enabled__summary),
|
||||
visibleIf = { AndroidVersion.ATLEAST_API30_R },
|
||||
)
|
||||
ListPreference(
|
||||
prefs.suggestion.incognitoMode,
|
||||
icon = vectorResource(id = R.drawable.ic_incognito),
|
||||
title = stringRes(R.string.pref__suggestion__incognito_mode__label),
|
||||
entries = enumDisplayEntriesOf(IncognitoMode::class),
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__correction__title)) {
|
||||
|
||||
@@ -45,9 +45,6 @@ import dev.patrickgold.florisboard.app.FlorisAppActivity
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import dev.patrickgold.florisboard.lib.util.launchActivity
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisBulletSpacer
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreenScope
|
||||
@@ -56,9 +53,12 @@ import dev.patrickgold.florisboard.lib.compose.FlorisStepLayout
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisStepState
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
|
||||
import dev.patrickgold.florisboard.lib.util.launchActivity
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceUiScope
|
||||
import kotlinx.coroutines.delay
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -105,128 +105,65 @@ private fun FlorisScreenScope.content(
|
||||
hasNotificationPermission: NotificationPermissionState,
|
||||
) {
|
||||
|
||||
// Show screen without notification permission if the android version is below android 13.
|
||||
if (AndroidVersion.ATMOST_API32_S_V2) {
|
||||
val stepState = rememberSaveable(saver = FlorisStepState.Saver) {
|
||||
val initStep = when {
|
||||
!isFlorisBoardEnabled -> Steps.EnableIme.id
|
||||
!isFlorisBoardSelected -> Steps.SelectIme.id
|
||||
hasNotificationPermission == NotificationPermissionState.NOT_SET && AndroidVersion.ATLEAST_API33_T -> Steps.SelectNotification.id
|
||||
else -> Steps.FinishUp.id
|
||||
}
|
||||
FlorisStepState.new(init = initStep)
|
||||
}
|
||||
|
||||
val stepState = rememberSaveable(saver = FlorisStepState.Saver) {
|
||||
val initStep = when {
|
||||
!isFlorisBoardEnabled -> Steps.WithoutNotifications.EnableIme.id
|
||||
!isFlorisBoardSelected -> Steps.WithoutNotifications.SelectIme.id
|
||||
else -> Steps.WithoutNotifications.FinishUp.id
|
||||
}
|
||||
FlorisStepState.new(init = initStep)
|
||||
content {
|
||||
LaunchedEffect(isFlorisBoardEnabled, isFlorisBoardSelected, hasNotificationPermission) {
|
||||
stepState.setCurrentAuto(
|
||||
when {
|
||||
!isFlorisBoardEnabled -> Steps.EnableIme.id
|
||||
!isFlorisBoardSelected -> Steps.SelectIme.id
|
||||
hasNotificationPermission == NotificationPermissionState.NOT_SET && AndroidVersion.ATLEAST_API33_T -> Steps.SelectNotification.id
|
||||
else -> Steps.FinishUp.id
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
content {
|
||||
LaunchedEffect(isFlorisBoardEnabled, isFlorisBoardSelected) {
|
||||
stepState.setCurrentAuto(
|
||||
when {
|
||||
!isFlorisBoardEnabled -> Steps.WithoutNotifications.EnableIme.id
|
||||
!isFlorisBoardSelected -> Steps.WithoutNotifications.SelectIme.id
|
||||
else -> Steps.WithoutNotifications.FinishUp.id
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Below block allows to return from the system IME enabler activity
|
||||
// as soon as it gets selected.
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
delay(200L)
|
||||
val isEnabled = InputMethodUtils.isFlorisboardEnabled(context)
|
||||
if (stepState.getCurrentAuto().value == Steps.WithoutNotifications.EnableIme.id &&
|
||||
stepState.getCurrentManual().value == -1 &&
|
||||
!isFlorisBoardEnabled &&
|
||||
!isFlorisBoardSelected &&
|
||||
isEnabled
|
||||
) {
|
||||
context.launchActivity(FlorisAppActivity::class) {
|
||||
it.flags = (Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
// Below block allows to return from the system IME enabler activity
|
||||
// as soon as it gets selected.
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
delay(200L)
|
||||
val isEnabled = InputMethodUtils.isFlorisboardEnabled(context)
|
||||
if (stepState.getCurrentAuto().value == Steps.EnableIme.id &&
|
||||
stepState.getCurrentManual().value == -1 &&
|
||||
!isFlorisBoardEnabled &&
|
||||
!isFlorisBoardSelected &&
|
||||
hasNotificationPermission == NotificationPermissionState.NOT_SET &&
|
||||
isEnabled
|
||||
) {
|
||||
context.launchActivity(FlorisAppActivity::class) {
|
||||
it.flags = (Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
}
|
||||
}
|
||||
FlorisStepLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
stepState = stepState,
|
||||
header = {
|
||||
StepText(stringRes(R.string.setup__intro_message))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
},
|
||||
steps = steps(
|
||||
context, navController, requestNotification
|
||||
),
|
||||
footer = {
|
||||
footer(context)
|
||||
},
|
||||
)
|
||||
}
|
||||
// Show the screen with notification permission on android 13+
|
||||
} else {
|
||||
val stepState = rememberSaveable(saver = FlorisStepState.Saver) {
|
||||
val initStep = when {
|
||||
!isFlorisBoardEnabled -> Steps.WithNotifications.EnableIme.id
|
||||
!isFlorisBoardSelected -> Steps.WithNotifications.SelectIme.id
|
||||
hasNotificationPermission == NotificationPermissionState.NOT_SET -> Steps.WithNotifications.SelectNotification.id
|
||||
else -> Steps.WithNotifications.FinishUp.id
|
||||
}
|
||||
FlorisStepState.new(init = initStep)
|
||||
}
|
||||
|
||||
content {
|
||||
LaunchedEffect(isFlorisBoardEnabled, isFlorisBoardSelected, hasNotificationPermission) {
|
||||
stepState.setCurrentAuto(
|
||||
when {
|
||||
!isFlorisBoardEnabled -> Steps.WithNotifications.EnableIme.id
|
||||
!isFlorisBoardSelected -> Steps.WithNotifications.SelectIme.id
|
||||
hasNotificationPermission == NotificationPermissionState.NOT_SET -> Steps.WithNotifications.SelectNotification.id
|
||||
else -> Steps.WithNotifications.FinishUp.id
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Below block allows to return from the system IME enabler activity
|
||||
// as soon as it gets selected.
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
delay(200L)
|
||||
val isEnabled = InputMethodUtils.isFlorisboardEnabled(context)
|
||||
if (stepState.getCurrentAuto().value == Steps.WithNotifications.EnableIme.id &&
|
||||
stepState.getCurrentManual().value == -1 &&
|
||||
!isFlorisBoardEnabled &&
|
||||
!isFlorisBoardSelected &&
|
||||
hasNotificationPermission == NotificationPermissionState.NOT_SET &&
|
||||
isEnabled
|
||||
) {
|
||||
context.launchActivity(FlorisAppActivity::class) {
|
||||
it.flags = (Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FlorisStepLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
stepState = stepState,
|
||||
header = {
|
||||
StepText(stringRes(R.string.setup__intro_message))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
},
|
||||
steps = steps(
|
||||
context, navController, requestNotification
|
||||
),
|
||||
footer = {
|
||||
footer(context)
|
||||
},
|
||||
)
|
||||
}
|
||||
FlorisStepLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
stepState = stepState,
|
||||
header = {
|
||||
StepText(stringRes(R.string.setup__intro_message))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
},
|
||||
steps = steps(
|
||||
context, navController, requestNotification
|
||||
),
|
||||
footer = {
|
||||
footer(context)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,105 +192,60 @@ private fun footer(context: Context) {
|
||||
private fun PreferenceUiScope<AppPrefs>.steps(
|
||||
context: Context,
|
||||
navController: NavController,
|
||||
requestNotification: ManagedActivityResultLauncher<String, Boolean>
|
||||
requestNotification: ManagedActivityResultLauncher<String, Boolean>,
|
||||
): List<FlorisStep> {
|
||||
return if (AndroidVersion.ATMOST_API32_S_V2) {
|
||||
listOf(
|
||||
|
||||
return listOfNotNull(
|
||||
FlorisStep(
|
||||
id = Steps.EnableIme.id,
|
||||
title = stringRes(R.string.setup__enable_ime__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__enable_ime__description))
|
||||
StepButton(label = stringRes(R.string.setup__enable_ime__open_settings_btn)) {
|
||||
InputMethodUtils.showImeEnablerActivity(context)
|
||||
}
|
||||
},
|
||||
FlorisStep(
|
||||
id = Steps.SelectIme.id,
|
||||
title = stringRes(R.string.setup__select_ime__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__select_ime__description))
|
||||
StepButton(label = stringRes(R.string.setup__select_ime__switch_keyboard_btn)) {
|
||||
InputMethodUtils.showImePicker(context)
|
||||
}
|
||||
},
|
||||
if (AndroidVersion.ATLEAST_API33_T) {
|
||||
FlorisStep(
|
||||
id = Steps.WithoutNotifications.EnableIme.id,
|
||||
title = stringRes(R.string.setup__enable_ime__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__enable_ime__description))
|
||||
StepButton(label = stringRes(R.string.setup__enable_ime__open_settings_btn)) {
|
||||
InputMethodUtils.showImeEnablerActivity(context)
|
||||
}
|
||||
},
|
||||
FlorisStep(
|
||||
id = Steps.WithoutNotifications.SelectIme.id,
|
||||
title = stringRes(R.string.setup__select_ime__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__select_ime__description))
|
||||
StepButton(label = stringRes(R.string.setup__select_ime__switch_keyboard_btn)) {
|
||||
InputMethodUtils.showImePicker(context)
|
||||
}
|
||||
},
|
||||
FlorisStep(
|
||||
id = Steps.WithoutNotifications.FinishUp.id,
|
||||
title = stringRes(R.string.setup__finish_up__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__finish_up__description_p1))
|
||||
StepText(stringRes(R.string.setup__finish_up__description_p2))
|
||||
StepButton(label = stringRes(R.string.setup__finish_up__finish_btn)) {
|
||||
this@steps.prefs.internal.isImeSetUp.set(true)
|
||||
navController.navigate(Routes.Settings.Home) {
|
||||
popUpTo(Routes.Setup.Screen) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
FlorisStep(
|
||||
id = Steps.WithNotifications.EnableIme.id,
|
||||
title = stringRes(R.string.setup__enable_ime__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__enable_ime__description))
|
||||
StepButton(label = stringRes(R.string.setup__enable_ime__open_settings_btn)) {
|
||||
InputMethodUtils.showImeEnablerActivity(context)
|
||||
}
|
||||
},
|
||||
FlorisStep(
|
||||
id = Steps.WithNotifications.SelectIme.id,
|
||||
title = stringRes(R.string.setup__select_ime__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__select_ime__description))
|
||||
StepButton(label = stringRes(R.string.setup__select_ime__switch_keyboard_btn)) {
|
||||
InputMethodUtils.showImePicker(context)
|
||||
}
|
||||
},
|
||||
FlorisStep(
|
||||
id = Steps.WithNotifications.SelectNotification.id,
|
||||
title = stringRes(R.string.setup__grant_notification_permission__title)
|
||||
id = Steps.SelectNotification.id,
|
||||
title = stringRes(R.string.setup__grant_notification_permission__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__grant_notification_permission__description))
|
||||
StepButton(stringRes(R.string.setup__grant_notification_permission__btn)) {
|
||||
if (AndroidVersion.ATLEAST_API33_T) {
|
||||
requestNotification.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
requestNotification.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
} else null,
|
||||
FlorisStep(
|
||||
id = Steps.FinishUp.id,
|
||||
title = stringRes(R.string.setup__finish_up__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__finish_up__description_p1))
|
||||
StepText(stringRes(R.string.setup__finish_up__description_p2))
|
||||
StepButton(label = stringRes(R.string.setup__finish_up__finish_btn)) {
|
||||
this@steps.prefs.internal.isImeSetUp.set(true)
|
||||
navController.navigate(Routes.Settings.Home) {
|
||||
popUpTo(Routes.Setup.Screen) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
},
|
||||
FlorisStep(
|
||||
id = Steps.WithNotifications.FinishUp.id,
|
||||
title = stringRes(R.string.setup__finish_up__title),
|
||||
) {
|
||||
StepText(stringRes(R.string.setup__finish_up__description_p1))
|
||||
StepText(stringRes(R.string.setup__finish_up__description_p2))
|
||||
StepButton(label = stringRes(R.string.setup__finish_up__finish_btn)) {
|
||||
this@steps.prefs.internal.isImeSetUp.set(true)
|
||||
navController.navigate(Routes.Settings.Home) {
|
||||
popUpTo(Routes.Setup.Screen) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private sealed class Steps(val id: Int) {
|
||||
sealed class WithoutNotifications(id: Int) : Steps(id) {
|
||||
data object EnableIme : WithoutNotifications(id = 1)
|
||||
data object SelectIme : WithoutNotifications(id = 2)
|
||||
data object FinishUp : WithoutNotifications(id = 3)
|
||||
}
|
||||
|
||||
sealed class WithNotifications(id: Int) : Steps(id) {
|
||||
data object EnableIme : WithNotifications(id = 1)
|
||||
data object SelectIme : WithNotifications(id = 2)
|
||||
data object SelectNotification : WithNotifications(id = 3)
|
||||
data object FinishUp : WithNotifications(id = 4)
|
||||
}
|
||||
data object EnableIme : Steps(id = 1)
|
||||
data object SelectIme : Steps(id = 2)
|
||||
data object SelectNotification : Steps(id = 3)
|
||||
data object FinishUp : Steps(id = 4)
|
||||
}
|
||||
|
||||
@@ -31,20 +31,16 @@ import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
@@ -54,9 +50,7 @@ import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.ToggleOff
|
||||
import androidx.compose.material.icons.filled.ToggleOn
|
||||
import androidx.compose.material.icons.filled.Videocam
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -73,12 +67,6 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
@@ -90,16 +78,12 @@ import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.media.KeyboardLikeButton
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButtonWithInnerPadding
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisStaggeredVerticalGrid
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.autoMirrorForRtl
|
||||
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.lib.util.NetworkUtils
|
||||
@@ -108,20 +92,14 @@ import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.snygg.SnyggPropertySet
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggClip
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggIconButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
private val ContentPadding = PaddingValues(horizontal = 4.dp)
|
||||
private val ItemMargin = PaddingValues(all = 6.dp)
|
||||
private val ItemPadding = PaddingValues(vertical = 8.dp, horizontal = 12.dp)
|
||||
private val DescriptionPadding = PaddingValues(top = 4.dp, start = 12.dp, end = 12.dp)
|
||||
private val ItemWidth = 200.dp
|
||||
private val DialogWidth = 240.dp
|
||||
|
||||
@@ -139,69 +117,79 @@ fun ClipboardInputLayout(
|
||||
val historyEnabled by prefs.clipboard.historyEnabled.observeAsState()
|
||||
val history by clipboardManager.history.observeAsNonNullState()
|
||||
|
||||
val innerHeight = FlorisImeSizing.imeUiHeight() - FlorisImeSizing.smartbarHeight
|
||||
var popupItem by remember(history) { mutableStateOf<ClipboardItem?>(null) }
|
||||
var showClearAllHistory by remember { mutableStateOf(false) }
|
||||
|
||||
val headerStyle = FlorisImeTheme.style.get(FlorisImeUi.ClipboardHeader)
|
||||
val itemStyle = FlorisImeTheme.style.get(FlorisImeUi.ClipboardItem)
|
||||
val popupStyle = FlorisImeTheme.style.get(FlorisImeUi.ClipboardItemPopup)
|
||||
val enableHistoryButtonStyle = FlorisImeTheme.style.get(FlorisImeUi.ClipboardEnableHistoryButton)
|
||||
|
||||
fun isPopupSurfaceActive() = popupItem != null || showClearAllHistory
|
||||
|
||||
@Composable
|
||||
fun HeaderRow() {
|
||||
Row(
|
||||
SnyggRow(FlorisImeUi.ClipboardHeader.elementName,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.smartbarHeight)
|
||||
.snyggBackground(context, headerStyle),
|
||||
.height(FlorisImeSizing.smartbarHeight),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FlorisIconButtonWithInnerPadding(
|
||||
val sizeModifier = Modifier
|
||||
.sizeIn(maxHeight = FlorisImeSizing.smartbarHeight)
|
||||
.aspectRatio(1f)
|
||||
SnyggIconButton(
|
||||
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
|
||||
onClick = { keyboardManager.activeState.imeUiMode = ImeUiMode.TEXT },
|
||||
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
iconColor = headerStyle.foreground.solidColor(context),
|
||||
)
|
||||
Text(
|
||||
modifier = sizeModifier,
|
||||
) {
|
||||
SnyggIcon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
)
|
||||
}
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardHeaderText.elementName,
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringRes(R.string.clipboard__header_title),
|
||||
color = headerStyle.foreground.solidColor(context),
|
||||
fontSize = headerStyle.fontSize.spSize(),
|
||||
)
|
||||
FlorisIconButtonWithInnerPadding(
|
||||
SnyggIconButton(
|
||||
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
|
||||
onClick = { prefs.clipboard.historyEnabled.set(!historyEnabled) },
|
||||
modifier = Modifier.autoMirrorForRtl(),
|
||||
icon = if (historyEnabled) {
|
||||
Icons.Default.ToggleOn
|
||||
} else {
|
||||
Icons.Default.ToggleOff
|
||||
},
|
||||
iconColor = headerStyle.foreground.solidColor(context),
|
||||
modifier = sizeModifier.autoMirrorForRtl(),
|
||||
enabled = !deviceLocked && !isPopupSurfaceActive(),
|
||||
)
|
||||
FlorisIconButtonWithInnerPadding(
|
||||
) {
|
||||
SnyggIcon(
|
||||
imageVector = if (historyEnabled) {
|
||||
Icons.Default.ToggleOn
|
||||
} else {
|
||||
Icons.Default.ToggleOff
|
||||
},
|
||||
)
|
||||
}
|
||||
SnyggIconButton(
|
||||
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
|
||||
onClick = { showClearAllHistory = true },
|
||||
modifier = Modifier.autoMirrorForRtl(),
|
||||
icon = Icons.Default.ClearAll,
|
||||
iconColor = headerStyle.foreground.solidColor(context),
|
||||
modifier = sizeModifier.autoMirrorForRtl(),
|
||||
enabled = !deviceLocked && historyEnabled && history.all.isNotEmpty() && !isPopupSurfaceActive(),
|
||||
)
|
||||
FlorisIconButtonWithInnerPadding(
|
||||
) {
|
||||
SnyggIcon(
|
||||
imageVector = Icons.Default.ClearAll,
|
||||
)
|
||||
}
|
||||
SnyggIconButton(
|
||||
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
|
||||
onClick = {
|
||||
context.showShortToast("TODO: implement inline clip item editing")
|
||||
},
|
||||
icon = Icons.Default.Edit,
|
||||
iconColor = headerStyle.foreground.solidColor(context),
|
||||
modifier = sizeModifier,
|
||||
enabled = !deviceLocked && historyEnabled && !isPopupSurfaceActive(),
|
||||
)
|
||||
) {
|
||||
SnyggIcon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
)
|
||||
}
|
||||
KeyboardLikeButton(
|
||||
modifier = sizeModifier,
|
||||
inputEventDispatcher = keyboardManager.inputEventDispatcher,
|
||||
keyData = TextKeyData.DELETE,
|
||||
element = FlorisImeUi.ClipboardHeader,
|
||||
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Outlined.Backspace, null)
|
||||
SnyggIcon(imageVector = Icons.AutoMirrored.Outlined.Backspace)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,16 +198,11 @@ fun ClipboardInputLayout(
|
||||
@Composable
|
||||
fun ClipItemView(
|
||||
item: ClipboardItem,
|
||||
style: SnyggPropertySet,
|
||||
contentScrollInsteadOfClip: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SnyggSurface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(ItemMargin),
|
||||
style = style,
|
||||
clip = true,
|
||||
SnyggBox(FlorisImeUi.ClipboardItem.elementName,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
clickAndSemanticsModifier = Modifier.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(),
|
||||
@@ -251,14 +234,9 @@ fun ClipboardInputLayout(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(ItemPadding),
|
||||
SnyggText(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = bitmap.exceptionOrNull()?.message ?: "Unknown error",
|
||||
style = TextStyle(textDirection = TextDirection.Ltr),
|
||||
color = Color.Red,
|
||||
fontSize = style.fontSize.spSize(),
|
||||
)
|
||||
}
|
||||
} else if (item.type == ItemType.VIDEO) {
|
||||
@@ -298,31 +276,20 @@ fun ClipboardInputLayout(
|
||||
tint = Color.Black,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(ItemPadding),
|
||||
SnyggText(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = bitmap.exceptionOrNull()?.message ?: "Unknown error",
|
||||
style = TextStyle(textDirection = TextDirection.Ltr),
|
||||
color = Color.Red,
|
||||
fontSize = style.fontSize.spSize(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val text = item.stringRepresentation()
|
||||
Column {
|
||||
ClipTextItemDescription(text, style)
|
||||
Text(
|
||||
ClipTextItemDescription(text)
|
||||
SnyggText(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.run { if (contentScrollInsteadOfClip) this.florisVerticalScroll() else this }
|
||||
.padding(ItemPadding),
|
||||
.run { if (contentScrollInsteadOfClip) this.florisVerticalScroll() else this },
|
||||
text = item.displayText(),
|
||||
style = TextStyle(textDirection = TextDirection.ContentOrLtr),
|
||||
color = style.foreground.solidColor(context),
|
||||
fontSize = style.fontSize.spSize(),
|
||||
maxLines = if (contentScrollInsteadOfClip) Int.MAX_VALUE else 5,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -331,15 +298,12 @@ fun ClipboardInputLayout(
|
||||
|
||||
@Composable
|
||||
fun HistoryMainView() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(innerHeight),
|
||||
SnyggBox(FlorisImeUi.ClipboardContent.elementName,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
val historyAlpha by animateFloatAsState(targetValue = if (isPopupSurfaceActive()) 0.12f else 1f)
|
||||
Column(
|
||||
SnyggColumn(
|
||||
modifier = Modifier
|
||||
.padding(ContentPadding)
|
||||
.fillMaxSize()
|
||||
.alpha(historyAlpha)
|
||||
.florisVerticalScroll(),
|
||||
@@ -347,42 +311,38 @@ fun ClipboardInputLayout(
|
||||
if (history.pinned.isNotEmpty()) {
|
||||
ClipCategoryTitle(
|
||||
text = stringRes(R.string.clipboard__group_pinned),
|
||||
style = itemStyle,
|
||||
)
|
||||
FlorisStaggeredVerticalGrid(maxColumnWidth = ItemWidth) {
|
||||
for (item in history.pinned) {
|
||||
ClipItemView(item, itemStyle, contentScrollInsteadOfClip = false)
|
||||
ClipItemView(item, contentScrollInsteadOfClip = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (history.recent.isNotEmpty()) {
|
||||
ClipCategoryTitle(
|
||||
text = stringRes(R.string.clipboard__group_recent),
|
||||
style = itemStyle,
|
||||
)
|
||||
FlorisStaggeredVerticalGrid(maxColumnWidth = ItemWidth) {
|
||||
for (item in history.recent) {
|
||||
ClipItemView(item, itemStyle, contentScrollInsteadOfClip = false)
|
||||
ClipItemView(item, contentScrollInsteadOfClip = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (history.other.isNotEmpty()) {
|
||||
ClipCategoryTitle(
|
||||
text = stringRes(R.string.clipboard__group_other),
|
||||
style = itemStyle,
|
||||
)
|
||||
FlorisStaggeredVerticalGrid(maxColumnWidth = ItemWidth) {
|
||||
for (item in history.other) {
|
||||
ClipItemView(item, itemStyle, contentScrollInsteadOfClip = false)
|
||||
ClipItemView(item, contentScrollInsteadOfClip = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (popupItem != null) {
|
||||
Row(
|
||||
SnyggRow(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(ContentPadding)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures { popupItem = null }
|
||||
},
|
||||
@@ -392,17 +352,9 @@ fun ClipboardInputLayout(
|
||||
ClipItemView(
|
||||
modifier = Modifier.widthIn(max = ItemWidth),
|
||||
item = popupItem!!,
|
||||
style = itemStyle,
|
||||
contentScrollInsteadOfClip = true,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(ItemMargin)
|
||||
.snyggShadow(popupStyle)
|
||||
.snyggBorder(context, popupStyle)
|
||||
.snyggBackground(context, popupStyle)
|
||||
.snyggClip(popupStyle),
|
||||
) {
|
||||
SnyggColumn(FlorisImeUi.ClipboardItemPopup.elementName) {
|
||||
PopupAction(
|
||||
iconId = R.drawable.ic_pin,
|
||||
text = stringRes(if (popupItem!!.isPinned) {
|
||||
@@ -410,7 +362,6 @@ fun ClipboardInputLayout(
|
||||
} else {
|
||||
R.string.clip__pin_item
|
||||
}),
|
||||
style = popupStyle,
|
||||
) {
|
||||
if (popupItem!!.isPinned) {
|
||||
clipboardManager.unpinClip(popupItem!!)
|
||||
@@ -422,7 +373,6 @@ fun ClipboardInputLayout(
|
||||
PopupAction(
|
||||
iconId = R.drawable.ic_delete,
|
||||
text = stringRes(R.string.clip__delete_item),
|
||||
style = popupStyle,
|
||||
) {
|
||||
clipboardManager.deleteClip(popupItem!!)
|
||||
popupItem = null
|
||||
@@ -430,7 +380,6 @@ fun ClipboardInputLayout(
|
||||
PopupAction(
|
||||
iconId = R.drawable.ic_content_paste,
|
||||
text = stringRes(R.string.clip__paste_item),
|
||||
style = popupStyle,
|
||||
) {
|
||||
clipboardManager.pasteItem(popupItem!!)
|
||||
popupItem = null
|
||||
@@ -439,51 +388,53 @@ fun ClipboardInputLayout(
|
||||
}
|
||||
}
|
||||
if (showClearAllHistory) {
|
||||
Row(
|
||||
SnyggRow(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(ContentPadding)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures { showClearAllHistory = false }
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
) {
|
||||
Column(
|
||||
SnyggColumn(
|
||||
elementName = FlorisImeUi.ClipboardClearAllDialog.elementName,
|
||||
modifier = Modifier
|
||||
.width(DialogWidth)
|
||||
.snyggShadow(popupStyle)
|
||||
.snyggBorder(context, popupStyle)
|
||||
.snyggBackground(context, popupStyle)
|
||||
.snyggClip(popupStyle)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures { /* Do nothing */ }
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardClearAllDialogMessage.elementName,
|
||||
text = stringRes(R.string.clipboard__confirm_clear_history__message),
|
||||
color = popupStyle.foreground.solidColor(context),
|
||||
)
|
||||
Row(modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||
SnyggRow(FlorisImeUi.ClipboardClearAllDialogButtons.elementName) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
FlorisTextButton(
|
||||
SnyggButton(
|
||||
elementName = FlorisImeUi.ClipboardClearAllDialogButton.elementName,
|
||||
attributes = mapOf("action" to "no"),
|
||||
onClick = {
|
||||
showClearAllHistory = false
|
||||
},
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
text = stringRes(R.string.action__no),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = popupStyle.foreground.solidColor(context)),
|
||||
)
|
||||
FlorisTextButton(
|
||||
) {
|
||||
SnyggText(
|
||||
text = stringRes(R.string.action__no),
|
||||
)
|
||||
}
|
||||
SnyggButton(
|
||||
elementName = FlorisImeUi.ClipboardClearAllDialogButton.elementName,
|
||||
attributes = mapOf("action" to "yes"),
|
||||
onClick = {
|
||||
clipboardManager.clearHistory()
|
||||
context.showShortToast(R.string.clipboard__cleared_history)
|
||||
showClearAllHistory = false
|
||||
},
|
||||
text = stringRes(R.string.action__yes),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = popupStyle.foreground.solidColor(context)),
|
||||
)
|
||||
) {
|
||||
SnyggText(
|
||||
text = stringRes(R.string.action__yes),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,98 +444,62 @@ fun ClipboardInputLayout(
|
||||
|
||||
@Composable
|
||||
fun HistoryEmptyView() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(innerHeight)
|
||||
.padding(ContentPadding),
|
||||
SnyggColumn(FlorisImeUi.ClipboardContent.elementName,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp),
|
||||
SnyggText(
|
||||
text = stringRes(R.string.clipboard__empty__title),
|
||||
color = itemStyle.foreground.solidColor(context),
|
||||
fontSize = itemStyle.fontSize.spSize() safeTimes 1.1f,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
SnyggText(
|
||||
text = stringRes(R.string.clipboard__empty__message),
|
||||
color = itemStyle.foreground.solidColor(context),
|
||||
fontSize = itemStyle.fontSize.spSize(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HistoryDisabledView() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(innerHeight)
|
||||
.padding(ContentPadding),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
SnyggColumn(FlorisImeUi.ClipboardContent.elementName,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
SnyggSurface(
|
||||
modifier = Modifier
|
||||
.padding(ItemMargin)
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
style = itemStyle,
|
||||
contentPadding = ItemPadding,
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardHistoryDisabledTitle.elementName,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
text = stringRes(R.string.clipboard__disabled__title),
|
||||
)
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardHistoryDisabledMessage.elementName,
|
||||
text = stringRes(R.string.clipboard__disabled__message),
|
||||
)
|
||||
SnyggButton(FlorisImeUi.ClipboardHistoryDisabledButton.elementName,
|
||||
onClick = { prefs.clipboard.historyEnabled.set(true) },
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
text = stringRes(R.string.clipboard__disabled__title),
|
||||
color = itemStyle.foreground.solidColor(context),
|
||||
fontSize = itemStyle.fontSize.spSize() safeTimes 1.1f,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.clipboard__disabled__message),
|
||||
color = itemStyle.foreground.solidColor(context),
|
||||
fontSize = itemStyle.fontSize.spSize(),
|
||||
)
|
||||
SnyggButton(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.align(Alignment.End),
|
||||
onClick = { prefs.clipboard.historyEnabled.set(true) },
|
||||
style = enableHistoryButtonStyle,
|
||||
text = stringRes(R.string.clipboard__disabled__enable_button)
|
||||
)
|
||||
}
|
||||
SnyggText(
|
||||
text = stringRes(R.string.clipboard__disabled__enable_button),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HistoryLockedView() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(innerHeight)
|
||||
.padding(ContentPadding),
|
||||
SnyggColumn(FlorisImeUi.ClipboardContent.elementName,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp),
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardHistoryLockedTitle.elementName,
|
||||
text = stringRes(R.string.clipboard__locked__title),
|
||||
color = itemStyle.foreground.solidColor(context),
|
||||
fontSize = itemStyle.fontSize.spSize() safeTimes 1.1f,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardHistoryLockedMessage.elementName,
|
||||
text = stringRes(R.string.clipboard__locked__message),
|
||||
color = itemStyle.foreground.solidColor(context),
|
||||
fontSize = itemStyle.fontSize.spSize(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
SnyggColumn(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.imeUiHeight()),
|
||||
@@ -609,29 +524,19 @@ fun ClipboardInputLayout(
|
||||
@Composable
|
||||
private fun ClipCategoryTitle(
|
||||
text: String,
|
||||
style: SnyggPropertySet,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
modifier = modifier
|
||||
.padding(ItemMargin)
|
||||
.padding(top = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
SnyggText(FlorisImeUi.ClipboardSubheader.elementName,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = text.uppercase(),
|
||||
color = style.foreground.solidColor(context),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = style.fontSize.spSize() safeTimes 0.8f,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClipTextItemDescription(
|
||||
text: String,
|
||||
style: SnyggPropertySet,
|
||||
modifier: Modifier = Modifier,
|
||||
): Unit = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
val iconId: Int?
|
||||
val description: String?
|
||||
when {
|
||||
@@ -653,27 +558,16 @@ private fun ClipTextItemDescription(
|
||||
}
|
||||
}
|
||||
if (iconId != null && description != null) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(DescriptionPadding)
|
||||
.offset(y = DescriptionPadding.calculateTopPadding()),
|
||||
SnyggRow(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val fontSize = style.fontSize.spSize()
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.requiredSize(fontSize.toDp()),
|
||||
SnyggIcon(
|
||||
painter = painterResource(id = iconId),
|
||||
contentDescription = null,
|
||||
tint = style.foreground.solidColor(context),
|
||||
)
|
||||
Text(
|
||||
SnyggText(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = description,
|
||||
color = style.foreground.solidColor(context),
|
||||
fontSize = fontSize safeTimes 0.8f,
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -683,29 +577,19 @@ private fun ClipTextItemDescription(
|
||||
private fun PopupAction(
|
||||
@DrawableRes iconId: Int,
|
||||
text: String,
|
||||
style: SnyggPropertySet,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Row(
|
||||
modifier = modifier
|
||||
.width(ItemWidth)
|
||||
.rippleClickable(onClick = onClick)
|
||||
.padding(all = 8.dp),
|
||||
SnyggRow(FlorisImeUi.ClipboardItemPopupAction.elementName,
|
||||
modifier = modifier.rippleClickable(onClick = onClick),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
SnyggIcon(FlorisImeUi.ClipboardItemPopupActionIcon.elementName,
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
tint = style.foreground.solidColor(context),
|
||||
)
|
||||
Text(
|
||||
SnyggText(FlorisImeUi.ClipboardItemPopupActionText.elementName,
|
||||
modifier = Modifier.weight(1f),
|
||||
text = text,
|
||||
color = style.foreground.solidColor(context),
|
||||
fontSize = style.fontSize.spSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +120,8 @@ class FlorisCopyToClipboardActivity : ComponentActivity() {
|
||||
|
||||
setContent {
|
||||
ProvideLocalizedResources(this, forceLayoutDirection = LayoutDirection.Ltr) {
|
||||
val theme by prefs.advanced.settingsTheme.observeAsState()
|
||||
val isMaterialYouAware by prefs.advanced.useMaterialYou.observeAsState()
|
||||
FlorisAppTheme(theme, isMaterialYouAware) {
|
||||
val theme by prefs.other.settingsTheme.observeAsState()
|
||||
FlorisAppTheme(theme) {
|
||||
BottomSheet {
|
||||
Row {
|
||||
Text(
|
||||
@@ -162,7 +161,9 @@ class FlorisCopyToClipboardActivity : ComponentActivity() {
|
||||
Column {
|
||||
content()
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.End).padding(16.dp),
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(16.dp),
|
||||
onClick = { finish() },
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
//containerColor = buttonContainer.background.solidColor(context = context),
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.RadioButtonChecked
|
||||
import androidx.compose.material.icons.filled.RadioButtonUnchecked
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardState
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
@Composable
|
||||
fun SelectSubtypePanel(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val subtypeManager by context.subtypeManager()
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val subtypes by subtypeManager.subtypesFlow.collectAsState()
|
||||
|
||||
val currentlySelected = subtypeManager.activeSubtype.id
|
||||
|
||||
SnyggColumn(FlorisImeUi.SubtypePanel.elementName, modifier = modifier.safeDrawingPadding()) {
|
||||
SnyggRow(
|
||||
elementName = FlorisImeUi.SubtypePanelHeader.elementName,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
SnyggText(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(false) {},
|
||||
text = stringRes(R.string.select_subtype_panel__header),
|
||||
)
|
||||
}
|
||||
|
||||
Box {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
) {
|
||||
items(
|
||||
subtypes,
|
||||
key = {
|
||||
it.id
|
||||
}
|
||||
) {
|
||||
JetPrefListItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.rippleClickable {
|
||||
subtypeManager.switchToSubtypeById(it.id)
|
||||
keyboardManager.activeState.isSubtypeSelectionVisible = false
|
||||
},
|
||||
icon = {
|
||||
if (currentlySelected == it.id) {
|
||||
Icon(Icons.Default.RadioButtonChecked, null)
|
||||
} else {
|
||||
Icon(Icons.Default.RadioButtonUnchecked, null)
|
||||
}
|
||||
},
|
||||
text = it.primaryLocale.displayName(),
|
||||
colors = ListItemDefaults.colors(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun KeyboardState.isSubtypeSelectionShowing(): Boolean {
|
||||
return isSubtypeSelectionVisible
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogDebug
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@@ -227,4 +226,11 @@ class SubtypeManager(context: Context) {
|
||||
prefs.localization.activeSubtypeId.set(newActiveSubtype.id)
|
||||
activeSubtype = newActiveSubtype
|
||||
}
|
||||
|
||||
fun switchToSubtypeById(id: Long) {
|
||||
if (subtypes.any { it.id == id }) {
|
||||
activeSubtype = getSubtypeById(id)!!
|
||||
prefs.localization.activeSubtypeId.set(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
abstract fun determineComposer(composerName: ExtensionComponentName): Composer
|
||||
|
||||
protected open fun shouldDetermineComposingRegion(editorInfo: FlorisEditorInfo): Boolean {
|
||||
return editorInfo.isRichInputEditor
|
||||
return editorInfo.isRichInputEditor && !editorInfo.inputAttributes.flagTextNoSuggestions
|
||||
}
|
||||
|
||||
private suspend fun determineLocalComposing(
|
||||
|
||||
@@ -119,11 +119,11 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
//!instance.inputAttributes.flagTextAutoComplete &&
|
||||
//!instance.inputAttributes.flagTextNoSuggestions
|
||||
}
|
||||
activeState.isIncognitoMode = when (prefs.advanced.incognitoMode.get()) {
|
||||
activeState.isIncognitoMode = when (prefs.suggestion.incognitoMode.get()) {
|
||||
IncognitoMode.FORCE_OFF -> false
|
||||
IncognitoMode.FORCE_ON -> true
|
||||
IncognitoMode.DYNAMIC_ON_OFF -> {
|
||||
editorInfo.imeOptions.flagNoPersonalizedLearning || prefs.advanced.forceIncognitoModeFromDynamic.get()
|
||||
editorInfo.imeOptions.flagNoPersonalizedLearning || prefs.suggestion.forceIncognitoModeFromDynamic.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,5 +43,7 @@ enum class InputShiftState(val value: Int) {
|
||||
fun fromInt(int: Int) = entries.firstOrNull { it.value == int } ?: UNSHIFTED
|
||||
}
|
||||
|
||||
fun attrName() = name.lowercase()
|
||||
|
||||
fun toInt() = value
|
||||
}
|
||||
|
||||
@@ -206,7 +206,8 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
Icons.Default.DeleteSweep
|
||||
}
|
||||
KeyCode.COMPACT_LAYOUT_TO_LEFT,
|
||||
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> {
|
||||
KeyCode.COMPACT_LAYOUT_TO_RIGHT,
|
||||
KeyCode.TOGGLE_COMPACT_LAYOUT -> {
|
||||
context()?.vectorResource(id = R.drawable.ic_accessibility_one_handed)
|
||||
}
|
||||
KeyCode.VOICE_INPUT -> {
|
||||
|
||||
@@ -119,7 +119,7 @@ fun ProvideKeyboardRowBaseHeight(content: @Composable () -> Unit) {
|
||||
|
||||
val heightFactorPortrait by prefs.keyboard.heightFactorPortrait.observeAsTransformingState { it.toFloat() / 100f }
|
||||
val heightFactorLandscape by prefs.keyboard.heightFactorLandscape.observeAsTransformingState { it.toFloat() / 100f }
|
||||
val oneHandedMode by prefs.keyboard.oneHandedMode.observeAsState()
|
||||
val oneHandedMode by prefs.keyboard.oneHandedModeEnabled.observeAsState()
|
||||
val oneHandedModeScaleFactor by prefs.keyboard.oneHandedModeScaleFactor.observeAsTransformingState { it.toFloat() / 100f }
|
||||
|
||||
// Only set systemBarHeights on api 35 or later because https://developer.android.com/about/versions/15/behavior-changes-15#stable-configuration
|
||||
@@ -134,7 +134,7 @@ fun ProvideKeyboardRowBaseHeight(content: @Composable () -> Unit) {
|
||||
) {
|
||||
calcInputViewHeight(resources, systemBarHeights) * when {
|
||||
configuration.isOrientationLandscape() -> heightFactorLandscape
|
||||
else -> heightFactorPortrait * (if (oneHandedMode != OneHandedMode.OFF) oneHandedModeScaleFactor else 1f)
|
||||
else -> heightFactorPortrait * (if (oneHandedMode) oneHandedModeScaleFactor else 1f)
|
||||
}
|
||||
}
|
||||
val smartbarHeight = baseRowHeight * 0.753f
|
||||
|
||||
@@ -55,10 +55,6 @@ import dev.patrickgold.florisboard.ime.text.key.KeyType
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboardCache
|
||||
import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
import dev.patrickgold.florisboard.lib.devtools.LogTopic
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
@@ -67,6 +63,7 @@ import dev.patrickgold.florisboard.lib.uppercase
|
||||
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -75,9 +72,12 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.kotlin.collectIn
|
||||
import org.florisboard.lib.kotlin.collectLatestIn
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
private val DoubleSpacePeriodMatcher = """([^.!?‽\s]\s)""".toRegex()
|
||||
|
||||
@@ -189,7 +189,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
keyboardMode = mode,
|
||||
subtype = subtype,
|
||||
).await()
|
||||
}.await()
|
||||
}
|
||||
val computingEvaluator = ComputingEvaluatorImpl(
|
||||
version = activeEvaluatorVersion++,
|
||||
keyboard = computedKeyboard,
|
||||
@@ -236,11 +236,8 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
return subtypeManager.subtypes.size > 1
|
||||
}
|
||||
|
||||
fun toggleOneHandedMode(isRight: Boolean) {
|
||||
prefs.keyboard.oneHandedMode.set(when (prefs.keyboard.oneHandedMode.get()) {
|
||||
OneHandedMode.OFF -> if (isRight) { OneHandedMode.END } else { OneHandedMode.START }
|
||||
else -> OneHandedMode.OFF
|
||||
})
|
||||
fun toggleOneHandedMode() {
|
||||
prefs.keyboard.oneHandedModeEnabled.set(!prefs.keyboard.oneHandedModeEnabled.get())
|
||||
}
|
||||
|
||||
fun executeSwipeAction(swipeAction: SwipeAction) {
|
||||
@@ -272,6 +269,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
SwipeAction.REDO -> TextKeyData.REDO
|
||||
SwipeAction.UNDO -> TextKeyData.UNDO
|
||||
SwipeAction.SHOW_INPUT_METHOD_PICKER -> TextKeyData.SYSTEM_INPUT_METHOD_PICKER
|
||||
SwipeAction.SHOW_SUBTYPE_PICKER -> TextKeyData.SHOW_SUBTYPE_PICKER
|
||||
SwipeAction.SWITCH_TO_CLIPBOARD_CONTEXT -> TextKeyData.IME_UI_MODE_CLIPBOARD
|
||||
SwipeAction.SWITCH_TO_PREV_SUBTYPE -> TextKeyData.IME_PREV_SUBTYPE
|
||||
SwipeAction.SWITCH_TO_NEXT_SUBTYPE -> TextKeyData.IME_NEXT_SUBTYPE
|
||||
@@ -582,7 +580,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
* Handles a [KeyCode.TOGGLE_INCOGNITO_MODE] event.
|
||||
*/
|
||||
private fun handleToggleIncognitoMode() {
|
||||
prefs.advanced.forceIncognitoModeFromDynamic.set(!prefs.advanced.forceIncognitoModeFromDynamic.get())
|
||||
prefs.suggestion.forceIncognitoModeFromDynamic.set(!prefs.suggestion.forceIncognitoModeFromDynamic.get())
|
||||
val newState = !activeState.isIncognitoMode
|
||||
activeState.isIncognitoMode = newState
|
||||
lastToastReference.get()?.cancel()
|
||||
@@ -719,8 +717,15 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
clipboardManager.updatePrimaryClip(null)
|
||||
appContext.showShortToast(R.string.clipboard__cleared_primary_clip)
|
||||
}
|
||||
KeyCode.COMPACT_LAYOUT_TO_LEFT -> toggleOneHandedMode(isRight = false)
|
||||
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> toggleOneHandedMode(isRight = true)
|
||||
KeyCode.TOGGLE_COMPACT_LAYOUT -> toggleOneHandedMode()
|
||||
KeyCode.COMPACT_LAYOUT_TO_LEFT -> {
|
||||
prefs.keyboard.oneHandedMode.set(OneHandedMode.START)
|
||||
toggleOneHandedMode()
|
||||
}
|
||||
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> {
|
||||
prefs.keyboard.oneHandedMode.set(OneHandedMode.END)
|
||||
toggleOneHandedMode()
|
||||
}
|
||||
KeyCode.DELETE -> handleDelete()
|
||||
KeyCode.DELETE_WORD -> handleDeleteWord()
|
||||
KeyCode.ENTER -> handleEnter()
|
||||
@@ -742,6 +747,9 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
KeyCode.SHIFT -> handleShiftUp(data)
|
||||
KeyCode.SPACE -> handleSpace(data)
|
||||
KeyCode.SYSTEM_INPUT_METHOD_PICKER -> InputMethodUtils.showImePicker(appContext)
|
||||
KeyCode.SHOW_SUBTYPE_PICKER -> {
|
||||
appContext.keyboardManager.value.activeState.isSubtypeSelectionVisible = true
|
||||
}
|
||||
KeyCode.SYSTEM_PREV_INPUT_METHOD -> FlorisImeService.switchToPrevInputMethod()
|
||||
KeyCode.SYSTEM_NEXT_INPUT_METHOD -> FlorisImeService.switchToNextInputMethod()
|
||||
KeyCode.TOGGLE_SMARTBAR_VISIBILITY -> {
|
||||
@@ -867,17 +875,22 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
val punctuationRules = MutableLiveData<Map<ExtensionComponentName, PunctuationRule>>(emptyMap())
|
||||
val subtypePresets = MutableLiveData<List<SubtypePreset>>(emptyList())
|
||||
|
||||
private val anyChangedGuard = Mutex(locked = false)
|
||||
val anyChanged = MutableLiveData(Unit)
|
||||
|
||||
init {
|
||||
scope.launch(Dispatchers.Main.immediate) {
|
||||
extensionManager.keyboardExtensions.observeForever { keyboardExtensions ->
|
||||
parseKeyboardExtensions(keyboardExtensions)
|
||||
scope.launch {
|
||||
anyChangedGuard.withLock {
|
||||
parseKeyboardExtensions(keyboardExtensions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseKeyboardExtensions(keyboardExtensions: List<KeyboardExtension>) = scope.launch {
|
||||
private fun parseKeyboardExtensions(keyboardExtensions: List<KeyboardExtension>) {
|
||||
val localComposers = mutableMapOf<ExtensionComponentName, Composer>()
|
||||
val localCurrencySets = mutableMapOf<ExtensionComponentName, CurrencySet>()
|
||||
val localLayouts = mutableMapOf<LayoutType, MutableMap<ExtensionComponentName, LayoutArrangementComponent>>()
|
||||
@@ -956,10 +969,13 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> {
|
||||
editorInfo.isRichInputEditor
|
||||
}
|
||||
KeyCode.TOGGLE_INCOGNITO_MODE -> when (prefs.advanced.incognitoMode.get()) {
|
||||
KeyCode.TOGGLE_INCOGNITO_MODE -> when (prefs.suggestion.incognitoMode.get()) {
|
||||
IncognitoMode.FORCE_OFF, IncognitoMode.FORCE_ON -> false
|
||||
IncognitoMode.DYNAMIC_ON_OFF -> !editorInfo.imeOptions.flagNoPersonalizedLearning
|
||||
}
|
||||
KeyCode.LANGUAGE_SWITCH -> {
|
||||
subtypeManager.subtypes.size > 1
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,5 +34,7 @@ enum class KeyboardMode(val value: Int) {
|
||||
fun fromInt(int: Int) = entries.firstOrNull { it.value == int } ?: CHARACTERS
|
||||
}
|
||||
|
||||
fun attrName() = name.lowercase()
|
||||
|
||||
fun toInt() = value
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ import kotlin.properties.Delegates
|
||||
*
|
||||
* <Byte 7> | <Byte 6> | <Byte 5> | <Byte 4> | Description
|
||||
* ---------|----------|----------|----------|---------------------------------
|
||||
* | | | 1 | Subtype selection dialog visible
|
||||
* 1 | | | | Devtools: Show drag&drop helpers
|
||||
*
|
||||
* The resulting structure is only relevant during a runtime lifespan and
|
||||
@@ -91,6 +92,8 @@ open class KeyboardState protected constructor(open var rawValue: ULong) {
|
||||
|
||||
const val F_IS_RTL_LAYOUT_DIRECTION: ULong = 0x08000000u
|
||||
|
||||
const val F_IS_SUBTYPE_SELECTION_VISIBLE: ULong = 0x1_0000_0000u
|
||||
|
||||
const val F_DEBUG_SHOW_DRAG_AND_DROP_HELPERS = 0x01_00_00_00_00_00_00_00uL
|
||||
|
||||
const val STATE_ALL_ZERO: ULong = 0uL
|
||||
@@ -188,6 +191,10 @@ open class KeyboardState protected constructor(open var rawValue: ULong) {
|
||||
get() = getFlag(F_IS_ACTIONS_EDITOR_VISIBLE)
|
||||
set(v) { setFlag(F_IS_ACTIONS_EDITOR_VISIBLE, v) }
|
||||
|
||||
var isSubtypeSelectionVisible: Boolean
|
||||
get() = getFlag(F_IS_SUBTYPE_SELECTION_VISIBLE)
|
||||
set(v) { setFlag(F_IS_SUBTYPE_SELECTION_VISIBLE, v) }
|
||||
|
||||
var isComposingEnabled: Boolean
|
||||
get() = getFlag(F_IS_COMPOSING_ENABLED)
|
||||
set(v) { setFlag(F_IS_COMPOSING_ENABLED, v) }
|
||||
|
||||
@@ -65,9 +65,9 @@ private data class CachedPopupMapping(
|
||||
)
|
||||
|
||||
data class DebugLayoutComputationResult(
|
||||
val main: Result<CachedLayout>,
|
||||
val mod: Result<CachedLayout>,
|
||||
val ext: Result<CachedLayout>,
|
||||
val main: Result<CachedLayout?>,
|
||||
val mod: Result<CachedLayout?>,
|
||||
val ext: Result<CachedLayout?>,
|
||||
) {
|
||||
fun allLayoutsSuccess(): Boolean {
|
||||
return main.isSuccess && mod.isSuccess && ext.isSuccess
|
||||
@@ -96,8 +96,13 @@ class LayoutManager(context: Context) {
|
||||
*
|
||||
* @return A deferred result for a layout.
|
||||
*/
|
||||
private fun loadLayoutAsync(ltn: LTN?) = ioScope.runCatchingAsync {
|
||||
require(ltn != null) { "Invalid argument value for 'ltn': null" }
|
||||
private fun loadLayoutAsync(ltn: LTN?, allowNullLTN: Boolean) = ioScope.runCatchingAsync {
|
||||
if (!allowNullLTN) {
|
||||
requireNotNull(ltn) { "Invalid argument value for 'ltn': null" }
|
||||
}
|
||||
if (ltn == null) {
|
||||
return@runCatchingAsync null
|
||||
}
|
||||
layoutCacheGuard.withLock {
|
||||
val cached = layoutCache[ltn]
|
||||
if (cached != null) {
|
||||
@@ -176,7 +181,7 @@ class LayoutManager(context: Context) {
|
||||
val extendedPopupsDefault = loadPopupMappingAsync()
|
||||
val extendedPopups = loadPopupMappingAsync(subtype)
|
||||
|
||||
val mainLayoutResult = loadLayoutAsync(main).await()
|
||||
val mainLayoutResult = loadLayoutAsync(main, allowNullLTN = false).await()
|
||||
val mainLayout = mainLayoutResult.onFailure {
|
||||
flogWarning { "$keyboardMode - main - $it" }
|
||||
}.getOrNull()
|
||||
@@ -196,11 +201,11 @@ class LayoutManager(context: Context) {
|
||||
} else {
|
||||
modifier
|
||||
}
|
||||
val modifierLayoutResult = loadLayoutAsync(modifierToLoad).await()
|
||||
val modifierLayoutResult = loadLayoutAsync(modifierToLoad, allowNullLTN = true).await()
|
||||
val modifierLayout = modifierLayoutResult.onFailure {
|
||||
flogWarning { "$keyboardMode - mod - $it" }
|
||||
}.getOrNull()
|
||||
val extensionLayoutResult = loadLayoutAsync(extension).await()
|
||||
val extensionLayoutResult = loadLayoutAsync(extension, allowNullLTN = true).await()
|
||||
val extensionLayout = extensionLayoutResult.onFailure {
|
||||
flogWarning { "$keyboardMode - ext - $it" }
|
||||
}.getOrNull()
|
||||
|
||||
@@ -20,18 +20,19 @@ import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.waitForUpOrCancellation
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.indication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Backspace
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -46,7 +47,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.ime.input.InputEventDispatcher
|
||||
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
@@ -54,10 +54,12 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiData
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiPaletteView
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import org.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
|
||||
@SuppressLint("MutableCollectionMutableState")
|
||||
@Composable
|
||||
@@ -73,7 +75,8 @@ fun MediaInputLayout(
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
Column(
|
||||
SnyggColumn(
|
||||
elementName = FlorisImeUi.Media.elementName,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.imeUiHeight()),
|
||||
@@ -82,14 +85,17 @@ fun MediaInputLayout(
|
||||
modifier = Modifier.weight(1f),
|
||||
fullEmojiMappings = emojiLayoutDataMap,
|
||||
)
|
||||
Row(
|
||||
SnyggRow(
|
||||
elementName = FlorisImeUi.MediaBottomRow.elementName,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.keyboardRowBaseHeight * 0.8f),
|
||||
) {
|
||||
KeyboardLikeButton(
|
||||
elementName = FlorisImeUi.MediaBottomRowButton.elementName,
|
||||
inputEventDispatcher = keyboardManager.inputEventDispatcher,
|
||||
keyData = TextKeyData.IME_UI_MODE_TEXT,
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
) {
|
||||
Text(
|
||||
text = "ABC",
|
||||
@@ -98,8 +104,10 @@ fun MediaInputLayout(
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
KeyboardLikeButton(
|
||||
elementName = FlorisImeUi.MediaBottomRowButton.elementName,
|
||||
inputEventDispatcher = keyboardManager.inputEventDispatcher,
|
||||
keyData = TextKeyData.DELETE,
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Backspace, contentDescription = null)
|
||||
}
|
||||
@@ -113,42 +121,45 @@ internal fun KeyboardLikeButton(
|
||||
modifier: Modifier = Modifier,
|
||||
inputEventDispatcher: InputEventDispatcher,
|
||||
keyData: KeyData,
|
||||
element: String = FlorisImeUi.EmojiKey,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
elementName: String = FlorisImeUi.MediaEmojiKey.elementName,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
var isPressed by remember { mutableStateOf(false) }
|
||||
val keyStyle = FlorisImeTheme.style.get(
|
||||
element = element,
|
||||
code = keyData.code,
|
||||
isPressed = isPressed,
|
||||
)
|
||||
SnyggSurface(
|
||||
modifier = modifier.pointerInput(Unit) {
|
||||
awaitEachGesture {
|
||||
awaitFirstDown(requireUnconsumed = false).also {
|
||||
if (it.pressed != it.previousPressed) it.consume()
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
val selector = if (isPressed) {
|
||||
SnyggSelector.PRESSED
|
||||
} else {
|
||||
SnyggSelector.NONE
|
||||
}
|
||||
|
||||
SnyggBox(
|
||||
elementName = elementName,
|
||||
attributes = mapOf(FlorisImeUi.Attr.Code to keyData.code),
|
||||
selector = selector,
|
||||
clickAndSemanticsModifier = modifier
|
||||
.indication(interactionSource, ripple())
|
||||
.pointerInput(Unit) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown(requireUnconsumed = false).also {
|
||||
if (it.pressed != it.previousPressed) it.consume()
|
||||
}
|
||||
val press = PressInteraction.Press(down.position)
|
||||
interactionSource.tryEmit(press)
|
||||
inputEventDispatcher.sendDown(keyData)
|
||||
inputFeedbackController.keyPress(keyData)
|
||||
val up = waitForUpOrCancellation()
|
||||
if (up != null) {
|
||||
interactionSource.tryEmit(PressInteraction.Release(press))
|
||||
inputEventDispatcher.sendUp(keyData)
|
||||
} else {
|
||||
interactionSource.tryEmit(PressInteraction.Cancel(press))
|
||||
inputEventDispatcher.sendCancel(keyData)
|
||||
}
|
||||
}
|
||||
isPressed = true
|
||||
inputEventDispatcher.sendDown(keyData)
|
||||
inputFeedbackController.keyPress(keyData)
|
||||
val up = waitForUpOrCancellation()
|
||||
isPressed = false
|
||||
if (up != null) {
|
||||
inputEventDispatcher.sendUp(keyData)
|
||||
} else {
|
||||
inputEventDispatcher.sendCancel(keyData)
|
||||
}
|
||||
}
|
||||
},
|
||||
style = keyStyle,
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
content = content,
|
||||
)
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.PushPin
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.TabRowDefaults
|
||||
@@ -88,23 +88,22 @@ import dev.patrickgold.florisboard.editorInstance
|
||||
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.header
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
import kotlin.math.ceil
|
||||
|
||||
private val EmojiCategoryValues = EmojiCategory.entries
|
||||
@@ -164,10 +163,6 @@ fun EmojiPaletteView(
|
||||
|
||||
val preferredSkinTone by prefs.emoji.preferredSkinTone.observeAsState()
|
||||
val emojiHistoryEnabled by prefs.emoji.historyEnabled.observeAsState()
|
||||
val fontSizeMultiplier = prefs.keyboard.fontSizeMultiplier()
|
||||
val emojiKeyStyle = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiKey)
|
||||
val emojiKeyFontSize = emojiKeyStyle.fontSize.spSize(default = EmojiDefaultFontSize) safeTimes fontSizeMultiplier
|
||||
val contentColor = emojiKeyStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
|
||||
var activeCategory by remember(emojiHistoryEnabled) {
|
||||
if (emojiHistoryEnabled) {
|
||||
@@ -182,11 +177,9 @@ fun EmojiPaletteView(
|
||||
|
||||
@Composable
|
||||
fun GridHeader(text: String) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.MediaEmojiSubheader.elementName,
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = contentColor,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -202,9 +195,6 @@ fun EmojiPaletteView(
|
||||
preferredSkinTone = preferredSkinTone,
|
||||
isPinned = isPinned,
|
||||
isRecent = isRecent,
|
||||
contentColor = contentColor,
|
||||
fontSize = emojiKeyFontSize,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
onEmojiInput = { emoji ->
|
||||
keyboardManager.inputEventDispatcher.sendDownUp(emoji)
|
||||
scope.launch {
|
||||
@@ -259,7 +249,6 @@ fun EmojiPaletteView(
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__history__phone_locked_message),
|
||||
color = contentColor,
|
||||
)
|
||||
}
|
||||
} else if (activeCategory == EmojiCategory.RECENTLY_USED && isEmojiHistoryEmpty) {
|
||||
@@ -270,12 +259,10 @@ fun EmojiPaletteView(
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__history__empty_message),
|
||||
color = contentColor,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
text = stringRes(R.string.emoji__history__usage_tip),
|
||||
color = contentColor,
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
}
|
||||
@@ -284,7 +271,7 @@ fun EmojiPaletteView(
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisScrollbar(lazyListState, color = contentColor.copy(alpha = 0.28f)),
|
||||
.florisScrollbar(lazyListState),
|
||||
columns = GridCells.Adaptive(minSize = EmojiBaseWidth),
|
||||
state = lazyListState,
|
||||
) {
|
||||
@@ -322,30 +309,29 @@ private fun EmojiCategoriesTabRow(
|
||||
onCategoryChange: (EmojiCategory) -> Unit,
|
||||
emojiHistoryEnabled: Boolean,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
val tabStyle = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiTab)
|
||||
val tabStyleFocused = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiTab, isFocus = true)
|
||||
val unselectedContentColor = tabStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
val selectedContentColor = tabStyleFocused.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
|
||||
val selectedTabIndex = if (emojiHistoryEnabled) {
|
||||
EmojiCategoryValues.indexOf(activeCategory)
|
||||
} else {
|
||||
EmojiCategoryValues.indexOf(activeCategory) - 1
|
||||
}
|
||||
val style = rememberSnyggThemeQuery(FlorisImeUi.MediaEmojiTab.elementName)
|
||||
TabRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.smartbarHeight),
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = selectedContentColor,
|
||||
contentColor = style.foreground(),
|
||||
indicator = { tabPositions ->
|
||||
val style = rememberSnyggThemeQuery(
|
||||
elementName = FlorisImeUi.MediaEmojiTab.elementName,
|
||||
selector = SnyggSelector.FOCUS,
|
||||
)
|
||||
TabRowDefaults.PrimaryIndicator(
|
||||
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
|
||||
color = selectedContentColor,
|
||||
height = 4.dp
|
||||
height = 4.dp,
|
||||
color = style.foreground(),
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -359,13 +345,12 @@ private fun EmojiCategoriesTabRow(
|
||||
onCategoryChange(category)
|
||||
},
|
||||
selected = activeCategory == category,
|
||||
icon = { Icon(
|
||||
icon = { SnyggIcon(
|
||||
elementName = FlorisImeUi.MediaEmojiTab.elementName,
|
||||
selector = if (activeCategory == category) SnyggSelector.FOCUS else SnyggSelector.NONE,
|
||||
modifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
imageVector = category.icon(),
|
||||
contentDescription = null,
|
||||
) },
|
||||
unselectedContentColor = unselectedContentColor,
|
||||
selectedContentColor = selectedContentColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -378,9 +363,6 @@ private fun EmojiKey(
|
||||
preferredSkinTone: EmojiSkinTone,
|
||||
isPinned: Boolean,
|
||||
isRecent: Boolean,
|
||||
contentColor: Color,
|
||||
fontSize: TextUnit,
|
||||
fontSizeMultiplier: Float,
|
||||
onEmojiInput: (Emoji) -> Unit,
|
||||
onHistoryAction: () -> Unit,
|
||||
) {
|
||||
@@ -389,7 +371,7 @@ private fun EmojiKey(
|
||||
val variations = emojiSet.variations(withoutSkinTone = preferredSkinTone)
|
||||
var showVariantsBox by remember { mutableStateOf(false) }
|
||||
|
||||
Box(
|
||||
SnyggBox(FlorisImeUi.MediaEmojiKey.elementName,
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.pointerInput(Unit) {
|
||||
@@ -413,10 +395,9 @@ private fun EmojiKey(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = base.value,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
color = contentColor,
|
||||
fontSize = fontSize,
|
||||
)
|
||||
if (variations.isNotEmpty() || isPinned || isRecent) {
|
||||
val style = rememberSnyggThemeQuery(FlorisImeUi.MediaEmojiKeyPopupExtendedIndicator.elementName)
|
||||
val shape = when (LocalLayoutDirection.current) {
|
||||
LayoutDirection.Ltr -> VariantsTriangleShapeLtr
|
||||
LayoutDirection.Rtl -> VariantsTriangleShapeRtl
|
||||
@@ -426,7 +407,7 @@ private fun EmojiKey(
|
||||
.align(Alignment.BottomEnd)
|
||||
.offset(x = (-4).dp, y = (-4).dp)
|
||||
.size(4.dp)
|
||||
.background(contentColor, shape),
|
||||
.background(style.foreground(), shape),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -448,7 +429,6 @@ private fun EmojiKey(
|
||||
variations = variations,
|
||||
visible = showVariantsBox,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
onEmojiTap = { emoji ->
|
||||
onEmojiInput(emoji)
|
||||
showVariantsBox = false
|
||||
@@ -467,13 +447,10 @@ private fun EmojiVariationsPopup(
|
||||
variations: List<Emoji>,
|
||||
visible: Boolean,
|
||||
emojiCompatInstance: EmojiCompat?,
|
||||
fontSizeMultiplier: Float,
|
||||
onEmojiTap: (Emoji) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val popupStyle = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiKeyPopup)
|
||||
val emojiKeyHeight = FlorisImeSizing.smartbarHeight
|
||||
val context = LocalContext.current
|
||||
|
||||
if (visible) {
|
||||
Popup(
|
||||
@@ -484,29 +461,25 @@ private fun EmojiVariationsPopup(
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
FlowRow(
|
||||
SnyggRow(
|
||||
elementName = FlorisImeUi.MediaEmojiKeyPopupBox.elementName,
|
||||
modifier = Modifier
|
||||
.widthIn(max = EmojiBaseWidth * 6)
|
||||
.snyggShadow(popupStyle)
|
||||
.snyggBorder(context, popupStyle)
|
||||
.snyggBackground(context, popupStyle, fallbackColor = FlorisImeTheme.fallbackSurfaceColor()),
|
||||
.widthIn(max = EmojiBaseWidth * 6),
|
||||
) {
|
||||
for (emoji in variations) {
|
||||
Box(
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.MediaEmojiKeyPopupElement.elementName,
|
||||
modifier = Modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures { onEmojiTap(emoji) }
|
||||
}
|
||||
.width(EmojiBaseWidth)
|
||||
.height(emojiKeyHeight)
|
||||
.padding(all = 4.dp),
|
||||
.height(emojiKeyHeight),
|
||||
) {
|
||||
EmojiText(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = emoji.value,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
color = popupStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
fontSize = popupStyle.fontSize.spSize(default = EmojiDefaultFontSize) safeTimes fontSizeMultiplier,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -526,7 +499,6 @@ private fun EmojiHistoryPopup(
|
||||
) {
|
||||
val prefs by florisPreferenceModel()
|
||||
val scope = rememberCoroutineScope()
|
||||
val popupStyle = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiKeyPopup)
|
||||
val emojiKeyHeight = FlorisImeSizing.smartbarHeight
|
||||
val context = LocalContext.current
|
||||
val pinnedUS by prefs.emoji.historyPinnedUpdateStrategy.observeAsState()
|
||||
@@ -536,7 +508,8 @@ private fun EmojiHistoryPopup(
|
||||
|
||||
@Composable
|
||||
fun Action(icon: ImageVector, action: suspend () -> Unit) {
|
||||
Box(
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.MediaEmojiKeyPopupElement.elementName,
|
||||
modifier = Modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
@@ -547,14 +520,11 @@ private fun EmojiHistoryPopup(
|
||||
}
|
||||
}
|
||||
.width(EmojiBaseWidth)
|
||||
.height(emojiKeyHeight)
|
||||
.padding(all = 4.dp),
|
||||
.height(emojiKeyHeight),
|
||||
) {
|
||||
Icon(
|
||||
SnyggIcon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = popupStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -569,12 +539,10 @@ private fun EmojiHistoryPopup(
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
FlowRow(
|
||||
SnyggRow(
|
||||
elementName = FlorisImeUi.MediaEmojiKeyPopupBox.elementName,
|
||||
modifier = Modifier
|
||||
.widthIn(max = EmojiBaseWidth * 6)
|
||||
.snyggShadow(popupStyle)
|
||||
.snyggBorder(context, popupStyle)
|
||||
.snyggBackground(context, popupStyle, fallbackColor = FlorisImeTheme.fallbackSurfaceColor()),
|
||||
.widthIn(max = EmojiBaseWidth * 6),
|
||||
) {
|
||||
if (isCurrentlyPinned) {
|
||||
Action(
|
||||
|
||||
@@ -21,8 +21,6 @@ import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import io.github.reactivecircus.cache4k.Cache
|
||||
import org.florisboard.lib.kotlin.GuardedByLock
|
||||
import org.florisboard.lib.kotlin.guardedByLock
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
open class BreakIteratorGroup {
|
||||
private val charInstances = Cache.Builder().build<FlorisLocale, GuardedByLock<BreakIterator>>()
|
||||
@@ -32,19 +30,13 @@ open class BreakIteratorGroup {
|
||||
private val sentenceInstances = Cache.Builder().build<FlorisLocale, GuardedByLock<BreakIterator>>()
|
||||
|
||||
suspend fun <R> character(locale: FlorisLocale, action: (BreakIterator) -> R): R {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
val instance = charInstances.get(locale) {
|
||||
guardedByLock { BreakIterator.getCharacterInstance(locale.base) }
|
||||
}
|
||||
return instance.withLock { action(it) }
|
||||
return instance.withLock(null, action)
|
||||
}
|
||||
|
||||
suspend fun <R> word(locale: FlorisLocale, action: (BreakIterator) -> R): R {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
val instance = wordInstances.get(locale) {
|
||||
guardedByLock { BreakIterator.getWordInstance(locale.base) }
|
||||
}
|
||||
@@ -52,9 +44,6 @@ open class BreakIteratorGroup {
|
||||
}
|
||||
|
||||
suspend fun <R> sentence(locale: FlorisLocale, action: (BreakIterator) -> R): R {
|
||||
contract {
|
||||
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
val instance = sentenceInstances.get(locale) {
|
||||
guardedByLock { BreakIterator.getSentenceInstance(locale.base) }
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class NlpManager(context: Context) {
|
||||
prefs.suggestion.enabled.observeForever {
|
||||
assembleCandidates()
|
||||
}
|
||||
prefs.suggestion.clipboardContentEnabled.observeForever {
|
||||
prefs.clipboard.suggestionEnabled.observeForever {
|
||||
assembleCandidates()
|
||||
}
|
||||
prefs.emoji.suggestionEnabled.observeForever {
|
||||
@@ -370,14 +370,14 @@ class NlpManager(context: Context) {
|
||||
isPrivateSession: Boolean,
|
||||
): List<SuggestionCandidate> {
|
||||
// Check if enabled
|
||||
if (!prefs.suggestion.clipboardContentEnabled.get()) return emptyList()
|
||||
if (!prefs.clipboard.suggestionEnabled.get()) return emptyList()
|
||||
|
||||
val currentItem = validateClipboardItem(clipboardManager.primaryClip, lastClipboardItemId, content.text)
|
||||
?: return emptyList()
|
||||
|
||||
return buildList {
|
||||
val now = System.currentTimeMillis()
|
||||
if ((now - currentItem.creationTimestampMs) < prefs.suggestion.clipboardContentTimeout.get() * 1000) {
|
||||
if ((now - currentItem.creationTimestampMs) < prefs.clipboard.suggestionTimeout.get() * 1000) {
|
||||
add(ClipboardSuggestionCandidate(currentItem, sourceProvider = this@ClipboardSuggestionProvider, context = context))
|
||||
if (currentItem.isSensitive) {
|
||||
return@buildList
|
||||
|
||||
@@ -20,7 +20,6 @@ package dev.patrickgold.florisboard.ime.onehanded
|
||||
* Static object which contains all possible one-handed mode strings.
|
||||
*/
|
||||
enum class OneHandedMode {
|
||||
OFF,
|
||||
START,
|
||||
END;
|
||||
}
|
||||
|
||||
@@ -17,29 +17,26 @@
|
||||
package dev.patrickgold.florisboard.ime.onehanded
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.ZoomOutMap
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggIconButton
|
||||
|
||||
@Composable
|
||||
fun RowScope.OneHandedPanel(
|
||||
@@ -49,38 +46,43 @@ fun RowScope.OneHandedPanel(
|
||||
) {
|
||||
val prefs by florisPreferenceModel()
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
val oneHandedPanelStyle = FlorisImeTheme.style.get(FlorisImeUi.OneHandedPanel)
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(
|
||||
SnyggColumn(
|
||||
FlorisImeUi.OneHandedPanel.elementName,
|
||||
modifier = modifier
|
||||
.weight(weight)
|
||||
.snyggBackground(context, oneHandedPanelStyle)
|
||||
.height(FlorisImeSizing.imeUiHeight()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
IconButton(
|
||||
SnyggIconButton(
|
||||
FlorisImeUi.OneHandedPanelButton.elementName,
|
||||
onClick = {
|
||||
inputFeedbackController.keyPress()
|
||||
prefs.keyboard.oneHandedMode.set(OneHandedMode.OFF)
|
||||
prefs.keyboard.oneHandedModeEnabled.set(false)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f),
|
||||
) {
|
||||
Icon(
|
||||
SnyggIcon(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = Icons.Default.ZoomOutMap,
|
||||
contentDescription = stringRes(R.string.one_handed__close_btn_content_description),
|
||||
tint = oneHandedPanelStyle.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
SnyggIconButton(
|
||||
FlorisImeUi.OneHandedPanelButton.elementName,
|
||||
onClick = {
|
||||
inputFeedbackController.keyPress()
|
||||
prefs.keyboard.oneHandedMode.set(panelSide)
|
||||
},
|
||||
modifier = Modifier.weight(1f).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Icon(
|
||||
SnyggIcon(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = if (panelSide == OneHandedMode.START) {
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowLeft
|
||||
} else {
|
||||
@@ -93,7 +95,6 @@ fun RowScope.OneHandedPanel(
|
||||
R.string.one_handed__move_end_btn_content_description
|
||||
}
|
||||
),
|
||||
tint = oneHandedPanelStyle.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,79 +17,55 @@
|
||||
package dev.patrickgold.florisboard.ime.popup
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreHoriz
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import dev.patrickgold.florisboard.ime.keyboard.Key
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import org.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
@Composable
|
||||
fun PopupBaseBox(
|
||||
modifier: Modifier = Modifier,
|
||||
key: Key,
|
||||
fontSizeMultiplier: Float,
|
||||
shouldIndicateExtendedPopups: Boolean,
|
||||
): Unit = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val popupStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.KeyPopup,
|
||||
)
|
||||
val fontSize = popupStyle.fontSize.spSize() safeTimes fontSizeMultiplier
|
||||
SnyggSurface(
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.KeyPopupBox.elementName,
|
||||
modifier = modifier,
|
||||
style = popupStyle,
|
||||
clip = true,
|
||||
) {
|
||||
key.label?.let { label ->
|
||||
Box(
|
||||
SnyggBox(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(key.visibleBounds.height.toDp())
|
||||
.align(Alignment.TopCenter),
|
||||
) {
|
||||
Text(
|
||||
SnyggText(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = label,
|
||||
color = popupStyle.foreground.solidColor(context),
|
||||
fontSize = fontSize,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (shouldIndicateExtendedPopups) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.requiredSize(fontSize.toDp() * 0.65f)
|
||||
.align(Alignment.CenterEnd),
|
||||
SnyggIcon(
|
||||
elementName = FlorisImeUi.KeyPopupExtendedIndicator.elementName,
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
imageVector = Icons.Default.MoreHoriz,
|
||||
contentDescription = null,
|
||||
tint = popupStyle.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -99,71 +75,40 @@ fun PopupBaseBox(
|
||||
fun PopupExtBox(
|
||||
modifier: Modifier = Modifier,
|
||||
elements: List<List<PopupUiController.Element>>,
|
||||
fontSizeMultiplier: Float,
|
||||
elemArrangement: Arrangement.Horizontal,
|
||||
elemWidth: Dp,
|
||||
elemHeight: Dp,
|
||||
activeElementIndex: Int,
|
||||
): Unit = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val popupStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.KeyPopup,
|
||||
isFocus = false,
|
||||
)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.snyggShadow(popupStyle)
|
||||
.snyggBorder(context, popupStyle)
|
||||
.snyggBackground(context, popupStyle),
|
||||
) {
|
||||
SnyggColumn(FlorisImeUi.KeyPopupBox.elementName, modifier = modifier) {
|
||||
for (row in elements.asReversed()) {
|
||||
Row(
|
||||
SnyggRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.requiredHeight(elemHeight),
|
||||
horizontalArrangement = elemArrangement,
|
||||
) {
|
||||
for (element in row) {
|
||||
val elemStyle = if (activeElementIndex == element.orderedIndex) {
|
||||
FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.KeyPopup,
|
||||
isFocus = true,
|
||||
)
|
||||
val selector = if (activeElementIndex == element.orderedIndex) {
|
||||
SnyggSelector.FOCUS
|
||||
} else {
|
||||
popupStyle
|
||||
null
|
||||
}
|
||||
val elemFontSize = elemStyle.fontSize.spSize() safeTimes fontSizeMultiplier safeTimes
|
||||
if (element.data.code == KeyCode.URI_COMPONENT_TLD) { 0.6f } else { 1.0f }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(elemWidth, elemHeight)
|
||||
.run {
|
||||
if (activeElementIndex == element.orderedIndex) {
|
||||
snyggBackground(context, elemStyle)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
},
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.KeyPopupElement.elementName,
|
||||
selector = selector,
|
||||
modifier = Modifier.size(elemWidth, elemHeight),
|
||||
) {
|
||||
element.label?.let { label ->
|
||||
Text(
|
||||
SnyggText(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = label,
|
||||
color = elemStyle.foreground.solidColor(context),
|
||||
fontSize = elemFontSize,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
element.icon?.let { icon ->
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.requiredSize(elemFontSize.toDp() * 1.1f)
|
||||
.align(Alignment.Center),
|
||||
SnyggIcon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = elemStyle.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +454,6 @@ class PopupUiController(
|
||||
.requiredSize(renderInfo.bounds.size.toDpSize())
|
||||
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() },
|
||||
key = renderInfo.key,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
shouldIndicateExtendedPopups = renderInfo.shouldIndicateExtendedPopups && extRenderInfo == null,
|
||||
)
|
||||
}
|
||||
@@ -467,7 +466,6 @@ class PopupUiController(
|
||||
.requiredSize(renderInfo.bounds.size.toDpSize())
|
||||
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() },
|
||||
elements = renderInfo.elements,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
elemArrangement = if (renderInfo.anchorLeft) {
|
||||
Arrangement.Start
|
||||
} else {
|
||||
|
||||
@@ -48,7 +48,6 @@ fun BottomSheetHostUi(
|
||||
val bgColorOutOfBounds by animateColorAsState(
|
||||
if (isShowing) SheetOutOfBoundsBgColorActive else SheetOutOfBoundsBgColorInactive
|
||||
)
|
||||
|
||||
Column(Modifier.background(bgColorOutOfBounds)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -21,17 +21,11 @@ import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.waitForUpOrCancellation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -45,25 +39,23 @@ import androidx.compose.ui.input.pointer.PointerInputChange
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.nlp.ClipboardSuggestionCandidate
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.conditional
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggSpacer
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
val CandidatesRowScrollbarHeight = 2.dp
|
||||
|
||||
@@ -78,13 +70,10 @@ fun CandidatesRow(modifier: Modifier = Modifier) {
|
||||
val displayMode by prefs.suggestion.displayMode.observeAsState()
|
||||
val candidates by nlpManager.activeCandidatesFlow.collectAsState()
|
||||
|
||||
val rowStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarCandidatesRow)
|
||||
val spacerStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarCandidateSpacer)
|
||||
|
||||
Row(
|
||||
SnyggRow(
|
||||
elementName = FlorisImeUi.SmartbarCandidatesRow.elementName,
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.snyggBackground(context, rowStyle)
|
||||
.conditional(displayMode == CandidatesDisplayMode.DYNAMIC_SCROLLABLE && candidates.size > 1) {
|
||||
florisHorizontalScroll(scrollbarHeight = CandidatesRowScrollbarHeight)
|
||||
},
|
||||
@@ -115,12 +104,12 @@ fun CandidatesRow(modifier: Modifier = Modifier) {
|
||||
}
|
||||
for ((n, candidate) in list.withIndex()) {
|
||||
if (n > 0) {
|
||||
Spacer(
|
||||
SnyggSpacer(
|
||||
elementName = FlorisImeUi.SmartbarCandidateSpacer.elementName,
|
||||
modifier = Modifier
|
||||
.width(1.dp)
|
||||
.fillMaxHeight(0.6f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.snyggBackground(context, spacerStyle),
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
CandidateItem(
|
||||
@@ -156,24 +145,18 @@ private fun CandidateItem(
|
||||
onLongPress: () -> Boolean = { false },
|
||||
longPressDelay: Long,
|
||||
) = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
var isPressed by remember { mutableStateOf(false) }
|
||||
|
||||
val style = if (candidate is ClipboardSuggestionCandidate) {
|
||||
FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.SmartbarCandidateClip,
|
||||
isPressed = isPressed,
|
||||
)
|
||||
val elementName = if (candidate is ClipboardSuggestionCandidate) {
|
||||
FlorisImeUi.SmartbarCandidateClip
|
||||
} else {
|
||||
FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.SmartbarCandidateWord,
|
||||
isPressed = isPressed,
|
||||
)
|
||||
}
|
||||
FlorisImeUi.SmartbarCandidateWord
|
||||
}.elementName
|
||||
|
||||
Row(
|
||||
SnyggRow(
|
||||
elementName = elementName,
|
||||
selector = if (isPressed) SnyggSelector.PRESSED else SnyggSelector.NONE,
|
||||
modifier = modifier
|
||||
.snyggBackground(context, style)
|
||||
.pointerInput(Unit) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
@@ -197,47 +180,27 @@ private fun CandidateItem(
|
||||
}
|
||||
isPressed = false
|
||||
}
|
||||
}
|
||||
.padding(horizontal = 12.dp),
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (candidate.icon != null) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.requiredSize(
|
||||
style.fontSize
|
||||
.spSize()
|
||||
.toDp() * 1.5f
|
||||
)
|
||||
.padding(end = 4.dp),
|
||||
imageVector = candidate.icon!!,
|
||||
contentDescription = null,
|
||||
tint = style.foreground.solidColor(context),
|
||||
)
|
||||
SnyggIcon(imageVector = candidate.icon!!)
|
||||
}
|
||||
Column(
|
||||
SnyggColumn(
|
||||
modifier = if (displayMode == CandidatesDisplayMode.CLASSIC) Modifier.weight(1f) else Modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
SnyggText(
|
||||
elementName = null,
|
||||
attributes = mapOf("auto-commit" to if (candidate.isEligibleForAutoCommit) 1 else 0),
|
||||
text = candidate.text.toString(),
|
||||
color = style.foreground.solidColor(context),
|
||||
fontSize = style.fontSize.spSize(),
|
||||
fontWeight = if (candidate.isEligibleForAutoCommit) FontWeight.Bold else FontWeight.Normal,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (candidate.secondaryText != null) {
|
||||
Text(
|
||||
SnyggText(
|
||||
elementName = null,
|
||||
attributes = mapOf("auto-commit" to if (candidate.isEligibleForAutoCommit) 1 else 0),
|
||||
text = candidate.secondaryText!!.toString(),
|
||||
color = style.foreground.solidColor(context),
|
||||
fontSize = style.fontSize.spSize() safeTimes 0.6,
|
||||
fontWeight = if (candidate.isEligibleForAutoCommit) FontWeight.Bold else FontWeight.Normal,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -32,8 +33,22 @@ import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofillSuggestion
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.toIntOffset
|
||||
import org.florisboard.lib.snygg.SnyggPropertySet
|
||||
import org.florisboard.lib.snygg.SnyggSinglePropertySet
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
|
||||
var CachedInlineSuggestionsChipStyleSet: SnyggSinglePropertySet? = null
|
||||
|
||||
@Composable
|
||||
fun InlineSuggestionsStyleCache() {
|
||||
val chipStyleSet = rememberSnyggThemeQuery(FlorisImeUi.InlineAutofillChip.elementName)
|
||||
LaunchedEffect(chipStyleSet) {
|
||||
CachedInlineSuggestionsChipStyleSet = chipStyleSet
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@Composable
|
||||
|
||||
@@ -25,22 +25,19 @@ import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.UnfoldLess
|
||||
import androidx.compose.material.icons.filled.UnfoldMore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
@@ -61,7 +58,6 @@ import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionButton
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsRow
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.ToggleOverflowPanelAction
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.horizontalTween
|
||||
@@ -70,10 +66,10 @@ import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggIconButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
|
||||
private const val AnimationDuration = 200
|
||||
|
||||
@@ -157,65 +153,47 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
|
||||
val shouldAnimate by prefs.smartbar.sharedActionsExpandWithAnimation.observeAsState()
|
||||
|
||||
val smartbarStyle = FlorisImeTheme.style.get(FlorisImeUi.Smartbar)
|
||||
val primaryActionsToggleStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarSharedActionsToggle)
|
||||
val secondaryActionsToggleStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarExtendedActionsToggle)
|
||||
|
||||
@Composable
|
||||
fun SharedActionsToggle() {
|
||||
IconButton(
|
||||
SnyggIconButton(
|
||||
elementName = FlorisImeUi.SmartbarSharedActionsToggle.elementName,
|
||||
onClick = {
|
||||
if (/* was */ sharedActionsExpanded) {
|
||||
keyboardManager.activeState.isActionsOverflowVisible = false
|
||||
}
|
||||
prefs.smartbar.sharedActionsExpanded.set(!sharedActionsExpanded)
|
||||
},
|
||||
modifier = Modifier.sizeIn(maxHeight = FlorisImeSizing.smartbarHeight).aspectRatio(1f)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxHeight()
|
||||
.aspectRatio(1f)
|
||||
.snyggShadow(primaryActionsToggleStyle)
|
||||
.snyggBorder(context, primaryActionsToggleStyle)
|
||||
.snyggBackground(context, primaryActionsToggleStyle),
|
||||
contentAlignment = Alignment.Center,
|
||||
val transition = updateTransition(sharedActionsExpanded, label = "sharedActionsExpandedToggleBtn")
|
||||
val rotation by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (shouldAnimate) AnimationTween else NoAnimationTween
|
||||
},
|
||||
label = "rotation",
|
||||
) {
|
||||
val transition = updateTransition(sharedActionsExpanded, label = "sharedActionsExpandedToggleBtn")
|
||||
val rotation by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (shouldAnimate) AnimationTween else NoAnimationTween
|
||||
},
|
||||
label = "rotation",
|
||||
) {
|
||||
if (it) 180f else 0f
|
||||
}
|
||||
val arrowIcon = if (flipToggles) {
|
||||
Icons.AutoMirrored.Default.KeyboardArrowLeft
|
||||
} else {
|
||||
Icons.AutoMirrored.Default.KeyboardArrowRight
|
||||
}
|
||||
val incognitoIcon = vectorResource(id = R.drawable.ic_incognito)
|
||||
val incognitoDisplayMode = prefs.keyboard.incognitoDisplayMode.observeAsState()
|
||||
val isIncognitoMode = keyboardManager.activeState.isIncognitoMode
|
||||
val icon = if (isIncognitoMode) {
|
||||
when (incognitoDisplayMode.value) {
|
||||
IncognitoDisplayMode.REPLACE_SHARED_ACTIONS_TOGGLE -> incognitoIcon!!
|
||||
IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD -> arrowIcon
|
||||
}
|
||||
} else {
|
||||
arrowIcon
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.rotate(if (incognitoDisplayMode.value == IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD) rotation else 0f),
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = primaryActionsToggleStyle.foreground.solidColor(
|
||||
context,
|
||||
default = FlorisImeTheme.fallbackContentColor()
|
||||
),
|
||||
)
|
||||
if (it) 180f else 0f
|
||||
}
|
||||
val arrowIcon = if (flipToggles) {
|
||||
Icons.AutoMirrored.Default.KeyboardArrowLeft
|
||||
} else {
|
||||
Icons.AutoMirrored.Default.KeyboardArrowRight
|
||||
}
|
||||
val incognitoIcon = vectorResource(id = R.drawable.ic_incognito)
|
||||
val incognitoDisplayMode = prefs.keyboard.incognitoDisplayMode.observeAsState()
|
||||
val isIncognitoMode = keyboardManager.activeState.isIncognitoMode
|
||||
val icon = if (isIncognitoMode) {
|
||||
when (incognitoDisplayMode.value) {
|
||||
IncognitoDisplayMode.REPLACE_SHARED_ACTIONS_TOGGLE -> incognitoIcon!!
|
||||
IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD -> arrowIcon
|
||||
}
|
||||
} else {
|
||||
arrowIcon
|
||||
}
|
||||
SnyggIcon(
|
||||
modifier = Modifier.rotate(if (incognitoDisplayMode.value == IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD) rotation else 0f),
|
||||
imageVector = icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,10 +224,10 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
exit = exitTransition,
|
||||
) {
|
||||
QuickActionsRow(
|
||||
FlorisImeUi.SmartbarSharedActionsRow.elementName,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.smartbarHeight),
|
||||
elementName = FlorisImeUi.SmartbarSharedActionsRow,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -257,7 +235,8 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
fun ExtendedActionsToggle() {
|
||||
IconButton(
|
||||
SnyggIconButton(
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle.elementName,
|
||||
onClick = {
|
||||
if (/* was */ extendedActionsExpanded) {
|
||||
keyboardManager.activeState.isActionsOverflowVisible = false
|
||||
@@ -265,38 +244,25 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
prefs.smartbar.extendedActionsExpanded.set(!extendedActionsExpanded)
|
||||
},
|
||||
) {
|
||||
Box(
|
||||
val transition = updateTransition(extendedActionsExpanded, label = "smartbarSecondaryRowToggleBtn")
|
||||
val alpha by transition.animateFloat(label = "alpha") { if (it) 1f else 0f }
|
||||
val rotation by transition.animateFloat(label = "rotation") { if (it) 180f else 0f }
|
||||
// Expanded icon
|
||||
SnyggIcon(
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle.elementName,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxHeight()
|
||||
.aspectRatio(1f)
|
||||
.snyggShadow(secondaryActionsToggleStyle)
|
||||
.snyggBorder(context, secondaryActionsToggleStyle)
|
||||
.snyggBackground(context, secondaryActionsToggleStyle),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val transition = updateTransition(extendedActionsExpanded, label = "smartbarSecondaryRowToggleBtn")
|
||||
val alpha by transition.animateFloat(label = "alpha") { if (it) 1f else 0f }
|
||||
val rotation by transition.animateFloat(label = "rotation") { if (it) 180f else 0f }
|
||||
// Expanded icon
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.alpha(alpha)
|
||||
.rotate(rotation),
|
||||
imageVector = Icons.Default.UnfoldLess,
|
||||
contentDescription = null,
|
||||
tint = secondaryActionsToggleStyle.foreground.solidColor(context),
|
||||
)
|
||||
// Not expanded icon
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.alpha(1f - alpha)
|
||||
.rotate(rotation - 180f),
|
||||
imageVector = Icons.Default.UnfoldMore,
|
||||
contentDescription = null,
|
||||
tint = secondaryActionsToggleStyle.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
.alpha(alpha)
|
||||
.rotate(rotation),
|
||||
imageVector = Icons.Default.UnfoldLess,
|
||||
)
|
||||
// Not expanded icon
|
||||
SnyggIcon(
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle.elementName,
|
||||
modifier = Modifier
|
||||
.alpha(1f - alpha)
|
||||
.rotate(rotation - 180f),
|
||||
imageVector = Icons.Default.UnfoldMore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,11 +304,11 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
SnyggRow(
|
||||
FlorisImeUi.Smartbar.elementName,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.smartbarHeight)
|
||||
.snyggBackground(context, smartbarStyle),
|
||||
.height(FlorisImeSizing.smartbarHeight),
|
||||
) {
|
||||
when (smartbarLayout) {
|
||||
SmartbarLayout.SUGGESTIONS_ONLY -> {
|
||||
@@ -357,7 +323,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
if (shouldShowInlineSuggestionsUi) {
|
||||
InlineSuggestionsUi(inlineSuggestions)
|
||||
} else {
|
||||
QuickActionsRow(elementName = FlorisImeUi.SmartbarSharedActionsRow)
|
||||
QuickActionsRow(FlorisImeUi.SmartbarSharedActionsRow.elementName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,16 +356,16 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
private fun SmartbarSecondaryRow(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val prefs by florisPreferenceModel()
|
||||
val smartbarLayout by prefs.smartbar.layout.observeAsState()
|
||||
val secondaryRowStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarExtendedActionsRow)
|
||||
val secondaryRowStyle = rememberSnyggThemeQuery(FlorisImeUi.SmartbarExtendedActionsRow.elementName)
|
||||
val windowStyle = rememberSnyggThemeQuery(FlorisImeUi.Window.elementName)
|
||||
val extendedActionsExpanded by prefs.smartbar.extendedActionsExpanded.observeAsState()
|
||||
val extendedActionsPlacement by prefs.smartbar.extendedActionsPlacement.observeAsState()
|
||||
val background = secondaryRowStyle.background.solidColor(context).let { color ->
|
||||
val background = secondaryRowStyle.background().let { color ->
|
||||
if (extendedActionsPlacement == ExtendedActionsPlacement.OVERLAY_APP_UI) {
|
||||
if (color.isUnspecified || color.alpha == 0f) {
|
||||
FlorisImeTheme.style.get(FlorisImeUi.Keyboard).background.solidColor(context, default = Color.Black)
|
||||
windowStyle.background(default = Color.Black)
|
||||
} else {
|
||||
color
|
||||
}
|
||||
@@ -414,11 +380,11 @@ private fun SmartbarSecondaryRow(modifier: Modifier = Modifier) {
|
||||
exit = VerticalExitTransition,
|
||||
) {
|
||||
QuickActionsRow(
|
||||
FlorisImeUi.SmartbarExtendedActionsRow.elementName,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.smartbarHeight)
|
||||
.background(background),
|
||||
elementName = FlorisImeUi.SmartbarExtendedActionsRow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ fun QuickAction.computeDisplayName(evaluator: ComputingEvaluator): String {
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> R.string.quick_action__clipboard_select_all
|
||||
KeyCode.IME_UI_MODE_CLIPBOARD -> R.string.quick_action__ime_ui_mode_clipboard
|
||||
KeyCode.IME_UI_MODE_MEDIA -> R.string.quick_action__ime_ui_mode_media
|
||||
KeyCode.LANGUAGE_SWITCH -> R.string.quick_action__language_switch
|
||||
KeyCode.SETTINGS -> R.string.quick_action__settings
|
||||
KeyCode.UNDO -> R.string.quick_action__undo
|
||||
KeyCode.REDO -> R.string.quick_action__redo
|
||||
@@ -97,7 +98,7 @@ fun QuickAction.computeDisplayName(evaluator: ComputingEvaluator): String {
|
||||
KeyCode.TOGGLE_AUTOCORRECT -> R.string.quick_action__toggle_autocorrect
|
||||
KeyCode.VOICE_INPUT -> R.string.quick_action__voice_input
|
||||
// TODO: In the future this will be merged into the resize keyboard panel, for now it is a separate action
|
||||
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> R.string.quick_action__one_handed_mode
|
||||
KeyCode.TOGGLE_COMPACT_LAYOUT -> R.string.quick_action__one_handed_mode
|
||||
KeyCode.DRAG_MARKER -> if (evaluator.state.debugShowDragAndDropHelpers) {
|
||||
R.string.quick_action__drag_marker
|
||||
} else {
|
||||
@@ -125,6 +126,7 @@ fun QuickAction.computeTooltip(evaluator: ComputingEvaluator): String {
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> R.string.quick_action__clipboard_select_all__tooltip
|
||||
KeyCode.IME_UI_MODE_CLIPBOARD -> R.string.quick_action__ime_ui_mode_clipboard__tooltip
|
||||
KeyCode.IME_UI_MODE_MEDIA -> R.string.quick_action__ime_ui_mode_media__tooltip
|
||||
KeyCode.LANGUAGE_SWITCH -> R.string.quick_action__language_switch__tooltip
|
||||
KeyCode.SETTINGS -> R.string.quick_action__settings__tooltip
|
||||
KeyCode.UNDO -> R.string.quick_action__undo__tooltip
|
||||
KeyCode.REDO -> R.string.quick_action__redo__tooltip
|
||||
@@ -133,7 +135,7 @@ fun QuickAction.computeTooltip(evaluator: ComputingEvaluator): String {
|
||||
KeyCode.TOGGLE_AUTOCORRECT -> R.string.quick_action__toggle_autocorrect__tooltip
|
||||
KeyCode.VOICE_INPUT -> R.string.quick_action__voice_input__tooltip
|
||||
// TODO: In the future this will be merged into the resize keyboard panel, for now it is a separate action
|
||||
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> R.string.quick_action__one_handed_mode__tooltip
|
||||
KeyCode.TOGGLE_COMPACT_LAYOUT -> R.string.quick_action__one_handed_mode__tooltip
|
||||
KeyCode.DRAG_MARKER -> if (evaluator.state.debugShowDragAndDropHelpers) {
|
||||
R.string.quick_action__drag_marker__tooltip
|
||||
} else {
|
||||
|
||||
@@ -26,7 +26,7 @@ import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.plus
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
|
||||
private val QuickActionJsonConfig = Json(DefaultJsonConfig) {
|
||||
val QuickActionJsonConfig = Json(DefaultJsonConfig) {
|
||||
classDiscriminator = "$"
|
||||
encodeDefaults = false
|
||||
ignoreUnknownKeys = true
|
||||
@@ -47,7 +47,7 @@ data class QuickActionArrangement(
|
||||
val dynamicActions: List<QuickAction>,
|
||||
val hiddenActions: List<QuickAction>,
|
||||
) {
|
||||
fun contains(action: QuickAction): Boolean {
|
||||
operator fun contains(action: QuickAction): Boolean {
|
||||
return stickyAction == action || dynamicActions.contains(action) || hiddenActions.contains(action)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ data class QuickActionArrangement(
|
||||
QuickAction.InsertKey(TextKeyData.TOGGLE_INCOGNITO_MODE),
|
||||
QuickAction.InsertKey(TextKeyData.IME_UI_MODE_CLIPBOARD),
|
||||
QuickAction.InsertKey(TextKeyData.IME_UI_MODE_MEDIA),
|
||||
QuickAction.InsertKey(TextKeyData.COMPACT_LAYOUT_TO_RIGHT),
|
||||
QuickAction.InsertKey(TextKeyData.TOGGLE_COMPACT_LAYOUT),
|
||||
QuickAction.InsertKey(TextKeyData.TOGGLE_AUTOCORRECT),
|
||||
QuickAction.InsertKey(TextKeyData.ARROW_UP),
|
||||
QuickAction.InsertKey(TextKeyData.ARROW_DOWN),
|
||||
@@ -72,6 +72,7 @@ data class QuickActionArrangement(
|
||||
QuickAction.InsertKey(TextKeyData.CLIPBOARD_CUT),
|
||||
QuickAction.InsertKey(TextKeyData.CLIPBOARD_PASTE),
|
||||
QuickAction.InsertKey(TextKeyData.CLIPBOARD_SELECT_ALL),
|
||||
QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH),
|
||||
),
|
||||
hiddenActions = listOf(
|
||||
),
|
||||
|
||||
@@ -16,11 +16,7 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.smartbar.quickaction
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.waitForUpOrCancellation
|
||||
@@ -30,50 +26,33 @@ import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
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.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.compose.tooltip.tooltip
|
||||
import dev.patrickgold.compose.tooltip.PlainTooltip
|
||||
import dev.patrickgold.florisboard.FlorisImeService
|
||||
import dev.patrickgold.florisboard.ime.keyboard.ComputingEvaluator
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.keyboard.computeImageVector
|
||||
import dev.patrickgold.florisboard.ime.keyboard.computeLabel
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import org.florisboard.lib.snygg.ui.shape
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggClip
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
|
||||
private val BackgroundAnimationSpec = tween<Color>(durationMillis = 150, easing = FastOutSlowInEasing)
|
||||
private val DebugHelperColor = Color.Red.copy(alpha = 0.5f)
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
enum class QuickActionBarType {
|
||||
INTERACTIVE_BUTTON,
|
||||
INTERACTIVE_TILE,
|
||||
STATIC_TILE;
|
||||
EDITOR_TILE;
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -88,47 +67,17 @@ fun QuickActionButton(
|
||||
val inputFeedbackController = FlorisImeService.inputFeedbackController()
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
val isEnabled = type == QuickActionBarType.STATIC_TILE || evaluator.evaluateEnabled(action.keyData())
|
||||
val element = when (type) {
|
||||
val isEnabled = type == QuickActionBarType.EDITOR_TILE || evaluator.evaluateEnabled(action.keyData())
|
||||
val elementName = when (type) {
|
||||
QuickActionBarType.INTERACTIVE_BUTTON -> FlorisImeUi.SmartbarActionKey
|
||||
else -> FlorisImeUi.SmartbarActionTile
|
||||
QuickActionBarType.INTERACTIVE_TILE -> FlorisImeUi.SmartbarActionTile
|
||||
QuickActionBarType.EDITOR_TILE -> FlorisImeUi.SmartbarActionsEditorTile
|
||||
}.elementName
|
||||
val selector = when {
|
||||
isPressed -> SnyggSelector.PRESSED
|
||||
!isEnabled -> SnyggSelector.DISABLED
|
||||
else -> null
|
||||
}
|
||||
// We always need to know both state's styles to animate smoothly
|
||||
val actionStyleNotPressed = FlorisImeTheme.style.get(
|
||||
element = element,
|
||||
code = action.keyData().code,
|
||||
isPressed = false,
|
||||
isDisabled = !isEnabled,
|
||||
)
|
||||
val actionStylePressed = FlorisImeTheme.style.get(
|
||||
element = element,
|
||||
code = action.keyData().code,
|
||||
isPressed = true,
|
||||
isDisabled = !isEnabled,
|
||||
)
|
||||
val actionStyle = if (isPressed) actionStylePressed else actionStyleNotPressed
|
||||
val bgColor by animateColorAsState(
|
||||
targetValue = if (isPressed) {
|
||||
actionStylePressed.background.solidColor(context)
|
||||
} else {
|
||||
if (actionStyleNotPressed.background.solidColor(context).alpha == 0f) {
|
||||
actionStylePressed.background.solidColor(context).copy(0f)
|
||||
} else {
|
||||
actionStyleNotPressed.background.solidColor(context)
|
||||
}
|
||||
},
|
||||
animationSpec = BackgroundAnimationSpec, label = "bgColor",
|
||||
)
|
||||
val fgColor = when (action.keyData().code) {
|
||||
KeyCode.DRAG_MARKER -> {
|
||||
DebugHelperColor
|
||||
}
|
||||
|
||||
else -> {
|
||||
actionStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
}
|
||||
}
|
||||
val fgAlpha = if (action.keyData().code == KeyCode.NOOP) 0.5f else 1f
|
||||
|
||||
// Need to manually cancel an action if this composable suddenly leaves the composition to prevent the key from
|
||||
// being stuck in the pressed state
|
||||
@@ -140,92 +89,70 @@ fun QuickActionButton(
|
||||
}
|
||||
}
|
||||
|
||||
val tooltipModifier = if (type == QuickActionBarType.INTERACTIVE_BUTTON) {
|
||||
Modifier.tooltip(action.computeTooltip(evaluator))
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.aspectRatio(1f)
|
||||
.alpha(fgAlpha)
|
||||
.snyggShadow(actionStyle)
|
||||
.snyggBorder(context, actionStyle)
|
||||
.background(bgColor, actionStyle.shape.shape())
|
||||
.snyggClip(actionStyle)
|
||||
.indication(interactionSource, LocalIndication.current)
|
||||
.pointerInput(action, isEnabled) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
down.consume()
|
||||
if (isEnabled && type != QuickActionBarType.STATIC_TILE) {
|
||||
val press = PressInteraction.Press(down.position)
|
||||
inputFeedbackController?.keyPress(TextKeyData.UNSPECIFIED)
|
||||
interactionSource.tryEmit(press)
|
||||
action.onPointerDown(context)
|
||||
val up = waitForUpOrCancellation()
|
||||
if (up != null) {
|
||||
up.consume()
|
||||
interactionSource.tryEmit(PressInteraction.Release(press))
|
||||
action.onPointerUp(context)
|
||||
} else {
|
||||
interactionSource.tryEmit(PressInteraction.Cancel(press))
|
||||
action.onPointerCancel(context)
|
||||
PlainTooltip(action.computeTooltip(evaluator), enabled = type == QuickActionBarType.INTERACTIVE_BUTTON) {
|
||||
SnyggBox(
|
||||
elementName = elementName,
|
||||
attributes = mapOf(FlorisImeUi.Attr.Code to action.keyData().code),
|
||||
selector = selector,
|
||||
modifier = modifier,
|
||||
clickAndSemanticsModifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.indication(interactionSource, LocalIndication.current)
|
||||
.pointerInput(action, isEnabled) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
down.consume()
|
||||
if (isEnabled && type != QuickActionBarType.EDITOR_TILE) {
|
||||
val press = PressInteraction.Press(down.position)
|
||||
inputFeedbackController?.keyPress(TextKeyData.UNSPECIFIED)
|
||||
interactionSource.tryEmit(press)
|
||||
action.onPointerDown(context)
|
||||
val up = waitForUpOrCancellation()
|
||||
if (up != null) {
|
||||
up.consume()
|
||||
interactionSource.tryEmit(PressInteraction.Release(press))
|
||||
action.onPointerUp(context)
|
||||
} else {
|
||||
interactionSource.tryEmit(PressInteraction.Cancel(press))
|
||||
action.onPointerCancel(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.then(tooltipModifier)
|
||||
.padding(8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
// Render foreground
|
||||
Box(
|
||||
modifier = Modifier.size(FlorisImeSizing.smartbarHeight * 0.5f),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
when (action) {
|
||||
is QuickAction.InsertKey -> {
|
||||
val (imageVector, label) = remember(action, evaluator) {
|
||||
evaluator.computeImageVector(action.data) to evaluator.computeLabel(action.data)
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
// Render foreground
|
||||
Box(
|
||||
modifier = Modifier.size(FlorisImeSizing.smartbarHeight * 0.5f),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
when (action) {
|
||||
is QuickAction.InsertKey -> {
|
||||
val (imageVector, label) = remember(action, evaluator) {
|
||||
evaluator.computeImageVector(action.data) to evaluator.computeLabel(action.data)
|
||||
}
|
||||
if (imageVector != null) {
|
||||
SnyggIcon(imageVector = imageVector)
|
||||
} else if (label != null) {
|
||||
SnyggText(text = label)
|
||||
}
|
||||
}
|
||||
if (imageVector != null) {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = null,
|
||||
tint = fgColor,
|
||||
)
|
||||
} else if (label != null) {
|
||||
Text(
|
||||
text = label,
|
||||
color = fgColor,
|
||||
|
||||
is QuickAction.InsertText -> {
|
||||
SnyggText(
|
||||
text = action.data.firstOrNull().toString().ifBlank { "?" },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is QuickAction.InsertText -> {
|
||||
Text(
|
||||
text = action.data.firstOrNull().toString().ifBlank { "?" },
|
||||
color = fgColor,
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render additional info if this is a tile
|
||||
if (type != QuickActionBarType.INTERACTIVE_BUTTON) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = action.computeDisplayName(evaluator = evaluator),
|
||||
color = fgColor,
|
||||
fontSize = if (type == QuickActionBarType.STATIC_TILE) 10.sp else 13.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
// Render additional info if this is a tile
|
||||
if (type != QuickActionBarType.INTERACTIVE_BUTTON) {
|
||||
SnyggText(
|
||||
text = action.computeDisplayName(evaluator = evaluator),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ package dev.patrickgold.florisboard.ime.smartbar.quickaction
|
||||
|
||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
|
||||
@@ -33,7 +34,6 @@ import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -47,8 +47,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -58,25 +56,24 @@ import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.toIntOffset
|
||||
import org.florisboard.lib.snygg.SnyggPropertySet
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggClip
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggIconButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
private const val ItemNotFound = -1
|
||||
private val NoopAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.NOOP))
|
||||
private val DragMarkerAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.DRAG_MARKER))
|
||||
|
||||
@Composable
|
||||
fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
fun QuickActionsEditorPanel() {
|
||||
val prefs by florisPreferenceModel()
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
@@ -99,10 +96,6 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
var activeDragPosition by remember { mutableStateOf(IntOffset.Zero) }
|
||||
var activeDragSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
val panelStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsEditor)
|
||||
val headerStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsEditorHeader)
|
||||
val subheaderStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsEditorSubheader)
|
||||
|
||||
fun findItemForOffsetOrClosestInRow(offset: IntOffset): LazyGridItemInfo? {
|
||||
var closestItemInRow: LazyGridItemInfo? = null
|
||||
// Using manual for loop with indices instead of firstOrNull() because this method gets
|
||||
@@ -249,35 +242,34 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.snyggBackground(context, panelStyle, fallbackColor = FlorisImeTheme.fallbackSurfaceColor())
|
||||
.snyggClip(panelStyle),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.snyggBackground(context, headerStyle),
|
||||
SnyggColumn(FlorisImeUi.SmartbarActionsEditor.elementName, modifier = Modifier.safeDrawingPadding()) {
|
||||
SnyggRow(
|
||||
elementName = FlorisImeUi.SmartbarActionsEditorHeader.elementName,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FlorisIconButton(
|
||||
onClick = {
|
||||
keyboardManager.activeState.isActionsEditorVisible = false
|
||||
},
|
||||
icon = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||
iconColor = headerStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
)
|
||||
Text(
|
||||
// Extra box wrapper is needed to enforce size constraint but still allow for Snygg margin to be used
|
||||
Box(modifier = Modifier.size(48.dp)) {
|
||||
SnyggIconButton(
|
||||
elementName = FlorisImeUi.SmartbarActionsEditorHeaderButton.elementName,
|
||||
modifier = Modifier.fillMaxHeight().aspectRatio(1f),
|
||||
onClick = {
|
||||
keyboardManager.activeState.isActionsEditorVisible = false
|
||||
},
|
||||
) {
|
||||
SnyggIcon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||
)
|
||||
}
|
||||
}
|
||||
SnyggText(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringRes(R.string.quick_actions_editor__header),
|
||||
color = headerStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
fontSize = headerStyle.fontSize.spSize(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(Modifier.size(48.dp))
|
||||
}
|
||||
|
||||
Box {
|
||||
SnyggBox(FlorisImeUi.SmartbarActionsEditorTileGrid.elementName) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier
|
||||
.pointerInput(Unit) {
|
||||
@@ -295,7 +287,6 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
val n = if (stickyAction != NoopAction) 1 else 0
|
||||
Subheader(
|
||||
text = stringRes(R.string.quick_actions_editor__subheader_sticky_action, "n" to n),
|
||||
style = subheaderStyle,
|
||||
)
|
||||
}
|
||||
item(key = keyOf(stickyAction)) {
|
||||
@@ -303,14 +294,13 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
modifier = Modifier.animateItem(),
|
||||
action = stickyAction,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
type = QuickActionBarType.EDITOR_TILE,
|
||||
)
|
||||
}
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
val n = dynamicActions.count { it != NoopAction }
|
||||
Subheader(
|
||||
text = stringRes(R.string.quick_actions_editor__subheader_dynamic_actions, "n" to n),
|
||||
style = subheaderStyle,
|
||||
)
|
||||
}
|
||||
itemsIndexed(dynamicActions, key = { i, a -> keyOf(a) ?: i }) { _, action ->
|
||||
@@ -318,14 +308,13 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
modifier = Modifier.animateItem(),
|
||||
action = action,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
type = QuickActionBarType.EDITOR_TILE,
|
||||
)
|
||||
}
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
val n = hiddenActions.count { it != NoopAction }
|
||||
Subheader(
|
||||
text = stringRes(R.string.quick_actions_editor__subheader_hidden_actions, "n" to n),
|
||||
style = subheaderStyle,
|
||||
)
|
||||
}
|
||||
itemsIndexed(hiddenActions, key = { i, a -> keyOf(a) ?: i }) { _, action ->
|
||||
@@ -333,7 +322,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
modifier = Modifier.animateItem(),
|
||||
action = action,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
type = QuickActionBarType.EDITOR_TILE,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -348,7 +337,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
.offset(-size.width / 2, -size.height / 2),
|
||||
action = activeDragAction!!,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
type = QuickActionBarType.EDITOR_TILE,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -358,17 +347,11 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
@Composable
|
||||
private fun Subheader(
|
||||
text: String,
|
||||
style: SnyggPropertySet,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp, bottom = 8.dp, start = 16.dp, end = 16.dp),
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.SmartbarActionsEditorSubheader.elementName,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = text,
|
||||
color = style.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = style.fontSize.spSize() safeTimes 0.8f,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.smartbar.quickaction
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
@@ -31,17 +29,16 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
@Composable
|
||||
fun QuickActionsOverflowPanel() {
|
||||
@@ -63,19 +60,15 @@ fun QuickActionsOverflowPanel() {
|
||||
actionArrangement.dynamicActions.takeLast(dynamicActionsCountToShow)
|
||||
}
|
||||
|
||||
val panelStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsOverflow)
|
||||
val buttonStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsOverflowCustomizeButton)
|
||||
|
||||
Box(
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.SmartbarActionsOverflow.elementName,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.keyboardUiHeight())
|
||||
.snyggBackground(context, panelStyle),
|
||||
.height(FlorisImeSizing.keyboardUiHeight()),
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp),
|
||||
.fillMaxWidth(),
|
||||
columns = GridCells.Adaptive(FlorisImeSizing.smartbarHeight * 2.2f),
|
||||
) {
|
||||
items(visibleActions) { action ->
|
||||
@@ -87,13 +80,15 @@ fun QuickActionsOverflowPanel() {
|
||||
}
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
SnyggButton(
|
||||
elementName = FlorisImeUi.SmartbarActionsOverflowCustomizeButton.elementName,
|
||||
onClick = { keyboardManager.activeState.isActionsEditorVisible = true },
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
text = stringRes(R.string.quick_actions_overflow__customize_actions_button),
|
||||
style = buttonStyle,
|
||||
)
|
||||
.wrapContentWidth(),
|
||||
) {
|
||||
SnyggText(
|
||||
text = stringRes(R.string.quick_actions_overflow__customize_actions_button),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package dev.patrickgold.florisboard.ime.smartbar.quickaction
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
@@ -32,10 +31,9 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
|
||||
internal val ToggleOverflowPanelAction = QuickAction.InsertKey(TextKeyData.TOGGLE_ACTIONS_OVERFLOW)
|
||||
|
||||
@@ -67,8 +65,6 @@ fun QuickActionsRow(
|
||||
val showOverflowAction = actionArrangement.stickyAction != null ||
|
||||
smartbarLayout != SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED || !sharedActionsExpanded
|
||||
|
||||
val rowStyle = FlorisImeTheme.style.get(elementName)
|
||||
|
||||
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
|
||||
val width = constraints.maxWidth.toDp()
|
||||
val height = constraints.maxHeight.toDp()
|
||||
@@ -85,10 +81,9 @@ fun QuickActionsRow(
|
||||
}.coerceAtLeast(0)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.snyggBackground(context, rowStyle),
|
||||
SnyggRow(
|
||||
elementName = elementName,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.text
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import org.florisboard.lib.snygg.SnyggPropertySet
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.value.SnyggRoundedCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSolidColorValue
|
||||
|
||||
@Composable
|
||||
fun HowDidWeGetHere() {
|
||||
val context = LocalContext.current
|
||||
val extensionManager by context.extensionManager()
|
||||
val keyboardManager by context.keyboardManager()
|
||||
|
||||
val style = SnyggPropertySet(mapOf(
|
||||
"background" to SnyggSolidColorValue(Color.Yellow),
|
||||
"foreground" to SnyggSolidColorValue(Color.Black),
|
||||
"shape" to SnyggRoundedCornerDpShapeValue(16.dp, 16.dp, 16.dp, 16.dp, RoundedCornerShape(16.dp)),
|
||||
))
|
||||
|
||||
@Composable
|
||||
fun ColoredText(text: String) {
|
||||
Text(
|
||||
text = text,
|
||||
color = style.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.keyboardUiHeight())
|
||||
.padding(8.dp),
|
||||
) {
|
||||
SnyggSurface(style = style) {
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
ColoredText(text = "Challenge Complete! - How did we get here?\n")
|
||||
ColoredText(text = "You landed in a state which shouldn't be reachable, possibly related to the \"All keys invisible\" bug. Please report this bug and the steps to reproduce to the devs using the button below. Thanks!")
|
||||
Row {
|
||||
SnyggButton(
|
||||
onClick = {
|
||||
keyboardManager.activeState.rawValue = 0u
|
||||
extensionManager.init()
|
||||
},
|
||||
text = "Try reset keyboard",
|
||||
style = style,
|
||||
)
|
||||
SnyggButton(
|
||||
onClick = {
|
||||
context.launchUrl("https://github.com/florisboard/florisboard/issues/2362")
|
||||
},
|
||||
text = "Report bug to devs",
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -33,16 +32,17 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.InlineSuggestionsStyleCache
|
||||
import dev.patrickgold.florisboard.ime.smartbar.Smartbar
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsOverflowPanel
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboardLayout
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
|
||||
@Composable
|
||||
fun TextInputLayout(
|
||||
@@ -56,6 +56,8 @@ fun TextInputLayout(
|
||||
val state by keyboardManager.activeState.collectAsState()
|
||||
val evaluator by keyboardManager.activeEvaluator.collectAsState()
|
||||
|
||||
InlineSuggestionsStyleCache()
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
@@ -67,31 +69,19 @@ fun TextInputLayout(
|
||||
QuickActionsOverflowPanel()
|
||||
} else {
|
||||
Box {
|
||||
val showIncognitoIcon = evaluator.state.isIncognitoMode && prefs.keyboard.incognitoDisplayMode.observeAsState().value == IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD
|
||||
val incognitoDisplayMode by prefs.keyboard.incognitoDisplayMode.observeAsState()
|
||||
val showIncognitoIcon = evaluator.state.isIncognitoMode &&
|
||||
incognitoDisplayMode == IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD
|
||||
if (showIncognitoIcon) {
|
||||
val indicatorStyle = FlorisImeTheme.style.get(FlorisImeUi.IncognitoModeIndicator)
|
||||
Icon(
|
||||
SnyggIcon(
|
||||
FlorisImeUi.IncognitoModeIndicator.elementName,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(R.drawable.ic_incognito),
|
||||
contentDescription = null,
|
||||
tint = indicatorStyle.foreground.solidColor(
|
||||
context, default = FlorisImeTheme.fallbackContentColor().copy(alpha = 0.067f),
|
||||
),
|
||||
)
|
||||
}
|
||||
val debugLayoutResult by keyboardManager.layoutManager
|
||||
.debugLayoutComputationResultFlow.collectAsState()
|
||||
if (state.keyboardMode != KeyboardMode.EDITING) {
|
||||
if (debugLayoutResult?.allLayoutsSuccess() == true) {
|
||||
TextKeyboardLayout(evaluator = evaluator)
|
||||
} else {
|
||||
HowDidWeGetHere()
|
||||
}
|
||||
} else {
|
||||
HowDidWeGetHere()
|
||||
}
|
||||
TextKeyboardLayout(evaluator = evaluator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ enum class SwipeAction {
|
||||
SELECT_WORDS_PRECISELY,
|
||||
SHIFT,
|
||||
SHOW_INPUT_METHOD_PICKER,
|
||||
SHOW_SUBTYPE_PICKER,
|
||||
SWITCH_TO_PREV_SUBTYPE,
|
||||
SWITCH_TO_NEXT_SUBTYPE,
|
||||
SWITCH_TO_CLIPBOARD_CONTEXT,
|
||||
|
||||
@@ -68,6 +68,7 @@ object KeyCode {
|
||||
const val CLIPBOARD_CLEAR_FULL_HISTORY = -37
|
||||
const val CLIPBOARD_CLEAR_PRIMARY_CLIP = -38
|
||||
|
||||
const val TOGGLE_COMPACT_LAYOUT = -110
|
||||
const val COMPACT_LAYOUT_TO_LEFT = -111
|
||||
const val COMPACT_LAYOUT_TO_RIGHT = -112
|
||||
const val SPLIT_LAYOUT = -113
|
||||
@@ -95,6 +96,7 @@ object KeyCode {
|
||||
const val IME_PREV_SUBTYPE = -225
|
||||
const val IME_NEXT_SUBTYPE = -226
|
||||
const val LANGUAGE_SWITCH = -227
|
||||
const val SHOW_SUBTYPE_PICKER = -228
|
||||
|
||||
const val IME_SHOW_UI = -231
|
||||
const val IME_HIDE_UI = -232
|
||||
|
||||
@@ -121,6 +121,7 @@ class TextKeyData(
|
||||
CLIPBOARD_CLEAR_HISTORY,
|
||||
CLIPBOARD_CLEAR_FULL_HISTORY,
|
||||
CLIPBOARD_CLEAR_PRIMARY_CLIP,
|
||||
TOGGLE_COMPACT_LAYOUT,
|
||||
COMPACT_LAYOUT_TO_LEFT,
|
||||
COMPACT_LAYOUT_TO_RIGHT,
|
||||
UNDO,
|
||||
@@ -336,6 +337,12 @@ class TextKeyData(
|
||||
label = "clipboard_clear_primary_clip",
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.TOGGLE_COMPACT_LAYOUT] */
|
||||
val TOGGLE_COMPACT_LAYOUT = TextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.TOGGLE_COMPACT_LAYOUT,
|
||||
label = "toggle_compact_layout",
|
||||
)
|
||||
/** Predefined key data for [KeyCode.COMPACT_LAYOUT_TO_LEFT] */
|
||||
val COMPACT_LAYOUT_TO_LEFT = TextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
@@ -412,6 +419,12 @@ class TextKeyData(
|
||||
code = KeyCode.SYSTEM_INPUT_METHOD_PICKER,
|
||||
label = "system_input_method_picker",
|
||||
)
|
||||
/** Predefined key data for [KeyCode.SHOW_SUBTYPE_PICKER] */
|
||||
val SHOW_SUBTYPE_PICKER = TextKeyData(
|
||||
type = KeyType.FUNCTION,
|
||||
code = KeyCode.SHOW_SUBTYPE_PICKER,
|
||||
label = "subtype_picker",
|
||||
)
|
||||
/** Predefined key data for [KeyCode.SYSTEM_PREV_INPUT_METHOD] */
|
||||
val SYSTEM_PREV_INPUT_METHOD = TextKeyData(
|
||||
type = KeyType.FUNCTION,
|
||||
|
||||
@@ -28,6 +28,7 @@ import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
@@ -42,7 +43,7 @@ import kotlin.contracts.contract
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class TextKeyboardCache(ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||
private val cache: EnumMap<KeyboardMode, SparseArrayCompat<Deferred<TextKeyboard>>> = EnumMap(KeyboardMode::class.java)
|
||||
private val cache: EnumMap<KeyboardMode, SparseArrayCompat<TextKeyboard>> = EnumMap(KeyboardMode::class.java)
|
||||
private val scope: CoroutineScope = CoroutineScope(ioDispatcher + SupervisorJob())
|
||||
|
||||
init {
|
||||
@@ -105,7 +106,7 @@ class TextKeyboardCache(ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||
* @return The deferred computed keyboard or null if the cache does not have an entry associated with the given
|
||||
* params.
|
||||
*/
|
||||
fun getAsync(mode: KeyboardMode, subtype: Subtype): Deferred<TextKeyboard>? {
|
||||
fun getAsync(mode: KeyboardMode, subtype: Subtype): TextKeyboard? {
|
||||
return cache[mode]!![subtype.hashCode()].also {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "Get keyboard '$mode ${subtype.toShortString()}'" }
|
||||
}
|
||||
@@ -122,7 +123,7 @@ class TextKeyboardCache(ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||
*
|
||||
* @return The deferred computed keyboard either from the cache or from [block].
|
||||
*/
|
||||
fun getOrElseAsync(mode: KeyboardMode, subtype: Subtype, block: suspend () -> TextKeyboard): Deferred<TextKeyboard> {
|
||||
fun getOrElseAsync(mode: KeyboardMode, subtype: Subtype, block: suspend () -> TextKeyboard): TextKeyboard {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
@@ -130,7 +131,7 @@ class TextKeyboardCache(ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||
return if (cachedKeyboard != null) {
|
||||
cachedKeyboard
|
||||
} else {
|
||||
val keyboard = scope.async { block() }
|
||||
val keyboard = runBlocking { block() }
|
||||
set(mode, subtype, keyboard)
|
||||
keyboard
|
||||
}
|
||||
@@ -143,7 +144,7 @@ class TextKeyboardCache(ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
|
||||
* @param subtype The subtype of the computed keyboard to set.
|
||||
* @param keyboard The deferred computed keyboard to set for the given params.
|
||||
*/
|
||||
fun set(mode: KeyboardMode, subtype: Subtype, keyboard: Deferred<TextKeyboard>) {
|
||||
fun set(mode: KeyboardMode, subtype: Subtype, keyboard: TextKeyboard) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "Set keyboard '$mode ${subtype.toShortString()}'" }
|
||||
cache[mode]!![subtype.hashCode()] = keyboard
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@
|
||||
package dev.patrickgold.florisboard.ime.text.keyboard
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -55,7 +57,6 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -78,7 +79,6 @@ import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyType
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.FlorisRect
|
||||
@@ -95,13 +95,15 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.florisboard.lib.android.isOrientationLandscape
|
||||
import org.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun TextKeyboardLayout(
|
||||
@@ -119,8 +121,8 @@ fun TextKeyboardLayout(
|
||||
val glideEnabled = glideEnabledInternal && evaluator.editorInfo.isRichInputEditor &&
|
||||
evaluator.state.keyVariation != KeyVariation.PASSWORD
|
||||
val glideShowTrail by prefs.glide.showTrail.observeAsState()
|
||||
val glideTrailColor = FlorisImeTheme.style.get(element = FlorisImeUi.GlideTrail)
|
||||
.foreground.solidColor(context, default = Color.Green)
|
||||
val glideTrailStyle = rememberSnyggThemeQuery(FlorisImeUi.GlideTrail.elementName)
|
||||
val glideTrailColor = glideTrailStyle.foreground(default = Color.Green)
|
||||
|
||||
val controller = remember { TextKeyboardLayoutController(context) }.also {
|
||||
it.keyboard = keyboard
|
||||
@@ -295,7 +297,7 @@ fun TextKeyboardLayout(
|
||||
val debugShowTouchBoundaries by prefs.devtools.showKeyTouchBoundaries.observeAsState()
|
||||
for (textKey in keyboard.keys()) {
|
||||
TextKeyButton(
|
||||
textKey, evaluator, fontSizeMultiplier,
|
||||
textKey, evaluator,
|
||||
debugShowTouchBoundaries,
|
||||
)
|
||||
}
|
||||
@@ -316,49 +318,29 @@ fun TextKeyboardLayout(
|
||||
private fun TextKeyButton(
|
||||
key: TextKey,
|
||||
evaluator: ComputingEvaluator,
|
||||
fontSizeMultiplier: Float,
|
||||
debugShowTouchBoundaries: Boolean,
|
||||
) = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val keyStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.Key,
|
||||
code = key.computedData.code,
|
||||
mode = evaluator.state.inputShiftState.value,
|
||||
isPressed = key.isPressed && key.isEnabled,
|
||||
isDisabled = !key.isEnabled,
|
||||
evaluator.keyboard.mode
|
||||
val attributes = mapOf(
|
||||
FlorisImeUi.Attr.Code to key.computedData.code,
|
||||
FlorisImeUi.Attr.Mode to evaluator.keyboard.mode.attrName(),
|
||||
FlorisImeUi.Attr.ShiftState to evaluator.state.inputShiftState.attrName(),
|
||||
)
|
||||
val fontSize = keyStyle.fontSize.spSize() safeTimes fontSizeMultiplier safeTimes when (key.computedData.code) {
|
||||
KeyCode.VIEW_CHARACTERS,
|
||||
KeyCode.VIEW_SYMBOLS,
|
||||
KeyCode.VIEW_SYMBOLS2 -> 0.80f
|
||||
KeyCode.VIEW_NUMERIC,
|
||||
KeyCode.VIEW_NUMERIC_ADVANCED -> 0.55f
|
||||
else -> 1.0f
|
||||
val selector = when {
|
||||
!key.isEnabled -> SnyggSelector.DISABLED
|
||||
key.isPressed -> SnyggSelector.PRESSED
|
||||
else -> SnyggSelector.NONE
|
||||
}
|
||||
val size = key.visibleBounds.size.toDpSize()
|
||||
Box(
|
||||
SnyggBox(
|
||||
FlorisImeUi.Key.elementName,
|
||||
attributes = attributes,
|
||||
selector = selector,
|
||||
modifier = Modifier
|
||||
.requiredSize(size)
|
||||
.absoluteOffset { key.visibleBounds.topLeft.toIntOffset() },
|
||||
) {
|
||||
// TODO: maybe make this customizable through a size property for keyStyle
|
||||
val isReducedHeight = key.computedData.let { it.code == KeyCode.ENTER || it.code == KeyCode.SPACE }
|
||||
SnyggSurface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.run {
|
||||
if (isReducedHeight && FlorisImeTheme.config.isBorderless) {
|
||||
this.padding(vertical = size.height * 0.15f)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
.fillMaxHeight(),
|
||||
style = keyStyle,
|
||||
clip = false,
|
||||
) { }
|
||||
val isTelpadKey = key.computedData.type == KeyType.NUMERIC && evaluator.keyboard.mode == KeyboardMode.PHONE
|
||||
val isTelPadKey = key.computedData.type == KeyType.NUMERIC && evaluator.keyboard.mode == KeyboardMode.PHONE
|
||||
key.label?.let { label ->
|
||||
var customLabel = label
|
||||
if (key.computedData.code == KeyCode.SPACE) {
|
||||
@@ -370,52 +352,29 @@ private fun TextKeyButton(
|
||||
SpaceBarMode.SPACE_BAR_KEY -> customLabel = "␣"
|
||||
}
|
||||
}
|
||||
Text(
|
||||
SnyggText(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(if (isTelpadKey) BiasAlignment(-0.5f, 0f) else Alignment.Center),
|
||||
.align(if (isTelPadKey) BiasAlignment(-0.5f, 0f) else Alignment.Center),
|
||||
text = customLabel,
|
||||
color = keyStyle.foreground.solidColor(context),
|
||||
fontSize = fontSize,
|
||||
maxLines = if (key.computedData.code == KeyCode.VIEW_NUMERIC_ADVANCED) 2 else 1,
|
||||
softWrap = key.computedData.code == KeyCode.VIEW_NUMERIC_ADVANCED,
|
||||
overflow = when (key.computedData.code) {
|
||||
KeyCode.SPACE -> TextOverflow.Ellipsis
|
||||
else -> TextOverflow.Visible
|
||||
},
|
||||
)
|
||||
}
|
||||
key.hintedLabel?.let { hintedLabel ->
|
||||
val keyHintStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.KeyHint,
|
||||
code = key.computedHintData.code,
|
||||
mode = evaluator.state.inputShiftState.value,
|
||||
isPressed = key.isPressed,
|
||||
)
|
||||
val hintFontSize = keyHintStyle.fontSize.spSize() safeTimes fontSizeMultiplier
|
||||
Text(
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.KeyHint.elementName,
|
||||
attributes = attributes,
|
||||
selector = selector,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(if (isTelpadKey) BiasAlignment(0.5f, 0f) else Alignment.TopEnd)
|
||||
.snyggBackground(context, keyHintStyle)
|
||||
.padding(horizontal = (key.visibleBounds.width / 12f).toDp()),
|
||||
.align(if (isTelPadKey) BiasAlignment(0.5f, 0f) else Alignment.TopEnd),
|
||||
text = hintedLabel,
|
||||
color = keyHintStyle.foreground.solidColor(context),
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = hintFontSize,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
overflow = TextOverflow.Visible,
|
||||
)
|
||||
}
|
||||
key.foregroundImageVector?.let { imageVector ->
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.requiredSize(fontSize.toDp() * 1.1f)
|
||||
.align(Alignment.Center),
|
||||
SnyggIcon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
imageVector = imageVector,
|
||||
contentDescription = null,
|
||||
tint = keyStyle.foreground.solidColor(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
import android.content.Context
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import org.florisboard.lib.snygg.value.SnyggAssetResolver
|
||||
import java.net.URI
|
||||
|
||||
class FlorisAssetResolver(val context: Context, val themeInfo: ThemeManager.ThemeInfo) : SnyggAssetResolver {
|
||||
override fun resolveAbsolutePath(uri: String) = runCatching {
|
||||
val uri = URI.create(uri)
|
||||
require(uri.scheme == "flex")
|
||||
require(uri.authority.isNullOrEmpty())
|
||||
val baseDir = checkNotNull(themeInfo.loadedDir) { "Loaded directory was null" }
|
||||
val basePath = baseDir.canonicalPath
|
||||
val canonicalFile = baseDir.subFile(uri.path).canonicalFile
|
||||
val canonicalPath = canonicalFile.path
|
||||
check(canonicalPath.startsWith(basePath)) {
|
||||
"Calculated path '$canonicalPath' does not start with base path '$basePath'"
|
||||
}
|
||||
check(canonicalFile.exists()) {
|
||||
"Calculated path '$canonicalPath' does not exist"
|
||||
}
|
||||
check(canonicalFile.isFile()) {
|
||||
"Calculated path '$canonicalPath' is not a file"
|
||||
}
|
||||
canonicalPath
|
||||
}.onFailure { exception ->
|
||||
flogError { "FlorisAssetResolver failed to resolve URI '$uri'\n error: ${exception.message}\n with: $themeInfo" }
|
||||
}
|
||||
}
|
||||
@@ -18,93 +18,28 @@ package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import dev.patrickgold.florisboard.ime.input.InputShiftState
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.Snygg
|
||||
import org.florisboard.lib.snygg.SnyggStylesheet
|
||||
import org.florisboard.lib.snygg.ui.ProvideSnyggUiDefaults
|
||||
import org.florisboard.lib.snygg.ui.SnyggUiDefaults
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.snygg.ui.ProvideSnyggTheme
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggTheme
|
||||
|
||||
private val LocalConfig = staticCompositionLocalOf<ThemeExtensionComponent> { error("not init") }
|
||||
private val LocalStyle = staticCompositionLocalOf<SnyggStylesheet> { error("not init") }
|
||||
|
||||
private val MaterialDarkFallbackPalette = darkColorScheme()
|
||||
private val MaterialLightFallbackPalette = lightColorScheme()
|
||||
|
||||
object FlorisImeTheme {
|
||||
val config: ThemeExtensionComponent
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalConfig.current
|
||||
|
||||
val style: SnyggStylesheet
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalStyle.current
|
||||
|
||||
@Composable
|
||||
fun fallbackSurfaceColor(): Color {
|
||||
return if (config.isNightTheme) Color.Black else Color.White
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun fallbackContentColor(): Color {
|
||||
return if (config.isNightTheme) Color.White else Color.Black
|
||||
}
|
||||
|
||||
fun init() {
|
||||
Snygg.init(
|
||||
stylesheetSpec = FlorisImeUiSpec,
|
||||
rulePreferredElementSorting = listOf(
|
||||
FlorisImeUi.Keyboard,
|
||||
FlorisImeUi.Key,
|
||||
FlorisImeUi.KeyHint,
|
||||
FlorisImeUi.KeyPopup,
|
||||
FlorisImeUi.Smartbar,
|
||||
FlorisImeUi.SmartbarSharedActionsRow,
|
||||
FlorisImeUi.SmartbarSharedActionsToggle,
|
||||
FlorisImeUi.SmartbarExtendedActionsRow,
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle,
|
||||
FlorisImeUi.SmartbarActionKey,
|
||||
FlorisImeUi.SmartbarActionTile,
|
||||
FlorisImeUi.SmartbarActionsOverflow,
|
||||
FlorisImeUi.SmartbarActionsOverflowCustomizeButton,
|
||||
FlorisImeUi.SmartbarActionsEditor,
|
||||
FlorisImeUi.SmartbarActionsEditorHeader,
|
||||
FlorisImeUi.SmartbarActionsEditorSubheader,
|
||||
FlorisImeUi.SmartbarCandidatesRow,
|
||||
FlorisImeUi.SmartbarCandidateWord,
|
||||
FlorisImeUi.SmartbarCandidateClip,
|
||||
FlorisImeUi.SmartbarCandidateSpacer,
|
||||
),
|
||||
rulePlaceholders = mapOf(
|
||||
"c:delete" to KeyCode.DELETE,
|
||||
"c:enter" to KeyCode.ENTER,
|
||||
"c:shift" to KeyCode.SHIFT,
|
||||
"c:space" to KeyCode.SPACE,
|
||||
"sh:unshifted" to InputShiftState.UNSHIFTED.value,
|
||||
"sh:shifted_manual" to InputShiftState.SHIFTED_MANUAL.value,
|
||||
"sh:shifted_automatic" to InputShiftState.SHIFTED_AUTOMATIC.value,
|
||||
"sh:caps_lock" to InputShiftState.CAPS_LOCK.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -112,34 +47,29 @@ fun FlorisImeTheme(content: @Composable () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val themeManager by context.themeManager()
|
||||
|
||||
val prefs by florisPreferenceModel()
|
||||
val accentColor by prefs.theme.accentColor.observeAsState()
|
||||
|
||||
val activeThemeInfo by themeManager.activeThemeInfo.observeAsNonNullState()
|
||||
val activeConfig = remember(activeThemeInfo) { activeThemeInfo.config }
|
||||
val activeStyle = remember(activeThemeInfo) { activeThemeInfo.stylesheet }
|
||||
val materialColors = if (activeConfig.isNightTheme) {
|
||||
if (AndroidVersion.ATLEAST_API31_S) {
|
||||
dynamicDarkColorScheme(context)
|
||||
} else {
|
||||
MaterialDarkFallbackPalette
|
||||
}
|
||||
} else {
|
||||
if (AndroidVersion.ATLEAST_API31_S) {
|
||||
dynamicLightColorScheme(context)
|
||||
} else {
|
||||
MaterialLightFallbackPalette
|
||||
}
|
||||
|
||||
val assetResolver = remember(activeThemeInfo) {
|
||||
FlorisAssetResolver(context, activeThemeInfo)
|
||||
}
|
||||
MaterialTheme(materialColors) {
|
||||
val snyggTheme = rememberSnyggTheme(activeStyle, assetResolver)
|
||||
|
||||
MaterialTheme {
|
||||
CompositionLocalProvider(
|
||||
LocalConfig provides activeConfig,
|
||||
LocalStyle provides activeStyle,
|
||||
LocalTextStyle provides TextStyle.Default,
|
||||
) {
|
||||
val fallbackContentColor = FlorisImeTheme.fallbackContentColor()
|
||||
val fallbackSurfaceColor = FlorisImeTheme.fallbackSurfaceColor()
|
||||
val snyggUiDefaults = remember(fallbackContentColor, fallbackSurfaceColor) {
|
||||
SnyggUiDefaults(fallbackContentColor, fallbackSurfaceColor)
|
||||
}
|
||||
ProvideSnyggUiDefaults(snyggUiDefaults, content)
|
||||
ProvideSnyggTheme(
|
||||
snyggTheme = snyggTheme,
|
||||
dynamicAccentColor = accentColor,
|
||||
assetResolver = assetResolver,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,182 +16,296 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.ime.input.InputShiftState
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.SnyggStylesheet
|
||||
|
||||
val FlorisImeThemeBaseStyle = SnyggStylesheet {
|
||||
val FlorisImeThemeBaseStyle = SnyggStylesheet.v2 {
|
||||
defines {
|
||||
"primary" to rgbaColor(76, 175, 80)
|
||||
"primaryVariant" to rgbaColor(56, 142, 60)
|
||||
"secondary" to rgbaColor(245, 124, 0)
|
||||
"secondaryVariant" to rgbaColor(230, 81, 0)
|
||||
"background" to rgbaColor(33, 33, 33)
|
||||
"surface" to rgbaColor(66, 66, 66)
|
||||
"surfaceVariant" to rgbaColor(97, 97, 97)
|
||||
"--primary" to rgbaColor(76, 175, 80)
|
||||
"--primary-variant" to rgbaColor(56, 142, 60)
|
||||
"--secondary" to rgbaColor(245, 124, 0)
|
||||
"--secondary-variant" to rgbaColor(230, 81, 0)
|
||||
"--background" to rgbaColor(33, 33, 33)
|
||||
"--surface" to rgbaColor(66, 66, 66)
|
||||
"--surface-variant" to rgbaColor(97, 97, 97)
|
||||
|
||||
"onBackground" to rgbaColor(255, 255, 255)
|
||||
"onSurface" to rgbaColor(255, 255, 255)
|
||||
"--on-primary" to rgbaColor(240, 240, 240)
|
||||
"--on-background" to rgbaColor(255, 255, 255)
|
||||
"--on-background-disabled" to rgbaColor(80, 80, 80)
|
||||
"--on-surface" to rgbaColor(255, 255, 255)
|
||||
|
||||
"--shape" to roundedCornerShape(8.dp)
|
||||
"--shape-variant" to roundedCornerShape(12.dp)
|
||||
}
|
||||
|
||||
FlorisImeUi.Keyboard {
|
||||
background = `var`("background")
|
||||
FlorisImeUi.Window.elementName {
|
||||
background = `var`("--background")
|
||||
foreground = `var`("--on-background")
|
||||
}
|
||||
FlorisImeUi.Key {
|
||||
background = `var`("surface")
|
||||
foreground = `var`("onSurface")
|
||||
fontSize = size(22.sp)
|
||||
|
||||
FlorisImeUi.Key.elementName {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
fontSize = fontSize(22.sp)
|
||||
shadowElevation = size(2.dp)
|
||||
shape = roundedCornerShape(20)
|
||||
shape = `var`("--shape")
|
||||
textMaxLines = textMaxLines(1)
|
||||
}
|
||||
FlorisImeUi.Key(pressedSelector = true) {
|
||||
background = `var`("surfaceVariant")
|
||||
foreground = `var`("onSurface")
|
||||
FlorisImeUi.Key.elementName(selector = SnyggSelector.PRESSED) {
|
||||
background = `var`("--surface-variant")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
FlorisImeUi.Key(codes = listOf(KeyCode.ENTER)) {
|
||||
background = `var`("primary")
|
||||
foreground = `var`("onSurface")
|
||||
FlorisImeUi.Key.elementName(FlorisImeUi.Attr.Code to listOf(KeyCode.ENTER)) {
|
||||
background = `var`("--primary")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
FlorisImeUi.Key(codes = listOf(KeyCode.ENTER), pressedSelector = true) {
|
||||
background = `var`("primaryVariant")
|
||||
foreground = `var`("onSurface")
|
||||
FlorisImeUi.Key.elementName(FlorisImeUi.Attr.Code to listOf(KeyCode.ENTER), selector = SnyggSelector.PRESSED) {
|
||||
background = `var`("--primary-variant")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
FlorisImeUi.Key(
|
||||
codes = listOf(KeyCode.SHIFT),
|
||||
modes = listOf(InputShiftState.CAPS_LOCK.value),
|
||||
FlorisImeUi.Key.elementName(FlorisImeUi.Attr.Code to listOf(KeyCode.SPACE)) {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
fontSize = fontSize(12.sp)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.Key.elementName(FlorisImeUi.Attr.Code to listOf(
|
||||
KeyCode.VIEW_CHARACTERS,
|
||||
KeyCode.VIEW_SYMBOLS,
|
||||
KeyCode.VIEW_SYMBOLS2,
|
||||
)) {
|
||||
fontSize = fontSize(18.sp)
|
||||
}
|
||||
FlorisImeUi.Key.elementName(FlorisImeUi.Attr.Code to listOf(
|
||||
KeyCode.VIEW_NUMERIC,
|
||||
KeyCode.VIEW_NUMERIC_ADVANCED,
|
||||
)) {
|
||||
fontSize = fontSize(12.sp)
|
||||
}
|
||||
FlorisImeUi.Key.elementName(FlorisImeUi.Attr.Code to listOf(KeyCode.VIEW_NUMERIC_ADVANCED)) {
|
||||
textMaxLines = textMaxLines(2)
|
||||
}
|
||||
FlorisImeUi.Key.elementName(
|
||||
FlorisImeUi.Attr.Code to listOf(KeyCode.SHIFT),
|
||||
FlorisImeUi.Attr.ShiftState to listOf(InputShiftState.CAPS_LOCK.attrName()),
|
||||
) {
|
||||
foreground = rgbaColor(255, 152, 0)
|
||||
}
|
||||
FlorisImeUi.Key(codes = listOf(KeyCode.SPACE)) {
|
||||
background = `var`("surface")
|
||||
foreground = rgbaColor(144, 144, 144)
|
||||
fontSize = size(12.sp)
|
||||
}
|
||||
FlorisImeUi.KeyHint {
|
||||
FlorisImeUi.KeyHint.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = rgbaColor(184, 184, 184)
|
||||
fontSize = size(12.sp)
|
||||
foreground = `var`("--on-surface-variant")
|
||||
fontFamily = genericFontFamily(FontFamily.Monospace)
|
||||
fontSize = fontSize(12.sp)
|
||||
padding = padding(0.dp, 1.dp, 1.dp, 0.dp)
|
||||
textMaxLines = textMaxLines(1)
|
||||
}
|
||||
FlorisImeUi.KeyPopup {
|
||||
FlorisImeUi.KeyPopupBox.elementName {
|
||||
background = rgbaColor(117, 117, 117)
|
||||
foreground = `var`("onSurface")
|
||||
fontSize = size(22.sp)
|
||||
shape = roundedCornerShape(20)
|
||||
foreground = `var`("--on-surface")
|
||||
fontSize = fontSize(22.sp)
|
||||
shape = `var`("--shape")
|
||||
shadowElevation = size(2.dp)
|
||||
}
|
||||
FlorisImeUi.KeyPopup(focusSelector = true) {
|
||||
FlorisImeUi.KeyPopupElement.elementName(selector = SnyggSelector.FOCUS) {
|
||||
background = rgbaColor(189, 189, 189)
|
||||
foreground = `var`("onSurface")
|
||||
fontSize = size(22.sp)
|
||||
shape = roundedCornerShape(20)
|
||||
shape = `var`("--shape")
|
||||
}
|
||||
FlorisImeUi.KeyPopupExtendedIndicator.elementName {
|
||||
fontSize = fontSize(16.sp)
|
||||
}
|
||||
|
||||
FlorisImeUi.ClipboardHeader {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = `var`("onSurface")
|
||||
fontSize = size(16.sp)
|
||||
FlorisImeUi.Smartbar.elementName {
|
||||
fontSize = fontSize(18.sp)
|
||||
}
|
||||
FlorisImeUi.ClipboardItem {
|
||||
background = `var`("surface")
|
||||
foreground = `var`("onSurface")
|
||||
fontSize = size(14.sp)
|
||||
shape = roundedCornerShape(12.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardItemPopup {
|
||||
background = rgbaColor(117, 117, 117)
|
||||
foreground = `var`("onSurface")
|
||||
fontSize = size(14.sp)
|
||||
shape = roundedCornerShape(12.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardEnableHistoryButton {
|
||||
background = `var`("primary")
|
||||
foreground = rgbaColor(0, 0, 0)
|
||||
shape = roundedCornerShape(12.dp)
|
||||
}
|
||||
|
||||
FlorisImeUi.EmojiKey {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = `var`("onBackground")
|
||||
fontSize = size(22.sp)
|
||||
shape = roundedCornerShape(20)
|
||||
}
|
||||
FlorisImeUi.EmojiKey(pressedSelector = true) {
|
||||
background = `var`("surface")
|
||||
foreground = `var`("onSurface")
|
||||
}
|
||||
|
||||
FlorisImeUi.GlideTrail {
|
||||
foreground = `var`("primary")
|
||||
}
|
||||
|
||||
FlorisImeUi.IncognitoModeIndicator {
|
||||
foreground = rgbaColor(255, 255, 255, 0.067f)
|
||||
}
|
||||
|
||||
FlorisImeUi.OneHandedPanel {
|
||||
background = rgbaColor(27, 94, 32)
|
||||
foreground = rgbaColor(238, 238, 238)
|
||||
}
|
||||
|
||||
FlorisImeUi.Smartbar {
|
||||
FlorisImeUi.SmartbarSharedActionsRow.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
}
|
||||
FlorisImeUi.SmartbarSharedActionsRow {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
}
|
||||
FlorisImeUi.SmartbarSharedActionsToggle {
|
||||
background = `var`("surface")
|
||||
foreground = `var`("onSurface")
|
||||
FlorisImeUi.SmartbarSharedActionsToggle.elementName {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
shape = circleShape()
|
||||
}
|
||||
FlorisImeUi.SmartbarExtendedActionsRow {
|
||||
FlorisImeUi.SmartbarExtendedActionsRow.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
}
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle {
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = rgbaColor(144, 144, 144)
|
||||
shape = circleShape()
|
||||
}
|
||||
FlorisImeUi.SmartbarActionKey {
|
||||
FlorisImeUi.SmartbarActionKey.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = rgbaColor(220, 220, 220)
|
||||
fontSize = size(18.sp)
|
||||
shape = `var`("--shape")
|
||||
clip = yes()
|
||||
}
|
||||
FlorisImeUi.SmartbarActionKey.elementName(selector = SnyggSelector.DISABLED) {
|
||||
foreground = `var`("--on-background-disabled")
|
||||
}
|
||||
FlorisImeUi.SmartbarActionTile.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = `var`("--on-background")
|
||||
margin = padding(4.dp)
|
||||
shape = roundedCornerShape(20)
|
||||
clip = yes()
|
||||
textMaxLines = textMaxLines(2)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.SmartbarActionKey(pressedSelector = true) {
|
||||
background = `var`("surface")
|
||||
foreground = rgbaColor(220, 220, 220)
|
||||
FlorisImeUi.SmartbarActionTile.elementName(selector = SnyggSelector.DISABLED) {
|
||||
foreground = `var`("--on-background-disabled")
|
||||
}
|
||||
FlorisImeUi.SmartbarActionKey(disabledSelector = true) {
|
||||
FlorisImeUi.SmartbarActionsOverflowCustomizeButton.elementName {
|
||||
background = `var`("--primary")
|
||||
foreground = `var`("--on-primary")
|
||||
shape = roundedCornerShape(24.dp)
|
||||
}
|
||||
|
||||
FlorisImeUi.SmartbarActionsEditor.elementName {
|
||||
background = `var`("--background")
|
||||
foreground = `var`("--on-background")
|
||||
shape = roundedCornerShape(24.dp, 24.dp, 0.dp, 0.dp)
|
||||
clip = yes()
|
||||
}
|
||||
FlorisImeUi.SmartbarActionsEditorHeader.elementName {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
fontSize = fontSize(16.sp)
|
||||
textMaxLines = textMaxLines(1)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.SmartbarActionsEditorSubheader.elementName {
|
||||
foreground = `var`("--secondary")
|
||||
fontSize = fontSize(16.sp)
|
||||
fontWeight = fontWeight(FontWeight.Bold)
|
||||
padding = padding(12.dp, 16.dp, 12.dp, 8.dp)
|
||||
textMaxLines = textMaxLines(1)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.SmartbarActionsEditorTileGrid.elementName {
|
||||
margin = padding(4.dp, 0.dp)
|
||||
}
|
||||
FlorisImeUi.SmartbarActionsEditorTile.elementName {
|
||||
margin = padding(4.dp)
|
||||
padding = padding(8.dp)
|
||||
textAlign = textAlign(TextAlign.Center)
|
||||
textMaxLines = textMaxLines(2)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.SmartbarActionsEditorTile.elementName(FlorisImeUi.Attr.Code to listOf(KeyCode.NOOP)) {
|
||||
foreground = `var`("--on-background-disabled")
|
||||
}
|
||||
FlorisImeUi.SmartbarActionsEditorTile.elementName(FlorisImeUi.Attr.Code to listOf(KeyCode.DRAG_MARKER)) {
|
||||
foreground = rgbaColor(255, 0, 0)
|
||||
}
|
||||
|
||||
FlorisImeUi.SmartbarCandidateWord.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = `var`("surface")
|
||||
}
|
||||
FlorisImeUi.SmartbarCandidateWord {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = rgbaColor(220, 220, 220)
|
||||
fontSize = size(14.sp)
|
||||
foreground = `var`("--on-background")
|
||||
fontSize = fontSize(14.sp)
|
||||
margin = padding(4.dp)
|
||||
padding = padding(8.dp, 0.dp)
|
||||
shape = rectangleShape()
|
||||
textMaxLines = textMaxLines(1)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.SmartbarCandidateWord(pressedSelector = true) {
|
||||
background = `var`("surface")
|
||||
foreground = rgbaColor(220, 220, 220)
|
||||
FlorisImeUi.SmartbarCandidateWord.elementName(selector = SnyggSelector.PRESSED) {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
FlorisImeUi.SmartbarCandidateClip {
|
||||
FlorisImeUi.SmartbarCandidateClip.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = rgbaColor(220, 220, 220)
|
||||
fontSize = size(14.sp)
|
||||
fontSize = fontSize(14.sp)
|
||||
margin = padding(4.dp)
|
||||
padding = padding(8.dp, 0.dp)
|
||||
shape = roundedCornerShape(8)
|
||||
textMaxLines = textMaxLines(1)
|
||||
textOverflow = textOverflow(TextOverflow.Ellipsis)
|
||||
}
|
||||
FlorisImeUi.SmartbarCandidateClip(pressedSelector = true) {
|
||||
background = `var`("surface")
|
||||
foreground = rgbaColor(220, 220, 220)
|
||||
FlorisImeUi.SmartbarCandidateClip.elementName(selector = SnyggSelector.PRESSED) {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
FlorisImeUi.SmartbarCandidateSpacer {
|
||||
FlorisImeUi.SmartbarCandidateSpacer.elementName {
|
||||
foreground = rgbaColor(255, 255, 255, 0.25f)
|
||||
}
|
||||
|
||||
FlorisImeUi.SystemNavBar {
|
||||
background = `var`("background")
|
||||
FlorisImeUi.ClipboardHeader.elementName {
|
||||
foreground = `var`("--on-background")
|
||||
fontSize = fontSize(16.sp)
|
||||
}
|
||||
FlorisImeUi.ClipboardSubheader.elementName {
|
||||
fontSize = fontSize(14.sp)
|
||||
margin = padding(6.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardContent.elementName {
|
||||
padding = padding(10.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardItem.elementName {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
fontSize = fontSize(14.sp)
|
||||
margin = padding(4.dp)
|
||||
padding = padding(12.dp, 8.dp)
|
||||
shape = `var`("--shape-variant")
|
||||
clip = yes()
|
||||
shadowElevation = size(2.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardItemPopup.elementName {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
margin = padding(4.dp)
|
||||
shape = `var`("--shape-variant")
|
||||
clip = yes()
|
||||
shadowElevation = size(2.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardItemPopupAction.elementName {
|
||||
fontSize = fontSize(16.sp)
|
||||
padding = padding(12.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardItemPopupActionText.elementName {
|
||||
margin = padding(8.dp, 0.dp, 0.dp, 0.dp)
|
||||
}
|
||||
FlorisImeUi.ClipboardHistoryDisabledButton.elementName {
|
||||
background = `var`("--primary")
|
||||
foreground = `var`("--on-primary")
|
||||
shape = roundedCornerShape(24.dp)
|
||||
}
|
||||
|
||||
FlorisImeUi.MediaEmojiKey.elementName {
|
||||
background = rgbaColor(0, 0, 0, 0f)
|
||||
foreground = `var`("--on-background")
|
||||
fontSize = fontSize(22.sp)
|
||||
shape = `var`("--shape")
|
||||
}
|
||||
FlorisImeUi.MediaEmojiKey.elementName(selector = SnyggSelector.PRESSED) {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
|
||||
FlorisImeUi.GlideTrail.elementName {
|
||||
foreground = `var`("--primary")
|
||||
}
|
||||
|
||||
FlorisImeUi.InlineAutofillChip.elementName {
|
||||
background = `var`("--surface")
|
||||
foreground = `var`("--on-surface")
|
||||
}
|
||||
|
||||
FlorisImeUi.IncognitoModeIndicator.elementName {
|
||||
foreground = rgbaColor(255, 255, 255, 0.067f)
|
||||
}
|
||||
|
||||
FlorisImeUi.OneHandedPanel.elementName {
|
||||
background = rgbaColor(27, 94, 32)
|
||||
foreground = rgbaColor(238, 238, 238)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,47 +16,307 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
object FlorisImeUi {
|
||||
const val Keyboard = "keyboard"
|
||||
const val Key = "key"
|
||||
const val KeyHint = "key-hint"
|
||||
const val KeyPopup = "key-popup"
|
||||
import dev.patrickgold.florisboard.R
|
||||
|
||||
const val ClipboardHeader = "clipboard-header"
|
||||
const val ClipboardItem = "clipboard-item"
|
||||
const val ClipboardItemPopup = "clipboard-item-popup"
|
||||
const val ClipboardEnableHistoryButton = "clipboard-enable-history-button"
|
||||
enum class FlorisImeUi(val elementName: String, val resId: Int?) {
|
||||
Root(
|
||||
elementName = "root",
|
||||
resId = R.string.snygg__rule_element__root,
|
||||
),
|
||||
Window(
|
||||
elementName = "window",
|
||||
resId = R.string.snygg__rule_element__window,
|
||||
),
|
||||
|
||||
const val EmojiKey = "emoji-key"
|
||||
const val EmojiKeyPopup = "emoji-key-popup"
|
||||
const val EmojiTab = "emoji-tab"
|
||||
Key(
|
||||
elementName = "key",
|
||||
resId = R.string.snygg__rule_element__key,
|
||||
),
|
||||
KeyHint(
|
||||
elementName = "key-hint",
|
||||
resId = R.string.snygg__rule_element__key_hint,
|
||||
),
|
||||
KeyPopupBox(
|
||||
elementName = "key-popup-box",
|
||||
resId = R.string.snygg__rule_element__key_popup_box,
|
||||
),
|
||||
KeyPopupElement(
|
||||
elementName = "key-popup-element",
|
||||
resId = R.string.snygg__rule_element__key_popup_element,
|
||||
),
|
||||
KeyPopupExtendedIndicator(
|
||||
elementName = "key-popup-extended-indicator",
|
||||
resId = R.string.snygg__rule_element__key_popup_extended_indicator,
|
||||
),
|
||||
|
||||
const val ExtractedLandscapeInputLayout = "extracted-landscape-input-layout"
|
||||
const val ExtractedLandscapeInputField = "extracted-landscape-input-field"
|
||||
const val ExtractedLandscapeInputAction = "extracted-landscape-input-action"
|
||||
ClipboardHeader(
|
||||
elementName = "clipboard-header",
|
||||
resId = R.string.snygg__rule_element__clipboard_header,
|
||||
),
|
||||
ClipboardHeaderButton(
|
||||
elementName = "clipboard-header-button",
|
||||
resId = R.string.snygg__rule_element__clipboard_header_button,
|
||||
),
|
||||
ClipboardHeaderText(
|
||||
elementName = "clipboard-header-text",
|
||||
resId = R.string.snygg__rule_element__clipboard_header_text,
|
||||
),
|
||||
ClipboardSubheader(
|
||||
elementName = "clipboard-subheader",
|
||||
resId = R.string.snygg__rule_element__clipboard_subheader,
|
||||
),
|
||||
ClipboardContent(
|
||||
elementName = "clipboard-content",
|
||||
resId = R.string.snygg__rule_element__clipboard_content,
|
||||
),
|
||||
ClipboardItem(
|
||||
elementName = "clipboard-item",
|
||||
resId = R.string.snygg__rule_element__clipboard_item,
|
||||
),
|
||||
ClipboardItemPopup(
|
||||
elementName = "clipboard-item-popup",
|
||||
resId = R.string.snygg__rule_element__clipboard_item_popup,
|
||||
),
|
||||
ClipboardItemPopupAction(
|
||||
elementName = "clipboard-item-popup-action",
|
||||
resId = R.string.snygg__rule_element__clipboard_item_popup_action,
|
||||
),
|
||||
ClipboardItemPopupActionIcon(
|
||||
elementName = "clipboard-item-popup-action-icon",
|
||||
resId = R.string.snygg__rule_element__clipboard_item_popup_action_icon,
|
||||
),
|
||||
ClipboardItemPopupActionText(
|
||||
elementName = "clipboard-item-popup-action-text",
|
||||
resId = R.string.snygg__rule_element__clipboard_item_popup_action_text,
|
||||
),
|
||||
ClipboardClearAllDialog(
|
||||
elementName = "clipboard-clear-all-dialog",
|
||||
resId = R.string.snygg__rule_element__clipboard_clear_all_dialog,
|
||||
),
|
||||
ClipboardClearAllDialogMessage(
|
||||
elementName = "clipboard-clear-all-dialog-message",
|
||||
resId = R.string.snygg__rule_element__clipboard_clear_all_dialog_message,
|
||||
),
|
||||
ClipboardClearAllDialogButtons(
|
||||
elementName = "clipboard-clear-all-dialog-buttons",
|
||||
resId = R.string.snygg__rule_element__clipboard_clear_all_dialog_buttons,
|
||||
),
|
||||
ClipboardClearAllDialogButton(
|
||||
elementName = "clipboard-clear-all-dialog-button",
|
||||
resId = R.string.snygg__rule_element__clipboard_clear_all_dialog_button,
|
||||
),
|
||||
ClipboardHistoryDisabledTitle(
|
||||
elementName = "clipboard-history-disabled-title",
|
||||
resId = R.string.snygg__rule_element__clipboard_history_disabled_title,
|
||||
),
|
||||
ClipboardHistoryDisabledMessage(
|
||||
elementName = "clipboard-history-disabled-message",
|
||||
resId = R.string.snygg__rule_element__clipboard_history_disabled_message,
|
||||
),
|
||||
ClipboardHistoryDisabledButton(
|
||||
elementName = "clipboard-history-disabled-button",
|
||||
resId = R.string.snygg__rule_element__clipboard_history_disabled_button,
|
||||
),
|
||||
ClipboardHistoryLockedTitle(
|
||||
elementName = "clipboard-history-locked-title",
|
||||
resId = R.string.snygg__rule_element__clipboard_history_locked_title,
|
||||
),
|
||||
ClipboardHistoryLockedMessage(
|
||||
elementName = "clipboard-history-locked-message",
|
||||
resId = R.string.snygg__rule_element__clipboard_history_locked_message,
|
||||
),
|
||||
|
||||
const val GlideTrail = "glide-trail"
|
||||
ExtractedLandscapeInputLayout(
|
||||
elementName = "extracted-landscape-input-layout",
|
||||
resId = R.string.snygg__rule_element__extracted_landscape_input_layout,
|
||||
),
|
||||
ExtractedLandscapeInputField(
|
||||
elementName = "extracted-landscape-input-field",
|
||||
resId = R.string.snygg__rule_element__extracted_landscape_input_field,
|
||||
),
|
||||
ExtractedLandscapeInputAction(
|
||||
elementName = "extracted-landscape-input-action",
|
||||
resId = R.string.snygg__rule_element__extracted_landscape_input_action,
|
||||
),
|
||||
|
||||
const val IncognitoModeIndicator = "incognito-mode-indicator"
|
||||
GlideTrail(
|
||||
elementName = "glide-trail",
|
||||
resId = R.string.snygg__rule_element__glide_trail,
|
||||
),
|
||||
|
||||
const val OneHandedPanel = "one-handed-panel"
|
||||
IncognitoModeIndicator(
|
||||
elementName = "incognito-mode-indicator",
|
||||
resId = R.string.snygg__rule_element__incognito_mode_indicator,
|
||||
),
|
||||
|
||||
const val Smartbar = "smartbar"
|
||||
const val SmartbarSharedActionsRow = "smartbar-shared-actions-row"
|
||||
const val SmartbarSharedActionsToggle = "smartbar-shared-actions-toggle"
|
||||
const val SmartbarExtendedActionsRow = "smartbar-extended-actions-row"
|
||||
const val SmartbarExtendedActionsToggle = "smartbar-extended-actions-toggle"
|
||||
const val SmartbarActionKey = "smartbar-action-key"
|
||||
const val SmartbarActionTile = "smartbar-action-tile"
|
||||
const val SmartbarActionsOverflow = "smartbar-actions-overflow"
|
||||
const val SmartbarActionsOverflowCustomizeButton = "smartbar-actions-overflow-customize-button"
|
||||
const val SmartbarActionsEditor = "smartbar-actions-editor"
|
||||
const val SmartbarActionsEditorHeader = "smartbar-actions-editor-header"
|
||||
const val SmartbarActionsEditorSubheader = "smartbar-actions-editor-subheader"
|
||||
const val SmartbarCandidatesRow = "smartbar-candidates-row"
|
||||
const val SmartbarCandidateWord = "smartbar-candidate-word"
|
||||
const val SmartbarCandidateClip = "smartbar-candidate-clip"
|
||||
const val SmartbarCandidateSpacer = "smartbar-candidate-spacer"
|
||||
InlineAutofillChip(
|
||||
elementName = "inline-autofill-chip",
|
||||
resId = R.string.snygg__rule_element__inline_autofill_chip,
|
||||
),
|
||||
|
||||
const val SystemNavBar = "system-nav-bar"
|
||||
Media(
|
||||
elementName = "media",
|
||||
resId = R.string.snygg__rule_element__media,
|
||||
),
|
||||
|
||||
MediaEmojiSubheader(
|
||||
elementName = "media-emoji-subheader",
|
||||
resId = R.string.snygg__rule_element__media_emoji_subheader,
|
||||
),
|
||||
MediaEmojiKey(
|
||||
elementName = "media-emoji-key",
|
||||
resId = R.string.snygg__rule_element__media_emoji_key,
|
||||
),
|
||||
MediaEmojiKeyPopupBox(
|
||||
elementName = "media-emoji-key-popup-box",
|
||||
resId = R.string.snygg__rule_element__media_emoji_key_popup_box,
|
||||
),
|
||||
MediaEmojiKeyPopupElement(
|
||||
elementName = "media-emoji-key-popup-element",
|
||||
resId = R.string.snygg__rule_element__media_emoji_key_popup_element,
|
||||
),
|
||||
MediaEmojiKeyPopupExtendedIndicator(
|
||||
elementName = "media-emoji-key-popup-extended-indicator",
|
||||
resId = R.string.snygg__rule_element__media_emoji_key_popup_extended_indicator,
|
||||
),
|
||||
MediaEmojiTab(
|
||||
elementName = "media-emoji-tab",
|
||||
resId = R.string.snygg__rule_element__media_emoji_tab,
|
||||
),
|
||||
|
||||
MediaBottomRow(
|
||||
elementName = "media-bottom-row",
|
||||
resId = R.string.snygg__rule_element__media_bottom_row,
|
||||
),
|
||||
MediaBottomRowButton(
|
||||
elementName = "media-bottom-row-button",
|
||||
resId = R.string.snygg__rule_element__media_bottom_row_button,
|
||||
),
|
||||
|
||||
OneHandedPanel(
|
||||
elementName = "one-handed-panel",
|
||||
resId = R.string.snygg__rule_element__one_handed_panel,
|
||||
),
|
||||
OneHandedPanelButton(
|
||||
elementName = "one-handed-panel-button",
|
||||
resId = R.string.snygg__rule_element__one_handed_panel_button,
|
||||
),
|
||||
|
||||
Smartbar(
|
||||
elementName = "smartbar",
|
||||
resId = R.string.snygg__rule_element__smartbar,
|
||||
),
|
||||
SmartbarSharedActionsRow(
|
||||
elementName = "smartbar-shared-actions-row",
|
||||
resId = R.string.snygg__rule_element__smartbar_shared_actions_row,
|
||||
),
|
||||
SmartbarSharedActionsToggle(
|
||||
elementName = "smartbar-shared-actions-toggle",
|
||||
resId = R.string.snygg__rule_element__smartbar_shared_actions_toggle,
|
||||
),
|
||||
SmartbarExtendedActionsRow(
|
||||
elementName = "smartbar-extended-actions-row",
|
||||
resId = R.string.snygg__rule_element__smartbar_extended_actions_row,
|
||||
),
|
||||
SmartbarExtendedActionsToggle(
|
||||
elementName = "smartbar-extended-actions-toggle",
|
||||
resId = R.string.snygg__rule_element__smartbar_extended_actions_toggle,
|
||||
),
|
||||
SmartbarActionKey(
|
||||
elementName = "smartbar-action-key",
|
||||
resId = R.string.snygg__rule_element__smartbar_action_key,
|
||||
),
|
||||
SmartbarActionTile(
|
||||
elementName = "smartbar-action-tile",
|
||||
resId = R.string.snygg__rule_element__smartbar_action_tile,
|
||||
),
|
||||
SmartbarActionsOverflow(
|
||||
elementName = "smartbar-actions-overflow",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_overflow,
|
||||
),
|
||||
SmartbarActionsOverflowCustomizeButton(
|
||||
elementName = "smartbar-actions-overflow-customize-button",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_overflow_customize_button,
|
||||
),
|
||||
|
||||
SmartbarActionsEditor(
|
||||
elementName = "smartbar-actions-editor",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_editor,
|
||||
),
|
||||
SmartbarActionsEditorHeader(
|
||||
elementName = "smartbar-actions-editor-header",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_editor_header,
|
||||
),
|
||||
SmartbarActionsEditorHeaderButton(
|
||||
elementName = "smartbar-actions-editor-header-button",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_editor_header_button,
|
||||
),
|
||||
SmartbarActionsEditorSubheader(
|
||||
elementName = "smartbar-actions-editor-subheader",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_editor_subheader,
|
||||
),
|
||||
SmartbarActionsEditorTileGrid(
|
||||
elementName = "smartbar-actions-editor-tile-grid",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_editor_tile_grid,
|
||||
),
|
||||
SmartbarActionsEditorTile(
|
||||
elementName = "smartbar-actions-editor-tile",
|
||||
resId = R.string.snygg__rule_element__smartbar_actions_editor_tile,
|
||||
),
|
||||
|
||||
SmartbarCandidatesRow(
|
||||
elementName = "smartbar-candidates-row",
|
||||
resId = R.string.snygg__rule_element__smartbar_candidates_row,
|
||||
),
|
||||
SmartbarCandidateWord(
|
||||
elementName = "smartbar-candidate-word",
|
||||
resId = R.string.snygg__rule_element__smartbar_candidate_word,
|
||||
),
|
||||
SmartbarCandidateClip(
|
||||
elementName = "smartbar-candidate-clip",
|
||||
resId = R.string.snygg__rule_element__smartbar_candidate_clip,
|
||||
),
|
||||
SmartbarCandidateSpacer(
|
||||
elementName = "smartbar-candidate-spacer",
|
||||
resId = R.string.snygg__rule_element__smartbar_candidate_spacer,
|
||||
),
|
||||
|
||||
SubtypePanel(
|
||||
elementName = "subtype-panel",
|
||||
resId = R.string.snygg__rule_element__subtype_panel,
|
||||
),
|
||||
SubtypePanelHeader(
|
||||
elementName = "subtype-panel-header",
|
||||
resId = R.string.snygg__rule_element__subtype_panel_header,
|
||||
);
|
||||
|
||||
companion object {
|
||||
val elementNames by lazy { entries.map { it.elementName } }
|
||||
|
||||
val elementNamesToOrdinals by lazy {
|
||||
val enumEntries = entries
|
||||
buildMap {
|
||||
enumEntries.forEach { entry ->
|
||||
put(entry.elementName, entry.ordinal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val elementNamesToTranslation by lazy {
|
||||
val enumEntries = entries
|
||||
buildMap {
|
||||
put("defines", R.string.snygg__rule_annotation__defines)
|
||||
put("font", R.string.snygg__rule_annotation__font)
|
||||
enumEntries.forEach { entry ->
|
||||
put(entry.elementName, entry.resId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Attr {
|
||||
const val Code = "code"
|
||||
const val Mode = "mode"
|
||||
const val ShiftState = "shiftstate"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,41 +16,42 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
/*
|
||||
import org.florisboard.lib.snygg.Snygg
|
||||
import org.florisboard.lib.snygg.SnyggLevel
|
||||
import org.florisboard.lib.snygg.SnyggPropertySetSpecBuilder
|
||||
import org.florisboard.lib.snygg.SnyggSpec
|
||||
import org.florisboard.lib.snygg.SnyggPropertySetSpecDeclBuilder
|
||||
import org.florisboard.lib.snygg.SnyggSpecDecl
|
||||
import org.florisboard.lib.snygg.value.SnyggCircleShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggCutCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggCutCornerPercentShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDpSizeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggMaterialYouDarkColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggMaterialYouLightColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDynamicColorDarkColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggDynamicColorLightColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRectangleShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRoundedCornerDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggRoundedCornerPercentShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSolidColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggStaticColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSpSizeValue
|
||||
|
||||
fun SnyggPropertySetSpecBuilder.background() {
|
||||
fun SnyggPropertySetSpecDeclBuilder.background() {
|
||||
property(
|
||||
name = Snygg.Background,
|
||||
level = SnyggLevel.BASIC,
|
||||
supportedValues(SnyggSolidColorValue, SnyggMaterialYouLightColorValue, SnyggMaterialYouDarkColorValue),
|
||||
supportedValues(SnyggStaticColorValue, SnyggDynamicColorLightColorValue, SnyggDynamicColorDarkColorValue),
|
||||
)
|
||||
}
|
||||
fun SnyggPropertySetSpecBuilder.foreground() {
|
||||
fun SnyggPropertySetSpecDeclBuilder.foreground() {
|
||||
property(
|
||||
name = Snygg.Foreground,
|
||||
level = SnyggLevel.BASIC,
|
||||
supportedValues(SnyggSolidColorValue, SnyggMaterialYouLightColorValue, SnyggMaterialYouDarkColorValue),
|
||||
supportedValues(SnyggStaticColorValue, SnyggDynamicColorLightColorValue, SnyggDynamicColorDarkColorValue),
|
||||
)
|
||||
}
|
||||
fun SnyggPropertySetSpecBuilder.border() {
|
||||
fun SnyggPropertySetSpecDeclBuilder.border() {
|
||||
property(
|
||||
name = Snygg.BorderColor,
|
||||
level = SnyggLevel.ADVANCED,
|
||||
supportedValues(SnyggSolidColorValue, SnyggMaterialYouLightColorValue, SnyggMaterialYouDarkColorValue),
|
||||
supportedValues(SnyggStaticColorValue, SnyggDynamicColorLightColorValue, SnyggDynamicColorDarkColorValue),
|
||||
)
|
||||
property(
|
||||
name = Snygg.BorderWidth,
|
||||
@@ -58,21 +59,21 @@ fun SnyggPropertySetSpecBuilder.border() {
|
||||
supportedValues(SnyggDpSizeValue),
|
||||
)
|
||||
}
|
||||
fun SnyggPropertySetSpecBuilder.font() {
|
||||
fun SnyggPropertySetSpecDeclBuilder.font() {
|
||||
property(
|
||||
name = Snygg.FontSize,
|
||||
level = SnyggLevel.ADVANCED,
|
||||
supportedValues(SnyggSpSizeValue),
|
||||
)
|
||||
}
|
||||
fun SnyggPropertySetSpecBuilder.shadow() {
|
||||
fun SnyggPropertySetSpecDeclBuilder.shadow() {
|
||||
property(
|
||||
name = Snygg.ShadowElevation,
|
||||
level = SnyggLevel.ADVANCED,
|
||||
supportedValues(SnyggDpSizeValue),
|
||||
)
|
||||
}
|
||||
fun SnyggPropertySetSpecBuilder.shape() {
|
||||
fun SnyggPropertySetSpecDeclBuilder.shape() {
|
||||
property(
|
||||
name = Snygg.Shape,
|
||||
level = SnyggLevel.ADVANCED,
|
||||
@@ -87,7 +88,7 @@ fun SnyggPropertySetSpecBuilder.shape() {
|
||||
)
|
||||
}
|
||||
|
||||
object FlorisImeUiSpec : SnyggSpec({
|
||||
object FlorisImeUiSpec : SnyggSpecDecl({
|
||||
element(FlorisImeUi.Keyboard) {
|
||||
background()
|
||||
}
|
||||
@@ -276,3 +277,40 @@ object FlorisImeUiSpec : SnyggSpec({
|
||||
background()
|
||||
}
|
||||
})
|
||||
|
||||
Snygg.init(
|
||||
stylesheetSpec = FlorisImeUiSpec,
|
||||
rulePreferredElementSorting = listOf(
|
||||
FlorisImeUi.Keyboard,
|
||||
FlorisImeUi.Key,
|
||||
FlorisImeUi.KeyHint,
|
||||
FlorisImeUi.KeyPopup,
|
||||
FlorisImeUi.Smartbar,
|
||||
FlorisImeUi.SmartbarSharedActionsRow,
|
||||
FlorisImeUi.SmartbarSharedActionsToggle,
|
||||
FlorisImeUi.SmartbarExtendedActionsRow,
|
||||
FlorisImeUi.SmartbarExtendedActionsToggle,
|
||||
FlorisImeUi.SmartbarActionKey,
|
||||
FlorisImeUi.SmartbarActionTile,
|
||||
FlorisImeUi.SmartbarActionsOverflow,
|
||||
FlorisImeUi.SmartbarActionsOverflowCustomizeButton,
|
||||
FlorisImeUi.SmartbarActionsEditor,
|
||||
FlorisImeUi.SmartbarActionsEditorHeader,
|
||||
FlorisImeUi.SmartbarActionsEditorSubheader,
|
||||
FlorisImeUi.SmartbarCandidatesRow,
|
||||
FlorisImeUi.SmartbarCandidateWord,
|
||||
FlorisImeUi.SmartbarCandidateClip,
|
||||
FlorisImeUi.SmartbarCandidateSpacer,
|
||||
),
|
||||
rulePlaceholders = mapOf(
|
||||
"c:delete" to KeyCode.DELETE,
|
||||
"c:enter" to KeyCode.ENTER,
|
||||
"c:shift" to KeyCode.SHIFT,
|
||||
"c:space" to KeyCode.SPACE,
|
||||
"sh:unshifted" to InputShiftState.UNSHIFTED.value,
|
||||
"sh:shifted_manual" to InputShiftState.SHIFTED_MANUAL.value,
|
||||
"sh:shifted_automatic" to InputShiftState.SHIFTED_AUTOMATIC.value,
|
||||
"sh:caps_lock" to InputShiftState.CAPS_LOCK.value,
|
||||
),
|
||||
)
|
||||
*/
|
||||
|
||||
@@ -28,6 +28,12 @@ inline fun extCoreTheme(id: String) = ExtensionComponentName(
|
||||
componentId = id,
|
||||
)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun extPreviewTheme(id: String) = ExtensionComponentName(
|
||||
extensionId = "local.themes.preview",
|
||||
componentId = id,
|
||||
)
|
||||
|
||||
interface ThemeExtensionComponent : ExtensionComponent {
|
||||
companion object {
|
||||
fun defaultStylesheetPath(id: String): String {
|
||||
@@ -39,8 +45,6 @@ interface ThemeExtensionComponent : ExtensionComponent {
|
||||
override val label: String
|
||||
override val authors: List<String>
|
||||
val isNightTheme: Boolean
|
||||
val isBorderless: Boolean
|
||||
val isMaterialYouAware: Boolean
|
||||
val stylesheetPath: String?
|
||||
|
||||
fun stylesheetPath(): String = stylesheetPath.takeUnless { it.isNullOrBlank() } ?: defaultStylesheetPath(id)
|
||||
@@ -53,14 +57,12 @@ data class ThemeExtensionComponentImpl(
|
||||
override val authors: List<String>,
|
||||
@SerialName("isNight")
|
||||
override val isNightTheme: Boolean = true,
|
||||
override val isBorderless: Boolean = false,
|
||||
override val isMaterialYouAware: Boolean = false,
|
||||
@SerialName("stylesheet")
|
||||
override val stylesheetPath: String? = null,
|
||||
) : ThemeExtensionComponent {
|
||||
|
||||
fun edit() = ThemeExtensionComponentEditor(
|
||||
id, label, authors, isNightTheme, isBorderless, isMaterialYouAware, stylesheetPath ?: "",
|
||||
id, label, authors, isNightTheme, stylesheetPath ?: "",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,8 +71,6 @@ class ThemeExtensionComponentEditor(
|
||||
override var label: String = "",
|
||||
override var authors: List<String> = emptyList(),
|
||||
override var isNightTheme: Boolean = true,
|
||||
override var isBorderless: Boolean = false,
|
||||
override var isMaterialYouAware: Boolean = false,
|
||||
override var stylesheetPath: String = "",
|
||||
) : ThemeExtensionComponent {
|
||||
|
||||
@@ -83,8 +83,6 @@ class ThemeExtensionComponentEditor(
|
||||
label = label.trim(),
|
||||
authors = authors.filterNot { it.isBlank() },
|
||||
isNightTheme = isNightTheme,
|
||||
isBorderless = isBorderless,
|
||||
isMaterialYouAware = isMaterialYouAware,
|
||||
stylesheetPath = stylesheetPath.takeUnless { it.isBlank() },
|
||||
)
|
||||
check(id.isNotBlank()) { "Theme component ID cannot be blank" }
|
||||
|
||||
@@ -45,7 +45,10 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.appContext
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.smartbar.CachedInlineSuggestionsChipStyleSet
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogInfo
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
|
||||
import dev.patrickgold.florisboard.lib.io.ZipUtils
|
||||
import dev.patrickgold.florisboard.lib.util.ViewUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -54,11 +57,13 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.florisboard.lib.kotlin.io.FsDir
|
||||
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import org.florisboard.lib.snygg.SnyggStylesheet
|
||||
import org.florisboard.lib.snygg.SnyggStylesheetJsonConfig
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.value.MaterialYouColor
|
||||
import org.florisboard.lib.snygg.value.SnyggSolidColorValue
|
||||
import org.florisboard.lib.snygg.value.SnyggStaticColorValue
|
||||
import java.util.UUID
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
@@ -119,7 +124,6 @@ class ThemeManager(context: Context) {
|
||||
*/
|
||||
fun updateActiveTheme(action: () -> Unit = { }) = scope.launch {
|
||||
activeThemeGuard.withLock {
|
||||
MaterialYouColor.resetColorSchemeCache()
|
||||
action()
|
||||
previewThemeInfo?.let { previewThemeInfo ->
|
||||
_activeThemeInfo.postValue(previewThemeInfo)
|
||||
@@ -129,25 +133,40 @@ class ThemeManager(context: Context) {
|
||||
val cachedInfo = cachedThemeInfos.find { it.name == activeName }
|
||||
if (cachedInfo != null) {
|
||||
_activeThemeInfo.postValue(cachedInfo)
|
||||
} else {
|
||||
val themeExt = extensionManager.getExtensionById(activeName.extensionId) as? ThemeExtension
|
||||
val themeExtRef = themeExt?.sourceRef
|
||||
if (themeExtRef != null) {
|
||||
val themeConfig = themeExt.themes.find { it.id == activeName.componentId }
|
||||
if (themeConfig != null) {
|
||||
val newStylesheet = ZipUtils.readFileFromArchive(
|
||||
appContext, themeExtRef, themeConfig.stylesheetPath(),
|
||||
).getOrNull()?.let { raw -> SnyggStylesheetJsonConfig.decodeFromString<SnyggStylesheet>(raw) }
|
||||
if (newStylesheet != null) {
|
||||
val newCompiledStylesheet = (newStylesheet)
|
||||
.compileToFullyQualified(FlorisImeUiSpec)
|
||||
val newInfo = ThemeInfo(activeName, themeConfig, newCompiledStylesheet)
|
||||
cachedThemeInfos.add(newInfo)
|
||||
_activeThemeInfo.postValue(newInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
return@withLock
|
||||
}
|
||||
val themeExt = extensionManager.getExtensionById(activeName.extensionId) as? ThemeExtension
|
||||
val themeExtRef = themeExt?.sourceRef
|
||||
if (themeExtRef == null) {
|
||||
return@withLock
|
||||
}
|
||||
val themeConfig = themeExt.themes.find { it.id == activeName.componentId }
|
||||
if (themeConfig == null) {
|
||||
return@withLock
|
||||
}
|
||||
// TODO: loaded dir is implemented already...
|
||||
// TODO: this leaks the loaded dir, but at least the state is not kaputt from compose viewpoint
|
||||
val loadedDir = appContext.cacheDir.subDir("loaded").subDir(UUID.randomUUID().toString())
|
||||
runCatching {
|
||||
loadedDir.mkdirs()
|
||||
loadedDir.deleteContentsRecursively()
|
||||
ZipUtils.unzip(appContext, themeExtRef, loadedDir).getOrThrow()
|
||||
flogInfo { "Loaded extension ${themeExt.meta.id} into $loadedDir" }
|
||||
val stylesheetFile = loadedDir.subFile(themeConfig.stylesheetPath())
|
||||
val stylesheetJson = stylesheetFile.readText()
|
||||
SnyggStylesheet.fromJson(stylesheetJson).getOrThrow()
|
||||
}.fold(
|
||||
onSuccess = { newStylesheet ->
|
||||
val newInfo = ThemeInfo(activeName, themeConfig, newStylesheet, loadedDir, null)
|
||||
cachedThemeInfos.add(newInfo)
|
||||
_activeThemeInfo.postValue(newInfo)
|
||||
},
|
||||
onFailure = { cause ->
|
||||
_activeThemeInfo.postValue(ThemeInfo.DEFAULT.copy(
|
||||
loadFailure = LoadFailure(themeExt.meta, themeConfig, cause)
|
||||
))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,20 +207,16 @@ class ThemeManager(context: Context) {
|
||||
* Creates a new inline suggestion UI bundle based on the attributes of the given [style].
|
||||
*
|
||||
* @param context The context of the parent view/controller.
|
||||
* @param style The theme from which the color attributes should be fetched. Defaults to
|
||||
* [FlorisImeThemeBaseStyle].
|
||||
* @param style The style set which is responsible for styling the chips.
|
||||
*
|
||||
* @return A bundle containing all necessary attributes for the inline suggestion views to properly display.
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun createInlineSuggestionUiStyleBundle(
|
||||
context: Context,
|
||||
style: SnyggStylesheet = activeThemeInfo.value?.stylesheet ?: FlorisImeThemeBaseStyle,
|
||||
): Bundle {
|
||||
val snyggStyle = style.getStatic(FlorisImeUi.SmartbarSharedActionsToggle)
|
||||
val bgColor = snyggStyle.background.solidColor(context)
|
||||
val fgColor = snyggStyle.foreground.solidColor(context)
|
||||
fun createInlineSuggestionUiStyleBundle(context: Context): Bundle? {
|
||||
val styleSet = CachedInlineSuggestionsChipStyleSet ?: return null
|
||||
val bgColor = styleSet.background(default = Color.White)
|
||||
val fgColor = styleSet.foreground(default = Color.Black)
|
||||
|
||||
val bgDrawableId = androidx.autofill.R.drawable.autofill_inline_suggestion_chip_background
|
||||
val bgDrawable = Icon.createWithResource(context, bgDrawableId).apply {
|
||||
@@ -276,21 +291,35 @@ class ThemeManager(context: Context) {
|
||||
val name: ExtensionComponentName,
|
||||
val config: ThemeExtensionComponent,
|
||||
val stylesheet: SnyggStylesheet,
|
||||
val loadedDir: FsDir?,
|
||||
val loadFailure: LoadFailure?,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "ThemeInfo(name=$name, config=$config, loadedDir=$loadedDir)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEFAULT = ThemeInfo(
|
||||
name = extCoreTheme("base"),
|
||||
config = ThemeExtensionComponentImpl(id = "base", label = "Base", authors = listOf()),
|
||||
stylesheet = FlorisImeThemeBaseStyle.compileToFullyQualified(FlorisImeUiSpec),
|
||||
stylesheet = FlorisImeThemeBaseStyle,
|
||||
loadedDir = null,
|
||||
loadFailure = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class LoadFailure(
|
||||
val extension: ExtensionMeta,
|
||||
val component: ThemeExtensionComponent,
|
||||
val cause: Throwable,
|
||||
)
|
||||
|
||||
data class RemoteColors(
|
||||
val packageName: String,
|
||||
val colorPrimary: SnyggSolidColorValue?,
|
||||
val colorPrimaryVariant: SnyggSolidColorValue?,
|
||||
val colorSecondary: SnyggSolidColorValue?,
|
||||
val colorPrimary: SnyggStaticColorValue?,
|
||||
val colorPrimaryVariant: SnyggStaticColorValue?,
|
||||
val colorSecondary: SnyggStaticColorValue?,
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT = RemoteColors("undefined", null, null, null)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogDebug
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
|
||||
class WallpaperChangeReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent == null) return
|
||||
if (context == null) return
|
||||
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
|
||||
if (intent.action == Intent.ACTION_WALLPAPER_CHANGED) {
|
||||
flogDebug { "Wallpaper changed" }
|
||||
context.themeManager().value.updateActiveTheme()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ class FlorisLocale private constructor(val base: Locale) {
|
||||
*/
|
||||
val supportsCapitalization: Boolean
|
||||
get() = when (language) {
|
||||
"zh", "ko", "th", "bn" -> false
|
||||
"zh", "ko", "th", "bn", "hi" -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.florisboard.lib.kotlin.io.readJson
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
class CacheManager(context: Context) {
|
||||
@@ -62,6 +63,8 @@ class CacheManager(context: Context) {
|
||||
private const val ExporterDirName = "exporter"
|
||||
private const val EditorDirName = "editor"
|
||||
private const val BackupAndRestoreDirName = "backup-and-restore"
|
||||
|
||||
const val LoadedDirName = "loaded"
|
||||
}
|
||||
|
||||
private val appContext by context.appContext()
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.lib.compose
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceSerializer
|
||||
|
||||
object ColorPreferenceSerializer : PreferenceSerializer<Color> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun deserialize(value: String): Color {
|
||||
return Color(value.hexToULong())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun serialize(value: Color): String = value.value.toHexString()
|
||||
}
|
||||
@@ -16,10 +16,7 @@
|
||||
|
||||
package dev.patrickgold.florisboard.lib.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
@@ -34,14 +31,11 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
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.Shape
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun FlorisButton(
|
||||
@@ -166,38 +160,3 @@ fun FlorisIconButton(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FlorisIconButtonWithInnerPadding(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
enabled: Boolean = true,
|
||||
iconModifier: Modifier = Modifier,
|
||||
iconColor: Color = Color.Unspecified,
|
||||
) {
|
||||
IconButton(
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
) {
|
||||
val contentAlpha = if (enabled) 1f else 0.14f
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides iconColor,
|
||||
) {
|
||||
Box(
|
||||
modifier = iconModifier
|
||||
.padding(4.dp)
|
||||
.fillMaxHeight()
|
||||
.aspectRatio(1f),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = modifier.alpha(contentAlpha),
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
||||
//TODO: Use JetPrefDropDownMenu instead
|
||||
@Composable
|
||||
fun <T : Any> FlorisDropdownMenu(
|
||||
items: List<T>,
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.lib.compose
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.lib.ValidationResult
|
||||
|
||||
@Composable
|
||||
fun FlorisOutlinedTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
textStyle: TextStyle = TextStyle.Default,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
singleLine: Boolean = false,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
placeholder: String? = null,
|
||||
isError: Boolean = false,
|
||||
showValidationHint: Boolean = true,
|
||||
showValidationError: Boolean = false,
|
||||
validationResult: ValidationResult? = null,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
colors: TextFieldColors = OutlinedTextFieldDefaults.colors(),
|
||||
) {
|
||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) }
|
||||
val textFieldValue = textFieldValueState.copy(text = value)
|
||||
|
||||
FlorisOutlinedTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = {
|
||||
textFieldValueState = it
|
||||
if (value != it.text) {
|
||||
onValueChange(it.text)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = textStyle,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
singleLine = singleLine,
|
||||
maxLines = maxLines,
|
||||
placeholder = placeholder,
|
||||
isError = isError,
|
||||
showValidationHint = showValidationHint,
|
||||
showValidationError = showValidationError,
|
||||
validationResult = validationResult,
|
||||
visualTransformation = visualTransformation,
|
||||
interactionSource = interactionSource,
|
||||
shape = shape,
|
||||
colors = colors,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FlorisOutlinedTextField(
|
||||
value: TextFieldValue,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
textStyle: TextStyle = TextStyle.Default,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
singleLine: Boolean = false,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
placeholder: String? = null,
|
||||
isError: Boolean = false,
|
||||
showValidationHint: Boolean = true,
|
||||
showValidationError: Boolean = false,
|
||||
validationResult: ValidationResult? = null,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
colors: TextFieldColors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.outline,
|
||||
disabledBorderColor = MaterialTheme.colorScheme.outline,
|
||||
),
|
||||
) {
|
||||
val textColor = textStyle.color.takeOrElse {
|
||||
if (!enabled) {
|
||||
colors.disabledTextColor
|
||||
} else {
|
||||
colors.unfocusedTextColor
|
||||
}
|
||||
}
|
||||
val mergedTextStyle = textStyle.copy(color = textColor, textDirection = TextDirection.Content)
|
||||
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||
val isErrorState = isError || (showValidationError && validationResult?.isInvalid() == true)
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
BasicTextField(
|
||||
modifier = modifier.padding(vertical = 4.dp),
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = mergedTextStyle,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
singleLine = singleLine,
|
||||
maxLines = maxLines,
|
||||
visualTransformation = visualTransformation,
|
||||
cursorBrush = SolidColor(
|
||||
if (isErrorState) {
|
||||
colors.errorCursorColor
|
||||
} else {
|
||||
colors.cursorColor
|
||||
}
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
Surface(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
color = if (enabled) {
|
||||
if (isErrorState) {
|
||||
colors.errorContainerColor
|
||||
} else if (isFocused) {
|
||||
colors.focusedContainerColor
|
||||
} else {
|
||||
colors.unfocusedContainerColor
|
||||
}
|
||||
} else {
|
||||
colors.disabledContainerColor
|
||||
},
|
||||
border = if (isErrorState && enabled) {
|
||||
BorderStroke(ButtonDefaults.outlinedButtonBorder.width, MaterialTheme.colorScheme.error)
|
||||
} else if (isFocused) {
|
||||
BorderStroke(ButtonDefaults.outlinedButtonBorder.width, MaterialTheme.colorScheme.primary)
|
||||
} else {
|
||||
ButtonDefaults.outlinedButtonBorder
|
||||
},
|
||||
shape = shape,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.defaultMinSize(
|
||||
minWidth = ButtonDefaults.MinWidth,
|
||||
minHeight = 40.dp,
|
||||
)
|
||||
.padding(ButtonDefaults.ContentPadding),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
ProvideTextStyle(value = mergedTextStyle) {
|
||||
innerTextField()
|
||||
}
|
||||
if (!placeholder.isNullOrBlank()) {
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.56f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showValidationHint && validationResult?.isValid() == true && validationResult.hasHintMessage()) {
|
||||
Text(
|
||||
text = validationResult.hintMessage(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.56f),
|
||||
)
|
||||
}
|
||||
|
||||
if (showValidationError && validationResult?.isInvalid() == true && validationResult.hasErrorMessage()) {
|
||||
Text(
|
||||
text = validationResult.errorMessage(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -22,31 +22,23 @@ import android.content.ContextWrapper
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.view.Window
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
|
||||
|
||||
@Composable
|
||||
fun SystemUiIme() {
|
||||
val useDarkIcons = !FlorisImeTheme.config.isNightTheme
|
||||
val context = LocalContext.current
|
||||
val view = LocalView.current
|
||||
val backgroundColor = FlorisImeTheme.style.get(FlorisImeUi.SystemNavBar).background.solidColor(context)
|
||||
val window = view.context.findWindow()!!
|
||||
val windowInsetsController = WindowInsetsControllerCompat(window, view)
|
||||
|
||||
SideEffect {
|
||||
if (AndroidVersion.ATLEAST_API26_O) {
|
||||
window.navigationBarColor = backgroundColor.toArgb()
|
||||
windowInsetsController.isAppearanceLightNavigationBars = useDarkIcons
|
||||
if (AndroidVersion.ATLEAST_API29_Q) window.isNavigationBarContrastEnforced = true
|
||||
LaunchedEffect(useDarkIcons) {
|
||||
windowInsetsController.isAppearanceLightNavigationBars = useDarkIcons
|
||||
if (AndroidVersion.ATLEAST_API29_Q) {
|
||||
window.isNavigationBarContrastEnforced = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The FlorisBoard Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.lib.compose
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.patrickgold.florisboard.lib.ValidationResult
|
||||
|
||||
@Composable
|
||||
fun Validation(
|
||||
showValidationErrors: Boolean,
|
||||
validationResult: ValidationResult?,
|
||||
) {
|
||||
if (showValidationErrors) {
|
||||
if (validationResult is ValidationResult.Valid && validationResult.hasHintMessage()) {
|
||||
Text(
|
||||
text = validationResult.hintMessage(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.56f),
|
||||
)
|
||||
}
|
||||
if (validationResult is ValidationResult.Invalid && validationResult.hasErrorMessage()) {
|
||||
Text(
|
||||
text = validationResult.errorMessage(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user