Compare commits

...

219 Commits

Author SHA1 Message Date
Patrick Goldinger
bc5ed3475c Release v0.4.6 2025-03-04 22:47:39 +01:00
Patrick Goldinger
871ff0acb1 Merge branch 'main' into release/0.4 2025-03-04 22:45:30 +01:00
florisboard-bot
34f8fec2f6 Update translations from Crowdin 2025-03-04 22:39:56 +01:00
npnpatidar
7199fcdf12 Devanagari Script and Hindi Language Layout Added (#2723)
* test commit

* hi-IN popupmappings id added

* basic layout

* heart replaces with space

* code corrected

* lots of other keys added

* all characters have been added

* popmappings rearranged

* some positional change

* * and \ won't popup due to क्ष and  ज्ञ

* ओ औ code correced

* some vovels reordered

* Hindi does not capitalization concept
2025-03-04 22:27:16 +01:00
Lars Mühlbauer
8319f563b9 Add support for switching the subtype on space bar long press (#2781)
* Add initial support for switching the subtype on space bar long press

* Fix keyboard presses behind the header panel

* Fix taps behind sheet

* Extract string ressource and refactor code

* Instant select subtype

* Persist suptype change in prefs
2025-03-04 22:23:24 +01:00
Lars Mühlbauer
45efe52159 Add new screenshot generator and update fastlane images (#2787)
* Add new screenshot generator and update fastlane images

* Update featureGraphic.png to match g-play requirements
2025-03-04 21:46:54 +01:00
Lars Mühlbauer
7a286b932b Update snygg dynamic color scheme on wallpaper change (#2778)
* Update snygg dynamic color scheme on wallpaper change

* Move broadcast receiver handling to FlorisImeService and properly unregister the receiver

* Set systembar color on stylesheet change
2025-03-04 21:08:45 +01:00
Lars Mühlbauer
df3f84e96d Fix text being rendered behind the top app bar (#2758)
* Fix text being rendered behind the top app bar

This commit fixes an issue where the text was rendered
behind the top app bar in the crash dialog activity by
removing the top app bar in the style and adding a top
app bar in the layout XML.

* Fix toolbar rendering messed up in crash dialog screen

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2025-02-14 13:13:44 +01:00
Lars Mühlbauer
4cc2216940 Fix switching back from voice ime (#2684) (#2759)
This commit fixes an issue where the voice ime was
not possible to switch back to FlorisBoard, because
the keyboard subtype was not specified.
2025-02-14 12:23:46 +01:00
Lars Mühlbauer
21fdd31c88 Fix BottomSheetContent drawing behind system nav bar (#2757)
This commit fixes an issue where the BottomSheetContent
was drawn behind the system navigation bar. To fix this
issue, I applied the saveDrawingPadding Modifier to the
AnimatedVisibility. There was a color problem on the
system navigation bar, so I set the background color to
SheetOutOfBoundsBgColorInactive. Now the system nav bar
is colored the right way.
2025-02-14 12:20:13 +01:00
Lars Mühlbauer
53c6cb52ca Fix crash when opening clipboard history (#2756)
This Commit fixes a crash when opening the clipboard history
with a theme which does not provide a sp size for the header
or the clipboard item. The sp value is now saveTimes multiplied
with 1f.
2025-02-14 12:15:39 +01:00
Patrick Goldinger
9b12213b3e Possible fixes for all keys invisible bug (#2743)
* Fix possible race condition in extension manager init

* Fix deprecation warnings in Kotlin contracts

* Fix race condition in KeyboardManagerResources

* Add debug glide.enabled toggle
2025-02-02 22:35:25 +01:00
Patrick Goldinger
659a5062ab Merge pull request #2750 from florisboard/feat/implement-custom-material-you-color
Implement custom material you colors
2025-02-02 20:17:29 +01:00
lm41
086e5f7782 Add new JetPrefColorPicker for the keyboard accent color.
This commit introduces the new color schemes for spoofed
material you themes. The material you themes can now be
used on any android version that is supported by FlorisBoard
and not just on android 12+.
2025-02-02 19:40:04 +01:00
lm41
ae73e64cd2 Add new JetPrefColorPicker for the settings accent color. 2025-02-02 19:35:16 +01:00
lm41
9f5e8cddd5 Add :lib:color package
This package includes a set of material 3 color schemes
and a map of the corresponding colors to the color schemes.
Please visit the packages README.md for information
on how the schemes were created.
2025-02-02 19:30:39 +01:00
Lars Mühlbauer
3724495d4f Remove code duplication in setup screen (#2742)
* Remove code duplication in setup screen

* fixup! Remove code duplication in setup screen
2025-01-16 22:52:25 +01:00
Patrick Goldinger
a146c6f846 Release v0.4.5 2025-01-13 02:10:22 +01:00
Patrick Goldinger
40ad937384 Merge branch 'main' into release/0.4 2025-01-13 02:08:25 +01:00
Patrick Goldinger
15b5dd9e3e Fix issues with all keys invisible detection (#2737) 2025-01-13 02:07:05 +01:00
Patrick Goldinger
8408cb5133 Release v0.4.4 2025-01-13 00:54:07 +01:00
Patrick Goldinger
8b90cdf06e Merge branch 'main' into release/0.4 2025-01-13 00:44:39 +01:00
florisboard-bot
36f06e0b4b Update translations from Crowdin 2025-01-13 00:34:27 +01:00
Patrick Goldinger
eacb5259ec Merge pull request #2736 from florisboard/chore/add-copyright-templates-to-idea
Update copyright to the new copyright holders
2025-01-13 00:30:04 +01:00
Patrick Goldinger
e7a82a3123 Update README.md/LICENSE 2025-01-13 00:22:59 +01:00
Patrick Goldinger
123b3a6d39 Update copyright year and name in all source files 2025-01-13 00:05:38 +01:00
lm41
9531ad4c36 Add copyright templates for usage with android studio and intellij 2025-01-12 23:56:59 +01:00
Lars Mühlbauer
54ed179ead Fix NoSuchMethodError: removeFirst() on android 14 and lower (#2734)
* Fix NoSuchMethodError: removeFirst() on android 14 and lower

* Add RequiredApi annotations to SnyggMaterialYou classes

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2025-01-12 23:25:20 +01:00
Patrick Goldinger
2dc7efd811 Add better detection for all keys visible bug (#2735)
* Add better detection for all keys visible bug

* Remove on purpose fail for extension manager again
2025-01-12 23:23:38 +01:00
Lars Mühlbauer
01d0b02e6d Fix keyboard height calculation on android 15 to match previous android versions (#2722) 2025-01-11 17:31:51 +01:00
Patrick Goldinger
67c083131a Upgrade to Android 15 (#2733) 2025-01-11 17:17:35 +01:00
Lars Mühlbauer
03cde7296e Add option to delete EmojiHistory from settings screen (#2732)
* Add possibility to delete EmojiHistory from settings screen

* Apply review suggestions

* fixup! Apply review suggestions

* Fix stylistic issues
2025-01-11 16:12:23 +01:00
Lars Mühlbauer
32d26bcf80 Add subtypes and installed extensions to debug logs (#2731)
* Add subtypes and installed extensions to debug logs

* Fix debug log output

* Apply suggestion
2025-01-11 15:59:47 +01:00
Lars Mühlbauer
e61a2c6e82 Fix colors in add subtype screen and enhance subtype presets dialog (#2729) 2025-01-11 15:35:55 +01:00
Md. Rifat Hasan Jihan
ce7a97dce6 added Bengali (bn) to non-capitalization group (#2700) 2024-12-01 22:33:02 +01:00
Md. Rifat Hasan Jihan
9f17a1d36c Replace ZWNJ with ZWJ for Bengali 'র‌্য' in keyboard layout (#2696) 2024-12-01 22:32:27 +01:00
Lars Mühlbauer
a248b4f717 Fix the top message in TypingScreen (#2690) 2024-12-01 22:31:26 +01:00
Lars Mühlbauer
c470b792c1 Fix Keyboard height in one-handed mode (#2668) 2024-10-31 15:10:04 +01:00
Lars Mühlbauer
ff5cd1e7c2 Fix smartbar spacing (#2665) 2024-10-31 15:03:01 +01:00
Lars Mühlbauer
32fee44364 Auto clean sensitive clipboard items (#2659)
* Auto clean sensitive clipboard items

* Fix typo :D

* Apply review suggestions
2024-10-25 16:46:08 +02:00
Patrick Goldinger
97edc33d05 Fix NetworkUtils not detecting hostnames with digits correctly (#2660) 2024-10-25 15:16:51 +02:00
Lars Mühlbauer
a89af25eab Improve ExtensionEditScreen (#2656) 2024-10-22 22:47:20 +02:00
Patrick Goldinger
ff01120925 Release v0.4.3 2024-10-21 23:20:13 +02:00
Patrick Goldinger
74b5c845aa Merge branch 'main' into release/0.4 2024-10-21 23:19:26 +02:00
florisboard-bot
66340249d4 Update translations from Crowdin 2024-10-21 23:17:01 +02:00
Lars Mühlbauer
14147ca1b9 Fix migration in clipboard database (#2641)
* Fix migration in clipboard database

* Update database version

* Fix migration from version 3 to version 4
2024-10-21 23:15:24 +02:00
Patrick Goldinger
bdc740637b Fix inline autofill single icon style (#2628) (#2649) 2024-10-21 17:02:36 +02:00
Patrick Goldinger
eb20e80295 Fix state issues in TextKeyboardLayout (popups) (#2647)
* Fix state issues in TextKeyboardLayout (popups)

* Fix utility key not updating correctly (#2648)
2024-10-21 15:34:15 +02:00
Patrick Goldinger
453fb0253a Fix predictive back by removing generic onBackPressedHandler (#2646) 2024-10-19 15:13:35 +02:00
Lucas Sanginetto
13fc7679a2 Fix incorrect bracket labels in IPA symbols layout (#2644) 2024-10-19 11:26:25 +02:00
Patrick Goldinger
2421d13038 Update ROADMAP.md (#2639) 2024-10-18 00:34:15 +02:00
Patrick Goldinger
ddc4f7f1ba Release v0.4.2 2024-10-17 19:12:13 +02:00
Patrick Goldinger
afea8c721f Merge branch 'main' into release/0.4 2024-10-17 19:10:49 +02:00
florisboard-bot
7dedfd4f7a Update translations from Crowdin 2024-10-17 19:07:32 +02:00
Patrick Goldinger
ef37194900 Disable compose strong skipping mode (#2637) 2024-10-17 16:51:02 +02:00
Lars Mühlbauer
58134b1ceb Add fix for sensitive clipboard suggestions (#2635) 2024-10-16 23:17:54 +02:00
Lars Mühlbauer
53cfbad404 Clipboard History enhancements (#2631)
* Hide sensitive clip data in clipboard history

* Add is remoteDevice flag

* Do not link password length to displayed characters

* Add backspace in clipboard history (#2615)

* Use ClipboardItem level function for the obfuscation of the text

* Move the backspace button to the header bar

* Adjust innerHeight to match the full layout

* Use KeyboardLikeButton instead of FlorisIconButtonWithInnerPadding
2024-10-14 19:31:37 +02:00
Patrick Goldinger
9cffcea246 Release v0.4.1 2024-10-10 20:53:19 +02:00
Patrick Goldinger
5acf80db0f Merge branch 'main' into release/0.4 2024-10-10 16:03:17 +02:00
florisboard-bot
d6f724e518 Update translations from Crowdin 2024-10-10 16:00:04 +02:00
Patrick Goldinger
6c4aa36b06 Update Crowdin configuration and workflow 2024-10-10 15:54:48 +02:00
Patrick Goldinger
edc38b6c2c Fix keyboard key size not properly updating (#2625) 2024-10-10 14:48:50 +02:00
Patrick Goldinger
891a2c6bac Update README.md 2024-10-09 15:35:16 +02:00
Patrick Goldinger
229237153b Add new release configs for Obtainium 2024-10-09 15:35:16 +02:00
Patrick Goldinger
290fbb5239 Merge pull request #2613 from florisboard/rework/inline-autofill-impl
Rework inline autofill suggestions from password managers
2024-10-08 21:59:43 +02:00
Patrick Goldinger
409d4f9348 Fix theme editor state bugs (#2620)
* Fix emoji history header not using theme style

* Fix extension editor component list state not being updated
2024-10-06 10:34:26 +02:00
Patrick Goldinger
82938cda5b Fix emojis with differing meta data not being seen as identical (#2617) 2024-10-05 14:48:26 +02:00
Patrick Goldinger
f7b0a30271 Rework Smartbar layout implementation & Remove Smartbar shared auto expand 2024-10-02 16:46:30 +02:00
Patrick Goldinger
575f359a85 Fix inline autofill service potential concurrent modification exception 2024-10-01 01:15:17 +02:00
Patrick Goldinger
22591163b3 Improve state handling for inline autofill suggestions 2024-10-01 00:30:20 +02:00
Patrick Goldinger
8104ae60ca Add inline autofill debug overlay 2024-09-30 22:34:04 +02:00
Patrick Goldinger
165b682732 Rework inline autofill UI implementation 2024-09-30 21:36:54 +02:00
Patrick Goldinger
eb770fac6c Merge pull request #2611 from florisboard/rework/emoji-history
Major rework of emoji history
2024-09-30 00:58:02 +02:00
Patrick Goldinger
39c27426a4 Add ability to manually reorder emojis in history (#1975) 2024-09-29 14:29:59 +02:00
Patrick Goldinger
228d5055cc Add ability to pin emojis in emoji history (#2273) 2024-09-29 14:05:57 +02:00
Patrick Goldinger
b400e04560 Rewrite emoji history logic and data class 2024-09-28 19:35:12 +02:00
Patrick Goldinger
27c1bbf039 Fix emoji history update bug if items is max size (#2129) 2024-09-28 15:32:10 +02:00
Patrick Goldinger
f61b655f7d Add emoji history group and enable toggle (#1990) 2024-09-28 15:19:30 +02:00
Patrick Goldinger
f82af63e97 Merge pull request #2606 from florisboard/rework/emoji-suggestions
Major rework of emoji suggestions
2024-09-28 14:17:36 +02:00
Patrick Goldinger
0fbd950f6e Change default value of show emoji name to false 2024-09-28 13:41:53 +02:00
Patrick Goldinger
e97b5f54ac Update emoji data to CLDR 45 (#2607) 2024-09-27 23:28:47 +02:00
Patrick Goldinger
b611360dd5 Add emoji acceptance updates history toggle 2024-09-27 19:37:12 +02:00
Patrick Goldinger
1b9d260020 Add emoji name visibility toggle 2024-09-27 19:24:28 +02:00
Patrick Goldinger
d74fe62bc0 Fix emoji suggestions failing for non-default skin-tones (#2604) 2024-09-27 19:04:09 +02:00
Patrick Goldinger
fe6f61a282 Rework emoji suggestions prefs 2024-09-27 18:32:16 +02:00
Senthil Vignesh Kumar
8b4239d9be Added Tamil to layout extension config (#2514)
* adding tamil to characters

* Author name fixed
2024-09-24 02:32:37 +02:00
Patrick Goldinger
a0c7cf2794 Upgrade to Kotlin 2.0 (#2597) 2024-09-24 01:59:37 +02:00
Patrick Goldinger
7480d14a0f Upgrade Compose to 1.7 & Upgrade other deps (#2596)
* Upgrade dependencies

* Code base deprecation cleanup
2024-09-24 00:38:51 +02:00
Kevin
7274228a46 Update quick action reordering based on row (#2592) 2024-09-23 14:58:54 +02:00
Patrick Goldinger
a38f6a2c76 Fix utility key action not correctly applied (#2591) 2024-09-23 13:59:04 +02:00
Krzysztof Kozyra
eda6c09538 Add delete subtype on long-press (#2580)
* add delete subtype feature

* Delete dialog switch from AlertDialog.Builder (unstyled) to JetPrefAlertDialog() to match rest of the ui

* Rework code and add new changes, add translations for subtype delete dialog

* Fix: when dialog is open and screen is rotated app crashes

* Remove translations due to contribution guidelines

* Add english translation so that the project can be built again ;)

* Revert spacing in values-ru/strings.xml

---------

Co-authored-by: lm41 <lm41@lm41.xyz>
Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2024-09-22 22:50:49 +02:00
Patrick Goldinger
9e42d16cb0 Add workflow for automatically rejecting translation updates (#2588) 2024-09-22 22:30:16 +02:00
Lars Mühlbauer
11ba51c354 Remove placeholder suggestions (#2572) 2024-09-22 14:50:55 +02:00
Kevin
51f5196b8a Fix incognito icon messing up landscape input height (#2584) 2024-09-22 14:48:21 +02:00
Lars Mühlbauer
56bbe9d13c Update clipboard backup and restore behavior (#2547)
* Change Clipboard History Checkbox to TriStateCheckbox

* Fix history import error when files are already in ClipboardFileStorage

* Ahhhh yes, the nitpicks `:DD`

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

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2024-09-18 23:47:38 +02:00
Lars Mühlbauer
4d1ae52dc0 Localize debug log screen and add option for preformatted exporting (#2561)
* Add Button for copying the formatted debug log for GitHub and localize ExportDebugLogScreen

* Add review suggestions

* Ahh yes, we love to nitpick
2024-09-18 23:17:59 +02:00
Lars Mühlbauer
1e1916194b Remove trailing comma to comply with the JSON spec (#2560) 2024-09-18 21:59:40 +02:00
Patrick Goldinger
80fb20885b Release v0.4.0 2024-09-18 18:26:47 +02:00
Patrick Goldinger
bae3c8ec9d Fix Rust Cargo.lock file getting rewritten during build (#2568) 2024-09-18 18:25:04 +02:00
Patrick Goldinger
9ff7d86a8d Add hint to find_program for Rust discovery (#2567) 2024-09-18 16:56:20 +02:00
Patrick Goldinger
89ab0731d2 Add Cargo.lock files to VCS (#2566) 2024-09-18 07:23:24 +02:00
Patrick Goldinger
887a75a482 Rework version name handling in Gradle (#2565) 2024-09-18 05:43:48 +02:00
Patrick Goldinger
e52bea2456 Rename master to main & Update workflows (#2564)
* Update workflows to main branch

* Remove OSS plugin comment (is not used anymore)
2024-09-18 04:25:11 +02:00
Patrick Goldinger
2171e16346 Release v0.4.0-rc02 2024-09-02 22:23:40 +02:00
Lars Mühlbauer
566b6fbae3 Fix incorrect drawing behavior with display cutouts (#2533)
* Fix incorrect drawing behavior with display cutouts

* Replace old conditional logic with new function
2024-09-02 22:20:03 +02:00
Book-reader
5215227793 Fix incorrect addons store url (.com -> .org) (#2535) 2024-08-28 20:43:27 +02:00
Patrick Goldinger
671f97eddb Release v0.4.0-rc01 2024-08-23 15:59:55 +02:00
Patrick Goldinger
b6c9469826 Adjust gitignore for release.sh script 2024-08-23 14:01:35 +02:00
Lars Mühlbauer
77e4414467 Move FlorisCopyToClipboardActivity to ime.clipboard (#2531)
* Move FlorisCopyToClipboardActivity to `ime.clipboard`

* Fix image alignment
2024-08-21 21:57:54 +02:00
Lars Mühlbauer
db85e05714 Disable hinted number row toggle if number row is enabled (#2532) 2024-08-21 21:22:09 +02:00
Lars Mühlbauer
51890c93d4 Fix transparent navigation bar color on older api levels (#2529) 2024-08-21 20:45:28 +02:00
Lars Mühlbauer
1f16ac2c3b Update README.md to the current status (#2528)
* Update README.md

* Apply suggestions from code review

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

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2024-08-13 21:33:37 +02:00
Lars Mühlbauer
199211fdbf Switch to sliding navigation animation (#2527) 2024-08-13 21:13:11 +02:00
Lars Mühlbauer
c5ee414ec6 Remove preference category from ExtensionHomeScreen (#2525) 2024-08-13 02:15:25 +02:00
Patrick Goldinger
f1c5b1802b Release v0.4.0-beta03 2024-08-10 02:47:31 +02:00
Patrick Goldinger
0450c8c7a1 Fix null ptr exception in intent handling 2024-08-10 02:47:09 +02:00
florisboard-bot
6da6da74fc Update translations from Crowdin 2024-08-07 02:16:08 +02:00
Lars Mühlbauer
d7fca0aad1 Open subtype preset dialog when a new subtype is added (#2521)
* Open subtype preset dialog when a new subtype is added

* Update addons store url in build config
2024-08-07 02:13:07 +02:00
Patrick Goldinger
1af519e01d Update to Gradle 8.7 and AGP 8.5.1 (#2520) 2024-08-07 00:01:04 +02:00
Lars Mühlbauer
989d2884b1 Fix backspace icon (#2518) 2024-08-06 03:19:29 +02:00
Lars Mühlbauer
d137155ab0 Add CheckUpdatesScreen (#2509) 2024-07-15 19:12:16 +02:00
Patrick Goldinger
270ab4fe5f Add deep link support for UI (#2508) 2024-07-15 18:22:05 +02:00
klaurence
be4cd4d897 Change files of Udmurt subtype (#2445)
* Update extension.json

* Update udmurt_compact.json

* Update udmurt_compact.json
2024-07-08 03:48:22 +02:00
JP O'Neill
e46d53291b add support for the layout colemak-dhm (#2357)
Co-authored-by: JP O'Neill <oneilljp@proton.me>
2024-07-08 03:37:36 +02:00
Victor B
8292f9d4cc Add ЈЦУКЕН interslavic keyboard layout (#2354) 2024-07-08 03:37:07 +02:00
Thanh, H
192412b6dd Improve Vietnamese Telex keyboard (#2259)
* Init Full Telex

* Add Telex Rule for a__

* Add a__, ă__, â__ rule

* Update ô, ơ, ư, iê without sound

* Add ơ, iê, uyê. yê without sound

* Add ăc, ăm

* Add êm

* Add ăn

* Update ô

* Improve Vietnamese

* Add "ưc"

* Special case: "gi" + vowel + tone

* Special case: "ghi" + tone + "ê"

* Special case: "gi" + "a, u" + "tone"

* Add change tone feature: "áf" -> "à"

* Add tone remove for iê case
2024-07-08 03:23:14 +02:00
GasparAM
a7f8980d35 Added Armenian alternative phonetic layout (#2171) 2024-07-08 03:18:55 +02:00
moonbeamcelery
f5d80a5818 CJK keyboard adjustments for better Chinese input convenience (#2142)
* CJK keyboard fixes, see expanded message:

- fix full-width comma
- add () to first symbol screen popups
- merge postal sign with # popups
- add = to + popup
- add full-width = to half-width popup
- fix half-width <> and single guillemets ‹›
- add check mark to square root symbol popup
- Add [] to popups
- Add pinyin characters in popup mapping

Signed-off-by: moonbeamcelery <moonbeamcelery@proton.me>

* fix: issue with brackets, ü, and add cjk popup to Chinese preset

---------

Signed-off-by: moonbeamcelery <moonbeamcelery@proton.me>
2024-07-08 03:16:36 +02:00
Patrick Goldinger
d9b940f4f3 Add possible detection for "All keys invisible" bug (#2501) 2024-07-08 02:38:21 +02:00
Lars Mühlbauer
d86fc13cff Remove material2 dependency (#2500)
* remove material2

* fix icon rotation in smartbar

* add FB icon for Android Studio's NewUI

* Add new button styling option for clipboard history view
replace one hand mode icon

* apply review suggestions

* ah yes, the trailing commas
2024-07-08 01:54:29 +02:00
Patrick Goldinger
5b7727b884 Merge pull request #2499 from florisboard/refactor/move-lib-snygg
Move `lib.snygg` to separate module
2024-07-05 20:49:19 +02:00
Patrick Goldinger
b0649b1b7e Move lib.snygg to separate module 2024-07-05 19:43:00 +02:00
Patrick Goldinger
6244198795 Rework enum display string strategy
Preparation for further lib de-entanglement
2024-07-05 17:56:29 +02:00
Patrick Goldinger
bd9f7750aa Move lib.android to separate module (#2498) 2024-07-05 15:59:37 +02:00
Patrick Goldinger
c909d3ad7d Merge pull request #2473 from florisboard/feat/addons-support
Add addons support
2024-07-04 19:31:11 +02:00
lm41
736411e4f3 change hardcoded uri build config uri 2024-07-04 19:21:18 +02:00
lm41
9c7d980b3b localize addon manager 2024-07-04 18:47:09 +02:00
lm41
4d04eb1bb5 update ROADMAP.md 2024-06-30 17:54:11 +02:00
lm41
68c55d66be add better one hand mode icon 2024-06-28 12:40:16 +02:00
lm41
e1550d813b Remove AssetManager and switch to extension functions 2024-06-26 20:45:01 +02:00
lm41
f780ef0213 localize ExtensionValidation 2024-06-24 21:35:15 +02:00
lm41
025620a262 add FLADDONS_*** prefix to BuildConfig fields 2024-06-23 21:41:04 +02:00
lm41
4b83c907c3 simplify code 2024-06-23 19:43:51 +02:00
lm41
5542a131b9 Update UX for managing Extensions 2024-06-23 14:03:17 +02:00
kuroya
eb50498890 Add Diktor layout (#2495)
* Add Diktor layout

* change tab to spaces
2024-06-22 13:58:48 +02:00
Lars Mühlbauer
e6a408fbc0 Remove deprecated swipe-able API and switch to M3 ModalBottomSheet (#2496) 2024-06-22 13:50:14 +02:00
Lars Mühlbauer
9d76b684be Fix suggestion engine selection crash (#2492)
* fix suggestion engine selection crash

* replace rtl modifier with auto mirrored icon

* remove unnecessary OptIn annotation
2024-06-22 13:38:06 +02:00
lm41
b97cc52958 fix url encoding 2024-06-13 19:35:01 +02:00
lm41
aac7134433 add funktion to create an extension update url 2024-06-05 16:31:40 +02:00
Patrick Goldinger
f1d60d9958 Add barebones implementation for addons screen 2024-06-03 15:55:59 +02:00
lm41
e520a9c335 Prevent file/path name max length attacks 2024-06-02 13:55:39 +02:00
lm41
30294b02b4 Prevent large file size attacks by limiting the max file size to 100MB 2024-06-02 00:58:44 +02:00
Patrick Goldinger
f1bdf216fc Add error condition for failing octet-streams 2024-06-02 00:19:28 +02:00
lm41
82d43a53cc prevent zip-slip when unpacking an extension 2024-06-01 21:42:56 +02:00
Patrick Goldinger
c51a787ac4 Fix import extension intents for Firefox 2024-06-01 21:11:23 +02:00
Lars Mühlbauer
f116e20829 Remove systemuicontroller and switch to view apis (#2486) 2024-06-01 12:27:06 +02:00
lm41
baf2cbcd13 Add share import handler for .flex files 2024-05-31 18:01:03 +02:00
lm41
1edb90b0f7 change flex file importer mimetype to application/vnd.florisboard.extension+zip 2024-05-31 14:31:19 +02:00
Lars Mühlbauer
4c0c3f52e7 Smartbar enhancements (#2477)
* fix incorrect smartbar paste button state

* Add incognito mode indicator in Smartbar

* apply suggestions
2024-05-29 22:49:38 +02:00
Md. Rifat Hasan Jihan
d23575375d Updated bn-BD layout with the latest Unijoy layout (#2417)
and refined the popup keys
2024-05-29 07:51:09 +02:00
Lars Mühlbauer
e95bbf5192 Fix fullscreen input mode in portrait orientation (#2475) 2024-05-29 07:42:07 +02:00
Patrick Goldinger
0a4a4418ca Revamp theme settings screen 2024-05-13 01:53:28 +02:00
Patrick Goldinger
2c653853e2 Add basic support for importing flex files from the file explorer 2024-05-13 00:30:18 +02:00
Lars Mühlbauer
6f169997e9 Migrate Settings UI to Material 3 (#2467)
* implement material3 for the settings ui

* fix chip colors

* fix statusbar color

* fix aboutlibraries color

* fix alignment of subcheckboxen

* fix wrong card colors

* Update cornershape of the dropdown menu

* update ScrollableModifiers to material 3

* better card and background colors

* update jetpref

* change contrast of outlined cards

* apply suggestions

* implement suggestions

* add corners on text background in the theme editor

* apply the systembar color for the navbar

* set material you as default on android 12+

* fix card content padding

* Fix status bar color not adapting to navbar color state

* update jetpref dependency to 0.2.0-beta01

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2024-05-08 00:29:42 +02:00
Patrick Goldinger
f2e76cc72a Fix emoji suggestion logic (#2462)
* Rework emoji suggestion (#2460)

* Apply suggestions from code review

* Fix emoji suggestions caring about capitalization
2024-05-03 20:37:06 +02:00
Lars Mühlbauer
3da59cc94b Add clipboard history to backup (#2458)
* Implement backup/restore clipboard history

* Fix duplicate clipboard items when merging the same archive multiple times

* Apply suggestions

* Update UI

* Implement backup of media clipboard history items.

* Implement restore and add strings; apply suggestions

* change from popBackStack() to navigateUp()
2024-04-29 21:14:06 +02:00
Patrick Goldinger
cf7f6f5fe2 Release v0.4.0-beta02 2024-04-20 17:41:22 +02:00
Lars Mühlbauer
7724b07a75 Fix missing beta variant build issue (#2450) 2024-04-20 16:52:56 +02:00
Patrick Goldinger
30750d7842 Merge pull request #2448 from florisboard/fix-material-you-colors
Add new Material You surface colors
2024-04-20 16:24:19 +02:00
Kevin
7351a8bfa9 Implement smooth scrollbar in emoji palette (#2446)
* implement smooth scrollbar

* Code style and function visibility adjustments

* Apply suggestions from code review

Formatting

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
Co-authored-by: Lars Mühlbauer <59062169+lm41@users.noreply.github.com>

* Update app/src/main/kotlin/dev/patrickgold/florisboard/lib/compose/ScrollableModifiers.kt

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

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
Co-authored-by: Lars Mühlbauer <59062169+lm41@users.noreply.github.com>
2024-04-19 12:45:35 +02:00
Patrick Goldinger
7df9a93cb9 Fix issue with auto-mirroring of dropdown-like buttons 2024-04-19 12:42:56 +02:00
Patrick Goldinger
522f9a8153 Add new Material You surface colors
The following color names are now available:
- surfaceBright
- surfaceDim
- surfaceContainer
- surfaceContainerHigh
- surfaceContainerHighest
- surfaceContainerLow
- surfaceContainerLowest

These colors have replaced the color name "surface", which has been removed.

See section "Surfaces" in the blog entry https://material.io/blog/material-3-compose-1-2 for more info
2024-04-19 12:42:22 +02:00
Patrick Goldinger
5bff051d41 Fix FlorisDropdownMenu arrows being inverse in direction 2024-04-16 21:30:35 +02:00
Patrick Goldinger
0167e1231f Merge pull request #2439 from florisboard/switch-to-rust
Switch native code to Rust
2024-04-07 20:29:31 +02:00
Patrick Goldinger
97bca076e2 Fix Rust toolchain locating issues 2024-04-07 16:26:39 +02:00
Patrick Goldinger
83ec842196 Update CONTRIBUTING.md 2024-04-07 15:23:41 +02:00
Lars Mühlbauer
9c25b7f7a3 Switch to ImageVector icons (#2432)
* Remove (most) drawables and refactor codebase to use androidx.material.icons instead

* use ImageVector for ButtonBar

* use the new vectorRessource function instead of jeticon

* resolve build error

* Switch to rc jetpref

* Apply suggestions
2024-04-07 13:40:22 +02:00
Patrick Goldinger
4d78110a56 Update ROADMAP.md 2024-04-07 11:47:16 +02:00
Patrick Goldinger
2b1dd31ad7 Upgrade Gradle to 8.4 / Upgrade AGP to 8.3.1 2024-04-05 02:11:26 +02:00
Patrick Goldinger
c5d1d3b31e Add Rust build support 2024-04-05 01:35:04 +02:00
Patrick Goldinger
c198e8b376 Remove cpp native code from app 2024-04-04 17:48:52 +02:00
Patrick Goldinger
c5e7fea8f2 Remove florisboard/nlp submodule 2024-04-04 17:46:24 +02:00
Patrick Goldinger
397ef1a150 Add emoji data for most important languages (#2437) 2024-04-03 17:02:18 +02:00
Siddhesh Naik
bdd8f660c5 Add Emoji Suggestions for a More Expressive User Experience (#2385)
* Add Emoji Suggestions for a More Expressive User Experience

- Implement EmojiSuggestionProvider:
  - Manages emoji suggestion tasks, including initialization and maintenance of supported emojis.
  - Generates and returns suggestions based on user input and preferences.
- Updates in NlpManager to Include Emoji Suggestions:
  - Adds emoji suggestions when applicable and available.
- Create Custom EmojiSuggestionCandidate Class:
  - Encapsulates individual emoji suggestions for seamless integration.

Future Actions:
- Introduce Espresso tests in Florisboard to validate the emoji suggestion flow.

* Fix review comments

* Addressed review comments

- Added logic to dynamically get the locale file.
- Currently only supports English as related files are added.
- To support other locales in future, we can just add the related file.
- Added en.txt and en_US.txt and updated root.txt to match emojicon 44.1
  version.
2024-04-03 16:43:17 +02:00
Patrick Goldinger
632c4a7134 Merge pull request #2422 from florisboard/fix-clipboard-history-large-item-handling
Fix huge clipboard text items causing clipboard history crash
2024-03-23 15:15:23 +01:00
Patrick Goldinger
43e618333a Fix huge clipboard text items causing clipboard history crash (#2420) 2024-03-15 23:55:49 +01:00
Patrick Goldinger
6e1c7716dc Add generated schema 2 of clipboard database 2024-03-15 19:43:55 +01:00
Patrick Goldinger
b2a1e82963 Release v0.4.0-beta01 2024-03-10 23:09:01 +01:00
florisboard-bot
0dd1f90c83 Update translations from Crowdin 2024-03-10 22:59:09 +01:00
Lars Mühlbauer
22b7a675e4 Modularize lib.kotlin (#2404)
* extract dev.patrickgold.florisboard.lib.kotlin to org.florisboard.lib.kotlin

* apply review suggestions
2024-03-07 04:57:31 +01:00
Lars Mühlbauer
74dd67642c Fix random rotated images in threema (#2369)
* fix random rotated images in threema

* add filter for the projection

* do not filter only for orientation request but also for every other column

* Apply the suggestion

* get the orientation of the image on insert
2024-03-07 00:46:36 +01:00
Patrick Goldinger
44f0c9cd89 Merge pull request #2397 from vorgoron/feature/udmurt-layout
Add Udmurt layouts.
2024-02-29 17:13:19 +01:00
Lars Mühlbauer
28fdb423b4 Fix #2309, #2134, #2112, #2378 and some deprecation warnings (#2388)
* emoji history visibility when locked fix (#2309)

* Add POST_NOTIFICATIONS permission

* remove deprecation warning

* add better naming for readability

* rename QabType to QuickActionBarType for better readability

* add smartbar vibrations (#2134)

* add share to clipboard

* remove strings

* Add Notification permission to startup menu (#2378)

To display Notifications on Android 13+ the app nust request permission to do so.

* remove deprecation warnings (use defaultDeserializer instead of default)

* Rework NotificationPermissionState handeling on Android 13+.
If the permission is NOT_SET (the user installed the app when the permission wasn't necessary), restart the SetupScreen or add this option to the SetupScreen.
If the permission was granted or denied, the user will not be asked again even if he revokes the permission later in the settings.

* Add comments/docs to the NotificationSetup code

* Revert "remove strings"

This reverts commit ee8a62d647.

* fix crash when InputFeedbackManager is not initialized

* apply the usual formatting nitpicks

* Add the bottom sheet to CopyToClipboardActivity

* add strings

* reformat file

* fix resource context not initialized error

* apply the patch of patrick@patrickgold.dev;
Enhance the bottom sheet with swipe gestures;

* Update app/src/main/kotlin/dev/patrickgold/florisboard/FlorisCopyToClipboardActivity.kt

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

* Update app/src/main/res/values/themes.xml

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

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2024-02-29 16:41:23 +01:00
Grigory Grigorev
de169e9d0d Add udmurt layouts 2024-02-18 19:30:28 +04:00
Patrick Goldinger
ffbe7696a4 Merge pull request #2396 from florisboard/upgrade-project-dependencies
Upgrade project dependencies
2024-02-15 23:08:18 +01:00
Patrick Goldinger
57254e0ebe Upgrade about library and fix crash in third-party licenses screen 2024-02-15 03:16:28 +01:00
Patrick Goldinger
4b671b5794 Fix additional Kotlin migration issues 2024-02-15 02:39:47 +01:00
Patrick Goldinger
9cd413f2f3 Fix Android API level issues and suppress compose material import lints 2024-02-15 02:36:13 +01:00
Patrick Goldinger
777bf48b50 Fix usage of deprecated compose API 2024-02-15 02:32:21 +01:00
Patrick Goldinger
11d4ea206d Upgrade to Kotlin's new .entries shorthand for enums 2024-02-15 02:18:15 +01:00
Patrick Goldinger
8e7311ea01 Upgrade Kotlin to 1.9.22 & Upgrade project dependencies 2024-02-15 01:56:40 +01:00
Siddhesh Naik
e63186bebc Refine Clipboard Suggestion Validation to Prevent Empty and Invalid Items (#2387)
* Refine Clipboard Suggestion Validation to Prevent Empty and Invalid Items

Summary:
- This PR addresses an issue where empty or invalid clipboard items that
  could lead to incorrect suggestions.
- It introduces validation logic to ensure only meaningful content is suggested.

Context:
- The issue was observed when apps like KDE Connect added empty or special
  character-only items to the clipboard.
- The ClipboardSuggestionProvider previously lacked validation for such cases.

Changes:
- Added validation for empty clipboard items.
- Enhanced validation to handle special characters (newlines, tabs).
- Introduced private methods and constants for improved readability and maintainability.

* Update the validation with Regex
2024-02-06 04:54:48 +01:00
inson1
17ca0c1cb1 Update year in Copyright (#2391) 2024-02-04 18:59:49 +01:00
Patrick Goldinger
c9df7a9f49 Merge pull request #2372 from lm41/add-spacebar-character
Implement option to cycle throu the capitalization modes and add option to set spacebar to the spacebar character
2024-01-13 16:35:55 +01:00
lm41
0e9963bd3b refactor CapitalizationBehavior to use CapitalizationBehavior enum and resolve the requested changes 2024-01-13 15:31:25 +01:00
lm41
2cde597be6 add option to cycle through the captitalization cycle 2024-01-13 11:21:43 +01:00
Patrick Goldinger
5829ff5d07 Merge pull request #2371 from lm41/fix-wrong-primary-clipboard-entry-accessibility
fix primary clipboard entry accessible while phone is locked
2024-01-13 11:02:14 +01:00
Patrick Goldinger
9a7360bfee Merge pull request #2370 from lm41/fix-pinned-items-getting-unpinned
fix pinned items getting unpinned when copying the same content again
2024-01-13 10:56:03 +01:00
lm41
2d42ed1c06 add the option to select the spacebar character 2024-01-13 06:38:38 +01:00
lm41
031823c81c fix primary clipboard entry accessible while phone is locked 2024-01-13 03:51:29 +01:00
lm41
5b8b73ff16 fix pinned items getting unpinned when copying the same content again 2024-01-13 03:13:13 +01:00
Patrick Goldinger
80541095fd Merge pull request #2366 from lm41/issue-form
[Meta]: Switch to issue forms
2024-01-12 08:46:15 +01:00
lm41
ed7861ec12 Add issue forms 2024-01-12 02:04:58 +01:00
Patrick Goldinger
f6a1a091b5 Merge pull request #2364 from lm41/monochrome-icons
Add the monochrome icon variants
2024-01-12 00:13:26 +01:00
lm41
6d7bbc2df7 Add the monochrome icon variants 2024-01-10 23:15:56 +01:00
Patrick Goldinger
f7b65f788f Upgrade to Kotlin 1.9.21 2023-12-25 22:56:31 +01:00
Patrick Goldinger
af282693c8 Upgrade AGP to 8.2.0 & Add support for Android library modules 2023-12-25 19:50:04 +01:00
Patrick Goldinger
5bb7c6f786 Release v0.4.0-alpha06 2023-12-23 10:49:12 +01:00
Patrick Goldinger
7e1bce2cea Fix IME enabled/selected checker failing on API 34+ (#2344) 2023-12-23 10:34:09 +01:00
605 changed files with 42659 additions and 6513 deletions

View File

@@ -9,7 +9,7 @@ insert_final_newline = true
max_line_length = 120
trim_trailing_whitespace = true
[{*.har,*.json,*yml}]
[{*.har,*.json,*yml,*.sh}]
indent_size = 2
[*.kt]

View File

@@ -1,34 +0,0 @@
---
name: Bug report
about: Create a report to help FlorisBoard improve
title: ''
labels: bug
assignees: ''
---
<!--
Thank you for your help in making FlorisBoard better!
Guide to a good bug-report:
• Please search existing bug/crash reports reports to avoid creating duplicates.
• Give your bug report a good name (no generics like "Error" or "Crash"), so others can easily identify the topic of your issue.
• Describe the bug in a short but concise way.
• If you have a screenshot or screen recording of the bug, link them at the end of this issue.
• Also make sure to fill out the environment information. This info is valuable when trying to fix your described bug.
-->
#### Short description
Describe the bug in a short but concise way.
#### Steps to reproduce
1. Go to '…'
2. Click on '…'
3. Scroll down to '…'
4. See error
#### Environment information
- FlorisBoard Version: <!-- e.g. 0.X.X -->
- Install Source: <!-- Google PlayStore/F-Droid/GitHub/? -->
- Device: <!-- e.g. OnePlus 7T -->
- Android: <!-- e.g. 10, Stock -->

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Bug Report
description: Create a report to help FlorisBoard improve
labels:
- "bug"
body:
- type: markdown
attributes:
value: |
Thank you for your help in making FlorisBoard better!
Guide to a good bug-report:
• Please search existing bug/crash reports reports to avoid creating duplicates.
• Give your bug report a good name (no generics like "Error" or "Crash"), so others can easily identify the topic of your issue.
• Describe the bug in a short but concise way.
• If you have a screenshot or screen recording of the bug, link them at the end of this issue.
• Also make sure to fill out the environment information. This info is valuable when trying to fix your described bug.
- type: textarea
id: description
attributes:
label: Short description
description: Describe the bug in a short but concise way.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
value: |
1. Go to '…'
2. Click on '…'
3. Scroll down to '…'
4. See error
validations:
required: true
- type: input
id: florisversion
attributes:
label: FlorisBoard Version
placeholder: e.g. 0.X.X
validations:
required: true
- type: dropdown
id: installsource
attributes:
label: Install Source
options:
- Google PlayStore
- F-Droid
- GitHub
validations:
required: true
- type: input
id: device
attributes:
label: Device
placeholder: e.g. OnePlus 7T
validations:
required: true
- type: input
id: androidversion
attributes:
label: Android
placeholder: e.g. 10, Stock
validations:
required: true

View File

@@ -1,28 +0,0 @@
---
name: Crash report
about: Create a report with a generated crash log attached to help FlorisBoard improve
title: ''
labels: bug
assignees: ''
---
<!--
Thank you for your help in making FlorisBoard better!
Guide to a good crash-report:
• Please search existing bug/crash reports to avoid creating duplicates.
• Give your crash report a good name (no generics like "Error" or "Crash"), so others can easily identify the topic of your issue.
• Describe what you were doing what could've led to the crash and whether the crash is random or reproducible.
-->
#### Short description
Describe what you were doing that could've led to the crash.
#### Steps to reproduce
1. Go to '…'
2. Click on '…'
3. Scroll down to '…'
4. See crash
<!-- Paste the generated crash log below -->

38
.github/ISSUE_TEMPLATE/crash_report.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Crash report
description: Create a report with a generated crash log attached to help FlorisBoard improve
labels:
- "bug"
body:
- type: markdown
attributes:
value: |
Thank you for your help in making FlorisBoard better!
Guide to a good crash-report:
• Please search existing bug/crash reports to avoid creating duplicates.
• Give your crash report a good name (no generics like "Error" or "Crash"), so others can easily identify the topic of your issue.
• Describe what you were doing what could've led to the crash and whether the crash is random or reproducible.
- type: textarea
id: description
attributes:
label: Short description
description: Describe the bug in a short but concise way.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
value: |
1. Go to '…'
2. Click on '…'
3. Scroll down to '…'
4. See crash
validations:
required: true
- type: textarea
id: crashlog
attributes:
label: Crash log
description: Paste the generated crash log below
validations:
required: true

View File

@@ -1,18 +0,0 @@
---
name: Feature request / Suggestion
about: Suggest an idea for this project
title: ''
labels: proposal
assignees: ''
---
<!--
Thank you for your help in making FlorisBoard better!
Guide to a good feature-request:
• Please search existing proposals to avoid creating duplicates.
• If you have multiple ideas which are not directly connected to other, file a new issue for each idea. This makes it easier to implement your proposals.
• Describe your idea in a short but concise way.
• If you have any examples, e.g. screenshots or other keyboards have the proposed feature implemented, feel free to post them after your description.
-->

View File

@@ -0,0 +1,22 @@
name: Feature request / Suggestion
description: Suggest an idea for this project
labels:
- "proposal"
body:
- type: markdown
attributes:
value: |
Thank you for your help in making FlorisBoard better!
Guide to a good feature-request:
• Please search existing proposals to avoid creating duplicates.
• If you have multiple ideas which are not directly connected to other, file a new issue for each idea. This makes it easier to implement your proposals.
• Describe your idea in a short but concise way.
• If you have any examples, e.g. screenshots or other keyboards have the proposed feature implemented, feel free to post them after your description.
- type: textarea
id: feature
attributes:
label: Feature idea
description: Please explain your idea in a precise way.
validations:
required: true

View File

@@ -2,7 +2,7 @@ name: FlorisBoard CI
on:
push:
branches: [ master ]
branches: [ main ]
paths-ignore:
- ".github/ISSUE_TEMPLATE/**"
- ".github/FUNDING.yml"
@@ -14,31 +14,28 @@ on:
- "README.md"
- "ROADMAP.md"
pull_request:
branches: [ master ]
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/actions/wrapper-validation@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin
cache: gradle
- name: Set up CMake and Ninja
uses: lukka/get-cmake@latest
- name: Build with Gradle
# MUST call gradlew separately because of an OSS license plugin issue.
# See https://github.com/google/play-services-plugins/issues/199
run: ./gradlew clean && ./gradlew assembleDebug
- uses: actions/upload-artifact@v3
run: ./gradlew clean assembleDebug
- uses: actions/upload-artifact@v4
with:
name: app-debug.apk
path: app/build/outputs/apk/debug/app-debug.apk

View File

@@ -2,7 +2,7 @@ name: Crowdin Upload Sources
on:
push:
branches: [ master ]
branches: [ main ]
paths:
- "app/src/main/res/values/strings.xml"
- ".github/workflows/crowdin-upload.yml"
@@ -13,9 +13,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Upload
uses: crowdin/github-action@1.4.0
uses: crowdin/github-action@v2
with:
config: "crowdin.yml"
upload_sources: true

View File

@@ -0,0 +1,61 @@
name: Validate no translated strings.xml included
on:
pull_request_target:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Precheck if validation is required
id: precheck
run: |
pr_author="${{ github.event.pull_request.user.login }}"
if [[ "$pr_author" == "florisboard-bot" ]]; then
echo "PR is by florisboard-bot, skipping validation!"
echo "require_validation=false" >> "$GITHUB_OUTPUT"
else
echo "PR is not by florisboard-bot, requiring validation!"
echo "require_validation=true" >> "$GITHUB_OUTPUT"
fi
- name: Fetch PR changed files manually
id: fetch_changed_files
if: steps.precheck.outputs.require_validation == 'true'
run: |
pr_files="$(curl -sSf https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files?per_page=1000)" || exit 11
changed_files="$(jq -r '.[].filename' <<< "$pr_files")" || exit 12
illegal_changes_list="$(grep -E '^app/src/main/res/values-.+/strings.xml$' <<< "$changed_files")" || true
if [ -n "$illegal_changes_list" ]; then
echo -e "Illegal changes detected:\n$illegal_changes_list"
else
echo "No illegal changes detected"
fi
echo "illegal_changes_list<<EOF" >> "$GITHUB_OUTPUT"
echo "$illegal_changes_list" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Create comment if illegal files detected
uses: peter-evans/create-or-update-comment@v4
if: steps.precheck.outputs.require_validation == 'true' && steps.fetch_changed_files.outputs.illegal_changes_list != ''
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
⚠️ Illegal changes detected
Hey there!
We detected illegal changes that disobey the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md#translation). This is a kind reminder that pull requests must not contain translated `strings.xml` files, as those are exclusively managed from Crowdin.
Please remove changes to the following files:
```
${{ steps.fetch_changed_files.outputs.illegal_changes_list }}
```
- name: Fail workflow if illegal files detected
if: steps.precheck.outputs.require_validation == 'true' && steps.fetch_changed_files.outputs.illegal_changes_list != ''
run: echo -e "Illegal changes detected:\n${{ steps.fetch_changed_files.outputs.illegal_changes_list }}" && exit 1

14
.gitignore vendored
View File

@@ -1,7 +1,11 @@
# Built application files
*.apk
*.aab
*.ap_
# dotenv
.env
# Files for the ART/Dalvik VM
*.dex
@@ -36,6 +40,7 @@ captures/
# Intellij
*.iml
.idea/
!/.idea/copyright/
# Keystore files
*.jks
@@ -47,3 +52,12 @@ crowdin.properties
# Nix stuff
.direnv/
result
# VSCode
.vscode/
# Rust
debug/
target/
**/*.rs.bk
*.pdb

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "app/src/main/cpp/nlp"]
path = app/src/main/cpp/nlp
url = https://github.com/florisboard/nlp

6
.idea/copyright/copyright.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (C) &amp;#36;originalComment.match(&quot;Copyright \(C\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year The FlorisBoard Contributors&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
<option name="myName" value="copyright" />
</copyright>
</component>

15
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<component name="CopyrightManager">
<settings default="copyright">
<module2copyright>
<element module="Project Files" copyright="copyright" />
</module2copyright>
<LanguageOptions name="Shell Script">
<option name="fileTypeOverride" value="1" />
</LanguageOptions>
<LanguageOptions name="XML">
<option name="fileTypeOverride" value="1" />
<option name="prefixLines" value="false" />
</LanguageOptions>
<LanguageOptions name="__TEMPLATE__" />
</settings>
</component>

226
.idea/icon.svg generated Normal file
View File

@@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="vector"
width="108"
height="108"
viewBox="0 0 108 108"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10">
<radialGradient
gradientUnits="userSpaceOnUse"
r="20.594"
cx="54.141998"
cy="48.769001"
id="gradient_0"
gradientTransform="matrix(2.193116,0,0,2.193116,-64.427669,-59.442216)">
<stop
offset="0"
stop-color="#55E032FF"
id="stop1" />
<stop
offset="1"
stop-color="#227C53FF"
id="stop2" />
</radialGradient>
<radialGradient
gradientUnits="userSpaceOnUse"
r="20.594"
cx="54.141998"
cy="48.769001"
id="gradient_1"
gradientTransform="matrix(2.193116,0,0,2.193116,-64.427669,-59.442216)">
<stop
offset="0"
stop-color="#55E032FF"
id="stop3" />
<stop
offset="1"
stop-color="#227C53FF"
id="stop4" />
</radialGradient>
<radialGradient
gradientUnits="userSpaceOnUse"
r="12.849"
cx="54.029999"
cy="39.144001"
id="gradient_2"
gradientTransform="matrix(2.193116,0,0,2.193116,-64.427669,-59.442216)">
<stop
offset="0"
stop-color="#FE7901FF"
id="stop5" />
<stop
offset="1"
stop-color="#FEBE01FF"
id="stop6" />
</radialGradient>
<radialGradient
gradientUnits="userSpaceOnUse"
r="20.594"
cx="54.141998"
cy="48.769001"
id="gradient_3"
gradientTransform="matrix(2.193116,0,0,2.193116,-64.427669,-59.442216)">
<stop
offset="0"
stop-color="#55E032FF"
id="stop7" />
<stop
offset="1"
stop-color="#227C53FF"
id="stop8" />
</radialGradient>
<radialGradient
gradientUnits="userSpaceOnUse"
r="17.913"
cx="54.131001"
cy="49.819"
id="gradient_4"
gradientTransform="matrix(2.193116,0,0,2.193116,-64.427669,-59.442216)">
<stop
offset="0"
stop-color="#FE7901FF"
id="stop9" />
<stop
offset="1"
stop-color="#FEBE01FF"
id="stop10" />
</radialGradient>
</defs>
<path
fill="url(#gradient_0)"
d="m 12.291915,34.438502 0.434238,0.0066 c 20.014377,0.256595 36.184221,16.58873 36.184221,36.666707 v 6.504782 C 46.434346,77.090222 43.999987,76.362108 41.642388,75.436613 27.045007,69.710386 16.044337,56.538532 13.081437,41.131892 12.655973,38.923423 12.392799,36.686445 12.289722,34.440694 Z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_0"
style="fill:url(#gradient_0);stroke-width:2.19311" />
<path
fill="url(#gradient_1)"
d="m 41.438428,33.83978 c 1.451843,-7.875479 5.219616,-15.404446 11.305513,-21.490344 0.497838,-0.497837 1.004447,-0.980322 1.524216,-1.449649 0.517575,0.469327 1.026378,0.951812 1.526409,1.449649 6.074931,6.077125 9.842705,13.595127 11.29674,21.459641 -5.296375,4.153762 -9.689186,9.412854 -12.829729,15.428571 -3.136156,-5.974047 -7.53774,-11.23314 -12.823149,-15.395674 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_1"
style="fill:url(#gradient_1);stroke-width:2.19311" />
<path
fill="url(#gradient_2)"
d="M 33.758135,28.900884 C 35.892037,20.613098 40.199317,12.774901 46.684361,6.2876637 48.478331,4.4936948 50.377569,2.8642095 52.362338,1.4035943 L 54.268157,0 56.176168,1.4035943 c 1.984769,1.4628084 3.884009,3.0901005 5.677977,4.8840694 6.480658,6.4806583 10.790132,14.3210483 12.924033,22.6022543 -2.015473,1.030764 -3.949802,2.199695 -5.783247,3.49802 C 67.332549,24.486141 63.437574,16.961559 57.307815,10.833993 56.548998,10.072982 55.768249,9.3492537 54.965567,8.6584222 L 54.265964,8.0553153 53.56636,8.6584222 C 52.765872,9.3492537 51.985123,10.072982 51.226304,10.833993 45.089966,16.970332 41.190606,24.503686 39.53261,32.414255 37.694779,31.111545 35.764837,29.933841 33.753749,28.900884 Z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_2"
style="fill:url(#gradient_2);stroke-width:2.19311" />
<path
fill="url(#gradient_3)"
d="m 96.244398,34.438502 -0.434237,0.0066 c -20.01657,0.256595 -36.186415,16.58873 -36.186415,36.666707 v 6.506976 C 79.890332,73.322449 95.279427,55.74901 96.244398,34.440694 Z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_3"
style="fill:url(#gradient_3);stroke-width:2.19311" />
<path
fill="url(#gradient_4)"
d="m 12.68887,25.874383 0.149132,0.0022 c 4.506853,0.05702 8.996162,0.787328 13.290282,2.168991 11.959063,3.851112 21.891685,12.759549 27.168322,24.058483 l 0.973744,2.083461 0.969357,-2.085654 C 62.347596,36.789522 77.776168,26.104661 95.702698,25.876577 l 0.149133,-0.0022 c 0.291684,2.098811 0.443009,4.241486 0.445201,6.421443 l -0.510995,0.0066 C 74.600536,32.576546 57.485458,49.860493 57.485458,71.111788 v 6.917088 c -1.048309,0.155711 -2.046178,0.188608 -3.079135,0.192995 h -0.13378 c -1.068047,0 -2.144867,-0.07238 -3.212915,-0.219313 v -6.89077 c 0,-21.251295 -17.117271,-38.535242 -38.300578,-38.809382 l -0.510997,-0.0066 c 0,-2.147061 0.149133,-4.296315 0.445203,-6.421443 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_4"
style="fill:url(#gradient_4);stroke-width:2.19311" />
<path
fill="#53cd53"
d="m 101.9772,99.716601 c 0,-2.46287 -1.997925,-4.462992 -4.460795,-4.462992 H 85.243728 c -2.460676,0 -4.460798,1.997929 -4.460798,4.462992 v 3.822599 c 0,2.46068 1.997928,4.4608 4.460798,4.4608 h 12.272677 c 2.460676,0 4.460795,-1.99793 4.460795,-4.4608 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_5"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 78.620517,84.818763 c 0,-2.460676 -1.997928,-4.460798 -4.460797,-4.460798 h -6.096864 c -2.460676,0 -4.460798,1.997929 -4.460798,4.460798 v 3.824795 c 0,2.460676 1.997929,4.460798 4.460798,4.460798 h 6.096864 c 2.460676,0 4.460797,-1.997929 4.460797,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_6"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 61.450612,84.818763 c 0,-2.460676 -1.997929,-4.460798 -4.460798,-4.460798 h -6.09467 c -2.460676,0 -4.460798,1.997929 -4.460798,4.460798 v 3.824795 c 0,2.460676 1.997929,4.460798 4.460798,4.460798 h 6.09467 c 2.460676,0 4.460798,-1.997929 4.460798,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_7"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 27.11738,84.818763 c 0,-2.460676 -1.997929,-4.460798 -4.460798,-4.460798 h -6.096863 c -2.460676,0 -4.460798,1.997929 -4.460798,4.460798 v 3.824795 c 0,2.460676 1.997929,4.460798 4.460798,4.460798 h 6.096863 c 2.460676,0 4.460798,-1.997929 4.460798,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_8"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 95.78823,84.818763 c 0,-2.460676 -1.997929,-4.460798 -4.460798,-4.460798 h -6.09467 c -2.462869,0 -4.462991,1.997929 -4.462991,4.460798 v 3.824795 c 0,2.460676 1.997928,4.460798 4.462991,4.460798 h 6.09467 c 2.460676,0 4.460798,-1.997929 4.460798,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_9"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 21.038062,69.923119 c 0,-2.46287 -1.997928,-4.462991 -4.460797,-4.462991 h -6.09467 c -2.4606765,0 -4.4607984,1.997929 -4.4607984,4.462991 v 3.822601 c 0,2.460676 1.9979288,4.460798 4.4607984,4.460798 h 6.09467 c 2.460676,0 4.460797,-1.997928 4.460797,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_10"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 101.9772,69.923119 c 0,-2.46287 -1.997925,-4.462991 -4.460795,-4.462991 h -6.09467 c -2.460676,0 -4.460797,1.997929 -4.460797,4.462991 v 3.822601 c 0,2.460676 1.997928,4.460798 4.460797,4.460798 h 6.09467 c 2.460676,0 4.460795,-1.997928 4.460795,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_11"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 44.282899,84.818763 c 0,-2.460676 -1.997928,-4.460798 -4.460798,-4.460798 h -6.09467 c -2.462869,0 -4.462991,1.997929 -4.462991,4.460798 v 3.824795 c 0,2.460676 1.99793,4.460798 4.462991,4.460798 h 6.09467 c 2.460676,0 4.460798,-1.997929 4.460798,-4.460798 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_12"
style="stroke-width:2.19311" />
<path
fill="#fe9801"
d="m 27.21607,99.716601 c 0,-2.46287 -1.997928,-4.462992 -4.460798,-4.462992 H 10.482595 c -2.4606765,0 -4.4607984,1.997929 -4.4607984,4.462992 v 3.822599 c 0,2.46068 1.9979288,4.4608 4.4607984,4.4608 h 12.272677 c 2.460676,0 4.460798,-1.99793 4.460798,-4.4608 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_13"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 83.756795,67.041365 c 0.655742,0.776363 1.054889,1.783003 1.054889,2.881754 v 3.822601 c 0,2.460676 -1.997928,4.460798 -4.460798,4.460798 h -6.096863 c -1.557112,0 -2.927809,-0.798293 -3.723911,-2.006701 4.91258,-2.307158 9.377765,-5.414803 13.226683,-9.158452 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_14"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 24.529503,66.738714 c 3.796284,3.76558 8.20664,6.919282 13.066585,9.263722 -0.774169,1.320257 -2.210661,2.204082 -3.848919,2.204082 h -6.096862 c -2.460676,0 -4.460798,-1.997928 -4.460798,-4.460798 v -3.822601 c 0,-1.247883 0.510996,-2.375144 1.337801,-3.186598 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_15"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 14.015705,51.362778 c 1.868535,4.322632 4.359915,8.346999 7.366677,11.950289 h -4.822663 c -2.460676,0 -4.460798,-1.997928 -4.460798,-4.460797 v -3.824795 c 0,-1.517637 0.758819,-2.85763 1.916784,-3.664697 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_16"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 94.369284,51.764118 c 0.87286,0.813646 1.418946,1.975997 1.418946,3.263357 v 3.824795 c 0,2.460676 -1.997929,4.460797 -4.460798,4.460797 h -4.116479 c 2.897106,-3.471703 5.313921,-7.355711 7.156137,-11.548949 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_17"
style="stroke-width:2.19311" />
<path
fill="#ffffff"
d="m 78.782808,99.819677 c 0,-2.432165 -1.971611,-4.40597 -4.403777,-4.40597 H 33.738397 c -2.432166,0 -4.405969,1.973805 -4.405969,4.40597 v 3.774353 c 0,2.43217 1.973803,4.40378 4.405969,4.40378 h 40.640634 c 2.432166,0 4.403777,-1.97161 4.403777,-4.40378 z"
stroke-linejoin="round"
fill-rule="evenodd"
id="path_18"
style="stroke-width:2.19311" />
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,97 +1,97 @@
# Contributing
# Contribution guidelines
First off, thanks for considering contributing to FlorisBoard!
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.
## Giving general feedback
## General contributions
### Translation
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.
> [!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.
Optionally you can also use the review function within Google Play or email me at [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I love to hear from you! Note, that the amount of feedback emails I get is overwhelmingly high - so if I don't answer or answer really late, I apologize - I guarantee though that I read through every email and that I will use every feedback to improve FlorisBoard :)
### Bug reporting
## Translations
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.
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. This is
the only source of translations from now on - **PRs that add/update translations are no longer accepted.** The list of languages in Crowdin covers the top 20 languages, but feel free to email me at [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a language and I'll add 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.
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.
## Code contributions
You are always welcome to contribute new features or work on existing issues, there are a lot to choose from :) It is always best to quickly ask if someone is already working on this issue to avoid duplicate issues.
> [!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.
### 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)
- The following tools must be installed:
- Android Studio (bundles SDK and NDK)
- Java 17
- CMake 3.22+
- Clang 15+
- Git
- [Rust](https://www.rust-lang.org/tools/install)
- Utilities (optional)
- Python 3.10+
- Bash, realpath, grep, ...
### Manual build without Android Studio
If you want to manually build the project without Android Studio you must ensure that the Android SDK and NDK are properly installed on your system. Then issue
```./gradlew clean && ./gradlew assembleDebug```
and Gradle should take care of every build task.
## Joining the team
If you want to join the FlorisBoard maintainer/moderator team and be part of this project's journey that's great to hear! For more info see [this announcement](https://github.com/florisboard/florisboard/discussions/2314)!
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!
## Adding a new feature or making large changes
### Basic Requirements
If you intend to add a new feature or to make large changes, please discuss this first through a proposal on GitHub. Discussing your idea enables both you and the dev team that we are on the same page before you start on working on your change. If you have any questions, feel free to ask for help at any time!
- A passion for seeing FlorisBoard flourish
- Good English skills for team and public communication
- A GitHub account and a Matrix handle
## Adding a new keyboard layout
### Why Join
Adding a layout to FlorisBoard is very simple and does not require any coding skills, although you should understand the
basics of the JSON syntax (it is very easy though by just looking at some other layout files). Most of the time is
enough to look at the existing layout files, but the following attempts to help you in creating layouts from scratch.
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!
### Adding the layout
### Available Roles
Since v0.3.14-beta06 it is possible to add custom layouts for all types using the new extension format, Flex.
Currently the following roles are available and need help:
Keyboard layout assets are grouped in [`app/src/main/assets/ime/keyboard`](app/src/main/assets/ime/keyboard) and are
further sub-grouped into the following:
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
- `org.florisboard.composers`: Defines standard composers for interpreting input, currently supports basic typing and
Korean input. Most of the time you won't need to add new composers, so if you don't know what they are always
assume `appender` (the default composer which does not alter input in any way) is in use.
- `org.florisboard.currencysets`: Lists all currency sets, which can be chosen for each subtype. If you consider adding
a new one, make sure that the first currency symbol matches the name of the currency set and also ensure that you have
exactly 6 currency symbols. This is important as the symbol layouts have exactly 6 slots available to fill these
defined currency symbols in.
- `org.florisboard.layouts`: Contains the actual layout files for all layout types.
- `org.florisboard.localization`: Contains all popup mappings and subtype presets (formally the `config.json` file). The
subtype presets are a list of all pre-made subtypes. Each time the user selects a language in the `Subtype Add`
-dialog, all options configured here will get selected if found in the presets. The language tag must adhere to the
IETF BCP 47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)). For
example, Dutch as spoken in Belgium is `nl-be`.
To add a new layout, head to above directory and add the necessary files to each extension group.
For the `code` field of each key, make sure to use the UTF-8 code. An useful tool for finding the correct code
is [unicode-table.com](https://unicode-table.com/en/). From there, you search for your letter and then use the HTML
code, but without the `&#;`
For internal codes of functional or UI keys, see
[`app/src/main/kotlin/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt`](app/src/main/kotlin/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt)
.
The label is equally important and should always match up with the defined code. If `code` and `label` don't match up,
FlorisBoard won't crash but it will most likely lead to confusion in the key processing logic.
Any accents or diacritics that should be exposed via long press can be added
at [`app/src/main/assets/ime/keyboard/org.florisboard.localization/popupMappings/<languageTag>.json`](app/src/main/assets/ime/keyboard/org.florisboard.localization/popupMappings)
. For each key, you can add 1 main and several relevant accents. The main accent should be used for accents which are
important for the language you add. The main field is used for determining if a hint or an accent should take priority,
so please make sure to leave main empty and just use relevant for accents which are not-so important.
For popups of non-`characters` layout, simply add the popup directly to each key via the `popup` field.
## Adding a new dictionary for a language
Currently any kind of dictionaries are not accepted until the suggestion implementation has been sorted out.
## 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.
### 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.
Alternatively, you can also use ADB (Android Debug Bridge) to capture the error log. This is recommended for experienced
users only.
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
If none of the above options are feasible for you but you still want to show your support, you can also buy me a coffee,
so I can stay up all night and chase away bugs or add new cool stuff :)
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 :)
See the `Sponsors` button for available options!

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 Patrick Goldinger
Copyright 2020-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.

View File

@@ -1,21 +1,19 @@
<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/workflows/FlorisBoard%20CI/badge.svg?event=push)
# 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** is a free and open-source keyboard for Android 7.0+
devices. It aims at being modern, user-friendly and customizable while
fully respecting your privacy. Currently in early-beta state.
*Note: Due to various reasons development has been quite stuck in the past year, but things are slowly improving again and new releases/features will follow in the near future, please see the [roadmap](ROADMAP.md) for details!*
<table>
<tr>
<th align="center" width="50%">
<h3>Stable <a href="https://github.com/florisboard/florisboard/releases/latest"><img alt="Latest stable release" src="https://img.shields.io/github/v/release/florisboard/florisboard"></a></h3>
<h3>Stable <a href="https://github.com/florisboard/florisboard/releases/latest"><img alt="Latest stable release" src="https://img.shields.io/github/v/release/florisboard/florisboard?sort=semver&display_name=tag&color=28a745"></a></h3>
</th>
<th align="center" width="50%">
<h3>Beta <a href="https://github.com/florisboard/florisboard/releases"><img alt="Latest beta release" src="https://img.shields.io/github/v/release/florisboard/florisboard?include_prereleases"></a></h3>
<h3>Preview <a href="https://github.com/florisboard/florisboard/releases"><img alt="Latest preview release" src="https://img.shields.io/github/v/release/florisboard/florisboard?include_prereleases&sort=semver&display_name=tag&color=fd7e14"></a></h3>
</th>
</tr>
<tr>
@@ -23,7 +21,7 @@ fully respecting your privacy. Currently in early-beta state.
<p><i>Major versions only</i><br><br>Updates are more polished, new features are matured and tested through to ensure a stable experience.</p>
</td>
<td valign="top">
<p><i>Alpha/Beta versions</i><br><br>Updates contain new features that may not be fully matured yet and bugs are more likely to occur. Allows you to give early feedback.</p>
<p><i>Major + Alpha/Beta/Rc versions</i><br><br>Updates contain new features that may not be fully matured yet and bugs are more likely to occur. Allows you to give early feedback.</p>
</td>
</tr>
<tr>
@@ -36,6 +34,11 @@ fully respecting your privacy. Currently in early-beta state.
</p>
<p>
**Obtainium**: [Auto-import stable config][obtainium_stable]
</p>
<p>
**Manual**: Download and install the APK from the release page.
</p>
@@ -44,7 +47,12 @@ 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 [beta 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-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))
</p>
<p>
**Obtainium**: [Auto-import preview config][obtainium_preview]
</p>
<p>
@@ -56,25 +64,33 @@ fully respecting your privacy. Currently in early-beta state.
</tr>
</table>
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme.
Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
## Highlighted features
- Integrated clipboard manager / history
- Advanced theming support and customization
- Integrated extension support (still evolving)
- Emoji keyboard
- Emoji keyboard / history / suggestions
Word suggestions/spell checking are not included in the current releases and are a major goal for the v0.5.0 milestone.
> [!IMPORTANT]
> Word suggestions/spell checking are not included in the current releases
> and are a major goal for the v0.5 milestone.
Feature roadmap: See [ROADMAP.md](ROADMAP.md)
## Contributing
Wanna contribute to FlorisBoard? That's great to hear! There are lots of
different ways to help out. Bug reporting, making pull requests,
translating FlorisBoard to make it more accessible, etc. For more
information see the [contributing guidelines](CONTRIBUTING.md). Thank
you for your help!
Want to contribute to FlorisBoard? That's great to hear! There are lots of
different ways to help out, please see the [contribution guidelines](CONTRIBUTING.md) for more info.
## Addons Store
The official [Addons Store](https://beta.addons.florisboard.org) offers the possibility for the community to share and download FlorisBoard extensions.
Instructions on how to publish addons can be found [here](https://github.com/florisboard/florisboard/wiki/How-to-publish-on-FlorisBoard-Addons).
Many thanks to Ali ([@4H1R](https://github.com/4H1R)) for implementing the store!
> [!NOTE]
> During the initial beta release phase, the Addons Store _will_ only accept theme extensions.
> Later on we plan to add support for language packs and keyboard extensions.
## List of permissions FlorisBoard requests
Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List-of-permissions-FlorisBoard-requests)
@@ -83,8 +99,6 @@ to get more information on this topic.
## Used libraries, components and icons
* [AndroidX libraries](https://github.com/androidx/androidx) by
[Android Jetpack](https://github.com/androidx)
* [Accompanist Compose UI libraries](https://github.com/google/accompanist/) by
[Google](https://github.com/google)
* [AboutLibraries](https://github.com/mikepenz/AboutLibraries) by
[mikepenz](https://github.com/mikepenz)
* [Google Material icons](https://github.com/google/material-design-icons) by
@@ -95,14 +109,12 @@ to get more information on this topic.
[Kotlin](https://github.com/Kotlin)
* [KotlinX serialization library](https://github.com/Kotlin/kotlinx.serialization) by
[Kotlin](https://github.com/Kotlin)
* [ICU4C](https://github.com/unicode-org/icu) by
[The Unicode Consortium](https://github.com/unicode-org)
Many thanks to [Nikolay Anzarov](https://www.behance.net/nikolayanzarov) ([@BloodRaven0](https://github.com/BloodRaven0)) for designing and providing the main app icons to this project!
## License
```
Copyright 2020-2023 Patrick Goldinger
Copyright 2020-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.
@@ -116,3 +128,12 @@ 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.
```
Thanks to [The FlorisBoard Contributors](https://github.com/florisboard/florisboard/graphs/contributors) for making this project possible!
<!-- BEGIN SECTION: obtainium_links -->
<!-- auto-generated link templates, do NOT edit by hand -->
<!-- see fastlane/update-readme.sh -->
[obtainium_preview]: https://apps.obtainium.imranr.dev/redirect.html?r=obtainium://app/%7B%22id%22%3A%22dev.patrickgold.florisboard.beta%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fflorisboard%2Fflorisboard%22%2C%22author%22%3A%22florisboard%22%2C%22name%22%3A%22FlorisBoard%20Preview%22%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Atrue%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22preview%5C%22%7D%22%7D%0A
[obtainium_stable]: https://apps.obtainium.imranr.dev/redirect.html?r=obtainium://app/%7B%22id%22%3A%22dev.patrickgold.florisboard%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fflorisboard%2Fflorisboard%22%2C%22author%22%3A%22florisboard%22%2C%22name%22%3A%22FlorisBoard%20Stable%22%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22stable%5C%22%7D%22%7D%0A
<!-- END SECTION: obtainium_links -->

View File

@@ -2,65 +2,47 @@
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.
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 beta tracks.
## 0.4
**Main focus**: Getting the project back on track, see [this announcement](https://github.com/florisboard/florisboard/discussions/2314) for details. Note that this has also replaced the previous roadmap, however this step is necessary for getting the project back on track again.
This includes, but is not exclusive to:
- Fixing the most reported bugs/issues
- Merging in the Material You theme PR -> Adds Material You support (v0.4.0-alpha05)
- Merging in other external PRs as best as possible
- Reworking the Settings UI warning boxes and hiding any UI for features related to word suggestions until they are ready
- Remove existing glide/swipe typing (see 0.5 milestone)
- Fix compilation issues introduced with the 0.4 alphas as best as possible
Maybe in this release, but no guarantee and may be delayed to 0.5:
- Develop usable preview of an on-device statistical word suggestion algorithm (see the main [NLP project](https://github.com/florisboard/nlp) for details)
- Add experimental plugin system which supports communication with the aforementioned native suggestion algorithm
- Include precompiled dictionaries for major languages: English (US/UK), German, Spanish, French, Italian & Russian
Note that the previous versioning scheme has been dropped in favor of using a major.minor.patch versioning scheme, so versions like `0.3.16` are a thing of the past :)
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
- New text processing logic
- RFC document with technical details will be released later
- 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
- RFC document with technical details will be released later
- Add Tablet mode / Optimizations for landscape input based on new keyboard layout engine
- Reimplementation of glide typing with the new layout engine and word suggestion core
- Add support for any remaining new features introduced with Android 13
## 0.6
- Complete rework of the Emoji panel
- Recently used / Emoji history (already implemented with 0.3.14)
- Emoji search
- Emoji suggestions when using :emoji_name: syntax
- Kaomoji panel implementation (the third tab which currently has "not yet implemented")
- Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles)
- 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
- 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)
## Backlog / Planned (unassigned)
**Features that MAY be added (even in versions mentioned above) or dismissed**
- Upgrade Settings UI to Material 3
- Full on-board layout editor which allows users to create their own layouts without writing a JSON file
- Import/Export of custom layout files packed in Flex extensions
- 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
- Proximity-based key typo detection
- Floating keyboard
- Stickers/GIFs
- Kaomoji panel implementation
- FlorisBoard landing web page for presentation
- Implementing additional layouts
- Support for Tasker/Automate/MacroDroid plugins

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 Patrick Goldinger
* Copyright (C) 2022-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.
@@ -15,11 +15,12 @@
*/
import java.io.ByteArrayOutputStream
import java.io.File
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
plugins {
alias(libs.plugins.agp.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.plugin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.mannodermaus.android.junit5)
@@ -33,7 +34,7 @@ val projectBuildToolsVersion: String by project
val projectNdkVersion: String by project
val projectVersionCode: String by project
val projectVersionName: String by project
val projectVersionNameSuffix: String by project
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "")
android {
namespace = "dev.patrickgold.florisboard"
@@ -49,7 +50,6 @@ android {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = listOf(
"-Xallow-result-return-type",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-Xjvm-default=all-compatibility",
)
@@ -60,11 +60,13 @@ android {
minSdk = projectMinSdk.toInt()
targetSdk = projectTargetSdk.toInt()
versionCode = projectVersionCode.toInt()
versionName = projectVersionName
versionName = projectVersionName.substringBefore("-")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "BUILD_COMMIT_HASH", "\"${getGitCommitHash()}\"")
buildConfigField("String", "FLADDONS_API_VERSION", "\"v~draft2\"")
buildConfigField("String", "FLADDONS_STORE_URL", "\"beta.addons.florisboard.org\"")
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
@@ -72,23 +74,6 @@ android {
arg("room.expandProjection", "true")
}
externalNativeBuild {
cmake {
targets("florisboard-native")
cppFlags("-std=c++20", "-stdlib=libc++")
arguments(
"-DCMAKE_ANDROID_API=" + minSdk.toString(),
"-DICU_ASSET_EXPORT_DIR=" + project.file("src/main/assets/icu4c").absolutePath,
"-DBUILD_SHARED_LIBS=false",
"-DANDROID_STL=c++_static",
)
}
}
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
sourceSets {
maybeCreate("main").apply {
assets {
@@ -111,32 +96,18 @@ android {
}
buildFeatures {
buildConfig = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
}
externalNativeBuild {
cmake {
path("src/main/cpp/CMakeLists.txt")
}
}
buildTypes {
named("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug-${getGitCommitHash(short = true)}"
versionNameSuffix = "-debug+${getGitCommitHash(short = true)}"
isDebuggable = true
isJniDebuggable = false
ndk {
// For running FlorisBoard on the emulator
// abiFilters += listOf("x86", "x86_64")
}
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_debug")
resValue("mipmap", "floris_app_icon_round", "@mipmap/ic_app_icon_debug_round")
resValue("drawable", "floris_app_icon_foreground", "@drawable/ic_app_icon_debug_foreground")
@@ -174,11 +145,6 @@ android {
initWith(getByName("release"))
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += listOf("release")
ndk {
// For running FlorisBoard on the emulator
abiFilters += listOf("x86", "x86_64")
}
}
}
@@ -196,24 +162,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()
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation(libs.accompanist.flowlayout)
implementation(libs.accompanist.systemuicontroller)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.autofill)
implementation(libs.androidx.collection.ktx)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.compose.ui)
@@ -222,6 +185,8 @@ dependencies {
implementation(libs.androidx.core.splashscreen)
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)
@@ -236,6 +201,12 @@ dependencies {
implementation(libs.patrickgold.jetpref.datastore.ui)
implementation(libs.patrickgold.jetpref.material.ui)
implementation(project(":lib:android"))
implementation(project(":lib:color"))
implementation(project(":lib:kotlin"))
implementation(project(":lib:native"))
implementation(project(":lib:snygg"))
testImplementation(libs.equalsverifier)
testImplementation(libs.kotest.assertions.core)
testImplementation(libs.kotest.extensions.roboelectric)

3
app/lint.xml Normal file
View File

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

View File

@@ -0,0 +1,68 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "dc886df9792fe6b6ad532cc7aa7c4a84",
"entities": [
{
"tableName": "clipboard_files",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, `_display_name` TEXT NOT NULL, `_size` INTEGER NOT NULL, `orientation` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, PRIMARY KEY(`_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayName",
"columnName": "_display_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "size",
"columnName": "_size",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "orientation",
"columnName": "orientation",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mimeTypes",
"columnName": "mimeTypes",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_clipboard_files__id",
"unique": false,
"columnNames": [
"_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_files__id` ON `${TABLE_NAME}` (`_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'dc886df9792fe6b6ad532cc7aa7c4a84')"
]
}
}

View File

@@ -0,0 +1,94 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "145ca5bf4bff8e98f71ebc70ab3b495b",
"entities": [
{
"tableName": "clipboard_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL DEFAULT 0, `isRemoteDevice` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "creationTimestampMs",
"columnName": "creationTimestampMs",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isPinned",
"columnName": "isPinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mimeTypes",
"columnName": "mimeTypes",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSensitive",
"columnName": "isSensitive",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isRemoteDevice",
"columnName": "isRemoteDevice",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"_id"
]
},
"indices": [
{
"name": "index_clipboard_history__id",
"unique": false,
"columnNames": [
"_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '145ca5bf4bff8e98f71ebc70ab3b495b')"
]
}
}

View File

@@ -0,0 +1,94 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "1dd181d116dcb4530fb5b33451ea9ab5",
"entities": [
{
"tableName": "clipboard_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `is_sensitive` INTEGER NOT NULL DEFAULT 0, `is_remote_device` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "creationTimestampMs",
"columnName": "creationTimestampMs",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isPinned",
"columnName": "isPinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mimeTypes",
"columnName": "mimeTypes",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSensitive",
"columnName": "is_sensitive",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isRemoteDevice",
"columnName": "is_remote_device",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"_id"
]
},
"indices": [
{
"name": "index_clipboard_history__id",
"unique": false,
"columnNames": [
"_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dd181d116dcb4530fb5b33451ea9ab5')"
]
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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
import androidx.test.ext.junit.runners.AndroidJUnit4

View File

@@ -1,24 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020-2022 Patrick Goldinger
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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Permission needed to vibrate if the user has key press vibration enabled -->
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- Permission needed to create notifications on devices running Android 13+ -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Android 11+ only: Define that FlorisBoard requests to see all apps that
ship with an IME or Spell Check service. This is used to guide the user
in the Settings Ui why FlorisBoard may not be working.
@@ -39,10 +28,11 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/floris_app_icon"
android:label="@string/floris_app_name"
android:enableOnBackInvokedCallback="true"
android:roundIcon="@mipmap/floris_app_icon_round"
android:supportsRtl="true"
android:theme="@style/FlorisAppTheme"
tools:targetApi="s">
tools:targetApi="tiramisu">
<!-- Allow app to be profiled for benchmarking and baseline profile generation -->
<profileable android:shell="true"/>
@@ -81,9 +71,25 @@
android:roundIcon="@mipmap/floris_app_icon_round"
android:windowSoftInputMode="adjustResize"
android:theme="@style/FlorisAppTheme.Splash"
android:exported="false">
android:exported="true">
<intent-filter>
<data android:scheme="florisboard" android:host="app-ui"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="ui" android:host="florisboard" android:pathPrefix="/" />
</intent-filter>
<intent-filter android:label="Import Extension">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="application/vnd.florisboard.extension+zip"/>
<data android:mimeType="application/octet-stream"/><!-- Firefox looking at you :eyes: -->
</intent-filter>
<intent-filter android:label="Import Extension">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.florisboard.extension+zip"/>
<data android:mimeType="application/octet-stream"/><!-- Firefox looking at you :eyes: -->
</intent-filter>
</activity>
@@ -103,24 +109,6 @@
</intent-filter>
</activity-alias>
<!-- Import File Bridging Activity -->
<activity
android:name="dev.patrickgold.florisboard.app.ext.ImportFileActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__title"
android:launchMode="singleTask"
android:roundIcon="@mipmap/floris_app_icon_round"
android:windowSoftInputMode="adjustResize"
android:theme="@style/FlorisAppTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="*" android:host="*" android:pathPattern=".*\\.flex"/>
<data android:scheme="*" android:host="*" android:pathPattern=".*\\.xpi"/>
</intent-filter>
</activity>
<!-- Crash Dialog Activity -->
<activity
android:name="dev.patrickgold.florisboard.lib.crashutility.CrashDialogActivity"
@@ -128,6 +116,18 @@
android:label="@string/crash_dialog__title"
android:theme="@style/CrashDialogTheme"/>
<!-- Copy to Clipboard Activity -->
<activity
android:name="dev.patrickgold.florisboard.ime.clipboard.FlorisCopyToClipboardActivity"
android:theme="@style/FlorisAppTheme.Transparent"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
<!-- Clipboard Media File Provider -->
<provider
android:name="dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardMediaProvider"

View File

@@ -57,12 +57,89 @@
"ừz": "ư", "ửz": "ư", "ữz": "ư", "ứz": "ư", "ựz": "ư",
"ỳz": "y", "ỷz": "y", "ỹz": "y", "ýz": "y", "ỵz": "y",
"áf": "à", "ár": "ả", "áx": "ã", "áj": "ạ",
"às": "á", "àr": "ả", "àx": "ã", "àj": "ạ",
"ảs": "á", "ảf": "à", "ảx": "ã", "ảj": "ạ",
"ãs": "á", "ãf": "à", "ãr": "ả", "ãj": "ạ",
"ạs": "á", "ạf": "à", "ạr": "ả", "ạx": "ã",
"ấf": "ầ", "ấr": "ẩ", "ấx": "ẫ", "ấj": "ậ",
"ầs": "ấ", "ầr": "ẩ", "ầx": "ẫ", "ầj": "ậ",
"ẩs": "ấ", "ẩf": "ầ", "ẩx": "ẫ", "ẩj": "ậ",
"ẫs": "ấ", "ẫf": "ầ", "ẫr": "ẩ", "ẫj": "ậ",
"ậs": "ấ", "ậf": "ầ", "ậr": "ẩ", "ậx": "ẫ",
"ắf": "ằ", "ắr": "ẳ", "ắx": "ẵ", "ắj": "ặ",
"ằs": "ắ", "ằr": "ẳ", "ằx": "ẵ", "ằj": "ặ",
"ẳs": "ắ", "ẳf": "ằ", "ẳx": "ẵ", "ẳj": "ặ",
"ẵs": "ắ", "ẵf": "ằ", "ẵr": "ẳ", "ẵj": "ặ",
"ặs": "ắ", "ặf": "ằ", "ặr": "ẳ", "ặx": "ẵ",
"éf": "è", "ér": "ẻ", "éx": "ẽ", "éj": "ẹ",
"ès": "é", "èr": "ẻ", "èx": "ẽ", "èj": "ẹ",
"ẻs": "é", "ẻf": "è", "ẻx": "ẽ", "ẻj": "ẹ",
"ẽs": "é", "ẽf": "è", "ẽr": "ẻ", "ẽj": "ẹ",
"ẹs": "é", "ẹf": "è", "ẹr": "ẻ", "ẹx": "ẽ",
"ếf": "ề", "ếr": "ể", "ếx": "ễ", "ếj": "ệ",
"ềs": "ế", "ềr": "ể", "ềx": "ễ", "ềj": "ệ",
"ểs": "ế", "ểf": "ề", "ểx": "ễ", "ểj": "ệ",
"ễs": "ế", "ễf": "ề", "ễr": "ể", "ễj": "ệ",
"ệs": "ế", "ệf": "ề", "ệr": "ể", "ệx": "ễ",
"íf": "ì", "ír": "ỉ", "íx": "ĩ", "íj": "ị",
"ìs": "í", "ìr": "ỉ", "ìx": "ĩ", "ìj": "ị",
"ỉs": "í", "ỉf": "ì", "ỉx": "ĩ", "ỉj": "ị",
"ĩs": "í", "ĩf": "ì", "ĩr": "ỉ", "ĩj": "ị",
"ịs": "í", "ịf": "ì", "ịr": "ỉ", "ịx": "ĩ",
"óf": "ò", "ór": "ỏ", "óx": "õ", "ój": "ọ",
"òs": "ó", "òr": "ỏ", "òx": "õ", "òj": "ọ",
"ỏs": "ó", "ỏf": "ò", "ỏx": "õ", "ỏj": "ọ",
"õs": "ó", "õf": "ò", "õr": "ỏ", "õj": "ọ",
"ọs": "ó", "ọf": "ò", "ọr": "ỏ", "ọx": "õ",
"ốf": "ồ", "ốr": "ổ", "ốx": "ỗ", "ốj": "ộ",
"ồs": "ố", "ồr": "ổ", "ồx": "ỗ", "ồj": "ộ",
"ổs": "ố", "ổf": "ồ", "ổx": "ỗ", "ổj": "ộ",
"ỗs": "ố", "ỗf": "ồ", "ỗr": "ổ", "ỗj": "ộ",
"ộs": "ố", "ộf": "ồ", "ộr": "ổ", "ộx": "ỗ",
"ớf": "ờ", "ớr": "ở", "ớx": "ỡ", "ớj": "ợ",
"ờs": "ớ", "ờr": "ở", "ờx": "ỡ", "ờj": "ợ",
"ởs": "ớ", "ởf": "ờ", "ởx": "ỡ", "ởj": "ợ",
"ỡs": "ớ", "ỡf": "ờ", "ỡr": "ở", "ỡj": "ợ",
"ợs": "ớ", "ợf": "ờ", "ợr": "ở", "ợx": "ỡ",
"úf": "ù", "úr": "ủ", "úx": "ũ", "új": "ụ",
"ùs": "ú", "ùr": "ủ", "ùx": "ũ", "ùj": "ụ",
"ủs": "ú", "ủf": "ù", "ủx": "ũ", "ủj": "ụ",
"ũs": "ú", "ũf": "ù", "ũr": "ủ", "ũj": "ụ",
"ụs": "ú", "ụf": "ù", "ụr": "ủ", "ụx": "ũ",
"ứf": "ừ", "ứr": "ử", "ứx": "ữ", "ứj": "ự",
"ừs": "ứ", "ừr": "ử", "ừx": "ữ", "ừj": "ự",
"ửs": "ứ", "ửf": "ừ", "ửx": "ữ", "ửj": "ự",
"ữs": "ứ", "ữf": "ừ", "ữr": "ử", "ữj": "ự",
"ựs": "ứ", "ựf": "ừ", "ựr": "ử", "ựx": "ữ",
"ýf": "ỳ", "ýr": "ỷ", "ýx": "ỹ", "ýj": "ỵ",
"ỳs": "ý", "ỳr": "ỷ", "ỳx": "ỹ", "ỳj": "ỵ",
"ỷs": "ý", "ỷf": "ỳ", "ỷx": "ỹ", "ỷj": "ỵ",
"ỹs": "ý", "ỹf": "ỳ", "ỹr": "ỷ", "ỹj": "ỵ",
"ỵs": "ý", "ỵf": "ỳ", "ỵr": "ỷ", "ỵx": "ỹ",
"gias": "giá", "giaf": "già", "giar": "giả", "giax": "giã", "giaj": "giạ",
"gía": "giá", "gìa": "già", "gỉa": "giả", "gĩa": "giã", "gịa": "giạ",
"gíă": "giắ", "gìă": "giằ", "gỉă": "giẳ", "gĩă": "giẵ", "gịă": "giặ",
"gíâ": "giấ", "gìâ": "giầ", "gỉâ": "giẩ", "gĩẫ": "giẫ", "gịâ": "giậ",
"gíe": "gié", "gìe": "giè", "gỉe": "giẻ", "gĩe": "giẽ", "gịe": "giẹ",
"gíê": "giế", "gìê": "giề", "gỉê": "giể", "gĩê": "giễ", "gịê": "giệ",
"gío": "gió", "gìo": "giò", "gỉo": "giỏ", "gĩo": "giõ", "gịo": "giọ",
"gíô": "giố", "gìô": "giồ", "gỉô": "giổ", "gĩô": "giỗ", "gịô": "giộ",
"gíơ": "giớ", "gìơ": "giờ", "gỉơ": "giở", "gĩơ": "giỡ", "gịơ": "giợ",
"gius": "giú", "giuf": "giù", "giur": "giủ", "giux": "giũ", "giuj": "giụ",
"gíu": "giú", "gìu": "giù", "gỉu": "giủ", "gĩu": "giũ", "gịu": "giụ",
"gíư": "giứ", "gìư": "giừ", "gỉư": "giử", "gĩư": "giữ", "gịư": "giự",
"ghíê": "ghiế", "ghìê": "ghiề", "ghỉê": "ghiể", "ghĩê": "ghiễ", "ghịê": "ghiệ",
"acw": "ăc", "amw": "ăm", "anw": "ăn", "apw": "ăp", "atw": "ăt", "angw": "ăng",
"aca": "âc", "ama": "âm", "ana": "ân", "apa": "âp", "ata": "ât", "aua": "âu", "aya": "ây", "anga": "âng",
"eme": "êm", "ene": "ên", "epe": "êp", "ete": "êt", "enhe": "ênh",
"oio": "ôi","omo": "ôm", "ono": "ôn", "opo": "ôp", "oto": "ôt", "ongo": "ông",
"oiw": "ơi", "omw": "ơm", "onw": "ơn", "opw": "ơp", "otw": "ơt",
"uaw": "ưa", "uiw": "ưi", "umw": "ưm", "unw": "ưn", "utw": "ưt", "uuw": "ưu", "ungw": "ưng",
"uaw": "ưa", "ucw": "ưc", "uiw": "ưi", "umw": "ưm", "unw": "ưn", "utw": "ưt", "uuw": "ưu", "ungw": "ưng",
"ieme": "iêm", "iene": "iên", "iepe": "iêp", "iete": "iêt", "ieue": "iêu", "ienge": "iêng",
"uocw": "ươc", "uoiw": "ươi", "uomw": "ươm", "uonw": "ươn", "uotw": "ươt", "uongw": "ương",
"uoco": "uôc", "uoio": "uôi", "uomo": "uôm", "uono": "uôn", "uoto": "uôt", "uongo": "uông",
@@ -79,7 +156,7 @@
"ême": "eme", "êne": "ene", "êpe": "epe", "ête": "ete",
"ôio": "oio", "ômo": "omo", "ôno": "ono", "ôpo": "opo", "ôto": "oto", "ôngo": "ongo",
"ơmw": "omw", "ơnw": "onw", "ơpw": "opw", "ơtw": "otw",
"ưaw": "uaw", "ưiw": "uiw", "ưmw": "umw", "ưnw": "unw" , "ưtw": "utw", "ưuw": "uuw", "ưngw": "ungw",
"ưaw": "uaw", "ưcw": "ucw", "ưiw": "uiw", "ưmw": "umw", "ưnw": "unw" , "ưtw": "utw", "ưuw": "uuw", "ưngw": "ungw",
"iême": "ieme", "iêne": "iene", "iêpe": "iepe", "iête": "iete", "iêue": "ieue", "iênge": "ienge",
"ươcw": "uocw", "ươiw": "uoiw", "ươmw": "uomw", "ươnw": "uonw", "ươtw": "uotw", "ươngw": "uongw",
"uyêne": "uyene", "uyêt": "uyete",
@@ -138,6 +215,7 @@
"inhs": "ính", "inhf": "ình", "inhr": "ỉnh", "inhx": "ĩnh", "inhj": "ịnh",
"ías": "ias", "ìaf": "iaf", "ỉar": "iar", "ĩax": "iax", "ịaj": "iaj",
"iás": "ias", "iàf": "iaf", "iảr": "iar", "iãx": "iax", "iạj": "iaj",
"ícs": "ics", "ịcj": "icj",
"íms": "ims", "ìmf": "imf", "ỉmr": "imr", "ĩmx": "imx", "ịmj": "imj",
"íns": "ins", "ìnf": "inf", "ỉnr": "inr", "ĩnx": "inx", "ịnj": "inj",
@@ -385,6 +463,8 @@
"ưas": "ứa", "ưaf": "ừa", "ưar": "ửa", "ưax": "ữa", "ưaj": "ựa",
"úaw": "ứa", "ùaw": "ừa", "ủaw": "ửa", "ũaw": "ữa", "ụaw": "ựa",
"ưcs": "ức", "ưcj": "ực",
"úcw": "ức", "ụcw": "ực",
"ưis": "ứi", "ưif": "ừi", "ưir": "ửi", "ưix": "ữi", "ưij": "ựi",
"úiw": "ứi", "ùiw": "ừi", "ủiw": "ửi", "ũiw": "ữi", "ụiw": "ựi",
"ưms": "ứm", "ưmf": "ừm", "ưmr": "ửm", "ưmx": "ữm", "ưmj": "ựm",
@@ -403,6 +483,8 @@
"ửar": "ưar", "ửaw": "ủaw", "ưarw": "uarw", "ủawr": "uawr",
"ữax": "ưax", "ữaw": "ũaw", "ưaxw": "uaxw", "ũawx": "uawx",
"ựaj": "ưaj", "ựaw": "ụaw", "ưajw": "uajw", "ụawj": "uawj",
"ứcs": "ưcs", "ứcw": "úcw", "ưcsw": "ucsw", "úcws": "ucws",
"ựcj": "ưcj", "ựcw": "ụcw", "ưcjw": "ucjw", "ụcwj": "ucwj",
"ứis": "ưis", "ứiw": "úiw", "ưisw": "uisw", "úiws": "uiws",
"ừif": "ưif", "ừiw": "ùiw", "ưifw": "uifw", "ùiwf": "uiwf",
"ửir": "ưir", "ửiw": "ủiw", "ưirw": "uirw", "ủiwr": "uiwr",
@@ -450,6 +532,11 @@
"iénge": "iếng", "iènge": "iềng", "iẻnge": "iểng", "iẽnge": "iễng", "iẹnge": "iệng",
"iêngs": "iếng", "iêngf": "iềng", "iêngr": "iểng", "iêngx": "iễng", "iêngj": "iệng",
"iếs": "iês", "iếe": "iée", "iêse": "iese", "iées": "iees",
"iềf": "iêf", "iềe": "ièe", "iêfe": "iefe", "ièef": "ieef",
"iểr": "iêr", "iểe": "iẻe", "iêre": "iere", "iẻer": "ieer",
"iễx": "iêx", "iễe": "iẽe", "iêxe": "iexe", "iẽex": "ieex",
"iệj": "iêj", "iệe": "iẹe", "iêje": "ieje", "iẹej": "ieej",
"iếms": "iêms", "iếme": "iéme", "iêmse": "iemse", "iémes": "iemes",
"iềmf": "iêmf", "iềme": "ième", "iêmfe": "iemfe", "ièmef": "iemef",
"iểmr": "iêmr", "iểme": "iẻme", "iêmre": "iemre", "iẻmer": "iemer",

View File

@@ -49,6 +49,12 @@
"authors": [ "iamrasel" ],
"direction": "ltr"
},
{
"id": "hindi_in",
"label": "हिंदी",
"authors": [ "npnpatidar" ],
"direction": "ltr"
},
{
"id": "bepo",
"label": "BÉPO",
@@ -104,12 +110,25 @@
"authors": [ "blucin" ],
"direction": "ltr"
},
{
"id": "colemak_dhm",
"label": "ColemakDHm",
"authors": [ "SteveP", "oneilljp" ],
"direction": "ltr"
},
{
"id": "danish",
"label": "Danish (QWERTY)",
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "diktor",
"label": "Diktor",
"authors": [ "kuroya2mouse" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:diktor"
},
{
"id": "dvorak",
"label": "Dvorak",
@@ -379,6 +398,12 @@
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "tamil",
"label": "Tamil",
"authors": [ "Clem0908" ],
"direction": "ltr"
},
{
"id": "thai_kedmanee",
"label": "Thai Kedmanee",
@@ -403,6 +428,18 @@
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "udmurt_compact",
"label": "Udmurt (3 чур)",
"authors": [ "vorgoron" ],
"direction": "ltr"
},
{
"id": "udmurt_extended",
"label": "Udmurt (4 чур)",
"authors": [ "vorgoron" ],
"direction": "ltr"
},
{
"id": "urdu_phonetic",
"label": "Urdu Phonetic",
@@ -421,6 +458,12 @@
"label": "Workman",
"authors": [ "icyphox" ],
"direction": "ltr"
},
{
"id": "jcuken_interslavic",
"label": "Interslavic (ЈЦУКЕН)",
"authors": [ "victorbnl" ],
"direction": "ltr"
}
],
"charactersMod": [
@@ -442,6 +485,12 @@
"authors": [ "HeiWiper" ],
"direction": "rtl"
},
{
"id": "diktor",
"label": "Diktor",
"authors": [ "kuroya2mouse" ],
"direction": "ltr"
},
{
"id": "dvorak",
"label": "Dvorak",

View File

@@ -0,0 +1,49 @@
[
[
{ "$": "auto_text_key", "code": 1383, "label": "է" },
{ "$": "auto_text_key", "code": 1385, "label": "թ" },
{ "$": "auto_text_key", "code": 1411, "label": "փ" },
{ "$": "auto_text_key", "code": 1393, "label": "ձ" },
{ "$": "auto_text_key", "code": 1403, "label": "ջ" },
{ "$": "auto_text_key", "code": 1408, "label": "ր" },
{ "$": "auto_text_key", "code": 1401, "label": "չ" },
{ "$": "auto_text_key", "code": 1395, "label": "ճ" },
{ "$": "auto_text_key", "code": 1386, "label": "ժ" },
{ "$": "auto_text_key", "code": 1390, "label": "ծ" }
],
[
{ "$": "auto_text_key", "code": 1412, "label": "ք" },
{ "$": "auto_text_key", "code": 1400, "label": "ո" },
{ "$": "auto_text_key", "code": 1381, "label": "ե" },
{ "$": "auto_text_key", "code": 1404, "label": "ռ" },
{ "$": "auto_text_key", "code": 1407, "label": "տ" },
{ "$": "auto_text_key", "code": 1384, "label": "ը" },
{ "$": "auto_text_key", "code": 1410, "label": "ւ" },
{ "$": "auto_text_key", "code": 1387, "label": "ի" },
{ "$": "auto_text_key", "code": 1413, "label": "օ" },
{ "$": "auto_text_key", "code": 1402, "label": "պ" }
],
[
{ "$": "auto_text_key", "code": 1377, "label": "ա" },
{ "$": "auto_text_key", "code": 1405, "label": "ս" },
{ "$": "auto_text_key", "code": 1380, "label": "դ" },
{ "$": "auto_text_key", "code": 1414, "label": "ֆ" },
{ "$": "auto_text_key", "code": 1379, "label": "գ" },
{ "$": "auto_text_key", "code": 1392, "label": "հ" },
{ "$": "auto_text_key", "code": 1397, "label": "յ" },
{ "$": "auto_text_key", "code": 1391, "label": "կ" },
{ "$": "auto_text_key", "code": 1388, "label": "լ" },
{ "$": "auto_text_key", "code": 1389, "label": "խ" }
],
[
{ "$": "auto_text_key", "code": 1382, "label": "զ" },
{ "$": "auto_text_key", "code": 1394, "label": "ղ" },
{ "$": "auto_text_key", "code": 1409, "label": "ց" },
{ "$": "auto_text_key", "code": 1406, "label": "վ" },
{ "$": "auto_text_key", "code": 1378, "label": "բ" },
{ "$": "auto_text_key", "code": 1398, "label": "ն" },
{ "$": "auto_text_key", "code": 1396, "label": "մ" },
{ "$": "auto_text_key", "code": 1399, "label": "շ" }
]
]

View File

@@ -42,9 +42,9 @@
}
],
[
{ "$": "case_selector",
{ "$": "case_selector",
"lower": { "code": 2499, "label": "ৃ" },
"upper": { "$": "multi_text_key", "codePoints": [2480, 2509], "label": "র্" }
"upper": { "code": 2435, "label": "" }
},
{ "$": "case_selector",
"lower": { "code": 2497, "label": "ু" },
@@ -109,4 +109,4 @@
"upper": { "code": 2486, "label": "শ" }
}
]
]
]

View File

@@ -0,0 +1,46 @@
[
[
{ "$": "auto_text_key", "code": 113, "label": "q" },
{ "$": "auto_text_key", "code": 119, "label": "w" },
{ "$": "auto_text_key", "code": 102, "label": "f" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 106, "label": "j" },
{ "$": "auto_text_key", "code": 108, "label": "l" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "case_selector",
"lower": { "code": 59, "label": ";", "popup": {
"relevant": [
{ "code": 58, "label": ":" }
]
} },
"upper": { "code": 58, "label": ":", "popup": {
"relevant": [
{ "code": 59, "label": ";" }
]
} }
}
],
[
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
{ "$": "auto_text_key", "code": 115, "label": "s" },
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 109, "label": "m" },
{ "$": "auto_text_key", "code": 110, "label": "n" },
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 111, "label": "o" }
],
[
{ "$": "auto_text_key", "code": 122, "label": "z" },
{ "$": "auto_text_key", "code": 120, "label": "x" },
{ "$": "auto_text_key", "code": 99, "label": "c" },
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 104, "label": "h" }
]
]

View File

@@ -0,0 +1,49 @@
[
[
{ "$": "auto_text_key", "code": 1094, "label": "ц" },
{ "$": "auto_text_key", "code": 1100, "label": "ь" },
{ "$": "auto_text_key", "code": 1103, "label": "я" },
{ "$": "case_selector",
"lower": { "code": 44, "label": ",", "popup": {
"relevant": [
{ "code": 46, "label": "." }
]
} },
"upper": { "code": 46, "label": ".", "popup": {
"relevant": [
{ "code": 44, "label": "," }
]
} }
},
{ "$": "auto_text_key", "code": 1079, "label": "з" },
{ "$": "auto_text_key", "code": 1074, "label": "в" },
{ "$": "auto_text_key", "code": 1082, "label": "к" },
{ "$": "auto_text_key", "code": 1076, "label": "д" },
{ "$": "auto_text_key", "code": 1095, "label": "ч" },
{ "$": "auto_text_key", "code": 1096, "label": "ш" },
{ "$": "auto_text_key", "code": 1097, "label": "щ" }
],
[
{ "$": "auto_text_key", "code": 1091 , "label": "у" },
{ "$": "auto_text_key", "code": 1080 , "label": "и" },
{ "$": "auto_text_key", "code": 1077 , "label": "е" },
{ "$": "auto_text_key", "code": 1086 , "label": "о" },
{ "$": "auto_text_key", "code": 1072 , "label": "а" },
{ "$": "auto_text_key", "code": 1083 , "label": "л" },
{ "$": "auto_text_key", "code": 1085 , "label": "н" },
{ "$": "auto_text_key", "code": 1090 , "label": "т" },
{ "$": "auto_text_key", "code": 1089 , "label": "с" },
{ "$": "auto_text_key", "code": 1088 , "label": "р" },
{ "$": "auto_text_key", "code": 1081 , "label": "й" }
],
[
{ "$": "auto_text_key", "code": 1101 , "label": "э" },
{ "$": "auto_text_key", "code": 1093 , "label": "х" },
{ "$": "auto_text_key", "code": 1099 , "label": "ы" },
{ "$": "auto_text_key", "code": 1102 , "label": "ю" },
{ "$": "auto_text_key", "code": 1073 , "label": "б" },
{ "$": "auto_text_key", "code": 1084 , "label": "м" },
{ "$": "auto_text_key", "code": 1087 , "label": "п" },
{ "$": "auto_text_key", "code": 1075 , "label": "г" }
]
]

View File

@@ -0,0 +1,42 @@
[
[
{ "$": "case_selector", "lower": { "code": 2335, "label": "ट" }, "upper": { "code": 2336, "label": "ठ" } },
{ "$": "case_selector", "lower": { "code": 2337, "label": "ड" }, "upper": { "code": 2338, "label": "ढ" } },
{ "$": "case_selector", "lower": { "code": 2375, "label": "े" }, "upper": { "code": 2376, "label": "ै" } },
{ "$": "case_selector", "lower": { "code": 2352, "label": "र" }, "upper": { "code": 2371, "label": "ृ" } },
{ "$": "case_selector", "lower": { "code": 2340, "label": "त" }, "upper": { "code": 2341, "label": "थ" } },
{ "$": "case_selector", "lower": { "code": 2351, "label": "य" }, "upper": { "code": 2399, "label": "य़" } },
{ "$": "case_selector", "lower": { "code": 2369, "label": "ु" }, "upper": { "code": 2370, "label": "ू" } },
{ "$": "case_selector", "lower": { "code": 2367, "label": "ि" }, "upper": { "code": 2368, "label": "ी" } },
{ "$": "case_selector", "lower": { "code": 2379, "label": "ो" }, "upper": { "code": 2380, "label": "ौ" } },
{ "$": "case_selector", "lower": { "code": 2346, "label": "प" }, "upper": { "code": 2398, "label": "फ़" } }
],
[
{ "$": "case_selector", "lower": { "code": 2366, "label": "ा" }, "upper": { "code": 2309, "label": "अ" } },
{ "$": "case_selector", "lower": { "code": 2360, "label": "स" }, "upper": { "code": 2358, "label": "श" } },
{ "$": "case_selector", "lower": { "code": 2342, "label": "द" }, "upper": { "code": 2343, "label": "ध" } },
{ "$": "case_selector", "lower": { "code": 2347, "label": "फ" }, "upper": { "code": 2364, "label": " ़" } },
{ "$": "case_selector", "lower": { "code": 2327, "label": "ग" }, "upper": { "code": 2328, "label": "घ" } },
{ "$": "case_selector", "lower": { "code": 2361, "label": "ह" }, "upper": { "code": 2307, "label": "" } },
{ "$": "case_selector", "lower": { "code": 2332, "label": "ज" }, "upper": { "code": 2333, "label": "झ" } },
{ "$": "case_selector", "lower": { "code": 2325, "label": "क" }, "upper": { "code": 2326, "label": "ख" } },
{ "$": "case_selector", "lower": { "code": 2354, "label": "ल" }, "upper": { "code": 2355, "label": "ळ" } }
],
[
{
"$": "case_selector",
"lower": { "$": "multi_text_key", "codePoints": [2332, 2381, 2334], "label": "ज्ञ" },
"upper": { "code": 2395, "label": "ज़" }
},
{
"$": "case_selector",
"lower": { "$": "multi_text_key", "codePoints": [2325, 2381, 2359], "label": "क्ष" },
"upper": { "code": 2359, "label": "ष" }
},
{ "$": "case_selector", "lower": { "code": 2330, "label": "च" }, "upper": { "code": 2331, "label": "छ" } },
{ "$": "case_selector", "lower": { "code": 2357, "label": "व" }, "upper": { "code": 2381, "label": "्" } },
{ "$": "case_selector", "lower": { "code": 2348, "label": "ब" }, "upper": { "code": 2349, "label": "भ" } },
{ "$": "case_selector", "lower": { "code": 2344, "label": "न" }, "upper": { "code": 2339, "label": "ण" } },
{ "$": "case_selector", "lower": { "code": 2350, "label": "म" }, "upper": { "code": 2306, "label": "ं" } }
]
]

View File

@@ -0,0 +1,36 @@
[
[
{ "$": "auto_text_key", "code": 1112, "label": "ј" },
{ "$": "auto_text_key", "code": 1094, "label": "ц" },
{ "$": "auto_text_key", "code": 1091, "label": "у" },
{ "$": "auto_text_key", "code": 1082, "label": "к" },
{ "$": "auto_text_key", "code": 1077, "label": "е" },
{ "$": "auto_text_key", "code": 1085, "label": "н" },
{ "$": "auto_text_key", "code": 1114, "label": "њ" },
{ "$": "auto_text_key", "code": 1075, "label": "г" },
{ "$": "auto_text_key", "code": 1096, "label": "ш" },
{ "$": "auto_text_key", "code": 1079, "label": "з" },
{ "$": "auto_text_key", "code": 1093, "label": "х" }
],
[
{ "$": "auto_text_key", "code": 1092 , "label": "ф" },
{ "$": "auto_text_key", "code": 1099 , "label": "ы" },
{ "$": "auto_text_key", "code": 1074 , "label": "в" },
{ "$": "auto_text_key", "code": 1072 , "label": "а" },
{ "$": "auto_text_key", "code": 1087 , "label": "п" },
{ "$": "auto_text_key", "code": 1088 , "label": "р" },
{ "$": "auto_text_key", "code": 1086 , "label": "о" },
{ "$": "auto_text_key", "code": 1083 , "label": "л" },
{ "$": "auto_text_key", "code": 1113 , "label": "љ" },
{ "$": "auto_text_key", "code": 1078 , "label": "ж" }
],
[
{ "$": "auto_text_key", "code": 1108 , "label": "є" },
{ "$": "auto_text_key", "code": 1095 , "label": "ч" },
{ "$": "auto_text_key", "code": 1089 , "label": "с" },
{ "$": "auto_text_key", "code": 1084 , "label": "м" },
{ "$": "auto_text_key", "code": 1080 , "label": "и" },
{ "$": "auto_text_key", "code": 1090 , "label": "т" },
{ "$": "auto_text_key", "code": 1073 , "label": "б" }
]
]

View File

@@ -0,0 +1,39 @@
[
[
{ "$": "auto_text_key", "code": 1081, "label": "й" },
{ "$": "auto_text_key", "code": 1094, "label": "ц" },
{ "$": "auto_text_key", "code": 1091, "label": "у" },
{ "$": "auto_text_key", "code": 1082, "label": "к" },
{ "$": "auto_text_key", "code": 1077, "label": "е" },
{ "$": "auto_text_key", "code": 1085, "label": "н" },
{ "$": "auto_text_key", "code": 1075, "label": "г" },
{ "$": "auto_text_key", "code": 1096, "label": "ш" },
{ "$": "auto_text_key", "code": 1097, "label": "щ" },
{ "$": "auto_text_key", "code": 1079, "label": "з" },
{ "$": "auto_text_key", "code": 1093, "label": "х" }
],
[
{ "$": "auto_text_key", "code": 1092 , "label": "ф" },
{ "$": "auto_text_key", "code": 1099 , "label": "ы" },
{ "$": "auto_text_key", "code": 1074 , "label": "в" },
{ "$": "auto_text_key", "code": 1072 , "label": "а" },
{ "$": "auto_text_key", "code": 1087 , "label": "п" },
{ "$": "auto_text_key", "code": 1088 , "label": "р" },
{ "$": "auto_text_key", "code": 1086 , "label": "о" },
{ "$": "auto_text_key", "code": 1083 , "label": "л" },
{ "$": "auto_text_key", "code": 1076 , "label": "д" },
{ "$": "auto_text_key", "code": 1078 , "label": "ж" },
{ "$": "auto_text_key", "code": 1101 , "label": "э" }
],
[
{ "$": "auto_text_key", "code": 1103 , "label": "я" },
{ "$": "auto_text_key", "code": 1095 , "label": "ч" },
{ "$": "auto_text_key", "code": 1089 , "label": "с" },
{ "$": "auto_text_key", "code": 1084 , "label": "м" },
{ "$": "auto_text_key", "code": 1080 , "label": "и" },
{ "$": "auto_text_key", "code": 1090 , "label": "т" },
{ "$": "auto_text_key", "code": 1100 , "label": "ь" },
{ "$": "auto_text_key", "code": 1073 , "label": "б" },
{ "$": "auto_text_key", "code": 1102 , "label": "ю" }
]
]

View File

@@ -0,0 +1,48 @@
[
[
{ "$": "auto_text_key", "code": 1081, "label": "ё" },
{ "$": "auto_text_key", "code": 1245, "label": "ӝ" },
{ "$": "auto_text_key", "code": 1255, "label": "ӧ" },
{ "$": "auto_text_key", "code": 1269, "label": "ӵ" },
{ "$": "auto_text_key", "code": 1253, "label": "ӥ" },
{ "$": "auto_text_key", "code": 1247, "label": "ӟ" },
{ "$": "auto_text_key", "code": 1100, "label": "ь" },
{ "$": "auto_text_key", "code": 1098, "label": "ъ" },
{ "$": "auto_text_key", "code": 1093, "label": "х" },
{ "$": "auto_text_key", "code": 1101, "label": "э" }
],
[
{ "$": "auto_text_key", "code": 1081, "label": "й" },
{ "$": "auto_text_key", "code": 1094, "label": "ц" },
{ "$": "auto_text_key", "code": 1091, "label": "у" },
{ "$": "auto_text_key", "code": 1082, "label": "к" },
{ "$": "auto_text_key", "code": 1077, "label": "е" },
{ "$": "auto_text_key", "code": 1085, "label": "н" },
{ "$": "auto_text_key", "code": 1075, "label": "г" },
{ "$": "auto_text_key", "code": 1096, "label": "ш" },
{ "$": "auto_text_key", "code": 1097, "label": "щ" },
{ "$": "auto_text_key", "code": 1079, "label": "з" }
],
[
{ "$": "auto_text_key", "code": 1092 , "label": "ф" },
{ "$": "auto_text_key", "code": 1099 , "label": "ы" },
{ "$": "auto_text_key", "code": 1074 , "label": "в" },
{ "$": "auto_text_key", "code": 1072 , "label": "а" },
{ "$": "auto_text_key", "code": 1087 , "label": "п" },
{ "$": "auto_text_key", "code": 1088 , "label": "р" },
{ "$": "auto_text_key", "code": 1086 , "label": "о" },
{ "$": "auto_text_key", "code": 1083 , "label": "л" },
{ "$": "auto_text_key", "code": 1076 , "label": "д" },
{ "$": "auto_text_key", "code": 1078 , "label": "ж" }
],
[
{ "$": "auto_text_key", "code": 1103 , "label": "я" },
{ "$": "auto_text_key", "code": 1095 , "label": "ч" },
{ "$": "auto_text_key", "code": 1089 , "label": "с" },
{ "$": "auto_text_key", "code": 1084 , "label": "м" },
{ "$": "auto_text_key", "code": 1080 , "label": "и" },
{ "$": "auto_text_key", "code": 1090 , "label": "т" },
{ "$": "auto_text_key", "code": 1073 , "label": "б" },
{ "$": "auto_text_key", "code": 1102 , "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": 1092, "label": "ф", "groupId": 1 },
{ "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": 1078, "label": "ж", "groupId": 2 },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -8,15 +8,13 @@
},
"half": { "code": 64, "label": "@" }
},
{ "code": 12306, "label": "〒", "popup": {
"main": { "code": 12320, "label": "〠" }
}
},
{
"$": "char_width_selector",
"full": { "code": 65283, "label": "", "popup": {
"main": { "code": 35, "label": "#" },
"relevant": [
{ "code": 12306, "label": "〒"},
{ "code": 12320, "label": "〠" },
{ "code": 8470, "label": "№" }
]
}
@@ -24,6 +22,8 @@
"half": { "code": 35, "label": "#", "popup": {
"main": { "code": 65283, "label": "" },
"relevant": [
{ "code": 12306, "label": "〒"},
{ "code": 12320, "label": "〠" },
{ "code": 8470, "label": "№" }
]
}
@@ -99,14 +99,18 @@
"full": { "code": 65291, "label": "", "popup": {
"main": { "code": 43, "label": "+" },
"relevant": [
{ "code": 177, "label": "±" }
{ "code": 177, "label": "±" },
{ "code": 61, "label": "=" },
{ "code": 65309, "label": "" }
]
}
},
"half": { "code": 43, "label": "+", "popup": {
"main": { "code": 65291, "label": "" },
"relevant": [
{ "code": 177, "label": "±" }
{ "code": 177, "label": "±" },
{ "code": 61, "label": "=" },
{ "code": 65309, "label": "" }
]
}
}
@@ -117,16 +121,20 @@
"main": { "code": 12302, "label": "『" },
"relevant": [
{ "code": 12304, "label": "【" },
{ "code": 12310, "label": "〖" }
{ "code": 12310, "label": "〖" },
{ "code": 65288, "label": "" },
{ "code": 65339, "label": "" }
]
}
},
"half": { "code": 65378, "label": "「", "popup": {
"main": { "code": 12301, "label": "" },
"main": { "code": 12300, "label": "" },
"relevant": [
{ "code": 12303, "label": "" },
{ "code": 12302, "label": "" },
{ "code": 12304, "label": "【" },
{ "code": 12310, "label": "〖" }
{ "code": 12310, "label": "〖" },
{ "code": 40, "label": "(" },
{ "code": 91, "label": "[" }
]
}
}
@@ -137,7 +145,9 @@
"main": { "code": 12303, "label": "』" },
"relevant": [
{ "code": 12305, "label": "】" },
{ "code": 12311, "label": "〗" }
{ "code": 12311, "label": "〗" },
{ "code": 65289, "label": "" },
{ "code": 65341, "label": "" }
]
}
},
@@ -146,7 +156,9 @@
"relevant": [
{ "code": 12303, "label": "』" },
{ "code": 12305, "label": "】" },
{ "code": 12311, "label": "〗" }
{ "code": 12311, "label": "〗" },
{ "code": 41, "label": ")" },
{ "code": 93, "label": "]" }
]
}
}

View File

@@ -64,7 +64,7 @@
{ "code": 11816, "label": "⸨" },
{ "code": 10214, "label": "⟦" },
{ "code": 10216, "label": "⟨" },
{ "code": 10218, "label": "" },
{ "code": 10218, "label": "" },
{ "code": 123, "label": "{" }
]
} },
@@ -72,7 +72,7 @@
"relevant": [
{ "code": 41, "label": ")" },
{ "code": 11817, "label": "⸩" },
{ "code": 10215, "label": "" },
{ "code": 10215, "label": "" },
{ "code": 10217, "label": "⟩" },
{ "code": 10219, "label": "⟫" },
{ "code": 125, "label": "}" }

View File

@@ -54,7 +54,9 @@
]
} }
},
{ "code": 8730, "label": "√" },
{ "code": 8730, "label": "√", "popup": {
"main": { "code": 10003, "label": "✓" }
} },
{ "code": 960, "label": "π", "popup": {
"main": { "code": 928, "label": "Π" },
"relevant": [
@@ -104,7 +106,7 @@
{ "code": 61, "label": "=", "popup": {
"main": { "code": 8800, "label": "≠" },
"relevant": [
{ "code": 61, "label": "=" },
{ "code": 65309, "label": "" },
{ "code": 8734, "label": "∞" },
{ "code": 8776, "label": "≈" }
]

View File

@@ -6,15 +6,28 @@
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 12296, "label": "〈", "popup": {
"main": { "code": 12298, "label": "《" },
"relevant": [
{ "code": 8804, "label": "≤" },
{ "code": 8249, "label": "" },
{ "code": 10216, "label": "" },
{ "code": 65308, "label": "" }
]
} },
{ "$": "char_width_selector",
"full": { "code": 12296, "label": "〈", "popup": {
"main": { "code": 12298, "label": "《" },
"relevant": [
{ "code": 60, "label": "<" },
{ "code": 8804, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 10216, "label": "⟨" },
{ "code": 65308, "label": "" }
]
} },
"half": { "code": 60, "label": "<", "popup": {
"main": { "code": 12298, "label": "《" },
"relevant": [
{ "code": 12296, "label": "〈" },
{ "code": 8804, "label": "≤" },
{ "code": 8249, "label": "" },
{ "code": 10216, "label": "⟨" },
{ "code": 65308, "label": "" }
]
} }
},
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 12288, "label": "空白" },
{ "code": -9701, "label": "char_width_switcher", "type": "system_gui", "popup": {
@@ -24,15 +37,28 @@
]
}
},
{ "code": 12297, "label": "〉", "popup": {
"main": { "code": 12299, "label": "》" },
"relevant": [
{ "code": 62, "label": ">" },
{ "code": 8805, "label": "" },
{ "code": 10217, "label": "" },
{ "code": 65310, "label": "" }
]
} },
{ "$": "char_width_selector",
"full": { "code": 12297, "label": "〉", "popup": {
"main": { "code": 12299, "label": "》" },
"relevant": [
{ "code": 62, "label": ">" },
{ "code": 8805, "label": "" },
{ "code": 8250, "label": "" },
{ "code": 10217, "label": "⟩" },
{ "code": 65310, "label": "" }
]
} },
"half": { "code": 62, "label": ">", "popup": {
"main": { "code": 12299, "label": "》" },
"relevant": [
{ "code": 12297, "label": "〉" },
{ "code": 8805, "label": "≥" },
{ "code": 8250, "label": "" },
{ "code": 10217, "label": "⟩" },
{ "code": 65310, "label": "" }
]
} }
},
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -8,11 +8,17 @@
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "$": "char_width_selector",
"full": { "code": 12289, "label": "、", "popup": {
"main": { "code": 65292, "label": "" }
"main": { "code": 65292, "label": "" },
"relevant": [
{ "code": 44, "label": "," }
]
}
},
"half": { "code": 65380, "label": "、", "popup": {
"main": { "code": 44, "label": "," }
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 65292, "label": "" }
]
}
}
},

View File

@@ -31,6 +31,7 @@
{ "id": "bg", "authors": [ "iorvethe" ] },
{ "id": "bn-BD", "authors": [ "iamrasel" ] },
{ "id": "ca", "authors": [ "mikelloc" ] },
{ "id": "cjk", "authors": [ "moonbeamcelery" ] },
{ "id": "ckb", "authors": [ "GoRaN" ] },
{ "id": "cs", "authors": [ "stefan-misik" ] },
{ "id": "da", "authors": [ "patrickgold" ] },
@@ -47,6 +48,7 @@
{ "id": "fi", "authors": [ "patrickgold" ] },
{ "id": "fo", "authors": [ "BinFlush" ] },
{ "id": "fr", "authors": [ "patrickgold" ] },
{ "id": "hi-IN", "authors": [ "npnpatidar" ] },
{ "id": "hr", "authors": [ "hedidnothingwrong" ] },
{ "id": "hu", "authors": [ "zoli111, gabik65" ] },
{ "id": "hy", "authors": [ "PJTSearch" ] },
@@ -72,6 +74,7 @@
{ "id": "sr", "authors": [ "hedidnothingwrong", "GrbavaCigla" ] },
{ "id": "sv", "authors": [ "patrickgold" ] },
{ "id": "tr", "authors": [ "kisekinopureya", "patrickgold", "dvrnynr" ] },
{ "id": "udm", "authors": [ "vorgoron" ] },
{ "id": "uk", "authors": [ "williamtheaker", "33kk", "honsiorovskyi" ] },
{ "id": "uk-cyr-ext", "authors": [ "williamtheaker", "33kk", "honsiorovskyi" ] },
{ "id": "ur-PK", "authors": [ "mubashir-rehman", "mirfatif" ] },
@@ -649,6 +652,15 @@
"numericRow": "org.florisboard.layouts:warang_citi"
}
},
{
"languageTag": "udm",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:russian_ruble",
"popupMapping": "org.florisboard.localization:udm",
"preferred": {
"characters": "org.florisboard.layouts:udmurt_extended"
}
},
{
"languageTag": "ur-PK",
"composer": "org.florisboard.composers:appender",
@@ -666,7 +678,7 @@
"suggestion": "org.florisboard.nlp.providers.han.shape"
},
"currencySet": "org.florisboard.currencysets:yen",
"popupMapping": "org.florisboard.localization:en",
"popupMapping": "org.florisboard.localization:cjk",
"preferred": {
"characters": "org.florisboard.layouts:qwerty",
"symbols": "org.florisboard.layouts:cjk",
@@ -684,6 +696,16 @@
"numericAdvanced": "org.florisboard.layouts:bengali"
}
},
{
"languageTag": "hi-IN",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:indian_rupee",
"popupMapping": "org.florisboard.localization:hi-IN",
"preferred": {
"characters": "org.florisboard.layouts:hindi_in",
"numericRow": "org.florisboard.layouts:devanagari"
}
},
{
"languageTag": "ast",
"composer": "org.florisboard.composers:appender",

View File

@@ -30,102 +30,97 @@
"ড়": {
"main": { "$": "auto_text_key", "code": 2525, "label": "ঢ়" }
},
"ৃ": {
"main": { "$": "auto_text_key", "code": 2443, "label": "ঋ" },
"relevant": [
{ "$": "auto_text_key", "code": 2500, "label": "ৄ" },
{ "$": "auto_text_key", "code": 2528, "label": "ৠ" },
{ "$": "auto_text_key", "code": 2529, "label": "ৡ" },
{ "$": "auto_text_key", "code": 2530, "label": "ৢ" },
{ "$": "auto_text_key", "code": 2531, "label": "ৣ" }
]
"ৃ": {
"main": { "$": "auto_text_key", "code": 2443, "label": "ঋ" }
},
"ু": {
"ু": {
"main": { "$": "auto_text_key", "code": 2441, "label": "উ" }
},
"ি": {
"ি": {
"main": { "$": "auto_text_key", "code": 2439, "label": "ই" }
},
"া": {
"া": {
"main": { "$": "auto_text_key", "code": 2438, "label": "আ" },
"relevant": [
"relevant": [
{ "$": "auto_text_key", "code": 2437, "label": "অ" }
]
},
"্": {
"্": {
"main": { "$": "auto_text_key", "code": 2433, "label": "ঁ" }
},
"ব": {
"ব": {
"main": { "$": "auto_text_key", "code": 2477, "label": "ভ" }
},
"ক": {
"ক": {
"main": { "$": "auto_text_key", "code": 2454, "label": "খ" }
},
"ত": {
"ত": {
"main": { "$": "auto_text_key", "code": 2469, "label": "থ" },
"relevant": [
"relevant": [
{ "$": "auto_text_key", "code": 2510, "label": "ৎ" }
]
},
"দ": {
"দ": {
"main": { "$": "auto_text_key", "code": 2471, "label": "ধ" }
},
"ো": {
"ো": {
"main": { "$": "auto_text_key", "code": 2451, "label": "ও" }
},
"ে": {
"ে": {
"main": { "$": "auto_text_key", "code": 2447, "label": "এ" }
},
"র": {
"র": {
"main": { "$": "auto_text_key", "code": 2482, "label": "ল" },
"relevant": [
{ "code": -255, "label": "র্য" }
"relevant": [
{ "code": -255, "label": "র্য" }
]
},
"ন": {
"ন": {
"main": { "$": "auto_text_key", "code": 2467, "label": "ণ" }
},
"স": {
"স": {
"main": { "$": "auto_text_key", "code": 2487, "label": "ষ" }
},
"ম": {
"ম": {
"main": { "$": "auto_text_key", "code": 2486, "label": "শ" }
},
"ূ": {
"ূ": {
"main": { "$": "auto_text_key", "code": 2442, "label": "ঊ" }
},
"ী": {
"ী": {
"main": { "$": "auto_text_key", "code": 2440, "label": "ঈ" }
},
"ঁ": {
"ঁ": {
"main": { "$": "auto_text_key", "code": 2492, "label": "়" },
"relevant": [
{ "$": "auto_text_key", "code": 2493, "label": "" },
{ "$": "auto_text_key", "code": 2544, "label": "" },
{ "$": "auto_text_key", "code": 2545, "label": "" },
{ "$": "auto_text_key", "code": 2492, "label": "" },
{ "$": "auto_text_key", "code": 2554, "label": "" },
{ "$": "auto_text_key", "code": 2519, "label": "" }
{ "$": "auto_text_key", "code": 2500, "label": "" },
{ "$": "auto_text_key", "code": 2530, "label": "" },
{ "$": "auto_text_key", "code": 2531, "label": "" },
{ "$": "auto_text_key", "code": 2528, "label": "" },
{ "$": "auto_text_key", "code": 2444, "label": "" },
{ "$": "auto_text_key", "code": 2529, "label": "" },
{ "$": "auto_text_key", "code": 2544, "label": "ৰ" },
{ "$": "auto_text_key", "code": 2545, "label": "ৱ" },
{ "$": "auto_text_key", "code": 2493, "label": "ঽ" },
{ "$": "auto_text_key", "code": 2554, "label": "৺" },
{ "$": "auto_text_key", "code": 2519, "label": "ৗ" }
]
},
"ৌ": {
"ৌ": {
"main": { "$": "auto_text_key", "code": 2452, "label": "ঔ" }
},
"ৈ": {
"ৈ": {
"main": { "$": "auto_text_key", "code": 2448, "label": "ঐ" }
},
"~right": {
"main": { "code": 2404, "label": "।" },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 2435, "label": "" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
@@ -134,9 +129,13 @@
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 64, "label": "@" },
{ "code": 35, "label": "#" },
{ "code": 8205, "label": ">⁞<" },
{ "code": 8204, "label": "<⁞>" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
{ "code": 63, "label": "?" },
{ "code": 2405, "label": "॥" }
]
}
},

View File

@@ -0,0 +1,117 @@
{
"all": {
"a": {
"relevant": [
{ "$": "auto_text_key", "code": 257, "label": "ā" },
{ "$": "auto_text_key", "code": 225, "label": "á" },
{ "$": "auto_text_key", "code": 462, "label": "ǎ" },
{ "$": "auto_text_key", "code": 224, "label": "à" },
{ "$": "auto_text_key", "code": 230, "label": "æ" },
{ "$": "auto_text_key", "code": 227, "label": "ã" },
{ "$": "auto_text_key", "code": 229, "label": "å" },
{ "$": "auto_text_key", "code": 226, "label": "â" },
{ "$": "auto_text_key", "code": 228, "label": "ä" }
]
},
"c": {
"relevant": [
{ "$": "auto_text_key", "code": 231, "label": "ç" }
]
},
"e": {
"relevant": [
{ "$": "auto_text_key", "code": 275, "label": "ē" },
{ "$": "auto_text_key", "code": 233, "label": "é" },
{ "$": "auto_text_key", "code": 283, "label": "ě" },
{ "$": "auto_text_key", "code": 232, "label": "è" },
{ "$": "auto_text_key", "code": 234, "label": "ê" },
{ "$": "auto_text_key", "code": 235, "label": "ë" }
]
},
"i": {
"relevant": [
{ "$": "auto_text_key", "code": 299, "label": "ī" },
{ "$": "auto_text_key", "code": 237, "label": "í" },
{ "$": "auto_text_key", "code": 464, "label": "ǐ" },
{ "$": "auto_text_key", "code": 236, "label": "ì" },
{ "$": "auto_text_key", "code": 239, "label": "ï" },
{ "$": "auto_text_key", "code": 238, "label": "î" }
]
},
"n": {
"relevant": [
{ "$": "auto_text_key", "code": 241, "label": "ñ" },
{ "$": "auto_text_key", "code": 324, "label": "ń" }
]
},
"o": {
"relevant": [
{ "$": "auto_text_key", "code": 333, "label": "ō" },
{ "$": "auto_text_key", "code": 243, "label": "ó" },
{ "$": "auto_text_key", "code": 466, "label": "ǒ" },
{ "$": "auto_text_key", "code": 242, "label": "ò" },
{ "$": "auto_text_key", "code": 245, "label": "õ" },
{ "$": "auto_text_key", "code": 339, "label": "œ" },
{ "$": "auto_text_key", "code": 248, "label": "ø" },
{ "$": "auto_text_key", "code": 246, "label": "ö" },
{ "$": "auto_text_key", "code": 244, "label": "ô" }
]
},
"s": {
"relevant": [
{ "$": "auto_text_key", "code": 223, "label": "ß" }
]
},
"u": {
"relevant": [
{ "$": "auto_text_key", "code": 363, "label": "ū" },
{ "$": "auto_text_key", "code": 250, "label": "ú" },
{ "$": "auto_text_key", "code": 468, "label": "ǔ" },
{ "$": "auto_text_key", "code": 249, "label": "ù" },
{ "$": "auto_text_key", "code": 252, "label": "ü" },
{ "$": "auto_text_key", "code": 470, "label": "ǖ" },
{ "$": "auto_text_key", "code": 472, "label": "ǘ" },
{ "$": "auto_text_key", "code": 474, "label": "ǚ" },
{ "$": "auto_text_key", "code": 476, "label": "ǜ" },
{ "$": "auto_text_key", "code": 251, "label": "û" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}

View File

@@ -0,0 +1,173 @@
{
"all": {
"क": {
"main": { "$": "auto_text_key", "code": 2392, "label": "क़" },
"relevant": [{ "$": "auto_text_key", "code": 2393, "label": "ख़" }]
},
"ग": {
"main": { "$": "auto_text_key", "code": 2394, "label": "ग़" },
"relevant": [{ "$": "auto_text_key", "code": 2427, "label": "ॻ" }]
},
"च": {
"relevant": [
{ "$": "auto_text_key", "code": 2385, "label": " ॑" },
{ "$": "auto_text_key", "code": 2386, "label": " ॒" }
]
},
"ज": {
"main": { "$": "auto_text_key", "code": 2395, "label": "ज़" },
"relevant": [
{ "$": "auto_text_key", "code": 2428, "label": "ॼ" },
{ "$": "auto_text_key", "code": 2425, "label": "ॹ" }
]
},
"ड": {
"main": { "$": "auto_text_key", "code": 2396, "label": "ड़" },
"relevant": [{ "$": "auto_text_key", "code": 2397, "label": "ढ़" }]
},
"त": {
"main": { "$": "multi_text_key", "codePoints": [2340, 2381, 2352], "label": "त्र" }
},
"द": {
"main": { "$": "auto_text_key", "code": 2396, "label": "ड़" },
"relevant": [
{ "$": "auto_text_key", "code": 2430, "label": "ॾ" },
{ "$": "auto_text_key", "code": 2397, "label": "ढ़" },
{ "$": "auto_text_key", "code": 2424, "label": "ॸ" }
]
},
"न": {
"main": { "$": "auto_text_key", "code": 2329, "label": "ङ" },
"relevant": [
{ "$": "auto_text_key", "code": 2345, "label": "ऩ" },
{ "$": "auto_text_key", "code": 2334, "label": "ञ" }
]
},
"फ": {
"main": { "$": "auto_text_key", "code": 2398, "label": "फ़" }
},
"ब": {
"relevant": [
{ "$": "auto_text_key", "code": 2431, "label": "ॿ" },
{ "$": "auto_text_key", "code": 2365, "label": "ऽ" },
{ "$": "auto_text_key", "code": 2416, "label": "॰" }
]
},
"म": {
"main": { "$": "auto_text_key", "code": 2305, "label": "ँ" },
"relevant": [{ "$": "auto_text_key", "code": 2304, "label": "ऀ" }]
},
"य": {
"main": { "$": "auto_text_key", "code": 2426, "label": "ॺ" }
},
"र": {
"main": { "$": "auto_text_key", "code": 2315, "label": "ऋ" },
"relevant": [
{ "$": "auto_text_key", "code": 2400, "label": "ॠ" },
{ "$": "auto_text_key", "code": 2372, "label": "ॄ" },
{ "$": "auto_text_key", "code": 2353, "label": "ऱ" }
]
},
"ल": {
"relevant": [
{ "$": "auto_text_key", "code": 2402, "label": "ॢ" },
{ "$": "auto_text_key", "code": 2403, "label": "ॣ" },
{ "$": "auto_text_key", "code": 2316, "label": "ऌ" },
{ "$": "auto_text_key", "code": 2401, "label": "ॡ" },
{ "$": "auto_text_key", "code": 2356, "label": "ऴ" }
]
},
"व": {
"relevant": [
{ "$": "auto_text_key", "code": 2387, "label": " ॓" },
{ "$": "auto_text_key", "code": 2388, "label": " ॔" }
]
},
"स": {
"main": { "$": "multi_text_key", "codePoints": [2358, 2381, 2352], "label": "श्र" },
"relevant": [{ "$": "auto_text_key", "code": 2359, "label": "ष" }]
},
"ा": {
"main": { "$": "auto_text_key", "code": 2310, "label": "आ" },
"relevant": [
{ "$": "auto_text_key", "code": 2373, "label": "ॅ" },
{ "$": "auto_text_key", "code": 2418, "label": "ॲ" },
{ "$": "auto_text_key", "code": 2308, "label": "ऄ" }
]
},
"ि": {
"main": { "$": "auto_text_key", "code": 2311, "label": "इ" },
"relevant": [{ "$": "auto_text_key", "code": 2312, "label": "ई" }]
},
"ु": {
"main": { "$": "auto_text_key", "code": 2313, "label": "उ" },
"relevant": [
{ "$": "auto_text_key", "code": 2422, "label": "ॶ" },
{ "$": "auto_text_key", "code": 2423, "label": "ॷ" },
{ "$": "auto_text_key", "code": 2390, "label": " ॖ" },
{ "$": "auto_text_key", "code": 2314, "label": "ऊ" },
{ "$": "auto_text_key", "code": 2391, "label": " ॗ" }
]
},
"े": {
"main": { "$": "auto_text_key", "code": 2319, "label": "ए" },
"relevant": [
{ "$": "auto_text_key", "code": 2317, "label": "ऍ" },
{ "$": "auto_text_key", "code": 2318, "label": "ऎ" },
{ "$": "auto_text_key", "code": 2374, "label": " ॆ" },
{ "$": "auto_text_key", "code": 2320, "label": "ऐ" },
{ "$": "auto_text_key", "code": 2382, "label": " ॎ" },
{ "$": "auto_text_key", "code": 2389, "label": " ॕ" }
]
},
"ो": {
"main": { "$": "auto_text_key", "code": 2323, "label": "ओ" },
"relevant": [
{ "$": "auto_text_key", "code": 2322, "label": "ऒ" },
{ "$": "auto_text_key", "code": 2378, "label": " ॊ" },
{ "$": "auto_text_key", "code": 2383, "label": " ॏ" },
{ "$": "auto_text_key", "code": 2421, "label": "ॵ" },
{ "$": "auto_text_key", "code": 2384, "label": "ॐ" },
{ "$": "auto_text_key", "code": 2377, "label": "ॉ" },
{ "$": "auto_text_key", "code": 2419, "label": "ॳ" },
{ "$": "auto_text_key", "code": 2420, "label": "ॴ" },
{ "$": "auto_text_key", "code": 2362, "label": " ऺ" },
{ "$": "auto_text_key", "code": 2363, "label": " ऻ" },
{ "$": "auto_text_key", "code": 2324, "label": "औ" },
{ "$": "auto_text_key", "code": 2321, "label": "ऑ" }
]
},
"~right": {
"main": { "code": 2404, "label": "।" },
"relevant": [
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "$": "layout_direction_selector", "ltr": { "code": 40, "label": "(" }, "rtl": { "code": 41, "label": "(" } },
{ "$": "layout_direction_selector", "ltr": { "code": 41, "label": ")" }, "rtl": { "code": 40, "label": ")" } },
{ "code": 8205, "label": ">⁞<" },
{ "code": 8204, "label": "<⁞>" },
{ "code": 2417, "label": "ॱ" },
{ "code": 2429, "label": "" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" },
{ "code": 2405, "label": "॥" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" },
{ "code": -255, "label": ".in" },
{ "code": -255, "label": ".co.in" }
]
}
}
}

View File

@@ -0,0 +1,46 @@
{
"all": {
"ե": {
"main": { "$": "auto_text_key", "code": 1415, "label": "և" }
},
"յ": {
"main": { "$": "auto_text_key", "code": 1416, "label": "ֈ" }
},
"ա": {
"main": { "$": "auto_text_key", "code": 1376, "label": "ՠ" }
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 1417, "label": "։" },
{ "code": 1418, "label": "֊" },
{ "code": 1369, "label": "ՙ" },
{ "code": 1370, "label": "՚" },
{ "code": 1373, "label": "՝" },
{ "code": 1371, "label": "՛" },
{ "code": 1375, "label": "՟" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 46, "label": "." },
{ "code": 1372, "label": "՜" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" },
{ "code": 1374, "label": "՞" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".gr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}

View File

@@ -2,7 +2,7 @@
"all": {
"c": {
"relevant": [
{ "$": "auto_text_key", "code": 269, "label": "č" },
{ "$": "auto_text_key", "code": 269, "label": "č" }
]
},
"s": {

View File

@@ -0,0 +1,71 @@
{
"all": {
"о": {
"relevant": [
{ "$": "auto_text_key", "code": 1255, "label": "ӧ" }
]
},
"з": {
"relevant": [
{ "$": "auto_text_key", "code": 1247, "label": "ӟ" }
]
},
"ж": {
"relevant": [
{ "$": "auto_text_key", "code": 1245, "label": "ӝ" }
]
},
"ч": {
"relevant": [
{ "$": "auto_text_key", "code": 1269, "label": "ӵ" }
]
},
"и": {
"relevant": [
{ "$": "auto_text_key", "code": 1253, "label": "ӥ" }
]
},
"е": {
"relevant": [
{ "$": "auto_text_key", "code": 1105, "label": "ё" }
]
},
"ь": {
"relevant": [
{ "$": "auto_text_key", "code": 1098, "label": "ъ" }
]
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".ru" },
{ "code": -255, "label": ".udm.ru" }
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,6 @@
# Auto-generated by emojicon.py using CLDR v45
# DO NOT EDIT MANUALLY!
[smileys_emotion]
😀;;
😃;;
@@ -48,6 +51,9 @@
😬;;
😮‍💨;;
🤥;;
🫨;;
🙂‍↔️;;
🙂‍↕️;;
😌;;
😔;;
😪;;
@@ -125,7 +131,6 @@
🙈;;
🙉;;
🙊;;
💋;;
💌;;
💘;;
💝;;
@@ -140,14 +145,18 @@
❤️‍🔥;;
❤️‍🩹;;
❤️;;
🩷;;
🧡;;
💛;;
💚;;
💙;;
🩵;;
💜;;
🤎;;
🖤;;
🩶;;
🤍;;
💋;;
💯;;
💢;;
💥;;
@@ -155,7 +164,6 @@
💦;;
💨;;
🕳️;;
💣;;
💬;;
👁️‍🗨️;;
🗨️;;
@@ -218,6 +226,18 @@
🫴🏽;;
🫴🏾;;
🫴🏿;;
🫷;;
🫷🏻;;
🫷🏼;;
🫷🏽;;
🫷🏾;;
🫷🏿;;
🫸;;
🫸🏻;;
🫸🏼;;
🫸🏽;;
🫸🏾;;
🫸🏿;;
👌;;
👌🏻;;
👌🏼;;
@@ -1529,6 +1549,24 @@
🚶🏽‍♀️;;
🚶🏾‍♀️;;
🚶🏿‍♀️;;
🚶‍➡️;;
🚶🏻‍➡️;;
🚶🏼‍➡️;;
🚶🏽‍➡️;;
🚶🏾‍➡️;;
🚶🏿‍➡️;;
🚶‍♀️‍➡️;;
🚶🏻‍♀️‍➡️;;
🚶🏼‍♀️‍➡️;;
🚶🏽‍♀️‍➡️;;
🚶🏾‍♀️‍➡️;;
🚶🏿‍♀️‍➡️;;
🚶‍♂️‍➡️;;
🚶🏻‍♂️‍➡️;;
🚶🏼‍♂️‍➡️;;
🚶🏽‍♂️‍➡️;;
🚶🏾‍♂️‍➡️;;
🚶🏿‍♂️‍➡️;;
🧍;;
🧍🏻;;
🧍🏼;;
@@ -1565,60 +1603,132 @@
🧎🏽‍♀️;;
🧎🏾‍♀️;;
🧎🏿‍♀️;;
🧎‍➡️;;
🧎🏻‍➡️;;
🧎🏼‍➡️;;
🧎🏽‍➡️;;
🧎🏾‍➡️;;
🧎🏿‍➡️;;
🧎‍♀️‍➡️;;
🧎🏻‍♀️‍➡️;;
🧎🏼‍♀️‍➡️;;
🧎🏽‍♀️‍➡️;;
🧎🏾‍♀️‍➡️;;
🧎🏿‍♀️‍➡️;;
🧎‍♂️‍➡️;;
🧎🏻‍♂️‍➡️;;
🧎🏼‍♂️‍➡️;;
🧎🏽‍♂️‍➡️;;
🧎🏾‍♂️‍➡️;;
🧎🏿‍♂️‍➡️;;
🧑‍🦯;;
🧑🏻‍🦯;;
🧑🏼‍🦯;;
🧑🏽‍🦯;;
🧑🏾‍🦯;;
🧑🏿‍🦯;;
🧑‍🦯‍➡️;;
🧑🏻‍🦯‍➡️;;
🧑🏼‍🦯‍➡️;;
🧑🏽‍🦯‍➡️;;
🧑🏾‍🦯‍➡️;;
🧑🏿‍🦯‍➡️;;
👨‍🦯;;
👨🏻‍🦯;;
👨🏼‍🦯;;
👨🏽‍🦯;;
👨🏾‍🦯;;
👨🏿‍🦯;;
👨‍🦯‍➡️;;
👨🏻‍🦯‍➡️;;
👨🏼‍🦯‍➡️;;
👨🏽‍🦯‍➡️;;
👨🏾‍🦯‍➡️;;
👨🏿‍🦯‍➡️;;
👩‍🦯;;
👩🏻‍🦯;;
👩🏼‍🦯;;
👩🏽‍🦯;;
👩🏾‍🦯;;
👩🏿‍🦯;;
👩‍🦯‍➡️;;
👩🏻‍🦯‍➡️;;
👩🏼‍🦯‍➡️;;
👩🏽‍🦯‍➡️;;
👩🏾‍🦯‍➡️;;
👩🏿‍🦯‍➡️;;
🧑‍🦼;;
🧑🏻‍🦼;;
🧑🏼‍🦼;;
🧑🏽‍🦼;;
🧑🏾‍🦼;;
🧑🏿‍🦼;;
🧑‍🦼‍➡️;;
🧑🏻‍🦼‍➡️;;
🧑🏼‍🦼‍➡️;;
🧑🏽‍🦼‍➡️;;
🧑🏾‍🦼‍➡️;;
🧑🏿‍🦼‍➡️;;
👨‍🦼;;
👨🏻‍🦼;;
👨🏼‍🦼;;
👨🏽‍🦼;;
👨🏾‍🦼;;
👨🏿‍🦼;;
👨‍🦼‍➡️;;
👨🏻‍🦼‍➡️;;
👨🏼‍🦼‍➡️;;
👨🏽‍🦼‍➡️;;
👨🏾‍🦼‍➡️;;
👨🏿‍🦼‍➡️;;
👩‍🦼;;
👩🏻‍🦼;;
👩🏼‍🦼;;
👩🏽‍🦼;;
👩🏾‍🦼;;
👩🏿‍🦼;;
👩‍🦼‍➡️;;
👩🏻‍🦼‍➡️;;
👩🏼‍🦼‍➡️;;
👩🏽‍🦼‍➡️;;
👩🏾‍🦼‍➡️;;
👩🏿‍🦼‍➡️;;
🧑‍🦽;;
🧑🏻‍🦽;;
🧑🏼‍🦽;;
🧑🏽‍🦽;;
🧑🏾‍🦽;;
🧑🏿‍🦽;;
🧑‍🦽‍➡️;;
🧑🏻‍🦽‍➡️;;
🧑🏼‍🦽‍➡️;;
🧑🏽‍🦽‍➡️;;
🧑🏾‍🦽‍➡️;;
🧑🏿‍🦽‍➡️;;
👨‍🦽;;
👨🏻‍🦽;;
👨🏼‍🦽;;
👨🏽‍🦽;;
👨🏾‍🦽;;
👨🏿‍🦽;;
👨‍🦽‍➡️;;
👨🏻‍🦽‍➡️;;
👨🏼‍🦽‍➡️;;
👨🏽‍🦽‍➡️;;
👨🏾‍🦽‍➡️;;
👨🏿‍🦽‍➡️;;
👩‍🦽;;
👩🏻‍🦽;;
👩🏼‍🦽;;
👩🏽‍🦽;;
👩🏾‍🦽;;
👩🏿‍🦽;;
👩‍🦽‍➡️;;
👩🏻‍🦽‍➡️;;
👩🏼‍🦽‍➡️;;
👩🏽‍🦽‍➡️;;
👩🏾‍🦽‍➡️;;
👩🏿‍🦽‍➡️;;
🏃;;
🏃🏻;;
🏃🏼;;
@@ -1637,6 +1747,24 @@
🏃🏽‍♀️;;
🏃🏾‍♀️;;
🏃🏿‍♀️;;
🏃‍➡️;;
🏃🏻‍➡️;;
🏃🏼‍➡️;;
🏃🏽‍➡️;;
🏃🏾‍➡️;;
🏃🏿‍➡️;;
🏃‍♀️‍➡️;;
🏃🏻‍♀️‍➡️;;
🏃🏼‍♀️‍➡️;;
🏃🏽‍♀️‍➡️;;
🏃🏾‍♀️‍➡️;;
🏃🏿‍♀️‍➡️;;
🏃‍♂️‍➡️;;
🏃🏻‍♂️‍➡️;;
🏃🏼‍♂️‍➡️;;
🏃🏽‍♂️‍➡️;;
🏃🏾‍♂️‍➡️;;
🏃🏿‍♂️‍➡️;;
💃;;
💃🏻;;
💃🏼;;
@@ -2269,7 +2397,6 @@
👩🏿‍❤️‍👩🏽;;
👩🏿‍❤️‍👩🏾;;
👩🏿‍❤️‍👩🏿;;
👪;;
👨‍👩‍👦;;
👨‍👩‍👧;;
👨‍👩‍👧‍👦;;
@@ -2299,6 +2426,11 @@
👤;;
👥;;
🫂;;
👪;;
🧑‍🧑‍🧒;;
🧑‍🧑‍🧒‍🧒;;
🧑‍🧒;;
🧑‍🧒‍🧒;;
👣;;
[animals_nature]
@@ -2322,6 +2454,8 @@
🐅;;
🐆;;
🐴;;
🫎;;
🫏;;
🐎;;
🦄;;
🦓;;
@@ -2384,6 +2518,10 @@
🦩;;
🦚;;
🦜;;
🪽;;
🐦‍⬛;;
🪿;;
🐦‍🔥;;
🐸;;
🐊;;
🐢;;
@@ -2404,6 +2542,7 @@
🐙;;
🐚;;
🪸;;
🪼;;
🐌;;
🦋;;
🐛;;
@@ -2431,6 +2570,7 @@
🌻;;
🌼;;
🌷;;
🪻;;
🌱;;
🪴;;
🌲;;
@@ -2446,6 +2586,7 @@
🍃;;
🪹;;
🪺;;
🍄;;
[food_drink]
🍇;;
@@ -2453,6 +2594,7 @@
🍉;;
🍊;;
🍋;;
🍋‍🟩;;
🍌;;
🍍;;
🥭;;
@@ -2479,10 +2621,12 @@
🥦;;
🧄;;
🧅;;
🍄;;
🥜;;
🫘;;
🌰;;
🫚;;
🫛;;
🍄‍🟫;;
🍞;;
🥐;;
🥖;;
@@ -2859,11 +3003,10 @@
🎯;;
🪀;;
🪁;;
🔫;;
🎱;;
🔮;;
🪄;;
🧿;;
🪬;;
🎮;;
🕹️;;
🎰;;
@@ -2910,6 +3053,7 @@
🩳;;
👙;;
👚;;
🪭;;
👛;;
👜;;
👝;;
@@ -2924,6 +3068,7 @@
👡;;
🩰;;
👢;;
🪮;;
👑;;
👒;;
🎩;;
@@ -2962,6 +3107,8 @@
🪕;;
🥁;;
🪘;;
🪇;;
🪈;;
📱;;
📲;;
☎️;;
@@ -3081,7 +3228,7 @@
🛠️;;
🗡️;;
⚔️;;
🔫;;
💣;;
🪃;;
🏹;;
🛡️;;
@@ -3094,6 +3241,7 @@
⚖️;;
🦯;;
🔗;;
⛓️‍💥;;
⛓️;;
🪝;;
🧰;;
@@ -3142,6 +3290,8 @@
⚰️;;
🪦;;
⚱️;;
🧿;;
🪬;;
🗿;;
🪧;;
🪪;;
@@ -3206,6 +3356,7 @@
☮️;;
🕎;;
🔯;;
🪯;;
♈;;
♉;;
♊;;
@@ -3241,6 +3392,7 @@
🔅;;
🔆;;
📶;;
🛜;;
📳;;
📴;;
♀️;;

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 Patrick Goldinger
Copyright 2020-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.

View File

@@ -1,29 +0,0 @@
cmake_minimum_required(VERSION 3.22)
project(florisboard)
set(CMAKE_CXX_STANDARD 20)
include_directories(.)
### FlorisBoard ###
add_subdirectory(utils)
add_subdirectory(nlp)
add_library(
florisboard-native
SHARED
dev_patrickgold_florisboard_FlorisApplication.cpp
)
target_compile_options(florisboard-native PRIVATE -ffunction-sections -fdata-sections -fexceptions)
target_link_libraries(
# Destination
florisboard-native
# Sources
android
log
fl::nlp::core
utils
)

View File

@@ -1,15 +0,0 @@
add_library(
# Name
utils
SHARED
# Headers
jni_utils.h
log.h
# Sources
jni_utils.cpp
log.cpp
)
target_link_libraries(utils PUBLIC log)

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* 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.
*/
#include "jni_utils.h"
#include "log.h"
std::string utils::j2std_string(JNIEnv *env, jobject jStr) {
auto cStr = reinterpret_cast<const char *>(env->GetDirectBufferAddress(jStr));
auto size = env->GetDirectBufferCapacity(jStr);
std::string stdStr(cStr, size);
utils::log(ANDROID_LOG_DEBUG, "spell j2s", stdStr);
return stdStr;
}
jobject utils::std2j_string(JNIEnv *env, const std::string& stdStr) {
utils::log(ANDROID_LOG_DEBUG, "spell s2j", stdStr);
size_t byteCount = stdStr.length();
auto cStr = stdStr.c_str();
auto buffer = env->NewDirectByteBuffer((void *) cStr, byteCount);
return buffer;
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* 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.
*/
#include <android/log.h>
#include <cerrno>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>
#include <unistd.h>
#include "log.h"
void utils::log(int log_priority, const std::string &tag, const std::string &msg) {
__android_log_print(log_priority, tag.c_str(), "%s", msg.c_str());
}
/**
* Code below based on:
* https://codelab.wordpress.com/2014/11/03/how-to-use-standard-output-streams-for-logging-in-android-apps/
*/
int utils::start_stdout_stderr_logger(const std::string &app_name) {
static bool already_started = false;
if (already_started)
return 0;
int piperw[2];
if (pipe(piperw) < 0) {
std::string msg = "pipe(): ";
msg += strerror(errno);
utils::log(ANDROID_LOG_ERROR, "stdout/stderr logger", std::ref(msg));
return 1;
}
/* make stdout line-buffered and stderr unbuffered */
setvbuf(stdout, nullptr, _IOLBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
/* create the pipe and redirect stdout and stderr */
dup2(piperw[0], STDIN_FILENO);
dup2(piperw[1], STDOUT_FILENO);
dup2(piperw[1], STDERR_FILENO);
close(piperw[0]);
close(piperw[1]);
auto f = [](const std::string &tag) {
std::string buf;
while (std::getline(std::cin, buf)) {
char &back = buf.back();
if (back == '\n')
back = '\0';
utils::log(ANDROID_LOG_DEBUG, tag, std::ref(buf));
}
};
/* spawn the logging thread */
std::thread thr(f, app_name);
thr.detach();
already_started = true;
return 0;
}

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,21 +33,18 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyboardManager
import dev.patrickgold.florisboard.ime.media.emoji.FlorisEmojiCompat
import dev.patrickgold.florisboard.ime.nlp.NlpManager
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.lib.NativeStr
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.crashutility.CrashUtility
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.devtools.flogInfo
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.io.AssetManager
import dev.patrickgold.florisboard.lib.io.deleteContentsRecursively
import dev.patrickgold.florisboard.lib.io.subFile
import dev.patrickgold.florisboard.lib.kotlin.tryOrNull
import dev.patrickgold.florisboard.lib.toNativeStr
import dev.patrickgold.jetpref.datastore.JetPref
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.tryOrNull
import org.florisboard.libnative.dummyAdd
import java.lang.ref.WeakReference
/**
@@ -59,22 +56,18 @@ private var FlorisApplicationReference = WeakReference<FlorisApplication?>(null)
@Suppress("unused")
class FlorisApplication : Application() {
companion object {
private const val ICU_DATA_ASSET_PATH = "icu4c/icudt73l.dat"
private external fun nativeInitICUData(path: NativeStr): Int
init {
try {
System.loadLibrary("florisboard-native")
System.loadLibrary("fl_native")
} catch (_: Exception) {
}
FlorisImeTheme.init()
}
}
private val prefs by florisPreferenceModel()
private val mainHandler by lazy { Handler(mainLooper) }
val assetManager = lazy { AssetManager(this) }
val cacheManager = lazy { CacheManager(this) }
val clipboardManager = lazy { ClipboardManager(this) }
val editorInstance = lazy { EditorInstance(this) }
@@ -99,6 +92,7 @@ class FlorisApplication : Application() {
)
CrashUtility.install(this)
FlorisEmojiCompat.init(this)
flogError { "dummy result: ${dummyAdd(3,4)}" }
if (!UserManagerCompat.isUserUnlocked(this)) {
cacheDir?.deleteContentsRecursively()
@@ -115,7 +109,6 @@ class FlorisApplication : Application() {
}
fun init() {
initICU(this)
cacheDir?.deleteContentsRecursively()
prefs.initializeBlocking(this)
extensionManager.value.init()
@@ -123,28 +116,6 @@ class FlorisApplication : Application() {
DictionaryManager.init(this)
}
fun initICU(context: Context): Boolean {
try {
val androidAssetManager = context.assets ?: return false
val icuTmpDataFile = context.cacheDir.subFile("icudt.dat")
icuTmpDataFile.outputStream().use { os ->
androidAssetManager.open(ICU_DATA_ASSET_PATH).use { it.copyTo(os) }
}
val status = nativeInitICUData(icuTmpDataFile.absolutePath.toNativeStr())
icuTmpDataFile.delete()
return if (status != 0) {
flogError { "Native ICU data initializing failed with error code $status!" }
false
} else {
flogInfo { "Successfully loaded ICU data!" }
true
}
} catch (e: Exception) {
flogError { e.toString() }
return false
}
}
private inner class BootComplete : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null) return
@@ -173,8 +144,6 @@ private tailrec fun Context.florisApplication(): FlorisApplication {
fun Context.appContext() = lazyOf(this.florisApplication())
fun Context.assetManager() = this.florisApplication().assetManager
fun Context.cacheManager() = this.florisApplication().cacheManager
fun Context.clipboardManager() = this.florisApplication().clipboardManager

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package dev.patrickgold.florisboard
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.inputmethodservice.ExtractEditText
import android.os.Build
@@ -46,8 +47,9 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material.ButtonDefaults
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
@@ -78,8 +80,8 @@ import dev.patrickgold.florisboard.app.devtools.DevtoolsOverlay
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.ClipboardInputLayout
import dev.patrickgold.florisboard.ime.sheet.BottomSheetHostUi
import dev.patrickgold.florisboard.ime.sheet.isBottomSheetShowing
import dev.patrickgold.florisboard.ime.core.SelectSubtypePanel
import dev.patrickgold.florisboard.ime.core.isSubtypeSelectionShowing
import dev.patrickgold.florisboard.ime.editor.EditorRange
import dev.patrickgold.florisboard.ime.editor.FlorisEditorInfo
import dev.patrickgold.florisboard.ime.input.InputFeedbackController
@@ -89,21 +91,18 @@ import dev.patrickgold.florisboard.ime.keyboard.ProvideKeyboardRowBaseHeight
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.lifecycle.LifecycleInputMethodService
import dev.patrickgold.florisboard.ime.media.MediaInputLayout
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.onehanded.OneHandedPanel
import dev.patrickgold.florisboard.ime.sheet.BottomSheetHostUi
import dev.patrickgold.florisboard.ime.sheet.isBottomSheetShowing
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsEditorPanel
import dev.patrickgold.florisboard.ime.text.TextInputLayout
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.lib.android.AndroidInternalR
import dev.patrickgold.florisboard.lib.android.AndroidVersion
import dev.patrickgold.florisboard.lib.android.isOrientationLandscape
import dev.patrickgold.florisboard.lib.android.isOrientationPortrait
import dev.patrickgold.florisboard.lib.android.launchActivity
import dev.patrickgold.florisboard.lib.android.setLocale
import dev.patrickgold.florisboard.lib.android.showShortToast
import dev.patrickgold.florisboard.lib.android.systemServiceOrNull
import dev.patrickgold.florisboard.ime.theme.WallpaperChangeReceiver
import dev.patrickgold.florisboard.lib.compose.FlorisButton
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
import dev.patrickgold.florisboard.lib.compose.SystemUiIme
@@ -111,18 +110,25 @@ import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.devtools.flogInfo
import dev.patrickgold.florisboard.lib.devtools.flogWarning
import dev.patrickgold.florisboard.lib.kotlin.collectLatestIn
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import dev.patrickgold.florisboard.lib.snygg.ui.SnyggSurface
import dev.patrickgold.florisboard.lib.snygg.ui.shape
import dev.patrickgold.florisboard.lib.snygg.ui.snyggBackground
import dev.patrickgold.florisboard.lib.snygg.ui.snyggBorder
import dev.patrickgold.florisboard.lib.snygg.ui.snyggShadow
import dev.patrickgold.florisboard.lib.snygg.ui.solidColor
import dev.patrickgold.florisboard.lib.snygg.ui.spSize
import dev.patrickgold.florisboard.lib.util.ViewUtils
import dev.patrickgold.florisboard.lib.util.debugSummarize
import dev.patrickgold.florisboard.lib.util.launchActivity
import dev.patrickgold.jetpref.datastore.model.observeAsState
import 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.systemServiceOrNull
import org.florisboard.lib.kotlin.collectLatestIn
import org.florisboard.lib.snygg.ui.SnyggSurface
import org.florisboard.lib.snygg.ui.shape
import org.florisboard.lib.snygg.ui.snyggBackground
import org.florisboard.lib.snygg.ui.snyggBorder
import org.florisboard.lib.snygg.ui.snyggShadow
import org.florisboard.lib.snygg.ui.solidColor
import org.florisboard.lib.snygg.ui.spSize
import java.lang.ref.WeakReference
/**
@@ -230,10 +236,10 @@ class FlorisImeService : LifecycleInputMethodService() {
val imm = ims.systemServiceOrNull(InputMethodManager::class) ?: return false
val list: List<InputMethodInfo> = imm.enabledInputMethodList
for (el in list) {
for (i in 0 until el.subtypeCount){
for (i in 0 until el.subtypeCount) {
if (el.getSubtypeAt(i).mode != "voice") continue
if (AndroidVersion.ATLEAST_API28_P) {
ims.switchInputMethod(el.id)
ims.switchInputMethod(el.id, el.getSubtypeAt(i))
return true
} else {
ims.window.window?.let { window ->
@@ -265,6 +271,8 @@ class FlorisImeService : LifecycleInputMethodService() {
private var isExtractUiShown by mutableStateOf(false)
private var resourcesContext by mutableStateOf(this as Context)
private val wallpaperChangeReceiver = WallpaperChangeReceiver()
init {
setTheme(R.style.FlorisImeTheme)
}
@@ -272,11 +280,14 @@ class FlorisImeService : LifecycleInputMethodService() {
override fun onCreate() {
super.onCreate()
FlorisImeServiceReference = WeakReference(this)
WindowCompat.setDecorFitsSystemWindows(window.window!!, false)
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
val config = Configuration(resources.configuration)
config.setLocale(subtype.primaryLocale)
config.setLocale(subtype.primaryLocale.base)
resourcesContext = createConfigurationContext(config)
}
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
registerReceiver(wallpaperChangeReceiver, IntentFilter(Intent.ACTION_WALLPAPER_CHANGED))
}
override fun onCreateInputView(): View {
@@ -315,6 +326,7 @@ class FlorisImeService : LifecycleInputMethodService() {
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(wallpaperChangeReceiver)
FlorisImeServiceReference = WeakReference(null)
inputWindowView = null
}
@@ -369,7 +381,7 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo { "(no args)" }
super.onFinishInput()
editorInstance.handleFinishInput()
nlpManager.clearInlineSuggestions()
NlpInlineAutofill.clearInlineSuggestions()
}
override fun onWindowShown() {
@@ -401,6 +413,10 @@ class FlorisImeService : LifecycleInputMethodService() {
}
override fun onEvaluateFullscreenMode(): Boolean {
val config = resources.configuration
if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
return false
}
return when (prefs.keyboard.landscapeInputUiMode.get()) {
LandscapeInputUiMode.DYNAMICALLY_SHOW -> super.onEvaluateFullscreenMode()
LandscapeInputUiMode.NEVER_SHOW -> false
@@ -432,23 +448,26 @@ class FlorisImeService : LifecycleInputMethodService() {
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? {
return if (prefs.smartbar.enabled.get() && prefs.suggestion.api30InlineSuggestionsEnabled.get()) {
flogInfo(LogTopic.IMS_EVENTS) {
"Creating inline suggestions request because Smartbar and inline suggestions are enabled."
}
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
val spec = InlinePresentationSpec.Builder(InlineSuggestionUiSmallestSize, InlineSuggestionUiBiggestSize)
.setStyle(stylesBundle)
.build()
InlineSuggestionsRequest.Builder(listOf(spec)).let { request ->
request.setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
request.build()
}
} else {
if (!prefs.smartbar.enabled.get() || !prefs.suggestion.api30InlineSuggestionsEnabled.get()) {
flogInfo(LogTopic.IMS_EVENTS) {
"Ignoring inline suggestions request because Smartbar and/or inline suggestions are disabled."
}
null
return null
}
flogInfo(LogTopic.IMS_EVENTS) { "Creating inline suggestions request" }
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
val spec = InlinePresentationSpec.Builder(
InlineSuggestionUiSmallestSize,
InlineSuggestionUiBiggestSize,
).run {
setStyle(stylesBundle)
build()
}
return InlineSuggestionsRequest.Builder(listOf(spec)).run {
setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
build()
}
}
@@ -458,8 +477,7 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS) {
"Received inline suggestions response with ${inlineSuggestions.size} suggestion(s) provided."
}
nlpManager.showInlineSuggestions(inlineSuggestions)
return true
return NlpInlineAutofill.showInlineSuggestions(this, inlineSuggestions)
}
override fun onComputeInsets(outInsets: Insets?) {
@@ -486,7 +504,9 @@ class FlorisImeService : LifecycleInputMethodService() {
outInsets.visibleTopInsets = visibleTopY
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION
val left = 0
val top = if (keyboardManager.activeState.isBottomSheetShowing()) { 0 } else {
val top = if (keyboardManager.activeState.isBottomSheetShowing() || keyboardManager.activeState.isSubtypeSelectionShowing()) {
0
} else {
visibleTopY - if (needAdditionalOverlay) FlorisImeSizing.Static.smartbarHeightPx else 0
}
val right = inputViewSize.width
@@ -543,7 +563,7 @@ class FlorisImeService : LifecycleInputMethodService() {
.fillMaxWidth()
.weight(1f),
) {
DevtoolsUi()
DevtoolsOverlay(modifier = Modifier.fillMaxSize())
}
}
ImeUi()
@@ -589,6 +609,7 @@ class FlorisImeService : LifecycleInputMethodService() {
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.safeDrawingPadding()
// FIXME: removing this fixes the Smartbar sizing but breaks one-handed-mode
//.height(IntrinsicSize.Min)
.padding(bottom = bottomOffset),
@@ -629,14 +650,6 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
@Composable
private fun DevtoolsUi() {
val devtoolsEnabled by prefs.devtools.enabled.observeAsState()
if (devtoolsEnabled) {
DevtoolsOverlay(modifier = Modifier.fillMaxSize())
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean =
if (keyboardManager.onHardwareKeyDown(keyCode, event)) true
else super.onKeyDown(keyCode, event)
@@ -671,9 +684,30 @@ class FlorisImeService : LifecycleInputMethodService() {
@Composable
override fun Content() {
val context = LocalContext.current
val keyboardManager by context.keyboardManager()
val state by keyboardManager.activeState.collectAsState()
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
FlorisImeTheme {
BottomSheetHostUi()
BottomSheetHostUi(
isShowing = state.isBottomSheetShowing() || state.isSubtypeSelectionShowing(),
onHide = {
if (state.isBottomSheetShowing()) {
keyboardManager.activeState.isActionsEditorVisible = false
}
if (state.isSubtypeSelectionShowing()) {
keyboardManager.activeState.isSubtypeSelectionVisible = false
}
},
) {
if (state.isBottomSheetShowing()) {
QuickActionsEditorPanel()
}
if (state.isSubtypeSelectionShowing()) {
SelectSubtypePanel()
}
}
}
}
}
@@ -721,7 +755,8 @@ class FlorisImeService : LifecycleInputMethodService() {
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
val fieldColor = fieldStyle.foreground.solidColor(context, FlorisImeTheme.fallbackContentColor())
val fieldColor =
fieldStyle.foreground.solidColor(context, FlorisImeTheme.fallbackContentColor())
AndroidView(
modifier = Modifier
.padding(8.dp)
@@ -757,8 +792,14 @@ class FlorisImeService : LifecycleInputMethodService() {
?: "ACTION",
shape = actionStyle.shape.shape(),
colors = ButtonDefaults.buttonColors(
backgroundColor = actionStyle.background.solidColor(context, FlorisImeTheme.fallbackContentColor()),
contentColor = actionStyle.foreground.solidColor(context, FlorisImeTheme.fallbackSurfaceColor()),
containerColor = actionStyle.background.solidColor(
context,
FlorisImeTheme.fallbackContentColor()
),
contentColor = actionStyle.foreground.solidColor(
context,
FlorisImeTheme.fallbackSurfaceColor()
),
),
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,14 +23,14 @@ import android.view.textservice.TextInfo
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.nlp.SpellingResult
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
import dev.patrickgold.florisboard.ime.nlp.SpellingResult
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogInfo
import dev.patrickgold.florisboard.lib.kotlin.map
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.florisboard.lib.kotlin.map
class FlorisSpellCheckerService : SpellCheckerService() {
private val prefs by florisPreferenceModel()

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,22 +18,28 @@ package dev.patrickgold.florisboard.app
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
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.keyboard.IncognitoMode
import dev.patrickgold.florisboard.ime.keyboard.SpaceBarMode
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHairStyle
import dev.patrickgold.florisboard.ime.media.emoji.EmojiRecentlyUsedHelper
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
@@ -42,16 +48,21 @@ 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.ime.theme.extCoreTheme
import dev.patrickgold.florisboard.lib.android.isOrientationPortrait
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.snygg.SnyggLevel
import dev.patrickgold.florisboard.lib.util.VersionName
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
import dev.patrickgold.jetpref.datastore.model.PreferenceType
import dev.patrickgold.jetpref.datastore.model.observeAsState
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.color.DEFAULT_GREEN
import org.florisboard.lib.snygg.SnyggLevel
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
@@ -62,6 +73,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "advanced__settings_theme",
default = AppTheme.AUTO,
)
val accentColor = custom(
key = "advanced__accent_color",
default = when (AndroidVersion.ATLEAST_API31_S) {
true -> Color.Unspecified
false -> DEFAULT_GREEN
},
serializer = ColorPreferenceSerializer,
)
val settingsLanguage = string(
key = "advanced__settings_language",
default = "auto",
@@ -107,6 +126,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
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,
@@ -147,10 +174,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "devtools__enabled",
default = false,
)
val showHeapMemoryStats = boolean(
key = "devtools__show_heap_memory_stats",
default = false,
)
val showPrimaryClip = boolean(
key = "devtools__show_primary_clip",
default = false,
@@ -163,6 +186,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "devtools__show_spelling_overlay",
default = false,
)
val showInlineAutofillOverlay = boolean(
key = "devtools__show_inline_autofill_overlay",
default = false,
)
val showKeyTouchBoundaries = boolean(
key = "devtools__show_touch_boundaries",
default = false,
@@ -185,6 +212,67 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
)
}
val emoji = Emoji()
inner class Emoji {
val preferredSkinTone = enum(
key = "emoji__preferred_skin_tone",
default = EmojiSkinTone.DEFAULT,
)
val preferredHairStyle = enum(
key = "emoji__preferred_hair_style",
default = EmojiHairStyle.DEFAULT,
)
val historyEnabled = boolean(
key = "emoji__history_enabled",
default = true,
)
val historyData = custom(
key = "emoji__history_data",
default = EmojiHistory.Empty,
serializer = EmojiHistory.Serializer,
)
val historyPinnedUpdateStrategy = enum(
key = "emoji__history_pinned_update_strategy",
default = EmojiHistory.UpdateStrategy.MANUAL_SORT_PREPEND,
)
val historyPinnedMaxSize = int(
key = "emoji__history_pinned_max_size",
default = EmojiHistory.MaxSizeUnlimited,
)
val historyRecentUpdateStrategy = enum(
key = "emoji__history_recent_update_strategy",
default = EmojiHistory.UpdateStrategy.AUTO_SORT_PREPEND,
)
val historyRecentMaxSize = int(
key = "emoji__history_recent_max_size",
default = 90,
)
val suggestionEnabled = boolean(
key = "emoji__suggestion_enabled",
default = true,
)
val suggestionType = enum(
key = "emoji__suggestion_type",
default = EmojiSuggestionType.LEADING_COLON,
)
val suggestionUpdateHistory = boolean(
key = "emoji__suggestion_update_history",
default = true,
)
val suggestionCandidateShowName = boolean(
key = "emoji__suggestion_candidate_show_name",
default = false,
)
val suggestionQueryMinLength = int(
key = "emoji__suggestion_query_min_length",
default = 3,
)
val suggestionCandidateMaxCount = int(
key = "emoji__suggestion_candidate_max_count",
default = 5,
)
}
val gestures = Gestures()
inner class Gestures {
val swipeUp = enum(
@@ -364,6 +452,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "internal__version_last_changelog",
default = VersionName.DEFAULT_RAW,
)
val notificationPermissionState = enum(
key = "internal__notification_permission_state",
default = NotificationPermissionState.NOT_SET,
)
}
val keyboard = Keyboard()
@@ -396,9 +488,13 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "keyboard__utility_key_action",
default = UtilityKeyAction.DYNAMIC_SWITCH_LANGUAGE_EMOJIS,
)
val spaceBarLanguageDisplayEnabled = boolean(
key = "keyboard__space_bar_language_display_enabled",
default = true,
val spaceBarMode = enum(
key = "keyboard__space_bar_display_mode",
default = SpaceBarMode.CURRENT_LANGUAGE,
)
val capitalizationBehavior = enum(
key = "keyboard__capitalization_behavior",
default = CapitalizationBehavior.CAPSLOCK_BY_DOUBLE_TAP,
)
val fontSizeMultiplierPortrait = int(
key = "keyboard__font_size_multiplier_portrait",
@@ -460,6 +556,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "keyboard__space_bar_switches_to_characters",
default = true,
)
val incognitoDisplayMode = enum(
key = "keyboard__incognito_indicator",
default = IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD,
)
fun keyHintConfiguration(): KeyHintConfiguration {
return KeyHintConfiguration(
@@ -510,27 +610,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
)
}
val media = Media()
inner class Media {
val emojiRecentlyUsed = custom(
key = "media__emoji_recently_used",
default = emptyList(),
serializer = EmojiRecentlyUsedHelper.Serializer,
)
val emojiRecentlyUsedMaxSize = int(
key = "media__emoji_recently_used_max_size",
default = 90,
)
val emojiPreferredSkinTone = enum(
key = "media__emoji_preferred_skin_tone",
default = EmojiSkinTone.DEFAULT,
)
val emojiPreferredHairStyle = enum(
key = "media__emoji_preferred_hair_style",
default = EmojiHairStyle.DEFAULT,
)
}
val smartbar = Smartbar()
inner class Smartbar {
val enabled = boolean(
@@ -554,6 +633,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "smartbar__shared_actions_expanded",
default = false,
)
@Deprecated("Always enabled due to UX issues")
val sharedActionsAutoExpandCollapse = boolean(
key = "smartbar__shared_actions_auto_expand_collapse",
default = true,
@@ -622,24 +702,24 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
key = "theme__mode",
default = ThemeMode.FOLLOW_SYSTEM,
)
val dayThemeAdaptToApp = boolean(
key = "theme__day_theme_adapt_to_app",
default = false,
)
val dayThemeId = custom(
key = "theme__day_theme_id",
default = extCoreTheme("floris_day"),
serializer = ExtensionComponentName.Serializer,
)
val nightThemeAdaptToApp = boolean(
key = "theme__night_theme_adapt_to_app",
default = false,
)
val nightThemeId = custom(
key = "theme__night_theme_id",
default = extCoreTheme("floris_night"),
serializer = ExtensionComponentName.Serializer,
)
val accentColor = custom(
key = "theme__accent_color",
default = when (AndroidVersion.ATLEAST_API31_S) {
true -> Color.Unspecified
false -> DEFAULT_GREEN
},
serializer = ColorPreferenceSerializer,
)
//val sunriseTime = localTime(
// key = "theme__sunrise_time",
// default = LocalTime.of(6, 0),
@@ -671,8 +751,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
"gestures__space_bar_swipe_right", "gestures__space_bar_long_press", "gestures__delete_key_swipe_left",
"gestures__delete_key_long_press", "keyboard__hinted_number_row_mode", "keyboard__hinted_symbols_mode",
"keyboard__utility_key_action", "keyboard__one_handed_mode", "keyboard__landscape_input_ui_mode",
"localization__display_language_names_in", "media__emoji_preferred_skin_tone",
"media__emoji_preferred_hair_style", "smartbar__primary_actions_row_type",
"localization__display_language_names_in", "smartbar__primary_actions_row_type",
"smartbar__secondary_actions_placement", "smartbar__secondary_actions_row_type", "spelling__language_mode",
"suggestion__display_mode", "theme__mode", "theme__editor_display_colors_as",
"theme__editor_display_kbd_after_dialogs", "theme__editor_level",
@@ -694,6 +773,32 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
}
}
// Migrate media prefs to emoji prefs
// Keep migration rule until: 0.6 dev cycle
"media__emoji_recently_used" -> {
val emojiValues = entry.rawValue.split(";")
val recent = emojiValues.map {
dev.patrickgold.florisboard.ime.media.emoji.Emoji(it, "", emptyList())
}
val data = EmojiHistory(emptyList(), recent)
entry.transform(key = "emoji__history_data", rawValue = Json.encodeToString(data))
}
"media__emoji_recently_used_max_size" -> {
entry.transform(key = "emoji__history_recent_max_size")
}
"media__emoji_preferred_skin_tone" -> {
entry.transform(
key = "emoji__preferred_skin_tone",
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
)
}
"media__emoji_preferred_hair_style" -> {
entry.transform(
key = "emoji__preferred_hair_style",
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
)
}
// Default: keep entry
else -> entry.keepAsIs()
}

View File

@@ -0,0 +1,657 @@
/*
* 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
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.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.keyboard.IncognitoMode
import dev.patrickgold.florisboard.ime.keyboard.SpaceBarMode
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.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 org.florisboard.lib.snygg.SnyggLevel
import dev.patrickgold.jetpref.datastore.ui.ListPreferenceEntry
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
import org.florisboard.lib.kotlin.curlyFormat
import kotlin.reflect.KClass
private const val DEFAULT = ""
private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable () -> List<ListPreferenceEntry<*>>>(
AppTheme::class to DEFAULT to {
listPrefEntries {
entry(
key = AppTheme.AUTO,
label = stringRes(R.string.settings__system_default),
)
entry(
key = AppTheme.AUTO_AMOLED,
label = stringRes(R.string.pref__advanced__settings_theme__auto_amoled),
)
entry(
key = AppTheme.LIGHT,
label = stringRes(R.string.pref__advanced__settings_theme__light),
)
entry(
key = AppTheme.DARK,
label = stringRes(R.string.pref__advanced__settings_theme__dark),
)
entry(
key = AppTheme.AMOLED_DARK,
label = stringRes(R.string.pref__advanced__settings_theme__amoled_dark),
)
}
},
CandidatesDisplayMode::class to DEFAULT to {
listPrefEntries {
entry(
key = CandidatesDisplayMode.CLASSIC,
label = stringRes(R.string.enum__candidates_display_mode__classic),
)
entry(
key = CandidatesDisplayMode.DYNAMIC,
label = stringRes(R.string.enum__candidates_display_mode__dynamic),
)
entry(
key = CandidatesDisplayMode.DYNAMIC_SCROLLABLE,
label = stringRes(R.string.enum__candidates_display_mode__dynamic_scrollable),
)
}
},
CapitalizationBehavior::class to DEFAULT to {
listPrefEntries {
entry(
key = CapitalizationBehavior.CAPSLOCK_BY_DOUBLE_TAP,
label = stringRes(R.string.enum__capitalization_behavior__capslock_by_double_tap),
)
entry(
key = CapitalizationBehavior.CAPSLOCK_BY_CYCLE,
label = stringRes(R.string.enum__capitalization_behavior__capslock_by_cycle),
)
}
},
DisplayColorsAs::class to DEFAULT to {
listPrefEntries {
entry(
key = DisplayColorsAs.HEX8,
label = stringRes(R.string.enum__display_colors_as__hex8),
description = stringRes(R.string.general__example_given).curlyFormat("example" to "#4caf50ff"),
showDescriptionOnlyIfSelected = true,
)
entry(
key = DisplayColorsAs.RGBA,
label = stringRes(R.string.enum__display_colors_as__rgba),
description = stringRes(R.string.general__example_given).curlyFormat("example" to "rgba(76,175,80,1.0)"),
showDescriptionOnlyIfSelected = true,
)
}
},
DisplayKbdAfterDialogs::class to DEFAULT to {
listPrefEntries {
entry(
key = DisplayKbdAfterDialogs.ALWAYS,
label = stringRes(R.string.enum__display_kbd_after_dialogs__always),
description = stringRes(R.string.enum__display_kbd_after_dialogs__always__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = DisplayKbdAfterDialogs.NEVER,
label = stringRes(R.string.enum__display_kbd_after_dialogs__never),
description = stringRes(R.string.enum__display_kbd_after_dialogs__never__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = DisplayKbdAfterDialogs.REMEMBER,
label = stringRes(R.string.enum__display_kbd_after_dialogs__remember),
description = stringRes(R.string.enum__display_kbd_after_dialogs__remember__description),
showDescriptionOnlyIfSelected = true,
)
}
},
DisplayLanguageNamesIn::class to DEFAULT to {
listPrefEntries {
entry(
key = DisplayLanguageNamesIn.SYSTEM_LOCALE,
label = stringRes(R.string.enum__display_language_names_in__system_locale),
description = stringRes(R.string.enum__display_language_names_in__system_locale__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = DisplayLanguageNamesIn.NATIVE_LOCALE,
label = stringRes(R.string.enum__display_language_names_in__native_locale),
description = stringRes(R.string.enum__display_language_names_in__native_locale__description),
showDescriptionOnlyIfSelected = true,
)
}
},
EmojiHistory.UpdateStrategy::class to DEFAULT to {
listPrefEntries {
entry(
key = EmojiHistory.UpdateStrategy.AUTO_SORT_PREPEND,
label = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_prepend),
description = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_prepend__description),
)
entry(
key = EmojiHistory.UpdateStrategy.AUTO_SORT_APPEND,
label = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_append),
description = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_append__description),
)
entry(
key = EmojiHistory.UpdateStrategy.MANUAL_SORT_PREPEND,
label = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_prepend),
description = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_prepend__description),
)
entry(
key = EmojiHistory.UpdateStrategy.MANUAL_SORT_APPEND,
label = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_append),
description = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_append__description),
)
}
},
EmojiSkinTone::class to DEFAULT to {
listPrefEntries {
entry(
key = EmojiSkinTone.DEFAULT,
label = stringRes(
R.string.enum__emoji_skin_tone__default,
"emoji" to "\uD83D\uDC4B" // 👋
),
)
entry(
key = EmojiSkinTone.LIGHT_SKIN_TONE,
label = stringRes(
R.string.enum__emoji_skin_tone__light_skin_tone,
"emoji" to "\uD83D\uDC4B\uD83C\uDFFB" // 👋🏻
),
)
entry(
key = EmojiSkinTone.MEDIUM_LIGHT_SKIN_TONE,
label = stringRes(
R.string.enum__emoji_skin_tone__medium_light_skin_tone,
"emoji" to "\uD83D\uDC4B\uD83C\uDFFC" // 👋🏼
),
)
entry(
key = EmojiSkinTone.MEDIUM_SKIN_TONE,
label = stringRes(
R.string.enum__emoji_skin_tone__medium_skin_tone,
"emoji" to "\uD83D\uDC4B\uD83C\uDFFD" // 👋🏽
),
)
entry(
key = EmojiSkinTone.MEDIUM_DARK_SKIN_TONE,
label = stringRes(
R.string.enum__emoji_skin_tone__medium_dark_skin_tone,
"emoji" to "\uD83D\uDC4B\uD83C\uDFFE" // 👋🏾
),
)
entry(
key = EmojiSkinTone.DARK_SKIN_TONE,
label = stringRes(
R.string.enum__emoji_skin_tone__dark_skin_tone,
"emoji" to "\uD83D\uDC4B\uD83C\uDFFF" // 👋🏿
),
)
}
},
EmojiSuggestionType::class to DEFAULT to {
listPrefEntries {
entry(
key = EmojiSuggestionType.LEADING_COLON,
label = stringRes(R.string.enum__emoji_suggestion_type__leading_colon),
description = stringRes(R.string.enum__emoji_suggestion_type__leading_colon__description),
)
entry(
key = EmojiSuggestionType.INLINE_TEXT,
label = stringRes(R.string.enum__emoji_suggestion_type__inline_text),
description = stringRes(R.string.enum__emoji_suggestion_type__inline_text__description),
)
}
},
ExtendedActionsPlacement::class to DEFAULT to {
listPrefEntries {
entry(
key = ExtendedActionsPlacement.ABOVE_CANDIDATES,
label = stringRes(R.string.enum__extended_actions_placement__above_candidates),
description = stringRes(R.string.enum__extended_actions_placement__above_candidates__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = ExtendedActionsPlacement.BELOW_CANDIDATES,
label = stringRes(R.string.enum__extended_actions_placement__below_candidates),
description = stringRes(R.string.enum__extended_actions_placement__below_candidates__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = ExtendedActionsPlacement.OVERLAY_APP_UI,
label = stringRes(R.string.enum__extended_actions_placement__overlay_app_ui),
description = stringRes(R.string.enum__extended_actions_placement__overlay_app_ui__description),
showDescriptionOnlyIfSelected = true,
)
}
},
HapticVibrationMode::class to DEFAULT to {
listPrefEntries {
entry(
key = HapticVibrationMode.USE_VIBRATOR_DIRECTLY,
label = stringRes(R.string.enum__haptic_vibration_mode__use_vibrator_directly),
description = stringRes(R.string.enum__haptic_vibration_mode__use_vibrator_directly__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = HapticVibrationMode.USE_HAPTIC_FEEDBACK_INTERFACE,
label = stringRes(R.string.enum__haptic_vibration_mode__use_haptic_feedback_interface),
description = stringRes(R.string.enum__haptic_vibration_mode__use_haptic_feedback_interface__description),
showDescriptionOnlyIfSelected = true,
)
}
},
KeyHintMode::class to DEFAULT to {
listPrefEntries {
entry(
key = KeyHintMode.ACCENT_PRIORITY,
label = stringRes(R.string.enum__key_hint_mode__accent_priority),
description = stringRes(R.string.enum__key_hint_mode__accent_priority__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = KeyHintMode.HINT_PRIORITY,
label = stringRes(R.string.enum__key_hint_mode__hint_priority),
description = stringRes(R.string.enum__key_hint_mode__hint_priority__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = KeyHintMode.SMART_PRIORITY,
label = stringRes(R.string.enum__key_hint_mode__smart_priority),
description = stringRes(R.string.enum__key_hint_mode__smart_priority__description),
showDescriptionOnlyIfSelected = true,
)
}
},
IncognitoDisplayMode::class to DEFAULT to {
listPrefEntries {
entry(
key = IncognitoDisplayMode.REPLACE_SHARED_ACTIONS_TOGGLE,
label = stringRes(id = R.string.enum__incognito_display_mode__replace_shared_actions_toggle),
)
entry(
key = IncognitoDisplayMode.DISPLAY_BEHIND_KEYBOARD,
label = stringRes(id = R.string.enum__incognito_display_mode__display_behind_keyboard),
)
}
},
IncognitoMode::class to DEFAULT to {
listPrefEntries {
entry(
key = IncognitoMode.FORCE_OFF,
label = stringRes(R.string.enum__incognito_mode__force_off),
description = stringRes(R.string.enum__incognito_mode__force_off__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = IncognitoMode.DYNAMIC_ON_OFF,
label = stringRes(R.string.enum__incognito_mode__dynamic_on_off),
description = stringRes(R.string.enum__incognito_mode__dynamic_on_off__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = IncognitoMode.FORCE_ON,
label = stringRes(R.string.enum__incognito_mode__force_on),
description = stringRes(R.string.enum__incognito_mode__force_on__description),
showDescriptionOnlyIfSelected = true,
)
}
},
InputFeedbackActivationMode::class to "audio" to {
listPrefEntries {
entry(
key = InputFeedbackActivationMode.RESPECT_SYSTEM_SETTINGS,
label = stringRes(R.string.enum__input_feedback_activation_mode__audio_respect_system_settings),
)
entry(
key = InputFeedbackActivationMode.IGNORE_SYSTEM_SETTINGS,
label = stringRes(R.string.enum__input_feedback_activation_mode__audio_ignore_system_settings),
)
}
},
InputFeedbackActivationMode::class to "haptic" to {
listPrefEntries {
entry(
key = InputFeedbackActivationMode.RESPECT_SYSTEM_SETTINGS,
label = stringRes(R.string.enum__input_feedback_activation_mode__haptic_respect_system_settings),
)
entry(
key = InputFeedbackActivationMode.IGNORE_SYSTEM_SETTINGS,
label = stringRes(R.string.enum__input_feedback_activation_mode__haptic_ignore_system_settings),
)
}
},
LandscapeInputUiMode::class to DEFAULT to {
listPrefEntries {
entry(
key = LandscapeInputUiMode.NEVER_SHOW,
label = stringRes(R.string.enum__landscape_input_ui_mode__never_show),
)
entry(
key = LandscapeInputUiMode.ALWAYS_SHOW,
label = stringRes(R.string.enum__landscape_input_ui_mode__always_show),
)
entry(
key = LandscapeInputUiMode.DYNAMICALLY_SHOW,
label = stringRes(R.string.enum__landscape_input_ui_mode__dynamically_show),
)
}
},
OneHandedMode::class to DEFAULT to {
listPrefEntries {
entry(
key = OneHandedMode.OFF,
label = stringRes(R.string.enum__one_handed_mode__off),
)
entry(
key = OneHandedMode.START,
label = stringRes(R.string.enum__one_handed_mode__start),
)
entry(
key = OneHandedMode.END,
label = stringRes(R.string.enum__one_handed_mode__end),
)
}
},
SmartbarLayout::class to DEFAULT to {
listPrefEntries {
entry(
key = SmartbarLayout.SUGGESTIONS_ONLY,
label = stringRes(R.string.enum__smartbar_layout__suggestions_only),
description = stringRes(R.string.enum__smartbar_layout__suggestions_only__description),
)
entry(
key = SmartbarLayout.ACTIONS_ONLY,
label = stringRes(R.string.enum__smartbar_layout__actions_only),
description = stringRes(R.string.enum__smartbar_layout__actions_only__description),
)
entry(
key = SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED,
label = stringRes(R.string.enum__smartbar_layout__suggestions_action_shared),
description = stringRes(R.string.enum__smartbar_layout__suggestions_action_shared__description),
)
entry(
key = SmartbarLayout.SUGGESTIONS_ACTIONS_EXTENDED,
label = stringRes(R.string.enum__smartbar_layout__suggestions_actions_extended),
description = stringRes(R.string.enum__smartbar_layout__suggestions_actions_extended__description),
)
}
},
SnyggLevel::class to DEFAULT to {
listPrefEntries {
entry(
key = SnyggLevel.BASIC,
label = stringRes(R.string.enum__snygg_level__basic),
description = stringRes(R.string.enum__snygg_level__basic__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = SnyggLevel.ADVANCED,
label = stringRes(R.string.enum__snygg_level__advanced),
description = stringRes(R.string.enum__snygg_level__advanced__description),
showDescriptionOnlyIfSelected = true,
)
entry(
key = SnyggLevel.DEVELOPER,
label = stringRes(R.string.enum__snygg_level__developer),
description = stringRes(R.string.enum__snygg_level__developer__description),
showDescriptionOnlyIfSelected = true,
)
}
},
SpaceBarMode::class to DEFAULT to {
listPrefEntries {
entry(
key = SpaceBarMode.NOTHING,
label = stringRes(R.string.enum__space_bar_mode__nothing),
)
entry(
key = SpaceBarMode.CURRENT_LANGUAGE,
label = stringRes(R.string.enum__space_bar_mode__current_language),
)
entry(
key = SpaceBarMode.SPACE_BAR_KEY,
label = stringRes(R.string.enum__space_bar_mode__space_bar_key),
)
}
},
SpellingLanguageMode::class to DEFAULT to {
listPrefEntries {
entry(
key = SpellingLanguageMode.USE_SYSTEM_LANGUAGES,
label = stringRes(R.string.enum__spelling_language_mode__use_system_languages),
)
entry(
key = SpellingLanguageMode.USE_KEYBOARD_SUBTYPES,
label = stringRes(R.string.enum__spelling_language_mode__use_keyboard_subtypes),
)
}
},
SwipeAction::class to "general" to {
listPrefEntries {
entry(
key = SwipeAction.NO_ACTION,
label = stringRes(R.string.enum__swipe_action__no_action),
)
entry(
key = SwipeAction.CYCLE_TO_PREVIOUS_KEYBOARD_MODE,
label = stringRes(R.string.enum__swipe_action__cycle_to_previous_keyboard_mode),
)
entry(
key = SwipeAction.CYCLE_TO_NEXT_KEYBOARD_MODE,
label = stringRes(R.string.enum__swipe_action__cycle_to_next_keyboard_mode),
)
entry(
key = SwipeAction.DELETE_WORD,
label = stringRes(R.string.enum__swipe_action__delete_word),
)
entry(
key = SwipeAction.HIDE_KEYBOARD,
label = stringRes(R.string.enum__swipe_action__hide_keyboard),
)
entry(
key = SwipeAction.INSERT_SPACE,
label = stringRes(R.string.enum__swipe_action__insert_space),
)
entry(
key = SwipeAction.MOVE_CURSOR_UP,
label = stringRes(R.string.enum__swipe_action__move_cursor_up),
)
entry(
key = SwipeAction.MOVE_CURSOR_DOWN,
label = stringRes(R.string.enum__swipe_action__move_cursor_down),
)
entry(
key = SwipeAction.MOVE_CURSOR_LEFT,
label = stringRes(R.string.enum__swipe_action__move_cursor_left),
)
entry(
key = SwipeAction.MOVE_CURSOR_RIGHT,
label = stringRes(R.string.enum__swipe_action__move_cursor_right),
)
entry(
key = SwipeAction.MOVE_CURSOR_START_OF_LINE,
label = stringRes(R.string.enum__swipe_action__move_cursor_start_of_line),
)
entry(
key = SwipeAction.MOVE_CURSOR_END_OF_LINE,
label = stringRes(R.string.enum__swipe_action__move_cursor_end_of_line),
)
entry(
key = SwipeAction.MOVE_CURSOR_START_OF_PAGE,
label = stringRes(R.string.enum__swipe_action__move_cursor_start_of_page),
)
entry(
key = SwipeAction.MOVE_CURSOR_END_OF_PAGE,
label = stringRes(R.string.enum__swipe_action__move_cursor_end_of_page),
)
entry(
key = SwipeAction.SHIFT,
label = stringRes(R.string.enum__swipe_action__shift),
)
entry(
key = SwipeAction.REDO,
label = stringRes(R.string.enum__swipe_action__redo),
)
entry(
key = SwipeAction.UNDO,
label = stringRes(R.string.enum__swipe_action__undo),
)
entry(
key = SwipeAction.SWITCH_TO_CLIPBOARD_CONTEXT,
label = stringRes(R.string.enum__swipe_action__switch_to_clipboard_context),
)
entry(
key = SwipeAction.SHOW_INPUT_METHOD_PICKER,
label = stringRes(R.string.enum__swipe_action__show_input_method_picker),
)
entry(
key = SwipeAction.SHOW_SUBTYPE_PICKER,
label = "Show subtype picker"
)
entry(
key = SwipeAction.SWITCH_TO_PREV_SUBTYPE,
label = stringRes(R.string.enum__swipe_action__switch_to_prev_subtype),
)
entry(
key = SwipeAction.SWITCH_TO_NEXT_SUBTYPE,
label = stringRes(R.string.enum__swipe_action__switch_to_next_subtype),
)
entry(
key = SwipeAction.SWITCH_TO_PREV_KEYBOARD,
label = stringRes(R.string.enum__swipe_action__switch_to_prev_keyboard),
)
entry(
key = SwipeAction.TOGGLE_SMARTBAR_VISIBILITY,
label = stringRes(R.string.enum__swipe_action__toggle_smartbar_visibility),
)
}
},
SwipeAction::class to "deleteSwipe" to {
listPrefEntries {
entry(
key = SwipeAction.NO_ACTION,
label = stringRes(R.string.enum__swipe_action__no_action),
)
entry(
key = SwipeAction.DELETE_CHARACTERS_PRECISELY,
label = stringRes(R.string.enum__swipe_action__delete_characters_precisely),
)
entry(
key = SwipeAction.DELETE_WORD,
label = stringRes(R.string.enum__swipe_action__delete_word),
)
entry(
key = SwipeAction.DELETE_WORDS_PRECISELY,
label = stringRes(R.string.enum__swipe_action__delete_words_precisely),
)
entry(
key = SwipeAction.SELECT_CHARACTERS_PRECISELY,
label = stringRes(R.string.enum__swipe_action__select_characters_precisely),
)
entry(
key = SwipeAction.SELECT_WORDS_PRECISELY,
label = stringRes(R.string.enum__swipe_action__select_words_precisely),
)
}
},
SwipeAction::class to "deleteLongPress" to {
listPrefEntries {
entry(
key = SwipeAction.DELETE_CHARACTER,
label = stringRes(R.string.enum__swipe_action__delete_character),
)
entry(
key = SwipeAction.DELETE_WORD,
label = stringRes(R.string.enum__swipe_action__delete_word),
)
}
},
ThemeMode::class to DEFAULT to {
listPrefEntries {
entry(
key = ThemeMode.ALWAYS_DAY,
label = stringRes(R.string.enum__theme_mode__always_day),
)
entry(
key = ThemeMode.ALWAYS_NIGHT,
label = stringRes(R.string.enum__theme_mode__always_night),
)
entry(
key = ThemeMode.FOLLOW_SYSTEM,
label = stringRes(R.string.enum__theme_mode__follow_system),
)
entry(
key = ThemeMode.FOLLOW_TIME,
label = stringRes(R.string.enum__theme_mode__follow_time),
)
}
},
UtilityKeyAction::class to DEFAULT to {
listPrefEntries {
entry(
key = UtilityKeyAction.SWITCH_TO_EMOJIS,
label = stringRes(R.string.enum__utility_key_action__switch_to_emojis),
)
entry(
key = UtilityKeyAction.SWITCH_LANGUAGE,
label = stringRes(R.string.enum__utility_key_action__switch_language),
)
entry(
key = UtilityKeyAction.SWITCH_KEYBOARD_APP,
label = stringRes(R.string.enum__utility_key_action__switch_keyboard_app),
)
entry(
key = UtilityKeyAction.DYNAMIC_SWITCH_LANGUAGE_EMOJIS,
label = stringRes(R.string.enum__utility_key_action__dynamic_switch_language_emojis),
)
}
},
)
@Composable
fun <V : Any> enumDisplayEntriesOf(
enumClass: KClass<V>,
variant: String = DEFAULT,
): List<ListPreferenceEntry<V>> {
@Suppress("UNCHECKED_CAST")
return ENUM_DISPLAY_ENTRIES[enumClass to variant]?.invoke()
as List<ListPreferenceEntry<V>>
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,44 +17,49 @@
package dev.patrickgold.florisboard.app
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.isUnspecified
import androidx.compose.ui.platform.LocalConfiguration
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
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.cacheManager
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.android.AndroidVersion
import dev.patrickgold.florisboard.lib.android.hideAppIcon
import dev.patrickgold.florisboard.lib.android.setLocale
import dev.patrickgold.florisboard.lib.android.showAppIcon
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.SystemUiApp
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
enum class AppTheme(val id: String) {
AUTO("auto"),
@@ -70,9 +75,11 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
class FlorisAppActivity : ComponentActivity() {
private val prefs by florisPreferenceModel()
private val cacheManager by cacheManager()
private var appTheme by mutableStateOf(AppTheme.AUTO)
private var showAppIcon = true
private var resourcesContext by mutableStateOf(this as Context)
private var intentToBeHandled by mutableStateOf<Intent?>(null)
override fun onCreate(savedInstanceState: Bundle?) {
// Splash screen should be installed before calling super.onCreate()
@@ -87,7 +94,8 @@ class FlorisAppActivity : ComponentActivity() {
}
prefs.advanced.settingsLanguage.observe(this) {
val config = Configuration(resources.configuration)
config.setLocale(if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it))
val locale = if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it)
config.setLocale(locale.base)
resourcesContext = createConfigurationContext(config)
}
if (AndroidVersion.ATMOST_API28_P) {
@@ -96,20 +104,29 @@ class FlorisAppActivity : ComponentActivity() {
}
}
//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
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
setContent {
ProvideLocalizedResources(resourcesContext) {
FlorisAppTheme(theme = appTheme) {
Surface(color = MaterialTheme.colors.background) {
SystemUiApp()
val accentColor by prefs.advanced.accentColor.observeAsState()
FlorisAppTheme(theme = appTheme, isMaterialYouAware = accentColor.isUnspecified) {
Surface(color = MaterialTheme.colorScheme.background) {
AppContent()
}
}
}
}
onNewIntent(intent)
}
}
@@ -127,6 +144,25 @@ class FlorisAppActivity : ComponentActivity() {
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
if (intent.action == Intent.ACTION_VIEW && intent.categories?.contains(Intent.CATEGORY_BROWSABLE) == true) {
intentToBeHandled = intent
return
}
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
intentToBeHandled = intent
return
}
if (intent.action == Intent.ACTION_SEND && intent.clipData != null) {
intentToBeHandled = intent
return
}
intentToBeHandled = null
}
@Composable
private fun AppContent() {
val navController = rememberNavController()
@@ -145,8 +181,11 @@ class FlorisAppActivity : ComponentActivity() {
) {
Column(
modifier = Modifier
.statusBarsPadding()
//.statusBarsPadding()
.navigationBarsPadding()
.conditional(LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) {
displayCutoutPadding()
}
.imePadding(),
) {
Routes.AppNavHost(
@@ -159,8 +198,22 @@ class FlorisAppActivity : ComponentActivity() {
}
}
SideEffect {
navController.setOnBackPressedDispatcher(this.onBackPressedDispatcher)
LaunchedEffect(intentToBeHandled) {
val intent = intentToBeHandled
if (intent != null) {
if (intent.action == Intent.ACTION_VIEW && intent.categories?.contains(Intent.CATEGORY_BROWSABLE) == true) {
navController.handleDeepLink(intent)
} else {
val data = if (intent.action == Intent.ACTION_VIEW) {
intent.data!!
} else {
intent.clipData!!.getItemAt(0).uri
}
val workspace = runCatching { cacheManager.readFromUriIntoCache(data) }.getOrNull()
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, workspace?.uuid))
}
}
intentToBeHandled = null
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,32 @@
package dev.patrickgold.florisboard.app
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.IntOffset
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import dev.patrickgold.florisboard.app.devtools.AndroidLocalesScreen
import dev.patrickgold.florisboard.app.devtools.AndroidSettingsScreen
import dev.patrickgold.florisboard.app.devtools.DevtoolsScreen
import dev.patrickgold.florisboard.app.devtools.ExportDebugLogScreen
import dev.patrickgold.florisboard.app.ext.CheckUpdatesScreen
import dev.patrickgold.florisboard.app.ext.ExtensionEditScreen
import dev.patrickgold.florisboard.app.ext.ExtensionExportScreen
import dev.patrickgold.florisboard.app.ext.ExtensionHomeScreen
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreen
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.ext.ExtensionListScreen
import dev.patrickgold.florisboard.app.ext.ExtensionListScreenType
import dev.patrickgold.florisboard.app.ext.ExtensionViewScreen
import dev.patrickgold.florisboard.app.settings.HomeScreen
import dev.patrickgold.florisboard.app.settings.about.AboutScreen
@@ -56,9 +69,9 @@ 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 dev.patrickgold.florisboard.lib.kotlin.curlyFormat
import org.florisboard.lib.kotlin.curlyFormat
@Suppress("FunctionName")
@Suppress("FunctionName", "ConstPropertyName")
object Routes {
object Setup {
const val Screen = "setup"
@@ -117,6 +130,14 @@ object Routes {
}
object Ext {
const val Home = "ext"
const val List = "ext/list/{type}?showUpdate={showUpdate}"
fun List(
type: ExtensionListScreenType,
showUpdate: Boolean
) = List.curlyFormat("type" to type.id, "showUpdate" to showUpdate)
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 ?: ""))
@@ -133,6 +154,8 @@ object Routes {
const val View = "ext/view/{id}"
fun View(id: String) = View.curlyFormat("id" to id)
const val CheckUpdates = "ext/check-updates"
}
@Composable
@@ -141,97 +164,131 @@ object Routes {
navController: NavHostController,
startDestination: String,
) {
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,
startDestination = startDestination,
enterTransition = {
slideIn { IntOffset(it.width, 0) } + fadeIn()
},
exitTransition = {
slideOut { IntOffset(-it.width, 0) } + fadeOut()
},
popEnterTransition = {
slideIn { IntOffset(-it.width, 0) } + fadeIn()
},
popExitTransition = {
slideOut { IntOffset(it.width, 0) } + fadeOut()
}
) {
composable(Setup.Screen) { SetupScreen() }
composable(Settings.Home) { HomeScreen() }
composableWithDeepLink(Settings.Home) { HomeScreen() }
composable(Settings.Localization) { LocalizationScreen() }
composable(Settings.SelectLocale) { SelectLocaleScreen() }
composable(Settings.LanguagePackManager) { navBackStack ->
composableWithDeepLink(Settings.Localization) { LocalizationScreen() }
composableWithDeepLink(Settings.SelectLocale) { SelectLocaleScreen() }
composableWithDeepLink(Settings.LanguagePackManager) { navBackStack ->
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
LanguagePackManagerScreenAction.values().firstOrNull { it.id == actionId }
LanguagePackManagerScreenAction.entries.firstOrNull { it.id == actionId }
}
LanguagePackManagerScreen(action)
}
composable(Settings.SubtypeAdd) { SubtypeEditorScreen(null) }
composable(Settings.SubtypeEdit) { navBackStack ->
composableWithDeepLink(Settings.SubtypeAdd) { SubtypeEditorScreen(null) }
composableWithDeepLink(Settings.SubtypeEdit) { navBackStack ->
val id = navBackStack.arguments?.getString("id")?.toLongOrNull()
SubtypeEditorScreen(id)
}
composable(Settings.Theme) { ThemeScreen() }
composable(Settings.ThemeManager) { navBackStack ->
composableWithDeepLink(Settings.Theme) { ThemeScreen() }
composableWithDeepLink(Settings.ThemeManager) { navBackStack ->
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
ThemeManagerScreenAction.values().firstOrNull { it.id == actionId }
ThemeManagerScreenAction.entries.firstOrNull { it.id == actionId }
}
ThemeManagerScreen(action)
}
composable(Settings.Keyboard) { KeyboardScreen() }
composable(Settings.InputFeedback) { InputFeedbackScreen() }
composableWithDeepLink(Settings.Keyboard) { KeyboardScreen() }
composableWithDeepLink(Settings.InputFeedback) { InputFeedbackScreen() }
composable(Settings.Smartbar) { SmartbarScreen() }
composableWithDeepLink(Settings.Smartbar) { SmartbarScreen() }
composable(Settings.Typing) { TypingScreen() }
composableWithDeepLink(Settings.Typing) { TypingScreen() }
composable(Settings.Dictionary) { DictionaryScreen() }
composable(Settings.UserDictionary) { navBackStack ->
composableWithDeepLink(Settings.Dictionary) { DictionaryScreen() }
composableWithDeepLink(Settings.UserDictionary) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
UserDictionaryType.values().firstOrNull { it.id == typeId }
UserDictionaryType.entries.firstOrNull { it.id == typeId }
}
UserDictionaryScreen(type!!)
}
composable(Settings.Gestures) { GesturesScreen() }
composableWithDeepLink(Settings.Gestures) { GesturesScreen() }
composable(Settings.Clipboard) { ClipboardScreen() }
composableWithDeepLink(Settings.Clipboard) { ClipboardScreen() }
composable(Settings.Media) { MediaScreen() }
composableWithDeepLink(Settings.Media) { MediaScreen() }
composable(Settings.Advanced) { AdvancedScreen() }
composable(Settings.Backup) { BackupScreen() }
composable(Settings.Restore) { RestoreScreen() }
composableWithDeepLink(Settings.Advanced) { AdvancedScreen() }
composableWithDeepLink(Settings.Backup) { BackupScreen() }
composableWithDeepLink(Settings.Restore) { RestoreScreen() }
composable(Settings.About) { AboutScreen() }
composable(Settings.ProjectLicense) { ProjectLicenseScreen() }
composable(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
composableWithDeepLink(Settings.About) { AboutScreen() }
composableWithDeepLink(Settings.ProjectLicense) { ProjectLicenseScreen() }
composableWithDeepLink(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
composable(Devtools.Home) { DevtoolsScreen() }
composable(Devtools.AndroidLocales) { AndroidLocalesScreen() }
composable(Devtools.AndroidSettings) { navBackStack ->
composableWithDeepLink(Devtools.Home) { DevtoolsScreen() }
composableWithDeepLink(Devtools.AndroidLocales) { AndroidLocalesScreen() }
composableWithDeepLink(Devtools.AndroidSettings) { navBackStack ->
val name = navBackStack.arguments?.getString("name")
AndroidSettingsScreen(name)
}
composable(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
composableWithDeepLink(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
composable(Ext.Edit) { navBackStack ->
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.Edit) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
val serialType = navBackStack.arguments?.getString("serial_type")
ExtensionEditScreen(
id = extensionId.toString(),
createSerialType = serialType.takeIf { it != null && it.isNotBlank() },
createSerialType = serialType.takeIf { !it.isNullOrBlank() },
)
}
composable(Ext.Export) { navBackStack ->
composableWithDeepLink(Ext.Export) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
ExtensionExportScreen(id = extensionId.toString())
}
composable(Ext.Import) { navBackStack ->
composableWithDeepLink(Ext.Import) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
ExtensionImportScreenType.values().firstOrNull { it.id == typeId }
ExtensionImportScreenType.entries.firstOrNull { it.id == typeId }
} ?: ExtensionImportScreenType.EXT_ANY
val uuid = navBackStack.arguments?.getString("uuid")?.takeIf { it != "null" }
ExtensionImportScreen(type, uuid)
}
composable(Ext.View) { navBackStack ->
composableWithDeepLink(Ext.View) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
ExtensionViewScreen(id = extensionId.toString())
}
composableWithDeepLink(Ext.CheckUpdates) {
CheckUpdatesScreen()
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,85 @@ package dev.patrickgold.florisboard.app.apptheme
import androidx.compose.ui.graphics.Color
/* Legacy Colors
val Green500 = Color(0xFF4CAF50)
val Green700 = Color(0xFF388E3C)
val Orange700 = Color(0xFFF57C00)
val Orange900 = Color(0xFFE65100)
*/
//Colors created with the material theme builder
val primaryLight = Color(0xFF006E1C)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFF58BC5B)
val onPrimaryContainerLight = Color(0xFF002204)
val secondaryLight = Color(0xFF005E16)
val onSecondaryLight = Color(0xFFFFFFFF)
val secondaryContainerLight = Color(0xFF2E8534)
val onSecondaryContainerLight = Color(0xFFFFFFFF)
val tertiaryLight = Color(0xFF964900)
val onTertiaryLight = Color(0xFFFFFFFF)
val tertiaryContainerLight = Color(0xFFFF8926)
val onTertiaryContainerLight = Color(0xFF341500)
val errorLight = Color(0xFFBA1A1A)
val onErrorLight = Color(0xFFFFFFFF)
val errorContainerLight = Color(0xFFFFDAD6)
val onErrorContainerLight = Color(0xFF410002)
val backgroundLight = Color(0xFFF5FBEF)
val onBackgroundLight = Color(0xFF171D16)
val surfaceLight = Color(0xFFF5FBEF)
val onSurfaceLight = Color(0xFF171D16)
val surfaceVariantLight = Color(0xFFDAE6D4)
val onSurfaceVariantLight = Color(0xFF3F4A3C)
val outlineLight = Color(0xFF6F7A6B)
val outlineVariantLight = Color(0xFFBECAB9)
val scrimLight = Color(0xFF000000)
val inverseSurfaceLight = Color(0xFF2C322A)
val inverseOnSurfaceLight = Color(0xFFEDF3E7)
val inversePrimaryLight = Color(0xFF78DC77)
val surfaceDimLight = Color(0xFFD6DCD0)
val surfaceBrightLight = Color(0xFFF5FBEF)
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
val surfaceContainerLowLight = Color(0xFFF0F6EA)
val surfaceContainerLight = Color(0xFFEAF0E4)
val surfaceContainerHighLight = Color(0xFFE4EADE)
val surfaceContainerHighestLight = Color(0xFFDEE4D9)
val primaryDark = Color(0xFF78DC77)
val onPrimaryDark = Color(0xFF00390A)
val primaryContainerDark = Color(0xFF43A648)
val onPrimaryContainerDark = Color(0xFF000000)
val secondaryDark = Color(0xFF82DB7E)
val onSecondaryDark = Color(0xFF00390A)
val secondaryContainerDark = Color(0xFF2D8433)
val onSecondaryContainerDark = Color(0xFFFFFFFF)
val tertiaryDark = Color(0xFFFFB786)
val onTertiaryDark = Color(0xFF502400)
val tertiaryContainerDark = Color(0xFFEA7600)
val onTertiaryContainerDark = Color(0xFF030100)
val errorDark = Color(0xFFFFB4AB)
val onErrorDark = Color(0xFF690005)
val errorContainerDark = Color(0xFF93000A)
val onErrorContainerDark = Color(0xFFFFDAD6)
val backgroundDark = Color(0xFF0F120E)
val onBackgroundDark = Color(0xFFDEE4D9)
val surfaceDark = Color(0xFF0F120E)
val onSurfaceDark = Color(0xFFDEE4D9)
val surfaceVariantDark = Color(0xFF3F4A3C)
val onSurfaceVariantDark = Color(0xFFBECAB9)
val outlineDark = Color(0xFF899484)
val outlineVariantDark = Color(0xFF3F4A3C)
val scrimDark = Color(0xFF000000)
val inverseSurfaceDark = Color(0xFFDEE4D9)
val inverseOnSurfaceDark = Color(0xFF2C322A)
val inversePrimaryDark = Color(0xFF006E1C)
val surfaceDimDark = Color(0xFF0F150E)
val surfaceBrightDark = Color(0xFF353B33)
val surfaceContainerLowestDark = Color(0xFF0A1009)
val surfaceContainerLowDark = Color(0xFF171D16)
val surfaceContainerDark = Color(0xFF1B211A)
val surfaceContainerHighDark = Color(0xFF262C24)
val surfaceContainerHighestDark = Color(0xFF30362E)
val amoledDark = Color(0xFF000000)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package dev.patrickgold.florisboard.app.apptheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,41 +16,51 @@
package dev.patrickgold.florisboard.app.apptheme
import android.app.Activity
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
private val AmoledDarkColorPalette = darkColors(
/*private val AmoledDarkColorPalette = darkColorScheme(
primary = Green500,
primaryVariant = Green700,
secondary = Orange700,
secondaryVariant = Orange900,
secondary = Green700,
tertiary = Orange700,
// = Orange900,
background = Color(0xFF000000),
surface = Color(0xFF212121),
)
private val DarkColorPalette = darkColors(
private val DarkColorPalette = darkColorScheme(
primary = Green500,
primaryVariant = Green700,
secondary = Orange700,
secondaryVariant = Orange900,
secondary = Green700,
tertiary = Orange700,
//secondaryVariant = Orange900,
background = Color(0xFF1F1F1F),
surface = Color(0xFF212121),
)
private val LightColorPalette = lightColors(
private val LightColorPalette = lightColorScheme(
primary = Green500,
primaryVariant = Green700,
secondary = Orange700,
secondaryVariant = Orange900,
secondary = Green700,
tertiary = Orange700,
//secondaryVariant = Orange900,
background = Color(0xFFFFFFFF),
surface = Color(0xFFE7E7E7),
@@ -63,35 +73,107 @@ private val LightColorPalette = lightColors(
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
)*/
@Composable
fun getColorScheme(
context: Context,
isMaterialYouAware: Boolean,
themeColor: Color,
theme: AppTheme,
): ColorScheme {
val isDark = isSystemInDarkTheme()
return when (theme) {
AppTheme.AUTO -> {
if (isMaterialYouAware && AndroidVersion.ATLEAST_API31_S) {
when {
isDark -> dynamicDarkColorScheme(context)
else -> dynamicLightColorScheme(context)
}
} else {
ColorMappings.getColorSchemeOrDefault(themeColor, isDark, true)
}
}
AppTheme.DARK -> {
if (isMaterialYouAware && AndroidVersion.ATLEAST_API31_S) {
dynamicDarkColorScheme(context)
} else {
ColorMappings.getColorSchemeOrDefault(themeColor, isDark = true, settings = true)
}
}
AppTheme.LIGHT -> {
if (isMaterialYouAware && AndroidVersion.ATLEAST_API31_S) {
dynamicLightColorScheme(context)
} else {
ColorMappings.getColorSchemeOrDefault(themeColor, isDark = false, settings = true)
}
}
AppTheme.AMOLED_DARK -> {
if (isMaterialYouAware && AndroidVersion.ATLEAST_API31_S) {
dynamicDarkColorScheme(context).amoled()
} else {
ColorMappings.getColorSchemeOrDefault(themeColor, isDark = true, settings = true).amoled()
}
}
AppTheme.AUTO_AMOLED -> {
if (isMaterialYouAware && AndroidVersion.ATLEAST_API31_S) {
when {
isDark -> dynamicDarkColorScheme(context).amoled()
else -> dynamicLightColorScheme(context)
}
} else {
with(ColorMappings.getColorSchemeOrDefault(themeColor, isDark, true)) {
if (isDark) amoled() else this
}
}
}
}
}
fun ColorScheme.amoled(): ColorScheme {
return this.copy(background = Color.Black, surface = Color.Black)
}
@Composable
fun FlorisAppTheme(
theme: AppTheme,
content: @Composable () -> Unit
isMaterialYouAware: Boolean,
content: @Composable () -> Unit,
) {
val colors = when (theme) {
AppTheme.AUTO -> when {
isSystemInDarkTheme() -> DarkColorPalette
else -> LightColorPalette
val prefs by florisPreferenceModel()
val accent by prefs.advanced.accentColor.observeAsState()
val colors = getColorScheme(
context = LocalContext.current,
theme = theme,
isMaterialYouAware = isMaterialYouAware,
themeColor = accent,
)
val darkTheme =
theme == AppTheme.DARK
|| theme == AppTheme.AMOLED_DARK
|| (theme == AppTheme.AUTO && isSystemInDarkTheme())
|| (theme == AppTheme.AUTO_AMOLED && isSystemInDarkTheme())
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = !darkTheme
}
AppTheme.AUTO_AMOLED -> when {
isSystemInDarkTheme() -> AmoledDarkColorPalette
else -> LightColorPalette
}
AppTheme.LIGHT -> LightColorPalette
AppTheme.DARK -> DarkColorPalette
AppTheme.AMOLED_DARK -> AmoledDarkColorPalette
}
MaterialTheme(
colors = colors,
colorScheme = colors,
typography = Typography,
shapes = Shapes,
content = content,
)
}
val Colors.outline: Color
@Composable
get() = this.onSurface.copy(alpha = ButtonDefaults.OutlinedBorderOpacity)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
package dev.patrickgold.florisboard.app.apptheme
import androidx.compose.material.Typography
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
@@ -24,7 +24,7 @@ import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
body1 = TextStyle(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,24 +21,25 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.io.subDir
import dev.patrickgold.florisboard.lib.io.subFile
import dev.patrickgold.jetpref.datastore.model.observeAsState
import java.util.*
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import java.util.Locale
@Composable
fun AndroidLocalesScreen() = FlorisScreen {
@@ -73,7 +74,7 @@ fun AndroidLocalesScreen() = FlorisScreen {
)
}
},
icon = painterResource(R.drawable.ic_save),
icon = Icons.Default.Save,
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ package dev.patrickgold.florisboard.app.devtools
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -27,7 +27,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.android.AndroidSettings
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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,15 @@
package dev.patrickgold.florisboard.app.devtools
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
@@ -40,10 +42,15 @@ import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.keyboard.CachedLayout
import dev.patrickgold.florisboard.ime.keyboard.DebugLayoutComputationResult
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.observeAsNonNullState
import dev.patrickgold.florisboard.nlpManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.AndroidVersion
import java.text.SimpleDateFormat
import java.util.*
@@ -52,26 +59,38 @@ 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 keyboardManager by context.keyboardManager()
val devtoolsEnabled by prefs.devtools.enabled.observeAsState()
val showPrimaryClip by prefs.devtools.showPrimaryClip.observeAsState()
val showInputStateOverlay by prefs.devtools.showInputStateOverlay.observeAsState()
val showSpellingOverlay by prefs.devtools.showSpellingOverlay.observeAsState()
val showInlineAutofillOverlay by prefs.devtools.showInlineAutofillOverlay.observeAsState()
val debugLayoutResult by keyboardManager.layoutManager.debugLayoutComputationResultFlow.collectAsState()
CompositionLocalProvider(
LocalContentColor provides Color.White,
LocalLayoutDirection provides LayoutDirection.Ltr,
) {
Column(modifier = modifier) {
if (showPrimaryClip) {
if (devtoolsEnabled && showPrimaryClip) {
DevtoolsClipboardOverlay()
}
if (showInputStateOverlay) {
if (devtoolsEnabled && showInputStateOverlay) {
DevtoolsInputStateOverlay()
}
if (showSpellingOverlay) {
if (debugLayoutResult?.allLayoutsSuccess() == false) {
DevtoolsLastLayoutComputationOverlay(debugLayoutResult)
}
if (devtoolsEnabled && showSpellingOverlay) {
DevtoolsSpellingOverlay()
}
if (devtoolsEnabled && showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
DevtoolsInlineAutofillOverlay()
}
}
}
}
@@ -117,6 +136,33 @@ private fun DevtoolsInputStateOverlay() {
}
}
@Composable
private fun DevtoolsLastLayoutComputationOverlay(debugLayoutResult: DebugLayoutComputationResult?) {
@Composable
fun PrintResult(result: Result<CachedLayout?>) {
if (result.isSuccess) {
DevtoolsText(text = "loaded: ${result.getOrNull()?.name}")
} else {
DevtoolsText(text = "error: ${result.exceptionOrNull()}")
}
}
DevtoolsOverlayBox(title = "Last layout computation") {
if (debugLayoutResult == null) {
DevtoolsText(text = "No layout computation result available.")
return@DevtoolsOverlayBox
}
DevtoolsSubGroup(title = "main") {
PrintResult(debugLayoutResult!!.main)
}
DevtoolsSubGroup(title = "mod") {
PrintResult(debugLayoutResult!!.mod)
}
DevtoolsSubGroup(title = "ext") {
PrintResult(debugLayoutResult!!.ext)
}
}
}
@Composable
private fun DevtoolsSpellingOverlay() {
@@ -160,6 +206,25 @@ private fun DevtoolsSpellingOverlay() {
}
}
@RequiresApi(Build.VERSION_CODES.R)
@Composable
private fun DevtoolsInlineAutofillOverlay() {
val inlineSuggestions by NlpInlineAutofill.suggestions.collectAsState()
DevtoolsOverlayBox(title = "Inline autofill overlay (${inlineSuggestions.size})") {
for (inlineSuggestion in inlineSuggestions) {
DevtoolsSubGroup(title = "NlpInlineSuggestion") {
val info = inlineSuggestion.info
DevtoolsText(text = "info.type: ${info.type}")
DevtoolsText(text = "info.source: ${info.source}")
DevtoolsText(text = "info.isPinned: ${info.isPinned}")
val view = inlineSuggestion.view
DevtoolsText(text = "view: ${view?.javaClass?.name}")
}
}
}
}
@Composable
private fun DevtoolsOverlayBox(
title: String,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,8 @@ 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 dev.patrickgold.florisboard.lib.android.AndroidSettings
import dev.patrickgold.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.AndroidSettings
import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
@@ -36,6 +36,7 @@ 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.AndroidVersion
class DebugOnPurposeCrashException : Exception(
"Success! The app crashed purposely to display this beautiful screen we all love :)"
@@ -60,12 +61,6 @@ fun DevtoolsScreen() = FlorisScreen {
)
PreferenceGroup(title = stringRes(R.string.devtools__title)) {
SwitchPreference(
prefs.devtools.showHeapMemoryStats,
title = stringRes(R.string.devtools__show_heap_memory_stats__label),
summary = stringRes(R.string.devtools__show_heap_memory_stats__summary),
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
SwitchPreference(
prefs.devtools.showPrimaryClip,
title = stringRes(R.string.devtools__show_primary_clip__label),
@@ -84,6 +79,13 @@ fun DevtoolsScreen() = FlorisScreen {
summary = stringRes(R.string.devtools__show_spelling_overlay__summary),
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
SwitchPreference(
prefs.devtools.showInlineAutofillOverlay,
title = stringRes(R.string.devtools__show_inline_autofill_overlay__label),
summary = stringRes(R.string.devtools__show_inline_autofill_overlay__summary),
enabledIf = { prefs.devtools.enabled isEqualTo true },
visibleIf = { AndroidVersion.ATLEAST_API30_R },
)
SwitchPreference(
prefs.devtools.showKeyTouchBoundaries,
title = stringRes(R.string.devtools__show_key_touch_boundaries__label),
@@ -120,6 +122,13 @@ fun DevtoolsScreen() = FlorisScreen {
onClick = { navController.navigate(Routes.Devtools.ExportDebugLog) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
SwitchPreference(
prefs.glide.enabled,
title = "prefs.glide.enabled (debug)",
summaryOn = "This impacts your performance and may trigger the all keys invisible bug!",
summaryOff = "Recommended to keep this off!",
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.devtools__group_android__title)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 Patrick Goldinger
* Copyright (C) 2022-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.
@@ -16,12 +16,14 @@
package dev.patrickgold.florisboard.app.devtools
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -35,19 +37,21 @@ import androidx.compose.ui.platform.LocalLayoutDirection
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.clipboardManager
import dev.patrickgold.florisboard.lib.android.showShortToast
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
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI and in localization
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI
@Composable
fun ExportDebugLogScreen() = FlorisScreen {
title = "Debug log"
title = stringRes(R.string.devtools__debuglog__title)
scrollable = false
val prefs by florisPreferenceModel()
@@ -55,21 +59,36 @@ fun ExportDebugLogScreen() = FlorisScreen {
val clipboardManager by context.clipboardManager()
var debugLog by remember { mutableStateOf<List<String>?>(null) }
var formattedDebugLog by remember { mutableStateOf<List<String>?>(null) }
LaunchedEffect(Unit) {
debugLog = Devtools.generateDebugLog(context, prefs, includeLogcat = true).lines()
formattedDebugLog = Devtools.generateDebugLogForGithub(context, prefs, includeLogcat = true).lines()
}
bottomBar {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
context.showShortToast("Copied debug log to clipboard")
},
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth(),
text = "Export (copy to clipboard)",
enabled = debugLog != null,
)
) {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
modifier = Modifier,
text = stringRes(R.string.devtools__debuglog__copy_log),
enabled = debugLog != null,
)
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(formattedDebugLog!!.joinToString("\n"))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
text = stringRes(R.string.devtools__debuglog__copy_for_github),
enabled = debugLog != null,
)
}
}
content {
@@ -86,7 +105,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
val log = debugLog
if (log == null) {
item {
Text("Loading...")
Text(stringRes(R.string.devtools__debuglog__loading))
}
} else {
items(log) { logLine ->

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Shop
import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.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.kotlin.curlyFormat
@Composable
fun UpdateBox(extensionIndex: List<Extension>) {
val context = LocalContext.current
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
Text(
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 4.dp),
text = stringRes(id = R.string.ext__update_box__internet_permission_hint),
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
FlorisTextButton(
onClick = {
context.launchUrl(extensionIndex.generateUpdateUrl())
},
icon = Icons.Outlined.FileDownload,
text = stringRes(id = R.string.ext__update_box__search_for_updates)
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
@Composable
fun AddonManagementReferenceBox(
type: ExtensionListScreenType
) {
val navController = LocalNavController.current
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
title = stringRes(id = R.string.ext__addon_management_box__managing_placeholder).curlyFormat(
"extensions" to type.let { stringRes(id = it.titleResId).lowercase() }
)
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
text = stringRes(id = R.string.ext__addon_management_box__addon_manager_info),
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
val route = Routes.Ext.List(type, showUpdate = true)
navController.navigate(
route
)
},
icon = Icons.Default.Shop,
text = stringRes(id = R.string.ext__addon_management_box__go_to_page).curlyFormat(
"ext_home_title" to stringRes(type.titleResId),
),
)
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import androidx.compose.runtime.Composable
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
@Composable
fun CheckUpdatesScreen() = FlorisScreen {
title = stringRes(R.string.ext__check_updates__title)
val context = LocalContext.current
val extensionManager by context.extensionManager()
val extensionIndex = extensionManager.combinedExtensionList()
content {
UpdateBox(extensionIndex)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 Patrick Goldinger
* Copyright (C) 2022-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.
@@ -21,17 +21,18 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ListItem
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -93,8 +94,8 @@ fun ExtensionComponentView(
}
Text(
text = text,
style = MaterialTheme.typography.body2,
color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current,
)
}
is LanguagePackComponent -> {
@@ -109,8 +110,8 @@ fun ExtensionComponentView(
}
Text(
text = text,
style = MaterialTheme.typography.body2,
color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current,
)
}
else -> { }
@@ -125,10 +126,10 @@ fun ExtensionComponentView(
if (onDeleteBtnClick != null) {
FlorisTextButton(
onClick = onDeleteBtnClick,
icon = painterResource(R.drawable.ic_delete),
icon = Icons.Default.Delete,
text = stringRes(R.string.action__delete),
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colors.error,
contentColor = MaterialTheme.colorScheme.error,
),
)
}
@@ -136,7 +137,7 @@ fun ExtensionComponentView(
if (onEditBtnClick != null) {
FlorisTextButton(
onClick = onEditBtnClick,
icon = painterResource(R.drawable.ic_edit),
icon = Icons.Default.Edit,
text = stringRes(R.string.action__edit),
)
}
@@ -145,7 +146,6 @@ fun ExtensionComponentView(
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T : ExtensionComponent> ExtensionComponentListView(
modifier: Modifier = Modifier,
@@ -156,19 +156,19 @@ fun <T : ExtensionComponent> ExtensionComponentListView(
) {
Column(modifier = modifier) {
ListItem(
text = { Text(
headlineContent = { Text(
text = title,
color = MaterialTheme.colors.secondary,
color = MaterialTheme.colorScheme.secondary,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) },
trailing = if (onCreateBtnClick != null) {
trailingContent = if (onCreateBtnClick != null) {
@Composable {
FlorisIconButton(
onClick = onCreateBtnClick,
icon = painterResource(R.drawable.ic_add),
iconColor = MaterialTheme.colors.secondary,
icon = Icons.Default.Add,
iconColor = MaterialTheme.colorScheme.secondary,
)
}
} else { null },

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,9 +28,14 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.LibraryBooks
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Code
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -39,11 +44,9 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.apptheme.outline
import dev.patrickgold.florisboard.app.settings.advanced.RadioListItem
import dev.patrickgold.florisboard.app.settings.theme.DialogProperty
import dev.patrickgold.florisboard.app.settings.theme.ThemeEditorScreen
@@ -56,7 +59,6 @@ import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
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.android.showLongToast
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
@@ -65,7 +67,6 @@ import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisUnsavedChangesDialog
import dev.patrickgold.florisboard.lib.compose.autoMirrorForRtl
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.Extension
@@ -81,14 +82,16 @@ import dev.patrickgold.florisboard.lib.ext.ExtensionValidation
import dev.patrickgold.florisboard.lib.ext.validate
import dev.patrickgold.florisboard.lib.io.FlorisRef
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.florisboard.lib.io.subFile
import dev.patrickgold.florisboard.lib.io.writeJson
import dev.patrickgold.florisboard.lib.rememberValidationResult
import dev.patrickgold.florisboard.lib.snygg.SnyggStylesheetJsonConfig
import org.florisboard.lib.snygg.SnyggStylesheetJsonConfig
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.vectorResource
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import java.util.*
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.kotlin.io.writeJson
import java.util.UUID
import kotlin.reflect.KClass
private val TextFieldVerticalPadding = 8.dp
@@ -295,8 +298,7 @@ private fun EditScreen(
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
modifier = Modifier.autoMirrorForRtl(),
icon = painterResource(R.drawable.ic_arrow_back),
icon = Icons.AutoMirrored.Filled.ArrowBack,
)
}
@@ -320,19 +322,19 @@ private fun EditScreen(
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
this@content.Preference(
Preference(
onClick = { workspace.currentAction = EditorAction.ManageMetaData },
iconId = R.drawable.ic_code,
icon = Icons.Default.Code,
title = stringRes(R.string.ext__editor__metadata__title),
)
this@content.Preference(
Preference(
onClick = { workspace.currentAction = EditorAction.ManageDependencies },
iconId = R.drawable.ic_library_books,
icon = Icons.AutoMirrored.Outlined.LibraryBooks,
title = stringRes(R.string.ext__editor__dependencies__title),
)
this@content.Preference(
Preference(
onClick = { workspace.currentAction = EditorAction.ManageFiles },
iconId = R.drawable.ic_file_blank,
icon = vectorResource(R.drawable.ic_file_blank),
title = stringRes(R.string.ext__editor__files__title),
)
}
@@ -452,7 +454,7 @@ private fun ManageMetaDataScreen(
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
icon = Icons.Default.Close,
)
}
@@ -554,7 +556,7 @@ private fun ManageDependenciesScreen(workspace: CacheManager.ExtEditorWorkspace<
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
icon = Icons.Default.Close,
)
}
@@ -591,7 +593,7 @@ private fun ManageFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = F
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
icon = Icons.Default.Close,
)
}
@@ -750,7 +752,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
icon = Icons.Default.Close,
)
}
@@ -858,7 +860,7 @@ private fun EditorSheetTextField(
showValidationError: Boolean = false,
validationResult: ValidationResult? = null,
) {
val borderColor = MaterialTheme.colors.outline
val borderColor = MaterialTheme.colorScheme.outline
Column(modifier = Modifier.padding(vertical = TextFieldVerticalPadding)) {
Row(
modifier = Modifier
@@ -867,14 +869,14 @@ private fun EditorSheetTextField(
) {
Text(
text = label,
style = MaterialTheme.typography.subtitle2,
style = MaterialTheme.typography.titleSmall,
)
if (isRequired) {
Text(
modifier = Modifier.padding(start = 2.dp),
text = "*",
style = MaterialTheme.typography.subtitle2,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.error,
)
}
}
@@ -886,7 +888,7 @@ private fun EditorSheetTextField(
singleLine = singleLine,
showValidationError = showValidationError,
validationResult = validationResult,
colors = TextFieldDefaults.outlinedTextFieldColors(
colors = OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = borderColor,
disabledBorderColor = borderColor,
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.lib.android.showLongToast
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

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Input
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Shop
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.util.launchUrl
import dev.patrickgold.jetpref.datastore.ui.Preference
@Composable
fun ExtensionHomeScreen() = FlorisScreen {
title = stringRes(R.string.ext__home__title)
previewFieldVisible = false
val context = LocalContext.current
val navController = LocalNavController.current
val extensionManager by context.extensionManager()
val extensionIndex = extensionManager.combinedExtensionList()
content {
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
Text(
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 4.dp),
text = stringRes(id = R.string.ext__home__info),
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
FlorisTextButton(
onClick = {
context.launchUrl("https://${BuildConfig.FLADDONS_STORE_URL}/")
},
icon = Icons.Default.Shop,
text = stringRes(id = R.string.ext__home__visit_store),
)
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, null))
},
icon = Icons.AutoMirrored.Filled.Input,
text = stringRes(R.string.action__import),
)
}
}
UpdateBox(extensionIndex = extensionIndex)
Preference(
icon = Icons.Default.Palette,
title = stringRes(R.string.ext__list__ext_theme),
onClick = {
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_THEME, false))
},
)
Preference(
icon = Icons.Default.Keyboard,
title = stringRes(R.string.ext__list__ext_keyboard),
onClick = {
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_KEYBOARD, false))
},
)
Preference(
icon = Icons.Default.Language,
title = stringRes(R.string.ext__list__ext_languagepack),
onClick = {
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_LANGUAGEPACK, false))
},
)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,14 +29,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -51,7 +50,7 @@ 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 dev.patrickgold.florisboard.lib.android.showLongToast
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
@@ -61,9 +60,8 @@ 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.devtools.flogDebug
import dev.patrickgold.florisboard.lib.io.FileRegistry
import dev.patrickgold.florisboard.lib.kotlin.resultOk
import org.florisboard.lib.kotlin.resultOk
enum class ExtensionImportScreenType(
val id: String,
@@ -101,12 +99,6 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
val cacheManager by context.cacheManager()
val extensionManager by context.extensionManager()
val initWsUuid by rememberSaveable { mutableStateOf(initUuid) }
var importResult by remember {
val workspace = initWsUuid?.let { cacheManager.importer.getWorkspaceByUuid(it) }?.let { resultOk(it) }
mutableStateOf(workspace)
}
fun getSkipReason(fileInfo: CacheManager.FileInfo): Int {
return when {
!FileRegistry.matchesFileFilter(fileInfo, type.supportedFiles) -> {
@@ -120,29 +112,37 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
NATIVE_NULLPTR.toInt()
}
}
fileInfo.mediaType == FileRegistry.FlexExtension.mediaType -> {
else -> { // ext == null
R.string.ext__import__file_skip_ext_corrupted
}
else -> {
NATIVE_NULLPTR.toInt()
}
}
}
fun Result<CacheManager.ImporterWorkspace>.mapSkipReasons(): Result<CacheManager.ImporterWorkspace> {
return this.map { workspace ->
workspace.inputFileInfos.forEach { fileInfo ->
fileInfo.skipReason = getSkipReason(fileInfo)
}
workspace
}
}
var importResult by remember(initUuid) {
val workspace = initUuid?.let { cacheManager.importer.getWorkspaceByUuid(it) }
?.let { resultOk(it) }
?.mapSkipReasons()
mutableStateOf(workspace)
}
val importLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetMultipleContents(),
onResult = { uriList ->
// If uri is null it indicates that the selection activity
// was cancelled (mostly by pressing the back button), so
// we don't display an error message here.
if (uriList.isNullOrEmpty()) return@rememberLauncherForActivityResult
if (uriList.isEmpty()) return@rememberLauncherForActivityResult
importResult?.getOrNull()?.close()
importResult = runCatching { cacheManager.readFromUriIntoCache(uriList) }.map { workspace ->
workspace.inputFileInfos.forEach { fileInfo ->
fileInfo.skipReason = getSkipReason(fileInfo)
}
workspace
}
importResult = runCatching { cacheManager.readFromUriIntoCache(uriList) }.mapSkipReasons()
},
)
@@ -198,15 +198,17 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
}
content {
FlorisOutlinedButton(
onClick = {
importLauncher.launch("*/*")
},
modifier = Modifier
.padding(vertical = 16.dp)
.align(Alignment.CenterHorizontally),
text = stringRes(R.string.action__select_files),
)
if (initUuid == null) {
FlorisOutlinedButton(
onClick = {
importLauncher.launch("*/*")
},
modifier = Modifier
.padding(vertical = 16.dp)
.align(Alignment.CenterHorizontally),
text = stringRes(R.string.action__select_files),
)
}
val result = importResult
when {
@@ -229,8 +231,8 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__import__error_unexpected_exception),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
SelectionContainer {
Text(
@@ -238,8 +240,8 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
.florisHorizontalScroll()
.padding(horizontal = 16.dp),
text = result.exceptionOrNull()?.stackTraceToString() ?: "null",
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
fontStyle = FontStyle.Italic,
)
}
@@ -267,20 +269,20 @@ private fun FileInfoView(
Row {
Text(
text = Formatter.formatShortFileSize(LocalContext.current, fileInfo.size),
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
color = grayColor,
)
if (ext != null) {
FlorisBulletSpacer()
Text(
text = ext.meta.id,
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
color = grayColor,
)
FlorisBulletSpacer()
Text(
text = ext.meta.version,
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
color = grayColor,
)
}
@@ -289,12 +291,12 @@ private fun FileInfoView(
Spacer(modifier = Modifier.height(8.dp))
Text(
text = ext.meta.title,
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
)
ext.meta.description?.let { description ->
Text(
text = description,
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
fontStyle = FontStyle.Italic,
)
}
@@ -304,13 +306,13 @@ private fun FileInfoView(
}
Text(
text = stringRes(R.string.ext__meta__maintainers_by, "maintainers" to maintainers),
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
)
Spacer(modifier = Modifier.height(8.dp))
for (component in ext.components()) {
Text(
text = component.id,
style = MaterialTheme.typography.body2,
style = MaterialTheme.typography.bodyMedium,
)
}
}
@@ -319,16 +321,16 @@ private fun FileInfoView(
.fillMaxWidth()
.height(19.dp)
.padding(top = 10.dp, bottom = 8.dp)
.background(MaterialTheme.colors.error.copy(alpha = 0.56f)))
.background(MaterialTheme.colorScheme.error.copy(alpha = 0.56f)))
Text(
text = stringRes(R.string.ext__import__file_skip),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
Text(
text = stringRes(fileInfo.skipReason),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
fontStyle = FontStyle.Italic,
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,11 @@
package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.lib.compose.FlorisChip
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ExtensionKeywordChip(
keyword: String,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2024-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.
@@ -16,84 +16,166 @@
package dev.patrickgold.florisboard.app.ext
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.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
enum class ExtensionListScreenType(
val id: String,
@StringRes val titleResId: Int,
val getExtensionIndex: (ExtensionManager) -> ExtensionManager.ExtensionIndex<*>,
val launchExtensionCreate: ((NavController) -> Unit)?,
) {
EXT_THEME(
id = "ext-theme",
titleResId = R.string.ext__list__ext_theme,
getExtensionIndex = { it.themes },
launchExtensionCreate = { it.navigate(Routes.Ext.Edit("null", ThemeExtension.SERIAL_TYPE)) },
),
EXT_KEYBOARD(
id = "ext-keyboard",
titleResId = R.string.ext__list__ext_keyboard,
getExtensionIndex = { it.keyboardExtensions },
launchExtensionCreate = null,//{ it.navigate(Routes.Ext.Edit("null", KeyboardExtension.SERIAL_TYPE)) },
),
EXT_LANGUAGEPACK(
id = "ext-languagepack",
titleResId = R.string.ext__list__ext_languagepack,
getExtensionIndex = { it.languagePacks },
launchExtensionCreate = null,//{ it.navigate(Routes.Ext.Edit("null", LanguagePackExtension.SERIAL_TYPE)) },
);
}
@Composable
fun ExtensionListScreen() = FlorisScreen {
title = stringRes(R.string.about__title)
fun ExtensionListScreen(type: ExtensionListScreenType, showUpdate: Boolean) = FlorisScreen {
title = stringRes(type.titleResId)
previewFieldVisible = false
scrollable = false
/*val navController = LocalNavController.current
val context = LocalContext.current
val extensionManager = ExtensionManager.def
val navController = LocalNavController.current
val extensionManager by context.extensionManager()
val extensionIndex by type.getExtensionIndex(extensionManager).observeAsNonNullState()
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp, bottom = 32.dp)
) {
FlorisAppIcon()
Text(
text = stringRes(R.string.floris_app_name),
fontSize = 24.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 16.dp),
)
var fabHeight by remember {
mutableStateOf(0)
}
Preference(
iconId = R.drawable.ic_info,
title = stringRes(R.string.about__version__title),
summary = appVersion,
onClick = {
try {
val isImeSelected = InputMethodUtils.checkIsFlorisboardSelected(context)
if (isImeSelected) {
FlorisClipboardManager.getInstance().addNewPlaintext(appVersion)
} else {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Florisboard version", appVersion)
clipboard.setPrimaryClip(clip)
val fabHeightDp = with(LocalDensity.current) { fabHeight.toDp()+16.dp }
val listState = rememberLazyListState()
content {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.florisScrollbar(state = listState, isVertical = true),
state = listState,
contentPadding = PaddingValues(bottom = fabHeightDp),
) {
if (showUpdate) {
item {
UpdateBox(extensionIndex = extensionIndex)
}
Toast.makeText(context, R.string.about__version_copied__title, Toast.LENGTH_SHORT).show()
} catch (e: Throwable) {
Toast.makeText(
context, context.getString(R.string.about__version_copied__error, e.message), Toast.LENGTH_SHORT
).show()
}
},
)
Preference(
iconId = R.drawable.ic_history,
title = stringRes(R.string.about__changelog__title),
summary = stringRes(R.string.about__changelog__summary),
onClick = { launchUrl(context, R.string.florisboard__changelog_url, arrayOf(BuildConfig.VERSION_NAME)) },
)
Preference(
iconId = R.drawable.ic_code,
title = stringRes(R.string.about__repository__title),
summary = stringRes(R.string.about__repository__summary),
onClick = { launchUrl(context, R.string.florisboard__repo_url) },
)
Preference(
iconId = R.drawable.ic_policy,
title = stringRes(R.string.about__privacy_policy__title),
summary = stringRes(R.string.about__privacy_policy__summary),
onClick = { launchUrl(context, R.string.florisboard__privacy_policy_url) },
)
Preference(
iconId = R.drawable.ic_description,
title = stringRes(R.string.about__project_license__title),
summary = stringRes(R.string.about__project_license__summary, "license_name" to "Apache 2.0"),
onClick = { navController.navigate(Routes.Settings.ProjectLicense) },
)
Preference(
iconId = R.drawable.ic_description,
title = stringRes(id = R.string.about__third_party_licenses__title),
summary = stringRes(id = R.string.about__third_party_licenses__summary),
onClick = { navController.navigate(Routes.Settings.ThirdPartyLicenses) },
)*/
items(extensionIndex) { ext ->
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
title = ext.meta.title,
subtitle = ext.meta.id,
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
text = ext.meta.description ?: "",
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.View(ext.meta.id))
},
icon = Icons.Outlined.Info,
text = stringRes(id = R.string.ext__list__view_details),//stringRes(R.string.action__add),
colors = ButtonDefaults.textButtonColors(),
)
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.Edit(ext.meta.id))
},
icon = Icons.Default.Edit,
text = stringRes(R.string.action__edit),
enabled = extensionManager.canDelete(ext),
)
}
}
}
}
}
if (type.launchExtensionCreate != null) {
floatingActionButton {
ExtendedFloatingActionButton(
icon = {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringRes(id = R.string.ext__editor__title_create_any),
)
},
text = {
Text(
text = stringRes(id = R.string.ext__editor__title_create_any),
)
},
modifier = Modifier.onGloballyPositioned {
fabHeight = it.size.height
},
shape = FloatingActionButtonDefaults.extendedFabShape,
onClick = { type.launchExtensionCreate.invoke(navController) },
)
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,9 @@ package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.outlined.Mail
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -28,13 +30,11 @@ 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.R
import dev.patrickgold.florisboard.lib.android.launchUrl
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
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ExtensionMaintainerChip(
maintainer: ExtensionMaintainer,
@@ -48,11 +48,11 @@ fun ExtensionMaintainerChip(
text = maintainer.name,
trailingIcons = when {
maintainer.email != null && maintainer.url != null -> listOf(
R.drawable.ic_email,
R.drawable.ic_link,
Icons.Outlined.Mail,
Icons.Default.Link,
)
maintainer.email != null -> listOf(R.drawable.ic_email)
maintainer.url != null -> listOf(R.drawable.ic_link)
maintainer.email != null -> listOf(Icons.Outlined.Mail)
maintainer.url != null -> listOf(Icons.Default.Link)
else -> listOf()
},
onClick = { showDialog = !showDialog },
@@ -70,7 +70,7 @@ fun ExtensionMaintainerChip(
FlorisChip(
onClick = { context.launchUrl("mailto:${maintainer.email}") },
text = maintainer.email.toString(),
leadingIcons = listOf(R.drawable.ic_email),
leadingIcons = listOf(Icons.Outlined.Mail),
shape = RoundedCornerShape(4.dp),
)
}
@@ -78,7 +78,7 @@ fun ExtensionMaintainerChip(
FlorisChip(
onClick = { context.launchUrl(maintainer.url.toString()) },
text = maintainer.url.toString(),
leadingIcons = listOf(R.drawable.ic_link),
leadingIcons = listOf(Icons.Default.Link),
shape = RoundedCornerShape(4.dp),
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,10 +27,13 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -39,7 +42,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
@@ -49,7 +51,7 @@ 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 dev.patrickgold.florisboard.lib.android.showLongToast
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
@@ -144,10 +146,10 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
onClick = {
extToDelete = ext
},
icon = painterResource(R.drawable.ic_delete),
icon = Icons.Default.Delete,
text = stringRes(R.string.action__delete),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colors.error,
contentColor = MaterialTheme.colorScheme.error,
),
)
}
@@ -156,7 +158,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
onClick = {
navController.navigate(Routes.Ext.Export(ext.meta.id))
},
icon = painterResource(R.drawable.ic_share),
icon = Icons.Default.Share,
text = stringRes(R.string.action__export),
)
}
@@ -222,7 +224,7 @@ private fun ExtensionMetaRowSimpleText(
content: @Composable RowScope.() -> Unit,
) {
if (showDividerAbove) {
Divider()
HorizontalDivider()
}
Row(
modifier = modifier
@@ -244,7 +246,7 @@ private fun ExtensionMetaRowScrollableChips(
content: @Composable RowScope.() -> Unit,
) {
if (showDividerAbove) {
Divider()
HorizontalDivider()
}
Row(
modifier = modifier.fillMaxWidth(),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,25 +16,23 @@
package dev.patrickgold.florisboard.app.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Assignment
import androidx.compose.material.icons.filled.Extension
import androidx.compose.material.icons.filled.Gesture
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.SentimentSatisfiedAlt
import androidx.compose.material.icons.filled.SmartButton
import androidx.compose.material.icons.filled.Spellcheck
import androidx.compose.material.icons.outlined.Build
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Keyboard
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
@@ -108,62 +106,57 @@ fun HomeScreen() = FlorisScreen {
}
}*/
Preference(
iconId = R.drawable.ic_language,
icon = Icons.Default.Language,
title = stringRes(R.string.settings__localization__title),
onClick = { navController.navigate(Routes.Settings.Localization) },
)
Preference(
iconId = R.drawable.ic_palette,
icon = Icons.Outlined.Palette,
title = stringRes(R.string.settings__theme__title),
onClick = { navController.navigate(Routes.Settings.Theme) },
)
Preference(
iconId = R.drawable.ic_keyboard,
icon = Icons.Outlined.Keyboard,
title = stringRes(R.string.settings__keyboard__title),
onClick = { navController.navigate(Routes.Settings.Keyboard) },
)
Preference(
iconId = R.drawable.ic_smartbar,
icon = Icons.Default.SmartButton,
title = stringRes(R.string.settings__smartbar__title),
onClick = { navController.navigate(Routes.Settings.Smartbar) },
)
Preference(
iconId = R.drawable.ic_spellcheck,
icon = Icons.Default.Spellcheck,
title = stringRes(R.string.settings__typing__title),
onClick = { navController.navigate(Routes.Settings.Typing) },
)
Preference(
iconId = R.drawable.ic_library_books,
title = stringRes(R.string.settings__dictionary__title),
onClick = { navController.navigate(Routes.Settings.Dictionary) },
)
Preference(
iconId = R.drawable.ic_gesture,
icon = Icons.Default.Gesture,
title = stringRes(R.string.settings__gestures__title),
onClick = { navController.navigate(Routes.Settings.Gestures) },
)
Preference(
iconId = R.drawable.ic_assignment,
icon = Icons.AutoMirrored.Outlined.Assignment,
title = stringRes(R.string.settings__clipboard__title),
onClick = { navController.navigate(Routes.Settings.Clipboard) },
)
Preference(
iconId = R.drawable.ic_sentiment_satisfied,
icon = Icons.Default.SentimentSatisfiedAlt,
title = stringRes(R.string.settings__media__title),
onClick = { navController.navigate(Routes.Settings.Media) },
)
Preference(
iconId = R.drawable.ic_adb,
title = stringRes(R.string.devtools__title),
onClick = { navController.navigate(Routes.Devtools.Home) },
icon = Icons.Default.Extension,
title = stringRes(R.string.ext__home__title),
onClick = { navController.navigate(Routes.Ext.Home) },
)
Preference(
iconId = R.drawable.ic_build,
icon = Icons.Outlined.Build,
title = stringRes(R.string.settings__advanced__title),
onClick = { navController.navigate(Routes.Settings.Advanced) },
)
Preference(
iconId = R.drawable.ic_info,
icon = Icons.Outlined.Info,
title = stringRes(R.string.about__title),
onClick = { navController.navigate(Routes.Settings.About) },
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Policy
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -35,8 +41,8 @@ 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.android.launchUrl
import dev.patrickgold.florisboard.lib.android.stringRes
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
@@ -73,7 +79,7 @@ fun AboutScreen() = FlorisScreen {
)
}
Preference(
iconId = R.drawable.ic_info,
icon = Icons.Outlined.Info,
title = stringRes(R.string.about__version__title),
summary = appVersion,
onClick = {
@@ -90,31 +96,31 @@ fun AboutScreen() = FlorisScreen {
},
)
Preference(
iconId = R.drawable.ic_history,
icon = Icons.Default.History,
title = stringRes(R.string.about__changelog__title),
summary = stringRes(R.string.about__changelog__summary),
onClick = { context.launchUrl(R.string.florisboard__changelog_url, "version" to BuildConfig.VERSION_NAME) },
)
Preference(
iconId = R.drawable.ic_code,
icon = Icons.Default.Code,
title = stringRes(R.string.about__repository__title),
summary = stringRes(R.string.about__repository__summary),
onClick = { context.launchUrl(R.string.florisboard__repo_url) },
)
Preference(
iconId = R.drawable.ic_policy,
icon = Icons.Outlined.Policy,
title = stringRes(R.string.about__privacy_policy__title),
summary = stringRes(R.string.about__privacy_policy__summary),
onClick = { context.launchUrl(R.string.florisboard__privacy_policy_url) },
)
Preference(
iconId = R.drawable.ic_description,
icon = Icons.Outlined.Description,
title = stringRes(R.string.about__project_license__title),
summary = stringRes(R.string.about__project_license__summary, "license_name" to "Apache 2.0"),
onClick = { navController.navigate(Routes.Settings.ProjectLicense) },
)
Preference(
iconId = R.drawable.ic_description,
icon = Icons.Outlined.Description,
title = stringRes(id = R.string.about__third_party_licenses__title),
summary = stringRes(id = R.string.about__third_party_licenses__summary),
onClick = { navController.navigate(Routes.Settings.ThirdPartyLicenses) },

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@ package dev.patrickgold.florisboard.app.settings.about
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
@@ -28,12 +28,12 @@ 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.assetManager
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
@Composable
fun ProjectLicenseScreen() = FlorisScreen {
@@ -41,7 +41,6 @@ fun ProjectLicenseScreen() = FlorisScreen {
scrollable = false
val context = LocalContext.current
val assetManager by context.assetManager()
content {
// Forcing LTR because the Apache 2.0 License shipped and displayed
@@ -54,8 +53,8 @@ fun ProjectLicenseScreen() = FlorisScreen {
.florisVerticalScroll()
.florisHorizontalScroll(),
) {
val licenseText = assetManager.loadTextAsset(
FlorisRef.assets("license/project_license.txt")
val licenseText = FlorisRef.assets("license/project_license.txt").loadTextAsset(
context
).getOrElse {
stringRes(R.string.about__project_license__error_license_text_failed, "error_message" to (it.message ?: ""))
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,11 @@ package dev.patrickgold.florisboard.app.settings.about
import androidx.compose.foundation.layout.fillMaxSize
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.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.m3.LibraryDefaults
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
@@ -39,6 +41,13 @@ fun ThirdPartyLicensesScreen() = FlorisScreen {
modifier = Modifier
.fillMaxSize()
.florisScrollbar(lazyListState, isVertical = true),
colors = LibraryDefaults.libraryColors(
backgroundColor = MaterialTheme.colorScheme.background,
badgeBackgroundColor = MaterialTheme.colorScheme.primaryContainer,
badgeContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
contentColor = MaterialTheme.colorScheme.onBackground,
dialogConfirmButtonColor = MaterialTheme.colorScheme.primary,
),
lazyListState = lazyListState,
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,24 +16,40 @@
package dev.patrickgold.florisboard.app.settings.advanced
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Adb
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.FormatColorFill
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Preview
import androidx.compose.material.icons.filled.SettingsBackupRestore
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.android.AndroidVersion
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.ColorPickerPreference
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
import dev.patrickgold.jetpref.datastore.ui.vectorResource
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
@Composable
fun AdvancedScreen() = FlorisScreen {
@@ -41,38 +57,34 @@ fun AdvancedScreen() = FlorisScreen {
previewFieldVisible = false
val navController = LocalNavController.current
val context = LocalContext.current
content {
ListPreference(
prefs.advanced.settingsTheme,
iconId = R.drawable.ic_palette,
icon = Icons.Default.Palette,
title = stringRes(R.string.pref__advanced__settings_theme__label),
entries = listPrefEntries {
entry(
key = AppTheme.AUTO,
label = stringRes(R.string.settings__system_default),
)
entry(
key = AppTheme.AUTO_AMOLED,
label = stringRes(R.string.pref__advanced__settings_theme__auto_amoled),
)
entry(
key = AppTheme.LIGHT,
label = stringRes(R.string.pref__advanced__settings_theme__light),
)
entry(
key = AppTheme.DARK,
label = stringRes(R.string.pref__advanced__settings_theme__dark),
)
entry(
key = AppTheme.AMOLED_DARK,
label = stringRes(R.string.pref__advanced__settings_theme__amoled_dark),
)
},
entries = enumDisplayEntriesOf(AppTheme::class),
)
ColorPickerPreference(
pref = prefs.advanced.accentColor,
title = stringRes(R.string.pref__advanced__settings_accent_color__label),
defaultValueLabel = stringRes(R.string.action__default),
icon = Icons.Default.FormatColorFill,
defaultColors = ColorMappings.colors,
showAlphaSlider = false,
enableAdvancedLayout = false,
colorOverride = {
if (it.isMaterialYou(context)) {
Color.Unspecified
} else {
it
}
}
)
ListPreference(
prefs.advanced.settingsLanguage,
iconId = R.drawable.ic_language,
icon = Icons.Default.Language,
title = stringRes(R.string.pref__advanced__settings_language__label),
entries = listPrefEntries {
listOf(
@@ -136,7 +148,7 @@ fun AdvancedScreen() = FlorisScreen {
)
SwitchPreference(
prefs.advanced.showAppIcon,
iconId = R.drawable.ic_preview,
icon = Icons.Default.Preview,
title = stringRes(R.string.pref__advanced__show_app_icon__label),
summary = when {
AndroidVersion.ATLEAST_API29_Q -> stringRes(R.string.pref__advanced__show_app_icon__summary_atleast_q)
@@ -146,21 +158,26 @@ fun AdvancedScreen() = FlorisScreen {
)
ListPreference(
prefs.advanced.incognitoMode,
iconId = R.drawable.ic_incognito,
icon = vectorResource(id = R.drawable.ic_incognito),
title = stringRes(R.string.pref__advanced__incognito_mode__label),
entries = IncognitoMode.listEntries(),
entries = enumDisplayEntriesOf(IncognitoMode::class),
)
Preference(
icon = Icons.Default.Adb,
title = stringRes(R.string.devtools__title),
onClick = { navController.navigate(Routes.Devtools.Home) },
)
PreferenceGroup(title = stringRes(R.string.backup_and_restore__title)) {
Preference(
onClick = { navController.navigate(Routes.Settings.Backup) },
iconId = R.drawable.ic_archive,
icon = Icons.Default.Archive,
title = stringRes(R.string.backup_and_restore__back_up__title),
summary = stringRes(R.string.backup_and_restore__back_up__summary),
)
Preference(
onClick = { navController.navigate(Routes.Settings.Restore) },
iconId = R.drawable.ic_settings_backup_restore,
icon = Icons.Default.SettingsBackupRestore,
title = stringRes(R.string.backup_and_restore__restore__title),
summary = stringRes(R.string.backup_and_restore__restore__summary),
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,26 +16,36 @@
package dev.patrickgold.florisboard.app.settings.advanced
import android.content.ContentUris
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material.Checkbox
import androidx.compose.material.RadioButton
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Checkbox
import androidx.compose.material3.RadioButton
import androidx.compose.material3.TriStateCheckbox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
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.LocalNavController
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.android.writeFromFile
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
@@ -47,17 +57,22 @@ 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.florisboard.lib.io.subDir
import dev.patrickgold.florisboard.lib.io.subFile
import dev.patrickgold.florisboard.lib.io.writeJson
import dev.patrickgold.jetpref.datastore.jetprefDatastoreDir
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.writeFromFile
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.kotlin.io.writeJson
object Backup {
const val FILE_PROVIDER_AUTHORITY = "${BuildConfig.APPLICATION_ID}.provider.file"
const val METADATA_JSON_NAME = "backup_metadata.json"
const val CLIPBOARD_TEXT_ITEMS_JSON_NAME = "clipboard_text_items.json"
const val CLIPBOARD_IMAGES_JSON_NAME = "clipboard_images.json"
const val CLIPBOARD_VIDEO_JSON_NAME = "clipboard_video.json"
fun defaultFileName(metadata: Metadata): String {
return "backup_${metadata.packageName}_${metadata.versionCode}_${metadata.timestamp}.zip"
@@ -72,9 +87,34 @@ object Backup {
var jetprefDatastore by mutableStateOf(true)
var imeKeyboard by mutableStateOf(true)
var imeTheme by mutableStateOf(true)
var clipboardTextItems by mutableStateOf(false)
var clipboardImageItems by mutableStateOf(false)
var clipboardVideoItems by mutableStateOf(false)
private var _clipboardData: MutableState<ToggleableState> = mutableStateOf(ToggleableState.Off)
val clipboardData: State<ToggleableState> = _clipboardData
fun updateCheckboxState() {
val newValue = if (
!clipboardVideoItems && !clipboardImageItems && !clipboardTextItems
) {
ToggleableState.Off
} else if (
clipboardVideoItems && clipboardImageItems && clipboardTextItems
) {
ToggleableState.On
} else {
ToggleableState.Indeterminate
}
_clipboardData.value = newValue
}
fun provideClipboardItems(): Boolean {
return clipboardTextItems || clipboardImageItems || clipboardVideoItems
}
fun atLeastOneSelected(): Boolean {
return jetprefDatastore || imeKeyboard || imeTheme
return jetprefDatastore || imeKeyboard || imeTheme || clipboardTextItems || clipboardImageItems || clipboardVideoItems
}
}
@@ -102,7 +142,7 @@ fun BackupScreen() = FlorisScreen {
var backupWorkspace: CacheManager.BackupAndRestoreWorkspace? = null
val backUpToFileSystemLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(),
contract = ActivityResultContracts.CreateDocument("application/zip"),
onResult = { uri ->
if (uri == null) {
// User can modify checkboxes between cancellation and second
@@ -143,6 +183,36 @@ fun BackupScreen() = FlorisScreen {
dir.copyRecursively(workspaceFilesDir.subDir(ExtensionManager.IME_THEME_PATH))
}
}
if (backupFilesSelector.provideClipboardItems()) {
val clipboardHistory = context.clipboardManager().value.history().all
val clipboardFilesDir = workspace.inputDir.subDir("clipboard")
clipboardFilesDir.mkdir()
if (backupFilesSelector.clipboardTextItems) {
clipboardFilesDir.subFile(Backup.CLIPBOARD_TEXT_ITEMS_JSON_NAME)
.writeJson(clipboardHistory.filter { it.type == ItemType.TEXT })
}
if (backupFilesSelector.clipboardImageItems) {
clipboardFilesDir.subFile(Backup.CLIPBOARD_IMAGES_JSON_NAME)
.writeJson(clipboardHistory.filter { it.type == ItemType.IMAGE })
for (item in clipboardHistory.filter { it.type == ItemType.IMAGE }) {
val id = ContentUris.parseId(item.uri!!)
ClipboardFileStorage.getFileForId(context, id).copyTo(
clipboardFilesDir.subFile("${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/$id")
)
}
}
if (backupFilesSelector.clipboardVideoItems) {
clipboardFilesDir.subFile(Backup.CLIPBOARD_VIDEO_JSON_NAME)
.writeJson(clipboardHistory.filter { it.type == ItemType.VIDEO })
for (item in clipboardHistory.filter { it.type == ItemType.VIDEO }) {
val id = ContentUris.parseId(item.uri!!)
ClipboardFileStorage.getFileForId(context, id).copyTo(
clipboardFilesDir.subFile("${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/$id")
)
}
}
}
workspace.metadata = Backup.Metadata(
packageName = BuildConfig.APPLICATION_ID,
versionCode = BuildConfig.VERSION_CODE,
@@ -164,8 +234,10 @@ fun BackupScreen() = FlorisScreen {
Backup.Destination.FILE_SYS -> {
backUpToFileSystemLauncher.launch(backupWorkspace!!.zipFile.name)
}
Backup.Destination.SHARE_INTENT -> {
val uri = FileProvider.getUriForFile(context, Backup.FILE_PROVIDER_AUTHORITY, backupWorkspace!!.zipFile)
val uri =
FileProvider.getUriForFile(context, Backup.FILE_PROVIDER_AUTHORITY, backupWorkspace!!.zipFile)
val shareIntent = ShareCompat.IntentBuilder(context)
.setStream(uri)
.setType(FileRegistry.BackupArchive.mediaType)
@@ -253,6 +325,56 @@ internal fun BackupFilesSelector(
checked = filesSelector.imeTheme,
text = stringRes(R.string.backup_and_restore__back_up__files_ime_theme),
)
TriStateCheckboxListItem(
onClick = {
if (
filesSelector.clipboardData.value == ToggleableState.Off ||
filesSelector.clipboardData.value == ToggleableState.Indeterminate
) {
filesSelector.clipboardImageItems = true
filesSelector.clipboardVideoItems = true
filesSelector.clipboardTextItems = true
} else {
filesSelector.clipboardImageItems = false
filesSelector.clipboardVideoItems = false
filesSelector.clipboardTextItems = false
}
filesSelector.updateCheckboxState()
},
state = filesSelector.clipboardData.value,
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history),
)
CheckboxListItem(
onClick = {
filesSelector.clipboardTextItems = !filesSelector.clipboardTextItems
filesSelector.updateCheckboxState()
},
checked = filesSelector.clipboardTextItems,
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history__clipboard_text_items),
isSecondaryListItem = true,
)
CheckboxListItem(
onClick = {
filesSelector.clipboardImageItems = !filesSelector.clipboardImageItems
filesSelector.updateCheckboxState()
},
checked = filesSelector.clipboardImageItems,
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history__clipboard_image_items),
isSecondaryListItem = true,
)
CheckboxListItem(
onClick = {
filesSelector.clipboardVideoItems = !filesSelector.clipboardVideoItems
filesSelector.updateCheckboxState()
},
checked = filesSelector.clipboardVideoItems,
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history__clipboard_video_items),
isSecondaryListItem = true,
)
}
}
@@ -261,14 +383,44 @@ internal fun CheckboxListItem(
onClick: () -> Unit,
checked: Boolean,
text: String,
isSecondaryListItem: Boolean = false
) {
JetPrefListItem(
modifier = Modifier.rippleClickable(onClick = onClick),
icon = {
Checkbox(
checked = checked,
onCheckedChange = null,
)
Row {
if (isSecondaryListItem) {
Spacer(modifier = Modifier.width(40.dp))
}
Checkbox(
checked = checked,
onCheckedChange = null,
)
}
},
text = text,
)
}
@Composable
internal fun TriStateCheckboxListItem(
onClick: () -> Unit,
state: ToggleableState,
text: String,
isSecondaryListItem: Boolean = false,
) {
JetPrefListItem(
modifier = Modifier.rippleClickable(onClick = onClick),
icon = {
Row {
if (isSecondaryListItem) {
Spacer(modifier = Modifier.width(40.dp))
}
TriStateCheckbox(
state = state,
onClick = null,
)
}
},
text = text,
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -43,11 +46,13 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.lib.android.readToFile
import dev.patrickgold.florisboard.lib.android.showLongToast
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.CardDefaults
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
@@ -55,15 +60,17 @@ 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.florisboard.lib.io.deleteContentsRecursively
import dev.patrickgold.florisboard.lib.io.readJson
import dev.patrickgold.florisboard.lib.io.subDir
import dev.patrickgold.florisboard.lib.io.subFile
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.ui.Preference
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.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.*
@@ -131,7 +138,10 @@ fun RestoreScreen() = FlorisScreen {
}
restoreWorkspace = workspace
}.onFailure { error ->
context.showLongToast(R.string.backup_and_restore__restore__failure, "error_message" to error.localizedMessage)
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)
}
},
)
@@ -169,6 +179,61 @@ fun RestoreScreen() = FlorisScreen {
srcDir.copyRecursively(dstDir, overwrite = true)
}
}
val clipboardManager = context.clipboardManager().value
if (shouldReset) {
clipboardManager.clearFullHistory()
ClipboardFileStorage.resetClipboardFileStorage(context)
}
if (restoreFilesSelector.provideClipboardItems()) {
val clipboardFilesDir = workspace.outputDir.subDir("clipboard")
if (restoreFilesSelector.clipboardTextItems) {
val clipboardItems = clipboardFilesDir.subFile(Backup.CLIPBOARD_TEXT_ITEMS_JSON_NAME)
if (clipboardItems.exists()) {
val clipboardItemsList = clipboardItems.readJson<List<ClipboardItem>>()
clipboardManager.restoreHistory(items = clipboardItemsList.filter { it.type == ItemType.TEXT })
}
}
if (restoreFilesSelector.clipboardImageItems) {
val clipboardItems = clipboardFilesDir.subFile(Backup.CLIPBOARD_IMAGES_JSON_NAME)
if (clipboardItems.exists()) {
val clipboardItemsList = clipboardItems.readJson<List<ClipboardItem>>()
for (item in clipboardItemsList.filter { it.type == ItemType.IMAGE }) {
ClipboardFileStorage.insertFileFromBackupIfNotExisting(
context,
clipboardFilesDir.subFile(
relPath = "${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/${
item.uri!!.path!!.split(
'/'
).last()
}"
)
)
}
clipboardManager.restoreHistory(items = clipboardItemsList.filter { it.type == ItemType.IMAGE })
}
}
if (restoreFilesSelector.clipboardVideoItems) {
val clipboardItems = clipboardFilesDir.subFile(Backup.CLIPBOARD_VIDEO_JSON_NAME)
if (clipboardItems.exists()) {
val clipboardItemsList = clipboardItems.readJson<List<ClipboardItem>>()
for (item in clipboardItemsList.filter { it.type == ItemType.VIDEO }) {
ClipboardFileStorage.insertFileFromBackupIfNotExisting(
context,
clipboardFilesDir.subFile(
relPath = "${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/${
item.uri!!.path!!.split(
'/'
).last()
}"
)
)
}
clipboardManager.restoreHistory(items = clipboardItemsList.filter { it.type == ItemType.VIDEO })
}
}
}
}
bottomBar {
@@ -177,7 +242,7 @@ fun RestoreScreen() = FlorisScreen {
ButtonBarTextButton(
onClick = {
restoreWorkspace?.close()
navController.popBackStack()
navController.navigateUp()
},
text = stringRes(R.string.action__cancel),
)
@@ -187,9 +252,13 @@ fun RestoreScreen() = FlorisScreen {
try {
performRestore()
context.showLongToast(R.string.backup_and_restore__restore__success)
navController.popBackStack()
navController.navigateUp()
} catch (e: Throwable) {
context.showLongToast(R.string.backup_and_restore__restore__failure, "error_message" to e.localizedMessage)
e.printStackTrace()
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to e.localizedMessage,
)
}
}
},
@@ -224,7 +293,10 @@ fun RestoreScreen() = FlorisScreen {
runCatching {
restoreDataFromFileSystemLauncher.launch("*/*")
}.onFailure { error ->
context.showLongToast(R.string.backup_and_restore__restore__failure, "error_message" to error.localizedMessage)
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)
}
},
modifier = Modifier
@@ -246,16 +318,16 @@ fun RestoreScreen() = FlorisScreen {
modifier = Modifier.defaultFlorisOutlinedBox(),
title = stringRes(R.string.backup_and_restore__restore__metadata),
) {
this@content.Preference(
iconId = R.drawable.ic_code,
Preference(
icon = Icons.Default.Code,
title = workspace.metadata.packageName,
)
this@content.Preference(
iconId = R.drawable.ic_info,
Preference(
icon = Icons.Outlined.Info,
title = "${workspace.metadata.versionName} (${workspace.metadata.versionCode})",
)
this@content.Preference(
iconId = R.drawable.ic_schedule,
Preference(
icon = Icons.Default.Schedule,
title = remember(workspace.metadata.timestamp) {
val formatter = DateFormat.getDateTimeInstance()
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
@@ -264,34 +336,34 @@ fun RestoreScreen() = FlorisScreen {
},
)
if (workspace.restoreErrorId != null) {
Column(modifier = Modifier.padding(CardDefaults.ContentPadding)) {
Column(modifier = Modifier.padding(FlorisCardDefaults.ContentPadding)) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(9.dp)
.padding(bottom = 8.dp)
.background(MaterialTheme.colors.error.copy(alpha = 0.56f))
.background(MaterialTheme.colorScheme.error.copy(alpha = 0.56f))
)
Text(
text = stringRes(workspace.restoreErrorId!!),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
fontStyle = FontStyle.Italic,
)
}
} else if (workspace.restoreWarningId != null) {
Column(modifier = Modifier.padding(CardDefaults.ContentPadding)) {
Column(modifier = Modifier.padding(FlorisCardDefaults.ContentPadding)) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(9.dp)
.padding(bottom = 8.dp)
.background(LocalContentColor.current.copy(alpha = LocalContentAlpha.current))
.background(LocalContentColor.current)
)
Text(
text = stringRes(workspace.restoreWarningId!!),
style = MaterialTheme.typography.body2,
color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current,
fontStyle = FontStyle.Italic,
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Patrick Goldinger
* Copyright (C) 2021-2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.android.AndroidVersion
@OptIn(ExperimentalJetPrefDatastoreUi::class)
@Composable
@@ -71,6 +72,22 @@ fun ClipboardScreen() = FlorisScreen {
stepIncrement = 5,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.cleanUpOld isEqualTo true },
)
SwitchPreference(
prefs.clipboard.autoCleanSensitive,
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,
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 },
visibleIf = { AndroidVersion.ATLEAST_API33_T },
)
SwitchPreference(
prefs.clipboard.limitHistorySize,
title = stringRes(R.string.pref__clipboard__limit_history_size__label),

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