Compare commits

..

65 Commits

Author SHA1 Message Date
e2b361ac3d Add Android.bp for in-line building in aosp
Some checks failed
FlorisBoard CI / build (push) Has been cancelled
2025-11-03 00:22:30 -08:00
Patrick Goldinger
5562d49546 Merge pull request #3107 from florisboard/feat/clipboard-history-rework
Clipboard history rework
2025-09-30 21:19:29 +02:00
Patrick Goldinger
5891c53cf6 Fix paste icon not consistent with Smartbar 2025-09-30 01:08:57 +02:00
Patrick Goldinger
5466d00037 Add fine-grained clipboard sync behavior options (#811) 2025-09-30 00:44:24 +02:00
Patrick Goldinger
8a06d764bb Fix clear primary clip removes from history if pinned (#2089) 2025-09-29 23:16:31 +02:00
Patrick Goldinger
a800c7b230 Fix clipboard timestamp not shown for large item popups (#2941) 2025-09-29 22:31:36 +02:00
Patrick Goldinger
67e99aeca3 Add clipboard history hide on event controls (#1098 and #2168)
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-09-29 22:19:53 +02:00
Patrick Goldinger
ac14d92192 Tidy up code in clipboard manager and history
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-09-29 21:43:12 +02:00
Patrick Goldinger
3392f8f212 Rename clipboard prefs to improve consistency
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-09-29 21:10:44 +02:00
Lars Mühlbauer
8c7cafad61 Use different icon for paste action (#3106) 2025-09-29 20:42:57 +02:00
Shunnuo
721b25c349 Adding Korean Phonetic layout (#3079)
* korean

* duplicated

* KR

* KR
2025-09-10 12:10:15 +02:00
florisboard-bot
96e3af9c16 Update translations from Crowdin 2025-09-09 22:21:11 +02:00
Pelle
fa79fa3849 Add the swedish version of the dvorak layout (svorak) (#3080)
Signed-off-by: Pelle Windestam <pelle@windestam.se>
2025-09-09 21:32:56 +02:00
Ahmed Nagi
f646825e9c Add Arabic question mark to ~right popup mappings (#3088)
* Add Arabic question mark to ~right popup mappings

* Update ar.json

fix: add exclamation mark "!"

* Update ar.json

---------

Co-authored-by: Ahmed Nagi <144544047+Ahmed-Nagi1@users.noreply.github.com>
2025-09-09 21:22:24 +02:00
lnfinitesimal
0a299e1b04 Add superscript minus (⁻) and plus (⁺) symbols (#3039)
Complements superscript numbers. For mathematical and chemical notation (e.g., 10⁻³, Na⁺, Ca²⁺).
2025-09-09 21:15:39 +02:00
Patrick Goldinger
6417cf5958 Fix background not rendering on Android 8/9 (#3076) 2025-09-08 01:33:47 +02:00
Patrick Goldinger
234580fd48 Rework clipboard delete button to respect active filters (#3075) 2025-09-02 23:31:52 +02:00
Patrick Goldinger
fe015c549c Add IzzySoft badge for stable track to readme (#3073) 2025-09-02 17:23:33 +02:00
Patrick Goldinger
253ee969eb Update early-beta to beta (#3072) 2025-09-02 01:20:00 +02:00
Patrick Goldinger
8fa986ca76 Fix theme updates not properly propagating after save (#3052) 2025-08-19 22:34:03 +02:00
Lars Mühlbauer
640a1c56cc Update bug_report.yml (#3047)
* Update bug_report.yml

* Update .github/ISSUE_TEMPLATE/bug_report.yml

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2025-08-18 12:36:16 +02:00
Patrick Goldinger
059d2fd4bf Update minimim Android version in README.md 2025-08-15 03:42:59 +02:00
Patrick Goldinger
1275650ca5 Release v0.5.0-rc02 2025-08-11 22:17:29 +02:00
Patrick Goldinger
8359a6cd6c Update Crowdin domain name (#3038) 2025-08-11 22:12:20 +02:00
Lars Mühlbauer
31730348b9 Listen for configuration changes to update ime theme (#3036)
* Listen for configuration changes to update ime theme

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>

* Apply review suggestions

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2025-08-10 21:43:27 +02:00
Patrick Goldinger
f12170543f Release v0.5.0-rc01 2025-08-08 03:02:59 +02:00
florisboard-bot
37f9266ba3 Update translations from Crowdin 2025-08-08 03:01:46 +02:00
Lars Mühlbauer
ad47e51f0d Remove string length limitation (#3029) 2025-08-08 02:59:15 +02:00
Patrick Goldinger
6ccfb579b0 Merge pull request #3027 from florisboard/fix/delete-swipe-issues
Improve delete swipe behavior
2025-08-08 02:25:07 +02:00
Lars Mühlbauer
59e32a3a28 Fix restore failure when no preferences file is found (#3026) 2025-08-08 02:19:31 +02:00
Patrick Goldinger
cee3da97f0 Fix delete swipe not adhering to Unicode boundaries (#2638)
Additionally add forward delete + shift behavior for word deletions
2025-08-08 02:09:29 +02:00
Patrick Goldinger
afad229273 Fix forward delete gets stuck (#3017) 2025-08-08 01:36:01 +02:00
Patrick Goldinger
b23496afa4 Improve emoji suggestion weighting (#3025) 2025-08-08 01:13:56 +02:00
Patrick Goldinger
2dd37439ca Fix quick actions not reorderable (#3021) 2025-08-06 03:15:58 +02:00
Lars Mühlbauer
75f774d5e2 Fix bg image not correctly rendered in quick switch between apps (#3019) 2025-08-06 02:44:21 +02:00
Patrick Goldinger
38d91718ac Update ROADMAP.md (#3018) 2025-08-06 01:21:58 +02:00
Patrick Goldinger
8d9bacbefd Release v0.5.0-beta04 2025-08-05 22:53:03 +02:00
florisboard-bot
a1031acd8e Update translations from Crowdin 2025-08-05 22:51:30 +02:00
sammypanda
7a3af3eb54 Change emoji suggestions to match on name OR keyword (#3008) 2025-08-05 22:44:35 +02:00
Lars Mühlbauer
7a433e9860 Disable stylesheet overlay in bfu mode (#3015)
* Disable stylesheet overlay in bfu mode

* Grrr
2025-08-05 22:19:32 +02:00
Lars Mühlbauer
f687873cc5 Add the APK signing certificate hashes (#3016)
* Add the apk signing certificate hashes

* Fix typo
2025-08-05 22:12:01 +02:00
Patrick Goldinger
c6a84fd324 Merge pull request #3013 from florisboard/feat/autofill-correct-draw-behavior
Inline autofill correct draw behavior
2025-08-05 21:59:18 +02:00
Patrick Goldinger
dcc490cc9d Add support for GIFs (incl. content scale) in SnyggSurfaceView 2025-08-05 21:51:20 +02:00
Patrick Goldinger
d7b52cd489 Add support for content scale in SnyggSurfaceView 2025-08-04 15:13:03 +02:00
Patrick Goldinger
fc7700395c Adapt inline autofill UI and window integration 2025-08-04 06:53:25 +02:00
Patrick Goldinger
9f07702852 Add SnyggSurfaceView 2025-08-04 06:52:36 +02:00
Patrick Goldinger
f111544d81 Merge pull request #3010 from florisboard/feat/forward-delete
Add forward delete behavior & Add forward delete quick action
2025-08-03 18:46:03 +02:00
Patrick Goldinger
fc09bb74c0 Add explicit forward delete smartbar quick action (#2042) 2025-08-03 13:30:08 +02:00
Patrick Goldinger
674bdccc40 Update Google Group links in README.md (#3012) 2025-08-03 12:42:23 +02:00
Patrick Goldinger
3e69dddaa9 Add shift+delete key as forward delete behavior 2025-08-02 16:52:37 +02:00
Patrick Goldinger
3c175bc60d Add support for hardware shift pass-through 2025-08-02 16:52:01 +02:00
Patrick Goldinger
83150e17ab Release v0.5.0-beta03 2025-08-02 04:47:55 +02:00
Lars Mühlbauer
2d6646e674 Update jetpref to fix an app crash caused by a missing proguard rule (#3006) 2025-08-02 04:46:58 +02:00
florisboard-bot
cc9511edca Update translations from Crowdin 2025-08-02 03:23:32 +02:00
Lars Mühlbauer
f25df31147 Replace the usage of jetpref helper with official helper (#3005) 2025-08-02 03:12:32 +02:00
bmondream
0ccb0c33b4 Add Czech numeric row layout (#2973)
* Add Czech numeric row layout

* Remove alternative non-czech characters

* Add cs layout to extension.json

* Change numericRow ID from cs to czech

---------

Co-authored-by: Daniel Procházka <bmondream@magicalbits.xyz>
2025-08-02 02:57:08 +02:00
Softastur
7ea4e8c827 Update "ast" layout (#3003)
Added some tweaks for H, L, N and R letters
2025-08-02 02:46:44 +02:00
Lars Mühlbauer
93c76640f4 Migrate to type save navigation (#3004)
* Migrate to type save navigation

* Add docs for the Route interfaces

* Add better predictive back navigation animation

* Add the correct spices to the lasagne :D

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2025-08-02 02:41:08 +02:00
Lars Mühlbauer
c2c874a9e0 Add horizontal scroll to emoji view (#2997)
* Add horizontal scroll to emoji view

* Fix recently emojis not updating when in emoji panel

* git fix state bug
2025-08-02 00:54:08 +02:00
Patrick Goldinger
b14877da8c Merge pull request #2991 from florisboard/refactor/lib-compose
Move most of lib.compose to its own module
2025-08-02 00:46:26 +02:00
lm41
2fb1fdb281 Move Resources to :lib:compose 2025-08-02 00:39:32 +02:00
lm41
52646ecba1 Use inline createBitmap function 2025-08-01 18:06:18 +02:00
lm41
7905dae807 Use JetPrefTextField to match FlorisDropdownMenu 2025-08-01 18:06:15 +02:00
lm41
e73ac5f96f Replace FlorisDropdownMenu with JetPrefDropdownMenu 2025-08-01 18:05:47 +02:00
lm41
15039fb020 Move most of lib.compose to its own module 2025-08-01 18:05:22 +02:00
168 changed files with 2806 additions and 1702 deletions

View File

@@ -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
View 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"],
}

View File

@@ -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.**

View File

@@ -1,11 +1,11 @@
<img align="left" width="80" height="80"
src=".github/repo_icon.png" alt="App icon">
# FlorisBoard [![Crowdin](https://badges.crowdin.net/florisboard/localized.svg)](https://crowdin.florisboard.patrickgold.dev) [![Matrix badge](https://img.shields.io/badge/chat-%23florisboard%3amatrix.org-blue)](https://matrix.to/#/#florisboard:matrix.org) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![FlorisBoard CI](https://github.com/florisboard/florisboard/actions/workflows/android.yml/badge.svg?event=push)](https://github.com/florisboard/florisboard/actions/workflows/android.yml)
# FlorisBoard [![Crowdin](https://badges.crowdin.net/florisboard/localized.svg)](https://crowdin.florisboard.org) [![Matrix badge](https://img.shields.io/badge/chat-%23florisboard%3amatrix.org-blue)](https://matrix.to/#/#florisboard:matrix.org) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![FlorisBoard CI](https://github.com/florisboard/florisboard/actions/workflows/android.yml/badge.svg?event=push)](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)

View File

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

View File

@@ -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"))

View File

@@ -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",

View File

@@ -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" }
]
]

View File

@@ -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": "ㅁ"}
]
]

View File

@@ -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" }
]
]

View File

@@ -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" }
}
}
}
]
]

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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": "ٓ" },

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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(

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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(

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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() {

View File

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

View File

@@ -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 = ""

View File

@@ -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 {

View File

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

View File

@@ -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(

View File

@@ -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,

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) },
)

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 },
)
}

View File

@@ -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 {

View File

@@ -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)

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

@@ -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 = {

View File

@@ -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"

View File

@@ -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),
)
}

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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,

View File

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

View File

@@ -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)

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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>) {

View File

@@ -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),
)

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
}
/**

View File

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

View File

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

View File

@@ -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,

View File

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

View File

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

View File

@@ -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,
)
}

View File

@@ -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,

View File

@@ -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)

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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(
),

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(

View File

@@ -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",
)
}
}
}

View File

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