Compare commits
65 Commits
test/inlin
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e2b361ac3d | |||
|
|
5562d49546 | ||
|
|
5891c53cf6 | ||
|
|
5466d00037 | ||
|
|
8a06d764bb | ||
|
|
a800c7b230 | ||
|
|
67e99aeca3 | ||
|
|
ac14d92192 | ||
|
|
3392f8f212 | ||
|
|
8c7cafad61 | ||
|
|
721b25c349 | ||
|
|
96e3af9c16 | ||
|
|
fa79fa3849 | ||
|
|
f646825e9c | ||
|
|
0a299e1b04 | ||
|
|
6417cf5958 | ||
|
|
234580fd48 | ||
|
|
fe015c549c | ||
|
|
253ee969eb | ||
|
|
8fa986ca76 | ||
|
|
640a1c56cc | ||
|
|
059d2fd4bf | ||
|
|
1275650ca5 | ||
|
|
8359a6cd6c | ||
|
|
31730348b9 | ||
|
|
f12170543f | ||
|
|
37f9266ba3 | ||
|
|
ad47e51f0d | ||
|
|
6ccfb579b0 | ||
|
|
59e32a3a28 | ||
|
|
cee3da97f0 | ||
|
|
afad229273 | ||
|
|
b23496afa4 | ||
|
|
2dd37439ca | ||
|
|
75f774d5e2 | ||
|
|
38d91718ac | ||
|
|
8d9bacbefd | ||
|
|
a1031acd8e | ||
|
|
7a3af3eb54 | ||
|
|
7a433e9860 | ||
|
|
f687873cc5 | ||
|
|
c6a84fd324 | ||
|
|
dcc490cc9d | ||
|
|
d7b52cd489 | ||
|
|
fc7700395c | ||
|
|
9f07702852 | ||
|
|
f111544d81 | ||
|
|
fc09bb74c0 | ||
|
|
674bdccc40 | ||
|
|
3e69dddaa9 | ||
|
|
3c175bc60d | ||
|
|
83150e17ab | ||
|
|
2d6646e674 | ||
|
|
cc9511edca | ||
|
|
f25df31147 | ||
|
|
0ccb0c33b4 | ||
|
|
7ea4e8c827 | ||
|
|
93c76640f4 | ||
|
|
c2c874a9e0 | ||
|
|
b14877da8c | ||
|
|
2fb1fdb281 | ||
|
|
52646ecba1 | ||
|
|
7905dae807 | ||
|
|
e73ac5f96f | ||
|
|
15039fb020 |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -44,7 +44,8 @@ body:
|
||||
label: Install Source
|
||||
options:
|
||||
- Google PlayStore
|
||||
- F-Droid
|
||||
- F-Droid (F-Droid Main)
|
||||
- F-Droid (IzzyOnDroid)
|
||||
- GitHub
|
||||
validations:
|
||||
required: true
|
||||
|
||||
39
Android.bp
Normal file
39
Android.bp
Normal file
@@ -0,0 +1,39 @@
|
||||
android_app {
|
||||
name: "FlorisBoard",
|
||||
srcs: [
|
||||
"app/src/main/kotlin/**/*.kt",
|
||||
"app/src/main/java/**/*.java",
|
||||
],
|
||||
resource_dirs: ["app/src/main/res"],
|
||||
manifest: "app/src/main/AndroidManifest.xml",
|
||||
certificate: "platform",
|
||||
system_ext_specific: true,
|
||||
platform_apis: true,
|
||||
optimize: {
|
||||
enabled: true,
|
||||
},
|
||||
overrides: ["FlorisBoard"],
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"androidx.emoji2_emoji2",
|
||||
"androidx.emoji2_emoji2-views",
|
||||
"androidx.startup_startup",
|
||||
"androidx.appcompat_appcompat",
|
||||
"androidx.preference_preference",
|
||||
"androidx.recyclerview_recyclerview",
|
||||
"androidx.constraintlayout_constraintlayout",
|
||||
],
|
||||
required: ["android.permission.VIBRATE"],
|
||||
optional: ["android.permission.POST_NOTIFICATIONS"],
|
||||
allow_backup: true,
|
||||
backup_config: "res/xml/backup_rules.xml",
|
||||
full_backup_content: "res/xml/backup_rules.xml",
|
||||
enable_on_back_invoked_callback: true,
|
||||
profileable: true,
|
||||
package: "dev.patrickgold.florisboard",
|
||||
privileged: false,
|
||||
dex_preopt: {
|
||||
enabled: false,
|
||||
},
|
||||
asset_dirs: ["app/src/main/assets"],
|
||||
}
|
||||
@@ -10,7 +10,7 @@ The FlorisBoard community is international, as such we require all contributions
|
||||
|
||||
### Translations
|
||||
|
||||
To make FlorisBoard accessible in as many languages as possible, the platform [Crowdin](https://crowdin.florisboard.patrickgold.dev) is used to crowdsource and manage translations. The list of languages in Crowdin covers a good range of languages, but feel free to email [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a new language.
|
||||
To make FlorisBoard accessible in as many languages as possible, the platform [Crowdin](https://crowdin.florisboard.org) is used to crowdsource and manage translations. The list of languages in Crowdin covers a good range of languages, but feel free to email [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a new language.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is the only source of translations - **PRs that add/update translations are not accepted.**
|
||||
|
||||
31
README.md
31
README.md
@@ -1,11 +1,11 @@
|
||||
<img align="left" width="80" height="80"
|
||||
src=".github/repo_icon.png" alt="App icon">
|
||||
|
||||
# FlorisBoard [](https://crowdin.florisboard.patrickgold.dev) [](https://matrix.to/#/#florisboard:matrix.org) [](CODE_OF_CONDUCT.md) [](https://github.com/florisboard/florisboard/actions/workflows/android.yml)
|
||||
# FlorisBoard [](https://crowdin.florisboard.org) [](https://matrix.to/#/#florisboard:matrix.org) [](CODE_OF_CONDUCT.md) [](https://github.com/florisboard/florisboard/actions/workflows/android.yml)
|
||||
|
||||
**FlorisBoard** is a free and open-source keyboard for Android 7.0+
|
||||
**FlorisBoard** is a free and open-source keyboard for Android 8.0+
|
||||
devices. It aims at being modern, user-friendly and customizable while
|
||||
fully respecting your privacy. Currently in early-beta state.
|
||||
fully respecting your privacy. Currently in beta state.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -26,10 +26,13 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<p><a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a></p>
|
||||
<p>
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a>
|
||||
<a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard))
|
||||
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-closed-beta-test), then visit the [testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard))
|
||||
|
||||
</p>
|
||||
<p>
|
||||
@@ -47,7 +50,7 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
<p><a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard.beta"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a></p>
|
||||
<p>
|
||||
|
||||
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [preview testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
|
||||
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-closed-beta-test), then visit the [preview testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
|
||||
|
||||
</p>
|
||||
<p>
|
||||
@@ -96,6 +99,22 @@ Many thanks to Ali ([@4H1R](https://github.com/4H1R)) for implementing the store
|
||||
Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List-of-permissions-FlorisBoard-requests)
|
||||
to get more information on this topic.
|
||||
|
||||
## APK signing certificate hashes
|
||||
|
||||
The package names and SHA-256 hashes of the signature certificate are listed below, so you can verify both FlorisBoard variants with apksigner by using `apksigner verify --print-certs florisboard-<version>-<track>.apk` when you download the APK.
|
||||
If you have [AppVerifier](https://github.com/soupslurpr/AppVerifier) installed, you can alternatively copy both the package name and the hash of the corresponding track and share them to AppVerifier.
|
||||
|
||||
##### Stable track:
|
||||
|
||||
dev.patrickgold.florisboard<br>
|
||||
0B:80:71:64:50:8E:AF:EB:1F:BB:81:5B:E7:A2:3C:77:FE:68:9D:94:B1:43:75:C9:9B:DA:A9:B6:57:7F:D6:D6
|
||||
|
||||
##### Preview track:
|
||||
|
||||
dev.patrickgold.florisboard.beta<br>
|
||||
0B:80:71:64:50:8E:AF:EB:1F:BB:81:5B:E7:A2:3C:77:FE:68:9D:94:B1:43:75:C9:9B:DA:A9:B6:57:7F:D6:D6
|
||||
|
||||
|
||||
## Used libraries, components and icons
|
||||
* [AndroidX libraries](https://github.com/androidx/androidx) by
|
||||
[Android Jetpack](https://github.com/androidx)
|
||||
|
||||
@@ -9,13 +9,11 @@ Each major milestone has associated alpha/beta releases, so if you are intereste
|
||||
> [!NOTE]
|
||||
> The milestone 0.5 was split, thus the word suggestions now come with version 0.6. The old version 0.6 has been moved down and is now 0.7. The time it takes to implement word suggestions will not change, but we can now release the new theme editor earlier, which would otherwise lie dormant.
|
||||
|
||||
- [ ] Theme rework part II / Snygg v2
|
||||
- [x] Theme rework part II / Snygg v2
|
||||
- [x] See https://github.com/florisboard/florisboard/pull/2855
|
||||
- [x] Spaces in URI bug (See https://github.com/florisboard/florisboard/issues/2898)
|
||||
- [ ] Rework cache manager (See https://github.com/florisboard/florisboard/issues/2870)
|
||||
- [x] Re-add time based theme switching (See https://github.com/florisboard/florisboard/pull/2977)
|
||||
- [ ] Add support for any remaining new features introduced with Android 13 / 14
|
||||
- [ ] Proper physical keyboard support (See https://github.com/florisboard/florisboard/issues/2815)
|
||||
- [x] Add support for any remaining new features introduced with Android 13 / 14
|
||||
- [x] Raise minimum required Android version from Android 7 (SDK level 24) to Android 8 (SDK level 26)
|
||||
|
||||
## 0.6
|
||||
@@ -24,6 +22,8 @@ Each major milestone has associated alpha/beta releases, so if you are intereste
|
||||
- [ ] Add new extension type: Language Pack
|
||||
- Basically groups all locale-relevant data (predictive base model, emoji suggestion data, ...)
|
||||
in a dynamically importable extension file
|
||||
- [ ] Proper physical keyboard support (See https://github.com/florisboard/florisboard/issues/1972)
|
||||
- [ ] Rework cache manager (See https://github.com/florisboard/florisboard/issues/2870)
|
||||
|
||||
## k3lp
|
||||
|
||||
@@ -62,7 +62,6 @@ Each major milestone has associated alpha/beta releases, so if you are intereste
|
||||
- Text translation
|
||||
- Stickers/GIFs
|
||||
- Kaomoji panel implementation
|
||||
- FlorisBoard landing web page for presentation
|
||||
- Implementing additional layouts
|
||||
- Support for Tasker/Automate/MacroDroid plugins
|
||||
- Support for WearOS/Smartwatches
|
||||
|
||||
@@ -200,6 +200,7 @@ dependencies {
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.cache4k)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.mikepenz.aboutlibraries.core)
|
||||
@@ -212,6 +213,7 @@ dependencies {
|
||||
|
||||
implementation(project(":lib:android"))
|
||||
implementation(project(":lib:color"))
|
||||
implementation(project(":lib:compose"))
|
||||
implementation(project(":lib:kotlin"))
|
||||
implementation(project(":lib:native"))
|
||||
implementation(project(":lib:snygg"))
|
||||
|
||||
@@ -157,6 +157,13 @@
|
||||
"direction": "ltr",
|
||||
"modifier": "org.florisboard.layouts:dvorak"
|
||||
},
|
||||
{
|
||||
"id": "dvorak_se",
|
||||
"label": "Dvorak (SE)",
|
||||
"authors": [ "iceaway" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "org.florisboard.layouts:dvorak_se"
|
||||
},
|
||||
{
|
||||
"id": "esperanto",
|
||||
"label": "Esperanto",
|
||||
@@ -267,6 +274,12 @@
|
||||
"authors": [ "patrickgold", "Hayleia" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "korean_phonetic",
|
||||
"label": "South Korean Phonetic",
|
||||
"authors": [ "Shunnuo" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "kurdish",
|
||||
"label": "کوردی (قوەرتی نوێ)",
|
||||
@@ -510,6 +523,12 @@
|
||||
"authors": [ "msrd0" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "dvorak_se",
|
||||
"label": "Dvorak (SE)",
|
||||
"authors": [ "iceaway" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "hebrew",
|
||||
"label": "עברית",
|
||||
@@ -608,6 +627,12 @@
|
||||
"authors": [ "waelwindows" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "czech",
|
||||
"label": "Czech",
|
||||
"authors": [ "bmondream" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "devanagari",
|
||||
"label": "Devanagari",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 229, "label": "å" },
|
||||
{ "$": "auto_text_key", "code": 228, "label": "ä" },
|
||||
{ "$": "auto_text_key", "code": 246, "label": "ö" },
|
||||
{ "$": "auto_text_key", "code": 112, "label": "p" },
|
||||
{ "$": "auto_text_key", "code": 121, "label": "y" },
|
||||
{ "$": "auto_text_key", "code": 102, "label": "f" },
|
||||
{ "$": "auto_text_key", "code": 103, "label": "g" },
|
||||
{ "$": "auto_text_key", "code": 99, "label": "c" },
|
||||
{ "$": "auto_text_key", "code": 114, "label": "r" },
|
||||
{ "$": "auto_text_key", "code": 108, "label": "l" }
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 97, "label": "a" },
|
||||
{ "$": "auto_text_key", "code": 111, "label": "o" },
|
||||
{ "$": "auto_text_key", "code": 101, "label": "e" },
|
||||
{ "$": "auto_text_key", "code": 117, "label": "u" },
|
||||
{ "$": "auto_text_key", "code": 105, "label": "i" },
|
||||
{ "$": "auto_text_key", "code": 100, "label": "d" },
|
||||
{ "$": "auto_text_key", "code": 104, "label": "h" },
|
||||
{ "$": "auto_text_key", "code": 116, "label": "t" },
|
||||
{ "$": "auto_text_key", "code": 110, "label": "n" },
|
||||
{ "$": "auto_text_key", "code": 115, "label": "s" }
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 113, "label": "q" },
|
||||
{ "$": "auto_text_key", "code": 106, "label": "j" },
|
||||
{ "$": "auto_text_key", "code": 107, "label": "k" },
|
||||
{ "$": "auto_text_key", "code": 120, "label": "x" },
|
||||
{ "$": "auto_text_key", "code": 98, "label": "b" },
|
||||
{ "$": "auto_text_key", "code": 109, "label": "m" },
|
||||
{ "$": "auto_text_key", "code": 119, "label": "w" },
|
||||
{ "$": "auto_text_key", "code": 118, "label": "v" },
|
||||
{ "$": "auto_text_key", "code": 122, "label": "z" }
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
[
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 12615, "label": "ㅇ"},
|
||||
{ "$": "auto_text_key", "code": 12641, "label": "ㅡ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12628, "label": "ㅔ" },
|
||||
"upper": { "code": 12630, "label": "ㅖ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12601, "label": "ㄹ"},
|
||||
{ "$": "auto_text_key", "code": 12620, "label": "ㅌ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12624, "label": "ㅐ" },
|
||||
"upper": { "code": 12626, "label": "ㅒ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12636, "label": "ㅜ"},
|
||||
{ "$": "auto_text_key", "code": 12643, "label": "ㅣ"},
|
||||
{ "$": "auto_text_key", "code": 12631, "label": "ㅗ"},
|
||||
{ "$": "auto_text_key", "code": 12621, "label": "ㅍ"}
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 12623, "label": "ㅏ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12613, "label": "ㅅ" },
|
||||
"upper": { "code": 12614, "label": "ㅆ" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12599, "label": "ㄷ" },
|
||||
"upper": { "code": 12600, "label": "ㄸ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12625, "label": "ㅑ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12593, "label": "ㄱ" },
|
||||
"upper": { "code": 12594, "label": "ㄲ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12622, "label": "ㅎ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12616, "label": "ㅈ" },
|
||||
"upper": { "code": 12617, "label": "ㅉ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12619, "label": "ㅋ"},
|
||||
{ "$": "auto_text_key", "code": 12635, "label": "ㅛ"}
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 12629, "label": "ㅕ"},
|
||||
{ "$": "auto_text_key", "code": 12640, "label": "ㅠ"},
|
||||
{ "$": "auto_text_key", "code": 12618, "label": "ㅊ"},
|
||||
{ "$": "auto_text_key", "code": 12627, "label": "ㅓ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12610, "label": "ㅂ" },
|
||||
"upper": { "code": 12611, "label": "ㅃ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12596, "label": "ㄴ"},
|
||||
{ "$": "auto_text_key", "code": 12609, "label": "ㅁ"}
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
[
|
||||
{ "code": -11, "label": "shift", "type": "modifier" },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -7, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "$": "auto_text_key", "code": 44, "label": "," },
|
||||
{ "code": -227, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -212, "label": "ime_ui_mode_media", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "$": "auto_text_key", "code": 46, "label": "." },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,119 @@
|
||||
[
|
||||
[
|
||||
{ "$": "shift_state_selector",
|
||||
"default": {
|
||||
"code": 43, "label": "+", "type": "numeric", "popup": {
|
||||
"main": { "code": 49, "label": "1" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 282, "label": "Ě", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 283, "label": "ě", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 352, "label": "Š", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 353, "label": "š", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 268, "label": "Č", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 269, "label": "č", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 344, "label": "Ř", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 345, "label": "ř", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 381, "label": "Ž", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 382, "label": "ž", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 221, "label": "Ý", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 253, "label": "ý", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 193, "label": "Á", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 225, "label": "á", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 205, "label": "Í", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 237, "label": "í", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 201, "label": "É", "type": "numeric", "popup": {
|
||||
"main": { "code": 48, "label": "0" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 233, "label": "é", "type": "numeric", "popup": {
|
||||
"main": { "code": 48, "label": "0" }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -23,13 +23,17 @@
|
||||
{ "code": 45, "label": "-", "popup": {
|
||||
"main": { "code": 95, "label": "_" },
|
||||
"relevant": [
|
||||
{ "code": 8315, "label": "⁻" },
|
||||
{ "code": 8212, "label": "—" },
|
||||
{ "code": 8211, "label": "–" },
|
||||
{ "code": 183, "label": "·" }
|
||||
]
|
||||
} },
|
||||
{ "code": 43, "label": "+", "popup": {
|
||||
"main": { "code": 177, "label": "±" }
|
||||
"main": { "code": 177, "label": "±" },
|
||||
"relevant": [
|
||||
{ "code": 8314, "label": "⁺" }
|
||||
]
|
||||
} },
|
||||
{ "$": "layout_direction_selector",
|
||||
"ltr": { "code": 40, "label": "(", "popup": {
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
{ "id": "ja-JP-jis", "authors": [ "waelwindows" ] },
|
||||
{ "id": "kab", "authors": [ "yanis867" ] },
|
||||
{ "id": "ko", "authors": [ "patrickgold", "Hayleia" ] },
|
||||
{ "id": "ko-KR", "authors": [ "Shunnuo" ] },
|
||||
{ "id": "ku", "authors": [ "GoRaN" ] },
|
||||
{ "id": "lt", "authors": [ "patrickgold" ] },
|
||||
{ "id": "lv", "authors": [ "patrickgold", "eandersons" ] },
|
||||
@@ -606,6 +607,15 @@
|
||||
"characters": "org.florisboard.layouts:korean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "ko-KR",
|
||||
"composer": "org.florisboard.composers:hangul-unicode",
|
||||
"currencySet": "org.florisboard.currencysets:south_korean_won",
|
||||
"popupMapping": "org.florisboard.localization:ko",
|
||||
"preferred": {
|
||||
"characters": "org.florisboard.layouts:korean_phonetic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "lt-LT",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
|
||||
@@ -70,8 +70,9 @@
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 1611, "label": "ً" },
|
||||
"main": { "code": 1567, "label": "؟" },
|
||||
"relevant": [
|
||||
{ "code": 1611, "label": "ً" },
|
||||
{ "code": 1622, "label": "ٖ" },
|
||||
{ "code": 1648, "label": "ٰ" },
|
||||
{ "code": 1619, "label": "ٓ" },
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
]
|
||||
},
|
||||
"h": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 7717, "label": "ḥ" }
|
||||
]
|
||||
"main": { "$": "auto_text_key", "code": 7717, "label": "ḥ" }
|
||||
},
|
||||
"i": {
|
||||
"main": { "$": "auto_text_key", "code": 237, "label": "í" },
|
||||
@@ -49,13 +47,11 @@
|
||||
]
|
||||
},
|
||||
"l": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 7735, "label": "ḷ" }
|
||||
]
|
||||
"main": { "$": "auto_text_key", "code": 7735, "label": "ḷ" }
|
||||
},
|
||||
"n": {
|
||||
"main": { "$": "auto_text_key", "code": 241, "label": "ñ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 241, "label": "ñ" },
|
||||
{ "$": "auto_text_key", "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
@@ -73,9 +69,7 @@
|
||||
]
|
||||
},
|
||||
"r": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 691, "label": "ʳ" }
|
||||
]
|
||||
"main": { "$": "auto_text_key", "code": 691, "label": "ʳ" }
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
@@ -112,4 +106,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -102,7 +102,6 @@ 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.ime.theme.WallpaperChangeReceiver
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import dev.patrickgold.florisboard.lib.compose.SystemUiIme
|
||||
import dev.patrickgold.florisboard.lib.devtools.LogTopic
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
@@ -114,17 +113,19 @@ 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 kotlinx.coroutines.flow.update
|
||||
import org.florisboard.lib.android.AndroidInternalR
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.isOrientationLandscape
|
||||
import org.florisboard.lib.android.isOrientationPortrait
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
import org.florisboard.lib.android.systemServiceOrNull
|
||||
import org.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import org.florisboard.lib.kotlin.collectIn
|
||||
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.SnyggSurfaceView
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
|
||||
@@ -333,6 +334,11 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
return defaultExtractView
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
themeManager.configurationChangeCounter.update { it + 1 }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unregisterReceiver(wallpaperChangeReceiver)
|
||||
@@ -354,7 +360,9 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
if (info == null) return
|
||||
val editorInfo = FlorisEditorInfo.wrap(info)
|
||||
activeState.batchEdit {
|
||||
activeState.imeUiMode = ImeUiMode.TEXT
|
||||
if (activeState.imeUiMode != ImeUiMode.CLIPBOARD || prefs.clipboard.historyHideOnNextTextField.get()) {
|
||||
activeState.imeUiMode = ImeUiMode.TEXT
|
||||
}
|
||||
activeState.isSelectionMode = editorInfo.initialSelection.isSelectionMode
|
||||
editorInstance.handleStartInputView(editorInfo, isRestart = restarting)
|
||||
}
|
||||
@@ -422,6 +430,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
isWindowShown = false
|
||||
activeState.batchEdit {
|
||||
activeState.imeUiMode = ImeUiMode.TEXT
|
||||
activeState.isActionsOverflowVisible = false
|
||||
activeState.isActionsEditorVisible = false
|
||||
}
|
||||
@@ -572,7 +581,10 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
@Composable
|
||||
private fun ImeUiWrapper() {
|
||||
ProvideLocalizedResources(resourcesContext) {
|
||||
ProvideLocalizedResources(
|
||||
resourcesContext,
|
||||
appName = R.string.app_name,
|
||||
) {
|
||||
ProvideKeyboardRowBaseHeight {
|
||||
CompositionLocalProvider(LocalInputFeedbackController provides inputFeedbackController) {
|
||||
FlorisImeTheme {
|
||||
@@ -617,9 +629,19 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
clickAndSemanticsModifier = Modifier
|
||||
// Do not remove below line or touch input may get stuck
|
||||
.pointerInteropFilter { false },
|
||||
supportsBackgroundImage = true,
|
||||
supportsBackgroundImage = !AndroidVersion.ATLEAST_API30_R,
|
||||
allowClip = false,
|
||||
) {
|
||||
// The SurfaceView is used to render the background image under inline-autofill chips. These are only
|
||||
// available on Android >=11, and SurfaceView causes trouble on Android 8/9, thus we render the image
|
||||
// in the SurfaceView for Android >=11, and in the Compose View Tree for Android <=10.
|
||||
if (AndroidVersion.ATLEAST_API30_R) {
|
||||
SnyggSurfaceView(
|
||||
elementName = FlorisImeUi.Window.elementName,
|
||||
attributes = attributes,
|
||||
modifier = Modifier.matchParentSize(),
|
||||
)
|
||||
}
|
||||
val configuration = LocalConfiguration.current
|
||||
val bottomOffset by if (configuration.isOrientationPortrait()) {
|
||||
prefs.keyboard.bottomOffsetPortrait
|
||||
@@ -671,10 +693,13 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean =
|
||||
if (keyboardManager.onHardwareKeyDown(keyCode, event)) true
|
||||
else super.onKeyDown(keyCode, event)
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
return keyboardManager.onHardwareKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
return keyboardManager.onHardwareKeyUp(keyCode, event) || super.onKeyUp(keyCode, event)
|
||||
}
|
||||
|
||||
private inner class ComposeInputView : AbstractComposeView(this) {
|
||||
init {
|
||||
@@ -709,7 +734,11 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val state by keyboardManager.activeState.collectAsState()
|
||||
|
||||
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
|
||||
ProvideLocalizedResources(
|
||||
resourcesContext,
|
||||
appName = R.string.app_name,
|
||||
forceLayoutDirection = LayoutDirection.Ltr,
|
||||
) {
|
||||
FlorisImeTheme {
|
||||
BottomSheetHostUi(
|
||||
isShowing = state.isBottomSheetShowing() || state.isSubtypeSelectionShowing(),
|
||||
@@ -761,7 +790,11 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
@Composable
|
||||
fun Content() {
|
||||
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
|
||||
ProvideLocalizedResources(
|
||||
resourcesContext,
|
||||
appName = R.string.app_name,
|
||||
forceLayoutDirection = LayoutDirection.Ltr,
|
||||
) {
|
||||
FlorisImeTheme {
|
||||
val activeEditorInfo by editorInstance.activeInfoFlow.collectAsState()
|
||||
SnyggBox(FlorisImeUi.ExtractedLandscapeInputLayout.elementName) {
|
||||
|
||||
@@ -20,10 +20,12 @@ 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.ColorPreferenceSerializer
|
||||
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.clipboard.CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO
|
||||
import dev.patrickgold.florisboard.ime.clipboard.ClipboardSyncBehavior
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
|
||||
@@ -53,7 +55,6 @@ 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
|
||||
@@ -63,6 +64,7 @@ import dev.patrickgold.jetpref.datastore.model.LocalTime
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceData
|
||||
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 dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -84,62 +86,13 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
|
||||
key = "clipboard__use_internal_clipboard",
|
||||
default = false,
|
||||
)
|
||||
val syncToFloris = boolean(
|
||||
val syncToFloris = enum(
|
||||
key = "clipboard__sync_to_floris",
|
||||
default = true,
|
||||
default = ClipboardSyncBehavior.ALL_EVENTS,
|
||||
)
|
||||
val syncToSystem = boolean(
|
||||
val syncToSystem = enum(
|
||||
key = "clipboard__sync_to_system",
|
||||
default = false,
|
||||
)
|
||||
val historyEnabled = boolean(
|
||||
key = "clipboard__history_enabled",
|
||||
default = false,
|
||||
)
|
||||
val numHistoryGridColumnsPortrait = int(
|
||||
key = "clipboard__num_history_grid_columns_portrait",
|
||||
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
|
||||
)
|
||||
val numHistoryGridColumnsLandscape = int(
|
||||
key = "clipboard__num_history_grid_columns_landscape",
|
||||
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
|
||||
)
|
||||
@Composable
|
||||
fun numHistoryGridColumns(): PreferenceData<Int> {
|
||||
val configuration = LocalConfiguration.current
|
||||
return if (configuration.isOrientationPortrait()) {
|
||||
numHistoryGridColumnsPortrait
|
||||
} else {
|
||||
numHistoryGridColumnsLandscape
|
||||
}
|
||||
}
|
||||
val cleanUpOld = boolean(
|
||||
key = "clipboard__clean_up_old",
|
||||
default = false,
|
||||
)
|
||||
val cleanUpAfter = int(
|
||||
key = "clipboard__clean_up_after",
|
||||
default = 20,
|
||||
)
|
||||
val autoCleanSensitive = boolean(
|
||||
key = "clipboard__auto_clean_sensitive",
|
||||
default = false,
|
||||
)
|
||||
val autoCleanSensitiveAfter = int(
|
||||
key = "clipboard__auto_clean_sensitive_after",
|
||||
default = 20,
|
||||
)
|
||||
val limitHistorySize = boolean(
|
||||
key = "clipboard__limit_history_size",
|
||||
default = true,
|
||||
)
|
||||
val maxHistorySize = int(
|
||||
key = "clipboard__max_history_size",
|
||||
default = 20,
|
||||
)
|
||||
val clearPrimaryClipDeletesLastItem = boolean(
|
||||
key = "clipboard__clear_primary_clip_deletes_last_item",
|
||||
default = true,
|
||||
default = ClipboardSyncBehavior.NO_EVENTS,
|
||||
)
|
||||
val suggestionEnabled = boolean(
|
||||
key = "clipboard__suggestion_enabled",
|
||||
@@ -149,6 +102,63 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
|
||||
key = "clipboard__suggestion_timeout",
|
||||
default = 60,
|
||||
)
|
||||
val historyEnabled = boolean(
|
||||
key = "clipboard__history_enabled",
|
||||
default = false,
|
||||
)
|
||||
val historyNumGridColumnsPortrait = int(
|
||||
key = "clipboard__history_num_grid_columns_portrait",
|
||||
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
|
||||
)
|
||||
val historyNumGridColumnsLandscape = int(
|
||||
key = "clipboard__history_num_grid_columns_landscape",
|
||||
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
|
||||
)
|
||||
@Composable
|
||||
fun historyNumGridColumns(): PreferenceData<Int> {
|
||||
val configuration = LocalConfiguration.current
|
||||
return if (configuration.isOrientationPortrait()) {
|
||||
historyNumGridColumnsPortrait
|
||||
} else {
|
||||
historyNumGridColumnsLandscape
|
||||
}
|
||||
}
|
||||
val historyAutoCleanOldEnabled = boolean(
|
||||
key = "clipboard__history_auto_clean_old_enabled",
|
||||
default = false,
|
||||
)
|
||||
val historyAutoCleanOldAfter = int(
|
||||
key = "clipboard__history_auto_clean_old_after",
|
||||
default = 20,
|
||||
)
|
||||
val historyAutoCleanSensitiveEnabled = boolean(
|
||||
key = "clipboard__history_auto_clean_sensitive_enabled",
|
||||
default = false,
|
||||
)
|
||||
val historyAutoCleanSensitiveAfter = int(
|
||||
key = "clipboard__history_auto_clean_sensitive_after",
|
||||
default = 20,
|
||||
)
|
||||
val historySizeLimitEnabled = boolean(
|
||||
key = "clipboard__history_size_limit_enabled",
|
||||
default = true,
|
||||
)
|
||||
val historySizeLimit = int(
|
||||
key = "clipboard__history_size_limit",
|
||||
default = 20,
|
||||
)
|
||||
val historyHideOnPaste = boolean(
|
||||
key = "clipboard__history_hide_on_paste",
|
||||
default = false,
|
||||
)
|
||||
val historyHideOnNextTextField = boolean(
|
||||
key = "clipboard__history_hide_on_next_text_field",
|
||||
default = true,
|
||||
)
|
||||
val clearPrimaryClipAffectsHistoryIfUnpinned = boolean(
|
||||
key = "clipboard__clear_primary_clip_affects_history_if_unpinned",
|
||||
default = true,
|
||||
)
|
||||
}
|
||||
|
||||
val correction = Correction()
|
||||
@@ -860,6 +870,11 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
|
||||
dynamicActions = newArrangement.dynamicActions.plus(QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH))
|
||||
)
|
||||
}
|
||||
if (QuickAction.InsertKey(TextKeyData.FORWARD_DELETE) !in newArrangement) {
|
||||
newArrangement = newArrangement.copy(
|
||||
dynamicActions = newArrangement.dynamicActions.plus(QuickAction.InsertKey(TextKeyData.FORWARD_DELETE))
|
||||
)
|
||||
}
|
||||
val json = QuickActionJsonConfig.encodeToString(newArrangement.distinct())
|
||||
entry.transform(rawValue = json)
|
||||
}
|
||||
@@ -877,6 +892,44 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
|
||||
)
|
||||
}
|
||||
|
||||
// Migrate clipboard history pref names
|
||||
// Keep migration rules until: 0.7 dev cycle
|
||||
"clipboard__sync_to_floris", "clipboard__sync_to_system" -> {
|
||||
entry.transform(
|
||||
type = PreferenceType.string(),
|
||||
rawValue = when (entry.rawValue) {
|
||||
"true" -> ClipboardSyncBehavior.ALL_EVENTS
|
||||
else -> ClipboardSyncBehavior.NO_EVENTS
|
||||
}.name,
|
||||
)
|
||||
}
|
||||
"clipboard__num_history_grid_columns_portrait" -> {
|
||||
entry.transform(key = "clipboard__history_num_grid_columns_portrait")
|
||||
}
|
||||
"clipboard__num_history_grid_columns_landscape" -> {
|
||||
entry.transform(key = "clipboard__history_num_grid_columns_landscape")
|
||||
}
|
||||
"clipboard__clean_up_old" -> {
|
||||
entry.transform(key = "clipboard__history_auto_clean_old_enabled")
|
||||
}
|
||||
"clipboard__clean_up_after" -> {
|
||||
entry.transform(key = "clipboard__history_auto_clean_old_after")
|
||||
}
|
||||
"clipboard__auto_clean_sensitive" -> {
|
||||
entry.transform(key = "clipboard__history_auto_clean_sensitive_enabled")
|
||||
}
|
||||
"clipboard__auto_clean_sensitive_after" -> {
|
||||
entry.transform(key = "clipboard__history_auto_clean_sensitive_after")
|
||||
}
|
||||
"clipboard__limit_history_size" -> {
|
||||
entry.transform(key = "clipboard__history_size_limit_enabled")
|
||||
}
|
||||
"clipboard__max_history_size" -> {
|
||||
entry.transform(key = "clipboard__history_size_limit")
|
||||
}
|
||||
"clipboard__clear_primary_clip_deletes_last_item" -> {
|
||||
entry.transform(key = "clipboard__clear_primary_clip_affects_history_if_unpinned")
|
||||
}
|
||||
|
||||
// Default: keep entry
|
||||
else -> entry.keepAsIs()
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
|
||||
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
|
||||
import dev.patrickgold.florisboard.ime.clipboard.ClipboardSyncBehavior
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
|
||||
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
|
||||
@@ -42,10 +43,10 @@ import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
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 dev.patrickgold.jetpref.datastore.ui.ListPreferenceEntry
|
||||
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
|
||||
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@@ -104,6 +105,30 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
)
|
||||
}
|
||||
},
|
||||
ClipboardSyncBehavior::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
key = ClipboardSyncBehavior.NO_EVENTS,
|
||||
label = stringRes(R.string.enum__clipboard_sync_behavior__no_events),
|
||||
description = stringRes(R.string.enum__clipboard_sync_behavior__no_events__description),
|
||||
)
|
||||
entry(
|
||||
key = ClipboardSyncBehavior.ONLY_CLEAR_EVENTS,
|
||||
label = stringRes(R.string.enum__clipboard_sync_behavior__only_clear_events),
|
||||
description = stringRes(R.string.enum__clipboard_sync_behavior__only_clear_events__description),
|
||||
)
|
||||
entry(
|
||||
key = ClipboardSyncBehavior.ONLY_SET_EVENTS,
|
||||
label = stringRes(R.string.enum__clipboard_sync_behavior__only_set_events),
|
||||
description = stringRes(R.string.enum__clipboard_sync_behavior__only_set_events__description),
|
||||
)
|
||||
entry(
|
||||
key = ClipboardSyncBehavior.ALL_EVENTS,
|
||||
label = stringRes(R.string.enum__clipboard_sync_behavior__all_events),
|
||||
description = stringRes(R.string.enum__clipboard_sync_behavior__all_events__description),
|
||||
)
|
||||
}
|
||||
},
|
||||
ColorRepresentation::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
|
||||
@@ -51,16 +51,16 @@ import dev.patrickgold.florisboard.cacheManager
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.compose.LocalPreviewFieldController
|
||||
import dev.patrickgold.florisboard.lib.compose.PreviewKeyboardField
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import dev.patrickgold.florisboard.lib.compose.conditional
|
||||
import dev.patrickgold.florisboard.lib.compose.rememberPreviewFieldController
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.AppVersionUtils
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ProvideDefaultDialogPrefStrings
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.hideAppIcon
|
||||
import org.florisboard.lib.android.showAppIcon
|
||||
import org.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import org.florisboard.lib.compose.conditional
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.collectIn
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@@ -121,7 +121,10 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
}
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
setContent {
|
||||
ProvideLocalizedResources(resourcesContext) {
|
||||
ProvideLocalizedResources(
|
||||
resourcesContext,
|
||||
appName = R.string.app_name,
|
||||
) {
|
||||
FlorisAppTheme(theme = appTheme) {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
AppContent()
|
||||
@@ -194,7 +197,7 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
Routes.AppNavHost(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
navController = navController,
|
||||
startDestination = if (isImeSetUp) Routes.Settings.Home else Routes.Setup.Screen,
|
||||
startDestination = if (isImeSetUp) Routes.Settings.Home::class else Routes.Setup.Screen::class,
|
||||
)
|
||||
PreviewKeyboardField(previewFieldController)
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
package dev.patrickgold.florisboard.app
|
||||
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
@@ -30,6 +35,7 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.toRoute
|
||||
import dev.patrickgold.florisboard.app.devtools.AndroidLocalesScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.AndroidSettingsScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.DevtoolsScreen
|
||||
@@ -70,113 +76,185 @@ import dev.patrickgold.florisboard.app.settings.theme.ThemeManagerScreenAction
|
||||
import dev.patrickgold.florisboard.app.settings.theme.ThemeScreen
|
||||
import dev.patrickgold.florisboard.app.settings.typing.TypingScreen
|
||||
import dev.patrickgold.florisboard.app.setup.SetupScreen
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Deeplink(val path: String)
|
||||
|
||||
inline fun <reified T : Any> NavGraphBuilder.composableWithDeepLink(
|
||||
kClass: KClass<T>,
|
||||
noinline content: @Composable (AnimatedContentScope.(NavBackStackEntry) -> Unit),
|
||||
) {
|
||||
val deeplink = requireNotNull(kClass.annotations.firstOrNull { it is Deeplink } as? Deeplink) {
|
||||
"faulty class: $kClass with annotations ${kClass.annotations}"
|
||||
}
|
||||
composable<T>(
|
||||
deepLinks = listOf(navDeepLink<T>(basePath = "ui://florisboard/${deeplink.path}")),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName", "ConstPropertyName")
|
||||
object Routes {
|
||||
object Setup {
|
||||
const val Screen = "setup"
|
||||
@Serializable
|
||||
object Screen
|
||||
}
|
||||
|
||||
object Settings {
|
||||
const val Home = "settings"
|
||||
@Serializable
|
||||
@Deeplink("settings/home")
|
||||
object Home
|
||||
|
||||
const val Localization = "settings/localization"
|
||||
const val SelectLocale = "settings/localization/select-locale"
|
||||
const val LanguagePackManager = "settings/localization/language-pack-manage/{action}"
|
||||
fun LanguagePackManager(action: LanguagePackManagerScreenAction) =
|
||||
LanguagePackManager.curlyFormat("action" to action.id)
|
||||
const val SubtypeAdd = "settings/localization/subtype/add"
|
||||
const val SubtypeEdit = "settings/localization/subtype/edit/{id}"
|
||||
fun SubtypeEdit(id: Long) = SubtypeEdit.curlyFormat("id" to id)
|
||||
@Serializable
|
||||
@Deeplink("settings/localization")
|
||||
object Localization
|
||||
|
||||
const val Theme = "settings/theme"
|
||||
const val ThemeManager = "settings/theme/manage/{action}"
|
||||
fun ThemeManager(action: ThemeManagerScreenAction) = ThemeManager.curlyFormat("action" to action.id)
|
||||
@Serializable
|
||||
@Deeplink("settings/localization/select-locale")
|
||||
object SelectLocale
|
||||
|
||||
const val Keyboard = "settings/keyboard"
|
||||
const val InputFeedback = "settings/keyboard/input-feedback"
|
||||
@Serializable
|
||||
@Deeplink("settings/localization/language-pack-manage")
|
||||
data class LanguagePackManager(val action: LanguagePackManagerScreenAction)
|
||||
|
||||
const val Smartbar = "settings/smartbar"
|
||||
@Serializable
|
||||
@Deeplink("settings/localization/subtype/add")
|
||||
object SubtypeAdd
|
||||
|
||||
const val Typing = "settings/typing"
|
||||
@Serializable
|
||||
@Deeplink("settings/localization/subtype/edit")
|
||||
data class SubtypeEdit(val id: Long)
|
||||
|
||||
const val Dictionary = "settings/dictionary"
|
||||
const val UserDictionary = "settings/dictionary/user-dictionary/{type}"
|
||||
fun UserDictionary(type: UserDictionaryType) = UserDictionary.curlyFormat("type" to type.id)
|
||||
@Serializable
|
||||
@Deeplink("settings/theme")
|
||||
object Theme
|
||||
|
||||
const val Gestures = "settings/gestures"
|
||||
@Serializable
|
||||
@Deeplink("settings/theme/manage")
|
||||
data class ThemeManager(val action: ThemeManagerScreenAction)
|
||||
|
||||
const val Clipboard = "settings/clipboard"
|
||||
@Serializable
|
||||
@Deeplink("settings/keyboard")
|
||||
object Keyboard
|
||||
|
||||
const val Media = "settings/media"
|
||||
@Serializable
|
||||
@Deeplink("settings/keyboard/input-feedback")
|
||||
object InputFeedback
|
||||
|
||||
const val Other = "settings/other"
|
||||
const val PhysicalKeyboard = "settings/other/physical-keyboard"
|
||||
const val Backup = "settings/other/backup"
|
||||
const val Restore = "settings/other/restore"
|
||||
@Serializable
|
||||
@Deeplink("settings/smartbar")
|
||||
object Smartbar
|
||||
|
||||
const val About = "settings/about"
|
||||
const val ProjectLicense = "settings/about/project-license"
|
||||
const val ThirdPartyLicenses = "settings/about/third-party-licenses"
|
||||
@Serializable
|
||||
@Deeplink("settings/typing")
|
||||
object Typing
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/dictionary")
|
||||
object Dictionary
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/dictionary/user-dictionary")
|
||||
data class UserDictionary(val type: UserDictionaryType)
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/gestures")
|
||||
object Gestures
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/clipboard")
|
||||
object Clipboard
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/media")
|
||||
object Media
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/other")
|
||||
object Other
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/other/physical-keyboard")
|
||||
object PhysicalKeyboard
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/other/backup")
|
||||
object Backup
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/other/restore")
|
||||
object Restore
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/about")
|
||||
object About
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/about/project-license")
|
||||
object ProjectLicense
|
||||
|
||||
@Serializable
|
||||
@Deeplink("settings/about/third-party-licenses")
|
||||
object ThirdPartyLicenses
|
||||
}
|
||||
|
||||
object Devtools {
|
||||
const val Home = "devtools"
|
||||
@Serializable
|
||||
@Deeplink("devtools")
|
||||
object Home
|
||||
|
||||
const val AndroidLocales = "devtools/android/locales"
|
||||
const val AndroidSettings = "devtools/android/settings/{name}"
|
||||
fun AndroidSettings(name: String) = AndroidSettings.curlyFormat("name" to name)
|
||||
@Serializable
|
||||
@Deeplink("devtools/android/locales")
|
||||
object AndroidLocales
|
||||
|
||||
const val ExportDebugLog = "export-debug-log"
|
||||
@Serializable
|
||||
@Deeplink("devtools/android/settings")
|
||||
data class AndroidSettings(val name: String)
|
||||
|
||||
@Serializable
|
||||
@Deeplink("export-debug-log")
|
||||
object ExportDebugLog
|
||||
}
|
||||
|
||||
object Ext {
|
||||
const val Home = "ext"
|
||||
@Serializable
|
||||
@Deeplink("ext")
|
||||
object Home
|
||||
|
||||
const val List = "ext/list/{type}?showUpdate={showUpdate}"
|
||||
fun List(
|
||||
type: ExtensionListScreenType,
|
||||
showUpdate: Boolean
|
||||
) = List.curlyFormat("type" to type.id, "showUpdate" to showUpdate)
|
||||
@Serializable
|
||||
@Deeplink("ext/list")
|
||||
data class List(val type: ExtensionListScreenType, val showUpdate: Boolean? = null)
|
||||
|
||||
const val Edit = "ext/edit/{id}?create={serial_type}"
|
||||
fun Edit(id: String, serialType: String? = null): String {
|
||||
return Edit.curlyFormat("id" to id, "serial_type" to (serialType ?: ""))
|
||||
}
|
||||
@Serializable
|
||||
@Deeplink("ext/edit")
|
||||
data class Edit(val id: String, @SerialName("create") val serialType: String? = null)
|
||||
|
||||
const val Export = "ext/export/{id}"
|
||||
fun Export(id: String) = Export.curlyFormat("id" to id)
|
||||
@Serializable
|
||||
@Deeplink("ext/export")
|
||||
data class Export(val id: String)
|
||||
|
||||
const val Import = "ext/import/{type}?uuid={uuid}"
|
||||
fun Import(
|
||||
type: ExtensionImportScreenType,
|
||||
uuid: String?,
|
||||
) = Import.curlyFormat("type" to type.id, "uuid" to uuid.toString())
|
||||
@Serializable
|
||||
@Deeplink("ext/import")
|
||||
data class Import(val type: ExtensionImportScreenType, val uuid: String? = null)
|
||||
|
||||
const val View = "ext/view/{id}"
|
||||
fun View(id: String) = View.curlyFormat("id" to id)
|
||||
@Serializable
|
||||
@Deeplink("ext/view")
|
||||
data class View(val id: String)
|
||||
|
||||
const val CheckUpdates = "ext/check-updates"
|
||||
@Serializable
|
||||
@Deeplink("ext/check-updates")
|
||||
object CheckUpdates
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppNavHost(
|
||||
modifier: Modifier,
|
||||
navController: NavHostController,
|
||||
startDestination: String,
|
||||
startDestination: KClass<*>,
|
||||
) {
|
||||
fun NavGraphBuilder.composableWithDeepLink(
|
||||
route: String,
|
||||
content: @Composable (AnimatedContentScope.(NavBackStackEntry) -> Unit),
|
||||
) {
|
||||
composable(
|
||||
route = route,
|
||||
deepLinks = listOf(navDeepLink { uriPattern = "ui://florisboard/$route" }),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
NavHost(
|
||||
modifier = modifier,
|
||||
navController = navController,
|
||||
@@ -187,109 +265,103 @@ object Routes {
|
||||
exitTransition = {
|
||||
slideOut { IntOffset(-it.width, 0) } + fadeOut()
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideIn { IntOffset(-it.width, 0) } + fadeIn()
|
||||
},
|
||||
popEnterTransition = { EnterTransition.None },
|
||||
popExitTransition = {
|
||||
slideOut { IntOffset(it.width, 0) } + fadeOut()
|
||||
}
|
||||
scaleOut(
|
||||
targetScale = 0.85F,
|
||||
transformOrigin = TransformOrigin(pivotFractionX = 0.8f, pivotFractionY = 0.5f)
|
||||
) + fadeOut(spring(stiffness = Spring.StiffnessMedium))
|
||||
},
|
||||
) {
|
||||
composable(Setup.Screen) { SetupScreen() }
|
||||
composable<Setup.Screen> { SetupScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Home) { HomeScreen() }
|
||||
composableWithDeepLink(Settings.Home::class) { HomeScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Localization) { LocalizationScreen() }
|
||||
composableWithDeepLink(Settings.SelectLocale) { SelectLocaleScreen() }
|
||||
composableWithDeepLink(Settings.LanguagePackManager) { navBackStack ->
|
||||
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
|
||||
LanguagePackManagerScreenAction.entries.firstOrNull { it.id == actionId }
|
||||
}
|
||||
LanguagePackManagerScreen(action)
|
||||
composableWithDeepLink(Settings.Localization::class) { LocalizationScreen() }
|
||||
composableWithDeepLink(Settings.SelectLocale::class) { SelectLocaleScreen() }
|
||||
composableWithDeepLink(Settings.LanguagePackManager::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Settings.LanguagePackManager>()
|
||||
LanguagePackManagerScreen(payload.action)
|
||||
}
|
||||
composableWithDeepLink(Settings.SubtypeAdd) { SubtypeEditorScreen(null) }
|
||||
composableWithDeepLink(Settings.SubtypeEdit) { navBackStack ->
|
||||
val id = navBackStack.arguments?.getString("id")?.toLongOrNull()
|
||||
SubtypeEditorScreen(id)
|
||||
composableWithDeepLink(Settings.SubtypeAdd::class) { SubtypeEditorScreen(null) }
|
||||
composableWithDeepLink(Settings.SubtypeEdit::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Settings.SubtypeEdit>()
|
||||
SubtypeEditorScreen(payload.id)
|
||||
}
|
||||
|
||||
composableWithDeepLink(Settings.Theme) { ThemeScreen() }
|
||||
composableWithDeepLink(Settings.ThemeManager) { navBackStack ->
|
||||
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
|
||||
ThemeManagerScreenAction.entries.firstOrNull { it.id == actionId }
|
||||
}
|
||||
ThemeManagerScreen(action)
|
||||
composableWithDeepLink(Settings.Theme::class) { ThemeScreen() }
|
||||
composableWithDeepLink(Settings.ThemeManager::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Settings.ThemeManager>()
|
||||
ThemeManagerScreen(payload.action)
|
||||
}
|
||||
|
||||
composableWithDeepLink(Settings.Keyboard) { KeyboardScreen() }
|
||||
composableWithDeepLink(Settings.InputFeedback) { InputFeedbackScreen() }
|
||||
composableWithDeepLink(Settings.Keyboard::class) { KeyboardScreen() }
|
||||
composableWithDeepLink(Settings.InputFeedback::class) { InputFeedbackScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Smartbar) { SmartbarScreen() }
|
||||
composableWithDeepLink(Settings.Smartbar::class) { SmartbarScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Typing) { TypingScreen() }
|
||||
composableWithDeepLink(Settings.Typing::class) { TypingScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Dictionary) { DictionaryScreen() }
|
||||
composableWithDeepLink(Settings.UserDictionary) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
UserDictionaryType.entries.firstOrNull { it.id == typeId }
|
||||
}
|
||||
UserDictionaryScreen(type!!)
|
||||
composableWithDeepLink(Settings.Dictionary::class) { DictionaryScreen() }
|
||||
composableWithDeepLink(Settings.UserDictionary::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Settings.UserDictionary>()
|
||||
UserDictionaryScreen(payload.type)
|
||||
}
|
||||
|
||||
composableWithDeepLink(Settings.Gestures) { GesturesScreen() }
|
||||
composableWithDeepLink(Settings.Gestures::class) { GesturesScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Clipboard) { ClipboardScreen() }
|
||||
composableWithDeepLink(Settings.Clipboard::class) { ClipboardScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Media) { MediaScreen() }
|
||||
composableWithDeepLink(Settings.Media::class) { MediaScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.Other) { OtherScreen() }
|
||||
composableWithDeepLink(Settings.PhysicalKeyboard) { PhysicalKeyboardScreen() }
|
||||
composableWithDeepLink(Settings.Backup) { BackupScreen() }
|
||||
composableWithDeepLink(Settings.Restore) { RestoreScreen() }
|
||||
composableWithDeepLink(Settings.Other::class) { OtherScreen() }
|
||||
composableWithDeepLink(Settings.PhysicalKeyboard::class) { PhysicalKeyboardScreen() }
|
||||
composableWithDeepLink(Settings.Backup::class) { BackupScreen() }
|
||||
composableWithDeepLink(Settings.Restore::class) { RestoreScreen() }
|
||||
|
||||
composableWithDeepLink(Settings.About) { AboutScreen() }
|
||||
composableWithDeepLink(Settings.ProjectLicense) { ProjectLicenseScreen() }
|
||||
composableWithDeepLink(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
|
||||
composableWithDeepLink(Settings.About::class) { AboutScreen() }
|
||||
composableWithDeepLink(Settings.ProjectLicense::class) { ProjectLicenseScreen() }
|
||||
composableWithDeepLink(Settings.ThirdPartyLicenses::class) { ThirdPartyLicensesScreen() }
|
||||
|
||||
composableWithDeepLink(Devtools.Home) { DevtoolsScreen() }
|
||||
composableWithDeepLink(Devtools.AndroidLocales) { AndroidLocalesScreen() }
|
||||
composableWithDeepLink(Devtools.AndroidSettings) { navBackStack ->
|
||||
val name = navBackStack.arguments?.getString("name")
|
||||
AndroidSettingsScreen(name)
|
||||
composableWithDeepLink(Devtools.Home::class) { DevtoolsScreen() }
|
||||
composableWithDeepLink(Devtools.AndroidLocales::class) { AndroidLocalesScreen() }
|
||||
composableWithDeepLink(Devtools.AndroidSettings::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Devtools.AndroidSettings>()
|
||||
AndroidSettingsScreen(payload.name)
|
||||
}
|
||||
composableWithDeepLink(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
|
||||
composableWithDeepLink(Devtools.ExportDebugLog::class) { ExportDebugLogScreen() }
|
||||
|
||||
composableWithDeepLink(Ext.Home) { ExtensionHomeScreen() }
|
||||
composableWithDeepLink(Ext.List) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
ExtensionListScreenType.entries.firstOrNull { it.id == typeId }
|
||||
} ?: error("unknown type")
|
||||
val showUpdate = navBackStack.arguments?.getString("showUpdate")
|
||||
ExtensionListScreen(type, showUpdate == "true")
|
||||
composableWithDeepLink(Ext.Home::class) { ExtensionHomeScreen() }
|
||||
composableWithDeepLink(Ext.List::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Ext.List>()
|
||||
val showUpdate = payload.showUpdate != null && payload.showUpdate
|
||||
ExtensionListScreen(payload.type, showUpdate)
|
||||
}
|
||||
composableWithDeepLink(Ext.Edit) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
val serialType = navBackStack.arguments?.getString("serial_type")
|
||||
composableWithDeepLink(Ext.Edit::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Ext.Edit>()
|
||||
val extensionId = payload.id
|
||||
val serialType = payload.serialType
|
||||
ExtensionEditScreen(
|
||||
id = extensionId.toString(),
|
||||
id = extensionId,
|
||||
createSerialType = serialType.takeIf { !it.isNullOrBlank() },
|
||||
)
|
||||
}
|
||||
composableWithDeepLink(Ext.Export) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
ExtensionExportScreen(id = extensionId.toString())
|
||||
composableWithDeepLink(Ext.Export::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Ext.Export>()
|
||||
val extensionId = payload.id
|
||||
ExtensionExportScreen(id = extensionId)
|
||||
}
|
||||
composableWithDeepLink(Ext.Import) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
ExtensionImportScreenType.entries.firstOrNull { it.id == typeId }
|
||||
} ?: ExtensionImportScreenType.EXT_ANY
|
||||
val uuid = navBackStack.arguments?.getString("uuid")?.takeIf { it != "null" }
|
||||
ExtensionImportScreen(type, uuid)
|
||||
composableWithDeepLink(Ext.Import::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Ext.Import>()
|
||||
val uuid = payload.uuid
|
||||
ExtensionImportScreen(payload.type, uuid)
|
||||
}
|
||||
composableWithDeepLink(Ext.View) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
ExtensionViewScreen(id = extensionId.toString())
|
||||
composableWithDeepLink(Ext.View::class) { navBackStack ->
|
||||
val payload = navBackStack.toRoute<Ext.View>()
|
||||
val extensionId = payload.id
|
||||
ExtensionViewScreen(id = extensionId)
|
||||
}
|
||||
composableWithDeepLink(Ext.CheckUpdates) {
|
||||
composableWithDeepLink(Ext.CheckUpdates::class) {
|
||||
CheckUpdatesScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,11 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
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.model.observeAsState
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import java.util.Locale
|
||||
|
||||
@@ -27,11 +27,11 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import org.florisboard.lib.android.AndroidSettings
|
||||
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 org.florisboard.lib.android.AndroidSettings
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun AndroidSettingsScreen(name: String?) = FlorisScreen {
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.appContext
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.editorInstance
|
||||
import dev.patrickgold.florisboard.ime.keyboard.CachedLayout
|
||||
@@ -52,10 +53,10 @@ 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.*
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.SnyggMissingSchemaException
|
||||
|
||||
private val CardBackground = Color.Black.copy(0.6f)
|
||||
private val DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", FlorisLocale.default().base)
|
||||
@@ -64,6 +65,7 @@ private val DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", FlorisLocale.
|
||||
fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val prefs by FlorisPreferenceStore
|
||||
val appContext by context.appContext()
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val themeManager by context.themeManager()
|
||||
|
||||
@@ -72,6 +74,7 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
val showInputStateOverlay by prefs.devtools.showInputStateOverlay.observeAsState()
|
||||
val showSpellingOverlay by prefs.devtools.showSpellingOverlay.observeAsState()
|
||||
val showInlineAutofillOverlay by prefs.devtools.showInlineAutofillOverlay.observeAsState()
|
||||
val prefsLoaded by appContext.preferenceStoreLoaded.collectAsState()
|
||||
|
||||
val debugLayoutResult by keyboardManager.layoutManager.debugLayoutComputationResultFlow.collectAsState()
|
||||
val themeInfo by themeManager.activeThemeInfo.collectAsState()
|
||||
@@ -97,7 +100,7 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
DevtoolsInlineAutofillOverlay()
|
||||
}
|
||||
val loadFailure = themeInfo.loadFailure
|
||||
if (loadFailure != null) {
|
||||
if (loadFailure != null && prefsLoaded) {
|
||||
DevtoolsStylesheetFailedToLoadOverlay(loadFailure)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,18 +29,17 @@ import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
|
||||
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionJsonConfig
|
||||
import org.florisboard.lib.android.AndroidSettings
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
|
||||
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.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.android.AndroidSettings
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
|
||||
class DebugOnPurposeCrashException : Exception(
|
||||
|
||||
@@ -40,13 +40,13 @@ import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.devtools.Devtools
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.compose.FlorisButton
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.compose.florisScrollbar
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
|
||||
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI
|
||||
|
||||
@@ -35,13 +35,13 @@ 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.lib.compose.FlorisOutlinedBox
|
||||
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.ext.Extension
|
||||
import dev.patrickgold.florisboard.lib.ext.generateUpdateUrl
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.FlorisTextButton
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -21,7 +21,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun CheckUpdatesScreen() = FlorisScreen {
|
||||
|
||||
@@ -40,13 +40,13 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.nlp.LanguagePackComponent
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponent
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.FlorisTextButton
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ExtensionComponentNoneFoundView() {
|
||||
|
||||
@@ -48,9 +48,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
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
|
||||
@@ -62,6 +60,8 @@ import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.io.parentDir
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
|
||||
@@ -42,7 +42,9 @@ 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.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
@@ -60,15 +62,9 @@ import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentImpl
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionEditor
|
||||
import dev.patrickgold.florisboard.lib.ValidationResult
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
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.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
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponent
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
@@ -85,11 +81,15 @@ import dev.patrickgold.florisboard.lib.io.ZipUtils
|
||||
import dev.patrickgold.florisboard.lib.rememberValidationResult
|
||||
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.compose.FlorisButtonBar
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.FlorisInfoCard
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
@@ -353,7 +353,7 @@ private fun EditScreen(
|
||||
)
|
||||
Preference(
|
||||
onClick = { workspace.currentAction = EditorAction.ManageFiles },
|
||||
icon = vectorResource(R.drawable.ic_file_blank),
|
||||
icon = ImageVector.vectorResource(R.drawable.ic_file_blank),
|
||||
title = stringRes(R.string.ext__editor__files__title),
|
||||
)
|
||||
}
|
||||
@@ -628,7 +628,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
|
||||
for (theme in editor.themes) {
|
||||
put(ExtensionComponentName(extId, theme.id), theme)
|
||||
}
|
||||
for ((componentName, theme) in themeManager.indexedThemeConfigs.value ?: emptyMap()) {
|
||||
for ((componentName, theme) in themeManager.indexedThemeConfigs.value.first) {
|
||||
if (componentName.extensionId != extId) {
|
||||
put(componentName, theme)
|
||||
}
|
||||
@@ -710,7 +710,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
|
||||
}
|
||||
editor.themes.add(componentEditor)
|
||||
} else {
|
||||
val component = themeManager.indexedThemeConfigs.value?.get(componentName) ?: return
|
||||
val component = themeManager.indexedThemeConfigs.value.first.get(componentName) ?: return
|
||||
val componentEditor = (component as? ThemeExtensionComponentImpl)?.edit() ?: return
|
||||
componentEditor.id = componentId
|
||||
componentEditor.stylesheetPath = ""
|
||||
|
||||
@@ -27,8 +27,8 @@ import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ExtensionHomeScreen() = FlorisScreen {
|
||||
|
||||
@@ -50,17 +50,16 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyboardExtension
|
||||
import dev.patrickgold.florisboard.ime.nlp.LanguagePackExtension
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
|
||||
import dev.patrickgold.florisboard.lib.NATIVE_NULLPTR
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisBulletSpacer
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.io.FileRegistry
|
||||
import org.florisboard.lib.compose.FlorisBulletSpacer
|
||||
import org.florisboard.lib.compose.FlorisButtonBar
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.FlorisOutlinedButton
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.kotlin.resultOk
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisChip
|
||||
import org.florisboard.lib.compose.FlorisChip
|
||||
|
||||
@Composable
|
||||
fun ExtensionKeywordChip(
|
||||
|
||||
@@ -52,14 +52,14 @@ import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
|
||||
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.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.FlorisTextButton
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.florisScrollbar
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
enum class ExtensionListScreenType(
|
||||
val id: String,
|
||||
|
||||
@@ -30,10 +30,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisChip
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionMaintainer
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import org.florisboard.lib.compose.FlorisChip
|
||||
|
||||
@Composable
|
||||
fun ExtensionMaintainerChip(
|
||||
|
||||
@@ -24,7 +24,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
internal fun ExtensionNotFoundScreen(id: String) = FlorisScreen {
|
||||
|
||||
@@ -51,18 +51,17 @@ import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.nlp.LanguagePackExtension
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentImpl
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisHyperlinkText
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.Extension
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionMaintainer
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
|
||||
import dev.patrickgold.florisboard.lib.io.FlorisRef
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.compose.FlorisOutlinedButton
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ExtensionViewScreen(id: String) {
|
||||
|
||||
@@ -37,13 +37,13 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisErrorCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisWarningCard
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import org.florisboard.lib.compose.FlorisErrorCard
|
||||
import org.florisboard.lib.compose.FlorisWarningCard
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun HomeScreen() = FlorisScreen {
|
||||
|
||||
@@ -41,12 +41,12 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisCanvasIcon
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.compose.FlorisCanvasIcon
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun AboutScreen() = FlorisScreen {
|
||||
|
||||
@@ -29,11 +29,11 @@ import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.io.FlorisRef
|
||||
import dev.patrickgold.florisboard.lib.io.loadTextAsset
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.compose.florisVerticalScroll
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ProjectLicenseScreen() = FlorisScreen {
|
||||
|
||||
@@ -26,8 +26,8 @@ import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||
import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.compose.florisScrollbar
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ThirdPartyLicensesScreen() = FlorisScreen {
|
||||
|
||||
@@ -50,12 +50,7 @@ import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
|
||||
import dev.patrickgold.florisboard.lib.io.FileRegistry
|
||||
@@ -69,6 +64,11 @@ import kotlinx.serialization.Serializable
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.android.writeFromFile
|
||||
import org.florisboard.lib.compose.FlorisButtonBar
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.rippleClickable
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import org.florisboard.lib.kotlin.io.writeJson
|
||||
@@ -194,7 +194,8 @@ fun BackupScreen() = FlorisScreen {
|
||||
}
|
||||
|
||||
if (backupFilesSelector.provideClipboardItems()) {
|
||||
val clipboardHistory = context.clipboardManager().value.history().all
|
||||
val clipboardManager by context.clipboardManager()
|
||||
val clipboardHistory = clipboardManager.currentHistory.all
|
||||
val clipboardFilesDir = workspace.inputDir.subDir("clipboard")
|
||||
clipboardFilesDir.mkdir()
|
||||
if (backupFilesSelector.clipboardTextItems) {
|
||||
|
||||
@@ -27,7 +27,9 @@ 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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.AppTheme
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
@@ -36,7 +38,6 @@ import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
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
|
||||
@@ -45,9 +46,9 @@ 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
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -156,7 +157,7 @@ fun OtherScreen() = FlorisScreen {
|
||||
enabledIf = { AndroidVersion.ATMOST_API28_P },
|
||||
)
|
||||
Preference(
|
||||
icon = vectorResource(R.drawable.ic_keyboard_keys),
|
||||
icon = ImageVector.vectorResource(R.drawable.ic_keyboard_keys),
|
||||
title = stringRes(R.string.physical_keyboard__title),
|
||||
onClick = { navController.navigate(Routes.Settings.PhysicalKeyboard) },
|
||||
)
|
||||
|
||||
@@ -28,9 +28,9 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
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.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun PhysicalKeyboardScreen() = FlorisScreen {
|
||||
|
||||
@@ -52,32 +52,32 @@ import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisCardDefaults
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
|
||||
import dev.patrickgold.florisboard.lib.io.ZipUtils
|
||||
import dev.patrickgold.jetpref.datastore.runtime.AndroidAppDataStorage
|
||||
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
|
||||
import dev.patrickgold.jetpref.datastore.runtime.ImportStrategy
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import java.io.FileNotFoundException
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.readToFile
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.compose.FlorisButtonBar
|
||||
import org.florisboard.lib.compose.FlorisCardDefaults
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.FlorisOutlinedButton
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
|
||||
import org.florisboard.lib.kotlin.io.readJson
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import java.io.FileNotFoundException
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
object Restore {
|
||||
const val MIN_VERSION_CODE = 64
|
||||
@@ -148,11 +148,13 @@ fun RestoreScreen() = FlorisScreen {
|
||||
val workspace = restoreWorkspace!!
|
||||
val shouldReset = importStrategy == ImportStrategy.Erase
|
||||
if (restoreFilesSelector.jetprefDatastore) {
|
||||
val fileBasedStorage = workspace.outputDir
|
||||
val file = workspace.outputDir
|
||||
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
|
||||
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
|
||||
.let { FileBasedStorage(it.path) }
|
||||
FlorisPreferenceStore.import(importStrategy, fileBasedStorage).getOrThrow()
|
||||
if (file.exists()) {
|
||||
val fileBasedStorage = FileBasedStorage(file.path)
|
||||
FlorisPreferenceStore.import(importStrategy, fileBasedStorage).getOrThrow()
|
||||
}
|
||||
}
|
||||
val workspaceFilesDir = workspace.outputDir.subDir("files")
|
||||
if (restoreFilesSelector.imeKeyboard) {
|
||||
|
||||
@@ -18,15 +18,18 @@ package dev.patrickgold.florisboard.app.settings.clipboard
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.clipboard.CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO
|
||||
import dev.patrickgold.florisboard.ime.clipboard.ClipboardSyncBehavior
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.pluralsRes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.compose.pluralsRes
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
@@ -40,16 +43,16 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__clipboard__use_internal_clipboard__label),
|
||||
summary = stringRes(R.string.pref__clipboard__use_internal_clipboard__summary),
|
||||
)
|
||||
SwitchPreference(
|
||||
ListPreference(
|
||||
prefs.clipboard.syncToFloris,
|
||||
title = stringRes(R.string.pref__clipboard__sync_from_system_clipboard__label),
|
||||
summary = stringRes(R.string.pref__clipboard__sync_from_system_clipboard__summary),
|
||||
entries = enumDisplayEntriesOf(ClipboardSyncBehavior::class),
|
||||
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
ListPreference(
|
||||
prefs.clipboard.syncToSystem,
|
||||
title = stringRes(R.string.pref__clipboard__sync_to_system_clipboard__label),
|
||||
summary = stringRes(R.string.pref__clipboard__sync_to_system_clipboard__summary),
|
||||
entries = enumDisplayEntriesOf(ClipboardSyncBehavior::class),
|
||||
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
|
||||
)
|
||||
|
||||
@@ -77,8 +80,8 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
summary = stringRes(R.string.pref__clipboard__enable_clipboard_history__summary),
|
||||
)
|
||||
DialogSliderPreference(
|
||||
primaryPref = prefs.clipboard.numHistoryGridColumnsPortrait,
|
||||
secondaryPref = prefs.clipboard.numHistoryGridColumnsLandscape,
|
||||
primaryPref = prefs.clipboard.historyNumGridColumnsPortrait,
|
||||
secondaryPref = prefs.clipboard.historyNumGridColumnsLandscape,
|
||||
title = stringRes(R.string.pref__clipboard__num_history_grid_columns__label),
|
||||
primaryLabel = stringRes(R.string.screen_orientation__portrait),
|
||||
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
|
||||
@@ -95,53 +98,65 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.clipboard.cleanUpOld,
|
||||
prefs.clipboard.historyAutoCleanOldEnabled,
|
||||
title = stringRes(R.string.pref__clipboard__clean_up_old__label),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.cleanUpAfter,
|
||||
prefs.clipboard.historyAutoCleanOldAfter,
|
||||
title = stringRes(R.string.pref__clipboard__clean_up_after__label),
|
||||
valueLabel = { pluralsRes(R.plurals.unit__minutes__written, it, "v" to it) },
|
||||
min = 0,
|
||||
max = 120,
|
||||
stepIncrement = 5,
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.cleanUpOld isEqualTo true },
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.historyAutoCleanOldEnabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.clipboard.autoCleanSensitive,
|
||||
prefs.clipboard.historyAutoCleanSensitiveEnabled,
|
||||
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive__label),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
|
||||
visibleIf = { AndroidVersion.ATLEAST_API33_T },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.autoCleanSensitiveAfter,
|
||||
prefs.clipboard.historyAutoCleanSensitiveAfter,
|
||||
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive_after__label),
|
||||
valueLabel = { pluralsRes(R.plurals.unit__seconds__written, it, "v" to it) },
|
||||
min = 0,
|
||||
max = 300,
|
||||
stepIncrement = 10,
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.autoCleanSensitive isEqualTo true },
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.historyAutoCleanSensitiveEnabled isEqualTo true },
|
||||
visibleIf = { AndroidVersion.ATLEAST_API33_T },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.clipboard.limitHistorySize,
|
||||
prefs.clipboard.historySizeLimitEnabled,
|
||||
title = stringRes(R.string.pref__clipboard__limit_history_size__label),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.maxHistorySize,
|
||||
prefs.clipboard.historySizeLimit,
|
||||
title = stringRes(R.string.pref__clipboard__max_history_size__label),
|
||||
valueLabel = { pluralsRes(R.plurals.unit__items__written, it, "v" to it) },
|
||||
min = 5,
|
||||
max = 100,
|
||||
stepIncrement = 5,
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.limitHistorySize isEqualTo true },
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.historySizeLimitEnabled isEqualTo true },
|
||||
)
|
||||
|
||||
SwitchPreference(
|
||||
prefs.clipboard.historyHideOnPaste,
|
||||
title = stringRes(R.string.pref__clipboard__history_hide_on_paste__label),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true }
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.clipboard.clearPrimaryClipDeletesLastItem,
|
||||
title = stringRes(R.string.pref__clipboard__clear_primary_clip_deletes_last_item__label),
|
||||
summary = stringRes(R.string.pref__clipboard__clear_primary_clip_deletes_last_item__summary),
|
||||
prefs.clipboard.historyHideOnNextTextField,
|
||||
title = stringRes(R.string.pref__clipboard__history_hide_on_next_text_field__label),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true }
|
||||
)
|
||||
|
||||
SwitchPreference(
|
||||
prefs.clipboard.clearPrimaryClipAffectsHistoryIfUnpinned,
|
||||
title = stringRes(R.string.pref__clipboard__clear_primary_clip_affects_history_if_unpinned__label),
|
||||
summary = stringRes(R.string.pref__clipboard__clear_primary_clip_affects_history_if_unpinned__summary),
|
||||
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
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.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun DictionaryScreen() = FlorisScreen {
|
||||
|
||||
@@ -55,11 +55,8 @@ import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryDao
|
||||
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.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
|
||||
@@ -70,6 +67,9 @@ import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.rippleClickable
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
private val AllLanguagesLocale = FlorisLocale.from(language = "zz")
|
||||
private val UserDictionaryEntryToAdd = UserDictionaryEntry(id = 0, "", 255, null, null)
|
||||
|
||||
@@ -23,14 +23,13 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisInfoCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.compose.FlorisInfoCard
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
|
||||
@@ -23,7 +23,6 @@ import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
|
||||
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
@@ -31,6 +30,7 @@ import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.android.systemVibratorOrNull
|
||||
import org.florisboard.lib.android.vibrate
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
|
||||
@@ -29,13 +29,13 @@ import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
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 org.florisboard.lib.compose.stringRes
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
|
||||
@@ -47,20 +47,20 @@ import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.nlp.LanguagePackComponent
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
|
||||
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.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.Extension
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.FlorisTextButton
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.rippleClickable
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
|
||||
enum class LanguagePackManagerScreenAction(val id: String) {
|
||||
|
||||
@@ -45,8 +45,6 @@ import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.keyboard.LayoutType
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisWarningCard
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
@@ -55,8 +53,9 @@ 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
|
||||
import org.florisboard.lib.compose.FlorisWarningCard
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
internal val SubtypeSaver = Saver<MutableState<Subtype?>, String>(
|
||||
save = {
|
||||
|
||||
@@ -49,10 +49,10 @@ import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import org.florisboard.lib.compose.florisScrollbar
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
const val SelectLocaleScreenResultLanguageTag = "SelectLocaleScreen.languageTag"
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ShapeDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -74,18 +75,20 @@ import dev.patrickgold.florisboard.ime.nlp.han.HanShapeBasedLanguageProvider
|
||||
import dev.patrickgold.florisboard.ime.nlp.latin.LatinLanguageProvider
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisDropdownLikeButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisDropdownMenu
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefDropdown
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefDropdownMenuDefaults
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import org.florisboard.lib.compose.FlorisButtonBar
|
||||
import org.florisboard.lib.compose.FlorisDropdownLikeButton
|
||||
import org.florisboard.lib.compose.florisScrollbar
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
|
||||
private val SelectComponentName = ExtensionComponentName("00", "00")
|
||||
private val SelectNlpProviderId = SelectComponentName.toString()
|
||||
@@ -365,6 +368,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
onClick = {
|
||||
navController.navigate(Routes.Settings.SelectLocale)
|
||||
},
|
||||
appearance = JetPrefDropdownMenuDefaults.outlined(shape = ShapeDefaults.Small),
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_popup_mapping)) {
|
||||
@@ -374,16 +378,15 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
val popupMappingLabels = remember(popupMappings) {
|
||||
selectListValues + popupMappings.values.map { it.label }
|
||||
}
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
val selectedIndex = popupMappingIds.indexOf(popupMapping).coerceAtLeast(0)
|
||||
FlorisDropdownMenu(
|
||||
items = popupMappingLabels,
|
||||
JetPrefDropdown(
|
||||
options = popupMappingLabels,
|
||||
expanded = expanded,
|
||||
selectedIndex = selectedIndex,
|
||||
selectedOptionIndex = selectedIndex,
|
||||
isError = showSelectAsError && selectedIndex == 0,
|
||||
onSelectItem = { popupMapping = popupMappingIds[it] },
|
||||
onExpandRequest = { expanded = true },
|
||||
onDismissRequest = { expanded = false },
|
||||
onSelectOption = { popupMapping = popupMappingIds[it] },
|
||||
appearance = JetPrefDropdownMenuDefaults.outlined(shape = ShapeDefaults.Small),
|
||||
)
|
||||
}
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_characters_layout), LayoutType.CHARACTERS)
|
||||
@@ -404,19 +407,18 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
val nlpProviderMappingLabels = remember(nlpProviderMappings) {
|
||||
selectListValues + nlpProviderMappings.values.map { it }
|
||||
}
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
val selectedIndex = nlpProviderMappingIds.indexOf(nlpProviders.suggestion).coerceAtLeast(0)
|
||||
FlorisDropdownMenu(
|
||||
items = nlpProviderMappingLabels,
|
||||
JetPrefDropdown(
|
||||
options = nlpProviderMappingLabels,
|
||||
expanded = expanded,
|
||||
selectedIndex = selectedIndex,
|
||||
selectedOptionIndex = selectedIndex,
|
||||
isError = showSelectAsError && selectedIndex == 0,
|
||||
onSelectItem = { nlpProviders = SubtypeNlpProviderMap(
|
||||
onSelectOption = { nlpProviders = SubtypeNlpProviderMap(
|
||||
suggestion = nlpProviderMappingIds[it],
|
||||
spelling = nlpProviderMappingIds[it]
|
||||
) },
|
||||
onExpandRequest = { expanded = true },
|
||||
onDismissRequest = { expanded = false },
|
||||
appearance = JetPrefDropdownMenuDefaults.outlined(shape = ShapeDefaults.Small),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -432,15 +434,14 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
val composerNames = remember(composers) {
|
||||
selectListValues + composers.values.map { it.label }
|
||||
}
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
FlorisDropdownMenu(
|
||||
items = composerNames,
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
JetPrefDropdown(
|
||||
options = composerNames,
|
||||
expanded = expanded,
|
||||
selectedIndex = composerIds.indexOf(composer).coerceAtLeast(0),
|
||||
selectedOptionIndex = composerIds.indexOf(composer).coerceAtLeast(0),
|
||||
isError = showSelectAsError && composer == SelectComponentName,
|
||||
onSelectItem = { composer = composerIds[it] },
|
||||
onExpandRequest = { expanded = true },
|
||||
onDismissRequest = { expanded = false },
|
||||
onSelectOption = { composer = composerIds[it] },
|
||||
appearance = JetPrefDropdownMenuDefaults.outlined(shape = ShapeDefaults.Small),
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_currency_set)) {
|
||||
@@ -450,15 +451,14 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
val currencySetNames = remember(currencySets) {
|
||||
selectListValues + currencySets.values.map { it.label }
|
||||
}
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
FlorisDropdownMenu(
|
||||
items = currencySetNames,
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
JetPrefDropdown(
|
||||
options = currencySetNames,
|
||||
expanded = expanded,
|
||||
selectedIndex = currencySetIds.indexOf(currencySet).coerceAtLeast(0),
|
||||
selectedOptionIndex = currencySetIds.indexOf(currencySet).coerceAtLeast(0),
|
||||
isError = showSelectAsError && currencySet == SelectComponentName,
|
||||
onSelectItem = { currencySet = currencySetIds[it] },
|
||||
onExpandRequest = { expanded = true },
|
||||
onDismissRequest = { expanded = false },
|
||||
onSelectOption = { currencySet = currencySetIds[it] },
|
||||
appearance = JetPrefDropdownMenuDefaults.outlined(shape = ShapeDefaults.Small),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -556,16 +556,15 @@ private fun SubtypeLayoutDropdown(
|
||||
val layoutIds = remember(layouts) { SelectListKeys + layouts.keys.toList() }
|
||||
val layoutLabels = remember(layouts) { selectListValues + layouts.values.map { it.label } }
|
||||
val layoutId = remember(layoutMap) { layoutMap[layoutType] }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val expanded = remember { mutableStateOf(false) }
|
||||
val selectedIndex = layoutIds.indexOf(layoutId).coerceAtLeast(0)
|
||||
FlorisDropdownMenu(
|
||||
items = layoutLabels,
|
||||
JetPrefDropdown(
|
||||
options = layoutLabels,
|
||||
expanded = expanded,
|
||||
selectedIndex = selectedIndex,
|
||||
selectedOptionIndex = selectedIndex,
|
||||
isError = showSelectAsError && selectedIndex == 0,
|
||||
onSelectItem = { onLayoutMapChanged(layoutMap.copy(layoutType, layoutIds[it])!!) },
|
||||
onExpandRequest = { expanded = true },
|
||||
onDismissRequest = { expanded = false },
|
||||
onSelectOption = { onLayoutMapChanged(layoutMap.copy(layoutType = layoutType, componentName = layoutIds[it])!!) },
|
||||
appearance = JetPrefDropdownMenuDefaults.outlined(shape = ShapeDefaults.Small),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistoryHelper
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.pluralsRes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
@@ -44,6 +42,8 @@ import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.compose.pluralsRes
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
|
||||
@@ -24,10 +24,10 @@ import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun SmartbarScreen() = FlorisScreen {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.lib.compose
|
||||
package dev.patrickgold.florisboard.app.settings.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceSerializer
|
||||
@@ -77,13 +77,7 @@ import dev.patrickgold.florisboard.app.ext.FONTS
|
||||
import dev.patrickgold.florisboard.app.ext.IMAGES
|
||||
import dev.patrickgold.florisboard.lib.ValidationResult
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.DpSizeSaver
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisChip
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.Validation
|
||||
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionValidation
|
||||
import dev.patrickgold.florisboard.lib.rememberValidationResult
|
||||
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
|
||||
@@ -94,7 +88,14 @@ import dev.patrickgold.jetpref.material.ui.JetPrefDropdown
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
|
||||
import dev.patrickgold.jetpref.material.ui.rememberJetPrefColorPickerState
|
||||
import java.io.File
|
||||
import org.florisboard.lib.color.ColorPalette
|
||||
import org.florisboard.lib.compose.DpSizeSaver
|
||||
import org.florisboard.lib.compose.FlorisChip
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.FlorisTextButton
|
||||
import org.florisboard.lib.compose.florisVerticalScroll
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import org.florisboard.lib.kotlin.io.subDir
|
||||
import org.florisboard.lib.kotlin.toStringWithoutDotZero
|
||||
@@ -131,7 +132,6 @@ 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 java.io.File
|
||||
|
||||
internal val SnyggEmptyPropertyInfoForAdding = PropertyInfo(
|
||||
rule = SnyggEmptyRuleForAdding,
|
||||
|
||||
@@ -85,11 +85,7 @@ import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.NATIVE_NULLPTR
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisChip
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisHyperlinkText
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefDropdown
|
||||
@@ -98,6 +94,10 @@ import dev.patrickgold.jetpref.material.ui.JetPrefTextFieldDefaults
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.compose.FlorisChip
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import org.florisboard.lib.snygg.SnyggAnnotationRule
|
||||
import org.florisboard.lib.snygg.SnyggAttributes
|
||||
|
||||
@@ -22,11 +22,11 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
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
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
private val FineTuneContentPadding = PaddingValues(horizontal = 8.dp)
|
||||
|
||||
|
||||
@@ -80,16 +80,10 @@ 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.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
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionValidation
|
||||
import dev.patrickgold.florisboard.lib.rememberValidationResult
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
@@ -100,7 +94,13 @@ 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.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.florisVerticalScroll
|
||||
import org.florisboard.lib.compose.rippleClickable
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.io.subFile
|
||||
import org.florisboard.lib.snygg.SnyggAnnotationRule
|
||||
import org.florisboard.lib.snygg.SnyggElementRule
|
||||
|
||||
@@ -36,16 +36,16 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.themeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
|
||||
import org.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import org.florisboard.lib.compose.rippleClickable
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class ThemeManagerScreenAction(val id: String) {
|
||||
|
||||
@@ -37,7 +37,6 @@ import dev.patrickgold.florisboard.app.ext.ExtensionListScreenType
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
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
|
||||
@@ -47,6 +46,7 @@ import dev.patrickgold.jetpref.datastore.ui.LocalTimePickerPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
|
||||
import org.florisboard.lib.color.ColorMappings
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ThemeScreen() = FlorisScreen {
|
||||
@@ -60,7 +60,7 @@ fun ThemeScreen() = FlorisScreen {
|
||||
@Composable
|
||||
fun ThemeManager.getThemeLabel(id: ExtensionComponentName): String {
|
||||
val configs by indexedThemeConfigs.collectAsState()
|
||||
configs[id]?.let { return it.label }
|
||||
configs.first[id]?.let { return it.label }
|
||||
return id.toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,14 @@ 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.lib.compose.FlorisCanvasIcon
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisErrorCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisSimpleCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisWarningCard
|
||||
import dev.patrickgold.florisboard.lib.compose.observeAsState
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.launchActivity
|
||||
import org.florisboard.lib.android.AndroidSettings
|
||||
import org.florisboard.lib.compose.FlorisCanvasIcon
|
||||
import org.florisboard.lib.compose.FlorisErrorCard
|
||||
import org.florisboard.lib.compose.FlorisSimpleCard
|
||||
import org.florisboard.lib.compose.FlorisWarningCard
|
||||
import org.florisboard.lib.compose.observeAsState
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun SpellCheckerServiceSelector(florisSpellCheckerEnabled: MutableState<Boolean>) {
|
||||
|
||||
@@ -30,6 +30,8 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
@@ -37,19 +39,17 @@ 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
|
||||
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.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
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
|
||||
import org.florisboard.lib.compose.FlorisErrorCard
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
@@ -89,7 +89,7 @@ fun TypingScreen() = FlorisScreen {
|
||||
)
|
||||
ListPreference(
|
||||
prefs.suggestion.incognitoMode,
|
||||
icon = vectorResource(id = R.drawable.ic_incognito),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.ic_incognito),
|
||||
title = stringRes(R.string.pref__suggestion__incognito_mode__label),
|
||||
entries = enumDisplayEntriesOf(IncognitoMode::class),
|
||||
)
|
||||
|
||||
@@ -46,13 +46,8 @@ import dev.patrickgold.florisboard.app.FlorisPreferenceModel
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisBulletSpacer
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreenScope
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisStep
|
||||
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
|
||||
@@ -62,6 +57,11 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.compose.FlorisBulletSpacer
|
||||
import org.florisboard.lib.compose.FlorisStep
|
||||
import org.florisboard.lib.compose.FlorisStepLayout
|
||||
import org.florisboard.lib.compose.FlorisStepState
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun SetupScreen() = FlorisScreen {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.clipboard
|
||||
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
|
||||
|
||||
data class ClipboardHistory(val all: List<ClipboardItem>) {
|
||||
companion object {
|
||||
private const val RECENT_TIMESPAN_MS = 300_000 // 300 sec = 5 min
|
||||
|
||||
val EMPTY = ClipboardHistory(emptyList())
|
||||
}
|
||||
|
||||
private val now = System.currentTimeMillis()
|
||||
|
||||
val pinned = all.filter { it.isPinned }
|
||||
val unpinned = all.filter { !it.isPinned }
|
||||
val recent = unpinned.filter { (now - it.creationTimestampMs) < RECENT_TIMESPAN_MS }
|
||||
val other = unpinned.filter { (now - it.creationTimestampMs) >= RECENT_TIMESPAN_MS }
|
||||
}
|
||||
@@ -63,13 +63,14 @@ import androidx.compose.material.icons.filled.TextFields
|
||||
import androidx.compose.material.icons.filled.ToggleOff
|
||||
import androidx.compose.material.icons.filled.ToggleOn
|
||||
import androidx.compose.material.icons.filled.Videocam
|
||||
import androidx.compose.material.icons.outlined.ContentPaste
|
||||
import androidx.compose.material.icons.outlined.ContentPasteGo
|
||||
import androidx.compose.material.icons.outlined.Email
|
||||
import androidx.compose.material.icons.outlined.PushPin
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.mutableStateSetOf
|
||||
@@ -102,23 +103,22 @@ import dev.patrickgold.florisboard.ime.smartbar.VerticalExitTransition
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.LocalLocalizedDateTimeFormatter
|
||||
import dev.patrickgold.florisboard.lib.compose.autoMirrorForRtl
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
import dev.patrickgold.florisboard.lib.util.NetworkUtils
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import java.time.Instant
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.compose.LocalLocalizedDateTimeFormatter
|
||||
import org.florisboard.lib.compose.autoMirrorForRtl
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.compose.florisVerticalScroll
|
||||
import org.florisboard.lib.compose.rippleClickable
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.SnyggQueryAttributes
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
@@ -128,7 +128,6 @@ 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
|
||||
import java.time.Instant
|
||||
|
||||
private val ItemWidth = 200.dp
|
||||
private val DialogWidth = 240.dp
|
||||
@@ -148,23 +147,23 @@ fun ClipboardInputLayout(
|
||||
|
||||
val deviceLocked = androidKeyguardManager.let { it.isDeviceLocked || it.isKeyguardLocked }
|
||||
val historyEnabled by prefs.clipboard.historyEnabled.observeAsState()
|
||||
val unfilteredHistory by clipboardManager.history.observeAsNonNullState()
|
||||
|
||||
var isFilterRowShown by remember { mutableStateOf(false) }
|
||||
val activeFilterTypes = remember { mutableStateSetOf<ItemType>() }
|
||||
|
||||
val history = remember(unfilteredHistory, activeFilterTypes.toSet()) {
|
||||
val unfilteredHistory by clipboardManager.historyFlow.collectAsState()
|
||||
val filteredHistory = remember(unfilteredHistory, activeFilterTypes.toSet()) {
|
||||
if (activeFilterTypes.isEmpty()) {
|
||||
unfilteredHistory
|
||||
} else {
|
||||
unfilteredHistory.all
|
||||
.filter { activeFilterTypes.contains(it.type) }
|
||||
.let { ClipboardManager.ClipboardHistory(it) }
|
||||
.let { ClipboardHistory(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val gridState = rememberLazyStaggeredGridState()
|
||||
var popupItem by remember(history) { mutableStateOf<ClipboardItem?>(null) }
|
||||
var popupItem by remember(filteredHistory) { mutableStateOf<ClipboardItem?>(null) }
|
||||
var showClearAllHistory by remember { mutableStateOf(false) }
|
||||
|
||||
fun isPopupSurfaceActive() = popupItem != null || showClearAllHistory
|
||||
@@ -223,7 +222,7 @@ fun ClipboardInputLayout(
|
||||
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
|
||||
onClick = { showClearAllHistory = true },
|
||||
modifier = sizeModifier.autoMirrorForRtl(),
|
||||
enabled = !deviceLocked && historyEnabled && unfilteredHistory.all.isNotEmpty() && !isPopupSurfaceActive(),
|
||||
enabled = !deviceLocked && historyEnabled && filteredHistory.all.isNotEmpty() && !isPopupSurfaceActive(),
|
||||
) {
|
||||
SnyggIcon(
|
||||
imageVector = Icons.Default.DeleteSweep,
|
||||
@@ -371,7 +370,7 @@ fun ClipboardInputLayout(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
val historyAlpha by animateFloatAsState(targetValue = if (isPopupSurfaceActive()) 0.12f else 1f)
|
||||
val staggeredGridCells by prefs.clipboard.numHistoryGridColumns()
|
||||
val staggeredGridCells by prefs.clipboard.historyNumGridColumns()
|
||||
.observeAsTransformingState { numGridColumns ->
|
||||
if (numGridColumns == CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO) {
|
||||
StaggeredGridCells.Adaptive(160.dp)
|
||||
@@ -468,17 +467,17 @@ fun ClipboardInputLayout(
|
||||
columns = staggeredGridCells,
|
||||
) {
|
||||
clipboardItems(
|
||||
items = history.pinned,
|
||||
items = filteredHistory.pinned,
|
||||
key = "pinned-header",
|
||||
title = R.string.clipboard__group_pinned,
|
||||
)
|
||||
clipboardItems(
|
||||
items = history.recent,
|
||||
items = filteredHistory.recent,
|
||||
key = "recent-header",
|
||||
title = R.string.clipboard__group_recent,
|
||||
)
|
||||
clipboardItems(
|
||||
items = history.other,
|
||||
items = filteredHistory.other,
|
||||
key = "other-header",
|
||||
title = R.string.clipboard__group_other,
|
||||
)
|
||||
@@ -499,7 +498,9 @@ fun ClipboardInputLayout(
|
||||
SnyggColumn(modifier = Modifier.weight(0.5f)) {
|
||||
ClipItemView(
|
||||
elementName = FlorisImeUi.ClipboardItemPopup.elementName,
|
||||
modifier = Modifier.widthIn(max = ItemWidth),
|
||||
modifier = Modifier
|
||||
.widthIn(max = ItemWidth)
|
||||
.weight(1f, fill = false),
|
||||
item = popupItem!!,
|
||||
contentScrollInsteadOfClip = true,
|
||||
)
|
||||
@@ -532,11 +533,11 @@ fun ClipboardInputLayout(
|
||||
icon = Icons.Default.Delete,
|
||||
text = stringRes(R.string.clip__delete_item),
|
||||
) {
|
||||
clipboardManager.deleteClip(popupItem!!)
|
||||
clipboardManager.deleteClip(popupItem!!, onlyIfUnpinned = false)
|
||||
popupItem = null
|
||||
}
|
||||
PopupAction(
|
||||
icon = Icons.Outlined.ContentPaste,
|
||||
icon = Icons.Outlined.ContentPasteGo,
|
||||
text = stringRes(R.string.clip__paste_item),
|
||||
) {
|
||||
clipboardManager.pasteItem(popupItem!!)
|
||||
@@ -567,7 +568,13 @@ fun ClipboardInputLayout(
|
||||
) {
|
||||
SnyggText(
|
||||
elementName = FlorisImeUi.ClipboardClearAllDialogMessage.elementName,
|
||||
text = stringRes(R.string.clipboard__confirm_clear_history__message),
|
||||
text = stringRes(
|
||||
if (isFilterRowShown) {
|
||||
R.string.clipboard__confirm_clear_filtered_history__message
|
||||
} else {
|
||||
R.string.clipboard__confirm_clear_unfiltered_history__message
|
||||
}
|
||||
),
|
||||
)
|
||||
SnyggRow(FlorisImeUi.ClipboardClearAllDialogButtons.elementName) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
@@ -586,10 +593,9 @@ fun ClipboardInputLayout(
|
||||
elementName = FlorisImeUi.ClipboardClearAllDialogButton.elementName,
|
||||
attributes = mapOf("action" to "yes"),
|
||||
onClick = {
|
||||
clipboardManager.clearHistory()
|
||||
clipboardManager.clearExactHistory(filteredHistory.unpinned)
|
||||
context.showShortToastSync(R.string.clipboard__cleared_history)
|
||||
showClearAllHistory = false
|
||||
isFilterRowShown = false
|
||||
},
|
||||
) {
|
||||
SnyggText(
|
||||
@@ -670,7 +676,7 @@ fun ClipboardInputLayout(
|
||||
HistoryLockedView()
|
||||
} else {
|
||||
if (historyEnabled) {
|
||||
if (history.all.isNotEmpty() || !activeFilterTypes.isEmpty()) {
|
||||
if (filteredHistory.all.isNotEmpty() || !activeFilterTypes.isEmpty()) {
|
||||
HistoryMainView()
|
||||
} else {
|
||||
HistoryEmptyView()
|
||||
|
||||
@@ -18,8 +18,6 @@ package dev.patrickgold.florisboard.ime.clipboard
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.appContext
|
||||
import dev.patrickgold.florisboard.editorInstance
|
||||
@@ -41,8 +39,8 @@ import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.florisboard.lib.android.AndroidClipboardManager
|
||||
import org.florisboard.lib.android.AndroidClipboardManager_OnPrimaryClipChangedListener
|
||||
import org.florisboard.lib.android.clearPrimaryClipAnyApi
|
||||
import org.florisboard.lib.android.setOrClearPrimaryClip
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.kotlin.tryOrNull
|
||||
@@ -62,7 +60,6 @@ class ClipboardManager(
|
||||
companion object {
|
||||
// 1 minute
|
||||
private const val INTERVAL = 60 * 1000L
|
||||
private const val RECENT_TIMESPAN_MS = 300_000 // 300 sec = 5 min
|
||||
|
||||
/**
|
||||
* Taken from ClipboardDescription.java from the AOSP
|
||||
@@ -101,8 +98,10 @@ class ClipboardManager(
|
||||
private var clipHistoryDb: ClipboardHistoryDatabase? = null
|
||||
private val clipHistoryDao: ClipboardHistoryDao? get() = clipHistoryDb?.clipboardItemDao()
|
||||
|
||||
private val _history = MutableLiveData(ClipboardHistory.Empty)
|
||||
val history: LiveData<ClipboardHistory> get() = _history
|
||||
private val _historyFlow = MutableStateFlow(ClipboardHistory.EMPTY)
|
||||
val historyFlow = _historyFlow.asStateFlow()
|
||||
val currentHistory: ClipboardHistory
|
||||
get() = historyFlow.value
|
||||
|
||||
private val primaryClipLastFromCallbackGuard = Mutex(locked = false)
|
||||
private var primaryClipLastFromCallback: ClipData? = null
|
||||
@@ -119,7 +118,7 @@ class ClipboardManager(
|
||||
cleanUpJob = ioScope.launch {
|
||||
while (isActive) {
|
||||
delay(INTERVAL)
|
||||
enforceExpiryDate(history())
|
||||
enforceExpiryDate(currentHistory)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,20 +140,21 @@ class ClipboardManager(
|
||||
val itemsSorted = items.sortedByDescending { it.creationTimestampMs }
|
||||
val clipHistory = ClipboardHistory(itemsSorted)
|
||||
enforceHistoryLimit(clipHistory)
|
||||
_history.postValue(clipHistory)
|
||||
_historyFlow.value = clipHistory
|
||||
}
|
||||
|
||||
fun history(): ClipboardHistory = history.value!!
|
||||
|
||||
/**
|
||||
* Sets the current primary clip without updating the internal clipboard history.
|
||||
*/
|
||||
fun updatePrimaryClip(item: ClipboardItem?) {
|
||||
primaryClip = item
|
||||
if (prefs.clipboard.useInternalClipboard.get()) {
|
||||
// Purposely do not sync to system if disabled in prefs
|
||||
if (prefs.clipboard.syncToSystem.get()) {
|
||||
systemClipboardManager.setOrClearPrimaryClip(item?.toClipData(appContext))
|
||||
val syncBehavior = prefs.clipboard.syncToSystem.get()
|
||||
val clipData = item?.toClipData(appContext)
|
||||
if (clipData != null && syncBehavior.shouldSyncSet) {
|
||||
systemClipboardManager.setPrimaryClip(clipData)
|
||||
} else if (clipData == null && syncBehavior.shouldSyncClear) {
|
||||
systemClipboardManager.clearPrimaryClipAnyApi()
|
||||
}
|
||||
} else {
|
||||
systemClipboardManager.setOrClearPrimaryClip(item?.toClipData(appContext))
|
||||
@@ -165,7 +165,8 @@ class ClipboardManager(
|
||||
* Called by system clipboard when the system primary clip has changed.
|
||||
*/
|
||||
override fun onPrimaryClipChanged() {
|
||||
if (!prefs.clipboard.useInternalClipboard.get() || prefs.clipboard.syncToFloris.get()) {
|
||||
val syncBehavior = prefs.clipboard.syncToFloris.get()
|
||||
if (!prefs.clipboard.useInternalClipboard.get() || syncBehavior != ClipboardSyncBehavior.NO_EVENTS) {
|
||||
val systemPrimaryClip = systemClipboardManager.primaryClip
|
||||
ioScope.launch {
|
||||
val isDuplicate: Boolean
|
||||
@@ -173,7 +174,7 @@ class ClipboardManager(
|
||||
val a = primaryClipLastFromCallback?.getItemAt(0)
|
||||
val b = systemPrimaryClip?.getItemAt(0)
|
||||
isDuplicate = when {
|
||||
a === b || a == null && b == null -> true
|
||||
a === b -> true
|
||||
a == null || b == null -> false
|
||||
else -> a.text == b.text && a.uri == b.uri
|
||||
}
|
||||
@@ -184,12 +185,20 @@ class ClipboardManager(
|
||||
val internalPrimaryClip = primaryClip
|
||||
|
||||
if (systemPrimaryClip == null) {
|
||||
primaryClip = null
|
||||
if (syncBehavior.shouldSyncClear) {
|
||||
primaryClip = null
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (systemPrimaryClip.getItemAt(0).let { it.text == null && it.uri == null }) {
|
||||
primaryClip = null
|
||||
if (syncBehavior.shouldSyncClear) {
|
||||
primaryClip = null
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (!syncBehavior.shouldSyncSet) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -224,7 +233,7 @@ class ClipboardManager(
|
||||
*/
|
||||
private fun insertOrMoveBeginning(newItem: ClipboardItem) {
|
||||
if (prefs.clipboard.historyEnabled.get()) {
|
||||
val historyElement = history().all.firstOrNull { it.type == ItemType.TEXT && it.text == newItem.text }
|
||||
val historyElement = currentHistory.all.firstOrNull { it.type == ItemType.TEXT && it.text == newItem.text }
|
||||
if (historyElement != null) {
|
||||
moveToTheBeginning(
|
||||
oldItem = historyElement,
|
||||
@@ -241,9 +250,9 @@ class ClipboardManager(
|
||||
}
|
||||
|
||||
private fun enforceHistoryLimit(clipHistory: ClipboardHistory) {
|
||||
if (prefs.clipboard.limitHistorySize.get()) {
|
||||
if (prefs.clipboard.historySizeLimitEnabled.get()) {
|
||||
val nonPinnedItems = clipHistory.recent + clipHistory.other
|
||||
val nToRemove = nonPinnedItems.size - prefs.clipboard.maxHistorySize.get()
|
||||
val nToRemove = nonPinnedItems.size - prefs.clipboard.historySizeLimit.get()
|
||||
if (nToRemove > 0) {
|
||||
val itemsToRemove = nonPinnedItems.asReversed().filterIndexed { n, _ -> n < nToRemove }
|
||||
ioScope.launch {
|
||||
@@ -255,14 +264,14 @@ class ClipboardManager(
|
||||
|
||||
private fun enforceExpiryDate(clipHistory: ClipboardHistory) {
|
||||
val itemsToRemove = mutableSetOf<ClipboardItem>()
|
||||
if (prefs.clipboard.cleanUpOld.get()) {
|
||||
if (prefs.clipboard.historyAutoCleanOldEnabled.get()) {
|
||||
val nonPinnedItems = clipHistory.recent + clipHistory.other
|
||||
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.cleanUpAfter.get() * 60 * 1000)
|
||||
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.historyAutoCleanOldAfter.get() * 60 * 1000)
|
||||
itemsToRemove.addAll(nonPinnedItems.filter { it.creationTimestampMs < expiryTime })
|
||||
}
|
||||
if (prefs.clipboard.autoCleanSensitive.get()) {
|
||||
if (prefs.clipboard.historyAutoCleanSensitiveEnabled.get()) {
|
||||
val sensitiveData = clipHistory.all.filter { it.isSensitive }
|
||||
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.autoCleanSensitiveAfter.get() * 1000)
|
||||
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.historyAutoCleanSensitiveAfter.get() * 1000)
|
||||
itemsToRemove.addAll(sensitiveData.filter { it.creationTimestampMs < expiryTime })
|
||||
}
|
||||
if (itemsToRemove.isNotEmpty()) {
|
||||
@@ -274,7 +283,7 @@ class ClipboardManager(
|
||||
|
||||
private fun moveToTheBeginning(oldItem: ClipboardItem, newItem: ClipboardItem) {
|
||||
ioScope.launch {
|
||||
clipHistoryDao?.delete(oldItem)
|
||||
clipHistoryDao?.delete(oldItem.id)
|
||||
clipHistoryDao?.insert(newItem)
|
||||
}
|
||||
}
|
||||
@@ -286,12 +295,21 @@ class ClipboardManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun clearExactHistory(items: List<ClipboardItem>) {
|
||||
ioScope.launch {
|
||||
for (item in items) {
|
||||
item.close(appContext)
|
||||
}
|
||||
clipHistoryDao?.delete(items)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unpinned items from the clipboard history
|
||||
*/
|
||||
fun clearHistory() {
|
||||
ioScope.launch {
|
||||
for (item in history().all) {
|
||||
for (item in currentHistory.all) {
|
||||
item.close(appContext)
|
||||
}
|
||||
clipHistoryDao?.deleteAllUnpinned()
|
||||
@@ -303,7 +321,7 @@ class ClipboardManager(
|
||||
*/
|
||||
fun clearFullHistory() {
|
||||
ioScope.launch {
|
||||
for (item in history().all) {
|
||||
for (item in currentHistory.all) {
|
||||
item.close(appContext)
|
||||
}
|
||||
clipHistoryDao?.deleteAll()
|
||||
@@ -318,18 +336,22 @@ class ClipboardManager(
|
||||
*/
|
||||
fun restoreHistory(items: List<ClipboardItem>) {
|
||||
ioScope.launch {
|
||||
val currentHistory = this@ClipboardManager.history().all
|
||||
val currentHistory = currentHistory.all
|
||||
for (item in items) {
|
||||
if (!currentHistory.map { it.copy(id = 0) }.contains(item.copy(id = 0))) {
|
||||
this@ClipboardManager.insertClip(item.copy(id = 0))
|
||||
insertClip(item.copy(id = 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteClip(item: ClipboardItem) {
|
||||
fun deleteClip(item: ClipboardItem, onlyIfUnpinned: Boolean) {
|
||||
ioScope.launch {
|
||||
clipHistoryDao?.delete(item)
|
||||
if (onlyIfUnpinned) {
|
||||
clipHistoryDao?.deleteIfUnpinned(item.id)
|
||||
} else {
|
||||
clipHistoryDao?.delete(item.id)
|
||||
}
|
||||
tryOrNull {
|
||||
val uri = item.uri
|
||||
if (uri != null) {
|
||||
@@ -382,16 +404,4 @@ class ClipboardManager(
|
||||
systemClipboardManager.removePrimaryClipChangedListener(this)
|
||||
cleanUpJob.cancel()
|
||||
}
|
||||
|
||||
class ClipboardHistory(val all: List<ClipboardItem>) {
|
||||
companion object {
|
||||
val Empty = ClipboardHistory(emptyList())
|
||||
}
|
||||
|
||||
private val now = System.currentTimeMillis()
|
||||
|
||||
val pinned = all.filter { it.isPinned }
|
||||
val recent = all.filter { !it.isPinned && (now - it.creationTimestampMs < RECENT_TIMESPAN_MS) }
|
||||
val other = all.filter { !it.isPinned && (now - it.creationTimestampMs >= RECENT_TIMESPAN_MS) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.clipboard
|
||||
|
||||
enum class ClipboardSyncBehavior(val shouldSyncSet: Boolean, val shouldSyncClear: Boolean) {
|
||||
NO_EVENTS(shouldSyncSet = false, shouldSyncClear = false),
|
||||
ONLY_CLEAR_EVENTS(shouldSyncSet = false, shouldSyncClear = true),
|
||||
ONLY_SET_EVENTS(shouldSyncSet = true, shouldSyncClear = false),
|
||||
ALL_EVENTS(shouldSyncSet = true, shouldSyncClear = true);
|
||||
}
|
||||
@@ -49,13 +49,13 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.android.AndroidClipboardManager
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.mimeTypeFilterOf
|
||||
|
||||
class FlorisCopyToClipboardActivity : ComponentActivity() {
|
||||
@@ -136,7 +136,11 @@ class FlorisCopyToClipboardActivity : ComponentActivity() {
|
||||
@Composable
|
||||
private fun Content() {
|
||||
val prefs by FlorisPreferenceStore
|
||||
ProvideLocalizedResources(this, forceLayoutDirection = LayoutDirection.Ltr) {
|
||||
ProvideLocalizedResources(
|
||||
resourcesContext = this,
|
||||
appName = R.string.app_name,
|
||||
forceLayoutDirection = LayoutDirection.Ltr,
|
||||
) {
|
||||
val theme by prefs.other.settingsTheme.observeAsState()
|
||||
FlorisAppTheme(theme) {
|
||||
BottomSheet {
|
||||
|
||||
@@ -55,6 +55,7 @@ import org.florisboard.lib.android.UriSerializer
|
||||
import org.florisboard.lib.android.query
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.kotlin.tryOrNull
|
||||
import androidx.core.net.toUri
|
||||
|
||||
private const val CLIPBOARD_HISTORY_TABLE = "clipboard_history"
|
||||
private const val CLIPBOARD_FILES_TABLE = "clipboard_files"
|
||||
@@ -89,7 +90,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
val uri: Uri?,
|
||||
val creationTimestampMs: Long,
|
||||
val isPinned: Boolean,
|
||||
val mimeTypes: Array<String>,
|
||||
val mimeTypes: List<String>,
|
||||
@EncodeDefault
|
||||
@ColumnInfo(name = "is_sensitive", defaultValue = "0")
|
||||
val isSensitive: Boolean = false,
|
||||
@@ -101,7 +102,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
/**
|
||||
* So that every item doesn't have to allocate its own array.
|
||||
*/
|
||||
private val TEXT_PLAIN = arrayOf("text/plain")
|
||||
private val TEXT_PLAIN = listOf("text/plain")
|
||||
private val MEDIA_PROJECTION = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||
|
||||
const val FLORIS_CLIP_LABEL = "florisboard/clipboard_item"
|
||||
@@ -177,7 +178,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
val mimeTypes = when (type) {
|
||||
ItemType.TEXT -> TEXT_PLAIN
|
||||
ItemType.IMAGE, ItemType.VIDEO -> {
|
||||
Array(data.description.mimeTypeCount) { data.description.getMimeType(it) }
|
||||
List(data.description.mimeTypeCount) { data.description.getMimeType(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,34 +231,6 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ClipboardItem
|
||||
|
||||
if (id != other.id) return false
|
||||
if (type != other.type) return false
|
||||
if (text != other.text) return false
|
||||
if (uri != other.uri) return false
|
||||
if (creationTimestampMs != other.creationTimestampMs) return false
|
||||
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
|
||||
if (isSensitive != other.isSensitive) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
result = 31 * result + (text?.hashCode() ?: 0)
|
||||
result = 31 * result + (uri?.hashCode() ?: 0)
|
||||
result = 31 * result + creationTimestampMs.hashCode()
|
||||
result = 31 * result + mimeTypes.contentHashCode()
|
||||
result = 31 * result + isSensitive.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
fun stringRepresentation(): String {
|
||||
return when {
|
||||
text != null -> text
|
||||
@@ -270,7 +243,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
|
||||
class Converters {
|
||||
@TypeConverter
|
||||
fun uriFromString(value: String?): Uri? {
|
||||
return Uri.parse(value)
|
||||
return value?.toUri()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@@ -293,13 +266,13 @@ class Converters {
|
||||
* DOES NOT USE A GENERALIZED FORMAT.
|
||||
*/
|
||||
@TypeConverter
|
||||
fun mimeTypesToString(mimeTypes: Array<String>): String {
|
||||
fun mimeTypesToString(mimeTypes: List<String>): String {
|
||||
return mimeTypes.joinToString(",")
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToMimeTypes(value: String): Array<String> {
|
||||
return value.split(",").toTypedArray()
|
||||
fun stringToMimeTypes(value: String): List<String> {
|
||||
return value.split(",")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,12 +293,12 @@ interface ClipboardHistoryDao {
|
||||
@Update
|
||||
fun update(items: List<ClipboardItem>)
|
||||
|
||||
@Delete
|
||||
fun delete(item: ClipboardItem)
|
||||
|
||||
@Query("DELETE FROM $CLIPBOARD_HISTORY_TABLE WHERE ${BaseColumns._ID} = :id")
|
||||
fun delete(id: Long)
|
||||
|
||||
@Query("DELETE FROM $CLIPBOARD_HISTORY_TABLE WHERE ${BaseColumns._ID} = :id AND not isPinned ")
|
||||
fun deleteIfUnpinned(id: Long)
|
||||
|
||||
@Delete
|
||||
fun delete(items: List<ClipboardItem>)
|
||||
|
||||
@@ -382,30 +355,8 @@ data class ClipboardFileInfo(
|
||||
@ColumnInfo(name=OpenableColumns.DISPLAY_NAME) val displayName: String,
|
||||
@ColumnInfo(name=OpenableColumns.SIZE) val size: Long,
|
||||
@ColumnInfo(name=Media.ORIENTATION) val orientation: Int,
|
||||
val mimeTypes: Array<String>,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ClipboardFileInfo
|
||||
|
||||
if (id != other.id) return false
|
||||
if (displayName != other.displayName) return false
|
||||
if (size != other.size) return false
|
||||
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + displayName.hashCode()
|
||||
result = 31 * result + size.hashCode()
|
||||
result = 31 * result + mimeTypes.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
val mimeTypes: List<String>,
|
||||
)
|
||||
|
||||
@Dao
|
||||
interface ClipboardFilesDao {
|
||||
|
||||
@@ -35,6 +35,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.kotlin.tryOrNull
|
||||
import androidx.core.net.toUri
|
||||
|
||||
/**
|
||||
* Allows apps to access images and videos on the clipboard.
|
||||
@@ -49,8 +50,8 @@ class ClipboardMediaProvider : ContentProvider() {
|
||||
|
||||
companion object {
|
||||
const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.provider.clipboard"
|
||||
val IMAGE_CLIPS_URI: Uri = Uri.parse("content://$AUTHORITY/clips/images")
|
||||
val VIDEO_CLIPS_URI: Uri = Uri.parse("content://$AUTHORITY/clips/videos")
|
||||
val IMAGE_CLIPS_URI: Uri = "content://$AUTHORITY/clips/images".toUri()
|
||||
val VIDEO_CLIPS_URI: Uri = "content://$AUTHORITY/clips/videos".toUri()
|
||||
|
||||
private const val IMAGE_CLIP_ITEM = 0
|
||||
private const val IMAGE_CLIPS_TABLE = 1
|
||||
@@ -118,7 +119,7 @@ class ClipboardMediaProvider : ContentProvider() {
|
||||
override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? {
|
||||
return when (Matcher.match(uri)) {
|
||||
IMAGE_CLIP_ITEM, VIDEO_CLIP_ITEM -> {
|
||||
cachedFileInfos.getOrDefault(ContentUris.parseId(uri), null)?.mimeTypes
|
||||
cachedFileInfos.getOrDefault(ContentUris.parseId(uri), null)?.mimeTypes?.toTypedArray()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@@ -152,7 +153,7 @@ class ClipboardMediaProvider : ContentProvider() {
|
||||
}
|
||||
val id = ClipboardFileStorage.cloneUri(context!!, mediaUri)
|
||||
val size = ClipboardFileStorage.getFileForId(context!!, id).length()
|
||||
val mimeTypes = values.getAsString(Columns.MimeTypes).split(",").toTypedArray()
|
||||
val mimeTypes = values.getAsString(Columns.MimeTypes).split(",")
|
||||
val displayName = values.getAsString(OpenableColumns.DISPLAY_NAME)
|
||||
val fileInfo = ClipboardFileInfo(id, displayName, size, rotation, mimeTypes)
|
||||
cachedFileInfos[id] = fileInfo
|
||||
|
||||
@@ -35,8 +35,8 @@ 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.stringRes
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggListItem
|
||||
|
||||
@@ -40,6 +40,16 @@ import org.florisboard.lib.kotlin.guardedByLock
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
enum class OperationUnit {
|
||||
CHARACTERS,
|
||||
WORDS;
|
||||
}
|
||||
|
||||
enum class OperationScope {
|
||||
BEFORE_CURSOR,
|
||||
AFTER_CURSOR;
|
||||
}
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
abstract class AbstractEditorInstance(context: Context) {
|
||||
companion object {
|
||||
@@ -428,41 +438,69 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun deleteBeforeCursor(type: TextType, n: Int): Boolean {
|
||||
protected suspend fun deleteAroundCursor(unit: OperationUnit, scope: OperationScope, n: Int = 0): Boolean {
|
||||
val ic = currentInputConnection()
|
||||
if (ic == null || n < 1) return false
|
||||
val content = activeContent
|
||||
// Cannot perform below check due to editors which lie about their correct selection
|
||||
//if (content.selection.isValid && content.selection.start == 0) return true
|
||||
val oldTextBeforeSelection = content.textBeforeSelection
|
||||
return (if (activeInfo.isRawInputEditor || oldTextBeforeSelection.isEmpty()) {
|
||||
val scopeText = when (scope) {
|
||||
OperationScope.BEFORE_CURSOR -> content.textBeforeSelection
|
||||
OperationScope.AFTER_CURSOR -> content.textAfterSelection
|
||||
}
|
||||
return (if (activeInfo.isRawInputEditor || scopeText.isEmpty()) {
|
||||
// If editor is rich and text before selection is empty we seem to have an invalid state here, so we fall
|
||||
// back to emulating a hardware backspace.
|
||||
when (type) {
|
||||
TextType.CHARACTERS -> sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, count = n)
|
||||
TextType.WORDS -> sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, meta(ctrl = true), count = n)
|
||||
// back to emulating a hardware backspace/forward delete.
|
||||
val keyEventCode = when (scope) {
|
||||
OperationScope.BEFORE_CURSOR -> KeyEvent.KEYCODE_DEL
|
||||
OperationScope.AFTER_CURSOR -> KeyEvent.KEYCODE_FORWARD_DEL
|
||||
}
|
||||
val metaState = when (unit) {
|
||||
OperationUnit.CHARACTERS -> meta()
|
||||
OperationUnit.WORDS -> meta(ctrl = true)
|
||||
}
|
||||
sendDownUpKeyEvent(keyEventCode, metaState, count = n)
|
||||
} else {
|
||||
runBlocking {
|
||||
val locale = subtypeManager.activeSubtype.primaryLocale
|
||||
val length = when (type) {
|
||||
TextType.CHARACTERS -> breakIterators.measureLastUChars(oldTextBeforeSelection, n, locale)
|
||||
TextType.WORDS -> breakIterators.measureLastUWords(oldTextBeforeSelection, n, locale)
|
||||
val locale = subtypeManager.activeSubtype.primaryLocale
|
||||
when (scope) {
|
||||
OperationScope.BEFORE_CURSOR -> {
|
||||
val length = when (unit) {
|
||||
OperationUnit.CHARACTERS -> breakIterators.measureLastUChars(scopeText, n, locale)
|
||||
OperationUnit.WORDS -> breakIterators.measureLastUWords(scopeText, n, locale)
|
||||
}
|
||||
val selection = content.selection
|
||||
val newSelection = selection.translatedBy(-length)
|
||||
val newContent = content.generateCopy(
|
||||
selection = newSelection,
|
||||
textBeforeSelection = scopeText.dropLast(length),
|
||||
)
|
||||
expectedContentQueue.push(newContent)
|
||||
ic.beginBatchEdit()
|
||||
ic.finishComposingText()
|
||||
ic.deleteSurroundingText(length, 0)
|
||||
ic.setComposingRegion(newContent.composing)
|
||||
ic.endBatchEdit()
|
||||
}
|
||||
OperationScope.AFTER_CURSOR -> {
|
||||
val length = when (unit) {
|
||||
OperationUnit.CHARACTERS -> breakIterators.measureUChars(scopeText, n, locale)
|
||||
OperationUnit.WORDS -> breakIterators.measureUWords(scopeText, n, locale)
|
||||
}
|
||||
val selection = content.selection
|
||||
val newSelection = selection.translatedBy(length)
|
||||
val newContent = content.generateCopy(
|
||||
selection = newSelection,
|
||||
textAfterSelection = scopeText.drop(length),
|
||||
)
|
||||
expectedContentQueue.push(newContent)
|
||||
ic.beginBatchEdit()
|
||||
ic.finishComposingText()
|
||||
ic.deleteSurroundingText(0, length)
|
||||
ic.setComposingRegion(newContent.composing)
|
||||
ic.endBatchEdit()
|
||||
}
|
||||
val selection = content.selection
|
||||
val newSelection = selection.translatedBy(-length)
|
||||
val newContent = content.generateCopy(
|
||||
selection = newSelection,
|
||||
textBeforeSelection = oldTextBeforeSelection.dropLast(length),
|
||||
)
|
||||
expectedContentQueue.push(newContent)
|
||||
ic.beginBatchEdit()
|
||||
ic.finishComposingText()
|
||||
ic.deleteSurroundingText(length, 0)
|
||||
ic.setComposingRegion(newContent.composing)
|
||||
ic.endBatchEdit()
|
||||
true
|
||||
}
|
||||
true
|
||||
}).also {
|
||||
deleteMoveLastCommitPosition()
|
||||
}
|
||||
@@ -623,11 +661,6 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
return true
|
||||
}
|
||||
|
||||
protected enum class TextType {
|
||||
CHARACTERS,
|
||||
WORDS;
|
||||
}
|
||||
|
||||
private class ExpectedContentQueue {
|
||||
private val list = guardedByLock { mutableListOf<EditorContent>() }
|
||||
|
||||
|
||||
@@ -91,6 +91,9 @@ data class EditorContent(
|
||||
val currentWordText: String
|
||||
get() = if (localCurrentWord.isValid) text.safeSubstring(localCurrentWord.start, localCurrentWord.end) else ""
|
||||
|
||||
val safeEditorBounds: EditorRange
|
||||
get() = if (offset >= 0) EditorRange(0, offset + text.length) else EditorRange(0, 0)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Default editor content which indicates an unspecified content. This is used for raw editors or if there is
|
||||
|
||||
@@ -26,6 +26,7 @@ import dev.patrickgold.florisboard.FlorisImeService
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.appContext
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.ime.ImeUiMode
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
@@ -42,7 +43,6 @@ import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
|
||||
class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
@@ -318,7 +318,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
if (!file.exists()) return false
|
||||
val inputContentInfo = InputContentInfoCompat(
|
||||
item.uri,
|
||||
ClipDescription("clipboard media file", mimeTypes),
|
||||
ClipDescription("clipboard media file", mimeTypes.toTypedArray()),
|
||||
null,
|
||||
)
|
||||
val ic = currentInputConnection() ?: return false
|
||||
@@ -326,6 +326,10 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
val flags = InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
|
||||
InputConnectionCompat.commitContent(ic, activeInfo.base, inputContentInfo, flags, null)
|
||||
}
|
||||
}.also {
|
||||
if (prefs.clipboard.historyHideOnPaste.get()) {
|
||||
keyboardManager.activeState.imeUiMode = ImeUiMode.TEXT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,49 +340,75 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun deleteBackwards(): Boolean {
|
||||
fun deleteBackwards(unit: OperationUnit): Boolean {
|
||||
val content = activeContent
|
||||
if (phantomSpace.isActive && content.currentWord.isValid && prefs.glide.immediateBackspaceDeletesWord.get()) {
|
||||
return deleteWordBackwards()
|
||||
if (unit == OperationUnit.CHARACTERS) {
|
||||
if (phantomSpace.isActive && content.currentWord.isValid && prefs.glide.immediateBackspaceDeletesWord.get()) {
|
||||
return deleteBackwards(OperationUnit.WORDS)
|
||||
}
|
||||
}
|
||||
autoSpace.setInactive()
|
||||
phantomSpace.setInactive()
|
||||
return if (content.selection.isSelectionMode) {
|
||||
commitText("")
|
||||
} else {
|
||||
deleteBeforeCursor(TextType.CHARACTERS, 1)
|
||||
} else runBlocking {
|
||||
deleteAroundCursor(unit, OperationScope.BEFORE_CURSOR, n = 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a backward delete on this editor's text. If a text selection is active, all
|
||||
* characters inside this selection will be removed, else only the left-most word from
|
||||
* characters inside this selection will be removed, else only the left-most character from
|
||||
* the cursor's position.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun deleteWordBackwards(): Boolean {
|
||||
fun deleteForwards(unit: OperationUnit): Boolean {
|
||||
val content = activeContent
|
||||
autoSpace.setInactive()
|
||||
phantomSpace.setInactive()
|
||||
return if (activeContent.selection.isSelectionMode) {
|
||||
return if (content.selection.isSelectionMode) {
|
||||
commitText("")
|
||||
} else {
|
||||
deleteBeforeCursor(TextType.WORDS, 1)
|
||||
} else runBlocking {
|
||||
deleteAroundCursor(unit, OperationScope.AFTER_CURSOR, n = 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun selectionSetNWordsLeft(n: Int): Boolean {
|
||||
fun setSelectionSurrounding(n: Int, unit: OperationUnit, scope: OperationScope): Boolean {
|
||||
autoSpace.setInactive()
|
||||
phantomSpace.setInactive()
|
||||
val content = activeContent
|
||||
val selection = content.selection
|
||||
val safeEditorBounds = content.safeEditorBounds
|
||||
if (selection.isNotValid) return false
|
||||
if (n <= 0) {
|
||||
return setSelection(selection.end, selection.end)
|
||||
when (scope) {
|
||||
OperationScope.BEFORE_CURSOR -> {
|
||||
if (n <= 0) {
|
||||
return setSelection(selection.end, selection.end)
|
||||
}
|
||||
val textToAnalyze = content.text.substring(0, content.localSelection.end)
|
||||
val length = runBlocking {
|
||||
when (unit) {
|
||||
OperationUnit.CHARACTERS -> breakIterators.measureLastUChars(textToAnalyze, n)
|
||||
OperationUnit.WORDS -> breakIterators.measureLastUWords(textToAnalyze, n)
|
||||
}
|
||||
}
|
||||
return setSelection((selection.end - length).coerceAtLeast(safeEditorBounds.start), selection.end)
|
||||
}
|
||||
OperationScope.AFTER_CURSOR -> {
|
||||
if (n <= 0) {
|
||||
return setSelection(selection.start, selection.start)
|
||||
}
|
||||
val textToAnalyze = content.text.substring(content.localSelection.start)
|
||||
val length = runBlocking {
|
||||
when (unit) {
|
||||
OperationUnit.CHARACTERS -> breakIterators.measureUChars(textToAnalyze, n)
|
||||
OperationUnit.WORDS -> breakIterators.measureUWords(textToAnalyze, n)
|
||||
}
|
||||
}
|
||||
return setSelection(selection.start, (selection.start + length).coerceAtMost(safeEditorBounds.end))
|
||||
}
|
||||
}
|
||||
val textToAnalyze = content.text.substring(0, content.localSelection.end)
|
||||
val length = runBlocking { breakIterators.measureLastUWords(textToAnalyze, n) }
|
||||
return setSelection((selection.end - length).coerceAtLeast(0), selection.end)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,7 +426,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
} else {
|
||||
appContext.showShortToastSync("Failed to retrieve selected text requested to cut: Eiter selection state is invalid or an error occurred within the input connection.")
|
||||
}
|
||||
return deleteBackwards()
|
||||
return deleteBackwards(OperationUnit.CHARACTERS)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ import androidx.compose.material.icons.automirrored.outlined.Backspace
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.ContentCut
|
||||
import androidx.compose.material.icons.filled.ContentPaste
|
||||
import androidx.compose.material.icons.filled.ContentPasteGo
|
||||
import androidx.compose.material.icons.filled.DeleteSweep
|
||||
import androidx.compose.material.icons.filled.Done
|
||||
import androidx.compose.material.icons.filled.FontDownload
|
||||
@@ -55,7 +55,8 @@ import dev.patrickgold.florisboard.ime.input.InputShiftState
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyType
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import dev.patrickgold.florisboard.lib.compose.vectorResource
|
||||
import org.florisboard.lib.compose.icons.ForwardDelete
|
||||
|
||||
interface ComputingEvaluator {
|
||||
val version: Int
|
||||
@@ -197,7 +198,7 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
Icons.Default.ContentCut
|
||||
}
|
||||
KeyCode.CLIPBOARD_PASTE -> {
|
||||
Icons.Default.ContentPaste
|
||||
Icons.Default.ContentPasteGo
|
||||
}
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> {
|
||||
Icons.Default.SelectAll
|
||||
@@ -234,6 +235,9 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode.FORWARD_DELETE -> {
|
||||
Icons.AutoMirrored.Default.ForwardDelete
|
||||
}
|
||||
KeyCode.IME_UI_MODE_MEDIA -> {
|
||||
Icons.Default.SentimentSatisfiedAlt
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import dev.patrickgold.florisboard.ime.editor.EditorContent
|
||||
import dev.patrickgold.florisboard.ime.editor.FlorisEditorInfo
|
||||
import dev.patrickgold.florisboard.ime.editor.ImeOptions
|
||||
import dev.patrickgold.florisboard.ime.editor.InputAttributes
|
||||
import dev.patrickgold.florisboard.ime.editor.OperationUnit
|
||||
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
|
||||
import dev.patrickgold.florisboard.ime.input.InputEventDispatcher
|
||||
import dev.patrickgold.florisboard.ime.input.InputKeyEventReceiver
|
||||
@@ -75,7 +76,6 @@ import kotlinx.coroutines.sync.withLock
|
||||
import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.showLongToast
|
||||
import org.florisboard.lib.android.showLongToastSync
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.showShortToastSync
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.kotlin.collectIn
|
||||
@@ -417,27 +417,30 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
/**
|
||||
* Handles a [KeyCode.DELETE] event.
|
||||
*/
|
||||
private fun handleDelete() {
|
||||
private fun handleBackwardDelete(unit: OperationUnit) {
|
||||
if (inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
|
||||
return handleForwardDelete(unit)
|
||||
}
|
||||
activeState.batchEdit {
|
||||
it.isManualSelectionMode = false
|
||||
it.isManualSelectionModeStart = false
|
||||
it.isManualSelectionModeEnd = false
|
||||
}
|
||||
revertPreviouslyAcceptedCandidate()
|
||||
editorInstance.deleteBackwards()
|
||||
editorInstance.deleteBackwards(unit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.DELETE_WORD] event.
|
||||
* Handles a [KeyCode.FORWARD_DELETE] event.
|
||||
*/
|
||||
private fun handleDeleteWord() {
|
||||
private fun handleForwardDelete(unit: OperationUnit) {
|
||||
activeState.batchEdit {
|
||||
it.isManualSelectionMode = false
|
||||
it.isManualSelectionModeStart = false
|
||||
it.isManualSelectionModeEnd = false
|
||||
}
|
||||
revertPreviouslyAcceptedCandidate()
|
||||
editorInstance.deleteWordBackwards()
|
||||
editorInstance.deleteForwards(unit)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,7 +569,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
if (inputEventDispatcher.isConsecutiveUp(data)) {
|
||||
val text = editorInstance.run { activeContent.getTextBeforeCursor(2) }
|
||||
if (text.length == 2 && DoubleSpacePeriodMatcher.matches(text)) {
|
||||
editorInstance.deleteBackwards()
|
||||
editorInstance.deleteBackwards(OperationUnit.CHARACTERS)
|
||||
editorInstance.commitText(". ")
|
||||
return
|
||||
}
|
||||
@@ -714,8 +717,8 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
KeyCode.CLIPBOARD_CLEAR_HISTORY -> clipboardManager.clearHistory()
|
||||
KeyCode.CLIPBOARD_CLEAR_FULL_HISTORY -> clipboardManager.clearFullHistory()
|
||||
KeyCode.CLIPBOARD_CLEAR_PRIMARY_CLIP -> {
|
||||
if (prefs.clipboard.clearPrimaryClipDeletesLastItem.get()) {
|
||||
clipboardManager.primaryClip?.let { clipboardManager.deleteClip(it) }
|
||||
if (prefs.clipboard.clearPrimaryClipAffectsHistoryIfUnpinned.get()) {
|
||||
clipboardManager.primaryClip?.let { clipboardManager.deleteClip(it, onlyIfUnpinned = true) }
|
||||
}
|
||||
clipboardManager.updatePrimaryClip(null)
|
||||
appContext.showShortToastSync(R.string.clipboard__cleared_primary_clip)
|
||||
@@ -729,9 +732,11 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
prefs.keyboard.oneHandedMode.set(OneHandedMode.END)
|
||||
toggleOneHandedMode()
|
||||
}
|
||||
KeyCode.DELETE -> handleDelete()
|
||||
KeyCode.DELETE_WORD -> handleDeleteWord()
|
||||
KeyCode.DELETE -> handleBackwardDelete(OperationUnit.CHARACTERS)
|
||||
KeyCode.DELETE_WORD -> handleBackwardDelete(OperationUnit.WORDS)
|
||||
KeyCode.ENTER -> handleEnter()
|
||||
KeyCode.FORWARD_DELETE -> handleForwardDelete(OperationUnit.CHARACTERS)
|
||||
KeyCode.FORWARD_DELETE_WORD -> handleForwardDelete(OperationUnit.WORDS)
|
||||
KeyCode.IME_SHOW_UI -> FlorisImeService.showUi()
|
||||
KeyCode.IME_HIDE_UI -> FlorisImeService.hideUi()
|
||||
KeyCode.IME_PREV_SUBTYPE -> subtypeManager.switchToPrevSubtype()
|
||||
@@ -866,6 +871,20 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
handleEnter()
|
||||
return true
|
||||
}
|
||||
KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT -> {
|
||||
inputEventDispatcher.sendDown(TextKeyData.SHIFT)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
fun onHardwareKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT -> {
|
||||
inputEventDispatcher.sendUp(TextKeyData.SHIFT)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
@@ -52,6 +54,7 @@ import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
@@ -60,6 +63,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -87,14 +91,14 @@ import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
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.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.compose.florisScrollbar
|
||||
import org.florisboard.lib.compose.header
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
@@ -169,7 +173,6 @@ fun EmojiPaletteView(
|
||||
}
|
||||
}
|
||||
var recentlyUsedVersion by remember { mutableIntStateOf(0) }
|
||||
val lazyListState = rememberLazyGridState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@Composable
|
||||
@@ -204,22 +207,111 @@ fun EmojiPaletteView(
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
fun calculatePageNumbers(): Int {
|
||||
return when {
|
||||
!emojiHistoryEnabled -> EmojiCategoryValues.size - 1
|
||||
else -> EmojiCategoryValues.size
|
||||
}
|
||||
}
|
||||
|
||||
fun pageNumberToCategory(pageNumber: Int): EmojiCategory {
|
||||
return when {
|
||||
!emojiHistoryEnabled -> EmojiCategoryValues[pageNumber + 1]
|
||||
else -> EmojiCategoryValues[pageNumber]
|
||||
}
|
||||
}
|
||||
|
||||
fun categoryToPageNumber(category: EmojiCategory): Int {
|
||||
return if (emojiHistoryEnabled) {
|
||||
EmojiCategoryValues.indexOf(category)
|
||||
} else {
|
||||
EmojiCategoryValues.indexOf(category) - 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun EmojiCategoriesTabRow(
|
||||
activeCategory: EmojiCategory,
|
||||
onCategoryChange: (EmojiCategory) -> Unit,
|
||||
) {
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
val selectedTabIndex = categoryToPageNumber(activeCategory)
|
||||
val style = rememberSnyggThemeQuery(FlorisImeUi.MediaEmojiTab.elementName)
|
||||
TabRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(FlorisImeSizing.smartbarHeight),
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = style.foreground(),
|
||||
indicator = { tabPositions ->
|
||||
val style = rememberSnyggThemeQuery(
|
||||
elementName = FlorisImeUi.MediaEmojiTab.elementName,
|
||||
selector = SnyggSelector.FOCUS,
|
||||
)
|
||||
TabRowDefaults.PrimaryIndicator(
|
||||
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
|
||||
height = 4.dp,
|
||||
color = style.foreground(),
|
||||
)
|
||||
},
|
||||
) {
|
||||
for (category in EmojiCategoryValues) {
|
||||
if (category == EmojiCategory.RECENTLY_USED && !emojiHistoryEnabled) {
|
||||
continue
|
||||
}
|
||||
Tab(
|
||||
onClick = {
|
||||
inputFeedbackController.keyPress(TextKeyData.UNSPECIFIED)
|
||||
onCategoryChange(category)
|
||||
},
|
||||
selected = activeCategory == category,
|
||||
icon = { SnyggIcon(
|
||||
elementName = FlorisImeUi.MediaEmojiTab.elementName,
|
||||
selector = if (activeCategory == category) SnyggSelector.FOCUS else SnyggSelector.NONE,
|
||||
modifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
imageVector = category.icon(),
|
||||
) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
) {
|
||||
val pagerState = rememberPagerState(
|
||||
pageCount = { calculatePageNumbers() }
|
||||
)
|
||||
|
||||
// Reset the pager to the first page when emojiHistory is enabled
|
||||
LaunchedEffect(emojiHistoryEnabled) {
|
||||
pagerState.animateScrollToPage(0)
|
||||
}
|
||||
|
||||
EmojiCategoriesTabRow(
|
||||
activeCategory = activeCategory,
|
||||
onCategoryChange = { category ->
|
||||
scope.launch { lazyListState.scrollToItem(0) }
|
||||
activeCategory = category
|
||||
scope.launch { pagerState.animateScrollToPage(categoryToPageNumber(activeCategory)) }
|
||||
},
|
||||
emojiHistoryEnabled = emojiHistoryEnabled,
|
||||
)
|
||||
HorizontalPager(pagerState, beyondViewportPageCount = 1) { page ->
|
||||
// Every page needs its own lazyGridState in order to scroll correctly
|
||||
val lazyGridState = rememberLazyGridState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
) {
|
||||
val emojiMapping = if (activeCategory == EmojiCategory.RECENTLY_USED) {
|
||||
// Update the lazyGridState and active category on scroll
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||
lazyGridState.scrollToItem(0)
|
||||
activeCategory = pageNumberToCategory(page)
|
||||
recentlyUsedVersion++
|
||||
}
|
||||
}
|
||||
|
||||
val category = pageNumberToCategory(page)
|
||||
val emojiMapping = if (category == EmojiCategory.RECENTLY_USED) {
|
||||
// Purposely using remember here to prevent recomposition, as this would cause rapid
|
||||
// emoji changes for the user when in recently used category.
|
||||
remember(recentlyUsedVersion) {
|
||||
@@ -234,63 +326,68 @@ fun EmojiPaletteView(
|
||||
EmojiMappingForView(
|
||||
pinned = emptyList(),
|
||||
recent = emptyList(),
|
||||
simple = emojiMappings[activeCategory]!!,
|
||||
simple = emojiMappings[category]!!,
|
||||
)
|
||||
}
|
||||
|
||||
val isEmojiHistoryEmpty = emojiMapping.pinned.isEmpty() && emojiMapping.recent.isEmpty()
|
||||
if (activeCategory == EmojiCategory.RECENTLY_USED && deviceLocked) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__history__phone_locked_message),
|
||||
)
|
||||
}
|
||||
} else if (activeCategory == EmojiCategory.RECENTLY_USED && isEmojiHistoryEmpty) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__history__empty_message),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
text = stringRes(R.string.emoji__history__usage_tip),
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
}
|
||||
} else key(emojiMapping) {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
LazyVerticalGrid(
|
||||
when (category) {
|
||||
EmojiCategory.RECENTLY_USED if deviceLocked -> {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisScrollbar(lazyListState),
|
||||
columns = GridCells.Adaptive(minSize = EmojiBaseWidth),
|
||||
state = lazyListState,
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
if (emojiMapping.pinned.isNotEmpty()) {
|
||||
header("header_pinned") {
|
||||
GridHeader(text = stringRes(R.string.emoji__history__pinned))
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__history__phone_locked_message),
|
||||
)
|
||||
}
|
||||
}
|
||||
EmojiCategory.RECENTLY_USED if isEmojiHistoryEmpty -> {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__history__empty_message),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
text = stringRes(R.string.emoji__history__usage_tip),
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> key(emojiMapping) {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisScrollbar(lazyGridState),
|
||||
columns = GridCells.Adaptive(minSize = EmojiBaseWidth),
|
||||
state = lazyGridState,
|
||||
) {
|
||||
if (emojiMapping.pinned.isNotEmpty()) {
|
||||
header("header_pinned") {
|
||||
GridHeader(text = stringRes(R.string.emoji__history__pinned))
|
||||
}
|
||||
items(emojiMapping.pinned) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet, isPinned = true)
|
||||
}
|
||||
}
|
||||
items(emojiMapping.pinned) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet, isPinned = true)
|
||||
if (emojiMapping.recent.isNotEmpty()) {
|
||||
header("header_recent") {
|
||||
GridHeader(text = stringRes(R.string.emoji__history__recent))
|
||||
}
|
||||
items(emojiMapping.recent) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet, isRecent = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emojiMapping.recent.isNotEmpty()) {
|
||||
header("header_recent") {
|
||||
GridHeader(text = stringRes(R.string.emoji__history__recent))
|
||||
}
|
||||
items(emojiMapping.recent) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet, isRecent = true)
|
||||
}
|
||||
}
|
||||
if (emojiMapping.simple.isNotEmpty()) {
|
||||
items(emojiMapping.simple) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet)
|
||||
if (emojiMapping.simple.isNotEmpty()) {
|
||||
items(emojiMapping.simple) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,59 +397,6 @@ fun EmojiPaletteView(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmojiCategoriesTabRow(
|
||||
activeCategory: EmojiCategory,
|
||||
onCategoryChange: (EmojiCategory) -> Unit,
|
||||
emojiHistoryEnabled: Boolean,
|
||||
) {
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
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 = style.foreground(),
|
||||
indicator = { tabPositions ->
|
||||
val style = rememberSnyggThemeQuery(
|
||||
elementName = FlorisImeUi.MediaEmojiTab.elementName,
|
||||
selector = SnyggSelector.FOCUS,
|
||||
)
|
||||
TabRowDefaults.PrimaryIndicator(
|
||||
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
|
||||
height = 4.dp,
|
||||
color = style.foreground(),
|
||||
)
|
||||
},
|
||||
) {
|
||||
for (category in EmojiCategoryValues) {
|
||||
if (category == EmojiCategory.RECENTLY_USED && !emojiHistoryEnabled) {
|
||||
continue
|
||||
}
|
||||
Tab(
|
||||
onClick = {
|
||||
inputFeedbackController.keyPress(TextKeyData.UNSPECIFIED)
|
||||
onCategoryChange(category)
|
||||
},
|
||||
selected = activeCategory == category,
|
||||
icon = { SnyggIcon(
|
||||
elementName = FlorisImeUi.MediaEmojiTab.elementName,
|
||||
selector = if (activeCategory == category) SnyggSelector.FOCUS else SnyggSelector.NONE,
|
||||
modifier = Modifier.size(ButtonDefaults.IconSize),
|
||||
imageVector = category.icon(),
|
||||
) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmojiKey(
|
||||
emojiSet: EmojiSet,
|
||||
|
||||
@@ -70,12 +70,17 @@ class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider
|
||||
val emojis = cachedEmojiMappings.get(subtype.primaryLocale)?.get(preferredSkinTone) ?: emptyList()
|
||||
val candidates = withContext(Dispatchers.Default) {
|
||||
emojis.parallelStream()
|
||||
.filter { emoji ->
|
||||
emoji.name.contains(query, ignoreCase = true) &&
|
||||
emoji.keywords.any { it.contains(query, ignoreCase = true) }
|
||||
}
|
||||
.limit(maxCandidateCount.toLong())
|
||||
.map { emoji ->
|
||||
val nameWeight = emoji.name.containsWeighted(query, ignoreCase = true)
|
||||
val keywordWeight = emoji.keywords
|
||||
.any { it.contains(query, ignoreCase = true) }
|
||||
.let { if (it) 1.0 else 0.0 }
|
||||
emoji to (nameWeight * 0.7 + keywordWeight * 0.3)
|
||||
}
|
||||
.sorted { (_, a), (_, b) -> b.compareTo(a) }
|
||||
.limit(maxCandidateCount.toLong())
|
||||
.filter { (_, a) -> a > 0 }
|
||||
.map { (emoji, _) ->
|
||||
EmojiSuggestionCandidate(
|
||||
emoji = emoji,
|
||||
showName = showName,
|
||||
@@ -128,3 +133,11 @@ class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider
|
||||
return emojiPartialName
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.containsWeighted(other: String, ignoreCase: Boolean = false): Double = let { str ->
|
||||
if (str.contains(other, ignoreCase = ignoreCase)) {
|
||||
other.length.toDouble() / str.length.toDouble()
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
|
||||
@@ -24,13 +24,15 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreHoriz
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import dev.patrickgold.florisboard.ime.keyboard.Key
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.florisboard.lib.snygg.SnyggQueryAttributes
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
@@ -39,6 +41,8 @@ import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
import org.florisboard.lib.snygg.ui.SnyggRow
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
val GlobalStateNumPopupsShowing = MutableStateFlow(0)
|
||||
|
||||
@Composable
|
||||
fun PopupBaseBox(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -46,6 +50,13 @@ fun PopupBaseBox(
|
||||
key: Key,
|
||||
shouldIndicateExtendedPopups: Boolean,
|
||||
): Unit = with(LocalDensity.current) {
|
||||
DisposableEffect(key) {
|
||||
GlobalStateNumPopupsShowing.update { it + 1 }
|
||||
onDispose {
|
||||
GlobalStateNumPopupsShowing.update { it - 1 }
|
||||
}
|
||||
}
|
||||
|
||||
SnyggBox(
|
||||
elementName = FlorisImeUi.KeyPopupBox.elementName,
|
||||
attributes = attributes,
|
||||
@@ -59,7 +70,7 @@ fun PopupBaseBox(
|
||||
.align(Alignment.TopCenter),
|
||||
) {
|
||||
SnyggText(
|
||||
modifier = Modifier.align(Alignment.Center).zIndex(100f),
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = label,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.zIndex
|
||||
import dev.patrickgold.florisboard.ime.keyboard.ComputingEvaluator
|
||||
import dev.patrickgold.florisboard.ime.keyboard.DefaultComputingEvaluator
|
||||
import dev.patrickgold.florisboard.ime.keyboard.Key
|
||||
@@ -39,7 +38,6 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.keyboard.computeImageVector
|
||||
import dev.patrickgold.florisboard.ime.keyboard.computeLabel
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSet
|
||||
import dev.patrickgold.florisboard.ime.smartbar.Temp
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
|
||||
@@ -454,12 +452,11 @@ class PopupUiController(
|
||||
FlorisImeUi.Attr.Mode to evaluator.keyboard.mode.toString(),
|
||||
FlorisImeUi.Attr.ShiftState to evaluator.state.inputShiftState.toString(),
|
||||
)
|
||||
Temp = !(baseRenderInfo != null || extRenderInfo != null)
|
||||
baseRenderInfo?.let { renderInfo ->
|
||||
PopupBaseBox(
|
||||
modifier = Modifier
|
||||
.requiredSize(renderInfo.bounds.size.toDpSize())
|
||||
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() }.zIndex(100f),
|
||||
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() },
|
||||
attributes = attributes,
|
||||
key = renderInfo.key,
|
||||
shouldIndicateExtendedPopups = renderInfo.shouldIndicateExtendedPopups && extRenderInfo == null,
|
||||
|
||||
@@ -31,7 +31,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardState
|
||||
import dev.patrickgold.florisboard.lib.compose.conditional
|
||||
import org.florisboard.lib.compose.conditional
|
||||
|
||||
private val SheetOutOfBoundsBgColorInactive = Color(0x00000000)
|
||||
private val SheetOutOfBoundsBgColorActive = Color(0x52000000)
|
||||
|
||||
@@ -44,11 +44,11 @@ import dev.patrickgold.florisboard.ime.nlp.ClipboardSuggestionCandidate
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
|
||||
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.nlpManager
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.compose.conditional
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.smartbar
|
||||
|
||||
import android.graphics.PixelFormat
|
||||
import android.os.Build
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
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.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -35,18 +34,16 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.forEach
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofillSuggestion
|
||||
import dev.patrickgold.florisboard.ime.popup.GlobalStateNumPopupsShowing
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.toIntOffset
|
||||
import org.florisboard.lib.compose.florisHorizontalScroll
|
||||
import org.florisboard.lib.snygg.SnyggSinglePropertySet
|
||||
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
|
||||
|
||||
var CachedInlineSuggestionsChipStyleSet: SnyggSinglePropertySet? = null
|
||||
|
||||
var Temp: Boolean = false
|
||||
|
||||
@Composable
|
||||
fun InlineSuggestionsStyleCache() {
|
||||
val chipStyleSet = rememberSnyggThemeQuery(FlorisImeUi.InlineAutofillChip.elementName)
|
||||
@@ -62,9 +59,8 @@ fun InlineSuggestionsUi(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val almostEmptyRect = remember { android.graphics.Rect(0, 0, 1, 1) }
|
||||
|
||||
val backgroundColor = rememberSnyggThemeQuery(FlorisImeUi.SmartbarCandidatesRow.elementName).background()
|
||||
val numPopupsShowing by GlobalStateNumPopupsShowing.collectAsState()
|
||||
val isZOrderedOnTop = numPopupsShowing == 0
|
||||
|
||||
Row(
|
||||
modifier
|
||||
@@ -72,8 +68,7 @@ fun InlineSuggestionsUi(
|
||||
.florisHorizontalScroll(
|
||||
state = scrollState,
|
||||
scrollbarHeight = CandidatesRowScrollbarHeight,
|
||||
)
|
||||
.background(backgroundColor),
|
||||
),
|
||||
) {
|
||||
val xMin = scrollState.value
|
||||
val xMax = scrollState.value + scrollState.viewportSize
|
||||
@@ -81,34 +76,19 @@ fun InlineSuggestionsUi(
|
||||
if (inlineSuggestion.view == null) {
|
||||
continue
|
||||
}
|
||||
//inlineSuggestion.view.background = ColorDrawable(backgroundColor.toArgb())
|
||||
inlineSuggestion.view.forEach {
|
||||
with (it as SurfaceView) {
|
||||
//this.setBackgroundColor(backgroundColor.toArgb())
|
||||
setZOrderOnTop(false)
|
||||
holder.setFormat(PixelFormat.OPAQUE)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
var chipPos by remember { mutableStateOf(IntOffset.Zero) }
|
||||
AndroidView(
|
||||
modifier = Modifier.onGloballyPositioned { chipPos = it.positionInParent().toIntOffset() },
|
||||
factory = { inlineSuggestion.view },
|
||||
update = { view ->
|
||||
view.isZOrderedOnTop = isZOrderedOnTop
|
||||
view.clipBounds = android.graphics.Rect(
|
||||
(xMin - chipPos.x).coerceAtLeast(0),
|
||||
0,
|
||||
(xMax - chipPos.x).coerceAtMost(view.width),
|
||||
view.height,
|
||||
)
|
||||
// The empty rect is a workaround for a bug (I suppose) where an empty rect causes
|
||||
// no clipping, but we actually want to completely hide the view.
|
||||
// Thus we just show the topmost pixel of the view, which due to the round shape
|
||||
// of the theme should be transparent anyways.
|
||||
if (view.clipBounds.isEmpty) {
|
||||
view.clipBounds = almostEmptyRect
|
||||
}
|
||||
view.visibility = if (view.clipBounds.isEmpty) View.INVISIBLE else View.VISIBLE
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.isUnspecified
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
@@ -60,13 +62,12 @@ import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsRow
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.ToggleOverflowPanelAction
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.horizontalTween
|
||||
import dev.patrickgold.florisboard.lib.compose.verticalTween
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.compose.horizontalTween
|
||||
import org.florisboard.lib.compose.verticalTween
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
@@ -142,7 +143,6 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
val prefs by FlorisPreferenceStore
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val activeEvaluator by keyboardManager.activeEvaluator.collectAsState()
|
||||
val nlpManager by context.nlpManager()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -187,7 +187,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
} else {
|
||||
Icons.AutoMirrored.Default.KeyboardArrowRight
|
||||
}
|
||||
val incognitoIcon = vectorResource(id = R.drawable.ic_incognito)
|
||||
val incognitoIcon = ImageVector.vectorResource(id = R.drawable.ic_incognito)
|
||||
val incognitoDisplayMode = prefs.keyboard.incognitoDisplayMode.observeAsState()
|
||||
val isIncognitoMode = keyboardManager.activeState.isIncognitoMode
|
||||
val icon = if (isIncognitoMode) {
|
||||
|
||||
@@ -25,9 +25,9 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Serializable
|
||||
sealed class QuickAction {
|
||||
@@ -87,6 +87,7 @@ fun QuickAction.computeDisplayName(evaluator: ComputingEvaluator): String {
|
||||
KeyCode.CLIPBOARD_CUT -> R.string.quick_action__clipboard_cut
|
||||
KeyCode.CLIPBOARD_PASTE -> R.string.quick_action__clipboard_paste
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> R.string.quick_action__clipboard_select_all
|
||||
KeyCode.FORWARD_DELETE -> R.string.quick_action__forward_delete
|
||||
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
|
||||
|
||||
@@ -86,6 +86,7 @@ data class QuickActionArrangement(
|
||||
QuickAction.InsertKey(TextKeyData.CLIPBOARD_PASTE),
|
||||
QuickAction.InsertKey(TextKeyData.CLIPBOARD_SELECT_ALL),
|
||||
QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH),
|
||||
QuickAction.InsertKey(TextKeyData.FORWARD_DELETE),
|
||||
),
|
||||
hiddenActions = listOf(
|
||||
),
|
||||
|
||||
@@ -39,7 +39,6 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -58,9 +57,9 @@ import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.toIntOffset
|
||||
import kotlinx.coroutines.launch
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggColumn
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
@@ -76,7 +75,6 @@ private val DragMarkerAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.
|
||||
fun QuickActionsEditorPanel() {
|
||||
val prefs by FlorisPreferenceStore
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val keyboardManager by context.keyboardManager()
|
||||
|
||||
// We get the current arrangement once and do not observe on purpose
|
||||
@@ -236,7 +234,7 @@ fun QuickActionsEditorPanel() {
|
||||
dynamicActions.filter { it != NoopAction && it != DragMarkerAction },
|
||||
hiddenActions.filter { it != NoopAction && it != DragMarkerAction },
|
||||
)
|
||||
scope.launch {
|
||||
runBlocking {
|
||||
prefs.smartbar.actionArrangement.set(newActionArrangement)
|
||||
}
|
||||
if (keyboardManager.activeState.isActionsEditorVisible) {
|
||||
|
||||
@@ -34,10 +34,10 @@ import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
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 dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggButton
|
||||
import org.florisboard.lib.snygg.ui.SnyggText
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -59,6 +59,8 @@ import dev.patrickgold.florisboard.FlorisImeService
|
||||
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
|
||||
import dev.patrickgold.florisboard.editorInstance
|
||||
import dev.patrickgold.florisboard.glideTypingManager
|
||||
import dev.patrickgold.florisboard.ime.editor.OperationScope
|
||||
import dev.patrickgold.florisboard.ime.editor.OperationUnit
|
||||
import dev.patrickgold.florisboard.ime.input.InputEventDispatcher
|
||||
import dev.patrickgold.florisboard.ime.keyboard.ComputingEvaluator
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
@@ -78,7 +80,6 @@ import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.FlorisRect
|
||||
import dev.patrickgold.florisboard.lib.Pointer
|
||||
import dev.patrickgold.florisboard.lib.PointerMap
|
||||
import dev.patrickgold.florisboard.lib.compose.DisposableLifecycleEffect
|
||||
import dev.patrickgold.florisboard.lib.devtools.LogTopic
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogDebug
|
||||
import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
@@ -88,6 +89,7 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.florisboard.lib.android.isOrientationLandscape
|
||||
import org.florisboard.lib.compose.DisposableLifecycleEffect
|
||||
import org.florisboard.lib.snygg.SnyggSelector
|
||||
import org.florisboard.lib.snygg.ui.SnyggBox
|
||||
import org.florisboard.lib.snygg.ui.SnyggIcon
|
||||
@@ -498,7 +500,7 @@ private class TextKeyboardLayoutController(
|
||||
if (pointer.hasTriggeredGestureMove && pointer.initialKey?.computedData?.code == KeyCode.DELETE) {
|
||||
val selection = editorInstance.activeContent.selection
|
||||
if (selection.isSelectionMode) {
|
||||
editorInstance.deleteBackwards()
|
||||
editorInstance.deleteBackwards(OperationUnit.CHARACTERS)
|
||||
}
|
||||
}
|
||||
onTouchCancelInternal(event, pointer)
|
||||
@@ -521,7 +523,7 @@ private class TextKeyboardLayoutController(
|
||||
prefs.gestures.deleteKeySwipeLeft.get() != SwipeAction.SELECT_WORDS_PRECISELY) {
|
||||
val selection = editorInstance.activeContent.selection
|
||||
if (selection.isSelectionMode) {
|
||||
editorInstance.deleteBackwards()
|
||||
editorInstance.deleteBackwards(OperationUnit.CHARACTERS)
|
||||
}
|
||||
}
|
||||
onTouchCancelInternal(event, pointer)
|
||||
@@ -765,10 +767,21 @@ private class TextKeyboardLayoutController(
|
||||
}
|
||||
val activeSelection = editorInstance.activeContent.selection
|
||||
if (activeSelection.isValid) {
|
||||
editorInstance.setSelection(
|
||||
(activeSelection.end + event.absUnitCountX + 1).coerceIn(0, activeSelection.end),
|
||||
activeSelection.end,
|
||||
)
|
||||
if (!inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
|
||||
// Backward select
|
||||
editorInstance.setSelectionSurrounding(
|
||||
n = -event.absUnitCountX - 1,
|
||||
unit = OperationUnit.CHARACTERS,
|
||||
scope = OperationScope.BEFORE_CURSOR,
|
||||
)
|
||||
} else {
|
||||
// Forward select
|
||||
editorInstance.setSelectionSurrounding(
|
||||
n = -event.absUnitCountX - 1,
|
||||
unit = OperationUnit.CHARACTERS,
|
||||
scope = OperationScope.AFTER_CURSOR,
|
||||
)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -777,8 +790,22 @@ private class TextKeyboardLayoutController(
|
||||
inputFeedbackController?.gestureMovingSwipe(TextKeyData.DELETE)
|
||||
}
|
||||
val activeSelection = editorInstance.activeContent.selection
|
||||
if (activeSelection.isValid && event.absUnitCountX <= 0) {
|
||||
editorInstance.selectionSetNWordsLeft(abs(event.absUnitCountX / 2) - 1)
|
||||
if (activeSelection.isValid) {
|
||||
if (!inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
|
||||
// Backward select
|
||||
editorInstance.setSelectionSurrounding(
|
||||
n = -event.absUnitCountX / 2 - 1,
|
||||
unit = OperationUnit.WORDS,
|
||||
scope = OperationScope.BEFORE_CURSOR,
|
||||
)
|
||||
} else {
|
||||
// Forward select
|
||||
editorInstance.setSelectionSurrounding(
|
||||
n = -event.absUnitCountX / 2 - 1,
|
||||
unit = OperationUnit.WORDS,
|
||||
scope = OperationScope.AFTER_CURSOR,
|
||||
)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
|
||||
import dev.patrickgold.florisboard.lib.io.ZipUtils
|
||||
import dev.patrickgold.florisboard.lib.util.TimeUtils.javaLocalTime
|
||||
import dev.patrickgold.florisboard.lib.util.ViewUtils
|
||||
import java.time.LocalTime
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -65,8 +67,7 @@ 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.value.SnyggStaticColorValue
|
||||
import java.time.LocalTime
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* Core class which manages the keyboard theme. Note, that this does not affect the UI theme of the
|
||||
@@ -79,11 +80,13 @@ class ThemeManager(context: Context) {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
private val _indexedThemeConfigs = MutableStateFlow(mapOf<ExtensionComponentName, ThemeExtensionComponent>())
|
||||
private val _indexedThemeConfigs = MutableStateFlow(mapOf<ExtensionComponentName, ThemeExtensionComponent>() to 0)
|
||||
val indexedThemeConfigs get() = _indexedThemeConfigs.asStateFlow()
|
||||
private val indexedThemeConfigVersion = AtomicInteger(0)
|
||||
|
||||
val previewThemeId = MutableStateFlow<ExtensionComponentName?>(null)
|
||||
val previewThemeInfo = MutableStateFlow<ThemeInfo?>(null)
|
||||
val wallpaperChangedCounter = MutableStateFlow(0)
|
||||
val configurationChangeCounter = MutableStateFlow(0)
|
||||
|
||||
private val cachedThemeInfos = mutableListOf<ThemeInfo>()
|
||||
private val activeThemeGuard = Mutex(locked = false)
|
||||
@@ -92,13 +95,14 @@ class ThemeManager(context: Context) {
|
||||
|
||||
init {
|
||||
extensionManager.themes.observeForever { themeExtensions ->
|
||||
val version = indexedThemeConfigVersion.incrementAndGet()
|
||||
_indexedThemeConfigs.value = buildMap {
|
||||
for (themeExtension in themeExtensions) {
|
||||
for (themeComponent in themeExtension.themes) {
|
||||
put(ExtensionComponentName(themeExtension.meta.id, themeComponent.id), themeComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
} to version
|
||||
}
|
||||
indexedThemeConfigs.collectIn(scope) {
|
||||
updateActiveTheme { cachedThemeInfos.clear() }
|
||||
@@ -109,7 +113,7 @@ class ThemeManager(context: Context) {
|
||||
prefs.theme.nightThemeId.asFlow(),
|
||||
previewThemeId,
|
||||
previewThemeInfo,
|
||||
wallpaperChangedCounter,
|
||||
configurationChangeCounter,
|
||||
) {}.collectIn(scope) {
|
||||
updateActiveTheme()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class WallpaperChangeReceiver : BroadcastReceiver() {
|
||||
if (intent.action == Intent.ACTION_WALLPAPER_CHANGED) {
|
||||
flogDebug { "Wallpaper changed" }
|
||||
val themeManager by context.themeManager()
|
||||
themeManager.wallpaperChangedCounter.update { it + 1 }
|
||||
themeManager.configurationChangeCounter.update { it + 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ package dev.patrickgold.florisboard.lib
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
import org.florisboard.lib.kotlin.CurlyArg
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import org.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun FlorisConfirmDeleteDialog(
|
||||
|
||||
@@ -1,163 +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.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.ShapeDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
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>,
|
||||
expanded: Boolean,
|
||||
selectedIndex: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
isError: Boolean = false,
|
||||
labelProvider: (@Composable (T) -> String)? = null,
|
||||
onSelectItem: (Int) -> Unit = { },
|
||||
onExpandRequest: () -> Unit = { },
|
||||
onDismissRequest: () -> Unit = { },
|
||||
) {
|
||||
@Composable
|
||||
fun asString(v: T): String {
|
||||
return labelProvider?.invoke(v) ?: v.toString()
|
||||
}
|
||||
|
||||
Box(modifier = modifier.wrapContentSize(Alignment.TopStart)) {
|
||||
val indicatorRotation by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
|
||||
val index = selectedIndex.coerceIn(items.indices)
|
||||
val color = if (!enabled) {
|
||||
MaterialTheme.colorScheme.outline
|
||||
} else if (isError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
}
|
||||
OutlinedButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
border = if (isError && enabled) {
|
||||
BorderStroke(ButtonDefaults.outlinedButtonBorder.width, MaterialTheme.colorScheme.error)
|
||||
} else {
|
||||
ButtonDefaults.outlinedButtonBorder
|
||||
},
|
||||
shape = ShapeDefaults.ExtraSmall,
|
||||
enabled = enabled,
|
||||
onClick = onExpandRequest,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
text = asString(items[index]),
|
||||
textAlign = TextAlign.Start,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
color = color,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.rotate(indicatorRotation),
|
||||
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||
tint = if (enabled) {
|
||||
color.copy(alpha = 0.74f) //Also test 0.60f
|
||||
} else {
|
||||
color
|
||||
},
|
||||
contentDescription = "Dropdown indicator",
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
for ((n, item) in items.withIndex()) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = asString(item))
|
||||
},
|
||||
onClick = {
|
||||
onSelectItem(n)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FlorisDropdownLikeButton(
|
||||
item: String,
|
||||
modifier: Modifier = Modifier,
|
||||
isError: Boolean = false,
|
||||
onClick: () -> Unit = { },
|
||||
) {
|
||||
Box(modifier = modifier.wrapContentSize(Alignment.TopStart)) {
|
||||
val color = if (isError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
}
|
||||
OutlinedButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
border = if (isError) {
|
||||
BorderStroke(ButtonDefaults.outlinedButtonBorder.width, MaterialTheme.colorScheme.error)
|
||||
} else {
|
||||
ButtonDefaults.outlinedButtonBorder
|
||||
},
|
||||
shape = ShapeDefaults.ExtraSmall,
|
||||
onClick = onClick,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
text = item,
|
||||
textAlign = TextAlign.Start,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
color = color,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
tint = color.copy(alpha = 0.74f), //Also test 0.60f
|
||||
contentDescription = "Dropdown indicator",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,10 @@ import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceUiContent
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.compose.FlorisAppBar
|
||||
import org.florisboard.lib.compose.FlorisIconButton
|
||||
import org.florisboard.lib.compose.autoMirrorForRtl
|
||||
import org.florisboard.lib.compose.florisVerticalScroll
|
||||
|
||||
@Composable
|
||||
fun FlorisScreen(builder: @Composable FlorisScreenScope.() -> Unit) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user