Compare commits
142 Commits
v0.3.14-be
...
v0.3.14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c6a719aa5 | ||
|
|
693051b699 | ||
|
|
91865a0e14 | ||
|
|
ebd45375f6 | ||
|
|
1c9f24c533 | ||
|
|
d6daa87ce6 | ||
|
|
99eaee1477 | ||
|
|
47824a8e5f | ||
|
|
83f45f9c5d | ||
|
|
5e36f57e82 | ||
|
|
ec65d9e6f8 | ||
|
|
e3ede1160e | ||
|
|
a72d570065 | ||
|
|
db2227cc04 | ||
|
|
1a77f94af9 | ||
|
|
5bba116e01 | ||
|
|
5c177cc225 | ||
|
|
1d0826f854 | ||
|
|
85fdd3876a | ||
|
|
59def39114 | ||
|
|
463644c1d2 | ||
|
|
117654c95c | ||
|
|
21e97d6933 | ||
|
|
36d9f91009 | ||
|
|
07c15f0782 | ||
|
|
97d3223122 | ||
|
|
2e12d756df | ||
|
|
1ca1763138 | ||
|
|
93f6ba3f69 | ||
|
|
83fd9a27a8 | ||
|
|
4c8587fa1e | ||
|
|
92f4ef12d9 | ||
|
|
db401e0b69 | ||
|
|
ca5706b638 | ||
|
|
63d132ad3c | ||
|
|
97456645e4 | ||
|
|
81f949a1ee | ||
|
|
438ca8c7ad | ||
|
|
51bd2cc5cd | ||
|
|
864ee749ca | ||
|
|
ba40307f0f | ||
|
|
ae091bdae8 | ||
|
|
e98b12f3c5 | ||
|
|
ae49342ae7 | ||
|
|
85ad815ae7 | ||
|
|
948e9bb75f | ||
|
|
898dc25cb6 | ||
|
|
33f2336676 | ||
|
|
e64ab1e75d | ||
|
|
d46e95ceac | ||
|
|
dd25420e03 | ||
|
|
b29519a784 | ||
|
|
2d62398186 | ||
|
|
573e55f1c4 | ||
|
|
523eabee17 | ||
|
|
913872e4cd | ||
|
|
c12a802e5e | ||
|
|
3129617402 | ||
|
|
dc2d130b13 | ||
|
|
e9b140a9fe | ||
|
|
4189955554 | ||
|
|
51f0843a2f | ||
|
|
2496ada14b | ||
|
|
544e3857fc | ||
|
|
f3d076b51e | ||
|
|
40efbd0f65 | ||
|
|
8d667297c2 | ||
|
|
dbb3f97abd | ||
|
|
627919e34f | ||
|
|
9f67789337 | ||
|
|
96d830e5c4 | ||
|
|
80ca97388c | ||
|
|
afa021f67d | ||
|
|
e14f54cac1 | ||
|
|
21c1915233 | ||
|
|
5af80a2270 | ||
|
|
8a1a3d3bb4 | ||
|
|
a2f15606c7 | ||
|
|
01e2ee7835 | ||
|
|
7adc045752 | ||
|
|
266af61e3a | ||
|
|
a814190012 | ||
|
|
109c323369 | ||
|
|
654e160503 | ||
|
|
3aa6d7ccbe | ||
|
|
5d447d7a8f | ||
|
|
25bfd61814 | ||
|
|
b4caa66377 | ||
|
|
e5dde63efc | ||
|
|
78f64adbbf | ||
|
|
3f39bc8768 | ||
|
|
d2274c4d9e | ||
|
|
8674a04a5c | ||
|
|
2f14529902 | ||
|
|
1d74a17b98 | ||
|
|
52435d9837 | ||
|
|
b6fbbe5a91 | ||
|
|
3f85e1167c | ||
|
|
9c05096184 | ||
|
|
ef3bc015b0 | ||
|
|
75fd600448 | ||
|
|
2f01e7770f | ||
|
|
12b6edf872 | ||
|
|
6053f2d16b | ||
|
|
636c5f4df4 | ||
|
|
bb0bd478cf | ||
|
|
79eb080811 | ||
|
|
b5b82836bc | ||
|
|
cef0f2b53d | ||
|
|
dbf031469f | ||
|
|
5b87c933da | ||
|
|
adc4b9a372 | ||
|
|
0ff8f7776e | ||
|
|
c04fdeb491 | ||
|
|
295d8e5326 | ||
|
|
b032ac64f7 | ||
|
|
8ebe99d2c9 | ||
|
|
f0b027557b | ||
|
|
462030bcd7 | ||
|
|
888af9d28d | ||
|
|
ea159527f3 | ||
|
|
0dc0f53a91 | ||
|
|
d5aac7ac14 | ||
|
|
9f58088545 | ||
|
|
b684f1759d | ||
|
|
aa7a264d6c | ||
|
|
6ac537c517 | ||
|
|
2386ae7749 | ||
|
|
7d559acfae | ||
|
|
7783b9b218 | ||
|
|
548f7d7b1e | ||
|
|
4629c07812 | ||
|
|
3b2b7da841 | ||
|
|
25ef53510a | ||
|
|
0064f248d3 | ||
|
|
0c721696f2 | ||
|
|
131ab6214d | ||
|
|
70bc7a1236 | ||
|
|
6c88716a2a | ||
|
|
ff3c37e360 | ||
|
|
58bab443c4 | ||
|
|
a8b0a6d555 |
147
CONTRIBUTING.md
147
CONTRIBUTING.md
@@ -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!
|
||||
|
||||
32
README.md
32
README.md
@@ -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.
|
||||
|
||||
110
ROADMAP.md
110
ROADMAP.md
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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": [
|
||||
]
|
||||
}
|
||||
@@ -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": "م" }
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
{ "code": 1605, "label": "م" },
|
||||
{ "code": 1567, "label": "؟" },
|
||||
{ "code": 1548, "label": "،" },
|
||||
{ "code": 46, "label": "." }
|
||||
{ "code": 58, "label": ":" }
|
||||
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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": "م" }
|
||||
]
|
||||
|
||||
@@ -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": "-" }
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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": "/" }
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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": "@" }
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": "/" }
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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": "/" }
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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": "]" }
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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": "]" }
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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": "]" }
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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": "?" }
|
||||
|
||||
@@ -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
3642
app/src/main/assets/ime/media/emoji/root.txt
Normal file
3642
app/src/main/assets/ime/media/emoji/root.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)"
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -244,7 +244,8 @@ private fun ColumnScope.Step(
|
||||
) {
|
||||
Column(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisVerticalScroll(),
|
||||
.florisVerticalScroll()
|
||||
.padding(end = 8.dp),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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!!,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user