Compare commits

...

166 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
Patrick Goldinger
c035e19cfc Merge pull request #3000 from florisboard/feat/upgrade-to-jetpref-v3
Upgrade to JetPref v3
2025-08-01 16:58:14 +02:00
Patrick Goldinger
8d30403676 Upgrade Kotlin to 2.2.0 2025-08-01 16:47:43 +02:00
Patrick Goldinger
2886a24fcc Upgrade JetPref to 0.3.0-beta01 2025-08-01 16:22:20 +02:00
Patrick Goldinger
352c98205c Fix toasts being constructed on non-main threads 2025-07-31 13:03:13 +02:00
Patrick Goldinger
18c76b7195 Bump JetPref snapshot version 2025-07-30 02:14:49 +02:00
Patrick Goldinger
ea0712225a Fix backup and restore again 2025-07-30 01:35:51 +02:00
Patrick Goldinger
1132eaf59b Upgrade ThemeManager.kt to use Kotlin flows 2025-07-29 17:42:41 +02:00
Patrick Goldinger
cba85f1c0e Rework common collect(Latest)In functions 2025-07-29 16:41:36 +02:00
Patrick Goldinger
15dba9f33a Fix backup and restore functionality 2025-07-29 16:13:13 +02:00
Patrick Goldinger
3859528120 Try fix JvmTarget inconsistencies 2025-07-28 16:38:13 +02:00
Patrick Goldinger
046d253382 Upgrade to JetPref v3 2025-07-28 16:08:57 +02:00
Lars Mühlbauer
c55a87862f Remove enforcement for navigationBarContrast (#2987)
* Remove enforcement for navigationBarContrast

* Use luminance for color contrast detection

* Always use light icons when background image is present
2025-07-22 01:10:29 +02:00
Patrick Goldinger
ee08c58db2 Release v0.5.0-beta02 2025-07-18 15:59:23 +02:00
Patrick Goldinger
05d4a5cf62 Update README.md (#2984)
Updates the milestone version numbers
2025-07-18 15:56:38 +02:00
florisboard-bot
c5c8f7a4c3 Update translations from Crowdin 2025-07-18 15:44:11 +02:00
Patrick Goldinger
ce3aee93d6 Merge pull request #2719 from florisboard/reproducible-builds
Add support for reproducible builds
2025-07-18 15:25:00 +02:00
Patrick Goldinger
96340f7277 Remove old flake build config 2025-07-16 23:11:28 +02:00
Patrick Goldinger
d0dcf5be38 Improve repr_build assemble.sh script 2025-07-16 23:03:09 +02:00
Patrick Goldinger
15450a760e Add ability for APK/AAB signing in repr_build setup 2025-07-16 22:28:14 +02:00
Patrick Goldinger
de0027d87e Add Gradle cache sharing & Add sha256sum generation 2025-07-16 20:24:03 +02:00
Patrick Goldinger
c7e83fca21 Add option to pass custom output directory 2025-07-16 15:29:11 +02:00
Lars Mühlbauer
115dc5c42a Add support for physical keyboard settings (#2981) 2025-07-16 14:41:49 +02:00
Patrick Goldinger
c78cf84d6c Fix Android SDK location and rename to repr_build 2025-07-16 12:58:03 +02:00
Lars Mühlbauer
5e59f144dd Update ROADMAP.md (#2980)
* Update ROADMAP.md

This commit updated the roadmap to include the latest changes,
especially that the 0.5 update was split and word suggestions
are pushed down to 0.6.

* Add time base theme switching to ROADMAP

* Mark spaces in uri bug as fixed in ROADMAP

* Fix typos
2025-07-16 00:55:59 +02:00
Patrick Goldinger
564c075763 Fix files with spaces causing UI issues (#2982) 2025-07-16 00:31:49 +02:00
Lars Mühlbauer
86365d393b Re-add time based theme switching (#2977)
* Add prefs for time based theme switching

* Implement time based theme switching

* Update to jetpref 0.2.0-rc04

* Remove hardcoded strings
2025-07-15 23:56:44 +02:00
Patrick Goldinger
45a0c9ef63 Upgrade gradle/actions/wrapper-validation to v4 2025-07-15 23:16:42 +02:00
Patrick Goldinger
25da6be1a4 Upgrade CMake to 4.0.2 2025-07-15 23:15:01 +02:00
Patrick Goldinger
976afc2e51 Fiz containerization failing for aab builds 2025-07-15 22:47:39 +02:00
Patrick Goldinger
d994da8c97 Rework containerization and add track logic 2025-07-15 22:30:31 +02:00
Patrick Goldinger
40a22a762a Add containerization config for building the project 2025-07-15 16:53:18 +02:00
Patrick Goldinger
264b9ff98b Add -Wl,--build-id=none linker option
Required for reproducible builds to work
2025-07-15 16:52:54 +02:00
Patrick Goldinger
f1b7ddedb8 Add new tools version catalog 2025-07-15 16:51:53 +02:00
Lars Mühlbauer
b1665f61e5 Remove checks for API 26 as this is the min API (#2972) 2025-07-15 11:49:29 +02:00
Patrick Goldinger
0fa663f658 Release v0.5.0-beta01 2025-07-11 00:54:31 +02:00
florisboard-bot
b7f5bf267d Update translations from Crowdin 2025-07-11 00:08:54 +02:00
Lars Mühlbauer
8563f37c98 Refactor FlorisCopyToClipboardActivity (#2968)
* Refactor FlorisCopyToClipboardActivity

* Apply review suggestions
2025-07-10 22:13:05 +02:00
Patrick Goldinger
d3a2e50dda Merge pull request #2963 from florisboard/fix/font-import-mime-types
Relax font mime-type importing restrictions
2025-07-10 12:23:48 +02:00
Patrick Goldinger
29562c9980 Add custom MimeTypeFilter 2025-07-09 14:02:22 +02:00
Patrick Goldinger
5bbb5cfbce Relax font mime-type importing restrictions (#2957) 2025-07-09 14:02:22 +02:00
Lars Mühlbauer
4ada583418 Remove obsolete api level check and simplify logic (#2967)
* Remove obsolete api level check and simplify logic

* Remove obsolete min api level checks
2025-07-09 01:42:46 +02:00
eqilnou
200a195ae6 Add subscripts and missing fractions in number row popups (#2947)
* Add subscripts and missing fractions in western arabic number row

Add subscripts and missing fractions (1/ and 0/3) in western_arabic.json number row layout

* Add subscripts and missing fractions in bengali number row

Added subscripts and missing fractions (1/ and 0/3) in bengali.json number row layout

* Fix ordering of hints for character "1" in western arabic number row

* Add subscripts and missing fractions in devanagari number row

Added subscripts and missing fractions (1/ and 0/3) in devanagari.json number row layout

* Add subscripts and missing fractions in eastern arabic number row

Added subscripts and missing fractions (1/ and 0/3) in eastern_arabic.json number row layout

* Add subscripts and missing fractions in gujarati number row

Added subscripts and missing fractions (1/ and 0/3) in gujarati.json number row layout

* Add subscripts and missing fractions in gurmukhi number row

Added subscripts and missing fractions (1/ and 0/3) in gurmukhi.json number row layout

* Add subscripts and missing fractions in kannada number row

Added subscripts and missing fractions in kannada.json number row layout

* Add subscripts and missing fractions in malayalam number row

Added subscripts and missing fractions in malayalam.json number row layout

* Add subscripts and missing fractions in oriya number row layout

Added subscripts and missing fractions in oriya.json number row layout

* Add subscripts and missing fractions in persian number row

Added subscripts and missing fractions in persian.json number row layout

* Add subscripts and missing fractions in tamil number row

Added subscripts and missing fractions in tamil.json number row layout

* Add subscripts and missing fractions in telugu number row

Added subscripts and missing fractions in telugu.json number row layout

* Add subscripts and missing fractions in warang_citi number row

Added subscripts and missing fractions in warang_citi.json number row layout

* Add subscripts and missing fractions in thai number row

Added subscripts and missing fractions in thai.json number row layout

* Fix ordering of fractions 3/n in western arabic number row

* Fix ordering of fractions 3/n in thai number row

* Fix label of digit '4' in cjk number row

Use the correct symbol U+FF14 instead of U+FF13
2025-07-09 01:16:13 +02:00
MikayelB
fa910692bc Fix Amenian phonetic layout addition (#2904) 2025-07-09 01:02:39 +02:00
Patrick Goldinger
c3545d1625 Merge pull request #2966 from florisboard/chore/update-gradle-and-fix-release-build
Update gradle and fix build error [ExtraTranslation]
2025-07-09 00:55:48 +02:00
lm41
300ca755ec Update to gradle version 8.14.3 2025-07-08 21:20:54 +02:00
lm41
3efb760787 Ignore extra translations when building FlorisBoard 2025-07-08 16:23:10 +02:00
Lars Mühlbauer
647f7c106b Fix always enabling one-hand mode after prefs migration (#2962) 2025-07-07 19:55:35 +02:00
Lars Mühlbauer
d998f8fe0c Fix numbering in setup screen on api < 33 (Android 13 and below) (#2960) 2025-07-07 19:48:14 +02:00
Lars Mühlbauer
b7020d7cc9 Fix gestures not working on api 34+ (Android 14+) (#2959) 2025-07-07 19:44:16 +02:00
Patrick Goldinger
5b874fc22a Release v0.5.0-alpha04 2025-06-04 00:36:53 +02:00
Patrick Goldinger
4a6fad851c Upgrade to Gradle 8.14.1 2025-06-04 00:36:05 +02:00
florisboard-bot
6de6b37f6f Update translations from Crowdin 2025-06-03 23:44:35 +02:00
Patrick Goldinger
d2eb6fd1c4 Merge pull request #2938 from florisboard/feat/clipboard-sorting-and-timestamps
Clipboard filtering and timestamp display
2025-06-03 23:41:53 +02:00
Patrick Goldinger
4b84a1ed40 Adapt other default themes to new element names/styles 2025-06-03 23:17:02 +02:00
Patrick Goldinger
a1d4079ef9 Add display of clipboard timestamps (#2926) 2025-06-03 23:11:10 +02:00
Patrick Goldinger
ba6d401447 Add style for clipboard item description and improve icons
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-31 16:17:24 +02:00
Patrick Goldinger
43168fc31f Add SnyggChip
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-31 16:17:24 +02:00
Patrick Goldinger
d234d30154 Improve clipboard item style
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-31 16:17:24 +02:00
Patrick Goldinger
e010a22863 Add clipboard filter by item type (#2925)
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-31 16:17:24 +02:00
Patrick Goldinger
da3b99fda8 Upgrade to Compose 1.8 and Migrate to Compose BOM (#2931)
* Upgrade AGP to 8.9.3 and Gradle to 8.11.1

* Upgrade to Compose 1.8 & Migrate to Compose BOM
2025-05-31 16:14:14 +02:00
Patrick Goldinger
23f58496e3 Upgrade to Compose 1.8 & Migrate to Compose BOM 2025-05-31 16:08:29 +02:00
Patrick Goldinger
40ecbe1428 Upgrade AGP to 8.9.3 and Gradle to 8.11.1 2025-05-30 13:34:40 +02:00
szgabor3
62a2131fa8 Update hu.json (#2922)
I suggest modifications for main alternatives to key 
o -> ó instead of ö
u -> ú instead of ü
as those (ö and ü) already have their own keys on the keyboard.
So this way the main alternatives fit better to the similars (e.g. a-á, e-é, i-í) and the layout becomes more conveniently usable.
2025-05-27 20:51:50 +02:00
Patrick Goldinger
df8f96396f Merge pull request #2927 from florisboard/feat/clipboard-history-improvements
Clipboard history UI improvements
2025-05-25 01:48:27 +02:00
Patrick Goldinger
bace0259ec Rework element names of clipboard item popup (#2908) 2025-05-25 01:37:35 +02:00
Patrick Goldinger
64f062307d Remove restriction of clipboard item visible character length (#2900) 2025-05-25 01:18:24 +02:00
Patrick Goldinger
2ca69f8fa9 Rework clipboard history grid implementation (#2906) 2025-05-25 01:09:29 +02:00
Lars Mühlbauer
f8537a2245 Move the checklist to the bottom and correct typos (#2924) 2025-05-24 22:54:27 +02:00
Patrick Goldinger
cdb6707850 Fix theme editor navbar color bug (#2920) 2025-05-21 01:19:11 +02:00
Patrick Goldinger
35db9be1bf Revert "Fix ignored paths not applied to pull_request in workflow (#2917)" (#2919)
This reverts commit 8551bb6842.
2025-05-21 00:43:05 +02:00
Patrick Goldinger
8551bb6842 Fix ignored paths not applied to pull_request in workflow (#2917) 2025-05-21 00:28:59 +02:00
Patrick Goldinger
9ad5bc68d3 Improve issue templates, contrib guidelines, and roadmap (#2916)
* Improve repo issue templates

Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>

* Improve repo contribution guidelines

Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>

* Adjust roadmap to 0.5 dev cycle

Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>

---------

Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-21 00:16:23 +02:00
klaurence
ebf5c0f898 Fix one-handed button migration (#2907)
* Update AppPrefs.kt

* Fix one-handed button migration (florisboard/florisboard#2905)

* Fix accidentally-missing brace

* Try to fix null exception
2025-05-15 22:47:52 +02:00
Patrick Goldinger
f913b4f7db Release v0.5.0-alpha03 2025-05-14 02:08:57 +02:00
florisboard-bot
4f966385ec Update translations from Crowdin 2025-05-14 02:05:58 +02:00
Patrick Goldinger
e2ce63a2e9 Add mode attribute to select element styles (#2899)
* Add `mode` attribute to element styles

Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>

* Add `mode` attribute to key popup elements

---------

Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-14 02:04:07 +02:00
Patrick Goldinger
64603c4692 Fix theme state bugs (#2897)
Also enable Compose strong skipping mode
2025-05-13 22:50:13 +02:00
Patrick Goldinger
90d3fbfdf1 Merge pull request #2895 from florisboard/fix/smartbar-default-style
Fix Smartbar styles (actions, candidates, ...)
2025-05-13 20:34:54 +02:00
Patrick Goldinger
1f01c56c68 Fix font size multiplier not having any effect (#2896) 2025-05-13 20:21:15 +02:00
MikayelB
c0a5486619 Fix Armenian layout in extension.json (#2871)
The Armenian Alt Phonetic json was configured but wasn't properly added to extension.json, that's why it doesn't show up in the layout menu.
2025-05-13 02:51:58 +02:00
Patrick Goldinger
511802cb25 Disallow clip on window element styles 2025-05-13 01:18:09 +02:00
Patrick Goldinger
eb057ecaf5 Fix Smartbar extended actions style (#2881) 2025-05-13 01:11:21 +02:00
Patrick Goldinger
f93938b43a Allow fine-tuned styling of Smartbar candidates (#2887) 2025-05-13 00:44:56 +02:00
Patrick Goldinger
2b7f8212c7 Allow fine-tuned styling of Smartbar action tiles (#2887) 2025-05-13 00:15:00 +02:00
Patrick Goldinger
44c2c6e9c1 Fix fallback style of Smartbar shared actions toggle 2025-05-12 23:29:22 +02:00
Patrick Goldinger
a2cfe091a6 Add SnyggListItem / Allow styling of subtype pnale list items (#2893)
* Add SnyggListItem and allow subtype list to be styled (#2885)

* Update default themes regarding subtype panel style

* Fix text overflow issues for default subtype panel list item style
2025-05-12 23:16:54 +02:00
Patrick Goldinger
4d9b4d5522 Merge pull request #2890 from florisboard/fix/snygg-v2-minor-issues
Small fixes in new theme editor
2025-05-11 10:57:51 +02:00
Patrick Goldinger
defe45fec6 Fix theme editor rules being cut off (#2888) 2025-05-10 22:15:16 +02:00
Patrick Goldinger
c6721067bf Fix translation error in media theme category (#2886) 2025-05-10 21:54:33 +02:00
Patrick Goldinger
a7f55d4589 Fix theme manager dropdown state not updating (#2876) 2025-05-10 00:35:34 +02:00
Patrick Goldinger
eb2e260924 Fix version suffix missing minus delimiter (#2874) (#2875) 2025-05-09 23:48:54 +02:00
Patrick Goldinger
4950533409 Add distinct filtering to Smartbar quick actions in prefs migration
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-09 22:16:28 +02:00
Patrick Goldinger
813047fb40 Add option to reset Smartbar quick actions from devtools 2025-05-09 22:16:28 +02:00
Patrick Goldinger
96f3dccfc2 Fix Smartbar quick actions duplicated in prefs migration
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-09 22:16:28 +02:00
Patrick Goldinger
65595076ee Release v0.5.0-alpha02 2025-05-09 00:42:58 +02:00
florisboard-bot
bf1563ae78 Update translations from Crowdin 2025-05-09 00:33:24 +02:00
Patrick Goldinger
d111771e06 Upgrade jetpref and compose-tooltip 2025-05-09 00:29:24 +02:00
Patrick Goldinger
00ce01e102 Fix inline autofill chip style (#2868) 2025-05-07 00:59:10 +02:00
Patrick Goldinger
15b7022204 Improve Select files screen UX (#2859, #2860)
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-05-07 00:27:00 +02:00
Lars Mühlbauer
759b7f0033 Add language switch option to smartbar (#2816)
* Add language switch option to smartbar.

* Only enable switch language button when there are multiple subtypes
2025-05-06 23:31:23 +02:00
Lars Mühlbauer
73c3777529 Add transparent navigation bar to android xml themes (#2867) 2025-05-06 22:54:28 +02:00
284 changed files with 10629 additions and 3950 deletions

11
.dockerignore Normal file
View File

@@ -0,0 +1,11 @@
**/.*/
!.git/
**/build/
**/dist/
**/out/
**/target/
utils/
!utils/repr_build/scripts/
.env
gradlew.bat
local.properties

1
.envrc
View File

@@ -1 +0,0 @@
use_flake

View File

@@ -1,4 +1,4 @@
name: Bug Report
name: 🐛 Bug Report
description: Create a report to help FlorisBoard improve
labels:
- "bug"
@@ -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
@@ -62,3 +63,14 @@ body:
placeholder: e.g. 10, Stock
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/florisboard/florisboard/issues) or [closed](https://github.com/florisboard/florisboard/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md)."
required: true
- label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise."
required: true

View File

@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/florisboard/florisboard/discussions/new?category=q-a
about: Ask here if you have a question about FlorisBoard or need assistance
- name: General feedback
url: https://github.com/florisboard/florisboard/discussions/new?category=feedback
about: Give general feedback about this project
- name: 💬 Matrix
url: https://matrix.to/#/#florisboard:matrix.org
about: General chat about FlorisBoard and quick Q/A
- name: ❓ Questions / Feedback
url: https://github.com/florisboard/florisboard/discussions/new/choose
about: Post your questions or feedback in the discussions panel

View File

@@ -1,4 +1,4 @@
name: Crash report
name: 💥 Crash report
description: Create a report with a generated crash log attached to help FlorisBoard improve
labels:
- "bug"
@@ -36,3 +36,14 @@ body:
description: Paste the generated crash log below
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/florisboard/florisboard/issues) or [closed](https://github.com/florisboard/florisboard/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md)."
required: true
- label: "I have taken the time to fill in all the required details. I understand that the crash report will be dismissed otherwise."
required: true

View File

@@ -1,4 +1,4 @@
name: Feature request / Suggestion
name: Feature request
description: Suggest an idea for this project
labels:
- "proposal"
@@ -20,3 +20,14 @@ body:
description: Please explain your idea in a precise way.
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/florisboard/florisboard/issues) or [closed](https://github.com/florisboard/florisboard/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md)."
required: true
- label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise."
required: true

View File

@@ -25,14 +25,14 @@ jobs:
with:
submodules: recursive
- uses: gradle/actions/wrapper-validation@v3
- uses: gradle/actions/wrapper-validation@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin
- name: Set up CMake and Ninja
uses: lukka/get-cmake@latest
uses: lukka/get-cmake@v4.0.2
- name: Build with Gradle
run: ./gradlew clean assembleDebug
- uses: actions/upload-artifact@v4

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

@@ -4,32 +4,34 @@ Thanks for considering contributing to FlorisBoard!
There are several ways to contribute to FlorisBoard. This document provides some general guidelines for each type of contribution.
## General contributions
The FlorisBoard community is international, as such we require all contributions, including issues, pull requests, and participation in the Matrix chat to be in English and follow the [code of conduct](https://github.com/florisboard/florisboard/blob/main/CODE_OF_CONDUCT.md). Contributions not adhering to these requirements will be dismissed. Thanks for making the FlorisBoard community an inclusive and safe space for everyone!
### Translation
## Non-code contributions
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 send an email to [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a new language.
### Translations
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.**
### Feedback
You can [give general feedback](https://github.com/florisboard/florisboard/discussions/new?category=feedback) directly here on GitHub. This is the preferred way to give feedback, as it allows not only for me to read and respond to feedback, but for everyone in this community.
### Bug reporting
This kind of contribution is the most important, as it tells where FlorisBoard has flaws and thus should be improved to maximize stability and user experience. To make this process as smooth as possible, please use the pre-made [issue template](.github/ISSUE_TEMPLATE/bug_report.md) for bug reporting. This makes it easy for us to understand what the bug is and how to solve it.
Allows us to see where FlorisBoard has flaws and should be improved to maximize stability and user experience. To make this process as smooth as possible, please use the pre-made [bug report template](https://github.com/florisboard/florisboard/issues/new?template=bug_report.yml). This makes it easy for us to understand what the bug is and how to solve it.
#### Capturing error logs
Logs are captured by FlorisBoard's crash handler, which gives you the ability to copy it to the clipboard and paste it in GitHub. This is the preferred way to capture logs.
Logs are captured by FlorisBoard's crash handler, which gives you the ability to copy it to the clipboard and paste it in the crash report [issue template](https://github.com/florisboard/florisboard/issues/new?template=crash_report.yml). This is the preferred way to capture logs.
Alternatively, you can also use ADB (Android Debug Bridge) to capture the error log. This is recommended for experienced users only.
### Feature proposals
Use the feature proposal issue template to suggest a new idea or improvement for this project.
Use the feature proposal [issue template](https://github.com/florisboard/florisboard/issues/new?template=feature_request.yml) to suggest a new idea or improvement for this project.
### Feedback
You can [give general feedback](https://github.com/florisboard/florisboard/discussions/new?category=feedback) directly here on GitHub. This is the preferred way to give feedback, as it allows not only for me to read and respond to feedback, but for everyone in this community.
## Code contributions
@@ -38,15 +40,15 @@ You are always welcome to contribute new features or work on existing issues, th
> [!NOTE]
> If you intend to implement a bigger feature please coordinate with us so we can prevent that there's a major difference in expected implementation.
If you are overwhelmed by the code don't hesistate to ask for help in the [dev chat](https://matrix.to/#/#florisboard-dev:matrix.org) or the discussions tab! Some issues are also marked as good first issue, which are easy to do tasks.
If you are overwhelmed by the code don't hesitate to ask for help in the [dev chat](https://matrix.to/#/#florisboard-dev:matrix.org) or the discussions tab! Some issues are also marked as good first issue, which are easy to do tasks.
### System requirements for development
- Desktop PC with Linux or WSL2 (Windows)
- MacOS and Windows without WSL2 probably works too however there's no official support
- At least 16GB of RAM (because of Android Studio)
- At least 16GB of RAM (because of Android Studio / IntelliJ)
- The following tools must be installed:
- Android Studio (bundles SDK and NDK)
- Android Studio (bundles SDK and NDK) or IntelliJ with Android and Compose plugin
- Java 17
- CMake 3.22+
- Clang 15+
@@ -64,34 +66,7 @@ If you want to manually build the project without Android Studio you must ensure
and Gradle should take care of every build task.
## Joining the team
If you want to join the core maintainer/moderator team on a volunteer basis and be part of this project's journey that's great to hear!
### Basic Requirements
- A passion for seeing FlorisBoard flourish
- Good English skills for team and public communication
- A GitHub account and a Matrix handle
### Why Join
You'll have the chance to work directly with me and other team members. While the general idea is for us to work on all kinds of different aspects of the project as a team, if you're particularly interested in a specific area (e.g., UI, extensions, text processing), that's totally okay too!
### Available Roles
Currently the following roles are available and need help:
Role Description | Required Dev Experience
---|---
Software Developer (Kotlin) for Core App | Java/Kotlin development experience (on Android)
Software Developer (Rust) for Native Core | Some Rust development experience
GitHub Issues/Discussions Moderator | None
Crowdin Translation Verifier | Language proficiency for the language you want to verify
Interested? Feel free to dm me ([@patrickgold](https://matrix.to/#/@patrickgold:matrix.org)) on Matrix or send an email to [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev).
## Donating
Alternatively you can also show your support by buying me a coffee, so I can stay up all night and chase away bugs or add new cool stuff :)
You can also show your support by buying me a coffee, so I can stay up all night and chase away bugs or add new cool stuff :)
See the `Sponsors` button for available options!

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>
@@ -64,7 +67,7 @@ fully respecting your privacy. Currently in early-beta state.
</tr>
</table>
Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
Beginning with v0.7 FlorisBoard will enter the public beta on Google Play.
## Highlighted features
- Integrated clipboard manager / history
@@ -74,7 +77,7 @@ Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
> [!IMPORTANT]
> Word suggestions/spell checking are not included in the current releases
> and are a major goal for the v0.5 milestone.
> and are a major goal for the v0.6 milestone.
Feature roadmap: See [ROADMAP.md](ROADMAP.md)
@@ -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

@@ -1,49 +1,67 @@
# Roadmap
This feature roadmap intents to provide transparency to what is planned to be added to FlorisBoard in the foreseeable future. Note that there are no ETAs for any version milestones down below, experience has shown these won't hold anyways.
This feature roadmap intents to provide transparency to what is planned to be added to FlorisBoard in the foreseeable future. Note that there are no ETAs for any version milestones down below, experience has shown these won't hold anyway.
Each major milestone has associated alpha/beta releases, so if you are interested in previewing features quicker, keep an eye out! Each major 0.x release has also patch releases after the initial major release, which will be published on both the stable and preview tracks.
## 0.5
## 0.5 (currently in development)
- Implement predictive text support / spell checking
- Consider adding proximity-based key typo detection
- Add new extension type: Language Pack
- Basically groups all locale-relevant data (predictive base model, emoji suggestion data, ...)
in a dynamically importable extension file
- New text processing logic (maybe moved back / split to 0.6)
- Add floating keyboard mode
- New keyboard layout engine + file syntax based on the upcoming Unicode Keyboard v3 standard
- Add Tablet mode / Optimizations for landscape input based on new keyboard layout engine
- Add support for any remaining new features introduced with Android 13
> [!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.
- [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)
- [x] Re-add time based theme switching (See https://github.com/florisboard/florisboard/pull/2977)
- [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
- Complete rework of the Emoji panel
- Emoji search
- Fully scrollable emoji list (soft category borders)
- More granular themeing options
- Layout customization (e.g. placement of category buttons)
- Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles
- Reimplementation of glide typing with the new layout engine and predictive text core
- Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable 0.6)
- Rework branding images and texts of FlorisBoard for the app stores
- Focus on stability and experience improvements of the app and keyboard
- Add support for new features introduced with Android 14 / 15
- Not finalized, but planned: raise minimum required Android version from Android 7 (SDK level 24) to Android 8 (SDK level 26)
- [ ] Implement predictive text support / spell checking
- [ ] 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
> [!NOTE]
> The development of k3lp is not tied to a florisboard version and takes place on [codeberg.org](https://codeberg.org/k3lp/k3lp) simultaneously.
- [ ] New keyboard layout engine + file syntax based on the upcoming Unicode Keyboard v3 standard
- [ ] Add Tablet mode / Optimizations for landscape input based on new keyboard layout engine
## 0.7+
> [!NOTE]
> From 0.6 onwards we plan to have more stable 0.X releases but with at most one large feature per release, thus having a much quicker iteration of new features on the stable track, which is a benefit for everyone involved.
- [ ] Add floating keyboard mode
- [ ] New text processing logic
- [ ] Complete rework of the Emoji panel
- [ ] Emoji search
- [ ] Fully scrollable emoji list (soft category borders)
- [ ] Side scrollable emoji list (swipe for next category)
- [ ] More granular theming options
- [ ] Layout customization (e.g. placement of category buttons)
- [ ] Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles
- [ ] Reimplementation of glide typing with the new layout engine and predictive text core
- [ ] Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable 0.7)
- [ ] Rework branding images and texts of FlorisBoard for the app stores
- [ ] Focus on stability and experience improvements of the app and keyboard
- [ ] Add support for new features introduced with Android 15 / 16
## Backlog / Planned (unassigned)
**Features that MAY be added (even in versions mentioned above) or dismissed**
- Full on-board layout editor which allows users to create their own layouts without writing a JSON file
- Theme rework part II
- Adaptive themes v2
- Voice-to-text with Mozilla's open-source voice service (or any other oss voice provider)
- 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

@@ -15,7 +15,6 @@
*/
import java.io.ByteArrayOutputStream
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
plugins {
alias(libs.plugins.agp.application)
@@ -29,25 +28,29 @@ plugins {
val projectMinSdk: String by project
val projectTargetSdk: String by project
val projectCompileSdk: String by project
val projectBuildToolsVersion: String by project
val projectNdkVersion: String by project
val projectVersionCode: String by project
val projectVersionName: String by project
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "")
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "").let { suffix ->
if (suffix.isNotEmpty()) {
"-$suffix"
} else {
suffix
}
}
android {
namespace = "dev.patrickgold.florisboard"
compileSdk = projectCompileSdk.toInt()
buildToolsVersion = projectBuildToolsVersion
ndkVersion = projectNdkVersion
buildToolsVersion = tools.versions.buildTools.get()
ndkVersion = tools.versions.ndk.get()
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
freeCompilerArgs = listOf(
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-Xjvm-default=all-compatibility",
@@ -149,7 +152,13 @@ android {
}
aboutLibraries {
configPath = "app/src/main/config"
collect {
configPath = file("src/main/config")
}
}
lint {
baseline = file("lint.xml")
}
testOptions {
@@ -162,21 +171,21 @@ android {
}
}
composeCompiler {
// DO NOT ENABLE STRONG SKIPPING! This project currently relies on
// recomposition on parent state change to update the UI correctly.
featureFlags.add(ComposeFeatureFlag.StrongSkipping.disabled())
}
tasks.withType<Test> {
useJUnitPlatform()
}
dependencies {
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
// testImplementation(composeBom)
// androidTestImplementation(composeBom)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.autofill)
implementation(libs.androidx.collection.ktx)
implementation(libs.androidx.compose.material.icons)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.compose.ui)
@@ -186,27 +195,30 @@ dependencies {
implementation(libs.androidx.emoji2)
implementation(libs.androidx.emoji2.views)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.material.icons)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.profileinstaller)
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)
implementation(libs.mikepenz.aboutlibraries.compose)
implementation(libs.patrickgold.compose.tooltip)
implementation(libs.patrickgold.jetpref.datastore.model)
ksp(libs.patrickgold.jetpref.datastore.model.processor)
implementation(libs.patrickgold.jetpref.datastore.ui)
implementation(libs.patrickgold.jetpref.material.ui)
implementation(project(":lib:android"))
implementation(project(":lib:color"))
implementation(project(":lib:compose"))
implementation(project(":lib:kotlin"))
implementation(project(":lib:native"))
implementation(project(":lib:snygg"))
testImplementation(libs.kotlin.test.junit5)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
}

View File

@@ -1,3 +1,3 @@
<lint>
<issue id="UsingMaterialAndMaterial3Libraries" severity="ignore" />
<issue id="ExtraTranslation" severity="warning"/>
</lint>

View File

@@ -17,6 +17,13 @@
"direction": "rtl",
"modifier": "org.florisboard.layouts:arabic"
},
{
"id": "armenian_alt_phonetic",
"label": "Armenian Alt Phonetic",
"authors": [ "MikayelB" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:armenian"
},
{
"id": "western_armenian",
"label": "Armenian (Western)",
@@ -150,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",
@@ -260,6 +274,12 @@
"authors": [ "patrickgold", "Hayleia" ],
"direction": "ltr"
},
{
"id": "korean_phonetic",
"label": "South Korean Phonetic",
"authors": [ "Shunnuo" ],
"direction": "ltr"
},
{
"id": "kurdish",
"label": "کوردی (قوەرتی نوێ)",
@@ -503,6 +523,12 @@
"authors": [ "msrd0" ],
"direction": "ltr"
},
{
"id": "dvorak_se",
"label": "Dvorak (SE)",
"authors": [ "iceaway" ],
"direction": "ltr"
},
{
"id": "hebrew",
"label": "עברית",
@@ -601,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

@@ -3,46 +3,52 @@
{ "code": 2535, "label": "১", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 2536, "label": "২", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2537, "label": "৩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 2538, "label": "", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 2539, "label": "৫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 2540, "label": "৬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2542, "label": "৮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2543, "label": "৯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2534, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -33,7 +33,7 @@
{ "code": 32902, "label": "肆" },
{ "code": 18825, "label": "䦉" },
{ "code": 20118, "label": "亖" },
{ "code": 65300, "label": "" }
{ "code": 65300, "label": "" }
]
} },
{ "code": 20116, "label": "五", "type": "numeric", "popup": {

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

@@ -3,46 +3,52 @@
{ "code": 2407, "label": "१", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 2408, "label": "२", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2409, "label": "३", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 2410, "label": "४", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 2411, "label": "५", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 2412, "label": "६", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2414, "label": "८", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2415, "label": "९", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2406, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 1633, "label": "١", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 1634, "label": "٢", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 1635, "label": "٣", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 1636, "label": "٤", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 1637, "label": "٥", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 1638, "label": "٦", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 1640, "label": "٨", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 1641, "label": "٩", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 1632, "label": "٠", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 2791, "label": "૧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 2792, "label": "૨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2793, "label": "૩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 2794, "label": "૪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 2795, "label": "૫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 2796, "label": "૬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2798, "label": "૮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2799, "label": "૯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2790, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 2663, "label": "", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 2664, "label": "੨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2665, "label": "੩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 2666, "label": "", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 2667, "label": "੫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 2668, "label": "੬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2670, "label": "੮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2671, "label": "੯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2662, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 3303, "label": "೧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 3304, "label": "೨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3305, "label": "೩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 3306, "label": "೪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3307, "label": "೫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 3308, "label": "೬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3310, "label": "೮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3311, "label": "೯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3302, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 3431, "label": "൧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 3432, "label": "൨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3433, "label": "൩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 3434, "label": "൪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3435, "label": "൫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 3436, "label": "൬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3438, "label": "൮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3439, "label": "൯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3430, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 2919, "label": "୧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 2920, "label": "", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2921, "label": "୩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 2922, "label": "୪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 2923, "label": "୫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 2924, "label": "୬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2926, "label": "୮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2927, "label": "୯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2918, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 1777, "label": "۱", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 1778, "label": "۲", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 1779, "label": "۳", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 1780, "label": "۴", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 1781, "label": "۵", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 1782, "label": "۶", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 1784, "label": "۸", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 1785, "label": "۹", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 1776, "label": "۰", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 3047, "label": "௧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 3048, "label": "௨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3049, "label": "௩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 3050, "label": "௪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3051, "label": "௫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 3052, "label": "௬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3054, "label": "௮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3055, "label": "௯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3046, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 3175, "label": "౧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 3176, "label": "౨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3177, "label": "౩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 3178, "label": "౪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3179, "label": "౫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 3180, "label": "౬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3182, "label": "౮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3183, "label": "౯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3174, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,20 +3,23 @@
{ "code": 3665, "label": "๑", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 3666, "label": "๒", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
@@ -24,44 +27,59 @@
{ "code": 3667, "label": "๓", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": "₃"},
{ "code": 190, "label": "¾" },
{ "code": 8540, "label": "" }
{ "code": 8535, "label": "" }
]
} },
{ "code": 3668, "label": "๔", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "⁴" },
"relevant": [
{ "code": 8324, "label": "₄" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3669, "label": "๕", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8325, "label": "₅" },
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
} },
{ "code": 3670, "label": "๖", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" }
"main": { "code": 8310, "label": "⁶" },
"relevant": [
{ "code": 8326, "label": "₆" }
]
} },
{ "code": 3671, "label": "๗", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8327, "label": "₇" },
{ "code": 8542, "label": "⅞" }
]
} },
{ "code": 3672, "label": "๘", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" }
"main": { "code": 8312, "label": "⁸" },
"relevant": [
{ "code": 8328, "label": "₈" }
]
} },
{ "code": 3673, "label": "๙", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" }
"main": { "code": 8313, "label": "⁹" },
"relevant": [
{ "code": 8329, "label": "₉" }
]
} },
{ "code": 3664, "label": "", "type": "numeric", "popup": {
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" }
{ "code": 8319, "label": "ⁿ" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -3,46 +3,52 @@
{ "code": 71905, "label": "𑣡", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
} },
{ "code": 71906, "label": "𑣢", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "" },
{ "code": 8322, "label": ""},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 71907, "label": "𑣣", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "⅜" },
{ "code": 8323, "label": "₃"},
{ "code": 179, "label": "³" }
]
} },
{ "code": 71908, "label": "𑣤", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 71909, "label": "𑣥", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -50,6 +56,7 @@
{ "code": 71910, "label": "𑣦", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -57,27 +64,32 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 71912, "label": "𑣨", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 71913, "label": "𑣩", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 71904, "label": "𑣠", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "" }
{ "code": 8319, "label": "" },
{ "code": 8304, "label": "⁰" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
} }
]

View File

@@ -10,15 +10,17 @@
"code": 49, "label": "1", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8537, "label": "" },
{ "code": 8321, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
]
}
}
@@ -29,6 +31,7 @@
"code": 50, "label": "2", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
@@ -45,9 +48,10 @@
"code": 51, "label": "3", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8535, "label": "" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": "₃"},
{ "code": 190, "label": "¾" },
{ "code": 8540, "label": "" }
{ "code": 8535, "label": "" }
]
}
}
@@ -68,6 +72,7 @@
"code": 52, "label": "4", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "⁴" },
"relevant": [
{ "code": 8324, "label": "₄" },
{ "code": 8536, "label": "⅘" }
]
}
@@ -86,6 +91,7 @@
"code": 53, "label": "5", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8325, "label": "₅" },
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
@@ -105,7 +111,10 @@
},
"default": {
"code": 54, "label": "6", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" }
"main": { "code": 8310, "label": "⁶" },
"relevant": [
{ "code": 8326, "label": "₆" }
]
}
}
},
@@ -115,6 +124,7 @@
"code": 55, "label": "7", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8327, "label": "₇" },
{ "code": 8542, "label": "⅞" }
]
}
@@ -132,7 +142,10 @@
},
"default": {
"code": 56, "label": "8", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" }
"main": { "code": 8312, "label": "⁸" },
"relevant": [
{ "code": 8328, "label": "₈" }
]
}
}
},
@@ -160,7 +173,10 @@
},
"default": {
"code": 57, "label": "9", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" }
"main": { "code": 8313, "label": "⁹" },
"relevant": [
{ "code": 8329, "label": "₉" }
]
}
}
},
@@ -191,7 +207,9 @@
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" }
{ "code": 8319, "label": "ⁿ" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
]
}
}

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" ] },
@@ -401,6 +402,16 @@
"characters": "org.florisboard.layouts:qwertz"
}
},
{
"languageTag": "hy",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:armenian_dram",
"popupMapping": "org.florisboard.localization:hy",
"preferred": {
"characters": "org.florisboard.layouts:armenian_alt_phonetic",
"symbols": "org.florisboard.layouts:armenian"
}
},
{
"languageTag": "hy",
"composer": "org.florisboard.composers:appender",
@@ -596,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

@@ -10,9 +10,9 @@
"main": { "$": "auto_text_key" ,"code" : 237, "label": "í" }
},
"o": {
"main": { "$": "auto_text_key", "code" : 246, "label": "ö" },
"main": { "$": "auto_text_key", "code" : 243, "label": "ó" },
"relevant": [
{ "$": "auto_text_key", "code" : 243, "label": "ó" },
{ "$": "auto_text_key", "code" : 246, "label": "ö" },
{ "$": "auto_text_key", "code" : 337, "label": "ő" }
]
},
@@ -22,9 +22,9 @@
]
},
"u": {
"main": { "$": "auto_text_key", "code" : 252, "label": "ü" },
"main": { "$": "auto_text_key", "code" : 250, "label": "ú" },
"relevant": [
{ "$": "auto_text_key", "code" : 250, "label": "ú" },
{ "$": "auto_text_key", "code" : 252, "label": "ü" },
{ "$": "auto_text_key", "code" : 369, "label": "ű" }
]
},

View File

@@ -6,6 +6,7 @@
"--secondary": "#ff9800",
"--secondary-variant": "#e65100",
"--background": "#e0e0e0",
"--background-variant": "#d0d0d0",
"--surface": "#ffffff",
"--surface-variant": "#f5f5f5",
@@ -27,13 +28,13 @@
"--on-surface-variant": "#5f5f5f",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"clip": "no"
"foreground": "var(--on-background)"
},
"key": {
@@ -110,6 +111,7 @@
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
@@ -120,23 +122,34 @@
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
},
"smartbar-action-tile": {
"background": "transparent",
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "var(--shape)",
"text-max-lines": "2",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()"
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
},
"smartbar-actions-editor": {
@@ -194,6 +207,10 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
@@ -208,6 +225,9 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--spacer-color)"
},
@@ -229,31 +249,80 @@
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp"
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp"
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup-action": {
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
@@ -316,6 +385,11 @@
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
@@ -373,5 +447,17 @@
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
}
}

View File

@@ -6,6 +6,7 @@
"--secondary": "#ff9800",
"--secondary-variant": "#e65100",
"--background": "#e0e0e0",
"--background-variant": "#d0d0d0",
"--surface": "#f0f0f0",
"--surface-variant": "#ffffff",
@@ -27,13 +28,13 @@
"--on-surface-variant": "#5f5f5f",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"clip": "no"
"foreground": "var(--on-background)"
},
"key": {
@@ -112,6 +113,7 @@
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
@@ -126,28 +128,36 @@
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
},
"smartbar-action-tile": {
"background": "transparent",
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "var(--shape)",
"text-max-lines": "2",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()"
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
@@ -203,6 +213,10 @@
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
@@ -217,13 +231,15 @@
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"background": "transparent",
"foreground": "var(--on-surface)",
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
@@ -239,32 +255,80 @@
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp"
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp"
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
},
"clipboard-item-popup": {
"background": "var(--surface-variant)",
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup-action": {
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
@@ -327,6 +391,11 @@
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
@@ -384,6 +453,17 @@
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
}
}

View File

@@ -6,6 +6,7 @@
"--secondary": "#f57c00",
"--secondary-variant": "#e65100",
"--background": "#212121",
"--background-variant": "#313131",
"--surface": "#424242",
"--surface-variant": "#616161",
"--popup-surface": "#757575",
@@ -22,13 +23,13 @@
"--on-surface-variant": "#a0a0a0",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"clip": "no"
"foreground": "var(--on-background)"
},
"key": {
@@ -91,6 +92,7 @@
"key-popup-extended-indicator": {
"font-size": "16sp"
},
"smartbar": {
"font-size": "18sp"
},
@@ -104,6 +106,7 @@
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
@@ -114,23 +117,34 @@
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
},
"smartbar-action-tile": {
"background": "transparent",
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "var(--shape)",
"text-max-lines": "2",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()"
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
},
"smartbar-actions-editor": {
@@ -188,6 +202,10 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
@@ -202,6 +220,9 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--spacer-color)"
},
@@ -223,31 +244,80 @@
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp"
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp"
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup-action": {
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
@@ -309,6 +379,12 @@
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
@@ -365,5 +441,17 @@
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
}
}

View File

@@ -6,6 +6,7 @@
"--secondary": "#f57c00",
"--secondary-variant": "#e65100",
"--background": "#212121",
"--background-variant": "#313131",
"--surface": "#424242",
"--surface-variant": "#616161",
"--popup-surface": "#757575",
@@ -22,13 +23,13 @@
"--on-surface-variant": "#a0a0a0",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"clip": "no"
"foreground": "var(--on-background)"
},
"key": {
@@ -93,6 +94,7 @@
"key-popup-extended-indicator": {
"font-size": "16sp"
},
"smartbar": {
"font-size": "18sp"
},
@@ -106,6 +108,7 @@
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
@@ -120,28 +123,36 @@
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
},
"smartbar-action-tile": {
"background": "transparent",
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "var(--shape)",
"text-max-lines": "2",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()"
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
@@ -197,6 +208,10 @@
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
@@ -211,13 +226,15 @@
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"background": "transparent",
"foreground": "var(--on-surface)",
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
@@ -233,32 +250,80 @@
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp"
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp"
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
},
"clipboard-item-popup": {
"background": "var(--surface-variant)",
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup-action": {
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
@@ -320,6 +385,12 @@
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
@@ -376,5 +447,17 @@
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
}
}

View File

@@ -6,6 +6,7 @@
"--secondary": "#ff9800",
"--secondary-variant": "#804c00",
"--background": "#000000",
"--background-variant": "#111111",
"--surface": "#212121",
"--surface-variant": "#3d3d3d",
"--popup-surface": "#424242",
@@ -22,13 +23,13 @@
"--on-surface-variant": "#ffffff73",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"clip": "no"
"foreground": "var(--on-background)"
},
"key": {
@@ -91,6 +92,7 @@
"key-popup-extended-indicator": {
"font-size": "16sp"
},
"smartbar": {
"font-size": "18sp"
},
@@ -104,6 +106,7 @@
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
@@ -118,23 +121,34 @@
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
},
"smartbar-action-tile": {
"background": "transparent",
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "var(--shape)",
"text-max-lines": "2",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()"
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
},
"smartbar-actions-editor": {
@@ -192,6 +206,10 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
@@ -206,6 +224,9 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--spacer-color)"
},
@@ -227,31 +248,80 @@
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp"
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp"
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup-action": {
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
@@ -313,6 +383,12 @@
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
@@ -369,5 +445,17 @@
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
}
}

View File

@@ -6,6 +6,7 @@
"--secondary": "#ff9800",
"--secondary-variant": "#804c00",
"--background": "#000000",
"--background-variant": "#111111",
"--surface": "#212121",
"--surface-variant": "#3d3d3d",
"--popup-surface": "#424242",
@@ -22,13 +23,13 @@
"--on-surface-variant": "#ffffff73",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"clip": "no"
"foreground": "var(--on-background)"
},
"key": {
@@ -93,6 +94,7 @@
"key-popup-extended-indicator": {
"font-size": "16sp"
},
"smartbar": {
"font-size": "18sp"
},
@@ -106,6 +108,7 @@
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
@@ -120,28 +123,36 @@
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
},
"smartbar-action-tile": {
"background": "transparent",
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "var(--shape)",
"text-max-lines": "2",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()"
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
@@ -197,6 +208,10 @@
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
@@ -211,13 +226,15 @@
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"background": "transparent",
"foreground": "var(--on-surface)",
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
@@ -233,32 +250,80 @@
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp"
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp"
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
},
"clipboard-item-popup": {
"background": "var(--surface-variant)",
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"padding": "12dp 8dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup-action": {
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
@@ -320,6 +385,12 @@
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
@@ -376,5 +447,17 @@
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
}
}

