Compare commits

...

142 Commits

Author SHA1 Message Date
Patrick Goldinger
1c6a719aa5 Release v0.3.14 2022-03-22 20:03:11 +01:00
Patrick Goldinger
693051b699 Fix popup UI controller remember keys causing unintended resets 2022-03-22 18:29:10 +01:00
Patrick Goldinger
91865a0e14 Update CONTRIBUTING.md (#1506) 2022-03-21 22:47:39 +01:00
Patrick Goldinger
ebd45375f6 Release v0.3.14-rc04 2022-03-20 22:53:41 +01:00
Patrick Goldinger
1c9f24c533 Fix numeric and numeric advanced popup width (#1044) 2022-03-20 22:31:51 +01:00
Patrick Goldinger
d6daa87ce6 Fix currency key in symbols not having popups (#1671) 2022-03-20 22:04:59 +01:00
Patrick Goldinger
99eaee1477 Fix clipboard crash when item still has old URI format (#1660) 2022-03-20 12:24:15 +01:00
Patrick Goldinger
47824a8e5f Release v0.3.14-rc03 2022-03-19 20:04:48 +01:00
florisboard-bot
83f45f9c5d Update translations from Crowdin 2022-03-19 19:56:20 +01:00
Patrick Goldinger
5e36f57e82 Fix theme properties share values between different rules (#1626) 2022-03-19 19:48:52 +01:00
Patrick Goldinger
ec65d9e6f8 Merge pull request #1666 from florisboard/fix-clipboard-and-input-logic-crashes
Fix clipboard and input logic crashes
2022-03-19 13:11:03 +01:00
Patrick Goldinger
e3ede1160e Fix ConcurrentModificationException in glide typing (#887, #1632) 2022-03-19 12:53:25 +01:00
Patrick Goldinger
a72d570065 Fix ArrayIndexOutOfBounds in glide typing (#801, #927, #1398) 2022-03-19 11:51:22 +01:00
Patrick Goldinger
db2227cc04 Fix StringIndexOutOfBoundsException (#1140, #1301, #1519, #1662) 2022-03-19 11:44:41 +01:00
Patrick Goldinger
1a77f94af9 Fix clipboard history UI crashing if data file not existing (#1660) 2022-03-19 11:15:52 +01:00
Patrick Goldinger
5bba116e01 Release v0.3.14-rc02 2022-03-18 19:51:41 +01:00
florisboard-bot
5c177cc225 Update translations from Crowdin 2022-03-18 19:37:29 +01:00
Patrick Goldinger
1d0826f854 Update in-app notes about feature availability and welcome screen 2022-03-18 19:33:29 +01:00
Patrick Goldinger
85fdd3876a Merge pull request #1653 from florisboard/fix-clipboard-issues
Fix clipboard issues / Re-add clipboard image support
2022-03-18 19:00:25 +01:00
Patrick Goldinger
59def39114 Fix duplicate primary clip callback adding duplicates (#1555)
This is a mitigation of a bug when a target app quickly adds the same clip twice, to prevent unnecessary duplicates.
2022-03-18 18:53:26 +01:00
Patrick Goldinger
463644c1d2 Improve image scaling in clipboard history view 2022-03-18 17:47:56 +01:00
Patrick Goldinger
117654c95c Fix clipboard history text clip deletion crash 2022-03-18 17:39:20 +01:00
Patrick Goldinger
21e97d6933 Improve clipboard image source display name retrieval logic 2022-03-18 17:18:52 +01:00
Patrick Goldinger
36d9f91009 Fix copy image crash when source does not provide display name 2022-03-18 16:54:16 +01:00
Patrick Goldinger
07c15f0782 Clipboard image provider now supports OpenableColumns 2022-03-18 00:56:34 +01:00
Patrick Goldinger
97d3223122 Re-implement clipboard image support (#1642) 2022-03-17 23:49:31 +01:00
Patrick Goldinger
2e12d756df Fix clipboard adding empty string silently when actually failing (#1647) 2022-03-17 00:04:40 +01:00
Patrick Goldinger
1ca1763138 Remove Handler utils 2022-03-16 23:49:28 +01:00
Patrick Goldinger
93f6ba3f69 Update home screen text displayed 2022-03-15 23:50:56 +01:00
Patrick Goldinger
83fd9a27a8 Fix SnyggStringValueSpec not having meaningful error message (#1643) 2022-03-14 21:54:40 +01:00
Patrick Goldinger
4c8587fa1e Fix Corner Percent Shape allowing percentages over 100 (#1643) 2022-03-14 21:52:12 +01:00
Patrick Goldinger
92f4ef12d9 Fix auto-backup not working on certain devices and with ADB 2022-03-13 23:19:25 +01:00
James
db401e0b69 Fix selectedText not checking for null when performing a copy (#1637) 2022-03-13 22:58:00 +01:00
GoRaN
ca5706b638 Fix kurmanji kurdish letter ş (#1638)
Fix kurmanji letter ş
2022-03-13 16:07:03 +01:00
Patrick Goldinger
63d132ad3c Fix editable color strings contain Unicode Ctrl Chars (#1636) 2022-03-12 20:09:57 +01:00
Patrick Goldinger
97456645e4 Update README.md and ROADMAP.md 2022-03-12 14:45:09 +01:00
Patrick Goldinger
81f949a1ee Release v0.3.14-rc01 2022-03-11 19:57:21 +01:00
florisboard-bot
438ca8c7ad Update translations from Crowdin 2022-03-11 19:42:26 +01:00
Patrick Goldinger
51bd2cc5cd Add note for glide typing preview (#1627) 2022-03-11 19:29:24 +01:00
Patrick Goldinger
864ee749ca Fix Smartbar Settings preference enable state bug 2022-03-11 19:02:47 +01:00
Patrick Goldinger
ba40307f0f Fix long titles and summaries in Settings (#998) 2022-03-11 19:00:56 +01:00
Patrick Goldinger
ae091bdae8 Fix floris_user_dictionary not included in auto-backup 2022-03-11 17:56:19 +01:00
Patrick Goldinger
e98b12f3c5 Merge pull request #1629 from florisboard/localization-fixes
Add support for RTL preference/keyboard UI layout and other minor fixes
2022-03-11 17:29:43 +01:00
Patrick Goldinger
ae49342ae7 Fix Emoji RTL issues 2022-03-11 17:23:26 +01:00
Patrick Goldinger
85ad815ae7 Fix parentheses for RTL languages (#422, #1624) 2022-03-10 22:46:02 +01:00
Patrick Goldinger
948e9bb75f Fix RTL layout issues in theme editor / extension editor 2022-03-10 20:55:11 +01:00
Patrick Goldinger
898dc25cb6 Fix one-handed mode and emoji in RTL layout direction 2022-03-10 00:17:29 +01:00
Patrick Goldinger
33f2336676 Add base support for localization and RTL in keyboard UI 2022-03-09 23:59:21 +01:00
Patrick Goldinger
e64ab1e75d Add support for RTL layout inside new preference UI 2022-03-08 19:55:07 +01:00
Patrick Goldinger
d46e95ceac Disable bundle language split for GPlay releases
This is only necessary for GPLay bundles because both F-Droid and
GitHub releases are a simple APK, which does split at all.
2022-03-08 19:09:23 +01:00
Patrick Goldinger
dd25420e03 Add Japanese (ja) as available language 2022-03-08 19:03:58 +01:00
Patrick Goldinger
b29519a784 Release v0.3.14-beta14 2022-03-06 20:33:01 +01:00
Patrick Goldinger
2d62398186 Adjust home screen message 2022-03-06 20:01:53 +01:00
florisboard-bot
573e55f1c4 Update translations from Crowdin 2022-03-06 19:59:33 +01:00
blueberry-robin
523eabee17 Update Urdu phonetic layout (#1619)
2 fixes for wrong layout in the Urdu phonetic keyboard. One was a wrong(duplicate) HTML code causing 2 different keys to output the same character. The other was a wrong label on a key resulting in 1 key label appearing twice and giving 2 different outputs.
2022-03-06 19:28:38 +01:00
GoRaN
913872e4cd Fix Kurdish language not changing app language (#1620)
* Fix Kurdish language not changing app language manually + some correct and improvement to kurdish layouts

* Add language mappings for Kurdish languages (ckb and ku)

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2022-03-06 19:27:18 +01:00
Patrick Goldinger
c12a802e5e Possible fix for overflow button now showing (#1615) 2022-03-06 18:40:19 +01:00
Patrick Goldinger
3129617402 Merge pull request #1614 from florisboard/reimplement-composers
Reimplement composers into new extension system
2022-03-06 16:15:52 +01:00
Patrick Goldinger
dc2d130b13 Reduce noise of Appender and WithRules composer code 2022-03-06 16:00:10 +01:00
Patrick Goldinger
e9b140a9fe Reimplement composers into new extension system (#1478) 2022-03-06 16:00:10 +01:00
Patrick Goldinger
4189955554 Merge pull request #1618 from florisboard/smartbar-beta14-overhaul
Smartbar overhaul for 0.3.14-beta14
2022-03-06 15:58:52 +01:00
Patrick Goldinger
51f0843a2f Fix Smartbar primary actions auto-collapse for selection (#870) 2022-03-05 19:31:32 +01:00
Patrick Goldinger
2496ada14b Rework and simplify Smartbar themeing 2022-03-05 13:35:11 +01:00
Patrick Goldinger
544e3857fc Add ability to switch Smartbar layouts (#1548) 2022-03-04 20:03:54 +01:00
Patrick Goldinger
f3d076b51e Re-arrange Smartbar settings screen and change terminology a bit 2022-03-04 18:30:16 +01:00
Patrick Goldinger
40efbd0f65 Fix cursor movement for RTL text inside preview field (#1575) 2022-03-04 17:03:07 +01:00
Patrick Goldinger
8d667297c2 Add clear clipboard history confirmation (#1606) 2022-03-03 23:14:10 +01:00
Patrick Goldinger
dbb3f97abd Merge pull request #1608 from florisboard/improve-emoji-implementation
Improve emoji implementation
2022-03-03 20:33:15 +01:00
Patrick Goldinger
627919e34f Fix EmojiCompat IllegalStateException crash (#1610) 2022-03-03 20:25:58 +01:00
Patrick Goldinger
9f67789337 Add ability to specify preferred emoji skin tone (#132) 2022-03-01 01:48:53 +01:00
Patrick Goldinger
96d830e5c4 Add ability to specify emoji history max size 2022-03-01 01:03:24 +01:00
Patrick Goldinger
80ca97388c Fix Emoji font size having "unspecified" as default value (#1607) 2022-03-01 00:11:59 +01:00
Patrick Goldinger
afa021f67d Upgrade AGP to 7.1.2, JC to 1.1.1, and other dependencies 2022-02-28 23:58:21 +01:00
Patrick Goldinger
e14f54cac1 Release v0.3.14-beta13 2022-02-27 23:37:46 +01:00
florisboard-bot
21c1915233 Update translations from Crowdin 2022-02-27 23:20:35 +01:00
Patrick Goldinger
5af80a2270 Fix positioning of emoji variations popup 2022-02-27 23:18:55 +01:00
Patrick Goldinger
8a1a3d3bb4 Improve emoji palette view performance 2022-02-27 23:04:57 +01:00
Patrick Goldinger
a2f15606c7 Fix EmojiCompat load state not correctly checked 2022-02-27 19:11:47 +01:00
Patrick Goldinger
01e2ee7835 Fix crash dialog PendingIntent notification bug 2022-02-26 19:45:01 +01:00
Patrick Goldinger
7adc045752 Merge pull request #1589 from florisboard/reimplement-basic-emoji-palette
Re-implement basic emoji palette view
2022-02-25 18:04:56 +01:00
Patrick Goldinger
266af61e3a Fix font size multiplier ignored in emoji view 2022-02-25 17:56:58 +01:00
Patrick Goldinger
a814190012 Improve and fix themeing of emoji popup palette 2022-02-23 18:05:28 +01:00
Patrick Goldinger
109c323369 Fix emoji palette height not properly adapting 2022-02-23 01:01:39 +01:00
Patrick Goldinger
654e160503 Fix bug that variant emojis weren't checked for availability 2022-02-23 00:51:25 +01:00
Patrick Goldinger
3aa6d7ccbe Add emoji history for recently used emojis (#268, #944) 2022-02-23 00:21:12 +01:00
Patrick Goldinger
5d447d7a8f Improve emoji display and add support for emoji compat drawing 2022-02-22 22:53:26 +01:00
Patrick Goldinger
25bfd61814 Add emoji variations popup in emoji palette 2022-02-22 22:10:30 +01:00
Patrick Goldinger
b4caa66377 Add emoji2 library / Improve emoji UI a lot 2022-02-22 01:40:37 +01:00
Patrick Goldinger
e5dde63efc Improve tabs display in emoji palette view 2022-02-21 23:45:55 +01:00
Patrick Goldinger
78f64adbbf Add base buttons (switch to text and delete) in emoji 2022-02-21 20:34:50 +01:00
Patrick Goldinger
3f39bc8768 Add input event processor for base emojis 2022-02-21 00:54:32 +01:00
Patrick Goldinger
d2274c4d9e Add base skeleton for emoji frontend palette and backend mapping 2022-02-21 00:25:28 +01:00
Patrick Goldinger
8674a04a5c Release v0.3.14-beta12 2022-02-14 21:38:40 +01:00
florisboard-bot
2f14529902 Update translations from Crowdin 2022-02-14 21:25:00 +01:00
Patrick Goldinger
1d74a17b98 Merge pull request #1569 from florisboard/prefs-rework-polishing-1
Preference Rework Polishing Part 1
2022-02-14 21:18:45 +01:00
Patrick Goldinger
52435d9837 Add option to specify language name display type (#1568) 2022-02-14 20:14:12 +01:00
Patrick Goldinger
b6fbbe5a91 Fix transparent colors not drawing with a checkered background 2022-02-14 00:44:22 +01:00
Patrick Goldinger
3f85e1167c Fix blank screen on initial app launch after install (#1537) 2022-02-14 00:20:46 +01:00
Patrick Goldinger
9c05096184 Rework internal implementation of window insets within Settings UI 2022-02-13 21:24:23 +01:00
Patrick Goldinger
ef3bc015b0 Add "display kbd after dialog" option to fine-tune editor options 2022-02-13 20:19:46 +01:00
Patrick Goldinger
75fd600448 Remove ExternalContentUtils.kt 2022-02-13 19:21:09 +01:00
Patrick Goldinger
2f01e7770f Fix behavior and visual display for URLs where no scheme is specified 2022-02-13 19:17:44 +01:00
Patrick Goldinger
12b6edf872 Add missing icon for Smartbar and Advanced screen (#1428) 2022-02-13 18:27:11 +01:00
Patrick Goldinger
6053f2d16b Fix Scrollbar overlaps with text on welcome screen (#1427) 2022-02-13 17:51:47 +01:00
Patrick Goldinger
636c5f4df4 Add "System default (AMOLED)" option for Settings UI theme (#1539) 2022-02-13 17:40:23 +01:00
Patrick Goldinger
bb0bd478cf Fix crash in devtools system settings viewer (#1387) 2022-02-13 15:50:22 +01:00
Patrick Goldinger
79eb080811 Upgrade AGP to 7.1.1 / Upgrade other dependencies 2022-02-13 15:37:47 +01:00
Patrick Goldinger
b5b82836bc Fix NPE due to incorrect variable usage in restore screen (#1567) 2022-02-13 15:06:09 +01:00
Patrick Goldinger
cef0f2b53d Merge pull request #1565 from florisboard/improve-theme-engine-and-add-border
Improve theme editor/stylesheets and implement boder color/width
2022-02-13 14:57:33 +01:00
Patrick Goldinger
dbf031469f Fix follow system and time not updating (#1557) 2022-02-13 14:50:49 +01:00
Patrick Goldinger
5b87c933da Split FlorisImeTheme and BaseStyle into separate files 2022-02-13 12:37:17 +01:00
Patrick Goldinger
adc4b9a372 Improve ordering of value types in stylesheet property editor dialog
Value was changed from explicit inherit > var > *encoders to *encoders > var > explicit inherit. This speeds up the selection process, as users naturally read top to bottom and inherit is almost never used by a user anyways.
2022-02-11 19:58:47 +01:00
Patrick Goldinger
0ff8f7776e Improve float number display in theme editor (remove .0 suffix) 2022-02-11 19:49:28 +01:00
Patrick Goldinger
c04fdeb491 Fix alpha color value in HEX8 mode missing leading zero 2022-02-11 19:28:01 +01:00
Patrick Goldinger
295d8e5326 Implement border-color and border-width for stylesheets (#1105) 2022-02-11 18:59:56 +01:00
Patrick Goldinger
b032ac64f7 Add ability to show/hide keyboard UI on Android 8.1 and lower 2022-02-11 00:22:57 +01:00
Patrick Goldinger
8ebe99d2c9 Merge pull request #1562 from florisboard/reimplement-user-dictionary-manager-ui
Re-implement user-dictionary settings UI
2022-02-10 23:16:49 +01:00
Patrick Goldinger
f0b027557b Fix state issues within the user dictionary UI 2022-02-10 23:03:13 +01:00
Patrick Goldinger
462030bcd7 Re-implement user-dictionary settings UI (#1544) 2022-02-10 22:51:02 +01:00
Patrick Goldinger
888af9d28d Release v0.3.14-beta11 2022-02-08 19:43:54 +01:00
Patrick Goldinger
ea159527f3 Restrict variable name input validation and fix behavior bugs 2022-02-08 19:33:03 +01:00
Patrick Goldinger
0dc0f53a91 Fix missing elevation theme-attribute causing crash on Android 9 (#1553) 2022-02-08 19:11:20 +01:00
Patrick Goldinger
d5aac7ac14 Release v0.3.14-beta10 2022-02-07 21:09:41 +01:00
florisboard-bot
9f58088545 Update translations from Crowdin 2022-02-07 20:24:27 +01:00
Patrick Goldinger
b684f1759d Add option to select text via delete key (#705, #1534) 2022-02-06 23:21:49 +01:00
Patrick Goldinger
aa7a264d6c Add ability to hide language name on the space bar (#371, #1025) 2022-02-06 22:50:05 +01:00
Patrick Goldinger
6ac537c517 Fix spacebar switches to full keyboard in number-only fields (#1248)
Additionally also fixed it for phone and phone2 layouts.
2022-02-06 22:22:45 +01:00
Patrick Goldinger
2386ae7749 Merge pull request #1538 from florisboard/improve-theme-editor-ui
Improve theme editor UI
2022-02-06 22:12:40 +01:00
Patrick Goldinger
7d559acfae Improve redundancy definition of shape in SnyggShapeValue 2022-02-06 21:21:54 +01:00
Patrick Goldinger
7783b9b218 Add shadow elevation to theme and UI 2022-02-06 20:08:36 +01:00
Patrick Goldinger
548f7d7b1e Move SnyggValueIcon to own source file 2022-02-06 19:10:25 +01:00
Patrick Goldinger
4629c07812 Fix manual color string editing disregarding display colors as option 2022-02-06 12:16:43 +01:00
Patrick Goldinger
3b2b7da841 Add fine tune editor dialog / Add "Display colors as" option 2022-02-05 13:59:14 +01:00
Patrick Goldinger
25ef53510a Improve Snygg shape UI and naming implementation 2022-02-05 12:04:03 +01:00
Patrick Goldinger
0064f248d3 Move theme property translations to own file 2022-02-05 11:32:32 +01:00
Patrick Goldinger
0c721696f2 Improve default theme variable usage 2022-02-04 16:08:06 +01:00
Patrick Goldinger
131ab6214d Remove term "component" from title strings 2022-02-04 14:44:18 +01:00
Patrick Goldinger
70bc7a1236 Add translation for var references in basic and advanced mode
... in stylesheet editor
2022-02-04 14:35:26 +01:00
Patrick Goldinger
6c88716a2a Add Snygg circle shape value 2022-02-04 14:29:47 +01:00
Patrick Goldinger
ff3c37e360 Add ability to style color of glide trail
(property `foreground` is the color of glide trail)
2022-02-04 00:21:06 +01:00
Patrick Goldinger
58bab443c4 Add ability to modify color values manually 2022-02-03 23:32:26 +01:00
Patrick Goldinger
a8b0a6d555 Upgrade dependencies / Upgrade Kotlin to 1.6.10 2022-02-03 20:40:34 +01:00
223 changed files with 12238 additions and 7662 deletions

View File

@@ -2,127 +2,104 @@
First off, 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.
There are several ways to contribute to FlorisBoard. This document provides some general guidelines for each type of
contribution.
## Giving general feedback
NEW! You can now [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.
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 :)
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 :)
## Translations
To make FlorisBoard accessible in as many languages as possible, the
platform [Crowdin](https://crowdin.florisboard.patrickgold.dev) is used
to crowdsource and manage translations. 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.
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.
## Adding a new feature or making large changes
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!
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!
## Adding a new keyboard layout
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).
There are two main steps in adding new layouts, though the config step can
be skipped if you only add a layout without a new default language support.
### The config file ([`app/src/main/assets/ime/config.json`](app/src/main/assets/ime/config.json))
This file is very important, as it defines all default currency sets as
well as all default subtypes available in the Settings Subtype UI. Note
that you don't have to modify this file if you add a layout for an already
pre-configured language.
- `currencySets`: This is a list of 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.
- `defaultSubtyes`: This is 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 pre-selected. 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`. Use a unique value
for `id` to avoid possible crashes caused by duplicate ids.
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.
### Adding the layout
Since v0.3.10-beta05 it is possible to add custom layouts for all types.
Since v0.3.14-beta06 it is possible to add custom layouts for all types using the new extension format, Flex.
To add a new layout, head to [`app/src/main/assets/ime/text`](app/src/main/assets/ime/text) and then select
the correct sub-directory for the type of layout you want to add. In most cases
this will be `characters` to add a layout like QWERTY etc.
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:
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 `&#;`
- `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/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt`](app/src/main/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt).
[`app/src/main/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt`](app/src/main/java/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.
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/text/characters/extended_popups/<languageTag_name_here>.json`](app/src/main/assets/ime/text/characters/extended_popups).
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.
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.
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 the suggestions implementation is highly experimental and will
get a major if not complete rework, so dictionaries are currently not
accepted.
Currently the suggestions implementation is highly experimental and not available until 0.4.0, so dictionaries are
currently not accepted.
## 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.
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.
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.
Alternatively, you can also use ADB (Android Debug Bridge) to capture the error log. This is recommended for experienced
users only.
## 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 :)
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 :)
See the `Sponsors` button for available options!

View File

@@ -57,12 +57,14 @@ fully respecting your privacy. Currently in early-beta state.
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme and enter the public beta on Google Play.
## Highlighted features
- Spell checking service
- Word suggestions (currently English only and may not work on all devices)
- Glide typing (currently English only)
- Advanced theming support and customization
- Integrated clipboard manager / history
- Emoji keyboard (although lacking some features)
- Advanced theming support and customization
- Integrated extension support (still evolving)
- Emoji keyboard
- Spell checking service
- Glide typing (currently English only)
Word suggestions are not included in the current releases and are a major goal for the v0.4.0 milestone.
Feature roadmap: See [ROADMAP.md](ROADMAP.md)
@@ -78,18 +80,18 @@ Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List
to get more information on this topic.
## Used libraries, components and icons
* [Google Flexbox Layout for Android](https://github.com/google/flexbox-layout)
by [google](https://github.com/google)
* [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)
* [Google Material icons](https://github.com/google/material-design-icons) by
[google](https://github.com/google)
[Google](https://github.com/google)
* [JetPref preference library](https://github.com/patrickgold/jetpref) by
[patrickgold](https://github.com/patrickgold)
* [KotlinX coroutines library](https://github.com/Kotlin/kotlinx.coroutines) by
[Kotlin](https://github.com/Kotlin)
* [KotlinX serialization library](https://github.com/Kotlin/kotlinx.serialization) by
[Kotlin](https://github.com/Kotlin)
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
[Jared Rummler](https://github.com/jaredrummler)
* [Timber](https://github.com/JakeWharton/timber) by
[JakeWharton](https://github.com/JakeWharton)
* [expandable-fab](https://github.com/nambicompany/expandable-fab) by
[Nambi](https://github.com/nambicompany)
* [ICU4C](https://github.com/unicode-org/icu) by
[The Unicode Consortium](https://github.com/unicode-org)
* [Nuspell](https://github.com/nuspell/nuspell) by
@@ -97,7 +99,7 @@ to get more information on this topic.
## License
```
Copyright 2020 Patrick Goldinger
Copyright 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.

View File

@@ -1,89 +1,91 @@
# FlorisBoard's feature roadmap & milestones
This feature roadmap intents to provide transparency to what I want to add
to FlorisBoard in the foreseeable future. Note that there are no ETAs for any
version milestones down below, experience says these won't hold anyways.
This feature roadmap intents to provide transparency to what I want to add to FlorisBoard in the foreseeable future.
Note that there are no ETAs for any version milestones down below, experience says these won't hold anyways.
I try my best to release regularly, though some features take a lot longer
than others and thus releases can be spaced out a bit on the stable track.
If you are interested in following the development more closely, make sure to
follow along the beta track releases! These are generally more unstable but
you get new stuff faster and can provide early feedback, which helps a lot!
I try my best to release regularly, though some features take a lot longer than others and thus releases can be spaced
out a bit on the stable track. If you are interested in following the development more closely, make sure to follow
along the beta track releases! These are generally more unstable but you get new stuff faster and can provide early
feedback, which helps a lot!
## 0.3.x
Releases in this section still follow the old versioning scheme, meaning the
patch number is a feature upgrade. As this naming convention is more confusing
than useful, beginning with v0.4.0 development a new release/development cycle will be
introduced.
### 0.3.14 (currently in progress, much is already implemented and working well)
Releases in this section still follow the old versioning scheme, meaning the patch number is a feature upgrade. As this
naming convention is more confusing than useful, beginning with v0.4.0 development a new release/development cycle will
be introduced.
### 0.3.14 (almost completed, release candidate phase)
- Re-write of the Preference core
- Reduce redundancy in key/default value definitions
- Avoid having to manually add redundant code for adding a new pref
- Goes hand-in-hand with the Settings UI re-write
- Reduce redundancy in key/default value definitions
- Avoid having to manually add redundant code for adding a new pref
- Goes hand-in-hand with the Settings UI re-write
- Re-write of the Settings UI with Jetpack Compose
- Also re-structure UI into a more list-like panel
- Adjust theme colors of Settings a bit to make it more modern
- Preview the keyboard at any time from within the Settings
- Settings language different than device language
- Also re-structure UI into a more list-like panel
- Adjust theme colors of Settings a bit to make it more modern
- Preview the keyboard at any time from within the Settings
- Settings language different than device language
- Re-write the Setup UI in Jetpack Compose
- Simplify screen based on previously discussed ideas and mock-ups
- Improve backend setup logic
- Implement base-UI for extensions and further continue development
of existing Flex (FlorisBoard extension) format
- Allows for a continuous experience of customizing FlorisBoard in different areas
- Planned what will use Flex:
- Themes
- Layouts (Characters, symbols, numeric, ...)
- Composers for non-Latin script languages
- Word suggestion dictionaries (in 0.4.0)
- Spell check dictionaries
- User dictionaries (not in 0.3.14)
- Other features that require only data and no logic (not in 0.3.14)
- Simplify screen based on previously discussed ideas and mock-ups
- Improve backend setup logic
- Implement base-UI for extensions and further continue development of existing Flex (FlorisBoard extension) format
- Allows for a continuous experience of customizing FlorisBoard in different areas
- Planned what will use Flex:
- Themes
- Layouts (Characters, symbols, numeric, ...)
- Composers for non-Latin script languages
- Word suggestion dictionaries (in 0.4.0)
- Spell check dictionaries
- User dictionaries (not in 0.3.14)
- Other features that require only data and no logic (not in 0.3.14)
- Maybe full backup of preferences? Not 100% confirmed though and may be pushed back
- Theme rework part I:
- Custom key corner radius
- Custom key border color (not shadow!!)
- Re-work theme internals so they use Flex extension format and FlexCSS
- Improvement of the Smartbar
- Allow to have multiple Smartbars
- Better candidate view (in prep for 0.4.0)
- Allow to have multiple Smartbars
- Better candidate view (in prep for 0.4.0)
### 0.3.15
- Hotfix release for possible bugs in the preference rework, may be skipped and 0.4.0 is directly released.
### 0.3.15 & 0.3.16
- Hotfix releases for possible bugs in the preference rework, may be skipped.
## 0.4.0
- Re-adding word suggestions (at least for Latin-based languages at first)
- Importing the dictionaries as well as management relies on the Flex extension core and UI in Kotlin
- Actually parsing and generating suggestions happens in C++ to avoid another OOM catastrophe like in 0.3.9/10
- The actual format of the dictionary and word list source is not decided yet
- Importing the dictionaries as well as management relies on the Flex extension core and UI in Kotlin
- Actually parsing and generating suggestions happens in C++ to avoid another OOM catastrophe like in 0.3.9/10
- The actual format of the dictionary and word list source is not decided yet
- Community repository on GitHub for theme sharing across users (may be 0.5.0)
With this release the versioning scheme changes: the second number now indicates new features,
changes in the third "patch" number now indicates bug fixes for the stable track. The development
cycle for each 0.x release will have `-alphaXX`, `-betaXX` and `-rcXX` (release candidate) releases on the beta
track for interested people to follow along the development. The first release to follow the new scheme will be `0.4.0-alpha01`
on the beta track.
With this release the versioning scheme changes: the second number now indicates new features, changes in the third "
patch" number now indicates bug fixes and minor feature additions for the stable track. The development cycle for each
0.x release will have `-alphaXX`, `-betaXX` and `-rcXX` (release candidate) releases on the beta track for interested
people to follow along the development. The first release to follow the new scheme will be `0.4.0-alpha01` on the beta
track.
## 0.5.0
- Complete rework of the Emoji panel
- Recently used / Emoji history
- Emoji search
- Emoji suggestions when using :emoji_name: syntax
- Kaomoji panel implementation (the third tab which currently has "not yet implemented")
- 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")
- Smartbar customization improvements
- Quick actions customization (order and which buttons to show)
- Prepare FlorisBoard repository and app store presence for public beta release
on Google Play (will go live with stable 0.5.0!!)
- Quick actions customization (order and which buttons to show)
- Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable
0.5.0!!)
- Rework branding images and texts of FlorisBoard for the app stores
- Focus on stability and experience improvements of the app and keyboard
## 0.6.0
- Full on-board layout editor which allows users to create their own layouts
without writing a JSON file
- 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
## Backlog / Features that MAY be added, even in versions not mentioned above if the feature implementation fits perfectly with another feature
- Theme rework part II
- Adaptive themes v2
- Voice-to-text with Mozilla's open-source voice service

View File

@@ -31,7 +31,7 @@ android {
applicationId = "dev.patrickgold.florisboard"
minSdk = 23
targetSdk = 31
versionCode = 65
versionCode = 75
versionName = "0.3.14"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -70,12 +70,21 @@ android {
}
}
bundle {
language {
// We disable language split because FlorisBoard does not use
// runtime Google Play Service APIs and thus cannot dynamically
// request to download the language resources for a specific locale.
enableSplit = false
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.1.0-rc01"
kotlinCompilerExtensionVersion = "1.1.1"
}
externalNativeBuild {
@@ -90,7 +99,7 @@ android {
versionNameSuffix = "-debug"
isDebuggable = true
isJniDebuggable = true
isJniDebuggable = false
ndk {
// For running FlorisBoard on the emulator
@@ -106,7 +115,7 @@ android {
create("beta") // Needed because by default the "beta" BuildType does not exist
named("beta").configure {
applicationIdSuffix = ".beta"
versionNameSuffix = "-beta09"
versionNameSuffix = ""
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
@@ -144,22 +153,25 @@ dependencies {
implementation("androidx.activity:activity-ktx:1.4.0")
implementation("androidx.autofill:autofill:1.1.0")
implementation("androidx.collection:collection-ktx:1.2.0")
implementation("androidx.compose.material:material:1.1.0-rc01")
implementation("androidx.compose.runtime:runtime-livedata:1.1.0-rc01")
implementation("androidx.compose.ui:ui:1.1.0-rc01")
implementation("androidx.compose.ui:ui-tooling-preview:1.1.0-rc01")
implementation("androidx.compose.material:material:1.1.1")
implementation("androidx.compose.runtime:runtime-livedata:1.1.1")
implementation("androidx.compose.ui:ui:1.1.1")
implementation("androidx.compose.ui:ui-tooling-preview:1.1.1")
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
implementation("com.google.accompanist:accompanist-flowlayout:0.20.2")
implementation("com.google.accompanist:accompanist-systemuicontroller:0.20.2")
implementation("dev.patrickgold.jetpref:jetpref-datastore-model:0.1.0-beta05")
implementation("dev.patrickgold.jetpref:jetpref-datastore-ui:0.1.0-beta05")
implementation("dev.patrickgold.jetpref:jetpref-material-ui:0.1.0-beta05")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
implementation("androidx.room:room-runtime:2.4.0")
kapt("androidx.room:room-compiler:2.4.0")
implementation("androidx.core:core-splashscreen:1.0.0-beta01")
implementation("androidx.emoji2:emoji2:1.1.0")
implementation("androidx.emoji2:emoji2-views:1.1.0")
implementation("androidx.navigation:navigation-compose:2.4.1")
implementation("com.google.accompanist:accompanist-flowlayout:0.23.0")
implementation("com.google.accompanist:accompanist-insets:0.23.0")
implementation("com.google.accompanist:accompanist-systemuicontroller:0.23.0")
implementation("dev.patrickgold.jetpref:jetpref-datastore-model:0.1.0-beta08")
implementation("dev.patrickgold.jetpref:jetpref-datastore-ui:0.1.0-beta08")
implementation("dev.patrickgold.jetpref:jetpref-material-ui:0.1.0-beta08")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
implementation("androidx.room:room-runtime:2.4.2")
kapt("androidx.room:room-compiler:2.4.2")
testImplementation("io.kotest:kotest-runner-junit5:5.1.0")
testImplementation("io.kotest:kotest-assertions-core:5.1.0")

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 Patrick Goldinger
<!-- 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.
@@ -128,9 +128,10 @@
android:label="@string/crash_dialog__title"
android:theme="@style/CrashDialogTheme"/>
<!-- Clipboard Image File Provider -->
<provider
android:name="dev.patrickgold.florisboard.ime.clipboard.provider.FlorisContentProvider"
android:authorities="${applicationId}.provider.clip"
android:name="dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardImagesProvider"
android:authorities="${applicationId}.provider.clipboard"
android:grantUriPermissions="true"
android:exported="false">
</provider>

View File

@@ -1,11 +1,19 @@
{
"package": "dev.patrickgold.florisboard",
"$": "ime.extension.keyboard",
"meta": {
"id": "org.florisboard.composers",
"version": "0.1.0",
"title": "Default composers",
"description": "Default composers which are always available.",
"maintainers": [ "patrickgold <patrick@patrickgold.dev>" ],
"license": "apache-2.0"
},
"composers": [
{ "$": "appender" },
{ "$": "hangul-unicode" },
{ "$": "kana-unicode" },
{ "$": "with-rules",
"name": "basic-telex",
"id": "basic-telex",
"label": "Basic Telex",
"rules": {
"aw": "ă", "aa": "â", "dd": "đ", "ee": "ê", "oo": "ô", "ow": "ơ", "uw": "ư", "w": "ư",
@@ -50,8 +58,5 @@
"ỳz": "y", "ỷz": "y", "ỹz": "y", "ýz": "y", "ỵz": "y"
}
}
],
"currencySets": [],
"defaultSubtypes": [
]
}

View File

@@ -55,9 +55,7 @@
{ "code": 1580, "label": "ج" },
{ "code": 1670, "label": "چ" },
{ "code": 1581, "label": "ح" },
{ "code": 1593, "label": "ع", "popup": {
"main": { "code": 1551, "label": "؏" }
} },
{ "code": 1593, "label": "ع" },
{ "code": 1576, "label": "ب" },
{ "code": 1606, "label": "ن" },
{ "code": 1605, "label": "م" }

View File

@@ -60,7 +60,7 @@
{ "code": 1605, "label": "م" },
{ "code": 1567, "label": "؟" },
{ "code": 1548, "label": "،" },
{ "code": 46, "label": "." }
{ "code": 58, "label": ":" }
]
]

View File

@@ -4,7 +4,7 @@
{ "code": 1608, "label": "و" },
{ "code": 1593, "label": "ع" },
{ "code": 1585, "label": "ر" },
{ "code": 1587, "label": "ت" },
{ "code": 1578, "label": "ت" },
{ "code": 1746, "label": "ے" },
{ "code": 1569, "label": "ء" },
{ "code": 1740, "label": "ی" },
@@ -27,7 +27,7 @@
{ "code": 1588, "label": "ش" },
{ "code": 1670, "label": "چ" },
{ "code": 1591, "label": "ط" },
{ "code": 1576, "label": "پ" },
{ "code": 1576, "label": "ب" },
{ "code": 1606, "label": "ن" },
{ "code": 1605, "label": "م" }
]

View File

@@ -1,8 +1,14 @@
[
[
{ "code": 40, "label": "(" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "code": 47, "label": "/" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 45, "label": "-" }
],
[

View File

@@ -32,22 +32,42 @@
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
} },
{ "code": 40, "label": "(", "popup": {
"main": { "code":64830, "label": "" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 60, "label": "<" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 41, "label": ")", "popup": {
"main": { "code":64831, "label": "﴿" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 62, "label": ">" },
{ "code": 125, "label": "}" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(", "popup": {
"main": { "code":64830, "label": "" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 60, "label": "<" },
{ "code": 123, "label": "{" }
]
} },
"rtl": { "code": 41, "label": "(", "popup": {
"main": { "code":64830, "label": "" },
"relevant": [
{ "code": 93, "label": "[" },
{ "code": 62, "label": "<" },
{ "code": 125, "label": "{" }
]
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")", "popup": {
"main": { "code":64831, "label": "﴿" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 62, "label": ">" },
{ "code": 125, "label": "}" }
]
} },
"rtl": { "code": 40, "label": ")", "popup": {
"main": { "code":64831, "label": "﴿" },
"relevant": [
{ "code": 91, "label": "]" },
{ "code": 60, "label": ">" },
{ "code": 123, "label": "}" }
]
} }
},
{ "code": 47, "label": "/" }
],
[

View File

@@ -2,12 +2,24 @@
[
{ "code": 8230, "label": "…" },
{ "code": 95, "label": "_" },
{ "code": 91, "label": "[" },
{ "code": 93, "label": "]" },
{ "$": "layout_direction_selector",
"ltr": { "code": 91, "label": "[" },
"rtl": { "code": 93, "label": "[" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 93, "label": "]" },
"rtl": { "code": 91, "label": "]" }
},
{ "code": 94, "label": "^" },
{ "code": 33, "label": "!" },
{ "code": 60, "label": "<" },
{ "code": 62, "label": ">" },
{ "$": "layout_direction_selector",
"ltr": { "code": 60, "label": "<" },
"rtl": { "code": 62, "label": "<" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 62, "label": ">" },
"rtl": { "code": 60, "label": ">" }
},
{ "code": 61, "label": "=" },
{ "code": 38, "label": "&" },
{ "code": 383, "label": "ſ" }
@@ -15,12 +27,24 @@
[
{ "code": 92, "label": "\\" },
{ "code": 47, "label": "/" },
{ "code": 123, "label": "{" },
{ "code": 125, "label": "}" },
{ "$": "layout_direction_selector",
"ltr": { "code": 123, "label": "{" },
"rtl": { "code": 125, "label": "{" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 125, "label": "}" },
"rtl": { "code": 123, "label": "}" }
},
{ "code": 42, "label": "*" },
{ "code": 63, "label": "?" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 64, "label": "@" }

View File

@@ -32,22 +32,42 @@
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
} },
{ "code": 40, "label": "(", "popup": {
"main": { "code":64830, "label": "" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 60, "label": "<" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 41, "label": ")", "popup": {
"main": { "code":64831, "label": "﴿" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 62, "label": ">" },
{ "code": 125, "label": "}" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(", "popup": {
"main": { "code":64830, "label": "" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 60, "label": "<" },
{ "code": 123, "label": "{" }
]
} },
"rtl": { "code": 41, "label": "(", "popup": {
"main": { "code":64830, "label": "" },
"relevant": [
{ "code": 93, "label": "[" },
{ "code": 62, "label": "<" },
{ "code": 125, "label": "{" }
]
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")", "popup": {
"main": { "code":64831, "label": "﴿" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 62, "label": ">" },
{ "code": 125, "label": "}" }
]
} },
"rtl": { "code": 40, "label": ")", "popup": {
"main": { "code":64831, "label": "﴿" },
"relevant": [
{ "code": 91, "label": "]" },
{ "code": 60, "label": ">" },
{ "code": 123, "label": "}" }
]
} }
},
{ "code": 1643, "label": "٫", "popup": {
"main": { "code": 1644, "label": "٬" },
"relevant": [

View File

@@ -31,20 +31,38 @@
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
} },
{ "code": 40, "label": "(", "popup": {
"main": { "code": 60, "label": "<" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 41, "label": ")", "popup": {
"main": { "code": 62, "label": ">" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(", "popup": {
"main": { "code": 60, "label": "<" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
"rtl": { "code": 41, "label": "(", "popup": {
"main": { "code": 62, "label": "<" },
"relevant": [
{ "code": 93, "label": "[" },
{ "code": 125, "label": "{" }
]
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")", "popup": {
"main": { "code": 62, "label": ">" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
"rtl": { "code": 40, "label": ")", "popup": {
"main": { "code": 60, "label": ">" },
"relevant": [
{ "code": 91, "label": "]" },
{ "code": 123, "label": "}" }
]
} }
},
{ "code": 47, "label": "/" }
],
[

View File

@@ -43,20 +43,38 @@
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
} },
{ "code": 40, "label": "(", "popup": {
"main": { "code": 60, "label": "<" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 41, "label": ")", "popup": {
"main": { "code": 62, "label": ">" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(", "popup": {
"main": { "code": 60, "label": "<" },
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
"rtl": { "code": 41, "label": "(", "popup": {
"main": { "code": 62, "label": "<" },
"relevant": [
{ "code": 93, "label": "[" },
{ "code": 125, "label": "{" }
]
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")", "popup": {
"main": { "code": 62, "label": ">" },
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
"rtl": { "code": 40, "label": ")", "popup": {
"main": { "code": 60, "label": ">" },
"relevant": [
{ "code": 91, "label": "]" },
{ "code": 123, "label": "}" }
]
} }
},
{ "code": 47, "label": "/" }
],
[

View File

@@ -56,12 +56,22 @@
{ "code": 8776, "label": "≈" }
]
} },
{ "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
{ "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
"rtl": { "code": 125, "label": "{", "popup": {
"main": { "code": 41, "label": "(" }
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
"rtl": { "code": 123, "label": "}", "popup": {
"main": { "code": 40, "label": ")" }
} }
},
{ "code": 92, "label": "\\" }
],
[
@@ -70,7 +80,13 @@
{ "code": 174, "label": "®" },
{ "code": 8482, "label": "™" },
{ "code": 10003, "label": "✓" },
{ "code": 91, "label": "[" },
{ "code": 93, "label": "]" }
{ "$": "layout_direction_selector",
"ltr": { "code": 91, "label": "[" },
"rtl": { "code": 93, "label": "[" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 93, "label": "]" },
"rtl": { "code": 91, "label": "]" }
}
]
]

View File

@@ -56,12 +56,22 @@
{ "code": 8776, "label": "≈" }
]
} },
{ "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
{ "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
"rtl": { "code": 125, "label": "{", "popup": {
"main": { "code": 41, "label": "(" }
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
"rtl": { "code": 123, "label": "}", "popup": {
"main": { "code": 40, "label": ")" }
} }
},
{ "code": 92, "label": "\\" }
],
[
@@ -70,7 +80,13 @@
{ "code": 174, "label": "®" },
{ "code": 8482, "label": "™" },
{ "code": 10003, "label": "✓" },
{ "code": 91, "label": "[" },
{ "code": 93, "label": "]" }
{ "$": "layout_direction_selector",
"ltr": { "code": 91, "label": "[" },
"rtl": { "code": 93, "label": "[" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 93, "label": "]" },
"rtl": { "code": 91, "label": "]" }
}
]
]

View File

@@ -56,12 +56,22 @@
{ "code": 8776, "label": "≈" }
]
} },
{ "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
{ "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" }
} },
"rtl": { "code": 125, "label": "{", "popup": {
"main": { "code": 41, "label": "(" }
} }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" }
} },
"rtl": { "code": 123, "label": "}", "popup": {
"main": { "code": 40, "label": ")" }
} }
},
{ "code": 92, "label": "\\" }
],
[
@@ -70,7 +80,13 @@
{ "code": 174, "label": "®" },
{ "code": 8482, "label": "™" },
{ "code": 10003, "label": "✓" },
{ "code": 91, "label": "[" },
{ "code": 93, "label": "]" }
{ "$": "layout_direction_selector",
"ltr": { "code": 91, "label": "[" },
"rtl": { "code": 93, "label": "[" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 93, "label": "]" },
"rtl": { "code": 91, "label": "]" }
}
]
]

View File

@@ -6,24 +6,44 @@
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 60, "label": "<", "popup": {
"relevant": [
{ "code": 171, "label": "«" },
{ "code": 8804, "label": "" },
{ "code": 8249, "label": "" },
{ "code":10216, "label": "" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 60, "label": "<", "popup": {
"relevant": [
{ "code": 171, "label": "«" },
{ "code": 8804, "label": "" },
{ "code": 8249, "label": "" },
{ "code":10216, "label": "⟨" }
]
} },
"rtl": { "code": 62, "label": "<", "popup": {
"relevant": [
{ "code": 187, "label": "«" },
{ "code": 8805, "label": "≤" },
{ "code": 8250, "label": "" },
{ "code":10217, "label": "⟨" }
]
} }
},
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 62, "label": ">", "popup": {
"relevant": [
{ "code":10217, "label": "⟩" },
{ "code": 8250, "label": "" },
{ "code": 8805, "label": "" },
{ "code": 187, "label": "»" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 62, "label": ">", "popup": {
"relevant": [
{ "code":10217, "label": "" },
{ "code": 8250, "label": "" },
{ "code": 8805, "label": "" },
{ "code": 187, "label": "»" }
]
} },
"rtl": { "code": 60, "label": ">", "popup": {
"relevant": [
{ "code":10216, "label": "⟩" },
{ "code": 8249, "label": "" },
{ "code": 8804, "label": "≥" },
{ "code": 171, "label": "»" }
]
} }
},
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -18,8 +18,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -91,8 +91,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -88,8 +88,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -106,8 +106,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -85,8 +85,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -52,8 +52,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -80,8 +80,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -63,8 +63,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -88,8 +88,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -91,8 +91,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -111,8 +111,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -94,8 +94,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -34,8 +34,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -42,8 +42,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -83,8 +83,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -76,8 +76,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -13,8 +13,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -48,8 +48,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -63,7 +63,7 @@
]
},
"s": {
"main": { "$": "auto_text_key", "code": 219, "label": "ș" },
"main": { "$": "auto_text_key", "code": 351, "label": "ş" },
"relevant": [
{ "$": "auto_text_key", "code": 347, "label": "ś" },
{ "$": "auto_text_key", "code": 349, "label": "ŝ" },
@@ -102,8 +102,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -140,8 +140,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -80,8 +80,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -72,8 +72,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -82,8 +82,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -60,8 +60,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -81,8 +81,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -81,8 +81,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -52,8 +52,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -23,8 +23,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -42,8 +42,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -89,8 +89,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -32,8 +32,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -133,8 +133,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -40,8 +40,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -18,8 +18,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

View File

@@ -40,8 +40,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "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": "?" }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,12 @@
"--surface": "#ffffff",
"--surface-variant": "#f5f5f5",
"--on-primary": "#000000",
"--on-secondary": "#000000",
"--on-background": "#000000",
"--on-surface": "#000000"
"--on-background": "#121212",
"--on-surface": "#000000",
"--on-surface-variant": "#5f5f5f",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"keyboard": {
@@ -22,7 +24,8 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)"
"shadow-elevation": "2dp",
"shape": "var(--shape)"
},
"key:pressed": {
"background": "var(--surface-variant)",
@@ -30,36 +33,91 @@
},
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "#ffffff"
"foreground": "var(--on-surface)"
},
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "#ffffff"
"foreground": "var(--on-surface)"
},
"key[code={c:shift}][mode={m:capslock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "#909090",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp"
},
"key-hint": {
"background": "transparent",
"foreground": "#b8b8b8",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp"
},
"key-popup": {
"background": "#eeeeee",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)"
"shadow-elevation": "2dp",
"shape": "var(--shape)"
},
"key-popup:focus": {
"background": "#bdbdbd",
"foreground": "var(--on-surface)"
},
"smartbar-primary-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shadow-elevation": "2dp",
"shape": "circle()"
},
"smartbar-secondary-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"shape": "circle()"
},
"smartbar-quick-action": {
"background": "transparent",
"foreground": "var(--on-background)",
"shape": "circle()"
},
"smartbar-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-key:disabled": {
"background": "transparent",
"foreground": "#12121248"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"background": "transparent",
"foreground": "var(--on-surface)",
@@ -69,13 +127,43 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"shadow-elevation": "2dp",
"shape": "var(--shape-variant)"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"shadow-elevation": "2dp",
"shape": "var(--shape-variant)"
},
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#eeeeee",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shadow-elevation": "2dp",
"shape": "var(--shape)"
},
"emoji-tab": {
"foreground": "var(--on-background)"
},
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"glide-trail": {
"foreground": "var(--primary)"
},
"one-handed-panel": {
@@ -83,75 +171,6 @@
"foreground": "#424242"
},
"smartbar-primary-row": {
"background": "transparent"
},
"smartbar-primary-action-row-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "rounded-corner(50%, 50%, 50%, 50%)"
},
"smartbar-primary-secondary-row-toggle": {
"background": "transparent",
"foreground": "#121212",
"shape": "rounded-corner(50%, 50%, 50%, 50%)"
},
"smartbar-secondary-row": {
"background": "var(--background)"
},
"smartbar-action-row": {
"background": "transparent"
},
"smartbar-action-button": {
"background": "transparent",
"foreground": "#121212",
"shape": "rounded-corner(50%, 50%, 50%, 50%)"
},
"smartbar-candidate-row": {
"background": "transparent"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "#121212",
"font-size": "14sp",
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "#121212"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "#121212",
"font-size": "14sp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "#121212"
},
"smartbar-candidate-spacer": {
"foreground": "#ffffff40"
},
"smartbar-key": {
"background": "transparent",
"foreground": "#121212",
"font-size": "18sp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)"
},
"smartbar-key:pressed": {
"background": "var(--surface)",
"foreground": "#121212"
},
"smartbar-key:disabled": {
"background": "transparent",
"foreground": "#12121248"
},
"system-nav-bar": {
"background": "var(--background)"
}

View File

@@ -8,10 +8,12 @@
"--surface": "#424242",
"--surface-variant": "#616161",
"--on-primary": "#ffffff",
"--on-secondary": "#ffffff",
"--on-background": "#ffffff",
"--on-surface": "#ffffff"
"--on-background": "#dcdcdc",
"--on-surface": "#ffffff",
"--on-surface-variant": "#a0a0a0",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"keyboard": {
@@ -22,7 +24,8 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)"
"shadow-elevation": "2dp",
"shape": "var(--shape)"
},
"key:pressed": {
"background": "var(--surface-variant)",
@@ -41,25 +44,80 @@
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "#909090",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp"
},
"key-hint": {
"background": "transparent",
"foreground": "#b8b8b8",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp"
},
"key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)"
"shadow-elevation": "2dp",
"shape": "var(--shape)"
},
"key-popup:focus": {
"background": "#bdbdbd",
"foreground": "var(--on-surface)"
},
"smartbar-primary-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shadow-elevation": "2dp",
"shape": "circle()"
},
"smartbar-secondary-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"shape": "circle()"
},
"smartbar-quick-action": {
"background": "transparent",
"foreground": "var(--on-background)",
"shape": "circle()"
},
"smartbar-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-key:disabled": {
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"background": "transparent",
"foreground": "var(--on-surface)",
@@ -69,13 +127,43 @@
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"shadow-elevation": "2dp",
"shape": "var(--shape-variant)"
},
"clipboard-item-popup": {
"background": "#757575",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
"shadow-elevation": "2dp",
"shape": "var(--shape-variant)"
},
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shadow-elevation": "2dp",
"shape": "var(--shape)"
},
"emoji-tab": {
"foreground": "var(--on-background)"
},
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"glide-trail": {
"foreground": "var(--primary)"
},
"one-handed-panel": {
@@ -83,75 +171,6 @@
"foreground": "#eeeeee"
},
"smartbar-primary-row": {
"background": "transparent"
},
"smartbar-primary-action-row-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "rounded-corner(50%, 50%, 50%, 50%)"
},
"smartbar-primary-secondary-row-toggle": {
"background": "transparent",
"foreground": "#909090",
"shape": "rounded-corner(50%, 50%, 50%, 50%)"
},
"smartbar-secondary-row": {
"background": "var(--background)"
},
"smartbar-action-row": {
"background": "transparent"
},
"smartbar-action-button": {
"background": "transparent",
"foreground": "#dcdcdc",
"shape": "rounded-corner(50%, 50%, 50%, 50%)"
},
"smartbar-candidate-row": {
"background": "transparent"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "#dcdcdc",
"font-size": "14sp",
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "#dcdcdc"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "#dcdcdc",
"font-size": "14sp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "#dcdcdc"
},
"smartbar-candidate-spacer": {
"foreground": "#ffffff40"
},
"smartbar-key": {
"background": "transparent",
"foreground": "#dcdcdc",
"font-size": "18sp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)"
},
"smartbar-key:pressed": {
"background": "var(--surface)",
"foreground": "#dcdcdc"
},
"smartbar-key:disabled": {
"background": "transparent",
"foreground": "var(--surface)"
},
"system-nav-bar": {
"background": "var(--background)"
}

View File

@@ -24,29 +24,28 @@ import android.content.IntentFilter
import androidx.core.os.UserManagerCompat
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.common.NativeStr
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.common.toNativeStr
import dev.patrickgold.florisboard.crashutility.CrashUtility
import dev.patrickgold.florisboard.debug.Flog
import dev.patrickgold.florisboard.debug.LogTopic
import dev.patrickgold.florisboard.debug.flogError
import dev.patrickgold.florisboard.debug.flogInfo
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.keyboard.KeyboardManager
import dev.patrickgold.florisboard.ime.nlp.NlpManager
import dev.patrickgold.florisboard.ime.spelling.SpellingManager
import dev.patrickgold.florisboard.ime.spelling.SpellingService
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.res.AssetManager
import dev.patrickgold.florisboard.res.ext.ExtensionManager
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.nlp.NlpManager
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.res.cache.CacheManager
import dev.patrickgold.florisboard.res.ext.ExtensionManager
import dev.patrickgold.florisboard.res.io.deleteContentsRecursively
import dev.patrickgold.jetpref.datastore.JetPref
import java.io.File
import kotlin.Exception
@Suppress("unused")
class FlorisApplication : Application() {

View File

@@ -16,7 +16,9 @@
package dev.patrickgold.florisboard
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Size
@@ -40,6 +42,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -66,6 +69,7 @@ import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.common.android.isOrientationLandscape
import dev.patrickgold.florisboard.common.android.isOrientationPortrait
import dev.patrickgold.florisboard.common.android.launchActivity
import dev.patrickgold.florisboard.common.android.setLocale
import dev.patrickgold.florisboard.common.android.systemServiceOrNull
import dev.patrickgold.florisboard.common.observeAsTransformingState
import dev.patrickgold.florisboard.debug.LogTopic
@@ -80,6 +84,7 @@ import dev.patrickgold.florisboard.ime.keyboard.InputFeedbackController
import dev.patrickgold.florisboard.ime.keyboard.LocalInputFeedbackController
import dev.patrickgold.florisboard.ime.keyboard.ProvideKeyboardRowBaseHeight
import dev.patrickgold.florisboard.ime.lifecycle.LifecycleInputMethodService
import dev.patrickgold.florisboard.ime.media.MediaInputLayout
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.onehanded.OneHandedPanel
import dev.patrickgold.florisboard.ime.text.TextInputLayout
@@ -134,12 +139,25 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
}
fun showUi() {
val ims = FlorisImeServiceReference.get() ?: return
if (AndroidVersion.ATLEAST_API28_P) {
FlorisImeServiceReference.get()?.requestShowSelf(0)
ims.requestShowSelf(0)
} else {
@Suppress("DEPRECATION")
ims.systemServiceOrNull(InputMethodManager::class)
?.showSoftInputFromInputMethod(ims.currentInputBinding.connectionToken, 0)
}
}
fun hideUi() {
val ims = FlorisImeServiceReference.get() ?: return
if (AndroidVersion.ATLEAST_API28_P) {
ims.requestHideSelf(0)
} else {
@Suppress("DEPRECATION")
ims.systemServiceOrNull(InputMethodManager::class)
?.hideSoftInputFromInputMethod(ims.currentInputBinding.connectionToken, 0)
}
FlorisImeServiceReference.get()?.requestHideSelf(0)
}
@@ -184,8 +202,9 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
private val prefs by florisPreferenceModel()
private val keyboardManager by keyboardManager()
private val themeManager by themeManager()
private val nlpManager by nlpManager()
private val subtypeManager by subtypeManager()
private val themeManager by themeManager()
private val activeEditorInstance by lazy { EditorInstance(this) }
private val activeState get() = keyboardManager.activeState
@@ -193,11 +212,17 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
private var inputViewSize by mutableStateOf(IntSize.Zero)
private val inputFeedbackController by lazy { InputFeedbackController.new(this) }
private var isWindowShown: Boolean = false
private var resourcesContext by mutableStateOf(this as Context)
override fun onCreate() {
super.onCreate()
FlorisImeServiceReference = WeakReference(this)
activeEditorInstance.wordHistoryChangedListener = this
subtypeManager.activeSubtype.observe(this) { subtype ->
val config = Configuration(resources.configuration)
config.setLocale(subtype.primaryLocale)
resourcesContext = createConfigurationContext(config)
}
}
override fun onCreateInputView(): View {
@@ -302,6 +327,7 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
}
override fun onWindowHidden() {
@@ -372,9 +398,9 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
val visibleTopY = inputWindowView.height - inputViewSize.height
val needAdditionalOverlay =
prefs.smartbar.enabled.get() &&
prefs.smartbar.secondaryRowEnabled.get() &&
prefs.smartbar.secondaryRowExpanded.get() &&
prefs.smartbar.secondaryRowPlacement.get() == SecondaryRowPlacement.OVERLAY_APP_UI &&
prefs.smartbar.secondaryActionsEnabled.get() &&
prefs.smartbar.secondaryActionsExpanded.get() &&
prefs.smartbar.secondaryActionsPlacement.get() == SecondaryRowPlacement.OVERLAY_APP_UI &&
keyboardManager.activeState.imeUiMode == ImeUiMode.TEXT
outInsets.contentTopInsets = visibleTopY
@@ -408,12 +434,9 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
@Composable
private fun ImeUiWrapper() {
ProvideLocalizedResources(this) {
ProvideLocalizedResources(resourcesContext) {
ProvideKeyboardRowBaseHeight {
CompositionLocalProvider(
LocalInputFeedbackController provides inputFeedbackController,
LocalLayoutDirection provides LayoutDirection.Ltr,
) {
CompositionLocalProvider(LocalInputFeedbackController provides inputFeedbackController) {
FlorisImeTheme {
// Outer box is necessary as an "outer window"
Box(modifier = Modifier.fillMaxSize()) {
@@ -435,61 +458,70 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
element = FlorisImeUi.Keyboard,
mode = activeState.inputMode.value,
)
SnyggSurface(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.align(Alignment.BottomStart)
.onGloballyPositioned { coords -> inputViewSize = coords.size }
// Do not remove below line or touch input may get stuck
.pointerInteropFilter { false },
background = keyboardStyle.background,
) {
val configuration = LocalConfiguration.current
val bottomOffset by if (configuration.isOrientationPortrait()) {
prefs.keyboard.bottomOffsetPortrait
} else {
prefs.keyboard.bottomOffsetLandscape
}.observeAsTransformingState { it.dp }
Row(
val layoutDirection = LocalLayoutDirection.current
SideEffect {
keyboardManager.activeState.layoutDirection = layoutDirection
}
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
SnyggSurface(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
// FIXME: removing this fixes the Smartbar sizing but breaks one-handed-mode
//.height(IntrinsicSize.Min)
.padding(bottom = bottomOffset),
.align(Alignment.BottomStart)
.onGloballyPositioned { coords -> inputViewSize = coords.size }
// Do not remove below line or touch input may get stuck
.pointerInteropFilter { false },
style = keyboardStyle,
) {
val oneHandedMode by prefs.keyboard.oneHandedMode.observeAsState()
val oneHandedModeScaleFactor by prefs.keyboard.oneHandedModeScaleFactor.observeAsState()
val keyboardWeight = when {
oneHandedMode == OneHandedMode.OFF || configuration.isOrientationLandscape() -> 1f
else -> oneHandedModeScaleFactor / 100f
}
if (oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.START,
weight = 1f - keyboardWeight,
)
}
Box(
val configuration = LocalConfiguration.current
val bottomOffset by if (configuration.isOrientationPortrait()) {
prefs.keyboard.bottomOffsetPortrait
} else {
prefs.keyboard.bottomOffsetLandscape
}.observeAsTransformingState { it.dp }
Row(
modifier = Modifier
.weight(keyboardWeight)
.wrapContentHeight(),
.fillMaxWidth()
.wrapContentHeight()
// FIXME: removing this fixes the Smartbar sizing but breaks one-handed-mode
//.height(IntrinsicSize.Min)
.padding(bottom = bottomOffset),
) {
when (activeState.imeUiMode) {
ImeUiMode.TEXT -> TextInputLayout()
ImeUiMode.MEDIA -> {}
ImeUiMode.CLIPBOARD -> ClipboardInputLayout()
val oneHandedMode by prefs.keyboard.oneHandedMode.observeAsState()
val oneHandedModeScaleFactor by prefs.keyboard.oneHandedModeScaleFactor.observeAsState()
val keyboardWeight = when {
oneHandedMode == OneHandedMode.OFF || configuration.isOrientationLandscape() -> 1f
else -> oneHandedModeScaleFactor / 100f
}
if (oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.START,
weight = 1f - keyboardWeight,
)
}
CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
Box(
modifier = Modifier
.weight(keyboardWeight)
.wrapContentHeight(),
) {
when (activeState.imeUiMode) {
ImeUiMode.TEXT -> TextInputLayout()
ImeUiMode.MEDIA -> MediaInputLayout()
ImeUiMode.CLIPBOARD -> ClipboardInputLayout()
}
}
}
if (oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.END,
weight = 1f - keyboardWeight,
)
}
}
if (oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.END,
weight = 1f - keyboardWeight,
)
}
}
}
}
@Composable

View File

@@ -19,8 +19,6 @@ package dev.patrickgold.florisboard.app
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.view.View
import android.view.ViewTreeObserver
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
@@ -39,6 +37,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
import com.google.accompanist.insets.statusBarsPadding
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.app.res.ProvideLocalizedResources
@@ -59,9 +60,10 @@ import dev.patrickgold.jetpref.datastore.ui.ProvideDefaultDialogPrefStrings
enum class AppTheme(val id: String) {
AUTO("auto"),
AUTO_AMOLED("auto_amoled"),
LIGHT("light"),
DARK("dark"),
AMOLED_DARK("amoled_dark"),
AMOLED_DARK("amoled_dark");
}
val LocalNavController = staticCompositionLocalOf<NavController> {
@@ -97,35 +99,20 @@ class FlorisAppActivity : ComponentActivity() {
}
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
ProvideLocalizedResources(resourcesContext) {
FlorisAppTheme(theme = appTheme) {
Surface(color = MaterialTheme.colors.background) {
SystemUiApp()
if (isDatastoreReady) {
ProvideWindowInsets(windowInsetsAnimationsEnabled = false) {
Surface(color = MaterialTheme.colors.background) {
SystemUiApp()
AppContent()
}
}
}
}
}
// PreDraw observer for SplashScreen
val content = findViewById<View>(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
return if (isDatastoreReady) {
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
false
}
}
}
)
}
override fun onPause() {
@@ -159,7 +146,11 @@ class FlorisAppActivity : ComponentActivity() {
dismissLabel = stringRes(R.string.action__cancel),
neutralLabel = stringRes(R.string.action__default),
) {
Column {
Column(
modifier = Modifier
.statusBarsPadding()
.navigationBarsWithImePadding(),
) {
Routes.AppNavHost(
modifier = Modifier.weight(1.0f),
navController = navController,

View File

@@ -17,8 +17,14 @@
package dev.patrickgold.florisboard.app.prefs
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.ui.settings.theme.DisplayColorsAs
import dev.patrickgold.florisboard.app.ui.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
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.EmojiSkinTone
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.spelling.SpellingLanguageMode
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
@@ -27,9 +33,11 @@ import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.florisboard.ime.text.smartbar.CandidatesDisplayMode
import dev.patrickgold.florisboard.ime.text.smartbar.SecondaryRowPlacement
import dev.patrickgold.florisboard.ime.text.smartbar.SmartbarRowType
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
import dev.patrickgold.florisboard.res.ext.ExtensionComponentName
import dev.patrickgold.florisboard.snygg.SnyggLevel
import dev.patrickgold.florisboard.util.VersionName
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
@@ -301,7 +309,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val internal = Internal()
inner class Internal {
val homeIsBetaToolboxCollapsed = boolean(
key = "internal__home_is_beta_toolbox_collapsed_beta08",
key = "internal__home_is_beta_toolbox_collapsed_0314release",
default = false,
)
val isImeSetUp = boolean(
@@ -352,6 +360,10 @@ 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 fontSizeMultiplierPortrait = int(
key = "keyboard__font_size_multiplier_portrait",
default = 100,
@@ -430,6 +442,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val localization = Localization()
inner class Localization {
val displayLanguageNamesIn = enum(
key = "localization__display_language_names_in",
default = DisplayLanguageNamesIn.NATIVE_LOCALE,
)
val activeSubtypeId = long(
key = "localization__active_subtype_id",
default = Subtype.DEFAULT.id,
@@ -440,42 +456,71 @@ 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(
key = "smartbar__enabled",
default = true,
)
val primaryRowFlipToggles = boolean(
key = "smartbar__primary_row_flip_toggles",
val flipToggles = boolean(
key = "smartbar__flip_toggles",
default = false,
)
val secondaryRowEnabled = boolean(
key = "smartbar__secondary_row_enabled",
val primaryActionsExpanded = boolean(
key = "smartbar__primary_actions_expanded",
default = false,
)
val primaryActionsRowType = enum(
key = "smartbar__primary_actions_row_type",
default = SmartbarRowType.QUICK_ACTIONS,
)
val primaryActionsAutoExpandCollapse = boolean(
key = "smartbar__primary_actions_auto_expand_collapse",
default = true,
)
val secondaryRowExpanded = boolean(
key = "smartbar__secondary_row_expanded",
val primaryActionsExpandWithAnimation = boolean(
key = "smartbar__primary_actions_expand_with_animation",
default = true,
)
val secondaryActionsEnabled = boolean(
key = "smartbar__secondary_actions_enabled",
default = true,
)
val secondaryActionsExpanded = boolean(
key = "smartbar__secondary_actions_expanded",
default = false,
)
val secondaryRowPlacement = enum(
key = "smartbar__secondary_row_placement",
val secondaryActionsPlacement = enum(
key = "smartbar__secondary_actions_placement",
default = SecondaryRowPlacement.ABOVE_PRIMARY,
)
val actionRowExpanded = boolean(
key = "smartbar__action_row_expanded",
default = false,
val secondaryActionsRowType = enum(
key = "smartbar__secondary_actions_row_type",
default = SmartbarRowType.CLIPBOARD_CURSOR_TOOLS,
)
val actionRowExpandWithAnimation = boolean(
key = "smartbar__action_row_expand_with_animation",
default = true,
)
val actionRowAutoExpandCollapse = boolean(
key = "smartbar__action_row_auto_expand_collapse",
default = true,
)
val actions = string(
key = "smartbar__actions",
val quickActions = string(
key = "smartbar__quick_actions",
default = "[]",
)
}
@@ -560,5 +605,17 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
// key = "theme__sunset_time",
// default = LocalTime.of(18, 0),
//)
val editorDisplayColorsAs = enum(
key = "theme__editor_display_colors_as",
default = DisplayColorsAs.HEX8,
)
val editorDisplayKbdAfterDialogs = enum(
key = "theme__editor_display_kbd_after_dialogs",
default = DisplayKbdAfterDialogs.REMEMBER,
)
val editorLevel = enum(
key = "theme__editor_level",
default = SnyggLevel.ADVANCED,
)
}
}

View File

@@ -17,12 +17,15 @@
package dev.patrickgold.florisboard.app.res
import android.content.Context
import android.view.View
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.LayoutDirection
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.common.kotlin.CurlyArg
import dev.patrickgold.florisboard.common.kotlin.curlyFormat
@@ -40,8 +43,14 @@ fun ProvideLocalizedResources(
resourcesContext: Context,
content: @Composable () -> Unit,
) {
val layoutDirection = when (resourcesContext.resources.configuration.layoutDirection) {
View.LAYOUT_DIRECTION_LTR -> LayoutDirection.Ltr
View.LAYOUT_DIRECTION_RTL -> LayoutDirection.Rtl
else -> error("Given configuration specifies invalid layout direction!")
}
CompositionLocalProvider(
LocalResourcesContext provides resourcesContext,
LocalLayoutDirection provides layoutDirection,
LocalAppNameString provides stringResource(R.string.floris_app_name),
) {
content()

View File

@@ -38,12 +38,15 @@ import dev.patrickgold.florisboard.app.ui.settings.advanced.BackupScreen
import dev.patrickgold.florisboard.app.ui.settings.advanced.RestoreScreen
import dev.patrickgold.florisboard.app.ui.settings.clipboard.ClipboardScreen
import dev.patrickgold.florisboard.app.ui.settings.dictionary.DictionaryScreen
import dev.patrickgold.florisboard.app.ui.settings.dictionary.UserDictionaryScreen
import dev.patrickgold.florisboard.app.ui.settings.dictionary.UserDictionaryType
import dev.patrickgold.florisboard.app.ui.settings.gestures.GesturesScreen
import dev.patrickgold.florisboard.app.ui.settings.keyboard.InputFeedbackScreen
import dev.patrickgold.florisboard.app.ui.settings.keyboard.KeyboardScreen
import dev.patrickgold.florisboard.app.ui.settings.localization.LocalizationScreen
import dev.patrickgold.florisboard.app.ui.settings.localization.SelectLocaleScreen
import dev.patrickgold.florisboard.app.ui.settings.localization.SubtypeEditorScreen
import dev.patrickgold.florisboard.app.ui.settings.media.MediaScreen
import dev.patrickgold.florisboard.app.ui.settings.smartbar.SmartbarScreen
import dev.patrickgold.florisboard.app.ui.settings.spelling.ImportSpellingArchiveScreen
import dev.patrickgold.florisboard.app.ui.settings.spelling.ManageSpellingDictsScreen
@@ -94,11 +97,15 @@ object Routes {
const val ImportSpellingAffDic = "settings/spelling/import-aff-dic"
const val Dictionary = "settings/dictionary"
const val UserDictionary = "settings/dictionary/user-dictionary/{type}"
fun UserDictionary(type: UserDictionaryType) = UserDictionary.curlyFormat("type" to type.id)
const val Gestures = "settings/gestures"
const val Clipboard = "settings/clipboard"
const val Media = "settings/media"
const val Advanced = "settings/advanced"
const val Backup = "settings/advanced/backup"
const val Restore = "settings/advanced/restore"
@@ -181,11 +188,19 @@ object Routes {
composable(Settings.ImportSpellingArchive) { ImportSpellingArchiveScreen() }
composable(Settings.Dictionary) { DictionaryScreen() }
composable(Settings.UserDictionary) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
UserDictionaryType.values().firstOrNull { it.id == typeId }
}
UserDictionaryScreen(type!!)
}
composable(Settings.Gestures) { GesturesScreen() }
composable(Settings.Clipboard) { ClipboardScreen() }
composable(Settings.Media) { MediaScreen() }
composable(Settings.Advanced) { AdvancedScreen() }
composable(Settings.Backup) { BackupScreen() }
composable(Settings.Restore) { RestoreScreen() }

View File

@@ -25,19 +25,32 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.shrinkVertically
import androidx.compose.ui.Alignment
fun EnterTransition.Companion.verticalTween(duration: Int): EnterTransition {
return fadeIn(tween(duration)) + expandVertically(tween(duration))
fun EnterTransition.Companion.verticalTween(
duration: Int,
expandFrom: Alignment.Vertical = Alignment.Bottom,
): EnterTransition {
return fadeIn(tween(duration)) + expandVertically(tween(duration), expandFrom)
}
fun ExitTransition.Companion.verticalTween(duration: Int): ExitTransition {
return fadeOut(tween(duration)) + shrinkVertically(tween(duration))
fun ExitTransition.Companion.verticalTween(
duration: Int,
shrinkTowards: Alignment.Vertical = Alignment.Bottom,
): ExitTransition {
return fadeOut(tween(duration)) + shrinkVertically(tween(duration), shrinkTowards)
}
fun EnterTransition.Companion.horizontalTween(duration: Int): EnterTransition {
return fadeIn(tween(duration)) + expandHorizontally(tween(duration))
fun EnterTransition.Companion.horizontalTween(
duration: Int,
expandFrom: Alignment.Horizontal = Alignment.End,
): EnterTransition {
return fadeIn(tween(duration)) + expandHorizontally(tween(duration), expandFrom)
}
fun ExitTransition.Companion.horizontalTween(duration: Int): ExitTransition {
return fadeOut(tween(duration)) + shrinkHorizontally(tween(duration))
fun ExitTransition.Companion.horizontalTween(
duration: Int,
shrinkTowards: Alignment.Horizontal = Alignment.End,
): ExitTransition {
return fadeOut(tween(duration)) + shrinkHorizontally(tween(duration), shrinkTowards)
}

View File

@@ -150,8 +150,8 @@ fun FlorisDropdownLikeButton(
color = color,
)
Icon(
modifier = Modifier.rotate(-90.0f),
painter = painterResource(R.drawable.ic_keyboard_arrow_down),
modifier = Modifier.autoMirrorForRtl(),
painter = painterResource(R.drawable.ic_keyboard_arrow_right),
tint = color.copy(alpha = ContentAlpha.medium),
contentDescription = "Dropdown indicator",
)

View File

@@ -85,6 +85,7 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
val navController = LocalNavController.current
FlorisIconButton(
onClick = { navController.popBackStack() },
modifier = Modifier.autoMirrorForRtl(),
icon = painterResource(R.drawable.ic_arrow_back),
)
}

View File

@@ -244,7 +244,8 @@ private fun ColumnScope.Step(
) {
Column(modifier = Modifier
.fillMaxSize()
.florisVerticalScroll(),
.florisVerticalScroll()
.padding(end = 8.dp),
) {
content()
}

View File

@@ -34,6 +34,7 @@ import androidx.compose.material.Text
import androidx.compose.material.TextFieldColors
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -43,9 +44,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.ui.theme.outline
import dev.patrickgold.florisboard.common.ValidationResult
@@ -110,7 +114,7 @@ fun FlorisOutlinedTextField(
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = MaterialTheme.typography.button,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
@@ -131,59 +135,61 @@ fun FlorisOutlinedTextField(
val textColor = textStyle.color.takeOrElse {
colors.textColor(enabled).value
}
val mergedTextStyle = textStyle.copy(color = textColor)
val mergedTextStyle = textStyle.copy(color = textColor, textDirection = TextDirection.Content)
val isFocused by interactionSource.collectIsFocusedAsState()
val isErrorState = isError || (showValidationError && validationResult?.isInvalid() == true)
BasicTextField(
modifier = modifier.padding(vertical = 4.dp),
value = value,
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
textStyle = mergedTextStyle,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
visualTransformation = visualTransformation,
cursorBrush = SolidColor(colors.cursorColor(isErrorState).value),
decorationBox = { innerTextField ->
Surface(
modifier = modifier.fillMaxWidth(),
color = colors.backgroundColor(enabled).value,
border = if (isErrorState && enabled) {
BorderStroke(ButtonDefaults.OutlinedBorderSize, MaterialTheme.colors.error)
} else if (isFocused) {
BorderStroke(ButtonDefaults.OutlinedBorderSize, MaterialTheme.colors.primary)
} else {
ButtonDefaults.outlinedBorder
},
shape = shape,
) {
Box(
modifier = Modifier
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = 40.dp,
)
.padding(ButtonDefaults.ContentPadding),
contentAlignment = Alignment.CenterStart,
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
BasicTextField(
modifier = modifier.padding(vertical = 4.dp),
value = value,
onValueChange = onValueChange,
enabled = enabled,
readOnly = readOnly,
textStyle = mergedTextStyle,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
visualTransformation = visualTransformation,
cursorBrush = SolidColor(colors.cursorColor(isErrorState).value),
decorationBox = { innerTextField ->
Surface(
modifier = modifier.fillMaxWidth(),
color = colors.backgroundColor(enabled).value,
border = if (isErrorState && enabled) {
BorderStroke(ButtonDefaults.OutlinedBorderSize, MaterialTheme.colors.error)
} else if (isFocused) {
BorderStroke(ButtonDefaults.OutlinedBorderSize, MaterialTheme.colors.primary)
} else {
ButtonDefaults.outlinedBorder
},
shape = shape,
) {
ProvideTextStyle(value = mergedTextStyle) {
innerTextField()
}
if (!placeholder.isNullOrBlank()) {
Text(
text = placeholder,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.56f),
)
Box(
modifier = Modifier
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = 40.dp,
)
.padding(ButtonDefaults.ContentPadding),
contentAlignment = Alignment.CenterStart,
) {
ProvideTextStyle(value = mergedTextStyle) {
innerTextField()
}
if (!placeholder.isNullOrBlank()) {
Text(
text = placeholder,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.56f),
)
}
}
}
}
},
)
},
)
}
if (showValidationHint && validationResult?.isValid() == true && validationResult.hasHintMessage()) {
Text(

View File

@@ -27,6 +27,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
@@ -49,6 +50,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
@@ -103,6 +105,7 @@ fun PreviewKeyboardField(
.focusRequester(controller.focusRequester),
value = controller.text,
onValueChange = { controller.text = it },
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.ContentOrLtr),
placeholder = {
Text(
text = hint,

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.components
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
fun Modifier.autoMirrorForRtl() = composed {
if (LocalLayoutDirection.current == LayoutDirection.Rtl) {
this.scale(-1f, 1f)
} else {
this
}
}

View File

@@ -35,6 +35,8 @@ import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@@ -126,7 +128,8 @@ fun Modifier.florisScrollbar(
fun Modifier.florisScrollbar(
state: LazyListState,
scrollbarSize: Dp = DefaultScrollbarSize,
size: Dp = DefaultScrollbarSize,
color: Color = Color.Unspecified,
isVertical: Boolean,
): Modifier = composed {
var isInitial by remember { mutableStateOf(true) }
@@ -136,7 +139,7 @@ fun Modifier.florisScrollbar(
targetValue = targetAlpha,
animationSpec = tween(durationMillis = duration, easing = ScrollbarAnimationEasing),
)
val scrollbarColor = MaterialTheme.colors.onSurface.copy(alpha = 0.28f)
val scrollbarColor = color.takeOrElse { MaterialTheme.colors.onSurface.copy(alpha = 0.28f) }
LaunchedEffect(Unit) {
delay(1850)
@@ -155,16 +158,16 @@ fun Modifier.florisScrollbar(
if (isVertical) {
val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
scrollbarWidth = scrollbarSize.toPx()
scrollbarWidth = size.toPx()
scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight
scrollbarOffsetX = size.width - scrollbarWidth
scrollbarOffsetX = this.size.width - scrollbarWidth
scrollbarOffsetY = firstVisibleElementIndex * elementHeight
} else {
val elementWidth = this.size.width / state.layoutInfo.totalItemsCount
scrollbarWidth = state.layoutInfo.visibleItemsInfo.size * elementWidth
scrollbarHeight = scrollbarSize.toPx()
scrollbarHeight = size.toPx()
scrollbarOffsetX = firstVisibleElementIndex * elementWidth
scrollbarOffsetY = size.height - scrollbarHeight
scrollbarOffsetY = this.size.height - scrollbarHeight
}
drawRect(

View File

@@ -42,18 +42,17 @@ import dev.patrickgold.florisboard.common.android.AndroidVersion
fun SystemUiApp() {
val systemUiController = rememberFlorisSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
val backgroundColor = MaterialTheme.colors.background
SideEffect {
systemUiController.setStatusBarColor(
color = backgroundColor,
color = Color.Transparent,
darkIcons = useDarkIcons,
)
if (AndroidVersion.ATLEAST_API26_O) {
systemUiController.setNavigationBarColor(
color = backgroundColor,
color = Color.Transparent,
darkIcons = useDarkIcons,
navigationBarContrastEnforced = true,
navigationBarContrastEnforced = false,
)
}
}
@@ -194,4 +193,3 @@ private class FlorisSystemUiController(
return if (context is ContextWrapper) context.findWindow() else null
}
}

View File

@@ -31,8 +31,10 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
@@ -58,7 +60,10 @@ fun DevtoolsOverlay(
val showPrimaryClip by prefs.devtools.showPrimaryClip.observeAsState()
val showSpellingOverlay by prefs.devtools.showSpellingOverlay.observeAsState()
CompositionLocalProvider(LocalContentColor provides Color.White) {
CompositionLocalProvider(
LocalContentColor provides Color.White,
LocalLayoutDirection provides LayoutDirection.Ltr,
) {
Column(modifier = modifier) {
if (showPrimaryClip) {
val primaryClip by clipboardManager.primaryClip.observeAsState()

View File

@@ -52,6 +52,7 @@ import dev.patrickgold.florisboard.app.ui.components.FlorisOutlinedBox
import dev.patrickgold.florisboard.app.ui.components.FlorisOutlinedTextField
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.components.FlorisUnsavedChangesDialog
import dev.patrickgold.florisboard.app.ui.components.autoMirrorForRtl
import dev.patrickgold.florisboard.app.ui.components.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.app.ui.settings.advanced.RadioListItem
import dev.patrickgold.florisboard.app.ui.settings.theme.DialogProperty
@@ -299,6 +300,7 @@ private fun EditScreen(
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
modifier = Modifier.autoMirrorForRtl(),
icon = painterResource(R.drawable.ic_arrow_back),
)
}

View File

@@ -119,7 +119,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
if (!ext.meta.homepage.isNullOrBlank()) {
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__homepage)) {
FlorisHyperlinkText(
text = FlorisRef.from(ext.meta.homepage!!).authority,
text = FlorisRef.fromUrl(ext.meta.homepage!!).authority,
url = ext.meta.homepage!!,
)
}
@@ -127,7 +127,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
if (!ext.meta.issueTracker.isNullOrBlank()) {
ExtensionMetaRowSimpleText(label = stringRes(R.string.ext__meta__issue_tracker)) {
FlorisHyperlinkText(
text = FlorisRef.from(ext.meta.issueTracker!!).authority,
text = FlorisRef.fromUrl(ext.meta.issueTracker!!).authority,
url = ext.meta.issueTracker!!,
)
}

View File

@@ -22,7 +22,6 @@ 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.Button
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
@@ -32,11 +31,11 @@ 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.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.res.stringRes
@@ -45,7 +44,6 @@ import dev.patrickgold.florisboard.app.ui.components.FlorisErrorCard
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.components.FlorisWarningCard
import dev.patrickgold.florisboard.common.InputMethodUtils
import dev.patrickgold.florisboard.common.android.launchUrl
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.Preference
@@ -83,7 +81,7 @@ fun HomeScreen() = FlorisScreen {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Beta-access to new Settings UI",
text = "Note on the new Settings UI",
style = MaterialTheme.typography.subtitle1,
fontWeight = FontWeight.Bold,
)
@@ -100,20 +98,11 @@ fun HomeScreen() = FlorisScreen {
}
}
if (!isCollapsed) {
Text("You are currently testing out the new Settings of FlorisBoard.\n")
Text("If you want to give feedback on the development of the new prefs and keyboard logic, please do so in below linked feedback thread:\n")
Button(onClick = {
context.launchUrl("https://github.com/florisboard/florisboard/discussions/1235")
}) {
Text("Open Feedback Thread")
}
Text("Welcome to the new Settings of FlorisBoard!\n")
Text("It has been quite a long time since 0.3.13, but since then a lot has changed. FlorisBoard has undergone a major overhaul and now uses a completely new UI library, backend logic and also the Settings have been completely revamped. A big thanks goes to all my beta testers who continuously provided feedback and tested things out, this made the development much more interactive and better!\n")
Spacer(modifier = Modifier.height(16.dp))
Text("Current version: ${BuildConfig.VERSION_NAME}\n")
Text("List of unavailable features, will get implemented/fixed in the upcoming beta releases:\n")
Text(" - Glide typing bug fixes -> glide works somewhat but long words tend to not get recognized")
Text(" - Basic emoji view")
Text(" - Word suggestions (will just show word + a number to test out if the UI works) (new suggestions in 0.4.0)\n")
Text("Please do not file issues that above features do not work (especially word suggestions, it is more than known by now and the major goal for 0.4.0 after the preference rework and its hotfix phase has been completed). Thank you!\n")
Text("Note that this release does not contain support for word suggestions (will show the current word plus numbers as a placeholder).", color = Color.Red)
Text("Please DO NOT file an issue for this. It is already more than known and a major goal for implementation in 0.4.0. Thank you!\n")
Spacer(modifier = Modifier.height(16.dp))
}
}
@@ -134,7 +123,7 @@ fun HomeScreen() = FlorisScreen {
onClick = { navController.navigate(Routes.Settings.Keyboard) },
)
Preference(
iconId = null,
iconId = R.drawable.ic_smartbar,
title = stringRes(R.string.settings__smartbar__title),
onClick = { navController.navigate(Routes.Settings.Smartbar) },
)
@@ -163,6 +152,11 @@ fun HomeScreen() = FlorisScreen {
title = stringRes(R.string.settings__clipboard__title),
onClick = { navController.navigate(Routes.Settings.Clipboard) },
)
Preference(
iconId = R.drawable.ic_sentiment_satisfied,
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),

View File

@@ -20,9 +20,12 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
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.res.stringRes
@@ -41,23 +44,28 @@ fun ProjectLicenseScreen() = FlorisScreen {
val assetManager by context.assetManager()
content {
SelectionContainer(
modifier = Modifier
.fillMaxSize()
.florisVerticalScroll()
.florisHorizontalScroll(),
) {
val licenseText = assetManager.loadTextAsset(
FlorisRef.assets("license/project_license.txt")
).getOrElse {
stringRes(R.string.about__project_license__error_license_text_failed, "error_message" to (it.message ?: ""))
// Forcing LTR because the Apache 2.0 License shipped and displayed
// is hard to read if rendered in RTL. Also it is in English so forcing
// LTR here makes most sense.
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
SelectionContainer(
modifier = Modifier
.fillMaxSize()
.florisVerticalScroll()
.florisHorizontalScroll(),
) {
val licenseText = assetManager.loadTextAsset(
FlorisRef.assets("license/project_license.txt")
).getOrElse {
stringRes(R.string.about__project_license__error_license_text_failed, "error_message" to (it.message ?: ""))
}
Text(
text = licenseText,
fontFamily = FontFamily.Monospace,
fontSize = 10.sp,
softWrap = false,
)
}
Text(
text = licenseText,
fontFamily = FontFamily.Monospace,
fontSize = 10.sp,
softWrap = false,
)
}
}
}

View File

@@ -44,6 +44,7 @@ data class Library(val name: String, val licenseText: String)
fun ThirdPartyLicensesScreen() = FlorisScreen {
title = stringRes(R.string.about__third_party_licenses__title)
scrollable = false
iconSpaceReserved = false
val context = LocalContext.current

View File

@@ -17,6 +17,7 @@
package dev.patrickgold.florisboard.app.ui.settings.advanced
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.LocalNavController
@@ -25,6 +26,8 @@ import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
@@ -41,12 +44,17 @@ fun AdvancedScreen() = FlorisScreen {
content {
ListPreference(
prefs.advanced.settingsTheme,
iconId = R.drawable.ic_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),
@@ -63,6 +71,7 @@ fun AdvancedScreen() = FlorisScreen {
)
ListPreference(
prefs.advanced.settingsLanguage,
iconId = R.drawable.ic_language,
title = stringRes(R.string.pref__advanced__settings_language__label),
entries = listPrefEntries {
listOf(
@@ -71,7 +80,7 @@ fun AdvancedScreen() = FlorisScreen {
"bg",
"bs",
"ca",
"ckb-IR",
"ckb",
"cs",
"da",
"de",
@@ -87,8 +96,9 @@ fun AdvancedScreen() = FlorisScreen {
"in",
"it",
"iw",
"kmr-TR",
"ja",
"ko-KR",
"ku",
"lv-LV",
"mk",
"nds-DE",
@@ -112,14 +122,19 @@ fun AdvancedScreen() = FlorisScreen {
label = stringRes(R.string.settings__system_default),
)
} else {
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
val locale = FlorisLocale.fromTag(languageTag)
entry(locale.languageTag(), locale.displayName(locale))
entry(locale.languageTag(), when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> locale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> locale.displayName(locale)
})
}
}
}
)
SwitchPreference(
prefs.advanced.showAppIcon,
iconId = R.drawable.ic_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)
@@ -129,6 +144,7 @@ fun AdvancedScreen() = FlorisScreen {
)
SwitchPreference(
prefs.advanced.forcePrivateMode,
iconId = R.drawable.ic_security,
title = stringRes(R.string.pref__advanced__force_private_mode__label),
summary = stringRes(R.string.pref__advanced__force_private_mode__summary),
)
@@ -136,11 +152,13 @@ fun AdvancedScreen() = FlorisScreen {
PreferenceGroup(title = stringRes(R.string.backup_and_restore__title)) {
Preference(
onClick = { navController.navigate(Routes.Settings.Backup) },
iconId = R.drawable.ic_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,
title = stringRes(R.string.backup_and_restore__restore__title),
summary = stringRes(R.string.backup_and_restore__restore__summary),
)

View File

@@ -297,7 +297,7 @@ fun RestoreScreen() = FlorisScreen {
.background(LocalContentColor.current.copy(alpha = LocalContentAlpha.current))
)
Text(
text = stringRes(workspace.restoreErrorId!!),
text = stringRes(workspace.restoreWarningId!!),
style = MaterialTheme.typography.body2,
color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
fontStyle = FontStyle.Italic,

View File

@@ -17,9 +17,10 @@
package dev.patrickgold.florisboard.app.ui.settings.dictionary
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
@@ -29,7 +30,7 @@ fun DictionaryScreen() = FlorisScreen {
title = stringRes(R.string.settings__dictionary__title)
previewFieldVisible = true
val context = LocalContext.current
val navController = LocalNavController.current
content {
SwitchPreference(
@@ -40,7 +41,7 @@ fun DictionaryScreen() = FlorisScreen {
Preference(
title = stringRes(R.string.pref__dictionary__manage_system_user_dictionary__label),
summary = stringRes(R.string.pref__dictionary__manage_system_user_dictionary__summary),
onClick = { /* TODO */ },
onClick = { navController.navigate(Routes.Settings.UserDictionary(UserDictionaryType.SYSTEM)) },
enabledIf = { prefs.dictionary.enableSystemUserDictionary isEqualTo true },
)
SwitchPreference(
@@ -51,7 +52,7 @@ fun DictionaryScreen() = FlorisScreen {
Preference(
title = stringRes(R.string.pref__dictionary__manage_floris_user_dictionary__label),
summary = stringRes(R.string.pref__dictionary__manage_floris_user_dictionary__summary),
onClick = { /* TODO */ },
onClick = { navController.navigate(Routes.Settings.UserDictionary(UserDictionaryType.FLORIS)) },
enabledIf = { prefs.dictionary.enableFlorisUserDictionary isEqualTo true },
)
}

View File

@@ -0,0 +1,403 @@
/*
* 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.dictionary
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.text.font.FontStyle
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisIconButton
import dev.patrickgold.florisboard.app.ui.components.FlorisOutlinedTextField
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.components.rippleClickable
import dev.patrickgold.florisboard.app.ui.settings.theme.DialogProperty
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.common.android.launchActivity
import dev.patrickgold.florisboard.common.android.showLongToast
import dev.patrickgold.florisboard.common.android.stringRes
import dev.patrickgold.florisboard.common.rememberValidationResult
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FREQUENCY_MAX
import dev.patrickgold.florisboard.ime.dictionary.FREQUENCY_MIN
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryDao
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryEntry
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryValidation
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private val AllLanguagesLocale = FlorisLocale.from(language = "zz")
private val UserDictionaryEntryToAdd = UserDictionaryEntry(id = 0, "", 255, null, null)
private const val SystemUserDictionaryUiIntentAction = "android.settings.USER_DICTIONARY_SETTINGS"
enum class UserDictionaryType(val id: String) {
FLORIS("floris"),
SYSTEM("system");
}
@Composable
fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
title = stringRes(when (type) {
UserDictionaryType.FLORIS -> R.string.settings__udm__title_floris
UserDictionaryType.SYSTEM -> R.string.settings__udm__title_system
})
previewFieldVisible = false
scrollable = false
val navController = LocalNavController.current
val context = LocalContext.current
val dictionaryManager = DictionaryManager.default()
val scope = rememberCoroutineScope()
var currentLocale by remember { mutableStateOf<FlorisLocale?>(null) }
var languageList by remember { mutableStateOf(emptyList<FlorisLocale>()) }
var wordList by remember { mutableStateOf(emptyList<UserDictionaryEntry>()) }
var userDictionaryEntryForDialog by remember { mutableStateOf<UserDictionaryEntry?>(null) }
fun userDictionaryDao(): UserDictionaryDao? {
return when (type) {
UserDictionaryType.FLORIS -> dictionaryManager.florisUserDictionaryDao()
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDao()
}
}
fun getDisplayNameForLocale(locale: FlorisLocale): String {
return if (locale == AllLanguagesLocale) {
context.stringRes(R.string.settings__udm__all_languages)
} else {
locale.displayName()
}
}
fun buildUi() {
if (currentLocale != null) {
//subtitle = getDisplayNameForLocale(currentLocale)
val locale = if (currentLocale == AllLanguagesLocale) null else currentLocale
wordList = userDictionaryDao()?.queryAll(locale) ?: emptyList()
if (wordList.isEmpty()) {
currentLocale = null
}
}
if (currentLocale == null) {
//subtitle = null
languageList = userDictionaryDao()
?.queryLanguageList()
?.sortedBy { it?.displayLanguage() }
?.map { it ?: AllLanguagesLocale }
?: emptyList()
}
}
val importDictionary = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri ->
// 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 (uri == null) return@rememberLauncherForActivityResult
val db = when (type) {
UserDictionaryType.FLORIS -> dictionaryManager.florisUserDictionaryDatabase()
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToast("Database handle is null, failed to import")
return@rememberLauncherForActivityResult
}
runCatching {
db.importCombinedList(context, uri)
}.onSuccess {
buildUi()
context.showLongToast(R.string.settings__udm__dictionary_import_success)
}.onFailure { error ->
context.showLongToast("Error: ${error.localizedMessage}")
}
},
)
val exportDictionary = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(),
onResult = { uri ->
// 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 (uri == null) return@rememberLauncherForActivityResult
val db = when (type) {
UserDictionaryType.FLORIS -> dictionaryManager.florisUserDictionaryDatabase()
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToast("Database handle is null, failed to export")
return@rememberLauncherForActivityResult
}
runCatching {
db.exportCombinedList(context, uri)
}.onSuccess {
context.showLongToast(R.string.settings__udm__dictionary_export_success)
}.onFailure { error ->
context.showLongToast("Error: ${error.localizedMessage}")
}
},
)
navigationIcon {
FlorisIconButton(
onClick = {
if (currentLocale != null) {
currentLocale = null
buildUi()
} else {
navController.popBackStack()
}
},
icon = painterResource(if (currentLocale != null) {
R.drawable.ic_close
} else {
R.drawable.ic_arrow_back
}),
)
}
actions {
var expanded by remember { mutableStateOf(false) }
FlorisIconButton(
onClick = { expanded = !expanded },
icon = painterResource(R.drawable.ic_more_vert),
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
DropdownMenuItem(
onClick = {
importDictionary.launch("*/*")
expanded = false
},
content = { Text(text = stringRes(R.string.action__import)) },
)
DropdownMenuItem(
onClick = {
exportDictionary.launch("my-personal-dictionary.clb")
expanded = false
},
content = { Text(text = stringRes(R.string.action__export)) },
)
if (type == UserDictionaryType.SYSTEM) {
DropdownMenuItem(
onClick = {
context.launchActivity { it.action = SystemUserDictionaryUiIntentAction }
expanded = false
},
content = { Text(text = stringRes(R.string.settings__udm__open_system_manager_ui)) },
)
}
}
}
floatingActionButton {
ExtendedFloatingActionButton(
onClick = { userDictionaryEntryForDialog = UserDictionaryEntryToAdd },
icon = { Icon(painter = painterResource(R.drawable.ic_add), contentDescription = null) },
text = { Text(text = stringRes(R.string.settings__udm__dialog__title_add)) },
)
}
content {
BackHandler(currentLocale != null) {
currentLocale = null
buildUi()
}
LaunchedEffect(Unit) {
dictionaryManager.loadUserDictionariesIfNecessary()
buildUi()
}
LazyColumn {
if (languageList.isEmpty()) {
item {
Text(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
text = stringRes(R.string.settings__udm__no_words_in_dictionary),
fontStyle = FontStyle.Italic,
)
}
}
if (currentLocale == null) {
items(languageList) { language ->
JetPrefListItem(
modifier = Modifier.rippleClickable {
scope.launch {
// Delay makes UI ripple visible and experience better
delay(150)
currentLocale = language
buildUi()
}
},
text = getDisplayNameForLocale(language),
)
}
} else {
items(wordList) { wordEntry ->
JetPrefListItem(
modifier = Modifier.rippleClickable {
userDictionaryEntryForDialog = wordEntry
},
text = wordEntry.word,
secondaryText = stringRes(
if (wordEntry.shortcut != null) {
R.string.settings__udm__word_summary_freq_shortcut
} else {
R.string.settings__udm__word_summary_freq
},
"freq" to wordEntry.freq,
"shortcut" to wordEntry.shortcut,
),
)
}
}
}
val wordEntry = userDictionaryEntryForDialog
if (wordEntry != null) {
var showValidationErrors by rememberSaveable { mutableStateOf(false) }
val isAddWord = wordEntry === UserDictionaryEntryToAdd
var word by rememberSaveable { mutableStateOf(wordEntry.word) }
val wordValidation = rememberValidationResult(UserDictionaryValidation.Word, word)
var freq by rememberSaveable { mutableStateOf(wordEntry.freq.toString()) }
val freqValidation = rememberValidationResult(UserDictionaryValidation.Freq, freq)
var shortcut by rememberSaveable { mutableStateOf(wordEntry.shortcut ?: "") }
val shortcutValidation = rememberValidationResult(UserDictionaryValidation.Shortcut, shortcut)
var locale by rememberSaveable { mutableStateOf(wordEntry.locale ?: "") }
val localeValidation = rememberValidationResult(UserDictionaryValidation.Locale, locale)
JetPrefAlertDialog(
title = stringRes(if (isAddWord) {
R.string.settings__udm__dialog__title_add
} else {
R.string.settings__udm__dialog__title_edit
}),
confirmLabel = stringRes(if (isAddWord) {
R.string.action__add
} else {
R.string.action__apply
}),
onConfirm = {
val isInvalid = wordValidation.isInvalid() ||
freqValidation.isInvalid() ||
shortcutValidation.isInvalid() ||
localeValidation.isInvalid()
if (isInvalid) {
showValidationErrors = true
} else {
val entry = UserDictionaryEntry(
id = wordEntry.id,
word = word.trim(),
freq = freq.toInt(10),
shortcut = shortcut.trim().takeIf { it.isNotBlank() },
locale = locale.trim().takeIf { it.isNotBlank() }?.let {
// Normalize tag
FlorisLocale.fromTag(it).localeTag()
},
)
if (isAddWord) {
userDictionaryDao()?.insert(entry)
} else {
userDictionaryDao()?.update(entry)
}
userDictionaryEntryForDialog = null
buildUi()
}
},
dismissLabel = stringRes(R.string.action__cancel),
onDismiss = {
userDictionaryEntryForDialog = null
},
neutralLabel = if (isAddWord) {
null
} else {
stringRes(R.string.action__delete)
},
onNeutral = {
userDictionaryDao()?.delete(wordEntry)
userDictionaryEntryForDialog = null
buildUi()
},
) {
Column {
DialogProperty(text = stringRes(R.string.settings__udm__dialog__word_label)) {
FlorisOutlinedTextField(
value = word,
onValueChange = { word = it },
showValidationError = showValidationErrors,
validationResult = wordValidation,
)
}
DialogProperty(text = stringRes(
R.string.settings__udm__dialog__freq_label,
"f_min" to FREQUENCY_MIN, "f_max" to FREQUENCY_MAX,
)) {
FlorisOutlinedTextField(
value = freq,
onValueChange = { freq = it },
showValidationError = showValidationErrors,
validationResult = freqValidation,
)
}
DialogProperty(text = stringRes(R.string.settings__udm__dialog__shortcut_label)) {
FlorisOutlinedTextField(
value = shortcut,
onValueChange = { shortcut = it },
showValidationError = showValidationErrors,
validationResult = shortcutValidation,
)
}
DialogProperty(text = stringRes(R.string.settings__udm__dialog__locale_label)) {
FlorisOutlinedTextField(
value = locale,
onValueChange = { locale = it },
showValidationError = showValidationErrors,
validationResult = localeValidation,
)
}
}
}
}
}
}

View File

@@ -69,6 +69,7 @@ fun GesturesScreen() = FlorisScreen {
SwitchPreference(
prefs.glide.showPreview,
title = stringRes(R.string.pref__glide__show_preview),
summary = "Word suggestions must be enabled for this to take effect!",
enabledIf = { prefs.glide.enabled isEqualTo true },
)
DialogSliderPreference(

View File

@@ -72,6 +72,11 @@ fun KeyboardScreen() = FlorisScreen {
entries = UtilityKeyAction.listEntries(),
visibleIf = { prefs.keyboard.utilityKeyEnabled isEqualTo true },
)
SwitchPreference(
prefs.keyboard.spaceBarLanguageDisplayEnabled,
title = stringRes(R.string.pref__keyboard__space_bar_language_display_enabled__label),
summary = stringRes(R.string.pref__keyboard__space_bar_language_display_enabled__summary),
)
DialogSliderPreference(
primaryPref = prefs.keyboard.fontSizeMultiplierPortrait,
secondaryPref = prefs.keyboard.fontSizeMultiplierLandscape,

View File

@@ -33,9 +33,12 @@ import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.components.FlorisWarningCard
import dev.patrickgold.florisboard.common.observeAsNonNullState
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.keyboard.LayoutType
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.subtypeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
@@ -64,6 +67,11 @@ fun LocalizationScreen() = FlorisScreen {
}
content {
ListPreference(
prefs.localization.displayLanguageNamesIn,
title = stringRes(R.string.settings__localization__display_language_names_in__label),
entries = DisplayLanguageNamesIn.listEntries(),
)
PreferenceGroup(title = stringRes(R.string.settings__localization__group_subtypes__label)) {
val subtypes by subtypeManager.subtypes.observeAsNonNullState()
if (subtypes.isNullOrEmpty()) {
@@ -74,6 +82,7 @@ fun LocalizationScreen() = FlorisScreen {
} else {
val currencySets by keyboardManager.resources.currencySets.observeAsNonNullState()
val layouts by keyboardManager.resources.layouts.observeAsNonNullState()
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
for (subtype in subtypes) {
val cMeta = layouts[LayoutType.CHARACTERS]?.get(subtype.layoutMap.characters)
val sMeta = layouts[LayoutType.SYMBOLS]?.get(subtype.layoutMap.symbols)
@@ -85,7 +94,10 @@ fun LocalizationScreen() = FlorisScreen {
"currency_set_name" to (currMeta?.label ?: "null"),
)
Preference(
title = subtype.primaryLocale.displayName(),
title = when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> subtype.primaryLocale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> subtype.primaryLocale.displayName(subtype.primaryLocale)
},
summary = summary,
onClick = { navController.navigate(
Routes.Settings.SubtypeEdit(subtype.id)

View File

@@ -43,10 +43,13 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.components.florisScrollbar
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
const val SelectLocaleScreenResultLanguageTag = "SelectLocaleScreen.languageTag"
@@ -56,9 +59,19 @@ fun SelectLocaleScreen() = FlorisScreen {
title = stringRes(R.string.settings__localization__subtype_select_locale)
scrollable = false
val prefs by florisPreferenceModel()
val navController = LocalNavController.current
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
var searchTermValue by remember { mutableStateOf(TextFieldValue()) }
val systemLocales = remember { FlorisLocale.installedSystemLocales().sortedBy { it.displayName() } }
val systemLocales = remember(displayLanguageNamesIn) {
FlorisLocale.installedSystemLocales().sortedBy { locale ->
when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> locale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> locale.displayName(locale)
}.lowercase()
}
}
val filteredSystemLocales = remember(searchTermValue) {
if (searchTermValue.text.isBlank()) {
systemLocales
@@ -121,7 +134,10 @@ fun SelectLocaleScreen() = FlorisScreen {
?.set(SelectLocaleScreenResultLanguageTag, systemLocale.languageTag())
navController.popBackStack()
},
text = systemLocale.displayName(),
text = when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> systemLocale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> systemLocale.displayName(systemLocale)
},
)
}
}

View File

@@ -52,6 +52,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Observer
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisButtonBar
@@ -61,6 +62,7 @@ import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.common.observeAsNonNullState
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.core.SubtypeJsonConfig
import dev.patrickgold.florisboard.ime.core.SubtypeLayoutMap
@@ -68,15 +70,14 @@ import dev.patrickgold.florisboard.ime.core.SubtypePreset
import dev.patrickgold.florisboard.ime.keyboard.LayoutArrangementComponent
import dev.patrickgold.florisboard.ime.keyboard.LayoutType
import dev.patrickgold.florisboard.ime.keyboard.extCorePopupMapping
import dev.patrickgold.florisboard.ime.text.composing.Appender
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.res.ext.ExtensionComponentName
import dev.patrickgold.florisboard.subtypeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import java.util.*
private val SelectComponentName = ExtensionComponentName("00", "00")
private val SelectLayoutMap = SubtypeLayoutMap(
@@ -117,7 +118,7 @@ private class SubtypeEditorState(init: Subtype?) {
val id: MutableState<Long> = mutableStateOf(init?.id ?: -1)
val primaryLocale: MutableState<FlorisLocale> = mutableStateOf(init?.primaryLocale ?: SelectLocale)
val secondaryLocales: MutableState<List<FlorisLocale>> = mutableStateOf(init?.secondaryLocales ?: listOf())
val composer: MutableState<ExtensionComponentName> = mutableStateOf(init?.composer ?: Appender.name)
val composer: MutableState<ExtensionComponentName> = mutableStateOf(init?.composer ?: SelectComponentName)
val currencySet: MutableState<ExtensionComponentName> = mutableStateOf(init?.currencySet ?: SelectComponentName)
val popupMapping: MutableState<ExtensionComponentName> = mutableStateOf(init?.popupMapping ?: SelectComponentName)
val layoutMap: MutableState<SubtypeLayoutMap> = mutableStateOf(init?.layoutMap ?: SelectLayoutMap)
@@ -163,6 +164,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
val selectValue = stringRes(R.string.settings__localization__subtype_select_placeholder)
val selectListValues = remember (selectValue) { listOf(selectValue) }
val prefs by florisPreferenceModel()
val navController = LocalNavController.current
val context = LocalContext.current
val configuration = LocalConfiguration.current
@@ -170,6 +172,8 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
val keyboardManager by context.keyboardManager()
val subtypeManager by context.subtypeManager()
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
val composers by keyboardManager.resources.composers.observeAsNonNullState()
val currencySets by keyboardManager.resources.currencySets.observeAsNonNullState()
val layoutExtensions by keyboardManager.resources.layouts.observeAsNonNullState()
val popupMappings by keyboardManager.resources.popupMappings.observeAsNonNullState()
@@ -181,7 +185,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
}
var primaryLocale by subtypeEditor.primaryLocale
//var secondaryLocales by subtypeEditor.secondaryLocales
//var composer by subtypeEditor.composer
var composer by subtypeEditor.composer
var currencySet by subtypeEditor.currencySet
var popupMapping by subtypeEditor.popupMapping
var layoutMap by subtypeEditor.layoutMap
@@ -288,7 +292,10 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
modifier = Modifier.clickable {
subtypeEditor.applySubtype(suggestedPreset.toSubtype())
},
text = suggestedPreset.locale.displayName(),
text = when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> suggestedPreset.locale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> suggestedPreset.locale.displayName(suggestedPreset.locale)
},
secondaryText = suggestedPreset.preferred.characters.componentId,
)
}
@@ -314,7 +321,10 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
SubtypeProperty(stringRes(R.string.settings__localization__subtype_locale)) {
FlorisDropdownLikeButton(
item = if (primaryLocale == SelectLocale) selectValue else primaryLocale.displayName(),
item = if (primaryLocale == SelectLocale) selectValue else when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> primaryLocale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> primaryLocale.displayName(primaryLocale)
},
isError = showSelectAsError && primaryLocale == SelectLocale,
onClick = {
navController.navigate(Routes.Settings.SelectLocale)
@@ -376,6 +386,24 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
selectListValues = selectListValues,
)
}
SubtypeProperty(stringRes(R.string.settings__localization__subtype_composer)) {
val composerIds = remember(composers) {
SelectListKeys + composers.keys
}
val composerNames = remember(composers) {
selectListValues + composers.values.map { it.label }
}
var expanded by remember { mutableStateOf(false) }
FlorisDropdownMenu(
items = composerNames,
expanded = expanded,
selectedIndex = composerIds.indexOf(composer).coerceAtLeast(0),
isError = showSelectAsError && composer == SelectComponentName,
onSelectItem = { composer = composerIds[it] },
onExpandRequest = { expanded = true },
onDismissRequest = { expanded = false },
)
}
SubtypeProperty(stringRes(R.string.settings__localization__subtype_currency_set)) {
val currencySetIds = remember(currencySets) {
SelectListKeys + currencySets.keys
@@ -474,7 +502,10 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
subtypeEditor.applySubtype(subtypePreset.toSubtype())
showSubtypePresetsDialog = false
},
text = subtypePreset.locale.displayName(),
text = when (displayLanguageNamesIn) {
DisplayLanguageNamesIn.SYSTEM_LOCALE -> subtypePreset.locale.displayName()
DisplayLanguageNamesIn.NATIVE_LOCALE -> subtypePreset.locale.displayName(subtypePreset.locale)
},
secondaryText = subtypePreset.preferred.characters.componentId,
)
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.media
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.res.pluralsRes
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.ListPreference
@OptIn(ExperimentalJetPrefDatastoreUi::class)
@Composable
fun MediaScreen() = FlorisScreen {
title = stringRes(R.string.settings__media__title)
previewFieldVisible = true
iconSpaceReserved = false
content {
ListPreference(
prefs.media.emojiPreferredSkinTone,
title = stringRes(R.string.prefs__media__emoji_preferred_skin_tone),
entries = EmojiSkinTone.listEntries(),
)
val maxSize by prefs.media.emojiRecentlyUsedMaxSize.observeAsState()
DialogSliderPreference(
prefs.media.emojiRecentlyUsedMaxSize,
title = stringRes(R.string.prefs__media__emoji_recently_used_max_size),
summary = if (maxSize == 0) {
stringRes(R.string.general__unlimited)
} else {
pluralsRes(R.plurals.unit__items__written, maxSize)
},
min = 0,
max = 120,
stepIncrement = 1,
)
}
}

View File

@@ -21,6 +21,7 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.ime.text.smartbar.SecondaryRowPlacement
import dev.patrickgold.florisboard.ime.text.smartbar.SmartbarRowType
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
@@ -36,40 +37,42 @@ fun SmartbarScreen() = FlorisScreen {
title = stringRes(R.string.pref__smartbar__enabled__label),
summary = stringRes(R.string.pref__smartbar__enabled__summary),
)
SwitchPreference(
prefs.smartbar.flipToggles,
title = stringRes(R.string.pref__smartbar__flip_toggles__label),
summary = stringRes(R.string.pref__smartbar__flip_toggles__summary),
enabledIf = { prefs.smartbar.enabled isEqualTo true },
)
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_primary_row__label)) {
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_primary_actions__label)) {
SwitchPreference(
prefs.smartbar.primaryRowFlipToggles,
title = stringRes(R.string.pref__smartbar__primary_row_flip_toggles__label),
summary = stringRes(R.string.pref__smartbar__primary_row_flip_toggles__summary),
enabledIf = { prefs.smartbar.enabled isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_secondary_row__label)) {
SwitchPreference(
prefs.smartbar.secondaryRowEnabled,
title = stringRes(R.string.pref__smartbar__secondary_row_enabled__label),
summary = stringRes(R.string.pref__smartbar__secondary_row_enabled__summary),
prefs.smartbar.primaryActionsAutoExpandCollapse,
title = stringRes(R.string.pref__smartbar__primary_actions_auto_expand_collapse__label),
summary = stringRes(R.string.pref__smartbar__primary_actions_auto_expand_collapse__summary),
enabledIf = { prefs.smartbar.enabled isEqualTo true },
)
ListPreference(
prefs.smartbar.secondaryRowPlacement,
title = stringRes(R.string.pref__smartbar__secondary_row_placement__label),
entries = SecondaryRowPlacement.listEntries(),
enabledIf = {
(prefs.smartbar.enabled isEqualTo true) && (prefs.smartbar.secondaryRowEnabled isEqualTo true)
},
prefs.smartbar.primaryActionsRowType,
title = stringRes(R.string.pref__smartbar__any_row_type__label),
entries = SmartbarRowType.listEntries(),
enabledIf = { prefs.smartbar.enabled isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_action_row__label)) {
SwitchPreference(
prefs.smartbar.actionRowAutoExpandCollapse,
title = stringRes(R.string.pref__smartbar__action_row_auto_expand_collapse__label),
summary = stringRes(R.string.pref__smartbar__action_row_auto_expand_collapse__summary),
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_secondary_actions__label)) {
ListPreference(
listPref = prefs.smartbar.secondaryActionsPlacement,
switchPref = prefs.smartbar.secondaryActionsEnabled,
title = stringRes(R.string.pref__smartbar__secondary_actions_enabled__label),
entries = SecondaryRowPlacement.listEntries(),
enabledIf = { prefs.smartbar.enabled isEqualTo true },
)
ListPreference(
prefs.smartbar.secondaryActionsRowType,
title = stringRes(R.string.pref__smartbar__any_row_type__label),
entries = SmartbarRowType.listEntries(),
enabledIf = { prefs.smartbar.enabled isEqualTo true && prefs.smartbar.secondaryActionsEnabled isEqualTo true },
)
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.theme
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.common.kotlin.curlyFormat
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
/**
* DisplayColorsAs indicates how color strings should be visually presented to the user.
*/
enum class DisplayColorsAs {
HEX8,
RGBA;
companion object {
@Composable
fun listEntries() = listPrefEntries {
entry(
key = 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 = 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,
)
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.theme
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
/**
* DisplayPreviewAfterDialogs indicates if the keyboard should auto-open after closing
* any dialog. This is useful because the dialog always hides the keyboard and one may
* not want to always press the preview field again.
*/
enum class DisplayKbdAfterDialogs {
ALWAYS,
NEVER,
REMEMBER;
companion object {
@Composable
fun listEntries() = listPrefEntries {
entry(
key = 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 = 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 = REMEMBER,
label = stringRes(R.string.enum__display_kbd_after_dialogs__remember),
description = stringRes(R.string.enum__display_kbd_after_dialogs__remember__description),
showDescriptionOnlyIfSelected = true,
)
}
}
}

View File

@@ -23,14 +23,18 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -39,7 +43,14 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
@@ -50,22 +61,27 @@ import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.DpSizeSaver
import dev.patrickgold.florisboard.app.ui.components.FlorisChip
import dev.patrickgold.florisboard.app.ui.components.FlorisDropdownMenu
import dev.patrickgold.florisboard.app.ui.components.FlorisIconButton
import dev.patrickgold.florisboard.app.ui.components.FlorisOutlinedTextField
import dev.patrickgold.florisboard.app.ui.components.FlorisTextButton
import dev.patrickgold.florisboard.app.ui.components.rippleClickable
import dev.patrickgold.florisboard.common.ValidationResult
import dev.patrickgold.florisboard.common.kotlin.curlyFormat
import dev.patrickgold.florisboard.common.kotlin.toStringWithoutDotZero
import dev.patrickgold.florisboard.common.rememberValidationResult
import dev.patrickgold.florisboard.common.stripUnicodeCtrlChars
import dev.patrickgold.florisboard.res.ext.ExtensionValidation
import dev.patrickgold.florisboard.snygg.SnyggLevel
import dev.patrickgold.florisboard.snygg.SnyggPropertySetSpec
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerPercentageShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerPercentShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggDefinedVarValue
import dev.patrickgold.florisboard.snygg.value.SnyggDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggDpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggImplicitInheritValue
import dev.patrickgold.florisboard.snygg.value.SnyggPercentageShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggPercentShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerPercentageShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerPercentShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggSolidColorValue
import dev.patrickgold.florisboard.snygg.value.SnyggSpSizeValue
@@ -109,6 +125,7 @@ internal fun EditPropertyDialog(
propertySetSpec: SnyggPropertySetSpec?,
initProperty: PropertyInfo,
level: SnyggLevel,
displayColorsAs: DisplayColorsAs,
definedVariables: Map<String, SnyggValue>,
onConfirmNewValue: (String, SnyggValue) -> Boolean,
onDelete: () -> Unit,
@@ -138,7 +155,7 @@ internal fun EditPropertyDialog(
}
fun isPropertyNameValid(): Boolean {
return propertyName.isNotBlank() && propertyName != SnyggEmptyPropertyInfoForAdding.name
return propertyNameValidation.isValid() && propertyName != SnyggEmptyPropertyInfoForAdding.name
}
fun isPropertyValueValid(): Boolean {
@@ -222,6 +239,7 @@ internal fun EditPropertyDialog(
value = propertyValue,
onValueChange = { propertyValue = it },
level = level,
displayColorsAs = displayColorsAs,
definedVariables = definedVariables,
isError = showSelectAsError && !isPropertyValueValid(),
)
@@ -262,10 +280,14 @@ private fun PropertyNameInput(
onDismissRequest = { propertiesExpanded = false },
)
} else {
val focusManager = LocalFocusManager.current
FlorisOutlinedTextField(
value = name,
onValueChange = onNameChange,
enabled = isAddPropertyDialog,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
singleLine = true,
showValidationHint = isAddPropertyDialog,
showValidationError = showSelectAsError,
validationResult = nameValidation,
@@ -309,6 +331,7 @@ private fun PropertyValueEditor(
value: SnyggValue,
onValueChange: (SnyggValue) -> Unit,
level: SnyggLevel,
displayColorsAs: DisplayColorsAs,
definedVariables: Map<String, SnyggValue>,
isError: Boolean = false,
) {
@@ -347,33 +370,129 @@ private fun PropertyValueEditor(
}
}
is SnyggSolidColorValue -> {
Column(modifier = Modifier.padding(top = 8.dp)) {
Row(
modifier = Modifier.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
val colorPickerState = rememberJetPrefColorPickerState(initColor = value.color)
val colorPickerStr = translatePropertyValue(value, level, displayColorsAs)
var showEditColorStrDialog by rememberSaveable { mutableStateOf(false) }
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
Column(modifier = Modifier.padding(top = 8.dp)) {
Row(
modifier = Modifier
.padding(end = 12.dp)
.weight(1f),
text = value.encoder().serialize(value).getOrDefault("?"),
)
SnyggValueIcon(
value = value,
definedVariables = definedVariables,
.rippleClickable {
showEditColorStrDialog = true
}
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.padding(end = 12.dp)
.weight(1f),
text = colorPickerStr,
style = MaterialTheme.typography.body2,
fontFamily = FontFamily.Monospace,
)
SnyggValueIcon(
value = value,
definedVariables = definedVariables,
)
}
JetPrefColorPicker(
onColorChange = { onValueChange(SnyggSolidColorValue(it)) },
state = colorPickerState,
)
}
val state = rememberJetPrefColorPickerState(initColor = value.color)
JetPrefColorPicker(
onColorChange = { onValueChange(SnyggSolidColorValue(it)) },
state = state,
}
if (showEditColorStrDialog) {
var showValidationErrors by rememberSaveable { mutableStateOf(false) }
var showSyntaxHelp by rememberSaveable { mutableStateOf(false) }
var colorStr by rememberSaveable { mutableStateOf(colorPickerStr.stripUnicodeCtrlChars()) }
val colorStrValidation = rememberValidationResult(ExtensionValidation.SnyggSolidColorValue, colorStr)
JetPrefAlertDialog(
title = stringRes(R.string.settings__theme_editor__property_value_color_dialog_title),
confirmLabel = stringRes(R.string.action__apply),
onConfirm = {
if (colorStrValidation.isInvalid()) {
showValidationErrors = true
} else {
val newValue = SnyggSolidColorValue.deserialize(colorStr.trim()).getOrThrow()
onValueChange(newValue)
colorPickerState.setColor((newValue as SnyggSolidColorValue).color)
showEditColorStrDialog = false
}
},
dismissLabel = stringRes(R.string.action__cancel),
onDismiss = {
showEditColorStrDialog = false
},
trailingIconTitle = {
FlorisIconButton(
onClick = { showSyntaxHelp = !showSyntaxHelp },
modifier = Modifier.offset(x = 12.dp),
icon = painterResource(R.drawable.ic_help_outline),
)
},
) {
Column {
AnimatedVisibility(visible = showSyntaxHelp) {
Column(modifier = Modifier.padding(bottom = 16.dp)) {
Text(text = "Supported color string syntaxes:")
Text(
text = """
#RRGGBBAA
-> all in 00h..FFh
#RRGGBB
-> all in 00h..FFh
rgba(r,g,b,a)
-> r,g,b in 0..255
-> a in 0.0..1.0
rgb(r,g,b)
-> r,g,b in 0..255
""".trimIndent(),
style = MaterialTheme.typography.body2,
fontFamily = FontFamily.Monospace,
)
}
}
FlorisOutlinedTextField(
value = colorStr,
onValueChange = { colorStr = it },
showValidationError = showValidationErrors,
validationResult = colorStrValidation,
)
}
}
}
}
is SnyggDpSizeValue -> {
var sizeStr by remember {
val dp = value.dp.takeUnless { it.isUnspecified } ?: SnyggDpSizeValue.defaultValue().dp
mutableStateOf(dp.value.toStringWithoutDotZero())
}
Row(
modifier = Modifier.padding(top = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
FlorisOutlinedTextField(
modifier = Modifier.weight(1f),
value = sizeStr,
onValueChange = { value ->
sizeStr = value
val size = sizeStr.toFloatOrNull()?.let { SnyggDpSizeValue(it.dp) }
onValueChange(size ?: SnyggDpSizeValue(Dp.Unspecified))
},
isError = value.dp.isUnspecified || value.dp.value < 0f,
)
Text(
modifier = Modifier.padding(start = 8.dp),
text = "dp",
fontFamily = FontFamily.Monospace,
)
}
}
is SnyggSpSizeValue -> {
var sizeStr by remember {
val sp = value.sp.takeUnless { it.isUnspecified } ?: SnyggSpSizeValue.defaultValue().sp
mutableStateOf(sp.value.toString())
mutableStateOf(sp.value.toStringWithoutDotZero())
}
Row(
modifier = Modifier.padding(top = 8.dp),
@@ -441,10 +560,10 @@ private fun PropertyValueEditor(
LaunchedEffect(shape) {
onValueChange(when (value) {
is SnyggCutCornerDpShapeValue -> {
SnyggCutCornerDpShapeValue(shape as CutCornerShape, topStart, topEnd, bottomEnd, bottomStart)
SnyggCutCornerDpShapeValue(topStart, topEnd, bottomEnd, bottomStart)
}
is SnyggRoundedCornerDpShapeValue -> {
SnyggRoundedCornerDpShapeValue(shape as RoundedCornerShape, topStart, topEnd, bottomEnd, bottomStart)
SnyggRoundedCornerDpShapeValue(topStart, topEnd, bottomEnd, bottomStart)
}
})
}
@@ -461,7 +580,7 @@ private fun PropertyValueEditor(
showDialogInitDp = topStart
showDialogForCorner = ShapeCorner.TOP_START
},
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to topStart.value),
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to topStart.value.toStringWithoutDotZero()),
shape = MaterialTheme.shapes.medium,
)
FlorisChip(
@@ -469,7 +588,7 @@ private fun PropertyValueEditor(
showDialogInitDp = bottomStart
showDialogForCorner = ShapeCorner.BOTTOM_START
},
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to bottomStart.value),
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to bottomStart.value.toStringWithoutDotZero()),
shape = MaterialTheme.shapes.medium,
)
}
@@ -484,7 +603,7 @@ private fun PropertyValueEditor(
showDialogInitDp = topEnd
showDialogForCorner = ShapeCorner.TOP_END
},
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to topEnd.value),
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to topEnd.value.toStringWithoutDotZero()),
shape = MaterialTheme.shapes.medium,
)
FlorisChip(
@@ -492,7 +611,7 @@ private fun PropertyValueEditor(
showDialogInitDp = bottomEnd
showDialogForCorner = ShapeCorner.BOTTOM_END
},
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to bottomEnd.value),
text = stringRes(R.string.unit__display_pixel__symbol).curlyFormat("v" to bottomEnd.value.toStringWithoutDotZero()),
shape = MaterialTheme.shapes.medium,
)
}
@@ -501,7 +620,7 @@ private fun PropertyValueEditor(
if (dialogForCorner != null) {
var showValidationErrors by rememberSaveable { mutableStateOf(false) }
var size by rememberSaveable {
mutableStateOf(showDialogInitDp.value.toString())
mutableStateOf(showDialogInitDp.value.toStringWithoutDotZero())
}
val sizeValidation = rememberValidationResult(ExtensionValidation.SnyggDpShapeValue, size)
JetPrefAlertDialog(
@@ -553,7 +672,7 @@ private fun PropertyValueEditor(
}
}
}
is SnyggPercentageShapeValue -> {
is SnyggPercentShapeValue -> {
var showDialogInitPercentage by rememberSaveable {
mutableStateOf(0)
}
@@ -562,45 +681,45 @@ private fun PropertyValueEditor(
}
var topStart by rememberSaveable {
mutableStateOf(when (value) {
is SnyggCutCornerPercentageShapeValue -> value.topStart
is SnyggRoundedCornerPercentageShapeValue -> value.topStart
is SnyggCutCornerPercentShapeValue -> value.topStart
is SnyggRoundedCornerPercentShapeValue -> value.topStart
})
}
var topEnd by rememberSaveable {
mutableStateOf(when (value) {
is SnyggCutCornerPercentageShapeValue -> value.topEnd
is SnyggRoundedCornerPercentageShapeValue -> value.topEnd
is SnyggCutCornerPercentShapeValue -> value.topEnd
is SnyggRoundedCornerPercentShapeValue -> value.topEnd
})
}
var bottomEnd by rememberSaveable {
mutableStateOf(when (value) {
is SnyggCutCornerPercentageShapeValue -> value.bottomEnd
is SnyggRoundedCornerPercentageShapeValue -> value.bottomEnd
is SnyggCutCornerPercentShapeValue -> value.bottomEnd
is SnyggRoundedCornerPercentShapeValue -> value.bottomEnd
})
}
var bottomStart by rememberSaveable {
mutableStateOf(when (value) {
is SnyggCutCornerPercentageShapeValue -> value.bottomStart
is SnyggRoundedCornerPercentageShapeValue -> value.bottomStart
is SnyggCutCornerPercentShapeValue -> value.bottomStart
is SnyggRoundedCornerPercentShapeValue -> value.bottomStart
})
}
val shape = remember(topStart, topEnd, bottomEnd, bottomStart) {
when (value) {
is SnyggCutCornerPercentageShapeValue -> {
is SnyggCutCornerPercentShapeValue -> {
CutCornerShape(topStart, topEnd, bottomEnd, bottomStart)
}
is SnyggRoundedCornerPercentageShapeValue -> {
is SnyggRoundedCornerPercentShapeValue -> {
RoundedCornerShape(topStart, topEnd, bottomEnd, bottomStart)
}
}
}
LaunchedEffect(shape) {
onValueChange(when (value) {
is SnyggCutCornerPercentageShapeValue -> {
SnyggCutCornerPercentageShapeValue(shape as CutCornerShape, topStart, topEnd, bottomEnd, bottomStart)
is SnyggCutCornerPercentShapeValue -> {
SnyggCutCornerPercentShapeValue(topStart, topEnd, bottomEnd, bottomStart)
}
is SnyggRoundedCornerPercentageShapeValue -> {
SnyggRoundedCornerPercentageShapeValue(shape as RoundedCornerShape, topStart, topEnd, bottomEnd, bottomStart)
is SnyggRoundedCornerPercentShapeValue -> {
SnyggRoundedCornerPercentShapeValue(topStart, topEnd, bottomEnd, bottomStart)
}
})
}
@@ -659,7 +778,7 @@ private fun PropertyValueEditor(
var size by rememberSaveable {
mutableStateOf(showDialogInitPercentage.toString())
}
val sizeValidation = rememberValidationResult(ExtensionValidation.SnyggPercentageShapeValue, size)
val sizeValidation = rememberValidationResult(ExtensionValidation.SnyggPercentShapeValue, size)
JetPrefAlertDialog(
title = dialogForCorner.label(),
confirmLabel = stringRes(R.string.action__apply),

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.theme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.snygg.SnyggLevel
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
private val FineTuneContentPadding = PaddingValues(horizontal = 8.dp)
@Composable
fun FineTuneDialog(onDismiss: () -> Unit) {
JetPrefAlertDialog(
title = stringRes(R.string.settings__theme_editor__fine_tune__title),
onDismiss = onDismiss,
contentPadding = FineTuneContentPadding,
) {
PreferenceLayout(florisPreferenceModel(), iconSpaceReserved = false) {
ListPreference(
listPref = prefs.theme.editorLevel,
title = stringRes(R.string.settings__theme_editor__fine_tune__level),
entries = SnyggLevel.listEntries(),
)
ListPreference(
listPref = prefs.theme.editorDisplayColorsAs,
title = stringRes(R.string.settings__theme_editor__fine_tune__display_colors_as),
entries = DisplayColorsAs.listEntries(),
)
ListPreference(
listPref = prefs.theme.editorDisplayKbdAfterDialogs,
title = stringRes(R.string.settings__theme_editor__fine_tune__display_kbd_after_dialogs),
entries = DisplayKbdAfterDialogs.listEntries(),
)
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.theme
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggDefinedVarValue
import dev.patrickgold.florisboard.snygg.value.SnyggDpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggSolidColorValue
import dev.patrickgold.florisboard.snygg.value.SnyggSpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggValue
import dev.patrickgold.jetpref.material.ui.checkeredBackground
object SnyggValueIcon {
interface Spec {
val borderWith: Dp
val boxShape: Shape
val elevation: Dp
val gridSize: Dp
val iconSize: Dp
val iconSizeMinusBorder: Dp
}
object Small : Spec {
override val borderWith = Dp.Hairline
override val boxShape = RoundedCornerShape(4.dp)
override val elevation = 4.dp
override val gridSize = 2.dp
override val iconSize = 16.dp
override val iconSizeMinusBorder = 16.dp
}
object Normal : Spec {
override val borderWith = 1.dp
override val boxShape = RoundedCornerShape(8.dp)
override val elevation = 4.dp
override val gridSize = 3.dp
override val iconSize = 24.dp
override val iconSizeMinusBorder = 22.dp
}
}
@Composable
internal fun SnyggValueIcon(
value: SnyggValue,
definedVariables: Map<String, SnyggValue>,
modifier: Modifier = Modifier,
spec: SnyggValueIcon.Spec = SnyggValueIcon.Normal,
) {
when (value) {
is SnyggSolidColorValue -> {
Surface(
modifier = modifier.requiredSize(spec.iconSize),
color = MaterialTheme.colors.background,
elevation = spec.elevation,
shape = spec.boxShape,
) {
Box(
modifier = Modifier
.fillMaxSize()
.checkeredBackground(gridSize = spec.gridSize)
.background(value.color),
)
}
}
is SnyggShapeValue -> {
Box(
modifier = modifier
.requiredSize(spec.iconSizeMinusBorder)
.border(spec.borderWith, MaterialTheme.colors.onBackground, value.alwaysPercentShape())
)
}
is SnyggDpSizeValue -> {
Icon(
modifier = modifier.requiredSize(spec.iconSize),
painter = painterResource(R.drawable.ic_straighten),
contentDescription = null,
)
}
is SnyggSpSizeValue -> {
Icon(
modifier = modifier.requiredSize(spec.iconSize),
painter = painterResource(R.drawable.ic_format_size),
contentDescription = null,
)
}
is SnyggDefinedVarValue -> {
val realValue = definedVariables[value.key]
if (realValue == null) {
Icon(
modifier = modifier.requiredSize(spec.iconSize),
painter = painterResource(R.drawable.ic_link),
contentDescription = null,
)
} else {
val smallSpec = SnyggValueIcon.Small
Box(modifier = modifier
.requiredSize(spec.iconSize)
.offset(y = (-2).dp)) {
SnyggValueIcon(
modifier = Modifier.offset(x = 8.dp, y = 8.dp),
value = realValue,
definedVariables = definedVariables,
spec = smallSpec,
)
Box(
modifier = Modifier
.offset(x = 1.dp)
.requiredSize(smallSpec.iconSize)
.padding(vertical = 2.dp)
.background(MaterialTheme.colors.background, spec.boxShape),
)
Icon(
modifier = Modifier.requiredSize(smallSpec.iconSize),
painter = painterResource(R.drawable.ic_link),
contentDescription = null,
)
}
}
}
else -> {
// Render nothing
}
}
}
private const val AlwaysPercentUpscaleFactor = 3
fun SnyggShapeValue.alwaysPercentShape(): Shape {
return when (this) {
is SnyggRoundedCornerDpShapeValue -> {
RoundedCornerShape(
this.topStart.value.toInt() * AlwaysPercentUpscaleFactor,
this.topEnd.value.toInt() * AlwaysPercentUpscaleFactor,
this.bottomEnd.value.toInt() * AlwaysPercentUpscaleFactor,
this.bottomStart.value.toInt() * AlwaysPercentUpscaleFactor,
)
}
is SnyggCutCornerDpShapeValue -> {
CutCornerShape(
this.topStart.value.toInt() * AlwaysPercentUpscaleFactor,
this.topEnd.value.toInt() * AlwaysPercentUpscaleFactor,
this.bottomEnd.value.toInt() * AlwaysPercentUpscaleFactor,
this.bottomStart.value.toInt() * AlwaysPercentUpscaleFactor,
)
}
else -> this.shape
}
}

View File

@@ -18,29 +18,23 @@ package dev.patrickgold.florisboard.app.ui.settings.theme
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -58,16 +52,17 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.google.accompanist.insets.LocalWindowInsets
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisIconButton
import dev.patrickgold.florisboard.app.ui.components.FlorisOutlinedBox
@@ -80,9 +75,7 @@ import dev.patrickgold.florisboard.app.ui.components.rememberPreviewFieldControl
import dev.patrickgold.florisboard.app.ui.components.rippleClickable
import dev.patrickgold.florisboard.app.ui.ext.ExtensionComponentView
import dev.patrickgold.florisboard.common.android.showLongToast
import dev.patrickgold.florisboard.common.android.showShortToast
import dev.patrickgold.florisboard.common.rememberValidationResult
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.ime.theme.FlorisImeUiSpec
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
@@ -92,7 +85,6 @@ import dev.patrickgold.florisboard.res.cache.CacheManager
import dev.patrickgold.florisboard.res.ext.ExtensionValidation
import dev.patrickgold.florisboard.res.io.readJson
import dev.patrickgold.florisboard.res.io.subFile
import dev.patrickgold.florisboard.snygg.Snygg
import dev.patrickgold.florisboard.snygg.SnyggLevel
import dev.patrickgold.florisboard.snygg.SnyggPropertySetEditor
import dev.patrickgold.florisboard.snygg.SnyggPropertySetSpec
@@ -102,22 +94,8 @@ import dev.patrickgold.florisboard.snygg.SnyggStylesheetEditor
import dev.patrickgold.florisboard.snygg.SnyggStylesheetJsonConfig
import dev.patrickgold.florisboard.snygg.definedVariablesRule
import dev.patrickgold.florisboard.snygg.isDefinedVariablesRule
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerPercentageShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggDefinedVarValue
import dev.patrickgold.florisboard.snygg.value.SnyggDpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggExplicitInheritValue
import dev.patrickgold.florisboard.snygg.value.SnyggImplicitInheritValue
import dev.patrickgold.florisboard.snygg.value.SnyggPercentageSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRectangleShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerPercentageShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggSolidColorValue
import dev.patrickgold.florisboard.snygg.value.SnyggSpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggValue
import dev.patrickgold.florisboard.snygg.value.SnyggValueEncoder
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.coroutines.delay
@@ -136,6 +114,7 @@ fun ThemeEditorScreen(
title = stringRes(R.string.ext__editor__edit_component__title_theme)
scrollable = false
val prefs by florisPreferenceModel()
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val themeManager by context.themeManager()
@@ -164,12 +143,16 @@ fun ThemeEditorScreen(
}.also { editor.stylesheetEditor = it }
}
var snyggLevel by rememberSaveable { mutableStateOf(SnyggLevel.ADVANCED) }
val snyggLevel by prefs.theme.editorLevel.observeAsState()
val displayColorsAs by prefs.theme.editorDisplayColorsAs.observeAsState()
val displayKbdAfterDialogs by prefs.theme.editorDisplayKbdAfterDialogs.observeAsState()
var oldFocusState by remember { mutableStateOf(false) }
var snyggRuleToEdit by rememberSaveable(stateSaver = SnyggRule.Saver) { mutableStateOf(null) }
var snyggPropertyToEdit by remember { mutableStateOf<PropertyInfo?>(null) }
var snyggPropertySetForEditing = remember<SnyggPropertySetEditor?> { null }
var snyggPropertySetSpecForEditing = remember<SnyggPropertySetSpec?> { null }
var showEditComponentMetaDialog by rememberSaveable { mutableStateOf(false) }
var showFineTuneDialog by rememberSaveable { mutableStateOf(false) }
fun handleBackPress() {
workspace.currentAction = null
@@ -184,15 +167,8 @@ fun ThemeEditorScreen(
actions {
FlorisIconButton(
onClick = {
snyggLevel = when (snyggLevel) {
SnyggLevel.BASIC -> SnyggLevel.ADVANCED
SnyggLevel.ADVANCED -> SnyggLevel.DEVELOPER
SnyggLevel.DEVELOPER -> SnyggLevel.BASIC
}
context.showShortToast("level = $snyggLevel")
},
icon = painterResource(R.drawable.ic_language),
onClick = { showFineTuneDialog = true },
icon = painterResource(R.drawable.ic_tune),
)
}
@@ -218,13 +194,28 @@ fun ThemeEditorScreen(
handleBackPress()
}
LaunchedEffect(showEditComponentMetaDialog, snyggRuleToEdit, snyggPropertyToEdit) {
val visible = showEditComponentMetaDialog || snyggRuleToEdit != null || snyggPropertyToEdit != null
val isImeVisible = LocalWindowInsets.current.ime.isVisible
LaunchedEffect(showEditComponentMetaDialog, showFineTuneDialog, snyggRuleToEdit, snyggPropertyToEdit) {
val visible = showEditComponentMetaDialog || showFineTuneDialog ||
snyggRuleToEdit != null || snyggPropertyToEdit != null
if (visible) {
oldFocusState = isImeVisible
focusManager.clearFocus()
} else {
delay(250)
previewFieldController.focusRequester.requestFocus()
when (displayKbdAfterDialogs) {
DisplayKbdAfterDialogs.ALWAYS -> {
previewFieldController.focusRequester.requestFocus()
}
DisplayKbdAfterDialogs.NEVER -> {
// Do nothing
}
DisplayKbdAfterDialogs.REMEMBER -> {
if (oldFocusState) {
previewFieldController.focusRequester.requestFocus()
}
}
}
}
}
@@ -315,7 +306,7 @@ fun ThemeEditorScreen(
snyggPropertyToEdit = PropertyInfo(propertyName, propertyValue)
},
text = translatePropertyName(propertyName, snyggLevel),
secondaryText = translatePropertyValue(propertyValue, snyggLevel),
secondaryText = translatePropertyValue(propertyValue, snyggLevel, displayColorsAs),
singleLineSecondaryText = true,
trailing = { SnyggValueIcon(propertyValue, definedVariables) },
)
@@ -339,6 +330,10 @@ fun ThemeEditorScreen(
)
}
if (showFineTuneDialog) {
FineTuneDialog(onDismiss = { showFineTuneDialog = false })
}
val ruleToEdit = snyggRuleToEdit
if (ruleToEdit != null) {
EditRuleDialog(
@@ -396,6 +391,7 @@ fun ThemeEditorScreen(
propertySetSpec = snyggPropertySetSpecForEditing,
initProperty = propertyToEdit,
level = snyggLevel,
displayColorsAs = displayColorsAs,
definedVariables = definedVariables,
onConfirmNewValue = { name, value ->
val properties = snyggPropertySetForEditing?.properties ?: return@EditPropertyDialog false
@@ -476,6 +472,7 @@ private fun ComponentMetaEditorDialog(
FlorisOutlinedTextField(
value = id,
onValueChange = { id = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii),
singleLine = true,
showValidationError = showValidationErrors,
validationResult = idValidation,
@@ -516,6 +513,7 @@ private fun ComponentMetaEditorDialog(
FlorisOutlinedTextField(
value = stylesheetPath,
onValueChange = { stylesheetPath = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii),
singleLine = true,
placeholder = if (stylesheetPath.isEmpty()) {
ThemeExtensionComponent.defaultStylesheetPath(id.trim())
@@ -646,221 +644,3 @@ internal fun DialogProperty(
content()
}
}
object SnyggValueIcon {
interface Spec {
val borderWith: Dp
val boxShape: Shape
val elevation: Dp
val iconSize: Dp
val iconSizeMinusBorder: Dp
}
object Small : Spec {
override val borderWith = Dp.Hairline
override val boxShape = RoundedCornerShape(4.dp)
override val elevation = 4.dp
override val iconSize = 16.dp
override val iconSizeMinusBorder = 16.dp
}
object Normal : Spec {
override val borderWith = 1.dp
override val boxShape = RoundedCornerShape(8.dp)
override val elevation = 4.dp
override val iconSize = 24.dp
override val iconSizeMinusBorder = 22.dp
}
}
@Composable
internal fun SnyggValueIcon(
value: SnyggValue,
definedVariables: Map<String, SnyggValue>,
modifier: Modifier = Modifier,
spec: SnyggValueIcon.Spec = SnyggValueIcon.Normal,
) {
when (value) {
is SnyggSolidColorValue -> {
Surface(
modifier = modifier.requiredSize(spec.iconSize),
color = MaterialTheme.colors.background,
elevation = spec.elevation,
shape = spec.boxShape,
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(value.color),
)
}
}
is SnyggShapeValue -> {
Box(
modifier = modifier
.requiredSize(spec.iconSizeMinusBorder)
.border(spec.borderWith, MaterialTheme.colors.onBackground, value.shape)
)
}
is SnyggSpSizeValue -> {
Icon(
modifier = modifier.requiredSize(spec.iconSize),
painter = painterResource(R.drawable.ic_format_size),
contentDescription = null,
)
}
is SnyggDefinedVarValue -> {
val realValue = definedVariables[value.key]
if (realValue == null) {
Icon(
modifier = modifier.requiredSize(spec.iconSize),
painter = painterResource(R.drawable.ic_link),
contentDescription = null,
)
} else {
val smallSpec = SnyggValueIcon.Small
Box(modifier = modifier
.requiredSize(spec.iconSize)
.offset(y = (-2).dp)) {
SnyggValueIcon(
modifier = Modifier.offset(x = 8.dp, y = 8.dp),
value = realValue,
definedVariables = definedVariables,
spec = smallSpec,
)
Box(
modifier = Modifier
.offset(x = 1.dp)
.requiredSize(smallSpec.iconSize)
.padding(vertical = 2.dp)
.background(MaterialTheme.colors.background, spec.boxShape),
)
Icon(
modifier = Modifier.requiredSize(smallSpec.iconSize),
painter = painterResource(R.drawable.ic_link),
contentDescription = null,
)
}
}
}
else -> {
// Render nothing
}
}
}
@Composable
internal fun translateElementName(rule: SnyggRule, level: SnyggLevel): String {
return translateElementName(rule.element, level) ?: remember {
buildString {
if (rule.isAnnotation) {
append(SnyggRule.ANNOTATION_MARKER)
}
append(rule.element)
}
}
}
@Composable
internal fun translateElementName(element: String, level: SnyggLevel): String? {
return when (level) {
SnyggLevel.DEVELOPER -> null
else -> when (element) {
"defines" -> R.string.snygg__rule_element__defines
FlorisImeUi.Keyboard -> R.string.snygg__rule_element__keyboard
FlorisImeUi.Key -> R.string.snygg__rule_element__key
FlorisImeUi.KeyHint -> R.string.snygg__rule_element__key_hint
FlorisImeUi.KeyPopup -> R.string.snygg__rule_element__key_popup
FlorisImeUi.ClipboardHeader -> R.string.snygg__rule_element__clipboard_header
FlorisImeUi.ClipboardItem -> R.string.snygg__rule_element__clipboard_item
FlorisImeUi.ClipboardItemPopup -> R.string.snygg__rule_element__clipboard_item_popup
FlorisImeUi.OneHandedPanel -> R.string.snygg__rule_element__one_handed_panel
FlorisImeUi.SmartbarPrimaryRow -> R.string.snygg__rule_element__smartbar_primary_row
FlorisImeUi.SmartbarPrimaryActionRowToggle -> R.string.snygg__rule_element__smartbar_primary_action_row_toggle
FlorisImeUi.SmartbarPrimarySecondaryRowToggle -> R.string.snygg__rule_element__smartbar_primary_secondary_row_toggle
FlorisImeUi.SmartbarSecondaryRow -> R.string.snygg__rule_element__smartbar_secondary_row
FlorisImeUi.SmartbarActionRow -> R.string.snygg__rule_element__smartbar_action_row
FlorisImeUi.SmartbarActionButton -> R.string.snygg__rule_element__smartbar_action_button
FlorisImeUi.SmartbarCandidateRow -> R.string.snygg__rule_element__smartbar_candidate_row
FlorisImeUi.SmartbarCandidateWord -> R.string.snygg__rule_element__smartbar_candidate_word
FlorisImeUi.SmartbarCandidateClip -> R.string.snygg__rule_element__smartbar_candidate_clip
FlorisImeUi.SmartbarCandidateSpacer -> R.string.snygg__rule_element__smartbar_candidate_spacer
FlorisImeUi.SmartbarKey -> R.string.snygg__rule_element__smartbar_key
FlorisImeUi.SystemNavBar -> R.string.snygg__rule_element__system_nav_bar
else -> null
}
}.let { if (it != null) { stringRes(it) } else { null } }
}
@Composable
internal fun translatePropertyName(propertyName: String, level: SnyggLevel): String {
return when (level) {
SnyggLevel.DEVELOPER -> null
else -> when (propertyName) {
Snygg.Width -> R.string.snygg__property_name__width
Snygg.Height -> R.string.snygg__property_name__height
Snygg.Background -> R.string.snygg__property_name__background
Snygg.Foreground -> R.string.snygg__property_name__foreground
Snygg.Border -> R.string.snygg__property_name__border
Snygg.BorderTop -> R.string.snygg__property_name__border_top
Snygg.BorderBottom -> R.string.snygg__property_name__border_bottom
Snygg.BorderStart -> R.string.snygg__property_name__border_start
Snygg.BorderEnd -> R.string.snygg__property_name__border_end
Snygg.FontFamily -> R.string.snygg__property_name__font_family
Snygg.FontSize -> R.string.snygg__property_name__font_size
Snygg.FontStyle -> R.string.snygg__property_name__font_style
Snygg.FontVariant -> R.string.snygg__property_name__font_variant
Snygg.FontWeight -> R.string.snygg__property_name__font_weight
Snygg.Shadow -> R.string.snygg__property_name__shadow
Snygg.Shape -> R.string.snygg__property_name__shape
"--primary" -> R.string.snygg__property_name__var_primary
"--primary-variant" -> R.string.snygg__property_name__var_primary_variant
"--secondary" -> R.string.snygg__property_name__var_secondary
"--secondary-variant" -> R.string.snygg__property_name__var_secondary_variant
"--background" -> R.string.snygg__property_name__var_background
"--surface" -> R.string.snygg__property_name__var_surface
"--surface-variant" -> R.string.snygg__property_name__var_surface_variant
"--on-primary" -> R.string.snygg__property_name__var_on_primary
"--on-secondary" -> R.string.snygg__property_name__var_on_secondary
"--on-background" -> R.string.snygg__property_name__var_on_background
"--on-surface" -> R.string.snygg__property_name__var_on_surface
else -> null
}
}.let { resId ->
when {
resId != null -> {
stringRes(resId)
}
propertyName.isBlank() -> {
stringRes(R.string.general__select_dropdown_value_placeholder)
}
else -> {
propertyName
}
}
}
}
@Composable
internal fun translatePropertyValue(propertyValue: SnyggValue, level: SnyggLevel): String {
return propertyValue.encoder().serialize(propertyValue).getOrElse { propertyValue.toString() }
}
@Composable
internal fun translatePropertyValueEncoderName(encoder: SnyggValueEncoder): String {
return when (encoder) {
SnyggImplicitInheritValue -> R.string.general__select_dropdown_value_placeholder
SnyggExplicitInheritValue -> R.string.snygg__property_value__explicit_inherit
SnyggSolidColorValue -> R.string.snygg__property_value__solid_color
SnyggRectangleShapeValue -> R.string.snygg__property_value__rectangle_shape
SnyggCutCornerDpShapeValue -> R.string.snygg__property_value__cut_corner_shape_dp
SnyggCutCornerPercentageShapeValue -> R.string.snygg__property_value__cut_corner_shape_percent
SnyggRoundedCornerDpShapeValue -> R.string.snygg__property_value__rounded_corner_shape_dp
SnyggRoundedCornerPercentageShapeValue -> R.string.snygg__property_value__rounded_corner_shape_percent
SnyggDpSizeValue -> R.string.snygg__property_value__dp_size
SnyggSpSizeValue -> R.string.snygg__property_value__sp_size
SnyggPercentageSizeValue -> R.string.snygg__property_value__percentage_size
SnyggDefinedVarValue -> R.string.snygg__property_value__defined_var
else -> null
}.let { if (it != null) { stringRes(it) } else { encoder::class.simpleName ?: "" } }.toString()
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C) 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.
*/
package dev.patrickgold.florisboard.app.ui.settings.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.common.UnicodeCtrlChar
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.snygg.Snygg
import dev.patrickgold.florisboard.snygg.SnyggLevel
import dev.patrickgold.florisboard.snygg.SnyggRule
import dev.patrickgold.florisboard.snygg.value.RgbaColor
import dev.patrickgold.florisboard.snygg.value.SnyggCircleShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggCutCornerPercentShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggDefinedVarValue
import dev.patrickgold.florisboard.snygg.value.SnyggDpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggExplicitInheritValue
import dev.patrickgold.florisboard.snygg.value.SnyggImplicitInheritValue
import dev.patrickgold.florisboard.snygg.value.SnyggPercentageSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRectangleShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerDpShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggRoundedCornerPercentShapeValue
import dev.patrickgold.florisboard.snygg.value.SnyggSolidColorValue
import dev.patrickgold.florisboard.snygg.value.SnyggSpSizeValue
import dev.patrickgold.florisboard.snygg.value.SnyggValue
import dev.patrickgold.florisboard.snygg.value.SnyggValueEncoder
import kotlin.math.roundToInt
@Composable
internal fun translateElementName(rule: SnyggRule, level: SnyggLevel): String {
return translateElementName(rule.element, level) ?: remember {
buildString {
if (rule.isAnnotation) {
append(SnyggRule.ANNOTATION_MARKER)
}
append(rule.element)
}
}
}
@Composable
internal fun translateElementName(element: String, level: SnyggLevel): String? {
return when (level) {
SnyggLevel.DEVELOPER -> null
else -> when (element) {
"defines" -> R.string.snygg__rule_element__defines
FlorisImeUi.Keyboard -> R.string.snygg__rule_element__keyboard
FlorisImeUi.Key -> R.string.snygg__rule_element__key
FlorisImeUi.KeyHint -> R.string.snygg__rule_element__key_hint
FlorisImeUi.KeyPopup -> R.string.snygg__rule_element__key_popup
FlorisImeUi.ClipboardHeader -> R.string.snygg__rule_element__clipboard_header
FlorisImeUi.ClipboardItem -> R.string.snygg__rule_element__clipboard_item
FlorisImeUi.ClipboardItemPopup -> R.string.snygg__rule_element__clipboard_item_popup
FlorisImeUi.GlideTrail -> R.string.snygg__rule_element__glide_trail
FlorisImeUi.OneHandedPanel -> R.string.snygg__rule_element__one_handed_panel
FlorisImeUi.SmartbarPrimaryRow -> R.string.snygg__rule_element__smartbar_primary_row
FlorisImeUi.SmartbarSecondaryRow -> R.string.snygg__rule_element__smartbar_secondary_row
FlorisImeUi.SmartbarPrimaryActionsToggle -> R.string.snygg__rule_element__smartbar_primary_actions_toggle
FlorisImeUi.SmartbarSecondaryActionsToggle -> R.string.snygg__rule_element__smartbar_secondary_actions_toggle
FlorisImeUi.SmartbarQuickAction -> R.string.snygg__rule_element__smartbar_quick_action
FlorisImeUi.SmartbarKey -> R.string.snygg__rule_element__smartbar_key
FlorisImeUi.SmartbarCandidateWord -> R.string.snygg__rule_element__smartbar_candidate_word
FlorisImeUi.SmartbarCandidateClip -> R.string.snygg__rule_element__smartbar_candidate_clip
FlorisImeUi.SmartbarCandidateSpacer -> R.string.snygg__rule_element__smartbar_candidate_spacer
FlorisImeUi.SystemNavBar -> R.string.snygg__rule_element__system_nav_bar
else -> null
}
}.let { if (it != null) { stringRes(it) } else { null } }
}
@Composable
internal fun translatePropertyName(propertyName: String, level: SnyggLevel): String {
return when (level) {
SnyggLevel.DEVELOPER -> null
else -> when (propertyName) {
Snygg.Width -> R.string.snygg__property_name__width
Snygg.Height -> R.string.snygg__property_name__height
Snygg.Background -> R.string.snygg__property_name__background
Snygg.Foreground -> R.string.snygg__property_name__foreground
Snygg.BorderColor -> R.string.snygg__property_name__border_color
Snygg.BorderStyle -> R.string.snygg__property_name__border_style
Snygg.BorderWidth -> R.string.snygg__property_name__border_width
Snygg.FontFamily -> R.string.snygg__property_name__font_family
Snygg.FontSize -> R.string.snygg__property_name__font_size
Snygg.FontStyle -> R.string.snygg__property_name__font_style
Snygg.FontVariant -> R.string.snygg__property_name__font_variant
Snygg.FontWeight -> R.string.snygg__property_name__font_weight
Snygg.ShadowElevation -> R.string.snygg__property_name__shadow_elevation
Snygg.Shape -> R.string.snygg__property_name__shape
"--primary" -> R.string.snygg__property_name__var_primary
"--primary-variant" -> R.string.snygg__property_name__var_primary_variant
"--secondary" -> R.string.snygg__property_name__var_secondary
"--secondary-variant" -> R.string.snygg__property_name__var_secondary_variant
"--background" -> R.string.snygg__property_name__var_background
"--surface" -> R.string.snygg__property_name__var_surface
"--surface-variant" -> R.string.snygg__property_name__var_surface_variant
"--on-primary" -> R.string.snygg__property_name__var_on_primary
"--on-secondary" -> R.string.snygg__property_name__var_on_secondary
"--on-background" -> R.string.snygg__property_name__var_on_background
"--on-surface" -> R.string.snygg__property_name__var_on_surface
"--on-surface-variant" -> R.string.snygg__property_name__var_on_surface_variant
"--shape" -> R.string.snygg__property_name__var_shape
"--shape-variant" -> R.string.snygg__property_name__var_shape_variant
else -> null
}
}.let { resId ->
when {
resId != null -> {
stringRes(resId)
}
propertyName.isBlank() -> {
stringRes(R.string.general__select_dropdown_value_placeholder)
}
else -> {
propertyName
}
}
}
}
@Composable
internal fun translatePropertyValue(
propertyValue: SnyggValue,
level: SnyggLevel,
displayColorsAs: DisplayColorsAs,
): String {
return when (propertyValue) {
is SnyggSolidColorValue -> remember(propertyValue.color, displayColorsAs) {
val color = propertyValue.color
when (displayColorsAs) {
DisplayColorsAs.HEX8 -> buildString {
append(UnicodeCtrlChar.LeftToRightIsolate)
append("#")
append((color.red * RgbaColor.RedMax).roundToInt().toString(16).padStart(2, '0'))
append((color.green * RgbaColor.GreenMax).roundToInt().toString(16).padStart(2, '0'))
append((color.blue * RgbaColor.BlueMax).roundToInt().toString(16).padStart(2, '0'))
append((color.alpha * 0xFF).roundToInt().toString(16).padStart(2, '0'))
append(UnicodeCtrlChar.PopDirectionalIsolate)
}
DisplayColorsAs.RGBA -> buildString {
append(UnicodeCtrlChar.LeftToRightIsolate)
append("rgba(")
append((color.red * RgbaColor.RedMax).roundToInt())
append(",")
append((color.green * RgbaColor.GreenMax).roundToInt())
append(",")
append((color.blue * RgbaColor.BlueMax).roundToInt())
append(",")
append(color.alpha)
append(")")
append(UnicodeCtrlChar.PopDirectionalIsolate)
}
}
}
else -> when (level) {
SnyggLevel.DEVELOPER -> null
else -> when (propertyValue) {
is SnyggDefinedVarValue -> translatePropertyName(propertyValue.key, level)
else -> null
}
} ?: buildString {
append(UnicodeCtrlChar.LeftToRightIsolate)
append(propertyValue.encoder().serialize(propertyValue).getOrElse { propertyValue.toString() })
append(UnicodeCtrlChar.PopDirectionalIsolate)
}
}
}
@Composable
internal fun translatePropertyValueEncoderName(encoder: SnyggValueEncoder): String {
return when (encoder) {
SnyggImplicitInheritValue -> R.string.general__select_dropdown_value_placeholder
SnyggExplicitInheritValue -> R.string.snygg__property_value__explicit_inherit
SnyggSolidColorValue -> R.string.snygg__property_value__solid_color
SnyggRectangleShapeValue -> R.string.snygg__property_value__rectangle_shape
SnyggCircleShapeValue -> R.string.snygg__property_value__circle_shape
SnyggCutCornerDpShapeValue -> R.string.snygg__property_value__cut_corner_shape_dp
SnyggCutCornerPercentShapeValue -> R.string.snygg__property_value__cut_corner_shape_percent
SnyggRoundedCornerDpShapeValue -> R.string.snygg__property_value__rounded_corner_shape_dp
SnyggRoundedCornerPercentShapeValue -> R.string.snygg__property_value__rounded_corner_shape_percent
SnyggDpSizeValue -> R.string.snygg__property_value__dp_size
SnyggSpSizeValue -> R.string.snygg__property_value__sp_size
SnyggPercentageSizeValue -> R.string.snygg__property_value__percentage_size
SnyggDefinedVarValue -> R.string.snygg__property_value__defined_var
else -> null
}.let { if (it != null) { stringRes(it) } else { encoder::class.simpleName ?: "" } }.toString()
}

View File

@@ -22,7 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisInfoCard
import dev.patrickgold.florisboard.app.ui.components.FlorisErrorCard
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.ime.text.smartbar.CandidatesDisplayMode
@@ -47,18 +47,18 @@ fun TypingScreen() = FlorisScreen {
visibleIf = { AndroidVersion.ATLEAST_API30_R },
)
// This card is temporary and is therefore not using a string resource
FlorisInfoCard(
FlorisErrorCard(
modifier = Modifier.padding(8.dp),
text = if (AndroidVersion.ATLEAST_API30_R) {
"Suggestions (except autofill) are not available in this beta release"
"Suggestions (except autofill) are not available in this release"
} else {
"Suggestions are not available in this beta release"
"Suggestions are not available in this release"
},
)
SwitchPreference(
prefs.suggestion.enabled,
title = stringRes(R.string.pref__suggestion__enabled__label),
summary = stringRes(R.string.pref__suggestion__enabled__summary),
//summary = stringRes(R.string.pref__suggestion__enabled__summary),
)
ListPreference(
prefs.suggestion.displayMode,

View File

@@ -75,6 +75,10 @@ fun FlorisAppTheme(
isSystemInDarkTheme() -> DarkColorPalette
else -> LightColorPalette
}
AppTheme.AUTO_AMOLED -> when {
isSystemInDarkTheme() -> AmoledDarkColorPalette
else -> LightColorPalette
}
AppTheme.LIGHT -> LightColorPalette
AppTheme.DARK -> DarkColorPalette
AppTheme.AMOLED_DARK -> AmoledDarkColorPalette

View File

@@ -0,0 +1,42 @@
/*
* 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.
*/
package dev.patrickgold.florisboard.common
/**
* Character codes and comments source:
* https://www.w3.org/International/questions/qa-bidi-unicode-controls#basedirection
*/
@Suppress("unused")
object UnicodeCtrlChar {
/** Sets base direction to LTR and isolates the embedded content from the surrounding text */
const val LeftToRightIsolate = "\u2066"
/** Sets base direction to RTL and isolates the embedded content from the surrounding text */
const val RightToLeftIsolate = "\u2067"
/** Isolates the content and sets the direction according to the first strongly typed directional character */
const val FirstStrongIsolate = "\u2068"
/** Closes a previously opened isolated text block */
const val PopDirectionalIsolate = "\u2069"
val Matcher = """[$LeftToRightIsolate$RightToLeftIsolate$FirstStrongIsolate$PopDirectionalIsolate]""".toRegex()
}
fun String.stripUnicodeCtrlChars(): String {
return this.replace(UnicodeCtrlChar.Matcher, "")
}

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