Compare commits

..

227 Commits

Author SHA1 Message Date
Patrick Goldinger
bfe7852bdf Release v0.3.15-beta01 2022-03-25 19:54:49 +01:00
florisboard-bot
45fe2f311e Update translations from Crowdin 2022-03-25 19:43:46 +01:00
blucin
f73daa2b00 Add ColemakDH keyboard layout (#1401)
* Added ColemakDH layout

* Adjust ColemakDH to new Flex extension format

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2022-03-25 19:39:22 +01:00
Nijat Ismayilzada
4e8ff9ec14 Add Azerbaijani keyboard layout (#1639)
* Add Azerbaijani keyboard layout

* Add subtype preset for Azerbaijani

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2022-03-25 19:24:18 +01:00
Amir Yalon
a96fc84fc1 Add Hebrew SI-1452 niqqud and some punctuation marks (#1413)
Based on information from https://en.wikipedia.org/wiki/Hebrew_keyboard

Combining characters don’t display well on their own. In the source code, they are avoided altogether by using JSON
escape codes like `\u05c2`, while in labels a placeholder letter is used. The letter ס was chosen because it is hollow,
and the letter ש must be used for its dots because other letters take the HEBREW POINT SHIN DOT on the wrong side when
combined.
2022-03-25 17:55:34 +01:00
Patrick Goldinger
e62ddc37dd Disable forceDarkAllowed in IME base theme (#1694) 2022-03-25 17:25:12 +01:00
Patrick Goldinger
06cfa34a4b Remove popups from telpad layout (#1044) 2022-03-25 16:51:08 +01:00
Patrick Goldinger
ef849dfefd Add alphabetic letters to phone digits in telpad layout (#355) 2022-03-25 16:51:08 +01:00
pjtsearch
091d43520e Add Armenian keyboard layouts (#1654)
* feat: Add Western Armenian layouts, popup mapping, and currency

* feat: Add Eastern Armenian layouts, popup mapping, and currency

* fix: Fix Armenian popup mapping

* fix: Add yev to Armenian popup mapping

* fix: Fix Western Armenian language tag

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

* fix: Differentiate Western layout name

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

* fix: Differentiate Eastern layout name

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

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

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

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

Update and correct some characters place with there codes.

* Update kurdish.json
2022-02-02 23:08:27 +01:00
Patrick Goldinger
bd25ddb92e Merge pull request #1524 from svvvst/master
Add Rusyn Language Language Set, Layout, Popups
2022-02-02 23:05:03 +01:00
Patrick Goldinger
62bdd31af3 Fix rue popup mapping missing from extension manifest 2022-02-02 22:59:06 +01:00
Patrick Goldinger
d4af89bf99 Merge pull request #1513 from florisboard/add-theme-editor-ui
Add extension editor UI / theme editor UI
2022-02-02 22:51:21 +01:00
Patrick Goldinger
1c38a42c0b Fix system nav bar not using background var reference in default themes
This is done to avoid confusion why the system nav bar color does not change with the background.
2022-02-02 22:28:56 +01:00
Patrick Goldinger
6d1ebb74fb Adjust feedback and beta info test in Settings UI 2022-02-02 22:26:29 +01:00
Patrick Goldinger
9673e6de5c Clean-up old theme strings 2022-02-02 22:14:20 +01:00
Patrick Goldinger
ab709e2c69 Add create extension ability / Fix a lot of state and validation bugs 2022-02-02 21:57:29 +01:00
Patrick Goldinger
9144708cf0 Fix autofill chip not applying correct style (#1507) 2022-02-01 22:25:02 +01:00
Patrick Goldinger
38136de39d Add save extension function to extension editor 2022-01-31 22:21:23 +01:00
Patrick Goldinger
beb800a76e Add ability to modify shape property values in stylesheet editor 2022-01-31 21:40:23 +01:00
Patrick Goldinger
aab738526a Add ability to add new component from existing in extension editor 2022-01-31 00:48:03 +01:00
Patrick Goldinger
86bdad61a4 Add (semi-)live-preview to stylesheet editor
Semi-live only because you must apply a property value before the change reflects on the keyboard UI. THis is also done because instantly updating the stylesheet when using the color picker would create lag.
2022-01-30 23:18:52 +01:00
Patrick Goldinger
e4c56cab03 Fix bugs and fine-tune appearance across extension editor 2022-01-30 20:12:46 +01:00
Patrick Goldinger
cef1c4e3f6 Add hint to text field and trim input in component meta dialog 2022-01-30 18:29:30 +01:00
Patrick Goldinger
b2721c9faa Add input validation to component meta editor dialog 2022-01-30 18:07:26 +01:00
Patrick Goldinger
ab4ae62ffe Improve SnyggRule backend 2022-01-30 15:43:44 +01:00
Patrick Goldinger
74244bab74 Add empty component screen in extension editor 2022-01-29 14:51:51 +01:00
svvvst
57112ae692 Added missing character to layout. 2022-01-28 14:04:15 -04:00
svvvst
fd1314ccba Fixed layout. 2022-01-28 14:03:24 -04:00
svvvst
45d99df104 Added missing character. 2022-01-28 14:02:36 -04:00
Patrick Goldinger
17b87f6543 Remove old theme related strings 2022-01-28 17:40:00 +01:00
Patrick Goldinger
d62e82569d Add ability to modify component meta data in stylesheet editor 2022-01-28 17:28:05 +01:00
Patrick Goldinger
dc5e00cc07 Fix KeyCode range being too restrictive 2022-01-27 23:51:45 +01:00
Patrick Goldinger
6402511d38 Eradicate nasty bug in SnyggRule comparator and equality logic
This bug caused some rules to be magically hidden, because they were either
incorrectly resolved as equal or the comparator returned `=` even though the
codes, groups or modes did not match at all.
2022-01-27 23:49:20 +01:00
Patrick Goldinger
83c1f70077 Implement property apply in stylesheet editor 2022-01-27 19:07:15 +01:00
Patrick Goldinger
f3375f48ef Update AGP to 7.1.0 and Gradle to 7.2 2022-01-27 00:12:01 +01:00
Patrick Goldinger
e1b911086b Fix minor theming issues in stylesheet editor 2022-01-26 23:38:25 +01:00
Patrick Goldinger
b60c0cef51 Improve property set button layout in stylesheet editor 2022-01-26 23:14:01 +01:00
Patrick Goldinger
0c42185700 Add sp size property value field in stylesheet editor 2022-01-26 22:52:46 +01:00
Patrick Goldinger
ee3c779b17 Add custom design for text field in stylesheet editor 2022-01-26 22:08:52 +01:00
Patrick Goldinger
b5e6655c84 Add color picker to property editor in stylesheet editor 2022-01-26 19:38:29 +01:00
Patrick Goldinger
e1b45b9193 Add property value for vars and type selector in stylesheet editor 2022-01-24 20:38:16 +01:00
svvvst
588713bd55 Add Rusyn 2022-01-24 15:23:16 -04:00
svvvst
43ad452174 Add rusyn layouts 2022-01-24 15:21:56 -04:00
svvvst
2cf9146536 Add Rusyn popup mapping 2022-01-24 15:15:37 -04:00
svvvst
f81331baed Add Rusyn language keyboard 2022-01-24 15:14:25 -04:00
Patrick Goldinger
9c9c3b9428 Add skeleton for property editor in stylesheet editor 2022-01-24 01:09:59 +01:00
Patrick Goldinger
4b64d81c21 Extract EditRuleDialog in own file 2022-01-23 23:15:03 +01:00
Patrick Goldinger
e826f600f0 Rework stylesheet editor to use lazy list, avoiding layout freeze 2022-01-23 23:04:36 +01:00
Patrick Goldinger
0f845a9784 Add modes and finish rule mod functionality in stylesheet editor 2022-01-23 21:53:04 +01:00
Patrick Goldinger
a2805bedca Add codes modifier UI in rule editor for stylesheet editor 2022-01-23 13:30:07 +01:00
Patrick Goldinger
d860bbfb90 Add rule dialog in stylesheet editor 2022-01-22 20:47:28 +01:00
Patrick Goldinger
70e2d34410 Add variables box in stylesheet editor 2022-01-21 20:36:44 +01:00
Patrick Goldinger
1c49a11824 Add property preview in stylesheet editor 2022-01-21 19:57:29 +01:00
Patrick Goldinger
cd2a0000c0 Add rule and properties view in stylesheet editor 2022-01-21 00:50:18 +01:00
Patrick Goldinger
1304e49eb4 Add strings for all rule and property names in stylesheet editor 2022-01-20 23:55:43 +01:00
svvvst
9658cecb88 Create rue.json 2022-01-20 00:32:53 -04:00
svvvst
4c23d5bafc Create rusyn.json
Custom Rusyn keyboard layout.
2022-01-19 21:00:39 -04:00
Patrick Goldinger
82238c8c1a Add base skeleton for stylesheet editing 2022-01-20 00:35:13 +01:00
Patrick Goldinger
ead74e1c26 Fix issues with loosing state on rotating screen in extension editor 2022-01-19 23:43:46 +01:00
Patrick Goldinger
d58371be81 Add basic component creator screen 2022-01-19 00:48:53 +01:00
Patrick Goldinger
844d194533 Add stub screens for dependencies and files 2022-01-18 22:55:40 +01:00
Patrick Goldinger
84abc929d0 Improve meta data editor screen for extension editor 2022-01-18 21:27:33 +01:00
Patrick Goldinger
7497470875 Add discard changes dialog / Improve action screen code 2022-01-18 01:34:19 +01:00
Patrick Goldinger
cc5df41daa Rework and refactor UI code base for extension editor 2022-01-17 23:49:14 +01:00
Patrick Goldinger
d87b290a32 Add meta data manager in extension editor 2022-01-17 01:51:16 +01:00
Patrick Goldinger
a0f859ad03 Add basic skeleton for in-app extension editing 2022-01-16 23:49:01 +01:00
Patrick Goldinger
c86892ec0b Merge pull request #1498 from florisboard/add-backup-and-restore-feature
Add backup and restore feature
2022-01-12 22:33:24 +01:00
Patrick Goldinger
c85fea0799 Enable encrypted backup for app data (#272, #1324) 2022-01-12 22:32:38 +01:00
Patrick Goldinger
765e34a01d Improve restore metadata UI 2022-01-11 00:29:19 +01:00
Patrick Goldinger
96e7f2eeac Add restore data functionality 2022-01-10 23:00:00 +01:00
Patrick Goldinger
23dddfd16e Add restore screen UI and general B&R code improvements
B&R..Backup&Restore

Restore action itself does nothing atm
2022-01-08 20:31:39 +01:00
Patrick Goldinger
e2318d0af1 Add ability to share generated backup through share menu 2022-01-07 17:38:22 +01:00
Patrick Goldinger
ef3b840dce Add backup data screen (local file sys only)
Also add skeleton for restore data screen
2022-01-07 02:51:43 +01:00
Patrick Goldinger
9b9c5fa70e Change unhandled stacktraces dir to prevent backup interference
Old location:
`files/{timestamp}.stacktrace`

New location:
`no_backup/unhandled_stacktraces/{timestamp}.stacktrace`

Additionally clean up some "magics" and improve code in the crash utility.
2022-01-06 23:00:08 +01:00
Patrick Goldinger
6f0216cf9f Raise targetSdk from API 30 to 31 2022-01-06 00:59:06 +01:00
298 changed files with 19413 additions and 10438 deletions

View File

@@ -2,127 +2,104 @@
First off, thanks for considering contributing to FlorisBoard!
There are several ways to contribute to FlorisBoard. This document
provides some general guidelines for each type of contribution.
There are several ways to contribute to FlorisBoard. This document provides some general guidelines for each type of
contribution.
## Giving general feedback
NEW! You can now [give general feedback](https://github.com/florisboard/florisboard/discussions/new?category=feedback)
directly here on GitHub. This is the preferred way to give feedback, as
it allows not only for me to read and respond to feedback, but for everyone
in this community.
directly here on GitHub. This is the preferred way to give feedback, as it allows not only for me to read and respond to
feedback, but for everyone in this community.
Optionally you can also use the review function within Google Play or email me
at [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I
love to hear from you! Note, that the amount of feedback emails I get
is overwhelmingly high - so if I don't answer or answer really late, I
apologize - I guarantee though that I read through every email and that
I will use every feedback to improve FlorisBoard :)
at [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I love to hear from you! Note, that the amount of
feedback emails I get is overwhelmingly high - so if I don't answer or answer really late, I apologize - I guarantee
though that I read through every email and that I will use every feedback to improve FlorisBoard :)
## Translations
To make FlorisBoard accessible in as many languages as possible, the
platform [Crowdin](https://crowdin.florisboard.patrickgold.dev) is used
to crowdsource and manage translations. This is the only source of
translations from now on - **PRs that add/update translations are no
longer accepted.** The list of languages in Crowdin covers the top 20
languages, but feel free to email me at
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to
request a language and I'll add it.
platform [Crowdin](https://crowdin.florisboard.patrickgold.dev) is used to crowdsource and manage translations. This is
the only source of translations from now on - **PRs that add/update translations are no longer accepted.** The list of
languages in Crowdin covers the top 20 languages, but feel free to email me at
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a language and I'll add it.
## Adding a new feature or making large changes
If you intend to add a new feature or to make large changes, please
discuss this first through a proposal on GitHub. Discussing your idea
enables both you and the dev team that we are on the same page before
you start on working on your change. If you have any questions, feel
free to ask for help at any time!
If you intend to add a new feature or to make large changes, please discuss this first through a proposal on GitHub.
Discussing your idea enables both you and the dev team that we are on the same page before you start on working on your
change. If you have any questions, feel free to ask for help at any time!
## Adding a new keyboard layout
Adding a layout to FlorisBoard is very simple and does not require any
coding skills, although you should understand the basics of the JSON
syntax (it is very easy though by just looking at some other layout files).
There are two main steps in adding new layouts, though the config step can
be skipped if you only add a layout without a new default language support.
### The config file ([`app/src/main/assets/ime/config.json`](app/src/main/assets/ime/config.json))
This file is very important, as it defines all default currency sets as
well as all default subtypes available in the Settings Subtype UI. Note
that you don't have to modify this file if you add a layout for an already
pre-configured language.
- `currencySets`: This is a list of all currency sets, which can be chosen
for each subtype. If you consider adding a new one, make sure that the
first currency symbol matches the name of the currency set and also
ensure that you have exactly 6 currency symbols. This is important as the
symbol layouts have exactly 6 slots available to fill these defined
currency symbols in.
- `defaultSubtyes`: This is a list of all pre-made subtypes. Each time the
user selects a language in the `Subtype Add`-dialog, all options configured
here will get pre-selected. The language tag must adhere to the IETF BCP
47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)).
For example, Dutch as spoken in Belgium is `nl-be`. Use a unique value
for `id` to avoid possible crashes caused by duplicate ids.
Adding a layout to FlorisBoard is very simple and does not require any coding skills, although you should understand the
basics of the JSON syntax (it is very easy though by just looking at some other layout files). Most of the time is
enough to look at the existing layout files, but the following attempts to help you in creating layouts from scratch.
### Adding the layout
Since v0.3.10-beta05 it is possible to add custom layouts for all types.
Since v0.3.14-beta06 it is possible to add custom layouts for all types using the new extension format, Flex.
To add a new layout, head to [`app/src/main/assets/ime/text`](app/src/main/assets/ime/text) and then select
the correct sub-directory for the type of layout you want to add. In most cases
this will be `characters` to add a layout like QWERTY etc.
Keyboard layout assets are grouped in [`app/src/main/assets/ime/keyboard`](app/src/main/assets/ime/keyboard) and are
further sub-grouped into the following:
For the `code` field of each key, make sure to use the UTF-8 code. An
useful tool for finding the correct code is [unicode-table.com](https://unicode-table.com/en/).
From there, you search for your letter and then use the HTML code, but without the `&#;`
- `org.florisboard.composers`: Defines standard composers for interpreting input, currently supports basic typing and
Korean input. Most of the time you won't need to add new composers, so if you don't know what they are always
assume `appender` (the default composer which does not alter input in any way) is in use.
- `org.florisboard.currencysets`: Lists all currency sets, which can be chosen for each subtype. If you consider adding
a new one, make sure that the first currency symbol matches the name of the currency set and also ensure that you have
exactly 6 currency symbols. This is important as the symbol layouts have exactly 6 slots available to fill these
defined currency symbols in.
- `org.florisboard.layouts`: Contains the actual layout files for all layout types.
- `org.florisboard.localization`: Contains all popup mappings and subtype presets (formally the `config.json` file). The
subtype presets are a list of all pre-made subtypes. Each time the user selects a language in the `Subtype Add`
-dialog, all options configured here will get selected if found in the presets. The language tag must adhere to the
IETF BCP 47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)). For
example, Dutch as spoken in Belgium is `nl-be`.
To add a new layout, head to above directory and add the necessary files to each extension group.
For the `code` field of each key, make sure to use the UTF-8 code. An useful tool for finding the correct code
is [unicode-table.com](https://unicode-table.com/en/). From there, you search for your letter and then use the HTML
code, but without the `&#;`
For internal codes of functional or UI keys, see
[`app/src/main/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt`](app/src/main/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt).
[`app/src/main/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt`](app/src/main/java/dev/patrickgold/florisboard/ime/text/key/KeyCode.kt)
.
The label is equally important and should always match up with the defined
code. If `code` and `label` don't match up, FlorisBoard won't crash but
it will most likely lead to confusion in the key processing logic.
The label is equally important and should always match up with the defined code. If `code` and `label` don't match up,
FlorisBoard won't crash but it will most likely lead to confusion in the key processing logic.
Any accents or diacritics that should be exposed via long press can be
added at [`app/src/main/assets/ime/text/characters/extended_popups/<languageTag_name_here>.json`](app/src/main/assets/ime/text/characters/extended_popups).
For each key, you can add 1 main and several relevant accents. The main
accent should be used for accents which are important for the language
you add. The main field is used for determining if a hint or an accent
should take priority, so please make sure to leave main empty and just
use relevant for accents which are not-so important.
Any accents or diacritics that should be exposed via long press can be added
at [`app/src/main/assets/ime/keyboard/org.florisboard.localization/popupMappings/<languageTag>.json`](app/src/main/assets/ime/keyboard/org.florisboard.localization/popupMappings)
. For each key, you can add 1 main and several relevant accents. The main accent should be used for accents which are
important for the language you add. The main field is used for determining if a hint or an accent should take priority,
so please make sure to leave main empty and just use relevant for accents which are not-so important.
For popups of non-`characters` layout, simply add the popup directly to
each key via the `popup` field.
For popups of non-`characters` layout, simply add the popup directly to each key via the `popup` field.
## Adding a new dictionary for a language
Currently the suggestions implementation is highly experimental and will
get a major if not complete rework, so dictionaries are currently not
accepted.
Currently the suggestions implementation is highly experimental and not available until 0.4.0, so dictionaries are
currently not accepted.
## Bug reporting
This kind of contribution is the most important, as it tells where
FlorisBoard has flaws and thus should be improved to maximize stability
and user experience. To make this process as smooth as possible, please
use the pre-made [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
for bug reporting. This makes it easy for us to understand what the bug
is and how to solve it.
This kind of contribution is the most important, as it tells where FlorisBoard has flaws and thus should be improved to
maximize stability and user experience. To make this process as smooth as possible, please use the
pre-made [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
for bug reporting. This makes it easy for us to understand what the bug is and how to solve it.
### Capturing error logs
Logs are captured by FlorisBoard's crash handler, which gives you the
ability to copy it to the clipboard and paste it in GitHub. This is the
preferred way to capture logs.
Logs are captured by FlorisBoard's crash handler, which gives you the ability to copy it to the clipboard and paste it
in GitHub. This is the preferred way to capture logs.
Alternatively, you can also use ADB (Android Debug Bridge) to capture
the error log. This is recommended for experienced users only.
Alternatively, you can also use ADB (Android Debug Bridge) to capture the error log. This is recommended for experienced
users only.
## Donating
If none of the above options are feasible for you but you still want to
show your support, you can also buy me a coffee, so I can stay up all night
and chase away bugs or add new cool stuff :)
If none of the above options are feasible for you but you still want to show your support, you can also buy me a coffee,
so I can stay up all night and chase away bugs or add new cool stuff :)
See the `Sponsors` button for available options!

View File

@@ -18,10 +18,10 @@ fully respecting your privacy. Currently in early-beta state.
</tr>
<tr>
<td valign="top">
<p><i>Major versions only, 1 release per 1-3 months</i><br><br>Updates are more polished, new features are matured and tested through to ensure a stable experience.</p>
<p><i>Major versions only, 1 release per 1-5 months</i><br><br>Updates are more polished, new features are matured and tested through to ensure a stable experience.</p>
</td>
<td valign="top">
<p><i>Beta versions, 1-2 releases per week</i><br><br>Updates contain new features that may not be fully matured yet and bugs are more likely to occur. Allows you to give early feedback.</p>
<p><i>Beta versions, up to 1-2 releases per week</i><br><br>Updates contain new features that may not be fully matured yet and bugs are more likely to occur. Allows you to give early feedback.</p>
</td>
</tr>
<tr>
@@ -57,12 +57,14 @@ fully respecting your privacy. Currently in early-beta state.
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme and enter the public beta on Google Play.
## Highlighted features
- Spell checking service
- Word suggestions (currently English only and may not work on all devices)
- Glide typing (currently English only)
- Advanced theming support and customization
- Integrated clipboard manager / history
- Emoji keyboard (although lacking some features)
- Advanced theming support and customization
- Integrated extension support (still evolving)
- Emoji keyboard
- Spell checking service
- Glide typing (currently English only)
Word suggestions are not included in the current releases and are a major goal for the v0.4.0 milestone.
Feature roadmap: See [ROADMAP.md](ROADMAP.md)
@@ -78,18 +80,18 @@ Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List
to get more information on this topic.
## Used libraries, components and icons
* [Google Flexbox Layout for Android](https://github.com/google/flexbox-layout)
by [google](https://github.com/google)
* [AndroidX libraries](https://github.com/androidx/androidx) by
[Android Jetpack](https://github.com/androidx)
* [Accompanist Compose UI libraries](https://github.com/google/accompanist/) by
[Google](https://github.com/google)
* [Google Material icons](https://github.com/google/material-design-icons) by
[google](https://github.com/google)
[Google](https://github.com/google)
* [JetPref preference library](https://github.com/patrickgold/jetpref) by
[patrickgold](https://github.com/patrickgold)
* [KotlinX coroutines library](https://github.com/Kotlin/kotlinx.coroutines) by
[Kotlin](https://github.com/Kotlin)
* [KotlinX serialization library](https://github.com/Kotlin/kotlinx.serialization) by
[Kotlin](https://github.com/Kotlin)
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
[Jared Rummler](https://github.com/jaredrummler)
* [Timber](https://github.com/JakeWharton/timber) by
[JakeWharton](https://github.com/JakeWharton)
* [expandable-fab](https://github.com/nambicompany/expandable-fab) by
[Nambi](https://github.com/nambicompany)
* [ICU4C](https://github.com/unicode-org/icu) by
[The Unicode Consortium](https://github.com/unicode-org)
* [Nuspell](https://github.com/nuspell/nuspell) by
@@ -97,7 +99,7 @@ to get more information on this topic.
## License
```
Copyright 2020 Patrick Goldinger
Copyright 2020-2022 Patrick Goldinger
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

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

View File

@@ -30,9 +30,9 @@ android {
defaultConfig {
applicationId = "dev.patrickgold.florisboard"
minSdk = 23
targetSdk = 30
versionCode = 64
versionName = "0.3.14"
targetSdk = 31
versionCode = 76
versionName = "0.3.15"
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 = "-beta08"
versionNameSuffix = "-beta01"
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
@@ -133,10 +142,6 @@ android {
it.useJUnitPlatform()
}
}
lint {
isAbortOnError = false
}
}
tasks.withType<Test> {
@@ -148,26 +153,31 @@ 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-systemuicontroller:0.20.2")
implementation("dev.patrickgold.jetpref:jetpref-datastore-model:0.1.0-beta02")
implementation("dev.patrickgold.jetpref:jetpref-datastore-ui:0.1.0-beta02")
implementation("dev.patrickgold.jetpref:jetpref-material-ui:0.1.0-beta02")
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:4.6.3")
testImplementation("io.kotest:kotest-assertions-core:4.6.3")
testImplementation("io.kotest:kotest-property:4.6.3")
testImplementation("io.kotest.extensions:kotest-extensions-robolectric:0.4.0")
testImplementation("io.kotest:kotest-runner-junit5:5.1.0")
testImplementation("io.kotest:kotest-assertions-core:5.1.0")
testImplementation("io.kotest:kotest-property:5.1.0")
testImplementation("io.kotest.extensions:kotest-extensions-robolectric:0.5.0")
testImplementation("nl.jqno.equalsverifier:equalsverifier:3.8.3")
androidTestImplementation("androidx.test.ext", "junit", "1.1.2")
androidTestImplementation("androidx.test.espresso", "espresso-core", "3.3.0")

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 Patrick Goldinger
<!-- Copyright (C) 2020-2022 Patrick Goldinger
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -36,12 +36,15 @@
<application
android:name="dev.patrickgold.florisboard.FlorisApplication"
android:allowBackup="false"
android:allowBackup="true"
android:dataExtractionRules="@xml/backup_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/floris_app_icon"
android:label="@string/floris_app_name"
android:roundIcon="@mipmap/floris_app_icon_round"
android:supportsRtl="true"
android:theme="@style/FlorisAppTheme">
android:theme="@style/FlorisAppTheme"
tools:targetApi="s">
<!-- IME service -->
<service
@@ -125,13 +128,25 @@
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>
<!-- Default file provider to share files from the "files" or "cache" dir -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider.file"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
</manifest>

View File

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

View File

@@ -9,6 +9,18 @@
"license": "apache-2.0"
},
"currencySets": [
{
"id": "armenian_dram",
"label": "Armenian dram (֏)",
"slots": [
{ "code": 1423, "label": "֏" },
{ "code": 36, "label": "$" },
{ "code": 8364, "label": "€" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
]
},
{
"id": "azerbaijani_manat",
"label": "Azerbaijani manat (₼)",

View File

@@ -17,6 +17,26 @@
"direction": "rtl",
"modifier": "org.florisboard.layouts:arabic"
},
{
"id": "western_armenian",
"label": "Armenian (Western)",
"authors": [ "PJTSearch" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:armenian"
},
{
"id": "eastern_armenian",
"label": "Armenian (Eastern)",
"authors": [ "PJTSearch" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:armenian"
},
{
"id": "azerbaijani",
"label": "Azerbaijani",
"authors": [ "nijatismayilzada" ],
"direction": "ltr"
},
{
"id": "azerty",
"label": "AZERTY",
@@ -72,6 +92,12 @@
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "colemak_dh",
"label": "ColemakDH",
"authors": [ "blucin" ],
"direction": "ltr"
},
{
"id": "danish",
"label": "Danish (QWERTY)",
@@ -235,6 +261,18 @@
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "rusyn",
"label": "Rusyn",
"authors": [ "svvvst" ],
"direction": "ltr"
},
{
"id": "rusyn_us",
"label": "Rusyn (Phonetic)",
"authors": [ "svvvst" ],
"direction": "ltr"
},
{
"id": "sangaline",
"label": "Sangaline",
@@ -334,6 +372,12 @@
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "armenian",
"label": "Armenian",
"authors": [ "PJTSearch" ],
"direction": "ltr"
},
{
"id": "arabic",
"label": "Arabic",
@@ -528,6 +572,13 @@
}
],
"symbols": [
{
"id": "armenian",
"label": "Armenian",
"authors": [ "PJTSearch" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:armenian"
},
{
"id": "cjk",
"label": "CJK",
@@ -580,6 +631,12 @@
"authors": [ "patrickgold" ],
"direction": "ltr"
},
{
"id": "armenian",
"label": "Armenian",
"authors": [ "PJTSearch" ],
"direction": "ltr"
},
{
"id": "cjk",
"label": "CJK",

View File

@@ -0,0 +1,46 @@
[
[
{ "$": "auto_text_key", "code": 113, "label": "q" },
{ "$": "auto_text_key", "code": 252, "label": "ü" },
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "case_selector",
"lower": { "code": 105, "label": "i" },
"upper": { "code": 304, "label": "İ" }
},
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 246, "label": "ö" },
{ "$": "auto_text_key", "code": 287, "label": "ğ" }
],
[
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 115, "label": "s" },
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 102, "label": "f" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 104, "label": "h" },
{ "$": "auto_text_key", "code": 106, "label": "j" },
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 108, "label": "l" },
{ "$": "case_selector",
"lower": { "code": 305, "label": "ı" },
"upper": { "code": 73, "label": "I" }
},
{ "$": "auto_text_key", "code": 601, "label": "ə" }
],
[
{ "$": "auto_text_key", "code": 122, "label": "z" },
{ "$": "auto_text_key", "code": 120, "label": "x" },
{ "$": "auto_text_key", "code": 99, "label": "c" },
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 110, "label": "n" },
{ "$": "auto_text_key", "code": 109, "label": "m" },
{ "$": "auto_text_key", "code": 231, "label": "ç" },
{ "$": "auto_text_key", "code": 351, "label": "ş" }
]
]

View File

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

View File

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

View File

@@ -2,44 +2,85 @@
[
{ "code": 39, "label": "'", "popup": {
"relevant": [
{ "code": 1474, "label": "ש\u05c2" },
{ "code": 1467, "label": "ס\u05bb" },
{ "code": 1523, "label": "׳" },
{ "code": 1524, "label": "״" },
{ "code": 34, "label": "\"" },
{ "code": 96, "label": "`" }
]
} },
{ "code": 45, "label": "-", "popup": {
"relevant": [
{ "code": 1470, "label": "־" },
{ "code": 1473, "label": "ש\u05c1" },
{ "code": 95, "label": "_" }
]
} },
{ "code": 1511, "label": "ק" },
{ "code": 1511, "label": "ק", "popup": {
"relevant": [
{ "code": 1464, "label": "ס\u05b8" },
{ "code": 1459, "label": "ס\u05b3" }
]
} },
{ "code": 1512, "label": "ר" },
{ "code": 1488, "label": "א" },
{ "code": 1496, "label": "ט" },
{ "code": 1493, "label": "ו" },
{ "code": 1493, "label": "ו", "popup": {
"relevant": [
{ "code": 1465, "label": "ס\u05b9" }
]
} },
{ "code": 1503, "label": "ן" },
{ "code": 1501, "label": "ם" },
{ "code": 1508, "label": "פ" }
{ "code": 1508, "label": "פ", "popup": {
"relevant": [
{ "code": 1463, "label": "ס\u05b7" },
{ "code": 1458, "label": "ס\u05b2" }
]
} }
],
[
{ "code": 1513, "label": "ש" },
{ "code": 1491, "label": "ד" },
{ "code": 1513, "label": "ש", "popup": {
"relevant": [
{ "code": 1456, "label": "ס\u05b0" }
]
} },
{ "code": 1491, "label": "ד", "popup": {
"relevant": [
{ "code": 1468, "label": "ס\u05bc" }
]
} },
{ "code": 1490, "label": "ג" },
{ "code": 1499, "label": "כ" },
{ "code": 1506, "label": "ע" },
{ "code": 1497, "label": "י" },
{ "code": 1495, "label": "ח" },
{ "code": 1495, "label": "ח", "popup": {
"relevant": [
{ "code": 1460, "label": "ס\u05b4" }
]
} },
{ "code": 1500, "label": "ל" },
{ "code": 1498, "label": "ך" },
{ "code": 1507, "label": "ף" }
],
[
{ "code": 1494, "label": "ז" },
{ "code": 1505, "label": "ס" },
{ "code": 1505, "label": "ס", "popup": {
"relevant": [
{ "code": 1462, "label": "ס\u05b6" },
{ "code": 1457, "label": "ס\u05b1" }
]
} },
{ "code": 1489, "label": "ב" },
{ "code": 1492, "label": "ה" },
{ "code": 1504, "label": "נ" },
{ "code": 1502, "label": "מ" },
{ "code": 1510, "label": "צ" },
{ "code": 1510, "label": "צ", "popup": {
"relevant": [
{ "code": 1461, "label": "ס\u05b5" }
]
} },
{ "code": 1514, "label": "ת" },
{ "code": 1509, "label": "ץ" }
]

View File

@@ -1,13 +1,13 @@
[
[
[
{ "code": 1602, "label": "ق", "popup": {
"main": { "code": 1647, "label": "ٯ" }
} },
{ "code": 1608, "label": "و", "popup": {
"main": { "code": -255, "label": "وو" }
} },
{ "code": 1749, "label": "", "popup": {
"main": { "code": 1577, "label": "ة" }
{ "code": 1749, "label": "ە", "popup": {
"main": { "code": 1577, "label": "ة" }
} },
{ "code": 1585, "label": "ر", "popup": {
"main": { "code": 1685, "label": "ڕ" }
@@ -15,50 +15,49 @@
{ "code": 1578, "label": "ت", "popup": {
"main": { "code": 1591, "label": "ط" }
} },
{ "code": 1740, "label": "ی", "popup": {
"main": { "code": 1742, "label": "ێ" }
} },
{ "code": 1740, "label": "ی" },
{ "code": 1742, "label": "ێ" },
{ "code": 1574, "label": "ﺋ", "popup": {
"main": { "code": 1569, "label": "ء" }
} },
{ "code": 1593, "label": "ع", "popup": {
"main": { "code": 1594, "label": "غ" }
} },
{ "code": 1734, "label": "ۆ" },
{ "code": 1662, "label": "پ", "popup": {
"main": { "code": 1579, "label": "ث" }
} }
],
[
{"code": 1575, "label": "ا"},
{ "code": 1587, "label": "س" },
{ "code": 1588, "label": "ش" },
{ "code": 1583, "label": "د", "popup": {
"main": {"code": 1584, "label": "ذ" }
],
[
{ "code": 1575, "label": "ا"},
{ "code": 1587, "label": "س" },
{ "code": 1588, "label": "ش" },
{ "code": 1583, "label": "د", "popup": {
"main": {"code": 1584, "label": "ذ" }
} },
{ "code": 1601, "label": "ف" , "popup": {
"main": {"code": 1700, "label": "ڤ" }
} },
{ "code": 1601, "label": "ف" , "popup": {
"main": {"code": 1700, "label": "ڤ" }
} },
{ "code": 1607, "label": "ھ" },
{ "code": 1688, "label": "ژ", "popup": {
"main": { "code": 1600, "label": "" }
} },
{ "code": 1604, "label": "ل", "popup": {
"main": { "code": 1717, "label": "ڵ" }
} },
{ "code": 1705, "label": "ک" },
{ "code": 1711, "label": "گ" }
],
[
{ "code": 1586, "label": "ز", "popup": {
"main": {"code": 1592, "label": "ظ" }
} },
{ "code": 1582, "label": "خ" },
{ "code": 1580, "label": "ج" },
{ "code": 1670, "label": "چ" },
{ "code": 1581, "label": "ح" },
{ "code": 1576, "label": "ب" },
{ "code": 1606, "label": "ن" },
{ "code": 1605, "label": "م" }
{ "code": 1726, "label": "ھ" },
{ "code": 1688, "label": "ژ", "popup": {
"main": { "code": 1600, "label": "━" }
} },
{ "code": 1604, "label": "ل", "popup": {
"main": { "code": 1717, "label": "ڵ" }
} },
{ "code": 1705, "label": "ک" },
{ "code": 1711, "label": "گ" , "popup": {
"main": { "code": 1594, "label": "غ" }
} }
],
[
{ "code": 1586, "label": "ز", "popup": {
"main": {"code": 1592, "label": "ظ" }
} },
{ "code": 1582, "label": "خ" },
{ "code": 1580, "label": "ج" },
{ "code": 1670, "label": "چ" },
{ "code": 1581, "label": "ح" },
{ "code": 1593, "label": "ع" },
{ "code": 1576, "label": "ب" },
{ "code": 1606, "label": "ن" },
{ "code": 1605, "label": "م" }
]
]
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
[
[
{ "code": -11, "label": "shift", "type": "modifier" },
{ "code": 0, "type": "placeholder" },
{ "code": -7, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "$": "variation_selector",
"default": { "code": 44, "label": ",", "groupId": 1 },
"email": { "code": 64, "label": "@", "groupId": 1 },
"uri": { "code": 47, "label": "/", "groupId": 1 }
},
{ "code": -227, "label": "language_switch", "type": "system_gui" },
{ "code": -212, "label": "ime_ui_mode_media", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 1417, "label": "։", "groupId": 2 },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

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

View File

@@ -0,0 +1,92 @@
[
[
{ "code": 64, "label": "@" },
{ "code": 35, "label": "#", "popup": {
"main": { "code": 8470, "label": "№" }
} },
{ "code": -801, "label": "currency_slot_1", "popup": {
"main": { "code": -802, "label": "currency_slot_2" },
"relevant": [
{ "code": -806, "label": "currency_slot_6" },
{ "code": -803, "label": "currency_slot_3" },
{ "code": -804, "label": "currency_slot_4" },
{ "code": -805, "label": "currency_slot_5" }
]
} },
{ "code": 37, "label": "%", "popup": {
"main": { "code": 8240, "label": "‰" },
"relevant": [
{ "code": 8453, "label": "℅" }
]
} },
{ "code": 38, "label": "&" },
{ "code": 45, "label": "-", "popup": {
"main": { "code": 95, "label": "_" },
"relevant": [
{ "code": 8212, "label": "—" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
} },
{ "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": "}" }
]
} },
{ "code": 47, "label": "/" }
],
[
{ "code": 42, "label": "*", "popup": {
"main": { "code": 8224, "label": "†" },
"relevant": [
{ "code": 9733, "label": "★" },
{ "code": 8225, "label": "‡" }
]
} },
{ "code": 171, "label": "«", "popup": {
"main": { "code": 34, "label": "\"" },
"relevant": [
{ "code": 8221, "label": "”" },
{ "code": 8222, "label": "„" },
{ "code": 8220, "label": "“" },
{ "code": 8249, "label": "" }
]
} },
{ "code": 187, "label": "»", "popup": {
"main": { "code": 39, "label": "'" },
"relevant": [
{ "code": 8217, "label": "" },
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8250, "label": "" }
]
} },
{ "code": 1373, "label": "՝", "popup": {
"main": { "code": 58, "label": ":" }
} },
{ "code": 46, "label": "." },
{ "code": 1372, "label": "՜", "popup": {
"main": { "code": 33, "label": "!" }
} },
{ "code": 1374, "label": "՞", "popup": {
"main": { "code": 63, "label": "?" },
"relevant": [
{ "code": 191, "label": "¿" },
{ "code": 8253, "label": "‽" }
]
} },
{ "code": 1371, "label": "՛" }
]
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
[
[
{ "code": -203, "label": "view_symbols2", "type": "system_gui" },
{ "code": 0, "type": "placeholder" },
{ "code": -7, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 44, "label": "," },
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 1417, "label": "։", "popup": {
"main": { "code": 46, "label": "." }
} },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -90,6 +90,10 @@
"id": "hu",
"authors": [ "zoli111, gabik65" ]
},
{
"id": "hy",
"authors": [ "PJTSearch" ]
},
{
"id": "is",
"authors": [ "patrickgold" ]
@@ -150,6 +154,10 @@
"id": "ru",
"authors": [ "williamtheaker", "33kk" ]
},
{
"id": "rue",
"authors": [ "svvvst" ]
},
{
"id": "sk",
"authors": [ "stefan-misik", "majso" ]
@@ -431,6 +439,15 @@
"numericRow": "org.florisboard.layouts:eastern_arabic"
}
},
{
"languageTag": "az",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:azerbaijani_manat",
"popupMapping": "org.florisboard.localization:tr",
"preferred": {
"characters": "org.florisboard.layouts:azerbaijani"
}
},
{
"languageTag": "hu",
"composer": "org.florisboard.composers:appender",
@@ -458,6 +475,26 @@
"characters": "org.florisboard.layouts:qwertz"
}
},
{
"languageTag": "hy",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:armenian_dram",
"popupMapping": "org.florisboard.localization:hy",
"preferred": {
"characters": "org.florisboard.layouts:western_armenian",
"symbols": "org.florisboard.layouts:armenian"
}
},
{
"languageTag": "hy",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:armenian_dram",
"popupMapping": "org.florisboard.localization:hy",
"preferred": {
"characters": "org.florisboard.layouts:eastern_armenian",
"symbols": "org.florisboard.layouts:armenian"
}
},
{
"languageTag": "ru",
"composer": "org.florisboard.composers:appender",
@@ -467,6 +504,15 @@
"characters": "org.florisboard.layouts:jcuken_russian"
}
},
{
"languageTag": "rue",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:euro",
"popupMapping": "org.florisboard.localization:rue",
"preferred": {
"characters": "org.florisboard.layouts:rusyn"
}
},
{
"languageTag": "uk",
"composer": "org.florisboard.composers:appender",

View File

@@ -90,7 +90,7 @@
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".ir"},
"main": { "code": -255, "label": ".sa"},
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },

View File

@@ -18,8 +18,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -91,8 +91,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -88,8 +88,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -106,8 +106,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -85,8 +85,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -52,8 +52,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -80,8 +80,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -63,8 +63,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -88,8 +88,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -8,12 +8,12 @@
]
},
"ا": {
"main": { "code": 1570, "label": "آ"},
"relevant": [
{ "code": 1649, "label": "ٱ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" },
{ "code": 1570, "label": "آ" }
{ "code": 1573, "label": "إ" }
]
},
"ه": {

View File

@@ -91,8 +91,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -111,8 +111,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -94,8 +94,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -34,8 +34,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -42,8 +42,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -0,0 +1,41 @@
{
"all": {
"ե": {
"main": { "$": "auto_text_key", "code": 1415, "label": "և" }
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" },
{ "code": 45, "label": "-" },
{ "code": 1373, "label": "՝" },
{ "code": 1371, "label": "՛" },
{ "code": 64, "label": "@" },
{ "code": 46, "label": "." },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 1372, "label": "՜" },
{ "code": 1374, "label": "՞" },
{ "code": 1415, "label": "և" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".gr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}

View File

@@ -83,8 +83,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -76,8 +76,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -13,8 +13,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -48,8 +48,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -63,7 +63,7 @@
]
},
"s": {
"main": { "$": "auto_text_key", "code": 219, "label": "ș" },
"main": { "$": "auto_text_key", "code": 351, "label": "ş" },
"relevant": [
{ "$": "auto_text_key", "code": 347, "label": "ś" },
{ "$": "auto_text_key", "code": 349, "label": "ŝ" },
@@ -102,8 +102,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -140,8 +140,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -80,8 +80,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -72,8 +72,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -82,8 +82,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -60,8 +60,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -81,8 +81,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -81,8 +81,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -52,8 +52,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -23,8 +23,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -0,0 +1,69 @@
{
"all": {
"е": {
"main": { "$": "auto_text_key", "code": 234, "label": "ê" },
"relevant": [{ "$": "auto_text_key", "code": 1105, "label": "ё" }]
},
"у": {
"main": { "$": "auto_text_key", "code": 1263, "label": "ӯ" },
"relevant": [
{ "$": "auto_text_key", "code": 1118, "label": "ў" },
{ "$": "auto_text_key", "code": 1265, "label": "ӱ" },
{ "$": "auto_text_key", "code": 375, "label": "ŷ" }
]
},
"г": {
"main": { "$": "auto_text_key", "code": 1169, "label": "ґ" }
},
"і": {
"main": { "$": "auto_text_key", "code": 1123, "label": "î" },
"relevant": [
{ "$": "auto_text_key", "code": 1123, "label": "ѣ" },
{ "$": "auto_text_key", "code": 1111, "label": "ї" }
]
},
"о": {
"main": { "$": "auto_text_key", "code": 333, "label": "ō" },
"relevant": [{ "$": "auto_text_key", "code": 244, "label": "ô" }]
},
"ь": {
"main": { "$": "auto_text_key", "code": 1098, "label": "ъ" }
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [
{ "code": 38, "label": "&" },
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 34, "label": "\"" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 39, "label": "'" },
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}

View File

@@ -89,8 +89,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -32,8 +32,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -133,8 +133,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -40,8 +40,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -18,8 +18,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

View File

@@ -183,7 +183,7 @@
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".ir"},
"main": { "code": -255, "label": ".pk"},
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },

View File

@@ -40,8 +40,14 @@
{ "code": 64, "label": "@" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(" },
"rtl": { "code": 41, "label": "(" }
},
{ "$": "layout_direction_selector",
"ltr": { "code": 41, "label": ")" },
"rtl": { "code": 40, "label": ")" }
},
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -24,29 +24,28 @@ import android.content.IntentFilter
import androidx.core.os.UserManagerCompat
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.common.NativeStr
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.common.toNativeStr
import dev.patrickgold.florisboard.crashutility.CrashUtility
import dev.patrickgold.florisboard.debug.Flog
import dev.patrickgold.florisboard.debug.LogTopic
import dev.patrickgold.florisboard.debug.flogError
import dev.patrickgold.florisboard.debug.flogInfo
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.keyboard.KeyboardManager
import dev.patrickgold.florisboard.ime.nlp.NlpManager
import dev.patrickgold.florisboard.ime.spelling.SpellingManager
import dev.patrickgold.florisboard.ime.spelling.SpellingService
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.res.AssetManager
import dev.patrickgold.florisboard.res.ext.ExtensionManager
import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.nlp.NlpManager
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.res.cache.CacheManager
import dev.patrickgold.florisboard.res.ext.ExtensionManager
import dev.patrickgold.florisboard.res.io.deleteContentsRecursively
import dev.patrickgold.jetpref.datastore.JetPrefManager
import dev.patrickgold.jetpref.datastore.JetPref
import java.io.File
import kotlin.Exception
@Suppress("unused")
class FlorisApplication : Application() {
@@ -80,7 +79,7 @@ class FlorisApplication : Application() {
override fun onCreate() {
super.onCreate()
try {
JetPrefManager.init(saveIntervalMs = 1_000)
JetPref.configure(saveIntervalMs = 500)
Flog.install(
context = this,
isFloggingEnabled = BuildConfig.DEBUG,
@@ -93,12 +92,12 @@ class FlorisApplication : Application() {
if (AndroidVersion.ATLEAST_API24_N && !UserManagerCompat.isUserUnlocked(this)) {
val context = createDeviceProtectedStorageContext()
initICU(context)
prefs.initializeForContext(context)
prefs.initializeBlocking(context)
registerReceiver(BootComplete(), IntentFilter(Intent.ACTION_USER_UNLOCKED))
} else {
initICU(this)
cacheDir?.deleteContentsRecursively()
prefs.initializeForContext(this)
prefs.initializeBlocking(this)
clipboardManager.value.initializeForContext(this)
}
@@ -141,7 +140,7 @@ class FlorisApplication : Application() {
flogError { e.toString() }
}
cacheDir?.deleteContentsRecursively()
prefs.initializeForContext(this@FlorisApplication)
prefs.initializeBlocking(this@FlorisApplication)
clipboardManager.value.initializeForContext(this@FlorisApplication)
}
}

View File

@@ -16,7 +16,9 @@
package dev.patrickgold.florisboard
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Size
@@ -40,6 +42,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -66,6 +69,7 @@ import dev.patrickgold.florisboard.common.android.AndroidVersion
import dev.patrickgold.florisboard.common.android.isOrientationLandscape
import dev.patrickgold.florisboard.common.android.isOrientationPortrait
import dev.patrickgold.florisboard.common.android.launchActivity
import dev.patrickgold.florisboard.common.android.setLocale
import dev.patrickgold.florisboard.common.android.systemServiceOrNull
import dev.patrickgold.florisboard.common.observeAsTransformingState
import dev.patrickgold.florisboard.debug.LogTopic
@@ -80,13 +84,13 @@ 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
import dev.patrickgold.florisboard.ime.text.smartbar.SecondaryRowPlacement
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.snygg.ui.SnyggSurface
import dev.patrickgold.jetpref.datastore.model.observeAsState
import java.lang.ref.WeakReference
@@ -135,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)
}
@@ -186,6 +203,8 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
private val prefs by florisPreferenceModel()
private val keyboardManager by keyboardManager()
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,21 @@ 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)
init {
setTheme(R.style.FlorisImeTheme)
}
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 +331,7 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
}
override fun onWindowHidden() {
@@ -331,7 +361,7 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
flogInfo(LogTopic.IMS_EVENTS) {
"Creating inline suggestions request because Smartbar and inline suggestions are enabled."
}
val stylesBundle = ThemeManager.createInlineSuggestionUiStyleBundle(this)
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
val spec = InlinePresentationSpec.Builder(InlineSuggestionUiSmallestSize, InlineSuggestionUiBiggestSize)
.setStyle(stylesBundle)
.build()
@@ -372,9 +402,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 +438,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()) {
@@ -430,63 +457,75 @@ class FlorisImeService : LifecycleInputMethodService(), EditorInstance.WordHisto
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun BoxScope.ImeUi() {
val keyboardStyle = FlorisImeTheme.style.get(FlorisImeUi.Keyboard)
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 activeState by keyboardManager.observeActiveState()
val keyboardStyle = FlorisImeTheme.style.get(
element = FlorisImeUi.Keyboard,
mode = activeState.inputMode.value,
)
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),
) {
val activeState by keyboardManager.observeActiveState()
when (activeState.imeUiMode) {
ImeUiMode.TEXT -> TextInputLayout()
ImeUiMode.MEDIA -> {}
ImeUiMode.CLIPBOARD -> ClipboardInputLayout()
val oneHandedMode by prefs.keyboard.oneHandedMode.observeAsState()
val oneHandedModeScaleFactor by prefs.keyboard.oneHandedModeScaleFactor.observeAsState()
val keyboardWeight = when {
oneHandedMode == OneHandedMode.OFF || configuration.isOrientationLandscape() -> 1f
else -> oneHandedModeScaleFactor / 100f
}
if (oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.START,
weight = 1f - keyboardWeight,
)
}
CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
Box(
modifier = Modifier
.weight(keyboardWeight)
.wrapContentHeight(),
) {
when (activeState.imeUiMode) {
ImeUiMode.TEXT -> TextInputLayout()
ImeUiMode.MEDIA -> MediaInputLayout()
ImeUiMode.CLIPBOARD -> ClipboardInputLayout()
}
}
}
if (oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.END,
weight = 1f - keyboardWeight,
)
}
}
if (oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.END,
weight = 1f - keyboardWeight,
)
}
}
}
}
@Composable

View File

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

View File

@@ -16,11 +16,15 @@
package dev.patrickgold.florisboard.app.prefs
import android.os.Build
import androidx.annotation.RequiresApi
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
@@ -29,15 +33,16 @@ 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
import dev.patrickgold.jetpref.datastore.preferenceModel
import java.time.LocalTime
fun florisPreferenceModel() = preferenceModel(AppPrefs::class, ::AppPrefs)
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val advanced = Advanced()
@@ -304,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(
@@ -355,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,
@@ -433,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,
@@ -443,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 = "[]",
)
}
@@ -555,15 +597,25 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
default = extCoreTheme("floris_night"),
serializer = ExtensionComponentName.Serializer,
)
@RequiresApi(Build.VERSION_CODES.O)
val sunriseTime = localTime(
key = "theme__sunrise_time",
default = LocalTime.of(6, 0),
//val sunriseTime = localTime(
// key = "theme__sunrise_time",
// default = LocalTime.of(6, 0),
//)
//val sunsetTime = localTime(
// key = "theme__sunset_time",
// default = LocalTime.of(18, 0),
//)
val editorDisplayColorsAs = enum(
key = "theme__editor_display_colors_as",
default = DisplayColorsAs.HEX8,
)
@RequiresApi(Build.VERSION_CODES.O)
val sunsetTime = localTime(
key = "theme__sunset_time",
default = LocalTime.of(18, 0),
val editorDisplayKbdAfterDialogs = enum(
key = "theme__editor_display_kbd_after_dialogs",
default = DisplayKbdAfterDialogs.REMEMBER,
)
val editorLevel = enum(
key = "theme__editor_level",
default = SnyggLevel.ADVANCED,
)
}
}

View File

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

View File

@@ -24,6 +24,7 @@ import androidx.navigation.compose.composable
import dev.patrickgold.florisboard.app.ui.devtools.AndroidLocalesScreen
import dev.patrickgold.florisboard.app.ui.devtools.AndroidSettingsScreen
import dev.patrickgold.florisboard.app.ui.devtools.DevtoolsScreen
import dev.patrickgold.florisboard.app.ui.ext.ExtensionEditScreen
import dev.patrickgold.florisboard.app.ui.ext.ExtensionExportScreen
import dev.patrickgold.florisboard.app.ui.ext.ExtensionImportScreen
import dev.patrickgold.florisboard.app.ui.ext.ExtensionImportScreenType
@@ -33,14 +34,19 @@ import dev.patrickgold.florisboard.app.ui.settings.about.AboutScreen
import dev.patrickgold.florisboard.app.ui.settings.about.ProjectLicenseScreen
import dev.patrickgold.florisboard.app.ui.settings.about.ThirdPartyLicensesScreen
import dev.patrickgold.florisboard.app.ui.settings.advanced.AdvancedScreen
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
@@ -91,12 +97,18 @@ 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"
const val About = "settings/about"
const val ProjectLicense = "settings/about/project-license"
@@ -112,6 +124,11 @@ object Routes {
}
object Ext {
const val Edit = "ext/edit/{id}?create={serial_type}"
fun Edit(id: String, serialType: String? = null): String {
return Edit.curlyFormat("id" to id, "serial_type" to (serialType ?: ""))
}
const val Export = "ext/export/{id}"
fun Export(id: String) = Export.curlyFormat("id" to id)
@@ -171,12 +188,22 @@ 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() }
composable(Settings.About) { AboutScreen() }
composable(Settings.ProjectLicense) { ProjectLicenseScreen() }
@@ -189,11 +216,18 @@ object Routes {
AndroidSettingsScreen(name)
}
composable(Ext.Edit) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
val serialType = navBackStack.arguments?.getString("serial_type")
ExtensionEditScreen(
id = extensionId.toString(),
createSerialType = serialType.takeIf { it != null && it.isNotBlank() },
)
}
composable(Ext.Export) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
ExtensionExportScreen(id = extensionId.toString())
}
composable(Ext.Import) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
ExtensionImportScreenType.values().firstOrNull { it.id == typeId }
@@ -201,7 +235,6 @@ object Routes {
val uuid = navBackStack.arguments?.getString("uuid")?.takeIf { it != "null" }
ExtensionImportScreen(type, uuid)
}
composable(Ext.View) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
ExtensionViewScreen(id = extensionId.toString())

View File

@@ -0,0 +1,41 @@
/*
* 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.runtime.saveable.Saver
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.unit.sp
infix fun TextUnit.safeTimes(other: Float): TextUnit {
return if (this.isUnspecified) 0.sp else this.times(other)
}
infix fun TextUnit.safeTimes(other: Double): TextUnit {
return if (this.isUnspecified) this else this.times(other)
}
infix fun TextUnit.safeTimes(other: Int): TextUnit {
return if (this.isUnspecified) this else this.times(other)
}
val DpSizeSaver = Saver<Dp, Float>(
save = { it.value },
restore = { it.dp },
)

View File

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

View File

@@ -17,26 +17,21 @@
package dev.patrickgold.florisboard.app.ui.components
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
@Composable
fun FlorisAppBar(
title: String,
backArrowVisible: Boolean,
navigationIcon: FlorisScreenNavigationIcon?,
actions: @Composable RowScope.() -> Unit = { },
) {
TopAppBar(
navigationIcon = backNavBtn(backArrowVisible),
navigationIcon = navigationIcon,
title = {
Text(
text = title,
@@ -49,19 +44,3 @@ fun FlorisAppBar(
elevation = 0.dp,
)
}
@Composable
private fun backNavBtn(backArrowVisible: Boolean): @Composable (() -> Unit)? {
if (!backArrowVisible) return null
val navController = LocalNavController.current
return {
IconButton(
onClick = { navController.popBackStack() },
) {
Icon(
painter = painterResource(R.drawable.ic_arrow_back),
contentDescription = "Back",
)
}
}
}

View File

@@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.ui.theme.outline
@Composable
fun RowScope.FlorisBulletSpacer(
@@ -39,6 +40,6 @@ fun RowScope.FlorisBulletSpacer(
.padding(horizontal = 8.dp)
.size(4.dp)
.clip(CircleShape)
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.12f)),
.background(MaterialTheme.colors.outline),
)
}

View File

@@ -38,6 +38,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.unit.dp
@Composable
@@ -138,6 +139,35 @@ fun FlorisIconButton(
enabled: Boolean = true,
iconModifier: Modifier = Modifier,
iconColor: Color = Color.Unspecified,
) {
IconButton(
modifier = modifier,
enabled = enabled,
onClick = onClick,
) {
val contentAlpha = if (enabled) LocalContentAlpha.current else 0.14f
val contentColor = iconColor.takeOrElse { LocalContentColor.current }
CompositionLocalProvider(
LocalContentAlpha provides contentAlpha,
LocalContentColor provides contentColor,
) {
Icon(
modifier = iconModifier,
painter = icon,
contentDescription = null,
)
}
}
}
@Composable
fun FlorisIconButtonWithInnerPadding(
onClick: () -> Unit,
modifier: Modifier = Modifier,
icon: Painter,
enabled: Boolean = true,
iconModifier: Modifier = Modifier,
iconColor: Color = Color.Unspecified,
) {
IconButton(
modifier = modifier,

View File

@@ -20,6 +20,7 @@ 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.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -49,13 +50,21 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.theme.outline
private val IconRequiredSize = 24.dp
private val IconEndPadding = 8.dp
private val CardContentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
object CardDefaults {
val IconRequiredSize = 24.dp
val IconSpacing = 8.dp
private val OutlinedBoxShape = RoundedCornerShape(8.dp)
val ContentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp)
}
object BoxDefaults {
val OutlinedBoxShape = RoundedCornerShape(8.dp)
val ContentPadding = PaddingValues(all = 0.dp)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
@@ -65,7 +74,7 @@ fun FlorisSimpleCard(
secondaryText: String? = null,
backgroundColor: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(backgroundColor),
contentPadding: PaddingValues = CardContentPadding,
contentPadding: PaddingValues = CardDefaults.ContentPadding,
icon: (@Composable () -> Unit)? = null,
onClick: (() -> Unit)? = null,
) {
@@ -110,7 +119,7 @@ fun FlorisErrorCard(
text: String,
modifier: Modifier = Modifier,
showIcon: Boolean = true,
contentPadding: PaddingValues = CardContentPadding,
contentPadding: PaddingValues = CardDefaults.ContentPadding,
onClick: (() -> Unit)? = null,
) {
FlorisSimpleCard(
@@ -120,8 +129,8 @@ fun FlorisErrorCard(
onClick = onClick,
icon = if (showIcon) ({ Icon(
modifier = Modifier
.padding(end = IconEndPadding)
.requiredSize(IconRequiredSize),
.padding(end = CardDefaults.IconSpacing)
.requiredSize(CardDefaults.IconRequiredSize),
painter = painterResource(R.drawable.ic_error_outline),
contentDescription = null,
) }) else null,
@@ -135,7 +144,7 @@ fun FlorisWarningCard(
text: String,
modifier: Modifier = Modifier,
showIcon: Boolean = true,
contentPadding: PaddingValues = CardContentPadding,
contentPadding: PaddingValues = CardDefaults.ContentPadding,
onClick: (() -> Unit)? = null,
) {
FlorisSimpleCard(
@@ -145,8 +154,8 @@ fun FlorisWarningCard(
onClick = onClick,
icon = if (showIcon) ({ Icon(
modifier = Modifier
.padding(end = IconEndPadding)
.requiredSize(IconRequiredSize),
.padding(end = CardDefaults.IconSpacing)
.requiredSize(CardDefaults.IconRequiredSize),
painter = painterResource(R.drawable.ic_warning_outline),
contentDescription = null,
) }) else null,
@@ -160,7 +169,7 @@ fun FlorisInfoCard(
text: String,
modifier: Modifier = Modifier,
showIcon: Boolean = true,
contentPadding: PaddingValues = CardContentPadding,
contentPadding: PaddingValues = CardDefaults.ContentPadding,
onClick: (() -> Unit)? = null,
) {
FlorisSimpleCard(
@@ -168,8 +177,8 @@ fun FlorisInfoCard(
onClick = onClick,
icon = if (showIcon) ({ Icon(
modifier = Modifier
.padding(end = IconEndPadding)
.requiredSize(IconRequiredSize),
.padding(end = CardDefaults.IconSpacing)
.requiredSize(CardDefaults.IconRequiredSize),
painter = painterResource(R.drawable.ic_info),
contentDescription = null,
) }) else null,
@@ -186,9 +195,10 @@ fun FlorisOutlinedBox(
subtitle: String? = null,
onSubtitleClick: (() -> Unit)? = null,
borderWidth: Dp = 1.dp,
borderColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
shape: Shape = OutlinedBoxShape,
content: @Composable () -> Unit,
borderColor: Color = MaterialTheme.colors.outline,
shape: Shape = BoxDefaults.OutlinedBoxShape,
contentPadding: PaddingValues = BoxDefaults.ContentPadding,
content: @Composable ColumnScope.() -> Unit,
) {
FlorisOutlinedBox(
modifier = modifier,
@@ -220,10 +230,13 @@ fun FlorisOutlinedBox(
borderWidth = borderWidth,
borderColor = borderColor,
shape = shape,
contentPadding = contentPadding,
content = content,
)
}
// TODO: Rework internal implementation (with same API and visual appearance) of FlorisOutlinedBox
// to avoid too much nesting and improve performance
@Composable
fun FlorisOutlinedBox(
modifier: Modifier = Modifier,
@@ -232,9 +245,10 @@ fun FlorisOutlinedBox(
subtitle: (@Composable () -> Unit)? = null,
onSubtitleClick: (() -> Unit)? = null,
borderWidth: Dp = 1.dp,
borderColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
shape: Shape = OutlinedBoxShape,
content: @Composable () -> Unit,
borderColor: Color = MaterialTheme.colors.outline,
shape: Shape = BoxDefaults.OutlinedBoxShape,
contentPadding: PaddingValues = BoxDefaults.ContentPadding,
content: @Composable ColumnScope.() -> Unit,
) {
Box(
modifier = modifier
@@ -257,7 +271,12 @@ fun FlorisOutlinedBox(
subtitle()
}
}
content()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding),
content = content,
)
}
if (title != null) {
Box(
@@ -276,3 +295,9 @@ fun FlorisOutlinedBox(
}
}
}
fun Modifier.defaultFlorisOutlinedBox(): Modifier {
return this
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
}

View File

@@ -32,6 +32,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -43,16 +44,19 @@ fun FlorisChip(
modifier: Modifier = Modifier,
onClick: () -> Unit = { },
enabled: Boolean = true,
color: Color = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity),
color: Color = Color.Unspecified,
shape: Shape = CircleShape,
@DrawableRes leadingIcons: List<Int> = listOf(),
@DrawableRes trailingIcons: List<Int> = listOf(),
) {
val backgroundColor = color.takeOrElse {
MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity)
}
Surface(
modifier = modifier,
onClick = onClick,
enabled = enabled,
color = color,
color = backgroundColor,
shape = shape,
) {
Row(

View File

@@ -31,6 +31,7 @@ fun FlorisConfirmDeleteDialog(
what: String,
) {
JetPrefAlertDialog(
modifier = modifier,
title = stringRes(R.string.action__delete_confirm_title),
confirmLabel = stringRes(R.string.action__delete),
onConfirm = onConfirm,
@@ -40,3 +41,25 @@ fun FlorisConfirmDeleteDialog(
Text(text = stringRes(R.string.action__delete_confirm_message, "name" to what))
}
}
@Composable
fun FlorisUnsavedChangesDialog(
modifier: Modifier = Modifier,
onSave: () -> Unit,
onDiscard: () -> Unit,
onDismiss: () -> Unit,
) {
JetPrefAlertDialog(
modifier = modifier,
title = stringRes(R.string.action__discard_confirm_title),
confirmLabel = stringRes(R.string.action__save),
onConfirm = onSave,
dismissLabel = stringRes(R.string.action__discard),
onDismiss = onDiscard,
onOutsideDismissal = onDismiss,
neutralLabel = stringRes(R.string.action__cancel),
onNeutral = onDismiss,
) {
Text(text = stringRes(R.string.action__discard_confirm_message))
}
}

View File

@@ -35,43 +35,55 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.theme.outline
@Composable
fun FlorisDropdownMenu(
items: List<String>,
fun <T : Any> FlorisDropdownMenu(
items: List<T>,
expanded: Boolean,
selectedIndex: Int,
modifier: Modifier = Modifier,
enabled: Boolean = true,
isError: Boolean = false,
labelProvider: (@Composable (T) -> String)? = null,
onSelectItem: (Int) -> Unit = { },
onExpandRequest: () -> Unit = { },
onDismissRequest: () -> Unit = { },
) {
@Composable
fun asString(v: T): String {
return labelProvider?.invoke(v) ?: v.toString()
}
Box(modifier = modifier.wrapContentSize(Alignment.TopStart)) {
val indicatorRotation by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
val index = selectedIndex.coerceIn(items.indices)
val color = if (isError) {
val color = if (!enabled) {
MaterialTheme.colors.outline
} else if (isError) {
MaterialTheme.colors.error
} else {
MaterialTheme.colors.onBackground
}
OutlinedButton(
modifier = Modifier
.fillMaxWidth(),
border = if (isError) {
modifier = Modifier.fillMaxWidth(),
border = if (isError && enabled) {
BorderStroke(ButtonDefaults.OutlinedBorderSize, MaterialTheme.colors.error)
} else {
ButtonDefaults.outlinedBorder
},
onClick = { onExpandRequest() },
enabled = enabled,
onClick = onExpandRequest,
) {
Text(
modifier = Modifier.weight(1.0f),
text = items[index],
text = asString(items[index]),
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = color,
@@ -79,7 +91,11 @@ fun FlorisDropdownMenu(
Icon(
modifier = Modifier.rotate(indicatorRotation),
painter = painterResource(R.drawable.ic_keyboard_arrow_down),
tint = color.copy(alpha = ContentAlpha.medium),
tint = if (enabled) {
color.copy(alpha = ContentAlpha.medium)
} else {
color
},
contentDescription = "Dropdown indicator",
)
}
@@ -94,7 +110,7 @@ fun FlorisDropdownMenu(
onDismissRequest()
},
) {
Text(text = item)
Text(text = asString(item))
}
}
}
@@ -128,13 +144,14 @@ fun FlorisDropdownLikeButton(
modifier = Modifier.weight(1.0f),
text = item,
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = color,
)
Icon(
modifier = Modifier.rotate(-90.0f),
painter = painterResource(R.drawable.ic_keyboard_arrow_down),
modifier = Modifier.autoMirrorForRtl(),
painter = painterResource(R.drawable.ic_keyboard_arrow_right),
tint = color.copy(alpha = ContentAlpha.medium),
contentDescription = "Dropdown indicator",
)

View File

@@ -16,7 +16,6 @@
package dev.patrickgold.florisboard.app.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -28,6 +27,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.prefs.AppPrefs
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
@@ -44,11 +46,12 @@ typealias FlorisScreenActions = @Composable RowScope.() -> Unit
typealias FlorisScreenBottomBar = @Composable () -> Unit
typealias FlorisScreenContent = PreferenceUiContent<AppPrefs>
typealias FlorisScreenFab = @Composable () -> Unit
typealias FlorisScreenNavigationIcon = @Composable () -> Unit
interface FlorisScreenScope {
var title: String
var backArrowVisible: Boolean
var navigationIconVisible: Boolean
var previewFieldVisible: Boolean
@@ -60,22 +63,32 @@ interface FlorisScreenScope {
fun bottomBar(bottomBar: FlorisScreenBottomBar)
fun content(content: FlorisScreenContent)
fun floatingActionButton(fab: FlorisScreenFab)
fun content(content: FlorisScreenContent)
fun navigationIcon(navigationIcon: FlorisScreenNavigationIcon)
}
private class FlorisScreenScopeImpl : FlorisScreenScope {
override var title: String by mutableStateOf("")
override var backArrowVisible: Boolean by mutableStateOf(true)
override var navigationIconVisible: Boolean by mutableStateOf(true)
override var previewFieldVisible: Boolean by mutableStateOf(false)
override var scrollable: Boolean by mutableStateOf(true)
override var iconSpaceReserved: Boolean by mutableStateOf(true)
private var actions: FlorisScreenActions = { }
private var bottomBar: FlorisScreenBottomBar = { }
private var content: FlorisScreenContent = { }
private var fab: FlorisScreenFab = { }
private var actions: FlorisScreenActions = @Composable { }
private var bottomBar: FlorisScreenBottomBar = @Composable { }
private var content: FlorisScreenContent = @Composable { }
private var fab: FlorisScreenFab = @Composable { }
private var navigationIcon: FlorisScreenNavigationIcon = @Composable {
val navController = LocalNavController.current
FlorisIconButton(
onClick = { navController.popBackStack() },
modifier = Modifier.autoMirrorForRtl(),
icon = painterResource(R.drawable.ic_arrow_back),
)
}
override fun actions(actions: FlorisScreenActions) {
this.actions = actions
@@ -93,6 +106,10 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
this.fab = fab
}
override fun navigationIcon(navigationIcon: FlorisScreenNavigationIcon) {
this.navigationIcon = navigationIcon
}
@Composable
fun Render() {
val previewFieldController = LocalPreviewFieldController.current
@@ -102,7 +119,7 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
}
Scaffold(
topBar = { FlorisAppBar(title, backArrowVisible, actions) },
topBar = { FlorisAppBar(title, navigationIcon.takeIf { navigationIconVisible }, actions) },
bottomBar = bottomBar,
floatingActionButton = fab,
) { innerPadding ->
@@ -110,15 +127,15 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
Modifier.florisVerticalScroll()
} else {
Modifier
}
Box(modifier = modifier.padding(innerPadding)) {
PreferenceLayout(
florisPreferenceModel(),
modifier = Modifier.fillMaxWidth(),
iconSpaceReserved = iconSpaceReserved,
content = content,
)
}
}
PreferenceLayout(
florisPreferenceModel(),
modifier = modifier
.padding(innerPadding)
.fillMaxWidth(),
iconSpaceReserved = iconSpaceReserved,
content = content,
)
}
}
}

View File

@@ -60,6 +60,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.ui.theme.outline
private val StepHeaderPaddingVertical = 16.dp
private val StepHeaderNumberBoxSize = 40.dp
@@ -198,7 +199,7 @@ private fun ColumnScope.Step(
val autoStepId by stepState.getCurrentAuto()
val backgroundColor = when (ownStepId) {
currentStepId -> primaryColor
else -> MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
else -> MaterialTheme.colors.outline
}
val contentVisible = ownStepId == currentStepId
StepHeader(
@@ -243,7 +244,8 @@ private fun ColumnScope.Step(
) {
Column(modifier = Modifier
.fillMaxSize()
.florisVerticalScroll(),
.florisVerticalScroll()
.padding(end = 8.dp),
) {
content()
}

View File

@@ -0,0 +1,209 @@
/*
* 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.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
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.ProvideTextStyle
import androidx.compose.material.Surface
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
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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
@Composable
fun FlorisOutlinedTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
placeholder: String? = null,
isError: Boolean = false,
showValidationHint: Boolean = true,
showValidationError: Boolean = false,
validationResult: ValidationResult? = null,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(),
) {
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = value)) }
val textFieldValue = textFieldValueState.copy(text = value)
FlorisOutlinedTextField(
value = textFieldValue,
onValueChange = {
textFieldValueState = it
if (value != it.text) {
onValueChange(it.text)
}
},
modifier = modifier,
enabled = enabled,
readOnly = readOnly,
textStyle = textStyle,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
singleLine = singleLine,
maxLines = maxLines,
placeholder = placeholder,
isError = isError,
showValidationHint = showValidationHint,
showValidationError = showValidationError,
validationResult = validationResult,
visualTransformation = visualTransformation,
interactionSource = interactionSource,
shape = shape,
colors = colors,
)
}
@Composable
fun FlorisOutlinedTextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
placeholder: String? = null,
isError: Boolean = false,
showValidationHint: Boolean = true,
showValidationError: Boolean = false,
validationResult: ValidationResult? = null,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(
unfocusedBorderColor = MaterialTheme.colors.outline,
disabledBorderColor = MaterialTheme.colors.outline,
),
) {
val textColor = textStyle.color.takeOrElse {
colors.textColor(enabled).value
}
val mergedTextStyle = textStyle.copy(color = textColor, textDirection = TextDirection.Content)
val isFocused by interactionSource.collectIsFocusedAsState()
val isErrorState = isError || (showValidationError && validationResult?.isInvalid() == true)
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,
) {
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(
text = validationResult.hintMessage(),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.56f),
)
}
if (showValidationError && validationResult?.isInvalid() == true && validationResult.hasErrorMessage()) {
Text(
text = validationResult.errorMessage(),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.error,
)
}
}

View File

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

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.components
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
fun Modifier.autoMirrorForRtl() = composed {
if (LocalLayoutDirection.current == LayoutDirection.Rtl) {
this.scale(-1f, 1f)
} else {
this
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,167 @@
/*
* 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.ext
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ListItem
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
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.FlorisIconButton
import dev.patrickgold.florisboard.app.ui.components.FlorisOutlinedBox
import dev.patrickgold.florisboard.app.ui.components.FlorisTextButton
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.res.ext.ExtensionComponent
import dev.patrickgold.florisboard.res.ext.ExtensionComponentName
import dev.patrickgold.florisboard.res.ext.ExtensionMeta
@Composable
fun ExtensionComponentNoneFoundView() {
Text(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
text = stringRes(R.string.ext__meta__components_none_found),
fontStyle = FontStyle.Italic,
)
}
@Composable
fun ExtensionComponentView(
meta: ExtensionMeta,
component: ExtensionComponent,
modifier: Modifier = Modifier,
onDeleteBtnClick: (() -> Unit)? = null,
onEditBtnClick: (() -> Unit)? = null,
) {
val componentName = remember(meta.id, component.id) { ExtensionComponentName(meta.id, component.id).toString() }
FlorisOutlinedBox(
modifier = modifier,
title = component.label,
subtitle = componentName,
) {
Column(
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = if (onDeleteBtnClick == null && onEditBtnClick == null) 8.dp else 0.dp,
),
) {
when (component) {
is ThemeExtensionComponent -> {
val text = remember(
component.authors, component.isNightTheme, component.isBorderless,
component.isMaterialYouAware, component.stylesheetPath(),
) {
buildString {
appendLine("authors = ${component.authors}")
appendLine("isNightTheme = ${component.isNightTheme}")
appendLine("isBorderless = ${component.isBorderless}")
appendLine("isMaterialYouAware = ${component.isMaterialYouAware}")
append("stylesheetPath = ${component.stylesheetPath()}")
}
}
Text(
text = text,
style = MaterialTheme.typography.body2,
color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
)
}
else -> { }
}
}
if (onDeleteBtnClick != null || onEditBtnClick != null) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
if (onDeleteBtnClick != null) {
FlorisTextButton(
onClick = onDeleteBtnClick,
icon = painterResource(R.drawable.ic_delete),
text = stringRes(R.string.action__delete),
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colors.error,
),
)
}
Spacer(modifier = Modifier.weight(1f))
if (onEditBtnClick != null) {
FlorisTextButton(
onClick = onEditBtnClick,
icon = painterResource(R.drawable.ic_edit),
text = stringRes(R.string.action__edit),
)
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun <T : ExtensionComponent> ExtensionComponentListView(
modifier: Modifier = Modifier,
title: String,
components: List<T>,
onCreateBtnClick: (() -> Unit)? = null,
componentGenerator: @Composable (T) -> Unit,
) {
Column(modifier = modifier) {
ListItem(
text = { Text(
text = title,
color = MaterialTheme.colors.secondary,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) },
trailing = if (onCreateBtnClick != null) {
@Composable {
FlorisIconButton(
onClick = onCreateBtnClick,
icon = painterResource(R.drawable.ic_add),
iconColor = MaterialTheme.colors.secondary,
)
}
} else { null },
)
if (components.isEmpty()) {
ExtensionComponentNoneFoundView()
} else {
for (component in components) {
componentGenerator(component)
}
}
}
}

View File

@@ -0,0 +1,900 @@
/*
* 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.ext
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.res.stringRes
import dev.patrickgold.florisboard.app.ui.components.FlorisButtonBar
import dev.patrickgold.florisboard.app.ui.components.FlorisIconButton
import dev.patrickgold.florisboard.app.ui.components.FlorisInfoCard
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
import dev.patrickgold.florisboard.app.ui.settings.theme.ThemeEditorScreen
import dev.patrickgold.florisboard.app.ui.theme.outline
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.common.ValidationResult
import dev.patrickgold.florisboard.common.android.showLongToast
import dev.patrickgold.florisboard.common.rememberValidationResult
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.keyboard.KeyboardExtension
import dev.patrickgold.florisboard.ime.spelling.SpellingExtension
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentImpl
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionEditor
import dev.patrickgold.florisboard.res.FlorisRef
import dev.patrickgold.florisboard.res.ZipUtils
import dev.patrickgold.florisboard.res.cache.CacheManager
import dev.patrickgold.florisboard.res.ext.Extension
import dev.patrickgold.florisboard.res.ext.ExtensionComponent
import dev.patrickgold.florisboard.res.ext.ExtensionComponentName
import dev.patrickgold.florisboard.res.ext.ExtensionDefaults
import dev.patrickgold.florisboard.res.ext.ExtensionEditor
import dev.patrickgold.florisboard.res.ext.ExtensionJsonConfig
import dev.patrickgold.florisboard.res.ext.ExtensionMaintainer
import dev.patrickgold.florisboard.res.ext.ExtensionManager
import dev.patrickgold.florisboard.res.ext.ExtensionMeta
import dev.patrickgold.florisboard.res.ext.ExtensionValidation
import dev.patrickgold.florisboard.res.ext.validate
import dev.patrickgold.florisboard.res.io.subFile
import dev.patrickgold.florisboard.res.io.writeJson
import dev.patrickgold.florisboard.snygg.SnyggStylesheetJsonConfig
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import java.util.*
import kotlin.reflect.KClass
private val TextFieldVerticalPadding = 8.dp
private val MetaDataContentPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp)
private const val AnimationDuration = 300
private val ActionScreenEnterTransition = fadeIn(tween(AnimationDuration))
private val ActionScreenExitTransition = fadeOut(tween(AnimationDuration))
sealed class EditorAction {
object ManageMetaData : EditorAction()
object ManageDependencies : EditorAction()
object ManageFiles : EditorAction()
data class CreateComponent<T : ExtensionComponent>(val type: KClass<T>) : EditorAction()
data class ManageComponent(val editor: ExtensionComponent) : EditorAction()
}
@Composable
fun ExtensionEditScreen(id: String, createSerialType: String?) {
val context = LocalContext.current
val cacheManager by context.cacheManager()
val extensionManager by context.extensionManager()
@Suppress("unchecked_cast")
fun <W : CacheManager.ExtEditorWorkspace<T>, T : ExtensionEditor> getOrCreateWorkspace(
uuid: String,
container: CacheManager.WorkspacesContainer<W>,
ext: Extension,
): W {
val workspace = container.getWorkspaceByUuid(uuid)
return workspace ?: container.new(uuid).also { newWorkspace ->
val sourceRef = ext.sourceRef
if (createSerialType == null) {
checkNotNull(sourceRef) { "Extension source ref must not be null" }
ZipUtils.unzip(context, sourceRef, newWorkspace.extDir)
}
newWorkspace.ext = ext
newWorkspace.editor = ext.edit() as? T
}
}
val ext = extensionManager.getExtensionById(id) ?: remember {
val meta = ExtensionMeta(
id = ExtensionDefaults.createLocalId("themes", System.currentTimeMillis().toString()),
version = "0.0.0",
title = "My themes",
maintainers = listOf(ExtensionMaintainer(name = "Local")),
license = "(none specified)",
)
when (createSerialType) {
ThemeExtension.SERIAL_TYPE -> ThemeExtension(meta, null, emptyList())
else -> null
}
}
if (ext != null) {
val uuid = rememberSaveable { UUID.randomUUID().toString() }
val cacheWorkspace = remember {
runCatching {
when (ext) {
is ThemeExtension -> {
getOrCreateWorkspace(uuid, cacheManager.themeExtEditor, ext)
}
else -> null
}
}
}
cacheWorkspace.onSuccess { workspace ->
if (workspace?.editor != null) {
ExtensionEditScreenSheetSwitcher(workspace, isCreateExt = createSerialType != null)
} else {
ExtensionNotFoundScreen(id = id)
}
}.onFailure { error ->
Text(text = remember(error) { error.stackTraceToString() })
}
} else {
ExtensionNotFoundScreen(id)
}
}
@Composable
private fun ExtensionEditScreenSheetSwitcher(
workspace: CacheManager.ExtEditorWorkspace<*>,
isCreateExt: Boolean,
) {
Box(modifier = Modifier.fillMaxSize()) {
EditScreen(workspace, isCreateExt)
AnimatedVisibility(
visible = workspace.currentAction != null,
enter = ActionScreenEnterTransition,
exit = ActionScreenExitTransition,
) {
when (val action = workspace.currentAction) {
is EditorAction.ManageMetaData -> {
ManageMetaDataScreen(workspace, isCreateExt)
}
is EditorAction.ManageDependencies -> {
ManageDependenciesScreen(workspace)
}
is EditorAction.ManageFiles -> {
ManageFilesScreen(workspace)
}
is EditorAction.CreateComponent<*> -> {
CreateComponentScreen(workspace, action.type)
}
is EditorAction.ManageComponent -> when (action.editor) {
is ThemeExtensionComponentEditor -> {
ThemeEditorScreen(workspace, action.editor)
}
else -> {
// Render nothing
}
}
else -> {
// Render nothing
Box(modifier = Modifier.fillMaxSize())
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun EditScreen(
workspace: CacheManager.ExtEditorWorkspace<*>,
isCreateExt: Boolean,
) = FlorisScreen {
title = stringRes(if (isCreateExt) {
when (workspace.ext) {
is KeyboardExtension -> R.string.ext__editor__title_create_keyboard
is SpellingExtension -> R.string.ext__editor__title_create_spelling
is ThemeExtension -> R.string.ext__editor__title_create_theme
else -> R.string.ext__editor__title_create_any
}
} else {
when (workspace.ext) {
is KeyboardExtension -> R.string.ext__editor__title_edit_keyboard
is SpellingExtension -> R.string.ext__editor__title_edit_spelling
is ThemeExtension -> R.string.ext__editor__title_edit_theme
else -> R.string.ext__editor__title_edit_any
}
})
val context = LocalContext.current
val navController = LocalNavController.current
val extEditor = workspace.editor ?: return@FlorisScreen
var showUnsavedChangesDialog by remember { mutableStateOf(false) }
var showInvalidMetadataDialog by remember { mutableStateOf(false) }
fun handleBackPress() {
if (workspace.isModified) {
showUnsavedChangesDialog = true
} else {
workspace.close()
navController.popBackStack()
}
}
fun handleSave() {
if (!extEditor.meta.validate()) {
showUnsavedChangesDialog = false
showInvalidMetadataDialog = true
return
}
val manifest = extEditor.build()
val manifestFile = workspace.saverDir.subFile(ExtensionDefaults.MANIFEST_FILE_NAME)
manifestFile.writeJson(manifest, ExtensionJsonConfig)
when (extEditor) {
is ThemeExtensionEditor -> {
for (theme in extEditor.themes) {
val stylesheetFile = workspace.saverDir.subFile(theme.stylesheetPath())
stylesheetFile.parentFile?.mkdirs()
val stylesheetEditor = theme.stylesheetEditor
if (stylesheetEditor != null) {
val stylesheet = stylesheetEditor.build()
stylesheetFile.writeJson(stylesheet, SnyggStylesheetJsonConfig)
} else {
val unmodifiedStylesheetFile = workspace.extDir.subFile(theme.stylesheetPath())
if (unmodifiedStylesheetFile.exists()) {
unmodifiedStylesheetFile.copyTo(stylesheetFile, overwrite = true)
}
}
}
}
else -> { }
}
val flexArchiveName = ExtensionDefaults.createFlexName(extEditor.meta.id)
val flexArchiveFile = workspace.dir.subFile(flexArchiveName)
ZipUtils.zip(workspace.saverDir, flexArchiveFile)
val sourceRef = if (isCreateExt) {
FlorisRef.internal(ExtensionManager.IME_THEME_PATH).subRef(flexArchiveName)
} else {
workspace.ext!!.sourceRef!!
}
flexArchiveFile.copyTo(sourceRef.absoluteFile(context), overwrite = true)
workspace.close()
navController.popBackStack()
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
modifier = Modifier.autoMirrorForRtl(),
icon = painterResource(R.drawable.ic_arrow_back),
)
}
bottomBar {
FlorisButtonBar {
ButtonBarSpacer()
ButtonBarTextButton(text = stringRes(R.string.action__cancel)) {
handleBackPress()
}
ButtonBarButton(text = stringRes(R.string.action__save)) {
handleSave()
}
}
}
content {
BackHandler {
handleBackPress()
}
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
this@content.Preference(
onClick = { workspace.currentAction = EditorAction.ManageMetaData },
iconId = R.drawable.ic_code,
title = stringRes(R.string.ext__editor__metadata__title),
)
this@content.Preference(
onClick = { workspace.currentAction = EditorAction.ManageDependencies },
iconId = R.drawable.ic_library_books,
title = stringRes(R.string.ext__editor__dependencies__title),
)
this@content.Preference(
onClick = { workspace.currentAction = EditorAction.ManageFiles },
iconId = R.drawable.ic_file_blank,
title = stringRes(R.string.ext__editor__files__title),
)
}
when (extEditor) {
is ThemeExtensionEditor -> {
ExtensionComponentListView(
title = stringRes(R.string.ext__meta__components_theme),
components = extEditor.themes,
onCreateBtnClick = {
workspace.currentAction = EditorAction.CreateComponent(ThemeExtensionComponent::class)
},
) { component ->
ExtensionComponentView(
modifier = Modifier.defaultFlorisOutlinedBox(),
meta = extEditor.meta,
component = component,
onDeleteBtnClick = { workspace.update { extEditor.themes.remove(component) } },
onEditBtnClick = { workspace.currentAction = EditorAction.ManageComponent(component) },
)
}
}
else -> {
// Render nothing
}
}
if (showUnsavedChangesDialog) {
FlorisUnsavedChangesDialog(
onSave = {
handleSave()
},
onDiscard = {
navController.popBackStack()
showUnsavedChangesDialog = false
},
onDismiss = {
showUnsavedChangesDialog = false
},
)
}
if (showInvalidMetadataDialog) {
JetPrefAlertDialog(
title = stringRes(R.string.ext__editor__metadata__title_invalid),
confirmLabel = stringRes(R.string.action__ok),
onConfirm = {
showInvalidMetadataDialog = false
},
onDismiss = {
showInvalidMetadataDialog = false
},
content = {
Text(text = stringRes(R.string.ext__editor__metadata__message_invalid))
},
)
}
}
}
@Composable
private fun ManageMetaDataScreen(
workspace: CacheManager.ExtEditorWorkspace<*>,
isCreateExt: Boolean,
) = FlorisScreen {
title = stringRes(R.string.ext__editor__metadata__title)
val meta = workspace.editor?.meta ?: return@FlorisScreen
var showValidationErrors by rememberSaveable { mutableStateOf(false) }
var id by rememberSaveable { mutableStateOf(meta.id) }
val idValidation = rememberValidationResult(ExtensionValidation.MetaId, id)
var version by rememberSaveable { mutableStateOf(meta.version) }
val versionValidation = rememberValidationResult(ExtensionValidation.MetaVersion, version)
var title by rememberSaveable { mutableStateOf(meta.title) }
val titleValidation = rememberValidationResult(ExtensionValidation.MetaTitle, title)
var description by rememberSaveable { mutableStateOf(meta.description ?: "") }
var keywords by rememberSaveable { mutableStateOf(meta.keywords?.joinToString("\n") ?: "") }
var homepage by rememberSaveable { mutableStateOf(meta.homepage ?: "") }
var issueTracker by rememberSaveable { mutableStateOf(meta.issueTracker ?: "") }
var maintainers by rememberSaveable { mutableStateOf(meta.maintainers.joinToString("\n")) }
val maintainersValidation = rememberValidationResult(ExtensionValidation.MetaMaintainers, maintainers)
var license by rememberSaveable { mutableStateOf(meta.license) }
val licenseValidation = rememberValidationResult(ExtensionValidation.MetaLicense, license)
fun handleBackPress() {
workspace.currentAction = null
}
fun handleApply() {
val invalid = idValidation.isInvalid() ||
versionValidation.isInvalid() ||
titleValidation.isInvalid() ||
maintainersValidation.isInvalid() ||
licenseValidation.isInvalid()
if (invalid) {
showValidationErrors = true
} else {
workspace.update {
workspace.editor?.meta = ExtensionMeta(
id = id.trim(),
version = version.trim(),
title = title.trim(),
description = description.trim().takeIf { it.isNotBlank() },
keywords = keywords.lines().map { it.trim() }.filter { it.isNotBlank() }.takeIf { it.isNotEmpty() },
homepage = homepage.trim().takeIf { it.isNotBlank() },
issueTracker = issueTracker.trim().takeIf { it.isNotBlank() },
maintainers = maintainers.lines().map { it.trim() }.filter { it.isNotBlank() }
.map { ExtensionMaintainer.fromOrTakeRaw(it) },
license = license.trim(),
)
}
workspace.currentAction = null
}
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
)
}
bottomBar {
FlorisButtonBar {
ButtonBarSpacer()
ButtonBarTextButton(text = stringRes(R.string.action__cancel)) {
handleBackPress()
}
ButtonBarButton(text = stringRes(R.string.action__apply)) {
handleApply()
}
}
}
content {
BackHandler {
handleBackPress()
}
Column(modifier = Modifier.padding(MetaDataContentPadding)) {
EditorSheetTextField(
enabled = isCreateExt,
isRequired = true,
value = id,
onValueChange = { id = it },
label = stringRes(R.string.ext__meta__id),
showValidationError = showValidationErrors,
validationResult = idValidation,
)
EditorSheetTextField(
isRequired = true,
value = version,
onValueChange = { version = it },
label = stringRes(R.string.ext__meta__version),
showValidationError = showValidationErrors,
validationResult = versionValidation,
)
EditorSheetTextField(
isRequired = true,
value = title,
onValueChange = { title = it },
label = stringRes(R.string.ext__meta__title),
showValidationError = showValidationErrors,
validationResult = titleValidation,
)
EditorSheetTextField(
value = description,
onValueChange = { description = it },
label = stringRes(R.string.ext__meta__description),
)
EditorSheetTextField(
value = keywords,
onValueChange = { keywords = it },
label = stringRes(R.string.ext__meta__keywords),
singleLine = false,
)
EditorSheetTextField(
value = homepage,
onValueChange = { homepage = it },
label = stringRes(R.string.ext__meta__homepage),
)
EditorSheetTextField(
value = issueTracker,
onValueChange = { issueTracker = it },
label = stringRes(R.string.ext__meta__issue_tracker),
)
EditorSheetTextField(
isRequired = true,
value = maintainers,
onValueChange = { maintainers = it },
label = stringRes(R.string.ext__meta__maintainers),
singleLine = false,
showValidationError = showValidationErrors,
validationResult = maintainersValidation,
)
EditorSheetTextField(
isRequired = true,
value = license,
onValueChange = { license = it },
label = stringRes(R.string.ext__meta__license),
showValidationError = showValidationErrors,
validationResult = licenseValidation,
)
}
}
}
@Composable
private fun ManageDependenciesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = FlorisScreen {
title = stringRes(R.string.ext__editor__dependencies__title)
val dependencyList = workspace.editor?.dependencies ?: return@FlorisScreen
fun handleBackPress() {
workspace.currentAction = null
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
)
}
content {
BackHandler {
handleBackPress()
}
FlorisInfoCard(
modifier = Modifier.padding(all = 8.dp),
text = """
Dependencies are currently not implemented, but are already somewhat
integrated as a placeholder for the future.
""".trimIndent().replace('\n', ' '),
)
if (dependencyList.isEmpty()) {
Text(text = "no deps found")
} else {
for (dependency in dependencyList) {
Text(text = dependency)
}
}
}
}
@Composable
private fun ManageFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = FlorisScreen {
title = stringRes(R.string.ext__editor__files__title)
fun handleBackPress() {
workspace.currentAction = null
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
)
}
content {
BackHandler {
handleBackPress()
}
FlorisInfoCard(
modifier = Modifier.padding(all = 8.dp),
text = """
Managing archive files is currently not supported.
""".trimIndent().replace('\n', ' '),
)
}
}
private enum class CreateFrom {
EMPTY,
EXISTING;
}
@Composable
private fun <T : ExtensionComponent> CreateComponentScreen(
workspace: CacheManager.ExtEditorWorkspace<*>,
type: KClass<T>,
) = FlorisScreen {
title = stringRes(when (type) {
ThemeExtensionComponent::class -> R.string.ext__editor__create_component__title_theme
else -> R.string.ext__editor__create_component__title
})
val context = LocalContext.current
val extensionManager by context.extensionManager()
val themeManager by context.themeManager()
var createFrom by rememberSaveable { mutableStateOf(CreateFrom.EXISTING) }
val extId = workspace.editor?.meta?.id ?: "null"
val components = remember<Map<ExtensionComponentName, ExtensionComponent>> {
when (val editor = workspace.editor) {
is ThemeExtensionEditor -> buildMap {
for (theme in editor.themes) {
put(ExtensionComponentName(extId, theme.id), theme)
}
for ((componentName, theme) in themeManager.indexedThemeConfigs.value ?: emptyMap()) {
if (componentName.extensionId != extId) {
put(componentName, theme)
}
}
}
else -> {
emptyMap()
}
}
}
var selectedComponentName by rememberSaveable(stateSaver = ExtensionComponentName.Saver) {
mutableStateOf(null)
}
var showValidationErrors by rememberSaveable { mutableStateOf(false) }
var newId by rememberSaveable { mutableStateOf("") }
val newIdValidation = rememberValidationResult(ExtensionValidation.ComponentId, newId)
var newLabel by rememberSaveable { mutableStateOf("") }
val newLabelValidation = rememberValidationResult(ExtensionValidation.ComponentLabel, newLabel)
var newAuthors by rememberSaveable { mutableStateOf("") }
val newAuthorsValidation = rememberValidationResult(ExtensionValidation.ComponentAuthors, newAuthors)
fun handleBackPress() {
workspace.currentAction = null
}
fun handleCreate() {
val invalid = createFrom == CreateFrom.EMPTY && (newIdValidation.isInvalid() ||
newLabelValidation.isInvalid() || newAuthorsValidation.isInvalid())
if (invalid) {
showValidationErrors = true
} else {
when (val editor = workspace.editor) {
is ThemeExtensionEditor -> {
when (createFrom) {
CreateFrom.EMPTY -> {
if (editor.themes.any { it.id == newId.trim() }) {
context.showLongToast("A theme with this ID already exists!")
} else {
val componentEditor = ThemeExtensionComponentEditor(
id = newId.trim(),
label = newLabel.trim(),
authors = newAuthors.lines().map { it.trim() }.filter { it.isNotBlank() },
)
editor.themes.add(componentEditor)
workspace.currentAction = null
}
}
CreateFrom.EXISTING -> {
val componentName = selectedComponentName ?: return
val componentId = if (editor.themes.any { it.id == componentName.componentId }) {
var suffix = 1
var tempId: String
do {
tempId = "${componentName.componentId}_${suffix++}"
} while (editor.themes.any { it.id == tempId })
tempId
} else {
componentName.componentId
}
if (componentName.extensionId == extId) {
val component = editor.themes.find { it.id == componentName.componentId } ?: return
val componentEditor = component.let { c ->
ThemeExtensionComponentEditor(
componentId, c.label, c.authors, c.isNightTheme, c.isBorderless,
c.isMaterialYouAware, stylesheetPath = "",
).also { it.stylesheetEditor = c.stylesheetEditor }
}
if (componentEditor.stylesheetEditor != null) {
val stylesheet = componentEditor.stylesheetEditor!!.build()
val stylesheetFile = workspace.extDir.subFile(componentEditor.stylesheetPath())
stylesheetFile.parentFile?.mkdirs()
stylesheetFile.writeJson(stylesheet, SnyggStylesheetJsonConfig)
componentEditor.stylesheetEditor = null
} else {
val srcStylesheetFile = workspace.extDir.subFile(component.stylesheetPath())
val dstStylesheetFile = workspace.extDir.subFile(componentEditor.stylesheetPath())
dstStylesheetFile.parentFile?.mkdirs()
srcStylesheetFile.copyTo(dstStylesheetFile, overwrite = true)
}
editor.themes.add(componentEditor)
} else {
val component = themeManager.indexedThemeConfigs.value?.get(componentName) ?: return
val componentEditor = (component as? ThemeExtensionComponentImpl)?.edit() ?: return
componentEditor.id = componentId
componentEditor.stylesheetPath = ""
val externalExt = extensionManager.getExtensionById(componentName.extensionId) ?: return
val stylesheetJson = ZipUtils.readFileFromArchive(
context, externalExt.sourceRef!!, component.stylesheetPath()
).getOrNull() ?: return
val dstStylesheetFile = workspace.extDir.subFile(componentEditor.stylesheetPath())
dstStylesheetFile.parentFile?.mkdirs()
dstStylesheetFile.writeText(stylesheetJson)
editor.themes.add(componentEditor)
}
workspace.currentAction = null
}
}
}
}
}
}
fun hasSufficientInfoForCreating(): Boolean {
return when (createFrom) {
CreateFrom.EMPTY -> newId.isNotBlank() && newLabel.isNotBlank() && newAuthors.isNotBlank()
CreateFrom.EXISTING -> components.containsKey(selectedComponentName)
}
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = painterResource(R.drawable.ic_close),
)
}
bottomBar {
FlorisButtonBar {
ButtonBarSpacer()
ButtonBarTextButton(text = stringRes(R.string.action__cancel)) {
handleBackPress()
}
ButtonBarButton(
text = stringRes(R.string.action__create),
enabled = hasSufficientInfoForCreating(),
) {
handleCreate()
}
}
}
content {
BackHandler {
handleBackPress()
}
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
RadioListItem(
onClick = { createFrom = CreateFrom.EXISTING },
selected = createFrom == CreateFrom.EXISTING,
text = stringRes(R.string.ext__editor__create_component__from_existing),
)
RadioListItem(
onClick = { createFrom = CreateFrom.EMPTY },
selected = createFrom == CreateFrom.EMPTY,
text = stringRes(R.string.ext__editor__create_component__from_empty),
)
}
if (createFrom == CreateFrom.EXISTING) {
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
for ((componentName, component) in components) {
RadioListItem(
onClick = { selectedComponentName = componentName },
selected = selectedComponentName == componentName,
text = component.label,
secondaryText = componentName.toString(),
)
}
}
} else if (createFrom == CreateFrom.EMPTY) {
FlorisInfoCard(
modifier = Modifier.defaultFlorisOutlinedBox(),
text = stringRes(R.string.ext__editor__create_component__from_empty_warning),
)
DialogProperty(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__meta__id),
) {
FlorisOutlinedTextField(
value = newId,
onValueChange = { newId = it },
singleLine = true,
showValidationError = showValidationErrors,
validationResult = newIdValidation,
)
}
DialogProperty(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__meta__label),
) {
FlorisOutlinedTextField(
value = newLabel,
onValueChange = { newLabel = it },
singleLine = true,
showValidationError = showValidationErrors,
validationResult = newLabelValidation,
)
}
DialogProperty(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__meta__authors),
) {
FlorisOutlinedTextField(
value = newAuthors,
onValueChange = { newAuthors = it },
showValidationError = showValidationErrors,
validationResult = newAuthorsValidation,
)
}
}
}
}
@Composable
private fun EditorSheetTextField(
modifier: Modifier = Modifier,
enabled: Boolean = true,
isRequired: Boolean = false,
value: String,
onValueChange: (String) -> Unit,
label: String,
singleLine: Boolean = true,
showValidationError: Boolean = false,
validationResult: ValidationResult? = null,
) {
val borderColor = MaterialTheme.colors.outline
Column(modifier = Modifier.padding(vertical = TextFieldVerticalPadding)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = TextFieldVerticalPadding),
) {
Text(
text = label,
style = MaterialTheme.typography.subtitle2,
)
if (isRequired) {
Text(
modifier = Modifier.padding(start = 2.dp),
text = "*",
style = MaterialTheme.typography.subtitle2,
color = MaterialTheme.colors.error,
)
}
}
FlorisOutlinedTextField(
modifier = modifier.fillMaxWidth(),
enabled = enabled,
value = value,
onValueChange = onValueChange,
singleLine = singleLine,
showValidationError = showValidationError,
validationResult = validationResult,
colors = TextFieldDefaults.outlinedTextFieldColors(
unfocusedBorderColor = borderColor,
disabledBorderColor = borderColor,
)
)
}
}

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