View File

@@ -23,8 +23,10 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.util.Log
import androidx.core.os.UserManagerCompat
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
@@ -40,7 +42,11 @@ import dev.patrickgold.florisboard.lib.devtools.Flog
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.runtime.initAndroid
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.tryOrNull
import org.florisboard.libnative.dummyAdd
@@ -63,8 +69,9 @@ class FlorisApplication : Application() {
}
}
private val prefs by florisPreferenceModel()
private val mainHandler by lazy { Handler(mainLooper) }
private val scope = CoroutineScope(Dispatchers.Default)
val preferenceStoreLoaded = MutableStateFlow(false)
val cacheManager = lazy { CacheManager(this) }
val clipboardManager = lazy { ClipboardManager(this) }
@@ -80,7 +87,6 @@ class FlorisApplication : Application() {
super.onCreate()
FlorisApplicationReference = WeakReference(this)
try {
JetPref.configure(saveIntervalMs = 500)
Flog.install(
context = this,
isFloggingEnabled = BuildConfig.DEBUG,
@@ -108,7 +114,14 @@ class FlorisApplication : Application() {
fun init() {
cacheDir?.deleteContentsRecursively()
prefs.initializeBlocking(this)
scope.launch {
val result = FlorisPreferenceStore.initAndroid(
context = this@FlorisApplication,
datastoreName = FlorisPreferenceModel.NAME,
)
Log.i("PREFS", result.toString())
preferenceStoreLoaded.value = true
}
extensionManager.value.init()
clipboardManager.value.initializeForContext(this)
DictionaryManager.init(this)

View File

@@ -75,8 +75,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import dev.patrickgold.florisboard.app.FlorisAppActivity
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.devtools.DevtoolsOverlay
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.ClipboardInputLayout
import dev.patrickgold.florisboard.ime.core.SelectSubtypePanel
@@ -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,16 +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.kotlin.collectLatestIn
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
@@ -246,12 +248,12 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
}
ims.showShortToast("Failed to find voice IME, do you have one installed?")
ims.showShortToastSync("Failed to find voice IME, do you have one installed?")
return false
}
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val editorInstance by editorInstance()
private val keyboardManager by keyboardManager()
private val nlpManager by nlpManager()
@@ -277,20 +279,23 @@ class FlorisImeService : LifecycleInputMethodService() {
super.onCreate()
FlorisImeServiceReference = WeakReference(this)
WindowCompat.setDecorFitsSystemWindows(window.window!!, false)
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
subtypeManager.activeSubtypeFlow.collectIn(lifecycleScope) { subtype ->
val config = Configuration(resources.configuration)
if (prefs.localization.displayKeyboardLabelsInSubtypeLanguage.get()) {
config.setLocale(subtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.observeForever { shouldSync ->
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.asFlow().collectIn(lifecycleScope) { shouldSync ->
val config = Configuration(resources.configuration)
if (shouldSync) {
config.setLocale(subtypeManager.activeSubtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.physicalKeyboard.showOnScreenKeyboard.asFlow().collectIn(lifecycleScope) {
updateInputViewShown()
}
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
registerReceiver(wallpaperChangeReceiver, IntentFilter(Intent.ACTION_WALLPAPER_CHANGED))
}
@@ -329,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)
@@ -350,12 +360,21 @@ 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)
}
}
override fun onEvaluateInputViewShown(): Boolean {
val config = resources.configuration
return super.onEvaluateInputViewShown()
|| config.keyboard == Configuration.KEYBOARD_NOKEYS
|| prefs.physicalKeyboard.showOnScreenKeyboard.get()
}
override fun onUpdateSelection(
oldSelStart: Int,
oldSelEnd: Int,
@@ -398,7 +417,6 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
inputFeedbackController.updateSystemPrefsState()
}
@@ -412,6 +430,7 @@ class FlorisImeService : LifecycleInputMethodService() {
}
isWindowShown = false
activeState.batchEdit {
activeState.imeUiMode = ImeUiMode.TEXT
activeState.isActionsOverflowVisible = false
activeState.isActionsEditorVisible = false
}
@@ -528,8 +547,8 @@ class FlorisImeService : LifecycleInputMethodService() {
*/
private fun updateSoftInputWindowLayoutParameters() {
val w = window?.window ?: return
// TODO: setting this to false kinda helps with the nav bar color bug, but introduces padding issues...
WindowCompat.setDecorFitsSystemWindows(w, true)
// TODO: Verify that this doesn't give us a padding problem
WindowCompat.setDecorFitsSystemWindows(w, false)
ViewUtils.updateLayoutHeightOf(w, WindowManager.LayoutParams.MATCH_PARENT)
val layoutHeight = if (isFullscreenUiMode) {
WindowManager.LayoutParams.WRAP_CONTENT
@@ -562,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 {
@@ -588,6 +610,10 @@ class FlorisImeService : LifecycleInputMethodService() {
@Composable
private fun ImeUi() {
val state by keyboardManager.activeState.collectAsState()
val attributes = mapOf(
FlorisImeUi.Attr.Mode to state.keyboardMode.toString(),
FlorisImeUi.Attr.ShiftState to state.inputShiftState.toString(),
)
val layoutDirection = LocalLayoutDirection.current
LaunchedEffect(layoutDirection) {
keyboardManager.activeState.layoutDirection = layoutDirection
@@ -595,7 +621,7 @@ class FlorisImeService : LifecycleInputMethodService() {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
SnyggBox(
elementName = FlorisImeUi.Window.elementName,
attributes = mapOf(FlorisImeUi.Attr.ShiftState to state.inputShiftState.attrName()),
attributes = attributes,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
@@ -603,8 +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
@@ -656,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 {
@@ -694,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(),
@@ -746,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) {
@@ -754,7 +802,8 @@ class FlorisImeService : LifecycleInputMethodService() {
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
SnyggBox(FlorisImeUi.ExtractedLandscapeInputLayout.elementName,
SnyggBox(
elementName = FlorisImeUi.ExtractedLandscapeInputLayout.elementName,
modifier = Modifier
.fillMaxHeight()
.weight(1f),

View File

@@ -20,7 +20,7 @@ import android.service.textservice.SpellCheckerService
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextInfo
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
@@ -33,7 +33,7 @@ import kotlinx.coroutines.runBlocking
import org.florisboard.lib.kotlin.map
class FlorisSpellCheckerService : SpellCheckerService() {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val dictionaryManager get() = DictionaryManager.default()
private val nlpManager by nlpManager()
private val subtypeManager by subtypeManager()

View File

@@ -20,9 +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
@@ -52,13 +55,16 @@ import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
import dev.patrickgold.florisboard.lib.compose.ColorPreferenceSerializer
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import dev.patrickgold.florisboard.lib.util.VersionName
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.annotations.Preferences
import dev.patrickgold.jetpref.datastore.jetprefDataStoreOf
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
@@ -66,54 +72,27 @@ import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.color.DEFAULT_GREEN
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
val FlorisPreferenceStore = jetprefDataStoreOf(FlorisPreferenceModel::class)
@Preferences
abstract class FlorisPreferenceModel : PreferenceModel() {
companion object {
const val NAME = "florisboard-app-prefs"
}
class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val clipboard = Clipboard()
inner class Clipboard {
val useInternalClipboard = boolean(
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 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",
@@ -123,6 +102,63 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
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()
@@ -619,6 +655,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
)
}
val physicalKeyboard = PhysicalKeyboard()
inner class PhysicalKeyboard {
val showOnScreenKeyboard = boolean(
key = "physical_keyboard__show_on_screen_keyboard",
default = false,
)
}
val smartbar = Smartbar()
inner class Smartbar {
val enabled = boolean(
@@ -730,14 +774,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
},
serializer = ColorPreferenceSerializer,
)
//val sunriseTime = localTime(
// key = "theme__sunrise_time",
// default = LocalTime.of(6, 0),
//)
//val sunsetTime = localTime(
// key = "theme__sunset_time",
// default = LocalTime.of(18, 0),
//)
val sunriseTime = localTime(
key = "theme__sunrise_time",
default = LocalTime(6, 0),
)
val sunsetTime = localTime(
key = "theme__sunset_time",
default = LocalTime(18, 0),
)
val editorColorRepresentation = enum(
key = "theme__editor_color_representation",
default = ColorRepresentation.HEX,
@@ -800,26 +844,38 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
//Migrate one hand mode prefs keep until: 0.7 dev cycle
"keyboard__one_handed_mode" -> {
if (entry.rawValue != "OFF") {
val prefs by florisPreferenceModel()
prefs.keyboard.oneHandedModeEnabled.set(true)
entry.keepAsIs()
} else {
if (entry.rawValue == "OFF") {
entry.reset()
} else {
entry.keepAsIs()
}
}
"smartbar__action_arrangement" -> {
val arrangement = QuickActionJsonConfig.decodeFromString<QuickActionArrangement>(entry.rawValue)
val newArrangement = arrangement.copy(
dynamicActions = arrangement.dynamicActions.map { action ->
if (action is QuickAction.InsertKey && action.data.code == KeyCode.COMPACT_LAYOUT_TO_RIGHT) {
action.copy(TextKeyData.TOGGLE_COMPACT_LAYOUT)
} else {
action
}
fun migrateAction(action: QuickAction): QuickAction {
return if (action is QuickAction.InsertKey && action.data.code == KeyCode.COMPACT_LAYOUT_TO_RIGHT) {
action.copy(TextKeyData.TOGGLE_COMPACT_LAYOUT)
} else {
action
}
}
val arrangement = QuickActionJsonConfig.decodeFromString<QuickActionArrangement>(entry.rawValue)
var newArrangement = arrangement.copy(
stickyAction = arrangement.stickyAction?.let{ migrateAction(it) },
dynamicActions = arrangement.dynamicActions.map { migrateAction(it) },
hiddenActions = arrangement.hiddenActions.map { migrateAction(it) },
)
val json = QuickActionJsonConfig.encodeToString(newArrangement)
if (QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH) !in newArrangement) {
newArrangement = newArrangement.copy(
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)
}
@@ -836,6 +892,44 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
)
}
// 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,11 +20,14 @@ 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
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
import dev.patrickgold.florisboard.ime.input.InputShiftState
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
import dev.patrickgold.florisboard.ime.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.keyboard.SpaceBarMode
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
@@ -40,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
@@ -102,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(
@@ -364,6 +391,58 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
)
}
},
InputShiftState::class to DEFAULT to {
listPrefEntries {
entry(
key = InputShiftState.UNSHIFTED,
label = stringRes(R.string.enum__input_shift_state__unshifted),
)
entry(
key = InputShiftState.SHIFTED_MANUAL,
label = stringRes(R.string.enum__input_shift_state__shifted_manual),
)
entry(
key = InputShiftState.SHIFTED_AUTOMATIC,
label = stringRes(R.string.enum__input_shift_state__shifted_automatic),
)
entry(
key = InputShiftState.CAPS_LOCK,
label = stringRes(R.string.enum__input_shift_state__caps_lock),
)
}
},
KeyboardMode::class to DEFAULT to {
listPrefEntries {
entry(
key = KeyboardMode.CHARACTERS,
label = stringRes(R.string.enum__keyboard_mode__characters),
)
entry(
key = KeyboardMode.SYMBOLS,
label = stringRes(R.string.enum__keyboard_mode__symbols),
)
entry(
key = KeyboardMode.SYMBOLS2,
label = stringRes(R.string.enum__keyboard_mode__symbols2),
)
entry(
key = KeyboardMode.NUMERIC,
label = stringRes(R.string.enum__keyboard_mode__numeric),
)
entry(
key = KeyboardMode.NUMERIC_ADVANCED,
label = stringRes(R.string.enum__keyboard_mode__numeric_advanced),
)
entry(
key = KeyboardMode.PHONE,
label = stringRes(R.string.enum__keyboard_mode__phone),
)
entry(
key = KeyboardMode.PHONE2,
label = stringRes(R.string.enum__keyboard_mode__phone2),
)
}
},
LandscapeInputUiMode::class to DEFAULT to {
listPrefEntries {
entry(

View File

@@ -39,26 +39,30 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
import dev.patrickgold.florisboard.appContext
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
enum class AppTheme(val id: String) {
AUTO("auto"),
@@ -73,7 +77,8 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
}
class FlorisAppActivity : ComponentActivity() {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by appContext()
private val cacheManager by cacheManager()
private var appTheme by mutableStateOf(AppTheme.AUTO)
private var showAppIcon = true
@@ -83,40 +88,43 @@ class FlorisAppActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Splash screen should be installed before calling super.onCreate()
installSplashScreen().apply {
setKeepOnScreenCondition { !prefs.datastoreReadyStatus.get() }
setKeepOnScreenCondition { !appContext.preferenceStoreLoaded.value }
}
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
prefs.other.settingsTheme.observe(this) {
prefs.other.settingsTheme.asFlow().collectIn(lifecycleScope) {
appTheme = it
}
prefs.other.settingsLanguage.observe(this) {
prefs.other.settingsLanguage.asFlow().collectIn(lifecycleScope) {
val config = Configuration(resources.configuration)
val locale = if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it)
config.setLocale(locale.base)
resourcesContext = createConfigurationContext(config)
}
if (AndroidVersion.ATMOST_API28_P) {
prefs.other.showAppIcon.observe(this) {
prefs.other.showAppIcon.asFlow().collectIn(lifecycleScope) {
showAppIcon = it
}
}
//Check if android 13+ is running and the NotificationPermission is not set
if (AndroidVersion.ATLEAST_API33_T &&
prefs.internal.notificationPermissionState.get() == NotificationPermissionState.NOT_SET
) {
// update pref value to show the setup screen again again
prefs.internal.isImeSetUp.set(false)
}
// We defer the setContent call until the datastore model is loaded, until then the splash screen stays drawn
prefs.datastoreReadyStatus.observe(this) { isModelLoaded ->
if (!isModelLoaded) return@observe
val isModelLoaded = AtomicBoolean(false)
appContext.preferenceStoreLoaded.collectIn(lifecycleScope) { loaded ->
if (!loaded || isModelLoaded.getAndSet(true)) return@collectIn
// Check if android 13+ is running and the NotificationPermission is not set
if (AndroidVersion.ATLEAST_API33_T &&
prefs.internal.notificationPermissionState.get() == NotificationPermissionState.NOT_SET
) {
// update pref value to show the setup screen again
prefs.internal.isImeSetUp.set(false)
}
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
setContent {
ProvideLocalizedResources(resourcesContext) {
ProvideLocalizedResources(
resourcesContext,
appName = R.string.app_name,
) {
FlorisAppTheme(theme = appTheme) {
Surface(color = MaterialTheme.colorScheme.background) {
AppContent()
@@ -189,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
@@ -47,8 +53,9 @@ import dev.patrickgold.florisboard.app.settings.HomeScreen
import dev.patrickgold.florisboard.app.settings.about.AboutScreen
import dev.patrickgold.florisboard.app.settings.about.ProjectLicenseScreen
import dev.patrickgold.florisboard.app.settings.about.ThirdPartyLicensesScreen
import dev.patrickgold.florisboard.app.settings.advanced.OtherScreen
import dev.patrickgold.florisboard.app.settings.advanced.BackupScreen
import dev.patrickgold.florisboard.app.settings.advanced.OtherScreen
import dev.patrickgold.florisboard.app.settings.advanced.PhysicalKeyboardScreen
import dev.patrickgold.florisboard.app.settings.advanced.RestoreScreen
import dev.patrickgold.florisboard.app.settings.clipboard.ClipboardScreen
import dev.patrickgold.florisboard.app.settings.dictionary.DictionaryScreen
@@ -69,112 +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 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,
@@ -185,108 +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.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

@@ -29,9 +29,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
/*private val AmoledDarkColorPalette = darkColorScheme(
@@ -79,7 +78,7 @@ fun getColorScheme(
context: Context,
theme: AppTheme,
): ColorScheme {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val accentColor by prefs.other.accentColor.observeAsState()
val isDark = isSystemInDarkTheme()

View File

@@ -32,11 +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
@@ -66,9 +66,9 @@ fun AndroidLocalesScreen() = FlorisScreen {
out.appendLine()
}
}
context.showLongToast("Exported available system locales to \"${txtFile.path}\"")
context.showLongToastSync("Exported available system locales to \"${txtFile.path}\"")
} catch (e: Exception) {
context.showLongToast(
context.showLongToastSync(
R.string.error__snackbar_message_template,
"error_message" to e.message.toString(),
)

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

@@ -29,7 +29,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -40,7 +39,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.app.florisPreferenceModel
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
@@ -53,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,7 +64,8 @@ private val DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", FlorisLocale.
@Composable
fun DevtoolsOverlay(modifier: Modifier = Modifier) {
val context = LocalContext.current
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val appContext by context.appContext()
val keyboardManager by context.keyboardManager()
val themeManager by context.themeManager()
@@ -73,9 +74,10 @@ 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.observeAsState()
val themeInfo by themeManager.activeThemeInfo.collectAsState()
CompositionLocalProvider(
LocalContentColor provides Color.White,
@@ -97,8 +99,8 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
if (devtoolsEnabled && showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
DevtoolsInlineAutofillOverlay()
}
val loadFailure = themeInfo?.loadFailure
if (loadFailure != null) {
val loadFailure = themeInfo.loadFailure
if (loadFailure != null && prefsLoaded) {
DevtoolsStylesheetFailedToLoadOverlay(loadFailure)
}
}
@@ -163,13 +165,13 @@ private fun DevtoolsLastLayoutComputationOverlay(debugLayoutResult: DebugLayoutC
return@DevtoolsOverlayBox
}
DevtoolsSubGroup(title = "main") {
PrintResult(debugLayoutResult!!.main)
PrintResult(debugLayoutResult.main)
}
DevtoolsSubGroup(title = "mod") {
PrintResult(debugLayoutResult!!.mod)
PrintResult(debugLayoutResult.mod)
}
DevtoolsSubGroup(title = "ext") {
PrintResult(debugLayoutResult!!.ext)
PrintResult(debugLayoutResult.ext)
}
}
}

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
@@ -27,16 +28,19 @@ import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
import org.florisboard.lib.android.AndroidSettings
import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
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(
"Success! The app crashed purposely to display this beautiful screen we all love :)"
@@ -50,6 +54,7 @@ fun DevtoolsScreen() = FlorisScreen {
val context = LocalContext.current
val navController = LocalNavController.current
val extensionManager by context.extensionManager()
val scope = rememberCoroutineScope()
val (showDialog, setShowDialog) = remember { mutableStateOf(false) }
@@ -104,10 +109,21 @@ fun DevtoolsScreen() = FlorisScreen {
onClick = { setShowDialog(true) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.devtools__reset_quick_actions_to_default__label),
summary = stringRes(R.string.devtools__reset_quick_actions_to_default__summary),
onClick = {
scope.launch {
prefs.smartbar.actionArrangement.set(QuickActionArrangement.Default)
}
context.showLongToastSync(R.string.devtools__reset_quick_actions_to_default__toast_success)
},
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.devtools__reset_flag__label, "flag_name" to "isImeSetUp"),
summary = stringRes(R.string.devtools__reset_flag_is_ime_set_up__summary),
onClick = { prefs.internal.isImeSetUp.set(false) },
onClick = { scope.launch { prefs.internal.isImeSetUp.set(false) } },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
@@ -189,14 +205,14 @@ fun DevtoolsScreen() = FlorisScreen {
title = "keyboardExtensions",
summary = extensionManager.keyboardExtensions.internalModuleDir.absolutePath,
onClick = {
context.showLongToast(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
context.showLongToastSync(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
},
)
Preference(
title = "themes",
summary = extensionManager.themes.internalModuleDir.absolutePath,
onClick = {
context.showLongToast(extensionManager.themes.internalModuleDir.absolutePath)
context.showLongToastSync(extensionManager.themes.internalModuleDir.absolutePath)
},
)
}

View File

@@ -38,15 +38,16 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
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
@Composable
@@ -54,7 +55,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
title = stringRes(R.string.devtools__debuglog__title)
scrollable = false
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val clipboardManager by context.clipboardManager()
@@ -74,7 +75,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
modifier = Modifier,
text = stringRes(R.string.devtools__debuglog__copy_log),
@@ -83,7 +84,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(formattedDebugLog!!.joinToString("\n"))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
text = stringRes(R.string.devtools__debuglog__copy_for_github),
enabled = debugLog != null,

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

@@ -46,12 +46,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.content.MimeTypeFilter
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
@@ -60,24 +57,28 @@ import java.util.*
import org.florisboard.lib.android.query
import org.florisboard.lib.android.readToFile
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.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
import org.florisboard.lib.kotlin.mimeTypeFilterOf
const val FONTS = "fonts"
const val IMAGES = "images"
val MIME_TYPES = mapOf(
FONTS to listOf(
FONTS to mimeTypeFilterOf(
// Source: https://www.alienfactory.co.uk/articles/mime-types-for-web-fonts-in-bedsheet#mimeTypes
"font/*",
"application/vnd.ms-fontobject", // .eot
"application/font-woff", // .woff
"application/x-font-truetype", // .ttf
"application/x-font-opentype", // .otf
"application/font-*",
"application/x-font-*",
"application/vnd.ms-fontobject",
),
IMAGES to listOf(
IMAGES to mimeTypeFilterOf(
"image/*",
),
)
@@ -118,9 +119,9 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
val tempFile = context.cacheDir.subFile("temp_${UUID.randomUUID()}")
context.contentResolver.readToFile(uri, tempFile)
val mimeType = context.contentResolver.getType(uri)
val types = MIME_TYPES[currentImportDest!!]!!
checkNotNull(MimeTypeFilter.matches(mimeType, types.toTypedArray())) {
"Given file mime type was '$mimeType', expected one of $types"
val filter = MIME_TYPES[currentImportDest!!]!!
check(filter.matches(mimeType)) {
"Given file mime type was '$mimeType', expected one of ${filter.types}"
}
val fileName = context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME)).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return@use null
@@ -180,16 +181,16 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
dialogFile?.let { file ->
var fileNameInput by rememberSaveable { mutableStateOf(file.name) }
JetPrefAlertDialog(
title = "Rename or remove",
title = stringRes(R.string.general__properties),
confirmLabel = stringRes(R.string.action__apply),
dismissLabel = stringRes(R.string.action__cancel),
neutralLabel = stringRes(R.string.action__delete),
allowOutsideDismissal = true,
onNeutral = {
if (file.delete()) {
context.showShortToast("Successfully deleted")
context.showShortToastSync("Successfully deleted")
} else {
context.showShortToast("Failed to remove")
context.showShortToastSync("Failed to delete")
}
dialogFile = null
version++
@@ -197,18 +198,18 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
onConfirm = {
val newFile = file.parentFile!!.subFile(fileNameInput).canonicalFile
if (newFile.parentFile != file.canonicalFile.parentFile) {
context.showLongToast("Invalid file name!")
context.showLongToastSync("Invalid file name!")
return@JetPrefAlertDialog
}
if (newFile.exists()) {
context.showShortToast("Filename already exists.")
context.showShortToastSync("Filename already exists.")
return@JetPrefAlertDialog
}
val success = file.renameTo(newFile)
if (success) {
context.showShortToast("Successfully renamed")
context.showShortToastSync("Successfully renamed")
} else {
context.showShortToast("Failed to rename the file.")
context.showShortToastSync("Failed to rename the file.")
}
dialogFile = null
version++
@@ -218,6 +219,7 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
},
) {
JetPrefTextField(
labelText = stringRes(R.string.general__file_name),
value = fileNameInput,
onValueChange = { fileNameInput = it },
singleLine = true,
@@ -227,7 +229,7 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
}
FileList(
title = FONTS.replaceFirstChar { it.uppercase() },
title = stringRes(R.string.ext__editor__files__type_fonts),
icon = Icons.Default.TextFields,
files = fontFiles,
) {
@@ -236,7 +238,7 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
}
FileList(
title = IMAGES.replaceFirstChar { it.uppercase() },
title = stringRes(R.string.ext__editor__files__type_images),
icon = Icons.Default.Photo,
files = imageFiles,
) {
@@ -249,7 +251,7 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
if (dest != null && result != null) {
var fileNameInput by rememberSaveable { mutableStateOf(result.second) }
JetPrefAlertDialog(
title = "Import ${dest.substring(0, dest.length - 1)}",
title = stringRes(R.string.action__import_file),
confirmLabel = stringRes(R.string.action__add),
onConfirm = {
val fileName = fileNameInput.trim()
@@ -257,13 +259,13 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
dir.mkdirs()
val file = dir.subFile(fileName)
if (file.parentDir != workspace.extDir.subDir(dest)) {
context.showShortToast("Invalid file name")
context.showShortToastSync("Invalid file name")
} else if (file.exists()) {
context.showShortToast("File already exists")
context.showShortToastSync("File already exists")
} else {
val tempFile = result.first
if (!tempFile.renameTo(file)) {
context.showShortToast("Failed to rename file")
context.showShortToastSync("Failed to rename file")
tempFile.delete()
}
currentImportDest = null

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,16 @@ 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
import org.florisboard.lib.kotlin.io.subFile
@@ -287,7 +288,7 @@ private fun EditScreen(
stylesheetFile.writeText(stylesheet)
}.onFailure {
// TODO: better error handling
context.showLongToast(it.message.toString())
context.showLongToastSync(it.message.toString())
return
}
} else {
@@ -352,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),
)
}
@@ -627,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)
}
@@ -665,7 +666,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
when (createFrom) {
CreateFrom.EMPTY -> {
if (editor.themes.any { it.id == newId.trim() }) {
context.showLongToast("A theme with this ID already exists!")
context.showLongToastSync("A theme with this ID already exists!")
} else {
val componentEditor = ThemeExtensionComponentEditor(
id = newId.trim(),
@@ -709,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,6 +27,7 @@ import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.ExtensionDefaults
import org.florisboard.lib.android.showLongToastSync
@Composable
fun ExtensionExportScreen(id: String) {
@@ -61,9 +62,9 @@ private fun ExportScreen(ext: Extension) = FlorisScreen {
return@rememberLauncherForActivityResult
}
runCatching { extensionManager.export(ext, uri) }.onSuccess {
context.showLongToast(R.string.ext__export__success)
context.showLongToastSync(R.string.ext__export__success)
}.onFailure { error ->
context.showLongToast(R.string.ext__export__failure, "error_message" to error.localizedMessage)
context.showLongToastSync(R.string.ext__export__failure, "error_message" to error.localizedMessage)
}
navController.popBackStack()
},

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,17 @@ 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
enum class ExtensionImportScreenType(
@@ -188,10 +188,10 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
}
}.onSuccess {
workspace.close()
context.showLongToast(R.string.ext__import__success)
context.showLongToastSync(R.string.ext__import__success)
navController.popBackStack()
}.onFailure { error ->
context.showLongToast(R.string.ext__import__failure, "error_message" to error.localizedMessage)
context.showLongToastSync(R.string.ext__import__failure, "error_message" to error.localizedMessage)
}
}
}

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,17 +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) {
@@ -202,7 +202,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
}.onSuccess {
navController.popBackStack()
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.error__snackbar_message,
"error_message" to error.localizedMessage,
)

View File

@@ -19,7 +19,6 @@ package dev.patrickgold.florisboard.app.settings
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Assignment
import androidx.compose.material.icons.filled.Adb
import androidx.compose.material.icons.filled.Extension
import androidx.compose.material.icons.filled.Gesture
import androidx.compose.material.icons.filled.Language
@@ -38,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

@@ -21,12 +21,13 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.m3.LibraryDefaults
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

@@ -32,6 +32,7 @@ import androidx.compose.runtime.State
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.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -41,28 +42,33 @@ import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.cacheManager
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
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.jetpref.datastore.jetprefDatastoreDir
import dev.patrickgold.jetpref.datastore.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
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
@@ -136,6 +142,7 @@ fun BackupScreen() = FlorisScreen {
val navController = LocalNavController.current
val context = LocalContext.current
val cacheManager by context.cacheManager()
val scope = rememberCoroutineScope()
var backupDestination by remember { mutableStateOf(Backup.Destination.FILE_SYS) }
val backupFilesSelector = remember { Backup.FilesSelector() }
@@ -155,22 +162,24 @@ fun BackupScreen() = FlorisScreen {
context.contentResolver.writeFromFile(uri, backupWorkspace!!.zipFile)
backupWorkspace!!.close()
}.onSuccess {
context.showLongToast(R.string.backup_and_restore__back_up__success)
context.showLongToastSync(R.string.backup_and_restore__back_up__success)
navController.popBackStack()
}.onFailure { error ->
flogError { error.stackTraceToString() }
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
context.showLongToastSync(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
backupWorkspace = null
}
},
)
fun prepareBackupWorkspace() {
suspend fun prepareBackupWorkspace() {
val workspace = cacheManager.backupAndRestore.new()
if (backupFilesSelector.jetprefDatastore) {
context.jetprefDatastoreDir.let { dir ->
dir.copyRecursively(workspace.inputDir.subDir(dir.name))
}
val fileBasedStorage = workspace.inputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
.let { FileBasedStorage(it.path) }
FlorisPreferenceStore.export(fileBasedStorage).getOrThrow()
}
val workspaceFilesDir = workspace.inputDir.subDir("files")
if (backupFilesSelector.imeKeyboard) {
@@ -185,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) {
@@ -225,7 +235,7 @@ fun BackupScreen() = FlorisScreen {
backupWorkspace = workspace
}
fun prepareAndPerformBackup() {
suspend fun prepareAndPerformBackup() {
runCatching {
if (backupWorkspace == null || backupWorkspace!!.isClosed()) {
prepareBackupWorkspace()
@@ -265,7 +275,7 @@ fun BackupScreen() = FlorisScreen {
)
ButtonBarButton(
onClick = {
prepareAndPerformBackup()
scope.launch { prepareAndPerformBackup() }
},
text = stringRes(R.string.action__back_up),
enabled = backupFilesSelector.atLeastOneSelected(),

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
@@ -47,6 +48,7 @@ import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
import org.florisboard.lib.compose.stringRes
@Composable
@@ -154,6 +156,11 @@ fun OtherScreen() = FlorisScreen {
},
enabledIf = { AndroidVersion.ATMOST_API28_P },
)
Preference(
icon = ImageVector.vectorResource(R.drawable.ic_keyboard_keys),
title = stringRes(R.string.physical_keyboard__title),
onClick = { navController.navigate(Routes.Settings.PhysicalKeyboard) },
)
Preference(
icon = Icons.Default.Adb,
title = stringRes(R.string.devtools__title),

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.settings.advanced
import android.content.Intent
import android.content.res.Configuration
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.compose.stringRes
@Composable
fun PhysicalKeyboardScreen() = FlorisScreen {
title = stringRes(R.string.physical_keyboard__title)
val context = LocalContext.current
val physicalKeyboardAttached by remember {
mutableStateOf(context.resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS)
}
val activityForResult = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { }
content {
if (physicalKeyboardAttached) {
Preference(
title = stringRes(R.string.physical_keyboard__system_settings__title),
summary = stringRes(R.string.physical_keyboard__system_settings__summary),
onClick = {
activityForResult.launch(Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS))
}
)
} else {
Preference(
title = stringRes(R.string.physical_keyboard__system_settings__title),
summary = stringRes(R.string.physical_keyboard__system_settings__summary_not_attached),
)
}
SwitchPreference(
pref = prefs.physicalKeyboard.showOnScreenKeyboard,
title = stringRes(R.string.physical_keyboard__show_on_screen_keyboard__title),
summary = stringRes(R.string.physical_keyboard__show_on_screen_keyboard__summary),
)
}
}

View File

@@ -43,47 +43,46 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.clipboardManager
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.JetPref
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
const val PACKAGE_NAME = "dev.patrickgold.florisboard"
const val BACKUP_ARCHIVE_FILE_NAME = "backup.zip"
enum class Mode {
MERGE,
ERASE_AND_OVERWRITE;
}
}
@Composable
@@ -91,13 +90,12 @@ fun RestoreScreen() = FlorisScreen {
title = stringRes(R.string.backup_and_restore__restore__title)
previewFieldVisible = false
val prefs by florisPreferenceModel()
val navController = LocalNavController.current
val context = LocalContext.current
val cacheManager by context.cacheManager()
val restoreFilesSelector = remember { Backup.FilesSelector() }
var restoreMode by remember { mutableStateOf(Restore.Mode.MERGE) }
var importStrategy by remember { mutableStateOf(ImportStrategy.Merge) }
// TODO: rememberCoroutineScope() is unusable because it provides the scope in a cancelled state, which does
// not make sense at all. I suspect that this is a bug and once it is resolved we can use it here again.
val restoreScope = remember { CoroutineScope(Dispatchers.Main) }
@@ -138,7 +136,7 @@ fun RestoreScreen() = FlorisScreen {
}
restoreWorkspace = workspace
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)
@@ -148,14 +146,14 @@ fun RestoreScreen() = FlorisScreen {
suspend fun performRestore() {
val workspace = restoreWorkspace!!
val shouldReset = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE
val shouldReset = importStrategy == ImportStrategy.Erase
if (restoreFilesSelector.jetprefDatastore) {
val datastoreFile = workspace.outputDir
.subDir(JetPref.JETPREF_DIR_NAME)
.subFile("${prefs.name}.${JetPref.JETPREF_FILE_EXT}")
if (datastoreFile.exists()) {
prefs.datastorePersistenceHandler?.loadPrefs(datastoreFile, shouldReset)
prefs.datastorePersistenceHandler?.persistPrefs()
val file = workspace.outputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
if (file.exists()) {
val fileBasedStorage = FileBasedStorage(file.path)
FlorisPreferenceStore.import(importStrategy, fileBasedStorage).getOrThrow()
}
}
val workspaceFilesDir = workspace.outputDir.subDir("files")
@@ -275,16 +273,16 @@ fun RestoreScreen() = FlorisScreen {
) {
RadioListItem(
onClick = {
restoreMode = Restore.Mode.MERGE
importStrategy = ImportStrategy.Merge
},
selected = restoreMode == Restore.Mode.MERGE,
selected = importStrategy == ImportStrategy.Merge,
text = stringRes(R.string.backup_and_restore__restore__mode_merge),
)
RadioListItem(
onClick = {
restoreMode = Restore.Mode.ERASE_AND_OVERWRITE
importStrategy = ImportStrategy.Erase
},
selected = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE,
selected = importStrategy == ImportStrategy.Erase,
text = stringRes(R.string.backup_and_restore__restore__mode_erase_and_overwrite),
)
}
@@ -293,7 +291,7 @@ fun RestoreScreen() = FlorisScreen {
runCatching {
restoreDataFromFileSystemLauncher.launch("*/*")
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)

View File

@@ -18,14 +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
@@ -39,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 },
)
@@ -75,54 +79,84 @@ fun ClipboardScreen() = FlorisScreen {
title = stringRes(R.string.pref__clipboard__enable_clipboard_history__label),
summary = stringRes(R.string.pref__clipboard__enable_clipboard_history__summary),
)
DialogSliderPreference(
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),
valueLabel = { numGridColumns ->
if (numGridColumns == CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO) {
stringRes(R.string.general__auto)
} else {
numGridColumns.toString()
}
},
min = 0,
max = 10,
stepIncrement = 1,
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
@@ -68,7 +65,11 @@ 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.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)
@@ -143,16 +144,16 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToast("Database handle is null, failed to import")
context.showLongToastSync("Database handle is null, failed to import")
return@rememberLauncherForActivityResult
}
runCatching {
db.importCombinedList(context, uri)
}.onSuccess {
buildUi()
context.showLongToast(R.string.settings__udm__dictionary_import_success)
context.showLongToastSync(R.string.settings__udm__dictionary_import_success)
}.onFailure { error ->
context.showLongToast("Error: ${error.localizedMessage}")
context.showLongToastSync("Error: ${error.localizedMessage}")
}
},
)
@@ -168,15 +169,15 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToast("Database handle is null, failed to export")
context.showLongToastSync("Database handle is null, failed to export")
return@rememberLauncherForActivityResult
}
runCatching {
db.exportCombinedList(context, uri)
}.onSuccess {
context.showLongToast(R.string.settings__udm__dictionary_export_success)
context.showLongToastSync(R.string.settings__udm__dictionary_export_success)
}.onFailure { error ->
context.showLongToast("Error: ${error.localizedMessage}")
context.showLongToastSync("Error: ${error.localizedMessage}")
}
},
)

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

@@ -20,18 +20,17 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.systemVibratorOrNull
import org.florisboard.lib.android.vibrate
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
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
@@ -138,8 +137,6 @@ fun InputFeedbackScreen() = FlorisScreen {
summary = { strength ->
if (vibrator == null || !vibrator.hasVibrator()) {
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_no_vibrator)
} else if (AndroidVersion.ATMOST_API25_N_MR1) {
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_unsupported_android_version)
} else if (!vibrator.hasAmplitudeControl()) {
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_no_amplitude_ctrl)
} else {
@@ -157,7 +154,7 @@ fun InputFeedbackScreen() = FlorisScreen {
prefs.inputFeedback.hapticEnabled isEqualTo true &&
prefs.inputFeedback.hapticVibrationMode isEqualTo HapticVibrationMode.USE_VIBRATOR_DIRECTLY &&
vibrator != null && vibrator.hasVibrator() &&
AndroidVersion.ATLEAST_API26_O && vibrator.hasAmplitudeControl()
vibrator.hasAmplitudeControl()
},
)
SwitchPreference(

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

@@ -41,26 +41,27 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.florisPreferenceModel
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) {
MANAGE("manage-installed-language-packs");
@@ -75,7 +76,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
else -> error("LanguagePack manager screen action must not be null")
})
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val navController = LocalNavController.current
val context = LocalContext.current
val extensionManager by context.extensionManager()
@@ -199,7 +200,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
runCatching {
extensionManager.delete(languagePackExtToDelete!!)
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.error__snackbar_message,
"error_message" to error.localizedMessage,
)

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

@@ -44,15 +44,15 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
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"
@@ -61,7 +61,7 @@ fun SelectLocaleScreen() = FlorisScreen {
title = stringRes(R.string.settings__localization__subtype_select_locale)
scrollable = false
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val navController = LocalNavController.current
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()

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
@@ -58,9 +59,9 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Observer
import androidx.lifecycle.compose.LocalLifecycleOwner
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.core.SubtypeJsonConfig
@@ -74,19 +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 kotlinx.serialization.encodeToString
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()
@@ -186,7 +188,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
val selectValue = stringRes(R.string.settings__localization__subtype_select_placeholder)
val selectListValues = remember(selectValue) { listOf(selectValue) }
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val navController = LocalNavController.current
val context = LocalContext.current
val configuration = LocalConfiguration.current
@@ -366,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)) {
@@ -375,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)
@@ -405,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),
)
}
@@ -433,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)) {
@@ -451,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),
)
}
@@ -557,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

@@ -27,15 +27,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
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
@@ -52,7 +52,7 @@ fun MediaScreen() = FlorisScreen {
previewFieldVisible = true
iconSpaceReserved = true
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
var shouldDelete by remember { mutableStateOf<ShouldDelete?>(null) }
val scope = rememberCoroutineScope()

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 {
@@ -68,7 +68,7 @@ fun SmartbarScreen() = FlorisScreen {
// TODO: schedule to remove this preference in the future, but keep it for now so users
// know why the setting is not available anymore. Also force enable it for UI display.
SideEffect {
prefs.smartbar.sharedActionsAutoExpandCollapse.set(true)
// prefs.smartbar.sharedActionsAutoExpandCollapse.set(true)
}
SwitchPreference(
prefs.smartbar.sharedActionsAutoExpandCollapse,

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

@@ -16,6 +16,7 @@
package dev.patrickgold.florisboard.app.settings.theme
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -41,6 +42,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ManageSearch
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -60,8 +62,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
@@ -73,12 +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.stringRes
import dev.patrickgold.florisboard.lib.ext.ExtensionValidation
import dev.patrickgold.florisboard.lib.rememberValidationResult
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
@@ -89,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
@@ -622,35 +628,67 @@ private fun PropertyValueEditor(
mutableStateOf(value.uri)
}
Column(modifier) {
val workspaceFiles = remember {
buildList {
addAll(workspace.extDir.subDir(FONTS).listFiles { it.isFile }.orEmpty().asList())
addAll(workspace.extDir.subDir(IMAGES).listFiles { it.isFile }.orEmpty().asList())
}
val fontFiles = remember {
workspace.extDir.subDir(FONTS).listFiles { it.isFile }.orEmpty().asList()
}
val imageFiles = remember {
workspace.extDir.subDir(IMAGES).listFiles { it.isFile }.orEmpty().asList()
}
var showSelectFileDialog by rememberSaveable { mutableStateOf(false) }
if (showSelectFileDialog) {
@Composable
fun ClickableFilesWithHeading(title: String, files: List<File>) {
ListItem(
headlineContent = {
Text(
text = title,
color = MaterialTheme.colorScheme.secondary,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
colors = ListItemDefaults.colors(
containerColor = AlertDialogDefaults.containerColor
),
)
files.forEach { file ->
JetPrefListItem(
modifier = Modifier.clickable {
val relPath = file.path.removePrefix(workspace.extDir.path)
inputStr = "flex:" + Uri.encode(relPath, "/")
onValueChange(SnyggUriValue(inputStr))
showSelectFileDialog = false
},
text = file.name,
colors = ListItemDefaults.colors(
containerColor = AlertDialogDefaults.containerColor
),
)
}
}
JetPrefAlertDialog(
title = "Select file",
title = stringRes(R.string.settings__theme_editor__file_selector_dialog_title),
dismissLabel = stringRes(R.string.action__cancel),
onDismiss = {
showSelectFileDialog = false
},
contentPadding = PaddingValues(horizontal = 8.dp),
scrollModifier = Modifier.florisVerticalScroll(),
) {
Column {
workspaceFiles.forEach { file ->
JetPrefListItem(
modifier = Modifier.clickable {
val relPath = file.path.removePrefix(workspace.extDir.path)
inputStr = "flex:$relPath"
onValueChange(SnyggUriValue(inputStr))
showSelectFileDialog = false
},
text = file.name,
colors = ListItemDefaults.colors(
containerColor = AlertDialogDefaults.containerColor
)
if (fontFiles.isNotEmpty()) {
ClickableFilesWithHeading(stringRes(R.string.ext__editor__files__type_fonts), fontFiles)
}
if (imageFiles.isNotEmpty()) {
ClickableFilesWithHeading(stringRes(R.string.ext__editor__files__type_images), imageFiles)
}
if (fontFiles.isEmpty() && imageFiles.isEmpty()) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.settings__theme_editor__file_selector_no_files_text,
"action_title" to stringRes(R.string.ext__editor__files__title)),
style = MaterialTheme.typography.bodySmall,
)
}
}

View File

@@ -69,6 +69,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.input.InputKeyEventReceiver
import dev.patrickgold.florisboard.ime.input.InputShiftState
import dev.patrickgold.florisboard.ime.keyboard.ComputingEvaluator
@@ -84,24 +85,27 @@ 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
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import dev.patrickgold.jetpref.material.ui.JetPrefTextFieldDefaults
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.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
import org.florisboard.lib.snygg.SnyggElementRule
import org.florisboard.lib.snygg.SnyggRule
import org.florisboard.lib.snygg.SnyggSelector
import org.florisboard.lib.snygg.ui.NonNullSaver
import kotlin.reflect.KClass
private val TransparentTextSelectionColors = TextSelectionColors(
handleColor = Color.Transparent,
@@ -240,11 +244,13 @@ internal fun EditRuleDialog(
}
}
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_selectors)) {
Row(modifier = Modifier.florisHorizontalScroll()) {
//TODO: LazyRow
Row(
modifier = Modifier.florisHorizontalScroll(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
// TODO: avoid code duplication
FlorisChip(
onClick = { updateCurrentRule(SnyggSelector.PRESSED) },
modifier = Modifier.padding(end = 4.dp),
text = when (level) {
SnyggLevel.DEVELOPER -> SnyggSelector.PRESSED.id
else -> stringRes(R.string.snygg__rule_selector__pressed)
@@ -253,7 +259,6 @@ internal fun EditRuleDialog(
)
FlorisChip(
onClick = { updateCurrentRule(SnyggSelector.FOCUS) },
modifier = Modifier.padding( end = 4.dp),
text = when (level) {
SnyggLevel.DEVELOPER -> SnyggSelector.FOCUS.id
else -> stringRes(R.string.snygg__rule_selector__focus)
@@ -262,7 +267,6 @@ internal fun EditRuleDialog(
)
FlorisChip(
onClick = { updateCurrentRule(SnyggSelector.HOVER) },
modifier = Modifier.padding( end = 4.dp),
text = when (level) {
SnyggLevel.DEVELOPER -> SnyggSelector.HOVER.id
else -> stringRes(R.string.snygg__rule_selector__hover)
@@ -312,22 +316,17 @@ internal fun EditRuleDialog(
)
},
) {
Text(
modifier = Modifier.padding(vertical = 4.dp),
text = stringRes(
if (codes.isEmpty()) {
R.string.settings__theme_editor__no_codes_defined
} else {
R.string.settings__theme_editor__codes_defined
}
),
fontStyle = FontStyle.Italic,
)
FlowRow {
if (codes.isEmpty()) {
Text(
text = stringRes(R.string.settings__theme_editor__no_codes_defined),
fontStyle = FontStyle.Italic,
)
}
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
for (code in codes) {
FlorisChip(
onClick = { editCodeDialogValue = code },
text = code.toString(),
text = code,
selected = editCodeDialogValue == code,
shape = MaterialTheme.shapes.medium,
)
@@ -335,78 +334,23 @@ internal fun EditRuleDialog(
}
}
val shiftStateUnshifted = remember(currentRule) {
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.UNSHIFTED.attrName()) == true
}
val shiftStateShiftedManual = remember(currentRule) {
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.SHIFTED_MANUAL.attrName()) == true
}
val shiftStateShiftedAutomatic = remember(currentRule) {
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.SHIFTED_AUTOMATIC.attrName()) == true
}
val shiftStateCapsLock = remember(currentRule) {
attributes[FlorisImeUi.Attr.ShiftState]?.contains(InputShiftState.CAPS_LOCK.attrName()) == true
}
DialogProperty(text = stringRes(R.string.settings__theme_editor__rule_shift_states)) {
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
FlorisChip(
onClick = {
currentRule = copy(
attributes = attributes.toggling(
FlorisImeUi.Attr.ShiftState to InputShiftState.UNSHIFTED.attrName()
)
)
},
text = when (level) {
SnyggLevel.DEVELOPER -> InputShiftState.UNSHIFTED.attrName()
else -> stringRes(R.string.enum__input_shift_state__unshifted)
},
selected = shiftStateUnshifted,
)
FlorisChip(
onClick = {
currentRule = copy(
attributes = attributes.toggling(
FlorisImeUi.Attr.ShiftState to InputShiftState.SHIFTED_MANUAL.attrName()
)
)
},
text = when (level) {
SnyggLevel.DEVELOPER -> InputShiftState.SHIFTED_MANUAL.attrName()
else -> stringRes(R.string.enum__input_shift_state__shifted_manual)
},
selected = shiftStateShiftedManual,
)
FlorisChip(
onClick = {
currentRule = copy(
attributes = attributes.toggling(
FlorisImeUi.Attr.ShiftState to InputShiftState.SHIFTED_AUTOMATIC.attrName()
)
)
},
text = when (level) {
SnyggLevel.DEVELOPER -> InputShiftState.SHIFTED_AUTOMATIC.attrName()
else -> stringRes(R.string.enum__input_shift_state__shifted_automatic)
},
selected = shiftStateShiftedAutomatic,
)
FlorisChip(
onClick = {
currentRule = copy(
attributes = attributes.toggling(
FlorisImeUi.Attr.ShiftState to InputShiftState.CAPS_LOCK.attrName()
)
)
},
text = when (level) {
SnyggLevel.DEVELOPER -> InputShiftState.CAPS_LOCK.attrName()
else -> stringRes(R.string.enum__input_shift_state__caps_lock)
},
selected = shiftStateCapsLock,
)
}
}
EnumLikeAttributeBox(
text = stringRes(R.string.settings__theme_editor__rule_modes),
enumClass = KeyboardMode::class,
attribute = FlorisImeUi.Attr.Mode,
attributes = attributes,
setAttributes = { currentRule = copy(attributes = it) },
level = level,
)
EnumLikeAttributeBox(
text = stringRes(R.string.settings__theme_editor__rule_shift_states),
enumClass = InputShiftState::class,
attribute = FlorisImeUi.Attr.ShiftState,
attributes = attributes,
setAttributes = { currentRule = copy(attributes = it) },
level = level,
)
}
}
}
@@ -463,7 +407,7 @@ private fun EditCodeValueDialog(
}
if (!isFlorisBoardEnabled || !isFlorisBoardSelected) {
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToast(
lastRecordingToast = context.showShortToastSync(
R.string.settings__theme_editor__code_recording_requires_default_ime_floris,
"app_name" to context.stringRes(R.string.floris_app_name),
)
@@ -489,12 +433,12 @@ private fun EditCodeValueDialog(
val defaultReceiver = keyboardManager.inputEventDispatcher.keyEventReceiver
keyboardManager.inputEventDispatcher.keyEventReceiver = receiver
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToast(R.string.settings__theme_editor__code_recording_started)
lastRecordingToast = context.showShortToastSync(R.string.settings__theme_editor__code_recording_started)
focusRequester.requestFocus()
onDispose {
keyboardManager.inputEventDispatcher.keyEventReceiver = defaultReceiver
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToast(R.string.settings__theme_editor__code_recording_stopped)
lastRecordingToast = context.showShortToastSync(R.string.settings__theme_editor__code_recording_stopped)
}
}
}
@@ -715,3 +659,79 @@ private fun TextKeyDataPreviewBox(
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun <V : Any> EnumLikeAttributeBox(
text: String,
enumClass: KClass<V>,
attribute: String,
attributes: SnyggAttributes,
setAttributes: (SnyggAttributes) -> Unit,
level: SnyggLevel,
) {
val allEntries = enumDisplayEntriesOf(enumClass)
val (alreadyAddedEntries, notYetAddedEntries) = remember(attributes, attribute) {
allEntries.partition { entry ->
attributes[attribute]?.contains(entry.key.toString()) == true
}
}
var showAddDialog by remember { mutableStateOf(false) }
DialogProperty(
text = text,
trailingIconTitle = {
FlorisIconButton(
onClick = { showAddDialog = true },
modifier = Modifier.offset(x = 12.dp),
icon = Icons.Default.Add,
)
},
) {
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
for (entry in alreadyAddedEntries) {
FlorisChip(
onClick = {
setAttributes(attributes.excluding(attribute to entry.key.toString()))
},
text = entry.label,
)
}
if (alreadyAddedEntries.isEmpty()) {
Text(
text = stringRes(R.string.settings__theme_editor__no_codes_defined),
fontStyle = FontStyle.Italic,
)
}
}
}
if (showAddDialog) {
JetPrefAlertDialog(
title = stringRes(R.string.action__add),
dismissLabel = stringRes(R.string.action__cancel),
onDismiss = { showAddDialog = false },
) {
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
for (entry in notYetAddedEntries) {
FlorisChip(
onClick = {
setAttributes(attributes.including(attribute to entry.key.toString()))
showAddDialog = false
},
text = when (level) {
SnyggLevel.DEVELOPER -> entry.key.toString()
else -> entry.label
},
)
}
}
if (notYetAddedEntries.isEmpty()) {
Text(
text = stringRes(R.string.settings__theme_editor__no_enum_value_to_add_anymore),
fontStyle = FontStyle.Italic,
)
}
}
}
}

View File

@@ -20,13 +20,13 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
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.app.florisPreferenceModel
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)
@@ -37,7 +37,7 @@ fun FineTuneDialog(onDismiss: () -> Unit) {
onDismiss = onDismiss,
contentPadding = FineTuneContentPadding,
) {
PreferenceLayout(florisPreferenceModel(), iconSpaceReserved = false) {
PreferenceLayout(FlorisPreferenceStore, iconSpaceReserved = false) {
ListPreference(
listPref = prefs.theme.editorLevel,
title = stringRes(R.string.settings__theme_editor__fine_tune__level),

View File

@@ -30,7 +30,6 @@ import androidx.compose.material.icons.automirrored.filled.FormatAlignLeft
import androidx.compose.material.icons.automirrored.filled.FormatAlignRight
import androidx.compose.material.icons.automirrored.filled.WrapText
import androidx.compose.material.icons.filled.AttachFile
import androidx.compose.material.icons.filled.CheckBox
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
import androidx.compose.material.icons.filled.FontDownload
import androidx.compose.material.icons.filled.FormatAlignCenter
@@ -57,7 +56,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.snygg.value.SnyggCutCornerDpShapeValue
import org.florisboard.lib.snygg.value.SnyggDefinedVarValue
@@ -121,7 +120,7 @@ internal fun SnyggValueIcon(
modifier: Modifier = Modifier,
spec: SnyggValueIcon.Spec = SnyggValueIcon.Normal,
) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val accentColor by prefs.theme.accentColor.observeAsState()

View File

@@ -70,9 +70,9 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.apptheme.Shapes
import dev.patrickgold.florisboard.app.ext.ExtensionComponentView
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
@@ -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,6 +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
@@ -145,7 +146,7 @@ fun ThemeEditorScreen(
title = stringRes(R.string.ext__editor__edit_component__title_theme)
scrollable = false
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val themeManager by context.themeManager()
@@ -186,7 +187,7 @@ fun ThemeEditorScreen(
}.also { editor.stylesheetEditor = it }
}
val definedVariables = remember(stylesheetEditor.rules) {
val definedVariables = remember(stylesheetEditor.rules, workspace.version) {
stylesheetEditor.rules.firstNotNullOfOrNull { (rule, propertySet) ->
if (rule is SnyggAnnotationRule.Defines && propertySet is SnyggSinglePropertySetEditor) {
propertySet.properties
@@ -196,7 +197,7 @@ fun ThemeEditorScreen(
} ?: emptyMap()
}
val fontNames = remember(stylesheetEditor.rules) {
val fontNames = remember(stylesheetEditor.rules, workspace.version) {
stylesheetEditor.rules.mapNotNull { (rule, _) ->
if (rule is SnyggAnnotationRule.Font) {
rule.fontName
@@ -309,13 +310,14 @@ fun ThemeEditorScreen(
}
DisposableEffect(workspace.version) {
themeManager.previewThemeInfo = ThemeManager.ThemeInfo.DEFAULT.copy(
themeManager.previewThemeInfo.value = ThemeManager.ThemeInfo.DEFAULT.copy(
name = extPreviewTheme(System.currentTimeMillis().toString()),
config = editor.build(),
stylesheet = stylesheetEditor.build(),
loadedDir = workspace.extDir,
)
onDispose {
themeManager.previewThemeInfo = null
themeManager.previewThemeInfo.value = null
}
}
@@ -632,7 +634,7 @@ private fun ComponentMetaEditorDialog(
if (!allFieldsValid) {
showValidationErrors = true
} else if (id != editor.id && (workspace.editor as? ThemeExtensionEditor)?.themes?.find { it.id == id.trim() } != null) {
context.showLongToast("A theme with this ID already exists!")
context.showLongToastSync("A theme with this ID already exists!")
} else {
workspace.update {
editor.id = id.trim()
@@ -730,7 +732,6 @@ private fun SnyggRuleRow(
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = 0.56f),
fontFamily = FontFamily.Monospace,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -750,7 +751,7 @@ private fun SnyggRuleRow(
text = context.translateElementName(rule, level),
style = MaterialTheme.typography.bodyMedium,
fontFamily = FontFamily.Monospace,
maxLines = 1,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
Row(modifier = Modifier.fillMaxWidth()) {

View File

@@ -29,22 +29,24 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.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) {
SELECT_DAY("select-day"),
@@ -60,10 +62,11 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
})
previewFieldVisible = true
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val extensionManager by context.extensionManager()
val themeManager by context.themeManager()
val scope = rememberCoroutineScope()
val indexedThemeExtensions by extensionManager.themes.observeAsNonNullState()
val extGroupedThemes = remember(indexedThemeExtensions) {
@@ -83,7 +86,7 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
val extComponentName = ExtensionComponentName(extId, componentId)
when (action) {
ThemeManagerScreenAction.SELECT_DAY,
ThemeManagerScreenAction.SELECT_NIGHT -> {
ThemeManagerScreenAction.SELECT_NIGHT -> scope.launch {
getThemeIdPref().set(extComponentName)
}
}
@@ -96,9 +99,9 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
content {
DisposableEffect(activeThemeId) {
themeManager.previewThemeId = activeThemeId
themeManager.previewThemeId.value = activeThemeId
onDispose {
themeManager.previewThemeId = null
themeManager.previewThemeId.value = null
}
}
val grayColor = LocalContentColor.current.copy(alpha = 0.56f)

View File

@@ -16,19 +16,18 @@
package dev.patrickgold.florisboard.app.settings.theme
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Brightness2
import androidx.compose.material.icons.filled.BrightnessAuto
import androidx.compose.material.icons.filled.ColorLens
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.WbTwilight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
@@ -37,17 +36,17 @@ import dev.patrickgold.florisboard.app.ext.AddonManagementReferenceBox
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.FlorisInfoCard
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
import dev.patrickgold.jetpref.datastore.ui.ColorPickerPreference
import dev.patrickgold.jetpref.datastore.ui.ListPreference
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,41 +59,21 @@ fun ThemeScreen() = FlorisScreen {
@Composable
fun ThemeManager.getThemeLabel(id: ExtensionComponentName): String {
val configs by indexedThemeConfigs.observeAsState()
configs?.get(id)?.let { return it.label }
val configs by indexedThemeConfigs.collectAsState()
configs.first[id]?.let { return it.label }
return id.toString()
}
content {
val themeMode by prefs.theme.mode.observeAsState()
val dayThemeId by prefs.theme.dayThemeId.observeAsState()
val nightThemeId by prefs.theme.nightThemeId.observeAsState()
/*Card(modifier = Modifier.padding(8.dp)) {
Column(modifier = Modifier.padding(8.dp)) {
Text("If you want to give feedback on the new stylesheet editor and theme engine, please do so in below linked feedback thread:\n")
Button(onClick = {
context.launchUrl("https://github.com/florisboard/florisboard/discussions/1531")
}) {
Text("Open Feedback Thread")
}
}
}*/
ListPreference(
prefs.theme.mode,
icon = Icons.Default.BrightnessAuto,
title = stringRes(R.string.pref__theme__mode__label),
entries = enumDisplayEntriesOf(ThemeMode::class),
)
if (themeMode == ThemeMode.FOLLOW_TIME) {
FlorisInfoCard(
modifier = Modifier.padding(8.dp),
text = """
The theme mode "Follow time" is not available in this beta release.
""".trimIndent()
)
}
Preference(
icon = Icons.Default.LightMode,
title = stringRes(R.string.pref__theme__day),
@@ -113,6 +92,18 @@ fun ThemeScreen() = FlorisScreen {
navController.navigate(Routes.Settings.ThemeManager(ThemeManagerScreenAction.SELECT_NIGHT))
},
)
LocalTimePickerPreference(
pref = prefs.theme.sunriseTime,
title = stringRes(R.string.pref__theme__sunrise_time__label),
icon = Icons.Default.WbTwilight,
enabledIf = { prefs.theme.mode isEqualTo ThemeMode.FOLLOW_TIME },
)
LocalTimePickerPreference(
pref = prefs.theme.sunsetTime,
title = stringRes(R.string.pref__theme__sunset_time__label),
icon = Icons.Default.Brightness2,
enabledIf = { prefs.theme.mode isEqualTo ThemeMode.FOLLOW_TIME },
)
ColorPickerPreference(
pref = prefs.theme.accentColor,
title = stringRes(R.string.pref__theme__theme_accent_color__label),

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

Some files were not shown because too many files have changed in this diff Show More