Compare commits

..

250 Commits

Author SHA1 Message Date
Patrick Goldinger
fa96a239b4 Release v0.3.14-beta03 2021-09-26 20:39:42 +02:00
Mangu Purty
a7b6f06b38 Add Warang Citi layout (#1274)
* Update config.json

* Update config.json

* Create ho.json

* Update ho.json

* Create warang_citi

* Update ho.json

* Rename warang_citi to warang_citi.json

* Update config.json

* Update and rename ho.json to warang_citi.json

* Add numericRow preferred for hoc language

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2021-09-26 20:07:52 +02:00
Patrick Goldinger
aa37a3e80a Merge pull request #1276 from florisboard/prefs-rework-3
Preference Rework Part 3: Setup screen, Gesture prefs
2021-09-26 19:46:33 +02:00
Patrick Goldinger
9158afaf3e Fix GitHub workflow failing due to OSS license plugin issue 2021-09-24 20:08:20 +02:00
Patrick Goldinger
b6580adb94 Implement separate keyboard height prefs for portrait/landscape (#360) 2021-09-24 19:36:45 +02:00
Patrick Goldinger
1109a3efb1 Add third-party license screen in new UI 2021-09-24 18:58:40 +02:00
Patrick Goldinger
b02c780dd7 Remove systemLanguage property from FlorisApplication 2021-09-23 18:45:18 +02:00
Patrick Goldinger
fc8557d19b Remove Timber Logcat library 2021-09-23 00:19:11 +02:00
Patrick Goldinger
1733c10ce2 Implement gesture screen UI and logic 2021-09-22 19:58:36 +02:00
Patrick Goldinger
ae516bf18a Improve setup screen based on feedback (#996) 2021-09-22 18:00:33 +02:00
Patrick Goldinger
e4cba358bb Upgrade AndroidX Navigation Compose to 2.4.0-alpha09 2021-09-21 19:13:07 +02:00
Patrick Goldinger
51d07270be Fix duplicate vibration duration label (#1272) 2021-09-21 19:03:36 +02:00
Patrick Goldinger
67a0d19776 Add new setup screen UI (#996) 2021-09-21 18:55:30 +02:00
Patrick Goldinger
fdbe790a96 Add custom string resource methods 2021-09-20 19:11:32 +02:00
Patrick Goldinger
dcee5dbf2d Release v0.3.14-beta02 2021-09-19 18:37:50 +02:00
Patrick Goldinger
0392342384 Update translations from Crowdin 2021-09-19 18:23:09 +02:00
nd500
6744a9fc87 Add Igbo layout 1 (#1251)
* igbo.json

Igbo language character layout

* Update config.json

Updated config.json to add 'Igbo' default subType

* Update config.json

format tweak...

* Updated config.json - added igbo section

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2021-09-18 18:02:28 +02:00
Aman9das
a48b95127c Fix redundant % symbol (#1267)
* fix > symbol in symbols-additional layout putting ?

* double % symbols was redundant
2021-09-18 17:18:58 +02:00
Hayleia
c35a282d88 Add Basic Telex for Vietnamese (#978)
* Add Basic Telex composer

* Add vi-VN popups

* Declare vietnamese language

Not sure about currencies

* Composer with rules supports uppercase and lowercase

* Fix composer composition in Firefox URL bar (again)

* Add fallback Telex rules

Warning, uw needs uuww

* Save a bit of visual space on Telex rule declarations

* Add missing popup

* Add z-rules
2021-09-18 17:11:19 +02:00
Patrick Goldinger
0a9ee749c1 Merge pull request #1261 from florisboard/prefs-rework-2
Preference Rework Part 2: Clipboard preferences + other prefs
2021-09-18 17:01:47 +02:00
Patrick Goldinger
92da7b74a0 Change LifecycleInputMethodService to provide uiScope 2021-09-18 16:40:47 +02:00
Patrick Goldinger
940baa00ae Remove old about activity / Remove other old unused code 2021-09-18 01:55:09 +02:00
Patrick Goldinger
4ec91e5415 Implement input feedback preferences / Clean up unused old settings 2021-09-17 19:54:07 +02:00
Patrick Goldinger
03b01b58de Add input feedback preference screen 2021-09-17 19:40:11 +02:00
Patrick Goldinger
58a4ae2bc9 Implement new keyboard preferences in the keyboard logic 2021-09-17 18:29:01 +02:00
Patrick Goldinger
f5c08164a4 Upgrade to JetPref 0.1.0-alpha07 2021-09-17 17:55:24 +02:00
Patrick Goldinger
4e794cd05c Add descriptions to key hint mode options (#955) 2021-09-16 23:49:21 +02:00
Patrick Goldinger
936c671a7f Add Keyboard preferences screen / Clean up strings.xml 2021-09-16 23:33:57 +02:00
Patrick Goldinger
1e29b1683b Temporarily remove translations to avoid string IDs to mismatch
Translations are saved in Crowdin and will be re-fetched later
2021-09-16 21:07:26 +02:00
Patrick Goldinger
1321bfc9c9 Add LifecycleInputMethodService 2021-09-16 19:27:31 +02:00
Patrick Goldinger
8c79e633a0 Upgrade to JetPref 0.1.0-alpha05 2021-09-16 19:19:03 +02:00
Patrick Goldinger
98790485d1 Improve InputMethodUtils and picker button onClick 2021-09-15 23:54:19 +02:00
Patrick Goldinger
d784472d3d Fix system back button/gesture not working in Settings UI 2021-09-15 23:40:38 +02:00
Patrick Goldinger
d4a845c3a0 Adapt new clipboard prefs in ClipboardManager 2021-09-15 23:28:29 +02:00
Aman9das
5237458c21 Fix > symbol in western-additional layout using incorrect code (#1254) 2021-09-15 22:23:09 +02:00
Patrick Goldinger
b8e6bd5f40 Add clipboard settings screen 2021-09-15 22:08:04 +02:00
Patrick Goldinger
5881f42820 Add new unit strings (#692) 2021-09-15 21:54:30 +02:00
Patrick Goldinger
cae620da02 Add InputMethodUtils 2021-09-15 20:27:47 +02:00
Patrick Goldinger
2c6ed33f38 Update README.md to fix incorrect release links 2021-09-14 19:06:36 +02:00
Patrick Goldinger
a71c89d5d3 Release v0.3.14-beta01 2021-09-14 18:58:09 +02:00
Patrick Goldinger
408716f4c4 Fix custom app language setter bug 2021-09-14 18:48:22 +02:00
Patrick Goldinger
005040df66 Update translations from Crowdin 2021-09-14 18:41:20 +02:00
Patrick Goldinger
527b3b293e Fix glide typing preview not working (#1191) 2021-09-14 00:59:43 +02:00
mrdeck4rd
dec39e8bd9 Add Thai layout (#1226)
* Add Thai Kedmanee layout.

* Add Thai layout

-Thai Kedmanee
-Thai Manoonchai

* Added Thai Numerals

* Fix syntax issue in config file

* Add new line to thai_manoonchai.json

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2021-09-14 00:34:33 +02:00
Patrick Goldinger
eaa630b219 Merge pull request #1237 from florisboard/prefs-rework-1
Preference Rework Part 1: JetPref lib + base preparation
2021-09-14 00:25:22 +02:00
Patrick Goldinger
4217594dd4 Update JetPref to 0.1.0-alpha03 2021-09-14 00:16:24 +02:00
Patrick Goldinger
96d01a28fb Implement live preview for advanced preferences 2021-09-13 22:14:28 +02:00
Patrick Goldinger
ec9f4a6654 Add keyboard preview in Settings (#82) 2021-09-13 21:30:39 +02:00
Patrick Goldinger
1990916851 Change home card to be collapsible 2021-09-12 23:51:36 +02:00
Patrick Goldinger
9ab49ff1c9 Add Settings language option feature 2021-09-12 23:03:47 +02:00
Patrick Goldinger
b4f08c38b3 Add home screen beta tester note 2021-09-11 14:05:29 +02:00
Patrick Goldinger
5a375ec30f Implement devtools and force-private-mode 2021-09-11 13:33:46 +02:00
Patrick Goldinger
efded915c3 Implement show/hide app icon feature / Fix theme issues 2021-09-11 13:14:46 +02:00
Patrick Goldinger
4bf335f4af Implement Jetpack Theme + Settings Theme pref functionality 2021-09-11 12:20:12 +02:00
Patrick Goldinger
643920c094 Add advanced preferences 2021-09-10 19:46:44 +02:00
Patrick Goldinger
172eb54909 Add license screen / Improve internal UI code 2021-09-10 17:56:15 +02:00
Patrick Goldinger
3d3d8f74c4 Re-add app icon on about screen 2021-09-10 00:39:04 +02:00
Patrick Goldinger
cf77038336 Re-implement version copy feature 2021-09-10 00:15:59 +02:00
Patrick Goldinger
619e06be1b Rename MainAppActivity to FlorisAppActivity 2021-09-09 23:59:39 +02:00
Patrick Goldinger
36dde9f7e0 Add about screen structure 2021-09-09 23:51:38 +02:00
Patrick Goldinger
58d0954fe4 Add base skeleton for implementing new Settings UI 2021-09-08 19:29:40 +02:00
Patrick Goldinger
17d28c9d9d Clean up and sort Gradle dependencies 2021-09-08 18:48:28 +02:00
Patrick Goldinger
46fb33b2a0 Rename package settings to oldsettings 2021-09-08 18:25:32 +02:00
Patrick Goldinger
e36aa80cc0 Add JetPref library import into gradle dependencies 2021-09-07 23:59:03 +02:00
Patrick Goldinger
3ce323596c Update Gradle version to 7.0.2 2021-09-07 23:58:41 +02:00
Hèctor Godoy
abe67c2a6f Add ñ as popup on catalan layout (#1236) 2021-09-07 23:50:02 +02:00
Aman9das
5c1768a924 Add additonal Western layout with more symbols (#1214)
* Add files via upload

* Contributing guidelines hyperlink

* Rename western.json to western-numrow.json

* western keyboard-unchanged

* Update and rename western-numrow.json to western-additional-symbols.json

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2021-09-02 19:33:44 +02:00
Patrick Goldinger
92086954de Update README.md to make it less cluttered (#1195) 2021-08-28 18:58:00 +02:00
Patrick Goldinger
2b47d4a811 Release v0.3.13 2021-08-26 17:47:16 +02:00
Patrick Goldinger
e977c512a7 Fix increment step for vibration duration preference 2021-08-26 12:57:43 +02:00
Patrick Goldinger
0828c0a4db Update issue template chooser and contributing for new discussions 2021-08-25 22:47:19 +02:00
Patrick Goldinger
ecee7bfa56 Update README.md for 0.3.13 2021-08-25 17:41:35 +02:00
Patrick Goldinger
7a0485c0dc Release v0.3.13-beta12 2021-08-24 15:44:12 +02:00
Patrick Goldinger
d4ff6143f4 Add heap size restriction note to description of suggestion pref 2021-08-24 15:37:13 +02:00
Patrick Goldinger
8aa8b30a43 Lower minimum heap size requirement for word suggestions to 256 MB 2021-08-24 15:33:27 +02:00
Patrick Goldinger
019bdcae6f Add devtool override toggle for heap size restriction (#1178) 2021-08-24 15:31:14 +02:00
Patrick Goldinger
edfea2dbf2 Release v0.3.13-beta11 2021-08-23 22:25:00 +02:00
Waelwindows
eeec8e0d17 Add katakan middle dot to popups (#1177) 2021-08-23 22:15:14 +02:00
Patrick Goldinger
e79ab3c2e3 Add min memory size restriction for word suggestions (#1176) 2021-08-23 17:33:54 +02:00
Patrick Goldinger
2719cf4930 Release v0.3.13-beta10 2021-08-22 22:10:48 +02:00
Patrick Goldinger
d6d89aac43 Update translations from Crowdin 2021-08-22 21:53:37 +02:00
Patrick Goldinger
973c738059 Adapt Settings home screen for 0.3.13 2021-08-22 21:51:22 +02:00
Waelwindows
2345192728 Add Japanese JIS layout (#140)
* feat: Add Japanaese JIS layout

* feat: Add KanaSelector and KanaSizeSelector

* test: Try out new selectors

* fix: Add new selectors to polymorphic key data list

This should help make florisboard recognize the new selectors
when used in layouts.

* fix: Implement `evaluteKana` for ComputingEvaluator

This should fix the crashes, hopefully

* feat: Rework the JIS layout

* fix: Implement basic logic for kana state

* feat: Add popups for JIS

* chore: Rename JIS specific layouts

For some reason this change broke the pop-ups

* feat: Add Kana Unicode compositor

* feat: Implement flipping small state and refactor

This should allow small kana characters to be switched over to their
normal counterparts. Also clean up and refactor `KanaUnicode` and make
the filename more appropriate

* Merge `master` to `japanese`

* chore: Add internal codes for Kana switching

This should be the starting point for getting actual keys
to switch between the kana types

* feat: Add initial modifer layout for JIS

* chore: Add keycode for ideographic CJK space

* feat: Add logic for kana switching

* chore: Update layout modifer to use kana switch

* feat: Add icon for kana switcher button

* fix: Scale the icon and update relevant entries

* feat: Begin work on kana selector popup

* Add kana attributes to keyboard state

* Add workaround for Japanese popups

* chore: Remove URL popups for JIS layout

* chore: Add codes and groups for kana switching

This commit adds the "~kana" group with groupId 4.

* feat: Use "~kana" in JIS layout.

* feat: Remove `KanaSizeSelector`.

It will be redundant when compositor handles it instead.

* feat: Add alpha code for making kana small.

This code currently uses a sentinel character to trigger
the transformation. This is not ideal as that means that users
won't be able to use that character in the keyboard. This won't
be a problem if the sentinel character is a Unicode codepoint
that cannot or would not be used normally. As such, this is just
a demonstration of the how the feature should function not it's
final implementation.

* test: Add kana small key in JIS for testing.

This is to preview changes to the kana small algorithm,
This commit should be removed from history later on.

* fix: Add `KANA_SWITCHER` to exceptionsForKeyCodes

* chore: Change "~kana" groupId to avoid conflicts

* fix: Change Kana small behaviour

* chore: Change internal kana small key code

* test: Remove kana small button from JIS layout

* feat: Add iteration marks to daku list

* feat: Allow swtiching between kana modifiers

This allows users to add/remove dakuten/handakuten/small
from kana and swtiching between the three. This is helpful
as it allows users to quickly rectify mistakes. It also lays
the groundwork for the future 12 flick layout

* feat: Change Kana swticher behaviour on katakana

Makes katakana switch over to hiragana immediately. As half-width
katakana is not usually used. The kana-switcher pop-up suffices
switching over to it when needed.

A potential downside to doing it like this making the half-width
katakana more hidden and less likely to be used.

* Add CJK_SPACE special behavior to input logic

* feat: Add CJK punctuation to popups and JIS layout

* feat: Add CJK punctuation symbols

* feat: Add CJK row numbers

* feat: Add postal mark symbol in CJK symbols

And change kana small internal code. Used to be set to
postal mark with face. Now set to geta mark.

* chore: Set default numeric row to CJK for JIS

* feat: Add modifiers layouts for CJK punctuation

* chore: Add latin half-width braces to CJK layout

* fix: Change composer behaviour on empty string

* chore: Add non-composing (han)daku to JIS popups

* fix: Change composer behaviour to be consistent

Also clean up the code a little.

* fix: Change composer empty string behaviour

* feat: Add WidthSelector and migrate half_kata Kana

This adds width selector for all languages instead of tying it down
to only `KanaSelector`. As such `KanaSelector`'s `half_kata`' field
is obsolete.

* fix: Fix bug in composing logic

* fix: Implement width properly and remove half_kata

* refactor: Clean up JIS layout JSON

* fix: Change JIS punctuation to use width_selector

* Improve half width implementation / Clean-up code base

* fix: Remove Half-kata from kana switcher cycle.

* feat: Add char width keycodes and implementation

* feat: Add half-width forms to symbols/cjk layouts

* feat: Add char width switcher to CJK mod layouts

Icons are yet to be added, so the labels will be strings for now

* feat: Add half-width forms for symbols2/cjk layout

* chore: Add more appropriate CJK chars to symbols2

* feat: Add icons for character width switching.

Note that `ic_keyboard_char_width_full` and `ic_keyboard_char_width_half`
are just copies of `ic_keyboard_char_width_switcher` for now.

* feat: Add relevant entries to show char width icons

* chore: Update the character width pop up icons.

And rename the internal names so that they match up with kana_switcher.

* fix: Make char width swticher icon toggle instead

* fix: Make character width icons more consistent.

* chore: Remove reference to char_width_switcher icon

* test: Iest code for why the composer is not working

* test: Remove test code for KanaUnicode

This reverts commit f6ae545183.

* fix: Correct small kana association for E and U

* test: Remove test code for KanaUnicode

This reverts commit f6ae545183.

* fix: Don't add small sentinel on non small char.

* fix: Correct small ya entry in KanaUnicode

* feat: Make kana switcher icon toggle between modes

* fix: Update svg colors to fit with other vectors

* Update app/src/main/java/dev/patrickgold/florisboard/ime/text/TextInputManager.kt

Co-authored-by: Patrick Goldinger <patrick.goldinger@pm.me>

* chore: Fix author name

* chore: Fix author name in CJK numbers

* chore: Fix indentation on author field

Co-authored-by: Waelwindows <waelwindows@hotmail.com>
Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2021-08-22 21:02:55 +02:00
Patrick Goldinger
dc1c71a01d Vastly improve spell checker performance and caching
Additionally add support for multiple locales caching
2021-08-22 17:50:07 +02:00
Patrick Goldinger
b93b646d41 Improve spell checker internals, reduce setup calls 2021-08-22 17:50:07 +02:00
Patrick Goldinger
75354703ce Improve keyboard subtypes locale match for spell checker 2021-08-22 17:50:07 +02:00
Patrick Goldinger
7123f004e9 Fix candidates view showing for raw editors
Raw editors don't support composing input and suggestions hardly
make any sense, so the clipboard cursor row is shown instead.
2021-08-21 00:02:03 +02:00
Patrick Goldinger
3dac44d326 Re-add old suggestions implementation for stable track 2021-08-21 00:02:03 +02:00
Patrick Goldinger
76de7e5db9 Add Flictionary for en_US 2021-08-21 00:02:03 +02:00
Patrick Goldinger
95e0b3408d Fix delete word swipe for browser inputs 2021-08-20 21:42:53 +02:00
Patrick Goldinger
358440779f Fix crash when using delete swipe for raw input editors (#1014) 2021-08-20 21:42:53 +02:00
Patrick Goldinger
6518eebce7 Fix word alignment for delete_words_precisely swipe 2021-08-20 21:42:53 +02:00
Patrick Goldinger
e19df82147 Fix performance issues for large text areas 2021-08-20 21:42:53 +02:00
Patrick Goldinger
3ec3f90d9f Fix selection and composing state bugs in EditorInstance 2021-08-20 21:42:53 +02:00
Patrick Goldinger
85452eeb10 Fix UserDictionary locale string bug 2021-08-20 21:42:53 +02:00
Patrick Goldinger
e4520007ea Rework EditorInstance cached input and word detection logic 2021-08-20 21:42:53 +02:00
Patrick Goldinger
63b55a9560 Update ICU build repo status 2021-08-17 20:01:42 +02:00
Patrick Goldinger
4dbc1ca740 Update GitHub workflows to fetch submodules 2021-08-16 21:08:29 +02:00
Patrick Goldinger
06c585885e Move icu4c to separate repository
Preparation step to build ICU separately for F-Droid builds. Also the
main repo's stats are now not polluted again from the sheer massive
size of ICU.
2021-08-16 21:08:29 +02:00
Patrick Goldinger
5bede68a82 Release v0.3.13-beta09 2021-08-15 21:34:45 +02:00
Patrick Goldinger
1a83456d77 Update feature roadmap of FlorisBoard 2021-08-15 21:26:14 +02:00
Patrick Goldinger
58d8ce96d9 Update translations from Crowdin 2021-08-15 12:15:32 +02:00
Patrick Goldinger
5aec281e87 Fix Smartbar Cut/Copy buttons limited to few characters (#1146) 2021-08-15 12:01:52 +02:00
Patrick Goldinger
bcbf561887 Fix popup merge bug for group assigned keys (#1028) 2021-08-14 18:43:11 +02:00
Waelwindows
813f300a15 Adjust Arabic popups for main forms and remove nums (#1087)
This commit makes the most common popups the main ones which should
allow Arabic sub-layout users to use FlorisBoard's smart popup feature.
2021-08-14 11:49:56 +02:00
Patrick Goldinger
a356585cf8 Fix 5+1 keyboard layout bug (#1100) 2021-08-14 01:32:52 +02:00
dvrnynr
689881f981 Remove popups not related to Turkish 2021-08-13 23:01:31 +02:00
Patrick Goldinger
d473369f37 Improve haptic feedback UI and internal logic 2021-08-13 22:59:43 +02:00
Patrick Goldinger
5fcd605b7d Possibly fix repeating delete key 2021-08-13 20:04:25 +02:00
Patrick Goldinger
2ea9dfee60 Fix theme editor preview looking distorted (#1136) 2021-08-13 18:56:40 +02:00
Patrick Goldinger
07ad6820cc Merge pull request #1142 from florisboard/input-feedback-manager
Rework audio and haptic feedback of FlorisBoard
2021-08-12 12:31:32 +02:00
Patrick Goldinger
1c8523c6dd Adjust input feedback feature toggle internals 2021-08-12 12:14:07 +02:00
Patrick Goldinger
84f682aaa7 Add new InputFeedbackManager 2021-08-08 11:57:05 +02:00
Patrick Goldinger
efc03a90b5 Remove jcenter repository / Remove dependency update task 2021-08-08 11:43:02 +02:00
Patrick Goldinger
8f3562a0c8 Add alternative numpad with PC arrangement of digits (#1132) 2021-08-06 10:04:49 +02:00
Patrick Goldinger
b15f7f68ae Remove AssetRef and AssetSource 2021-08-06 00:22:24 +02:00
Patrick Goldinger
b646b3095b Rework code base to use FlorisRef instead of AssetRef 2021-08-06 00:22:24 +02:00
Patrick Goldinger
261ea5db2e Rename v0.4.0 milestone to v0.3.x 2021-08-04 23:32:56 +02:00
Patrick Goldinger
ff93377459 Release v0.3.13-beta08 2021-08-04 00:34:40 +02:00
Patrick Goldinger
f90befdfbe Fix crash after rebooting in clipboard manager (#1097) 2021-08-04 00:21:03 +02:00
Patrick Goldinger
d490d6d457 Update translations from Crowdin 2021-08-03 23:36:22 +02:00
Patrick Goldinger
3fdaa448af Merge pull request #1128 from Glitchy-Tozier/patch-2
Update question.md
2021-08-03 21:29:26 +02:00
Patrick Goldinger
7f88643361 Merge pull request #1127 from Glitchy-Tozier/patch-1
Update feature_request.md
2021-08-03 21:28:44 +02:00
Glitchy-Tozier
55dc817843 Update question.md 2021-08-03 21:07:18 +02:00
Glitchy-Tozier
6e2969d8a6 Update feature_request.md 2021-08-03 21:04:10 +02:00
Patrick Goldinger
9a146ba2f0 Fix crash in Settings when layout is selected but not installed 2021-08-03 21:01:06 +02:00
Patrick Goldinger
5f224806e2 Merge pull request #1126 from X-yl/master
Make sure to clone images in clipboard history
2021-08-03 20:22:33 +02:00
x-yl
77f048abda Make sure to clone images in clipboard history
Looks like I forgot to clone images when the internal clipboard is off
but the clipboard history is still on. We have to own the items in the
clipboard history since authorization to read them is temporary.
2021-08-03 11:50:43 +04:00
Patrick Goldinger
e45efc08a5 Fix gesture velocity threshold (#1086) 2021-08-01 20:41:54 +02:00
Patrick Goldinger
d1dd91d5c4 Update GitHub workflow to use Java 11
Required by Gradle 7.0.0
2021-07-31 20:01:37 +02:00
Patrick Goldinger
106ef0c417 Upgrade Gradle to 7.0.0 and adapt new Gradle syntax 2021-07-30 21:01:32 +02:00
Patrick Goldinger
8989b7130a Fix View invalidation from incorrect thread (#1118) 2021-07-30 09:25:03 +02:00
Patrick Goldinger
0663708afb Possible fix for NaN crash (#1018) 2021-07-30 09:09:41 +02:00
Patrick Goldinger
d58aba71b8 Add FLorisLocale and String utility functions 2021-07-30 09:07:19 +02:00
Patrick Goldinger
9d364f99e2 Fix glide typing which was broken in beta07 2021-07-25 19:31:50 +02:00
Patrick Goldinger
edb62f0f38 Release v0.3.13-beta07 2021-07-25 10:20:51 +02:00
Patrick Goldinger
e771eaf0a4 Merge pull request #1101 from florisboard/lithuanian-layout-support
Add support for Lithuanian layout / popup mapping
2021-07-25 09:55:32 +02:00
Patrick Goldinger
199b5c9e67 Add support for Lithuanian layout / popup mapping (#1092) 2021-07-25 09:50:26 +02:00
Patrick Goldinger
5d121935d2 Merge pull request #1091 from florisboard/keyboard-data-rework
Keyboard data logic rework
2021-07-25 08:24:32 +02:00
Patrick Goldinger
ee0677b6e5 Fix double dotted circle for combining diacritical marks (#1089) 2021-07-24 00:12:08 +02:00
Patrick Goldinger
11325e99c4 Update turkish layout and popups to fix the capital I with dot bug 2021-07-23 23:54:55 +02:00
Patrick Goldinger
fc5a6b5af3 Rework internal key data logic
This allows for selectors to be used within popups as well. Is also the solid foundation for further development with emojis and other keyboards than the text keyboard.
2021-07-23 23:54:14 +02:00
Patrick Goldinger
65d17ceea3 Fix feature roadmap text in README (#1081) 2021-07-22 09:12:17 +02:00
Patrick Goldinger
8a57ada148 Merge pull request #1082 from florisboard/spelling3
Spell checker Part 3: APK size reduction
2021-07-22 08:56:42 +02:00
Patrick Goldinger
82e07b4de3 Re-packagae ICU data file as arch-independent data archive
Re-add break iteration because it is needed for spelling corrections
2021-07-21 00:46:17 +02:00
Patrick Goldinger
6ca5645656 Remove Nuspell Dictionary Finder / Remove glob.h port 2021-07-19 23:05:30 +02:00
Patrick Goldinger
a75ff21305 Optimize ICU data feature filter and reduce used ICU headers
APK size is now at ~26MB, further improvements with .dat file possible
2021-07-18 23:33:53 +02:00
Patrick Goldinger
a7b00494e5 Fix Nuspell license / Remove Nuspell main.cxx (unused) 2021-07-18 23:32:04 +02:00
Patrick Goldinger
a0de409878 Release v0.3.13-beta06 2021-07-15 21:29:13 +02:00
Patrick Goldinger
3f0944906d Merge pull request #1069 from florisboard/spelling2
Spell checker Part 2: Bug fixes, feedback incorporation etc.
2021-07-15 20:22:44 +02:00
Patrick Goldinger
79ef5445a1 Fix dictionaries with legacy encodings crashing the app (#1064)
Especially Cyrillic (ISO8859-7)
2021-07-15 18:10:58 +02:00
Patrick Goldinger
dea2795499 Fix Czech dictionary import failing for XPI archives (#1064) 2021-07-15 17:31:17 +02:00
Patrick Goldinger
650e4fb3a9 Fix FreeOffice link not working for some locales (#1064) 2021-07-14 21:14:22 +02:00
Patrick Goldinger
29a630dcd1 Fix importer not detecting license for some Mozilla XPI files (#1064) 2021-07-14 20:47:02 +02:00
Patrick Goldinger
7733ea0c02 Merge pull request #1021 from empratyush/master
added support for direct boot
2021-07-14 20:34:29 +02:00
Pratyush
3d13d65c52 added support for direct boot 2021-07-14 22:34:07 +05:30
Patrick Goldinger
575058550a Fix auto capitalization pref incorrectly labeled (#1064) 2021-07-14 18:55:55 +02:00
Patrick Goldinger
ad3e3cb7ec Release v0.3.13-beta05 2021-07-12 20:01:19 +02:00
Patrick Goldinger
e24ca7ca4a Update translations from Crowdin 2021-07-12 19:33:49 +02:00
Patrick Goldinger
1b6d8c8f6d Merge pull request #1061 from GoRaN909/patch-6
Update kurdish_kurmanci.json
2021-07-12 19:22:25 +02:00
Patrick Goldinger
27e172cbe3 Merge pull request #1063 from florisboard/icu-header-fix
Extend build script to remove unused ICU header files
2021-07-12 19:03:19 +02:00
Patrick Goldinger
e40c720f99 Extend build script to remove unused ICU header files
Realized they are quite a lot lol
2021-07-12 18:48:48 +02:00
GoRaN
c8d7071741 Update kurdish_kurmanci.json
Replaecd none Kurmanci character "ı" by the correct one latin letter "i"
2021-07-10 22:22:58 +03:00
Patrick Goldinger
5c2154253d Merge pull request #1015 from jeremiah-miller/optimized-swype-layouts
Add keyboard layouts optimized for swype input
2021-07-10 20:46:00 +02:00
Patrick Goldinger
3c79cca77c Fix cut off hints in borderless themes (#1049) 2021-07-10 19:53:54 +02:00
Patrick Goldinger
65c0ab724f Merge pull request #1011 from X-yl/improvements
Code cleanup + some polish
2021-07-10 19:17:49 +02:00
Patrick Goldinger
d5d259e13e Merge pull request #1052 from florisboard/spelling
Add experimental spell checker & extension support
2021-07-10 19:06:55 +02:00
Patrick Goldinger
691d3929eb Prepare spell checker for experimental release on beta 2021-07-10 19:03:36 +02:00
Patrick Goldinger
57b3b7b5d7 Add spelling correction cache 2021-07-10 17:55:07 +02:00
Patrick Goldinger
1582c1a3cf Re-package ICU lib to reduce APK size a bit 2021-07-10 16:52:55 +02:00
Patrick Goldinger
e22fe940c1 Merge pull request #1057 from Luensche/patch-1
Link files in CONTRIBUTING.md
2021-07-10 02:53:44 +02:00
Björn Engel
7f19892444 Link files in CONTRIBUTING.md 2021-07-09 08:44:46 +02:00
Patrick Goldinger
123a016ec0 Raise import limit from 20 to 24 MiB 2021-07-08 01:27:41 +02:00
Patrick Goldinger
5b6dcb3bc4 Fix some issues with manual import 2021-07-08 01:15:45 +02:00
Patrick Goldinger
8d71200b66 Add manual aff/dic import 2021-07-07 21:10:03 +02:00
Patrick Goldinger
6d333d2b40 Remove language debug output code 2021-07-07 03:47:42 +02:00
Patrick Goldinger
baacfd4469 Rework data filters, add more debug logging
(For investigating crashes for some dictionary files)
2021-07-07 03:44:53 +02:00
Patrick Goldinger
e8925ce697 Add ICU data filter (reduce data library size by 10MB / arch) 2021-07-06 03:18:25 +02:00
Patrick Goldinger
e40c2a6736 Re-add ICU as git submodule
No more 2.5mil LOC added lol
2021-07-05 19:36:18 +02:00
x-yl
b9518dc92b Cache ideal gesture lengths 2021-07-05 11:24:47 +04:00
x-yl
47f26f2336 Make gesture trail time based 2021-07-05 11:24:47 +04:00
Patrick Goldinger
fbc8d98209 Add user dictionary lookup for spell checking 2021-07-03 20:52:15 +02:00
Patrick Goldinger
27aeda8921 Implement suggestions limit / Fix initial indexing bug 2021-07-03 12:29:38 +02:00
Patrick Goldinger
4c2e642a85 Temporarily disable x86/x86_64 support to reduce APK size 2021-07-01 21:33:18 +02:00
Patrick Goldinger
f8995827f6 Raise max spelling dict size limit from 6MiB to 16MiB 2021-07-01 20:40:34 +02:00
Patrick Goldinger
d7593d12f2 Remove Home tab current-word paragraph 2021-07-01 20:38:24 +02:00
Patrick Goldinger
cd471a8323 Update README.md and open source licenses 2021-07-01 19:26:42 +02:00
Patrick Goldinger
9ad962c7d0 Remove unused flict binary files 2021-07-01 19:18:01 +02:00
Patrick Goldinger
b4e16ca445 Remove hunspell library 2021-07-01 19:06:46 +02:00
Patrick Goldinger
c2269fe23d Fix nuspell initialization error 2021-07-01 19:04:55 +02:00
Patrick Goldinger
d720435945 Add nuspell library 2021-07-01 18:47:30 +02:00
Patrick Goldinger
e33b0d39f9 Add external glob implementation 2021-07-01 18:45:43 +02:00
Patrick Goldinger
bbf3fb96be Add pre-compiled ICU library files 2021-07-01 18:44:59 +02:00
Patrick Goldinger
09567234cd Add ICU4C library
Frickin finally, took me waaaay to long to add.
If there just was an NDK ICU library wrapper, this could have saved
me 4 days of work...
2021-07-01 17:19:41 +02:00
Patrick Goldinger
1c2179fc50 Fix UI theme/state bugs in the spelling activity 2021-06-25 19:03:08 +02:00
Patrick Goldinger
c7fff5d9e4 Add support for Firefox XPI dictionaries 2021-06-25 18:19:55 +02:00
Patrick Goldinger
25badd6c2e Add dictionary delete option 2021-06-25 17:08:00 +02:00
Patrick Goldinger
97fb7b9427 Fix some bugs in the JNI side of spellchecking 2021-06-25 16:24:54 +02:00
Patrick Goldinger
f9b1aba27d Add spelling extension / Fix spelling service 2021-06-25 03:42:34 +02:00
Patrick Goldinger
aa0b9acabc Add JNI/Java direct string passing implementation 2021-06-25 03:41:54 +02:00
Patrick Goldinger
67b3ae5170 Add JNI logging utils 2021-06-25 03:41:04 +02:00
Patrick Goldinger
7d796ebdb3 Add spelling dictionary indexer 2021-06-24 18:16:38 +02:00
Patrick Goldinger
5737e68b8f Make FlorisApplication crash loop safe 2021-06-24 18:15:49 +02:00
Patrick Goldinger
211019b78b Add FlorisRef unit test 2021-06-24 16:53:55 +02:00
Patrick Goldinger
1db6676c45 Add extension load/write support, improve FlorisRef 2021-06-22 19:31:51 +02:00
Patrick Goldinger
da7ae028bf Add interface for extension packages 2021-06-18 03:40:47 +02:00
Patrick Goldinger
f3aa739e72 Introduce new 'FlorisRef' class as replacement for 'AssetRef' 2021-06-17 16:40:06 +02:00
Patrick Goldinger
7f09d1a1d1 Rename package 'extension' to 'res' and move out of 'ime' 2021-06-17 14:52:27 +02:00
Patrick Goldinger
5a8483e78d Add import dictionary basic implementation 2021-06-16 21:32:49 +02:00
Jeremiah Miller
841d15056d Rename DGHP to Sangaline 2021-06-14 08:32:02 -07:00
Patrick Goldinger
09cdd0fff0 Add basic import dictionary UI sheet 2021-06-12 19:52:48 +02:00
Patrick Goldinger
ebb677d203 Add overview page for spell checker prefs 2021-06-12 12:28:21 +02:00
Patrick Goldinger
cf3236f57f Remove preferences cache
SharedPreferences already caches the preference values + the cache causes
state bugs.
2021-06-11 16:27:31 +02:00
bbgun7
3bd8169600 Add keyboard layouts optimized for swipe input 2021-06-10 10:11:56 -07:00
Patrick Goldinger
f9aaec6020 Add initial spelling manager activity 2021-06-10 16:29:44 +02:00
Patrick Goldinger
bb2cc995d6 Add JNI string utils 2021-06-10 16:29:11 +02:00
Patrick Goldinger
a65aaa5f95 Add spelling manager / config 2021-06-10 16:28:50 +02:00
Patrick Goldinger
92b9a978dc Add skeleton code for spell-checker implementation 2021-06-08 04:22:47 +02:00
Patrick Goldinger
5f2729e065 Add Hunspell ported code 2021-06-08 04:22:02 +02:00
Patrick Goldinger
37bb4cea43 Release v0.3.13-beta04 2021-06-08 00:05:36 +02:00
Patrick Goldinger
79d608feea Update translations from Crowdin 2021-06-07 23:53:37 +02:00
Patrick Goldinger
54573de3e3 Merge pull request #1006 from Luensche/move_clipboard_item_to_begin
Move new clipboard items with the same content to the beginning
2021-06-07 23:51:35 +02:00
x-yl
a2243b8825 Use coroutines and improve image loading for clipboard
Sorry, didn't know we were using coroutines when I first wrote this
code!
2021-06-07 17:50:21 +04:00
Björn Engel
2fba2d3b4a Do not compare images 2021-06-07 15:49:05 +02:00
Björn Engel
fd0cbbdcb1 Move new clipboard items with the same content to the beginning, closes #991 2021-06-07 14:08:39 +02:00
Patrick Goldinger
b6e3deedf4 Add default system subtype for proper display in system settings 2021-06-04 19:39:02 +02:00
Patrick Goldinger
4c74bf1b4a Fix glide typing not working for caps/caps-lock 2021-06-04 19:19:53 +02:00
Patrick Goldinger
2a4e3c8c58 Merge pull request #982 from dessalines/halmak
Add the Halmak keyboard layout
2021-06-04 18:56:48 +02:00
Patrick Goldinger
e34e5b4260 Merge pull request #992 from florisboard/rework-textkeyboard-rendering
Rework TextKeyboard rendering
2021-06-04 18:53:42 +02:00
Patrick Goldinger
ae2df7dfe4 Fix Smartbar incorrectly not updating selection-specific keys 2021-06-04 18:49:48 +02:00
Patrick Goldinger
1b3d0a5cf2 Fix touch logic incorrect pointer and capacity issues 2021-06-04 18:31:05 +02:00
Patrick Goldinger
4c94329071 Fix glide typing not correctly initialized at startup 2021-06-04 17:13:16 +02:00
Patrick Goldinger
6ffcf2f865 Fix keyboard preview in Settings 2021-06-04 05:43:42 +02:00
Patrick Goldinger
e2c9a66880 Fix further state bugs 2021-06-04 05:12:38 +02:00
Patrick Goldinger
e9bc25ebc7 Improve extended popup rendering performance 2021-06-04 03:53:03 +02:00
Patrick Goldinger
6379e63669 Rework TextKeyboard rendering 2021-06-04 03:31:46 +02:00
Patrick Goldinger
70a0763e7f Merge pull request #981 from florisboard/fix-keyboard-state-bug
Fix keyboard state bug for the active mode
2021-06-04 03:29:47 +02:00
Dessalines
863080e6ce Remove slash from bottom row. 2021-06-03 14:46:55 -04:00
Patrick Goldinger
3ef454b8bd Fix Smartbar not showing sometimes (#987) 2021-06-03 17:43:23 +02:00
Patrick Goldinger
2bbdfc71d0 Rework UI initialization and reduce duplicate state changes 2021-06-03 15:42:28 +02:00
Patrick Goldinger
d1c783dde1 Fix keyboard state bug for the active mode 2021-06-02 17:51:18 +02:00
Dessalines
644da67601 Add the Halmak keyboard layout 2021-06-01 21:39:26 -04:00
Patrick Goldinger
b8d99efd29 Merge pull request #977 from GoRaN909/patch-5
Update kurdish.json
2021-06-01 01:15:58 +02:00
GoRaN
4067d92a44 Update kurdish.json
Added stretched button (Kashida) to support all Kurdish layouts.
2021-06-01 01:06:00 +03:00
Patrick Goldinger
13a17f3a6b Merge pull request #974 from GoRaN909/patch-2
Update ckb.json
2021-05-31 23:50:01 +02:00
Patrick Goldinger
57c679e500 Merge pull request #975 from GoRaN909/patch-3
Update kurdish_standard.json
2021-05-31 23:41:42 +02:00
Patrick Goldinger
f70f45dab6 Merge pull request #973 from GoRaN909/patch-1
Update kurdish.json
2021-05-31 23:37:41 +02:00
GoRaN
8d8f723d66 Update kurdish_standard.json
popup characters added
2021-06-01 00:29:23 +03:00
GoRaN
7c3c6a7ad7 Update ckb.json
Added popup characters for letter (ح)
2021-06-01 00:24:18 +03:00
GoRaN
d7a1c9377a Update kurdish.json
Some changes of words position and corrections codes
2021-06-01 00:19:19 +03:00
340 changed files with 26045 additions and 9724 deletions

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/florisboard/florisboard/discussions/new?category=q-a
about: Ask here if you have a question about FlorisBoard or need assistance
- name: General feedback
url: https://github.com/florisboard/florisboard/blob/master/CONTRIBUTING.md
url: https://github.com/florisboard/florisboard/discussions/new?category=feedback
about: Give general feedback about this project

View File

@@ -8,12 +8,11 @@ assignees: ''
---
<!--
- Describe your idea in a short but concise way.
- If you have multiple ideas which are not directly connected to each
other, file an issue per idea. This makes it easy to implement one
feature proposal at a time.
- If you have any examples, e.g. screenshots or other keyboards which
have the proposed feature implemented, link them here.
- Please search existing proposals to avoid creating duplicates.
- Thank you for your help in making FlorisBoard better!
Thank you for your help in making FlorisBoard better!
Guide to a good feature-request:
• Please search existing proposals to avoid creating duplicates.
If you have multiple ideas which are not directly connected to other, file a new issue for each idea. This makes it easier to implement your proposals.
• Describe your idea in a short but concise way.
• If you have any examples, e.g. screenshots or other keyboards have the proposed feature implemented, feel free to post them after your description.
-->

View File

@@ -1,16 +0,0 @@
---
name: Question
about: Ask here if you have a question about FlorisBoard
title: ''
labels: question
assignees: ''
---
<!--
- If you need assistance in using FlorisBoard, ask it here!
- If you want to suggest an idea for this project, please use the
Feature request template instead.
- Please search existing questions to avoid creating duplicates.
- Thank you for your help in making FlorisBoard better!
-->

View File

@@ -11,11 +11,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
- uses: actions/checkout@v2
- name: Checkout submodules
run: git submodule update --init --recursive
- name: set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 11
- name: Setup CMake and Ninja
uses: lukka/get-cmake@v3.20.1
- uses: actions/cache@v2
@@ -27,7 +29,9 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle
run: ./gradlew clean assembleDebug
# MUST call gradlew separately because of an OSS license plugin issue.
# See https://github.com/google/play-services-plugins/issues/199
run: ./gradlew clean && ./gradlew assembleDebug
- uses: actions/upload-artifact@v2
with:
name: app-debug.apk

3
.gitmodules vendored Normal file
View File

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

View File

@@ -7,8 +7,13 @@ provides some general guidelines for each type of contribution.
## Giving general feedback
Either use the review function within Google Play or email me at
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I
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.
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
@@ -41,7 +46,7 @@ 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`)
### 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
@@ -66,7 +71,7 @@ pre-configured language.
Since v0.3.10-beta05 it is possible to add custom layouts for all types.
To add a new layout, head to `app/src/main/assets/ime/text` and then select
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.
@@ -74,14 +79,14 @@ 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).
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 `assets/ime/text/characters/extended_popups/<languageTag_name_here>.json`.
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

224
README.md
View File

@@ -7,179 +7,68 @@ src="fastlane/metadata/android/en-US/images/icon.png" alt="App icon">
devices. It aims at being modern, user-friendly and customizable while
fully respecting your privacy. Currently in early-beta state.
### Stable [![Latest stable release](https://img.shields.io/github/v/release/florisboard/florisboard)](https://github.com/florisboard/florisboard/releases/latest)
<table>
<tr>
<th align="center" width="50%">
<h3>Stable <a href="https://github.com/florisboard/florisboard/releases/latest"><img alt="Latest stable release" src="https://img.shields.io/github/v/release/florisboard/florisboard"></a></h3>
</th>
<th align="center" width="50%">
<h3>Beta <a href="https://github.com/florisboard/florisboard/releases"><img alt="Latest beta release" src="https://img.shields.io/github/v/release/florisboard/florisboard?include_prereleases"></a></h3>
</th>
</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>
</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>
</td>
</tr>
<tr>
<td valign="top">
<p><a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a></p>
<p>
Releases on this track are in general stable and ready for everyday use, except for features marked as experimental. Use one of the following options to receive FlorisBoard's stable releases:
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard))
_A. Get it on F-Droid_:
</p>
<p>
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge">](https://f-droid.org/packages/dev.patrickgold.florisboard)
**Manual**: Download and install the APK from the release page.
_B. Google Play Public Alpha Test_:
</p>
</td>
<td valign="top">
<p><a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard.beta"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a></p>
<p>
You can join the public alpha test programme on Google Play. To become a
tester, follow these steps:
1. Join the
[FlorisBoard Public Alpha Test](https://groups.google.com/g/florisboard-public-alpha-test)
Google Group to be able to access the testing programme.
2. Go to the
[FlorisBoard Testing Page](https://play.google.com/apps/testing/dev.patrickgold.florisboard),
then click "Become a tester". Now you are enrolled in the testing
programme.
3. To try out FlorisBoard, download it via Google Play. To do so, click
on "Download it on Google Play", which takes you to the [PlayStore
listing](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard).
4. Finished! You will receive future versions of FlorisBoard via Google
Play.
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [beta testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
With the v0.4.0 release FlorisBoard will enter the public beta in GPlay, allowing to directly search
for and download FlorisBoard without prior joining the alpha group.
</p>
<p>
_C. Use the APK provided in the release section of this repo_
**Manual**: Download and install the APK from the release page.
### Beta [![Latest beta release](https://img.shields.io/github/v/release/florisboard/florisboard?include_prereleases)](https://github.com/florisboard/florisboard/releases)
</p>
</td>
</tr>
</table>
Releases on this track are also in general stable and should be ready for everyday use, though crashes and bugs are more likely to occur. Use releases from this track if you want to get new features faster and give feedback for brand-new stuff. Options to get beta releases:
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme and enter the public beta on Google Play.
_A. IzzySoft's repo for F-Droid_:
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge">](https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard.beta)
_B. Use the APK provided in the release section of this repo_
### Giving feedback
If you want to give feedback to FlorisBoard, there are several ways to
do so, as listed [here](CONTRIBUTING.md#giving-general-feedback).
---
<img align="right" height="256"
src="https://patrickgold.dev/media/previews/florisboard-preview-day.png"
alt="Preview image">
## Implemented features
This list contains all implemented and fully functional features
FlorisBoard currently has to offer. For planned features and its
milestones, please refer to the [Feature roadmap](#feature-roadmap).
### Basics
* [x] Implementation of the keyboard core (InputMethodService)
* [x] Custom implementation of deprecated KeyboardView (base only)
* [x] Caps + Caps Lock
* [x] Key popups
* [x] Extended key popups (e.g. a -> á, à, ä, ...)
* [x] Key press sound/vibration
* [x] Portrait orientation support
* [x] Landscape orientation support (needs tweaks)
### Layouts
* [x] Latin character layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish, Norwegian, Swedish/Finnish, Icelandic, Danish,
Hungarian, Croatian, Polish, Romanian, Colemak, Dvorak, Turkish-Q, Turkish-F, and more...)
* [x] Non-latin character layouts (Arabic, Persian, Kurdish, Greek, Russian (JCUKEN), and more...)
* [x] Adapt to situation in app (password, url, text, etc. )
* [x] Special character layout(s)
* [x] Numeric layout
* [x] Numeric layout (advanced)
* [x] Phone number layout
* [x] Emoji layout
* [x] Emoticon layout
### Preferences
* [x] Setup wizard
* [x] Preferences screen
* [x] Customize look and behaviour of keyboard
* [x] Theme presets (currently only day/night theme + borderless)
* [x] Theme customization
* [x] Subtype selection (language/layout)
* [x] Keyboard behaviour preferences
* [x] Gesture preferences
* [x] User dictionary manager (system and internal)
### Other useful features
* [x] Support for Android 11+ inline autofill API
* [x] One-handed mode
* [x] Clipboard/cursor tools
* [x] Clipboard manager/history
* [x] Integrated number row / symbols in character layouts
* [x] Gesture support
* [x] Full support for the system user dictionary (shared dictionary
between all keyboards) and a private, internal user dictionary
* [x] Full integration in IME service list of Android (xml/method)
(integration is internal-only, because Android's default subtype
implementation not really allows for dynamic language/layout
pairs, only compile-time defined ones)
* [ ] Description and settings reference in System Language & Input
* [ ] (dev only) Generate well-structured documentation of code
* [ ] ...
## Feature roadmap
This section describes the features which are planned to be implemented
in FlorisBoard for the next major versions, modularized into sections.
Please note that the milestone due dates are only raw estimates and will
most likely be delayed back, even though I'm eager to stick to these as
close as possible.
### [v0.4.0](https://github.com/florisboard/florisboard/milestone/4)
- Module A: Smartbar rework (Implemented with [#91])
- Ability to enable/disable Smartbar (features below thus only work if
Smartbar is enabled)
- Dynamic switching between clipboard tools and word suggestions
- Ability to show both the number row and word suggestions at once
- Better icons in quick actions
- Complete rework of the Smartbar code base and the Smartbar layout
definition in XML
- Module B: Composing suggestions (Phase 1: [#329])
- Auto-suggestion of words based of precompiled dictionaries
- Management of custom dictionary entries
- Next-word suggestions by training language models. Data collected here is stored locally and never leaves
the user's device.
- Module C: Extension packs (Implemented with [#162], reworked several times and still not stable)
- Ability to load dictionaries (and later potentially other cool
features too) only if needed to keep the core APK size small
- Currently unclear how exactly this will work, but this is definitely
a must-have feature
- A full implementation may come only in v0.5.0
- Module D: Glide typing (Implemented with [#544])
- Swiping over the characters will automatically convert this to a word
- Possibly also add improvements based on the Flow keyboard
- Module E: Theme rework (Implemented with [#162])
- Themes are now based on the Asset schema
- Dynamic theme creation
- Different theme modes (`Always day`, `Always night`, `Follow system`
and `Follow time`)
- Define a separate theme both for day and night theme
- Adapt to app theme if possible
- Theme import/export
### [v0.5.0](https://github.com/florisboard/florisboard/milestone/5)
There's no exact roadmap yet, but these are the most important points:
- Full layout customization in runtime
- Extensive rework and customization of the media input (emojis, emoticons, kaomoji)
- Better Smartbar customization
- As an extension GIF support
### > v0.5.0
This is completely open as of now and will gather planned features as time
passes...
Backlog (currently not assigned to any milestone):
- Floating keyboard
[#91]: https://github.com/florisboard/florisboard/pull/91
[#162]: https://github.com/florisboard/florisboard/pull/162
[#329]: https://github.com/florisboard/florisboard/pull/329
[#544]: https://github.com/florisboard/florisboard/pull/544
## 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)
## Contributing
Wanna contribute to FlorisBoard? That's great to hear! There are lots of
different ways to help out. Bug reporting, making pull requests,
translating FlorisBoard to make it more accessible, etc. For more
information see the ![contributing guidelines](CONTRIBUTING.md). Thank
information see the [contributing guidelines](CONTRIBUTING.md). Thank
you for your help!
## List of permissions FlorisBoard requests
@@ -199,21 +88,10 @@ to get more information on this topic.
[JakeWharton](https://github.com/JakeWharton)
* [expandable-fab](https://github.com/nambicompany/expandable-fab) by
[Nambi](https://github.com/nambicompany)
## Usage notes for included binary dictionary files
All binary dictionaries included within this project in
(this)[app/src/main/assets/ime/dict] asset folder are built from various
sources, as stated below.
### Source 1: [wordfreq library by LuminosoInsight](https://github.com/LuminosoInsight/wordfreq):
`wordfreq` is a repository which provides both a Python library and raw
data (the wordlists). Only the data has been extracted in order to build
binary dictionary files from it. `wordfreq`'s data is licensed under the
Creative Commons Attribution-ShareAlike 4.0 license
(https://creativecommons.org/licenses/by-sa/4.0/).
For further information on what wordfreq's data depends on, see
(https://github.com/LuminosoInsight/wordfreq#license).
* [ICU4C](https://github.com/unicode-org/icu) by
[The Unicode Consortium](https://github.com/unicode-org)
* [Nuspell](https://github.com/nuspell/nuspell) by
[Nuspell](https://github.com/nuspell)
## License
```

106
ROADMAP.md Normal file
View File

@@ -0,0 +1,106 @@
# 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.
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 and 0.4.0
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, after the v0.4.0 release a new release/development cycle will be
introduced.
### 0.3.13 (currently in development and soon done)
- Spell checking (mainly completed and relatively well working, Smartbar integration still missing)
- Performance improvements in keyboard rendering
- Audio/haptic feedback rework
- Lots and lots of bug fixing in all areas, really fix some annoying bugs
- New layouts added by contributors
### 0.3.14
- 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
- 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
- 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 in the future (not in this version though) what will use Flex:
- Themes
- Layouts (Characters, symbols, numeric, ...)
- Composers for non-Latin script languages
- Word suggestion dictionaries
- Spell check dictionaries
- User dictionaries
- Other features that require only data and no logic
- Maybe full backup of preferences? Not 100% confirmed though and may be pushed back
### 0.3.15
- 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
- Improvement of the candidate view in Smartbar (for word suggestions)
- Theme rework part I:
- Custom key corner radius
- Custom key border color (not shadow!!)
- Re-work theme internals so they use Flex format
- Community repository on GitHub for theme sharing across users (when Theme Flex format is ready)
### 0.4.0
- Prepare FlorisBoard repository and app store presence for public beta release
on Google Play
- Rework branding images and texts of FlorisBoard for the app stores
- Focus on polishing the app and fixing bugs/crashes
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 -betaXX and -rcXX (release candidate) releases on the beta
track for interested people to follow along the development.
## 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")
- Full Smartbar customization
- Includes internal rework how Smartbar is build and assembled
- Allow for more than one Smartbar / Stackable and Collapsible Smartbars
- Customizable quick actions, clipboard row
## 0.6.0
- 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
- Theme rework part II
- Adaptive themes v2
- Voice-to-text with Mozilla's open-source voice service
- Text translation
- Glide typing better word detection
- Proximity-based key typo detection
- Floating keyboard
- Tablet mode / Optimizations for landscape input
- Stickers/GIFs
- FlorisBoard landing web page for presentation
- Implementing additional layouts
- Support for Tasker/Automate/MacroDroid plugins
- Support for WearOS/Smartwatches
- Handwriting
- ...

View File

@@ -1,14 +1,15 @@
plugins {
id("com.android.application") version "4.2.1"
kotlin("android") version "1.5.0"
kotlin("kapt") version "1.5.0"
kotlin("plugin.serialization") version "1.5.0"
id("com.android.application")
kotlin("android")
kotlin("kapt")
kotlin("plugin.serialization") version "1.5.30"
id("com.google.android.gms.oss-licenses-plugin")
}
android {
compileSdkVersion(30)
buildToolsVersion("30.0.3")
compileSdk = 31
buildToolsVersion = "30.0.3"
ndkVersion = "22.1.7171670"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -17,15 +18,15 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs = listOf("-Xallow-result-return-type", "-Xopt-in=kotlin.RequiresOptIn")
freeCompilerArgs = listOf("-Xallow-result-return-type", "-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.contracts.ExperimentalContracts")
}
defaultConfig {
applicationId = "dev.patrickgold.florisboard"
minSdkVersion(23)
targetSdkVersion(30)
versionCode(46)
versionName("0.3.13")
minSdk = 23
targetSdk = 30
versionCode = 59
versionName = "0.3.14"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -41,14 +42,36 @@ android {
externalNativeBuild {
cmake {
cppFlags("-std=c++17", "-fexceptions", "-frtti")
cFlags("-fvisibility=hidden", "-DU_STATIC_IMPLEMENTATION=1")
cppFlags("-fvisibility=hidden", "-std=c++17", "-fexceptions", "-ffunction-sections", "-fdata-sections", "-DU_DISABLE_RENAMING=1", "-DU_STATIC_IMPLEMENTATION=1")
arguments("-DANDROID_STL=c++_static")
}
}
ndk {
//abiFilters += listOf("x86", "x86_64", "armeabi-v7a", "arm64-v8a")
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
sourceSets {
maybeCreate("main").apply {
assets {
srcDirs("src/main/assets", "src/main/icu4c/prebuilt/assets")
}
jniLibs {
srcDirs("src/main/icu4c/prebuilt/jniLibs")
}
}
}
}
buildFeatures {
viewBinding = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.1.0-alpha04"
}
externalNativeBuild {
@@ -96,34 +119,41 @@ android {
}
}
lintOptions {
lint {
isAbortOnError = false
}
}
dependencies {
implementation("androidx.activity", "activity-ktx", "1.2.1")
implementation("androidx.appcompat", "appcompat", "1.2.0")
implementation("androidx.autofill", "autofill", "1.1.0")
implementation("androidx.core", "core-ktx", "1.3.2")
implementation("androidx.fragment", "fragment-ktx", "1.3.0")
implementation("androidx.preference", "preference-ktx", "1.1.1")
implementation("androidx.constraintlayout", "constraintlayout", "2.0.4")
implementation("androidx.lifecycle", "lifecycle-service", "2.2.0")
implementation("com.google.android", "flexbox", "2.0.1")
implementation("com.google.android.material", "material", "1.3.0")
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-android", "1.4.2")
implementation("org.jetbrains.kotlinx", "kotlinx-serialization-json", "1.1.0")
implementation("com.jaredrummler", "colorpicker", "1.1.0")
implementation("com.jakewharton.timber", "timber", "4.7.1")
implementation("com.nambimobile.widgets", "expandable-fab", "1.0.2")
implementation("androidx.room", "room-runtime", "2.2.6")
kapt("androidx.room", "room-compiler","2.2.6")
implementation("androidx.activity:activity-compose:1.3.1")
implementation("androidx.activity:activity-ktx:1.3.1") // possibly remove after settings rework
implementation("androidx.appcompat:appcompat:1.3.1") // possibly remove after settings rework
implementation("androidx.autofill:autofill:1.1.0")
implementation("androidx.compose.material:material:1.1.0-alpha04")
implementation("androidx.compose.ui:ui:1.1.0-alpha04")
implementation("androidx.compose.ui:ui-tooling-preview:1.1.0-alpha04")
implementation("androidx.constraintlayout:constraintlayout:2.1.0") // possibly remove after settings rework
implementation("androidx.core:core-ktx:1.6.0")
implementation("androidx.fragment:fragment-ktx:1.3.6") // possibly remove after settings rework
implementation("androidx.navigation:navigation-compose:2.4.0-alpha09")
implementation("androidx.preference:preference-ktx:1.1.1") // possibly remove after settings rework
implementation("com.google.accompanist:accompanist-systemuicontroller:0.18.0")
implementation("com.google.android.flexbox:flexbox:3.0.0") // possibly remove after settings rework
implementation("com.google.android.material:material:1.4.0") // possibly remove after settings rework
implementation("com.jaredrummler:colorpicker:1.1.0") // possibly remove after settings rework
implementation("com.nambimobile.widgets:expandable-fab:1.0.2") // possibly remove after settings rework
implementation("dev.patrickgold.jetpref:jetpref-datastore-model:0.1.0-alpha07")
implementation("dev.patrickgold.jetpref:jetpref-ui-compose:0.1.0-alpha07")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
implementation("androidx.room:room-runtime:2.3.0")
kapt("androidx.room:room-compiler:2.3.0")
testImplementation("junit", "junit", "4.13.1")
testImplementation(kotlin("test"))
testImplementation("androidx.test", "core", "1.3.0")
testImplementation("org.mockito", "mockito-inline", "3.7.7")
testImplementation("org.robolectric", "robolectric", "4.5.1")
androidTestImplementation("androidx.test.ext", "junit", "1.1.2")
androidTestImplementation("androidx.test.espresso", "espresso-core", "3.3.0")
}

View File

@@ -33,16 +33,40 @@
<service
android:name="dev.patrickgold.florisboard.FlorisImeService"
android:label="@string/floris_app_name"
android:permission="android.permission.BIND_INPUT_METHOD">
android:permission="android.permission.BIND_INPUT_METHOD"
android:directBootAware="true"
android:exported="true">
<intent-filter>
<action android:name="android.view.InputMethod"/>
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
</service>
<!-- Spellchecker service -->
<service
android:name="dev.patrickgold.florisboard.FlorisSpellCheckerService"
android:label="@string/floris_app_name"
android:permission="android.permission.BIND_TEXT_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.textservice.SpellCheckerService"/>
</intent-filter>
<meta-data android:name="android.view.textservice.scs" android:resource="@xml/spellchecker"/>
</service>
<!-- Main App Activity -->
<activity
android:name="dev.patrickgold.florisboard.app.FlorisAppActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__title"
android:launchMode="singleTask"
android:roundIcon="@mipmap/floris_app_icon_round"
android:windowSoftInputMode="adjustResize"
android:theme="@style/FlorisAppTheme"/>
<!-- Settings Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.SettingsMainActivity"
android:name="dev.patrickgold.florisboard.oldsettings.SettingsMainActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__title"
android:launchMode="singleTask"
@@ -56,7 +80,8 @@
android:label="@string/floris_app_name"
android:launchMode="singleTask"
android:roundIcon="@mipmap/floris_app_icon_round"
android:targetActivity="dev.patrickgold.florisboard.setup.SetupActivity">
android:targetActivity="dev.patrickgold.florisboard.app.FlorisAppActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -65,38 +90,30 @@
<!-- User Dictionary Manager Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.UdmActivity"
android:name="dev.patrickgold.florisboard.oldsettings.UdmActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__title"
android:theme="@style/SettingsTheme"/>
<!-- Theme Selector Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeManagerActivity"
android:name="dev.patrickgold.florisboard.oldsettings.ThemeManagerActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__title"
android:theme="@style/SettingsTheme"/>
<!-- Theme Editor Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeEditorActivity"
android:name="dev.patrickgold.florisboard.oldsettings.ThemeEditorActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__theme_editor__title"
android:theme="@style/SettingsTheme"/>
<!-- About Activity -->
<!-- Spelling Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.AboutActivity"
android:name="dev.patrickgold.florisboard.oldsettings.spelling.SpellingActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/about__title"
android:roundIcon="@mipmap/floris_app_icon_round"
android:theme="@style/SettingsTheme"/>
<!-- Advanced Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.AdvancedActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__advanced__title"
android:label="@string/settings__spelling__title_overview"
android:roundIcon="@mipmap/floris_app_icon_round"
android:theme="@style/SettingsTheme"/>

View File

@@ -2,7 +2,54 @@
"package": "dev.patrickgold.florisboard",
"composers": [
{ "$": "appender" },
{ "$": "hangul-unicode" }
{ "$": "hangul-unicode" },
{ "$": "kana-unicode" },
{ "$": "with-rules",
"name": "basic-telex",
"label": "Basic Telex",
"rules": {
"aw": "ă", "aa": "â", "dd": "đ", "ee": "ê", "oo": "ô", "ow": "ơ", "uw": "ư", "w": "ư",
"uow": "ươ",
"af": "à", "ar": "ả", "ax": "ã", "as": "á", "aj": "ạ",
"ăf": "ằ", "ăr": "ẳ", "ăx": "ẵ", "ăs": "ắ", "ăj": "ặ",
"âf": "ầ", "âr": "ẩ", "âx": "ẫ", "âs": "ấ", "âj": "ậ",
"ef": "è", "er": "ẻ", "ex": "ẽ", "es": "é", "ej": "ẹ",
"êf": "ề", "êr": "ể", "êx": "ễ", "ês": "ế", "êj": "ệ",
"if": "ì", "ir": "ỉ", "ix": "ĩ", "is": "í", "ij": "ị",
"of": "ò", "or": "ỏ", "ox": "õ", "os": "ó", "oj": "ọ",
"ôf": "ồ", "ôr": "ổ", "ôx": "ỗ", "ôs": "ố", "ôj": "ộ",
"ơf": "ờ", "ơr": "ở", "ơx": "ỡ", "ơs": "ớ", "ơj": "ợ",
"uf": "ù", "ur": "ủ", "ux": "ũ", "us": "ú", "uj": "ụ",
"ưf": "ừ", "ưr": "ử", "ưx": "ữ", "ưs": "ứ", "ưj": "ự",
"yf": "ỳ", "yr": "ỷ", "yx": "ỹ", "ys": "ý", "yj": "ỵ",
"ăw": "aw", "âa": "aa", "đd": "dd", "êe": "ee", "ôo": "oo", "ơw": "ow", "ưw": "w",
"ươw": "uow",
"àf": "af", "ảr": "ar", "ãx": "ax", "ás": "as", "ạj": "aj",
"ằf": "ăf", "ẳr": "ăr", "ẵx": "ăx", "ắs": "ăs", "ặj": "ăj",
"ầf": "âf", "ẩr": "âr", "ẫx": "âx", "ấs": "âs", "ậj": "âj",
"èf": "ef", "ẻr": "er", "ẽx": "ex", "és": "es", "ẹj": "ej",
"ềf": "êf", "ểr": "êr", "ễx": "êx", "ếs": "ês", "ệj": "êj",
"ìf": "if", "ỉr": "ir", "ĩx": "ix", "ís": "is", "ịj": "ij",
"òf": "of", "ỏr": "or", "õx": "ox", "ós": "os", "ọj": "oj",
"ồf": "ôf", "ổr": "ôr", "ỗx": "ôx", "ốs": "ôs", "ộj": "ôj",
"ờf": "ơf", "ởr": "ơr", "ỡx": "ơx", "ớs": "ơs", "ợj": "ơj",
"ùf": "uf", "ủr": "ur", "ũx": "ux", "ús": "us", "ụj": "uj",
"ừf": "ưf", "ửr": "ưr", "ữx": "ưx", "ứs": "ưs", "ựj": "ưj",
"ỳf": "yf", "ỷr": "yr", "ỹx": "yx", "ýs": "ys", "ỵj": "yj",
"àz": "a", "ảz": "a", "ãz": "a", "áz": "a", "ạz": "a",
"ằz": "ă", "ẳz": "ă", "ẵz": "ă", "ắz": "ă", "ặz": "ă",
"ầz": "â", "ẩz": "â", "ẫz": "â", "ấz": "â", "ậz": "â",
"èz": "e", "ẻz": "e", "ẽz": "e", "éz": "e", "ẹz": "e",
"ềz": "ê", "ểz": "ê", "ễz": "ê", "ếz": "ê", "ệz": "ê",
"ìz": "i", "ỉz": "i", "ĩz": "i", "íz": "i", "ịz": "i",
"òz": "o", "ỏz": "o", "õz": "o", "óz": "o", "ọz": "o",
"ồz": "ô", "ổz": "ô", "ỗz": "ô", "ốz": "ô", "ộz": "ô",
"ờz": "ơ", "ởz": "ơ", "ỡz": "ơ", "ớz": "ơ", "ợz": "ơ",
"ùz": "u", "ủz": "u", "ũz": "u", "úz": "u", "ụz": "u",
"ừz": "ư", "ửz": "ư", "ữz": "ư", "ứz": "ư", "ựz": "ư",
"ỳz": "y", "ỷz": "y", "ỹz": "y", "ýz": "y", "ỵz": "y"
}
}
],
"currencySets": [
{
@@ -209,6 +256,18 @@
{ "code": 165, "label": "¥" }
]
},
{
"name": "thai_baht",
"label": "Thai baht (฿)",
"slots": [
{ "code": 3647, "label": "฿" },
{ "code": 36, "label": "$" },
{ "code": 8364, "label": "€" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
]
},
{
"name": "turkish_lira",
"label": "Turkish lira (₺)",
@@ -233,6 +292,18 @@
{ "code": 165, "label": "¥" }
]
},
{
"name": "vietnamese_dong",
"label": "Vietnamese đồng (₫)",
"slots": [
{ "code": 8363, "label": "₫" },
{ "code": 36, "label": "$" },
{ "code": 8364, "label": "€" },
{ "code": 162, "label": "¢" },
{ "code": 163, "label": "£" },
{ "code": 165, "label": "¥" }
]
},
{
"name": "yen",
"label": "Yen (¥)",
@@ -682,6 +753,65 @@
"preferred": {
"characters": "korean"
}
},
{
"id": 3000,
"languageTag": "lt-LT",
"composer": "appender",
"currencySet": "euro",
"preferred": {
"characters": "qwerty"
}
},
{
"id": 3100,
"languageTag": "ja-JP-jis",
"composer": "kana-unicode",
"currencySet": "yen",
"preferred": {
"characters": "jis",
"symbols": "cjk",
"symbols2": "cjk",
"numericRow": "cjk"
}
},
{
"id": 3200,
"languageTag": "th-kd",
"composer": "appender",
"currencySet": "thai_baht",
"preferred": {
"characters": "thai_kedmanee",
"numericRow": "thai"
}
},
{
"id": 3300,
"languageTag": "vi-VN",
"composer": "basic-telex",
"currencySet": "vietnamese_dong",
"preferred": {
"characters": "qwerty"
}
},
{
"id": 3400,
"languageTag": "ig-NG",
"composer": "appender",
"currencySet": "nigerian_naira",
"preferred": {
"characters": "igbo"
}
},
{
"id": 3500,
"languageTag": "hoc",
"composer": "appender",
"currencySet": "indian_rupee",
"preferred": {
"characters": "warang_citi",
"numericRow": "warang_citi"
}
}
]
}

View File

@@ -0,0 +1,69 @@
{
"basePath": "ime/spelling",
"importSources": [
{
"id": "mozilla_firefox",
"label": "Mozilla Firefox Add-ons",
"url": "https://addons.mozilla.org/firefox/language-tools/",
"format": {
"$": "archive",
"file": {
"name": "^.+\\.xpi$",
"isRequired": true
}
}
},
{
"id": "libre_office",
"label": "LibreOffice [CURRENTLY UNSUPPORTED]",
"url": "https://extensions.libreoffice.org/?Tags%5B%5D=50",
"format": {
"$": "archive",
"file": {
"name": "^.+\\.oxt$",
"isRequired": true
}
}
},
{
"id": "open_office",
"label": "Apache OpenOffice [CURRENTLY UNSUPPORTED]",
"url": "https://extensions.openoffice.org/en/search?f%5B0%5D=field_project_tags%3A157",
"format": {
"$": "archive",
"file": {
"name": "^.+\\.oxt$",
"isRequired": true
}
}
},
{
"id": "free_office",
"label": "SoftMaker FreeOffice",
"url": "https://www.freeoffice.com/en/download/dictionaries",
"format": {
"$": "archive",
"file": {
"name": "^.+\\.sox$",
"isRequired": true
}
}
},
{
"id": "gh_wooorm",
"label": "GitHub collection by Titus Wormer",
"url": "https://github.com/wooorm/dictionaries",
"format": {
"$": "raw",
"affFile": {
"name": "^.+\\.aff$",
"isRequired": true
},
"dicFile": {
"name": "^.+\\.dic$",
"isRequired": true
}
}
}
]
}

View File

@@ -4,59 +4,21 @@
"authors": [ "HeiWiper" ],
"mapping": {
"all": {
"ض": {
"relevant": [
{ "code": 1633, "label": "١" }
]
},
"ص": {
"relevant": [
{ "code": 1634, "label": "٢" }
]
},
"ث": {
"relevant": [
{ "code": 1635, "label": "٣" }
]
},
"ق": {
"relevant": [
{ "code": 1704, "label": "ڨ" },
{ "code": 1636, "label": "٤" }
{ "code": 1704, "label": "ڨ" }
]
},
"ف": {
"relevant": [
{ "code": 1701, "label": "ڥ" },
{ "code": 1700, "label": "ڤ" },
{ "code": 1698, "label": "ڢ" },
{ "code": 1637, "label": "٥" }
]
},
"غ": {
"relevant": [
{ "code": 1638, "label": "٦" }
]
},
"ع": {
"relevant": [
{ "code": 1639, "label": "٧" }
{ "code": 1698, "label": "ڢ" }
]
},
"ه": {
"relevant": [
{ "code": 1726, "label": "ھ" },
{ "code": 1640, "label": "٨" }
]
},
"خ": {
"relevant": [
{ "code": 1641, "label": "٩" }
]
},
"ح": {
"relevant": [
{ "code": 1632, "label": "٠" }
{ "code": 1726, "label": "ھ" }
]
},
"ج": {
@@ -70,8 +32,8 @@
]
},
"ي": {
"main": { "code": 1574, "label": "ئ" },
"relevant": [
{ "code": 1574, "label": "ئ" },
{ "code": 1609, "label": "ى" }
]
},
@@ -89,10 +51,10 @@
]
},
"ا": {
"main": { "code": 1571, "label": "أ" },
"relevant": [
{ "code": 1570, "label": "آ" },
{ "code": 1569, "label": "ء" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" },
{ "code": 1649, "label": "ٱ" }
]
@@ -104,9 +66,7 @@
]
},
"ى": {
"relevant": [
{ "code": 1574, "label": "ئ" }
]
"main": { "code": 1574, "label": "ئ" }
},
"ز": {
"relevant": [

View File

@@ -79,6 +79,9 @@
{ "$": "auto_text_key", "code": 251, "label": "û" }
]
},
"n": {
"main": { "$": "auto_text_key", "code": 241, "label": "ñ" }
},
"~right": {
"main": { "code": 44, "label": "," },
"relevant": [

View File

@@ -4,28 +4,22 @@
"authors": [ "GoRaN" ],
"mapping": {
"all": {
"": {
"relevant": [
{ "code": 1577, "label": "ة" },
{ "code": 1729, "label": "ـہ" }
]
},
"ر": {
"relevant": [
{ "code": 1685, "label": "ڕ" },
{ "code": 1682, "label": "ڒ" }
]
},
"ی": {
"relevant": [
{ "code": 1746, "label": "ے" },
{ "code": 1610, "label": "ي" },
{ "code": 1744, "label": "ې" },
{ "code": 1741, "label": "ۍ" },
{ "code": 1742, "label": "ێ" },
{ "code": 1744, "label": "ې" },
{ "code": 1610, "label": "ي" },
{ "code": 1597, "label": "ؽ" }
]
},
@@ -34,10 +28,15 @@
"ﺋ": {
"relevant": [
{ "code": 65163, "label": "ﺋ" },
{ "code": 1569, "label": "ء" },
{ "code": 65139, "label": "ﹳ" }
]
},
"ح": {
"relevant": [
{ "code": 65010, "label": "ﷲ" },
{ "code": 65019, "label": "ﷻ" }
]
},
"ع": {
"relevant": [
@@ -56,12 +55,9 @@
]
},
"ف": {
"relevant": [
{ "code": 1701, "label": "ڥ" },
{ "code": 1700, "label": "ڤ" },
{ "code": 1698, "label": "ڢ" },
{ "code": 1697, "label": "ڡ" }
]
@@ -70,7 +66,6 @@
"د": {
"relevant": [
{ "code": 1676, "label": "ڌ" },
{ "code": 1584, "label": "ذ" },
{ "code": 64390, "label": "ﮆ" },
{ "code": 1774, "label": "ۮ" }
]
@@ -93,9 +88,7 @@
},
"ب": {
"relevant": [
{ "code": 65010, "label": "" },
{ "code": 65021, "label": "﷽" },
{ "code": 65019, "label": "ﷻ" }
{ "code": 65021, "label": "" }
]
},
"م": {
@@ -108,7 +101,6 @@
"relevant": [
{ "code": 1718, "label": "ڶ" },
{ "code": 1719, "label": "ڷ" },
{ "code": 1717, "label": "ڵ" },
{ "code": 1720, "label": "ڸ" }
]
},

View File

@@ -0,0 +1,292 @@
{
"type": "characters/extended_popups",
"name": "ja-JP-jis",
"authors": [ "waelwindows" ],
"mapping": {
"all": {
"あ": {
"main": { "code": 12353, "label": "ぁ" }
},
"ア": {
"main": { "code": 12449, "label": "ァ" }
},
"ア": {
"main": { "code": 65383, "label": "ァ" }
},
"い": {
"main": { "code": 12355, "label": "ぃ" },
"relevant": [
{ "code": 12432, "label": "ゐ" },
{ "code": 110928, "label": "𛅐" }
]
},
"イ": {
"main": { "code": 12451, "label": "ィ" },
"relevant": [
{ "code": 12528, "label": "ヰ" },
{ "code": 110948, "label": "𛅤" }
]
},
"イ": {
"main": { "code": 65384, "label": "ィ" }
},
"う": {
"main": { "code": 12357, "label": "ぅ" }
},
"ウ": {
"main": { "code": 12453, "label": "ゥ" }
},
"ウ": {
"main": { "code": 65385, "label": "ゥ" }
},
"え": {
"main": { "code": 12359, "label": "ぇ" },
"relevant": [
{ "code": 12433, "label": "ゑ" },
{ "code": 110929, "label": "𛅑" }
]
},
"エ": {
"main": { "code": 12455, "label": "ェ" },
"relevant": [
{ "code": 12529, "label": "ヱ" },
{ "code": 110949, "label": "𛅥" }
]
},
"エ": {
"main": { "code": 65386, "label": "ェ" }
},
"お": {
"main": { "code": 12361, "label": "ぉ" }
},
"オ": {
"main": { "code": 12457, "label": "ォ" }
},
"オ": {
"main": { "code": 65387, "label": "ォ" }
},
"や": {
"main": { "code": 12419, "label": "ゃ" }
},
"ヤ": {
"main": { "code": 12515, "label": "ャ" }
},
"ヤ": {
"main": { "code": 65388, "label": "ャ" }
},
"ゆ": {
"main": { "code": 12421, "label": "ゅ" }
},
"ユ": {
"main": { "code": 12517, "label": "ュ" }
},
"ユ": {
"main": { "code": 65389, "label": "ュ" }
},
"よ": {
"main": { "code": 12423, "label": "ょ" }
},
"ヨ": {
"main": { "code": 12519, "label": "ョ" }
},
"ヨ": {
"main": { "code": 65390, "label": "ョ" }
},
"わ": {
"main": { "code": 12434, "label": "を" },
"relevant": [
{ "code": 12430, "label": "ゎ" },
{ "code": 110930, "label": "𛅒" }
]
},
"ワ": {
"main": { "code": 12530, "label": "ヲ" },
"relevant": [
{ "code": 12526, "label": "ヮ" },
{ "code": 110950, "label": "𛅦" }
]
},
"ワ": {
"main": { "code": 65382, "label": "ヲ" }
},
"つ": {
"main": { "code": 12387, "label": "っ" }
},
"ツ": {
"main": { "code": 12483, "label": "ッ" }
},
"ツ": {
"main": { "code": 65391, "label": "ッ" }
},
"ト": {
"relevant": [
{ "code": 12787, "label": "ㇳ" }
]
},
"シ": {
"relevant": [
{ "code": 12785, "label": "ㇱ" }
]
},
"ス": {
"main": { "code": 12786, "label": "ㇲ" }
},
"か": {
"main": { "code": 12437, "label": "ゕ" }
},
"カ": {
"main": { "code": 12533, "label": "ヵ" }
},
"ク": {
"main": { "code": 12784, "label": "ㇰ" }
},
"け": {
"main": { "code": 12438, "label": "ゖ" }
},
"ケ": {
"main": { "code": 12534, "label": "ヶ" }
},
"ヌ": {
"relevant": [
{ "code": 12788, "label": "ㇴ" }
]
},
"ハ": {
"relevant": [
{ "code": 12789, "label": "ㇵ" }
]
},
"ヒ": {
"relevant": [
{ "code": 12790, "label": "ㇶ" }
]
},
"フ": {
"relevant": [
{ "code": 12791, "label": "ㇷ" }
]
},
"ヘ": {
"relevant": [
{ "code": 12792, "label": "ㇸ" }
]
},
"ホ": {
"relevant": [
{ "code": 12793, "label": "ㇹ" }
]
},
"ム": {
"relevant": [
{ "code": 12794, "label": "ㇺ" }
]
},
"ラ": {
"relevant": [
{ "code": 12795, "label": "ㇻ" }
]
},
"リ": {
"relevant": [
{ "code": 12796, "label": "ㇼ" }
]
},
"ル": {
"relevant": [
{ "code": 12797, "label": "ㇽ" }
]
},
"レ": {
"relevant": [
{ "code": 12798, "label": "ㇾ" }
]
},
"ロ": {
"relevant": [
{ "code": 12799, "label": "ㇿ" }
]
},
"゛": {
"main": { "code": 12443, "label": "゛" }
},
"゜": {
"main": { "code": 12444, "label": "゜" }
},
"~kana": {
"relevant": [
{ "code": -9711, "label": "あ" },
{ "code": -9712, "label": "ア" },
{ "code": -9713, "label": "ア" }
]
},
"~right": {
"main": { "$": "char_width_selector",
"full": { "code": 12539, "label": "・" },
"half": { "code": 9834, "label": "♪" }
},
"relevant": [
{ "$": "char_width_selector",
"full": { "code": 65286, "label": "" },
"half": { "code": 38, "label": "&" }
},
{ "$": "char_width_selector",
"full": { "code": 65285, "label": "" },
"half": { "code": 37, "label": "%" }
},
{ "$": "char_width_selector",
"full": { "code": 65291, "label": "" },
"half": { "code": 43, "label": "+" }
},
{ "$": "char_width_selector",
"full": { "code": 65340, "label": "" },
"half": { "code": 92, "label": "\\" }
},
{ "$": "char_width_selector",
"full": { "code": 65293, "label": "" },
"half": { "code": 45, "label": "-" }
},
{ "$": "char_width_selector",
"full": { "code": 65306, "label": "" },
"half": { "code": 58, "label": ":" }
},
{ "$": "char_width_selector",
"full": { "code": 65287, "label": "" },
"half": { "code": 39, "label": "'" }
},
{ "$": "char_width_selector",
"full": { "code": 65312, "label": "" },
"half": { "code": 64, "label": "@" }
},
{ "$": "char_width_selector",
"full": { "code": 65307, "label": "" },
"half": { "code": 59, "label": ";" }
},
{ "$": "char_width_selector",
"full": { "code": 65295, "label": "" },
"half": { "code": 47, "label": "/" }
},
{ "$": "char_width_selector",
"full": { "code": 65288, "label": "" },
"half": { "code": 40, "label": "(" }
},
{ "$": "char_width_selector",
"full": { "code": 65289, "label": "" },
"half": { "code": 41, "label": ")" }
},
{ "$": "char_width_selector",
"full": { "code": 65283, "label": "" },
"half": { "code": 35, "label": "#" }
},
{ "$": "char_width_selector",
"full": { "code": 65281, "label": "" },
"half": { "code": 33, "label": "!" }
},
{ "$": "char_width_selector",
"full": { "code": 65311, "label": "" },
"half": { "code": 63, "label": "?" }
}
]
}
}
}
}

View File

@@ -0,0 +1,167 @@
{
"type": "characters/extended_popups",
"name": "lv",
"authors": [ "patrickgold" ],
"mapping": {
"all": {
"a": {
"main": { "$": "auto_text_key", "code": 261, "label": "ą" },
"relevant": [
{ "$": "auto_text_key", "code": 226, "label": "â" },
{ "$": "auto_text_key", "code": 227, "label": "ã" },
{ "$": "auto_text_key", "code": 229, "label": "å" },
{ "$": "auto_text_key", "code": 230, "label": "æ" },
{ "$": "auto_text_key", "code": 228, "label": "ä" },
{ "$": "auto_text_key", "code": 257, "label": "ā" },
{ "$": "auto_text_key", "code": 224, "label": "à" },
{ "$": "auto_text_key", "code": 225, "label": "á" }
]
},
"c": {
"main": { "$": "auto_text_key", "code": 269, "label": "č" },
"relevant": [
{ "$": "auto_text_key", "code": 263, "label": "ć" },
{ "$": "auto_text_key", "code": 231, "label": "ç" }
]
},
"d": {
"main": { "$": "auto_text_key", "code": 271, "label": "ď" }
},
"e": {
"main": { "$": "auto_text_key", "code": 279, "label": "ė" },
"relevant": [
{ "$": "auto_text_key", "code": 235, "label": "ë" },
{ "$": "auto_text_key", "code": 233, "label": "é" },
{ "$": "auto_text_key", "code": 234, "label": "ê" },
{ "$": "auto_text_key", "code": 283, "label": "ě" },
{ "$": "auto_text_key", "code": 275, "label": "ē" },
{ "$": "auto_text_key", "code": 281, "label": "ę" },
{ "$": "auto_text_key", "code": 232, "label": "è" }
]
},
"g": {
"main": { "$": "auto_text_key", "code": 291, "label": "ģ" },
"relevant": [
{ "$": "auto_text_key", "code": 287, "label": "ğ" }
]
},
"i": {
"main": { "$": "auto_text_key", "code": 303, "label": "į" },
"relevant": [
{ "$": "auto_text_key", "code": 238, "label": "î" },
{ "$": "auto_text_key", "code": 239, "label": "ï" },
{ "$": "auto_text_key", "code": 236, "label": "ì" },
{ "$": "auto_text_key", "code": 299, "label": "ī" },
{ "$": "auto_text_key", "code": 237, "label": "í" }
]
},
"k": {
"main": { "$": "auto_text_key", "code": 311, "label": "ķ" }
},
"l": {
"main": { "$": "auto_text_key", "code": 316, "label": "ļ" },
"relevant": [
{ "$": "auto_text_key", "code": 318, "label": "ľ" },
{ "$": "auto_text_key", "code": 314, "label": "ĺ" },
{ "$": "auto_text_key", "code": 322, "label": "ł" }
]
},
"n": {
"main": { "$": "auto_text_key", "code": 326, "label": "ņ" },
"relevant": [
{ "$": "auto_text_key", "code": 324, "label": "ń" },
{ "$": "auto_text_key", "code": 241, "label": "ñ" }
]
},
"o": {
"main": { "$": "auto_text_key", "code": 246, "label": "ö" },
"relevant": [
{ "$": "auto_text_key", "code": 248, "label": "ø" },
{ "$": "auto_text_key", "code": 337, "label": "ő" },
{ "$": "auto_text_key", "code": 244, "label": "ô" },
{ "$": "auto_text_key", "code": 339, "label": "œ" },
{ "$": "auto_text_key", "code": 243, "label": "ó" },
{ "$": "auto_text_key", "code": 242, "label": "ò" },
{ "$": "auto_text_key", "code": 245, "label": "õ" }
]
},
"r": {
"main": { "$": "auto_text_key", "code": 343, "label": "ŗ" },
"relevant": [
{ "$": "auto_text_key", "code": 341, "label": "ŕ" },
{ "$": "auto_text_key", "code": 345, "label": "ř" }
]
},
"s": {
"main": { "$": "auto_text_key", "code": 353, "label": "š" },
"relevant": [
{ "$": "auto_text_key", "code": 347, "label": "ś" },
{ "$": "auto_text_key", "code": 223, "label": "ß" },
{ "$": "auto_text_key", "code": 351, "label": "ş" }
]
},
"t": {
"main": { "$": "auto_text_key", "code": 355, "label": "ţ" },
"relevant": [
{ "$": "auto_text_key", "code": 357, "label": "ť" }
]
},
"u": {
"main": { "$": "auto_text_key", "code": 363, "label": "ū" },
"relevant": [
{ "$": "auto_text_key", "code": 367, "label": "ů" },
{ "$": "auto_text_key", "code": 250, "label": "ú" },
{ "$": "auto_text_key", "code": 251, "label": "û" },
{ "$": "auto_text_key", "code": 369, "label": "ű" },
{ "$": "auto_text_key", "code": 249, "label": "ù" },
{ "$": "auto_text_key", "code": 252, "label": "ü" },
{ "$": "auto_text_key", "code": 371, "label": "ų" }
]
},
"y": {
"main": { "$": "auto_text_key", "code": 253, "label": "ý" },
"relevant": [
{ "$": "auto_text_key", "code": 255, "label": "ÿ" }
]
},
"z": {
"main": { "$": "auto_text_key", "code": 382, "label": "ž" },
"relevant": [
{ "$": "auto_text_key", "code": 378, "label": "ź" },
{ "$": "auto_text_key", "code": 380, "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": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".lv" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

@@ -1,99 +1,35 @@
{
"type": "characters/extended_popups",
"name": "tr",
"authors": [ "kisekinopureya", "patrickgold" ],
"authors": [ "kisekinopureya", "patrickgold", "dvrnynr" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "$": "auto_text_key", "code": 226, "label": "â" },
{ "$": "auto_text_key", "code": 228, "label": "ä" },
{ "$": "auto_text_key", "code": 225, "label": "á" }
]
},
"c": {
"main": { "$": "auto_text_key", "code": 231, "label": "ç" },
"relevant": [
{ "$": "auto_text_key", "code": 269, "label": "č" },
{ "$": "auto_text_key", "code": 263, "label": "ć" }
]
},
"e": {
"relevant": [
{ "$": "auto_text_key", "code": 233, "label": "é" },
{ "$": "auto_text_key", "code": 601, "label": "ə" },
{ "$": "auto_text_key", "code": 234, "label": "ê" }
]
"main": { "$": "auto_text_key", "code": 231, "label": "ç" }
},
"g": {
"main": { "$": "auto_text_key", "code": 287, "label": "ğ" }
},
"i": {
"main": { "$": "auto_text_key", "code": 305, "label": "ı" },
"relevant": [
{ "$": "auto_text_key", "code": 303, "label": "į" },
{ "$": "auto_text_key", "code": 236, "label": "ì" },
{ "$": "auto_text_key", "code": 237, "label": "í" },
{ "$": "auto_text_key", "code": 299, "label": "ī" },
{ "$": "auto_text_key", "code": 238, "label": "î" },
{ "$": "auto_text_key", "code": 239, "label": "ï" }
]
"main": { "$": "case_selector",
"lower": { "code": 305, "label": "ı" },
"upper": { "code": 73, "label": "I" }
}
},
"ı": {
"main": { "$": "auto_text_key", "code": 105, "label": "i" },
"relevant": [
{ "$": "auto_text_key", "code": 303, "label": "į" },
{ "$": "auto_text_key", "code": 236, "label": "ì" },
{ "$": "auto_text_key", "code": 237, "label": "í" },
{ "$": "auto_text_key", "code": 299, "label": "ī" },
{ "$": "auto_text_key", "code": 238, "label": "î" },
{ "$": "auto_text_key", "code": 239, "label": "ï" }
]
},
"n": {
"relevant": [
{ "$": "auto_text_key", "code": 328, "label": "ň" },
{ "$": "auto_text_key", "code": 241, "label": "ñ" }
]
"main": { "$": "case_selector",
"lower": { "code": 105, "label": "i" },
"upper": { "code": 304, "label": "İ" }
}
},
"o": {
"main": { "$": "auto_text_key", "code": 246, "label": "ö" },
"relevant": [
{ "$": "auto_text_key", "code": 333, "label": "ō" },
{ "$": "auto_text_key", "code": 248, "label": "ø" },
{ "$": "auto_text_key", "code": 243, "label": "ó" },
{ "$": "auto_text_key", "code": 245, "label": "õ" },
{ "$": "auto_text_key", "code": 242, "label": "ò" },
{ "$": "auto_text_key", "code": 339, "label": "œ" },
{ "$": "auto_text_key", "code": 244, "label": "ô" }
]
"main": { "$": "auto_text_key", "code": 246, "label": "ö" }
},
"s": {
"main": { "$": "auto_text_key", "code": 351, "label": "ş" },
"relevant": [
{ "$": "auto_text_key", "code": 347, "label": "ś" },
{ "$": "auto_text_key", "code": 223, "label": "ß" },
{ "$": "auto_text_key", "code": 353, "label": "š" }
]
"main": { "$": "auto_text_key", "code": 351, "label": "ş" }
},
"u": {
"main": { "$": "auto_text_key", "code": 252, "label": "ü" },
"relevant": [
{ "$": "auto_text_key", "code": 363, "label": "ū" },
{ "$": "auto_text_key", "code": 249, "label": "ù" },
{ "$": "auto_text_key", "code": 250, "label": "ú" },
{ "$": "auto_text_key", "code": 251, "label": "û" }
]
},
"y": {
"relevant": [
{ "$": "auto_text_key", "code": 253, "label": "ý" }
]
},
"z": {
"relevant": [
{ "$": "auto_text_key", "code": 382, "label": "ž" }
]
"main": { "$": "auto_text_key", "code": 252, "label": "ü" }
},
"~right": {
"main": { "code": 44, "label": "," },
@@ -120,10 +56,10 @@
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".gov.tr" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".tr" },
{ "code": -255, "label": ".edu.tr" },
{ "code": -255, "label": ".com.tr" },
{ "code": -255, "label": ".net" }
]
}

View File

@@ -0,0 +1,67 @@
{
"type": "characters/extended_popups",
"name": "vi-VN",
"authors": [ "patrickgold", "Hayleia" ],
"mapping": {
"all": {
"a": {
"relevant": [
{ "$": "auto_text_key", "code": 226, "label": "â" },
{ "$": "auto_text_key", "code": 259, "label": "ă" }
]
},
"e": {
"relevant": [
{ "$": "auto_text_key", "code": 234, "label": "ê" }
]
},
"o": {
"relevant": [
{ "$": "auto_text_key", "code": 244, "label": "ô" },
{ "$": "auto_text_key", "code": 417, "label": "ơ" }
]
},
"u": {
"relevant": [
{ "$": "auto_text_key", "code": 432, "label": "ư" }
]
},
"d": {
"relevant": [
{ "$": "auto_text_key", "code": 273, "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": "/" },
{ "code": 40, "label": "(" },
{ "code": 41, "label": ")" },
{ "code": 35, "label": "#" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" }
]
}
}
}
}

View File

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

View File

@@ -0,0 +1,55 @@
{
"type": "characters",
"name": "igbo",
"label": "Igbo",
"authors": [ "nd500" ],
"direction": "ltr",
"arrangement": [
[
{ "$": "case_selector",
"lower": { "code": 7909, "label": "ụ" },
"upper": { "code": 7908, "label": "Ụ" }
},
{ "$": "auto_text_key", "code": 119, "label": "w" },
{ "$": "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" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 112, "label": "p" }
],
[
{ "$": "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": 7883, "label": "ị" },
"upper": { "code": 7882, "label": "Ị" }
}
],
[
{ "$": "case_selector",
"lower": { "code": 7749, "label": "ṅ" },
"upper": { "code": 7748, "label": "Ṅ" }
},
{ "$": "case_selector",
"lower": { "code": 7885, "label": "ọ" },
"upper": { "code": 7884, "label": "Ọ" }
},
{ "$": "auto_text_key", "code": 122, "label": "z" },
{ "$": "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" }
]
]
}

View File

@@ -0,0 +1,354 @@
{
"type": "characters",
"name": "jis",
"label": "JIS",
"authors": [ "waelwindows" ],
"direction": "ltr",
"modifier": "jis",
"arrangement": [
[
{ "$": "kana_selector",
"hira": { "code": 12396, "label": "ぬ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12492, "label": "ヌ" },
"half": { "code": 65415, "label": "ヌ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12405, "label": "ふ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12501, "label": "フ" },
"half": { "code": 65420, "label": "フ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12354, "label": "あ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12450, "label": "ア" },
"half": { "code": 65393, "label": "ア" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12358, "label": "う" },
"kata": { "$": "char_width_selector",
"full": { "code": 12454, "label": "ウ" },
"half": { "code": 65395, "label": "ウ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12360, "label": "え" },
"kata": { "$": "char_width_selector",
"full": { "code": 12456, "label": "エ" },
"half": { "code": 65396, "label": "エ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12362, "label": "お" },
"kata": { "$": "char_width_selector",
"full": { "code": 12458, "label": "オ" },
"half": { "code": 65397, "label": "オ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12420, "label": "や" },
"kata": { "$": "char_width_selector",
"full": { "code": 12516, "label": "ヤ" },
"half": { "code": 65428, "label": "ヤ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12422, "label": "ゆ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12518, "label": "ユ" },
"half": { "code": 65429, "label": "ユ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12424, "label": "よ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12520, "label": "ヨ" },
"half": { "code": 65430, "label": "ヨ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12431, "label": "わ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12527, "label": "ワ" },
"half": { "code": 65436, "label": "ワ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12411, "label": "ほ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12507, "label": "ホ" },
"half": { "code": 65422, "label": "ホ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12408, "label": "へ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12504, "label": "ヘ" },
"half": { "code": 65421, "label": "ヘ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12540, "label": "ー" },
"kata": { "$": "char_width_selector",
"full": { "code": 12540, "label": "ー" },
"half": { "code": 65392, "label": "ー" }
}
}
],
[
{ "$": "kana_selector",
"hira": { "code": 12383, "label": "た" },
"kata": { "$": "char_width_selector",
"full": { "code": 12479, "label": "タ" },
"half": { "code": 65408, "label": "タ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12390, "label": "て" },
"kata": { "$": "char_width_selector",
"full": { "code": 12486, "label": "テ" },
"half": { "code": 65411, "label": "テ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12356, "label": "い" },
"kata": { "$": "char_width_selector",
"full": { "code": 12452, "label": "イ" },
"half": { "code": 65394, "label": "イ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12377, "label": "す" },
"kata": { "$": "char_width_selector",
"full": { "code": 12473, "label": "ス" },
"half": { "code": 65405, "label": "ス" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12363, "label": "か" },
"kata": { "$": "char_width_selector",
"full": { "code": 12459, "label": "カ" },
"half": { "code": 65398, "label": "カ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12435, "label": "ん" },
"kata": { "$": "char_width_selector",
"full": { "code": 12531, "label": "ン" },
"half": { "code": 65437, "label": "ン" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12394, "label": "な" },
"kata": { "$": "char_width_selector",
"full": { "code": 12490, "label": "ナ" },
"half": { "code": 65413, "label": "ナ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12395, "label": "に" },
"kata": { "$": "char_width_selector",
"full": { "code": 12491, "label": "ニ" },
"half": { "code": 65414, "label": "ニ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12425, "label": "ら" },
"kata": { "$": "char_width_selector",
"full": { "code": 12521, "label": "ラ" },
"half": { "code": 65431, "label": "ラ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12379, "label": "せ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12475, "label": "セ" },
"half": { "code": 65406, "label": "セ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12441, "label": "゛" },
"kata": { "$": "char_width_selector",
"full": { "code": 12441, "label": "゛" },
"half": { "code": 65438, "label": "゙" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12442, "label": "゜" },
"kata": { "$": "char_width_selector",
"full": { "code": 12442, "label": "゜" },
"half": { "code": 65439, "label": "゚" }
}
}
],
[
{ "$": "kana_selector",
"hira": { "code": 12385, "label": "ち" },
"kata": { "$": "char_width_selector",
"full": { "code": 12481, "label": "チ" },
"half": { "code": 65409, "label": "チ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12392, "label": "と" },
"kata": { "$": "char_width_selector",
"full": { "code": 12488, "label": "ト" },
"half": { "code": 65412, "label": "ト" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12375, "label": "し" },
"kata": { "$": "char_width_selector",
"full": { "code": 12471, "label": "シ" },
"half": { "code": 65410, "label": "シ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12399, "label": "は" },
"kata": { "$": "char_width_selector",
"full": { "code": 12495, "label": "ハ" },
"half": { "code": 65418, "label": "ハ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12365, "label": "き" },
"kata": { "$": "char_width_selector",
"full": { "code": 12461, "label": "キ" },
"half": { "code": 65399, "label": "キ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12367, "label": "く" },
"kata": { "$": "char_width_selector",
"full": { "code": 12463, "label": "ク" },
"half": { "code": 65400, "label": "ク" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12414, "label": "ま" },
"kata": { "$": "char_width_selector",
"full": { "code": 12510, "label": "マ" },
"half": { "code": 65423, "label": "マ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12398, "label": "の" },
"kata": { "$": "char_width_selector",
"full": { "code": 12494, "label": "" },
"half": { "code": 65417, "label": "ノ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12426, "label": "り" },
"kata": { "$": "char_width_selector",
"full": { "code": 12522, "label": "リ" },
"half": { "code": 65432, "label": "リ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12428, "label": "れ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12524, "label": "レ" },
"half": { "code": 65434, "label": "レ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12369, "label": "け" },
"kata": { "$": "char_width_selector",
"full": { "code": 12465, "label": "ケ" },
"half": { "code": 65401, "label": "ケ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12416, "label": "む" },
"kata": { "$": "char_width_selector",
"full": { "code": 12512, "label": "ム" },
"half": { "code": 65425, "label": "ム" }
}
}
],
[
{ "$": "kana_selector",
"hira": { "code": 12388, "label": "つ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12484, "label": "ツ" },
"half": { "code": 65410, "label": "ツ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12373, "label": "さ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12469, "label": "サ" },
"half": { "code": 65403, "label": "サ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12381, "label": "そ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12477, "label": "ソ" },
"half": { "code": 65407, "label": "ソ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12402, "label": "ひ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12498, "label": "ヒ" },
"half": { "code": 65419, "label": "ヒ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12371, "label": "こ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12467, "label": "コ" },
"half": { "code": 65402, "label": "コ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12415, "label": "み" },
"kata": { "$": "char_width_selector",
"full": { "code": 12511, "label": "ミ" },
"half": { "code": 65424, "label": "ミ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12418, "label": "も" },
"kata": { "$": "char_width_selector",
"full": { "code": 12514, "label": "モ" },
"half": { "code": 65427, "label": "モ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12397, "label": "ね" },
"kata": { "$": "char_width_selector",
"full": { "code": 12493, "label": "ネ" },
"half": { "code": 65416, "label": "ネ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12427, "label": "る" },
"kata": { "$": "char_width_selector",
"full": { "code": 12523, "label": "ル" },
"half": { "code": 65433, "label": "ル" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12417, "label": "め" },
"kata": { "$": "char_width_selector",
"full": { "code": 12513, "label": "メ" },
"half": { "code": 65426, "label": "メ" }
}
},
{ "$": "kana_selector",
"hira": { "code": 12429, "label": "ろ" },
"kata": { "$": "char_width_selector",
"full": { "code": 12525, "label": "ロ" },
"half": { "code": 65435, "label": "ロ" }
}
}
]
]
}

View File

@@ -1,7 +1,7 @@
{
"type": "characters",
"name": "kurdish",
"label": "کوردی",
"label": "کوردی (قوەرتی نوێ)",
"authors": [ "GoRaN" ],
"direction": "rtl",
"modifier": "kurdish",
@@ -13,34 +13,46 @@
{ "code": 1608, "label": "و", "popup": {
"main": { "code": -255, "label": "وو" }
} },
{ "code": 1749, "label": "" },
{ "code": 1585, "label": "ر" },
{ "code": 1749, "label": "", "popup": {
"main": { "code": 1577, "label": "ة" }
} },
{ "code": 1585, "label": "ر", "popup": {
"main": { "code": 1685, "label": "ڕ" }
} },
{ "code": 1578, "label": "ت", "popup": {
"main": { "code": 1591, "label": "ط" }
} },
{ "code": 1740, "label": "ی" },
{ "code": 1574, "label": "ﺋ"},
{ "code": 1740, "label": "ی", "popup": {
"main": { "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": 1575, "label": "ا"},
{ "code": 1587, "label": "س" },
{ "code": 1588, "label": "ش" },
{ "code": 1583, "label": "د" },
{ "code": 1601, "label": "ف" },
{ "code": 1583, "label": "د", "popup": {
"main": {"code": 1584, "label": "ذ" }
} },
{ "code": 1601, "label": "ف" , "popup": {
"main": {"code": 1700, "label": "ڤ" }
} },
{ "code": 1607, "label": "ھ" },
{ "code": 1688, "label": "ژ" },
{ "code": 1604, "label": "ل" },
{ "code": 1688, "label": "ژ", "popup": {
"main": { "code": 1600, "label": "" }
} },
{ "code": 1604, "label": "ل", "popup": {
"main": { "code": 1717, "label": "ڵ" }
} },
{ "code": 1705, "label": "ک" },
{ "code": 1711, "label": "گ" }
],

View File

@@ -13,7 +13,7 @@
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 305, "label": "ı" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 251, "label": "û" }

View File

@@ -1,7 +1,7 @@
{
"type": "characters",
"name": "kurdish_standard",
"label": "کوردی - ستاندارد",
"label": "کوردی (ق‌ڤ‌ف‌غ)",
"authors": [ "GoRaN" ],
"direction": "rtl",
"modifier": "kurdish",
@@ -10,16 +10,14 @@
{ "code": 1602, "label": "ق", "popup": {
"main": { "code": 1647, "label": "ٯ" }
} },
{ "code": 1700, "label": "ڤ", "popup": {
"main": { "code": 1701, "label": "ڥ" }
} },
{ "code": 1601, "label": "ف", "popup": {
"main": { "code": 1698, "label": "ڢ" }
} },
{ "code": 1700, "label": "ڤ" },
{ "code": 1601, "label": "ف" },
{ "code": 1594, "label": "غ" },
{ "code": 1593, "label": "ع"},
{ "code": 1607, "label": "ھ" },
{ "code": 1749, "label": "" },
{ "code": 1749, "label": "", "popup": {
"main": { "code": 1577, "label": "ة" }
} },
{ "code": 1578, "label": "ت", "popup": {
"main": { "code": 1591, "label": "ط" }
@@ -46,7 +44,9 @@
} },
{ "code": 1585, "label": "ر" },
{ "code": 1685, "label": "ڕ" },
{ "code": 1583, "label": "د" },
{ "code": 1583, "label": "د", "popup": {
"main": {"code": 1584, "label": "ذ" }
} },
{ "code": -255, "label": "وو" },
{ "code": 1608, "label": "و" },
{ "code": 1734, "label": "ۆ" },
@@ -55,8 +55,10 @@
],
[
{ "code": 1600, "label": "kashida", "variation": "normal" },
{ "code": 1574, "label": "ﺋ"},
{ "code": 1574, "label": "ﺋ", "popup": {
"main": { "code": 1569, "label": "ء" }
} },
{ "code": 1662, "label": "پ", "popup": {
"main": { "code": 1579, "label": "ث" }

View File

@@ -0,0 +1,29 @@
{
"type": "characters/mod",
"name": "jis",
"label": "JIS",
"authors": [ "waelwindows" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "$": "char_width_selector",
"full": { "code": 12289, "label": "、", "groupId": 1 },
"half": { "code": 65380, "label": "、", "groupId": 1 }
},
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 12288, "label": "空白" },
{ "code": -9710, "label": "かな", "groupId": 97, "type": "system_gui" },
{ "$": "char_width_selector",
"full": { "code": 12290, "label": "。", "groupId": 2 },
"half": { "code": 65377, "label": "。", "groupId": 2 }
},
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -6,14 +6,17 @@
"direction": "rtl",
"arrangement": [
[
{ "code": 1600, "label": "kashida", "popup":
{ "main": { "code": 8204, "label": "half_space" }
} },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "$": "variation_selector",
"default": { "code": 1567, "label": "؟", "groupId": 1 },
"password": { "code": 1548, "label": "،", "groupId": 1 },
"default": { "code": 1548, "label": "،", "groupId": 1 },
"password": { "code": 35, "label": "#", "groupId": 1 },
"email": { "code": 64, "label": "@", "groupId": 1 },
"uri": { "code": 47, "label": "/", "groupId": 1 }
},

View File

@@ -0,0 +1,41 @@
{
"type": "characters",
"name": "nalmy",
"label": "NALMY",
"authors": [ "jeremiah-mille", "jasmcole" ],
"direction": "ltr",
"arrangement": [
[
{ "$": "auto_text_key", "code": 122, "label": "z" },
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 110, "label": "n" },
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 108, "label": "l" },
{ "$": "auto_text_key", "code": 109, "label": "m" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 120, "label": "x" },
{ "$": "auto_text_key", "code": 106, "label": "j" }
],
[
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 113, "label": "q" }
],
[
{ "$": "auto_text_key", "code": 119, "label": "w" },
{ "$": "auto_text_key", "code": 104, "label": "h" },
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 115, "label": "s" },
{ "$": "auto_text_key", "code": 99, "label": "c" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 102, "label": "f" }
]
]
}

View File

@@ -0,0 +1,41 @@
{
"type": "characters",
"name": "sangaline",
"label": "Sangaline",
"authors": [ "jeremiah-miller", "sangaline" ],
"direction": "ltr",
"arrangement": [
[
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 104, "label": "h" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 115, "label": "s" },
{ "$": "auto_text_key", "code": 106, "label": "j" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 110, "label": "n" }
],
[
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 113, "label": "q" },
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 119, "label": "w" },
{ "$": "auto_text_key", "code": 99, "label": "c" },
{ "$": "auto_text_key", "code": 108, "label": "l" },
{ "$": "auto_text_key", "code": 120, "label": "x" },
{ "$": "auto_text_key", "code": 109, "label": "m" }
],
[
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 122, "label": "z" },
{ "$": "auto_text_key", "code": 102, "label": "f" },
{ "$": "auto_text_key", "code": 111, "label": "o" }
]
]
}

View File

@@ -0,0 +1,201 @@
{
"type": "characters",
"name": "thai_kedmanee",
"label": "Thai Kedmanee",
"authors": [ "mrdeck4rd" ],
"direction": "ltr",
"arrangement": [
[
{ "$": "case_selector",
"lower": { "code": 3653, "label": "ๅ" },
"upper": { "code": 43, "label": "+" }
},
{ "$": "case_selector",
"lower": { "code": 47, "label": "/" },
"upper": { "code": 3665, "label": "๑" }
},
{ "$": "case_selector",
"lower": { "code": 95, "label": "_" },
"upper": { "code": 3666, "label": "๒" }
},
{ "$": "case_selector",
"lower": { "code": 3616, "label": "ภ" },
"upper": { "code": 3667, "label": "๓" }
},
{ "$": "case_selector",
"lower": { "code": 3606, "label": "ถ" },
"upper": { "code": 3668, "label": "๔" }
},
{ "$": "case_selector",
"lower": { "code": 3640, "label": "ุ" },
"upper": { "code": 3641, "label": "ู" }
},
{ "$": "case_selector",
"lower": { "code": 3638, "label": "ึ" },
"upper": { "code": 3647, "label": "฿" }
},
{ "$": "case_selector",
"lower": { "code": 3588, "label": "ค" },
"upper": { "code": 3669, "label": "๕" }
},
{ "$": "case_selector",
"lower": { "code": 3605, "label": "ต" },
"upper": { "code": 3670, "label": "๖" }
},
{ "$": "case_selector",
"lower": { "code": 3592, "label": "จ" },
"upper": { "code": 3671, "label": "๗" }
},
{ "$": "case_selector",
"lower": { "code": 3586, "label": "ข" },
"upper": { "code": 3672, "label": "๘" }
},
{ "$": "case_selector",
"lower": { "code": 3594, "label": "ช" },
"upper": { "code": 3673, "label": "๙" }
}
],
[
{ "$": "case_selector",
"lower": { "code": 3654, "label": "ๆ" },
"upper": { "code": 3664, "label": "" }
},
{ "$": "case_selector",
"lower": { "code": 3652, "label": "ไ" },
"upper": { "code": 34, "label": "\"" }
},
{ "$": "case_selector",
"lower": { "code": 3635, "label": "ำ" },
"upper": { "code": 3598, "label": "ฎ" }
},
{ "$": "case_selector",
"lower": { "code": 3614, "label": "พ" },
"upper": { "code": 3601, "label": "ฑ" }
},
{ "$": "case_selector",
"lower": { "code": 3632, "label": "ะ" },
"upper": { "code": 3608, "label": "ธ" }
},
{ "$": "case_selector",
"lower": { "code": 3633, "label": "ั" },
"upper": { "code": 3661, "label": "ํ" }
},
{ "$": "case_selector",
"lower": { "code": 3637, "label": "ี" },
"upper": { "code": 3658, "label": "๊" }
},
{ "$": "case_selector",
"lower": { "code": 3619, "label": "ร" },
"upper": { "code": 3603, "label": "ณ" }
},
{ "$": "case_selector",
"lower": { "code": 3609, "label": "น" },
"upper": { "code": 3631, "label": "ฯ" }
},
{ "$": "case_selector",
"lower": { "code": 3618, "label": "ย" },
"upper": { "code": 3597, "label": "ญ" }
},
{ "$": "case_selector",
"lower": { "code": 3610, "label": "บ" },
"upper": { "code": 3600, "label": "ฐ" }
},
{ "$": "case_selector",
"lower": { "code": 3621, "label": "ล" },
"upper": { "code": 44, "label": "," }
}
],
[
{ "$": "case_selector",
"lower": { "code": 3615, "label": "ฟ" },
"upper": { "code": 3620, "label": "ฤ" }
},
{ "$": "case_selector",
"lower": { "code": 3627, "label": "ห" },
"upper": { "code": 3590, "label": "ฆ" }
},
{ "$": "case_selector",
"lower": { "code": 3585, "label": "ก" },
"upper": { "code": 3599, "label": "ฏ" }
},
{ "$": "case_selector",
"lower": { "code": 3604, "label": "ด" },
"upper": { "code": 3650, "label": "โ" }
},
{ "$": "case_selector",
"lower": { "code": 3648, "label": "เ" },
"upper": { "code": 3596, "label": "ฌ" }
},
{ "$": "case_selector",
"lower": { "code": 3657, "label": "้" },
"upper": { "code": 3655, "label": "็" }
},
{ "$": "case_selector",
"lower": { "code": 3656, "label": "่" },
"upper": { "code": 3659, "label": "๋" }
},
{ "$": "case_selector",
"lower": { "code": 3634, "label": "า" },
"upper": { "code": 3625, "label": "ษ" }
},
{ "$": "case_selector",
"lower": { "code": 3626, "label": "ส" },
"upper": { "code": 3624, "label": "ศ" }
},
{ "$": "case_selector",
"lower": { "code": 3623, "label": "ว" },
"upper": { "code": 3595, "label": "ซ" }
},
{ "$": "case_selector",
"lower": { "code": 3591, "label": "ง" },
"upper": { "code": 46, "label": "." }
},
{ "$": "case_selector",
"lower": { "code": 3587, "label": "ฃ" },
"upper": { "code": 3589, "label": "ฅ" }
}
],
[
{ "$": "case_selector",
"lower": { "code": 3612, "label": "ผ" },
"upper": { "code": 40, "label": "(" }
},
{ "$": "case_selector",
"lower": { "code": 3611, "label": "ป" },
"upper": { "code": 41, "label": ")" }
},
{ "$": "case_selector",
"lower": { "code": 3649, "label": "แ" },
"upper": { "code": 3593, "label": "ฉ" }
},
{ "$": "case_selector",
"lower": { "code": 3629, "label": "อ" },
"upper": { "code": 3630, "label": "ฮ" }
},
{ "$": "case_selector",
"lower": { "code": 3636, "label": "ิ" },
"upper": { "code": 3642, "label": "ฺ" }
},
{ "$": "case_selector",
"lower": { "code": 3639, "label": "ื" },
"upper": { "code": 3660, "label": "์" }
},
{ "$": "case_selector",
"lower": { "code": 3607, "label": "ท" },
"upper": { "code": 63, "label": "?" }
},
{ "$": "case_selector",
"lower": { "code": 3617, "label": "ม" },
"upper": { "code": 3602, "label": "ฒ" }
},
{ "$": "case_selector",
"lower": { "code": 3651, "label": "ใ" },
"upper": { "code": 3628, "label": "ฬ" }
},
{ "$": "case_selector",
"lower": { "code": 3613, "label": "ฝ" },
"upper": { "code": 3622, "label": "ฦ" }
}
]
]
}

View File

@@ -0,0 +1,151 @@
{
"type": "characters",
"name": "thai_manoonchai",
"label": "Thai Manoonchai",
"authors": [ "mrdeck4rd" ],
"direction": "ltr",
"arrangement": [
[
{ "$": "case_selector",
"lower": { "code": 3651, "label": "ใ" },
"upper": { "code": 3602, "label": "ฒ" }
},
{ "$": "case_selector",
"lower": { "code": 3605, "label": "ต" },
"upper": { "code": 3599, "label": "ฏ" }
},
{ "$": "case_selector",
"lower": { "code": 3627, "label": "ห" },
"upper": { "code": 3595, "label": "ซ" }
},
{ "$": "case_selector",
"lower": { "code": 3621, "label": "ล" },
"upper": { "code": 3597, "label": "ญ" }
},
{ "$": "case_selector",
"lower": { "code": 3626, "label": "ส" },
"upper": { "code": 3615, "label": "ฟ" }
},
{ "$": "case_selector",
"lower": { "code": 3611, "label": "ป" },
"upper": { "code": 3593, "label": "ฉ" }
},
{ "$": "case_selector",
"lower": { "code": 3633, "label": "ั" },
"upper": { "code": 3638, "label": "ึ" }
},
{ "$": "case_selector",
"lower": { "code": 3585, "label": "ก" },
"upper": { "code": 3608, "label": "ธ" }
},
{ "$": "case_selector",
"lower": { "code": 3636, "label": "ิ" },
"upper": { "code": 3600, "label": "ฐ" }
},
{ "$": "case_selector",
"lower": { "code": 3610, "label": "บ" },
"upper": { "code": 3598, "label": "ฎ" }
},
{ "$": "case_selector",
"lower": { "code": 3655, "label": "็" },
"upper": { "code": 3590, "label": "ฆ" }
},
{ "$": "case_selector",
"lower": { "code": 3628, "label": "ฬ" },
"upper": { "code": 3601, "label": "ฑ" }
},
{ "$": "case_selector",
"lower": { "code": 3631, "label": "ฯ" },
"upper": { "code": 3596, "label": "ฌ" }
}
],
[
{ "$": "case_selector",
"lower": { "code": 3591, "label": "ง" },
"upper": { "code": 3625, "label": "ษ" }
},
{ "$": "case_selector",
"lower": { "code": 3648, "label": "เ" },
"upper": { "code": 3606, "label": "ถ" }
},
{ "$": "case_selector",
"lower": { "code": 3619, "label": "ร" },
"upper": { "code": 3649, "label": "แ" }
},
{ "$": "case_selector",
"lower": { "code": 3609, "label": "น" },
"upper": { "code": 3594, "label": "ช" }
},
{ "$": "case_selector",
"lower": { "code": 3617, "label": "ม" },
"upper": { "code": 3614, "label": "พ" }
},
{ "$": "case_selector",
"lower": { "code": 3629, "label": "อ" },
"upper": { "code": 3612, "label": "ผ" }
},
{ "$": "case_selector",
"lower": { "code": 3634, "label": "า" },
"upper": { "code": 3635, "label": "ำ" }
},
{ "$": "case_selector",
"lower": { "code": 3656, "label": "่" },
"upper": { "code": 3586, "label": "ข" }
},
{ "$": "case_selector",
"lower": { "code": 3657, "label": "้" },
"upper": { "code": 3650, "label": "โ" }
},
{ "$": "case_selector",
"lower": { "code": 3623, "label": "ว" },
"upper": { "code": 3616, "label": "ภ" }
},
{ "$": "case_selector",
"lower": { "code": 3639, "label": "ื" },
"upper": { "code": 34, "label": "\"" }
}
],
[
{ "$": "case_selector",
"lower": { "code": 3640, "label": "ุ" },
"upper": { "code": 3620, "label": "ฤ" }
},
{ "$": "case_selector",
"lower": { "code": 3652, "label": "ไ" },
"upper": { "code": 3613, "label": "ฝ" }
},
{ "$": "case_selector",
"lower": { "code": 3607, "label": "ท" },
"upper": { "code": 3654, "label": "ๆ" }
},
{ "$": "case_selector",
"lower": { "code": 3618, "label": "ย" },
"upper": { "code": 3603, "label": "ณ" }
},
{ "$": "case_selector",
"lower": { "code": 3592, "label": "จ" },
"upper": { "code": 3658, "label": "๊" }
},
{ "$": "case_selector",
"lower": { "code": 3588, "label": "ค" },
"upper": { "code": 3659, "label": "๋" }
},
{ "$": "case_selector",
"lower": { "code": 3637, "label": "ี" },
"upper": { "code": 3660, "label": "์" }
},
{ "$": "case_selector",
"lower": { "code": 3604, "label": "ด" },
"upper": { "code": 3624, "label": "ศ" }
},
{ "$": "case_selector",
"lower": { "code": 3632, "label": "ะ" },
"upper": { "code": 3630, "label": "ฮ" }
},
{ "$": "case_selector",
"lower": { "code": 3641, "label": "ู" },
"upper": { "code": 63, "label": "?" }
}
]
]
}

View File

@@ -9,7 +9,10 @@
{ "$": "auto_text_key", "code": 102, "label": "f" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 287, "label": "ğ" },
{ "$": "auto_text_key", "code": 305, "label": "ı" },
{ "$": "case_selector",
"lower": { "code": 305, "label": "ı" },
"upper": { "code": 73, "label": "I" }
},
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
@@ -21,7 +24,10 @@
],
[
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "case_selector",
"lower": { "code": 105, "label": "i" },
"upper": { "code": 304, "label": "İ" }
},
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 252, "label": "ü" },

View File

@@ -13,7 +13,10 @@
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 305, "label": "ı" },
{ "$": "case_selector",
"lower": { "code": 305, "label": "ı" },
"upper": { "code": 73, "label": "I" }
},
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 287, "label": "ğ" },
@@ -30,7 +33,10 @@
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 108, "label": "l" },
{ "$": "auto_text_key", "code": 351, "label": "ş" },
{ "$": "auto_text_key", "code": 105, "label": "i" }
{ "$": "case_selector",
"lower": { "code": 105, "label": "i" },
"upper": { "code": 304, "label": "İ" }
}
],
[
{ "$": "auto_text_key", "code": 122, "label": "z" },

View File

@@ -0,0 +1,49 @@
{
"type": "characters",
"name": "warang_citi",
"label": "𑢹𑣗𑣁𑣜𑣁𑣊 𑢯𑣂𑣕𑣂",
"authors": [ "Singkiring57" ],
"direction": "ltr",
"arrangement": [
[
{ "$": "auto_text_key", "code": 71876, "label": "𑣄" },
{ "$": "auto_text_key", "code": 71878, "label": "𑣆" },
{ "$": "auto_text_key", "code": 71880, "label": "𑣈" },
{ "$": "auto_text_key", "code": 71900, "label": "𑣜" },
{ "$": "auto_text_key", "code": 71899, "label": "𑣛" },
{ "$": "auto_text_key", "code": 71893, "label": "𑣕" },
{ "$": "auto_text_key", "code": 71877, "label": "𑣅" },
{ "$": "auto_text_key", "code": 71875, "label": "𑣃" },
{ "$": "auto_text_key", "code": 71874, "label": "𑣂" },
{ "$": "auto_text_key", "code": 71881, "label": "𑣉" },
{ "$": "auto_text_key", "code": 71896, "label": "𑣘" }
],
[
{ "$": "auto_text_key", "code": 71873, "label": "𑣁" },
{ "$": "auto_text_key", "code": 71902, "label": "𑣞" },
{ "$": "auto_text_key", "code": 71889, "label": "𑣑" },
{ "$": "auto_text_key", "code": 71892, "label": "𑣔" },
{ "$": "auto_text_key", "code": 71883, "label": "𑣋" },
{ "$": "auto_text_key", "code": 71882, "label": "𑣊" },
{ "$": "auto_text_key", "code": 71897, "label": "𑣙" },
{ "$": "auto_text_key", "code": 71886, "label": "𑣎" },
{ "$": "auto_text_key", "code": 71884, "label": "𑣌" },
{ "$": "auto_text_key", "code": 71898, "label": "𑣚" }
],
[
{ "$": "auto_text_key", "code": 71890, "label": "𑣒" },
{ "$": "auto_text_key", "code": 71879, "label": "𑣇" },
{ "$": "auto_text_key", "code": 71887, "label": "𑣏" },
{ "$": "auto_text_key", "code": 71872, "label": "𑣀" },
{ "$": "auto_text_key", "code": 71885, "label": "𑣍" },
{ "$": "auto_text_key", "code": 71888, "label": "𑣐" },
{ "$": "auto_text_key", "code": 71895, "label": "𑣗" },
{ "$": "auto_text_key", "code": 71891, "label": "𑣓" },
{ "code": 71894, "label": "𑣖", "popup": {
"relevant": [
{ "code": 71935, "label": "𑣿" }
]
} }
]
]
}

View File

@@ -0,0 +1,55 @@
{
"type": "numeric_advanced",
"name": "western_arabic_pc",
"label": "Western Arabic (PC)",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 43, "label": "+", "popup": {
"relevant": [
{ "code": 45, "label": "-" },
{ "code": 42, "label": "*" },
{ "code": 47, "label": "/" }
]
} },
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": 37, "label": "%" }
],
[
{ "code": 40, "label": "(", "popup": {
"relevant": [
{ "code": 91, "label": "[" },
{ "code": 123, "label": "{" }
]
} },
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": "space" }
],
[
{ "code": 41, "label": ")", "popup": {
"relevant": [
{ "code": 93, "label": "]" },
{ "code": 125, "label": "}" }
]
} },
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 44, "label": "," },
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 48, "label": "0", "type": "numeric" },
{ "code": 61, "label": "=" },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -0,0 +1,108 @@
{
"type": "numeric_row",
"name": "cjk",
"label": "CJK",
"authors": [ "waelwindows" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 19968, "label": "一", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 22769, "label": "壱" },
{ "code": 22777, "label": "壹" },
{ "code": 24332, "label": "弌" },
{ "code": 65297, "label": "" }
]
} },
{ "code": 20108, "label": "二", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 24336, "label": "弐" },
{ "code": 36019, "label": "貳" },
{ "code": 36014, "label": "貮" },
{ "code": 65298, "label": "" }
]
} },
{ "code": 19977, "label": "三", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 21442, "label": "参" },
{ "code": 21443, "label": "參" },
{ "code": 24334, "label": "弎" },
{ "code": 65299, "label": "" }
]
} },
{ "code": 22235, "label": "四", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 32902, "label": "肆" },
{ "code": 18825, "label": "䦉" },
{ "code": 20118, "label": "亖" },
{ "code": 65300, "label": "" }
]
} },
{ "code": 20116, "label": "五", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 20237, "label": "伍" },
{ "code": 65301, "label": "" }
]
} },
{ "code": 20845, "label": "六", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 38520, "label": "陸" },
{ "code": 65302, "label": "" }
]
} },
{ "code": 19971, "label": "七", "type": "numeric", "popup": {
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 28422, "label": "漆" },
{ "code": 26578, "label": "柒" },
{ "code": 65303, "label": "" }
]
} },
{ "code": 20843, "label": "八", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 25420, "label": "捌" },
{ "code": 65304, "label": "" }
]
} },
{ "code": 20061, "label": "九", "type": "numeric", "popup": {
"main": {"code": 57, "label": "9" },
"relevant": [
{ "code": 29590, "label": "玖" },
{ "code": 65305, "label": "" }
]
} },
{ "code": 38646, "label": "零", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 12295, "label": "" },
{ "code": 65296, "label": "" }
]
} },
{ "code": 21313, "label": "十", "type": "numeric", "popup": {
"main": { "code": 25342, "label": "拾" },
"relevant": [
{ "code": 20160, "label": "什" }
]
} },
{ "code": 30334, "label": "百", "type": "numeric", "popup": {
"main": { "code": 20336, "label": "佰" },
"relevant": [
{ "code": 38476, "label": "陌" }
]
} },
{ "code": 21315, "label": "千", "type": "numeric", "popup": {
"main": { "code": 20191, "label": "仟" },
"relevant": [
{ "code": 38433, "label": "阡" }
]
} }
]
]
}

View File

@@ -0,0 +1,75 @@
{
"type": "numeric_row",
"name": "thai",
"label": "Thai",
"authors": [ "mrdeck4rd" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 3665, "label": "๑", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8537, "label": "⅙" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 3666, "label": "๒", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3667, "label": "๓", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 8540, "label": "⅜" }
]
} },
{ "code": 3668, "label": "๔", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "⁴" },
"relevant": [
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3669, "label": "๕", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
} },
{ "code": 3670, "label": "๖", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" }
} },
{ "code": 3671, "label": "๗", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8542, "label": "⅞" }
]
} },
{ "code": 3672, "label": "๘", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" }
} },
{ "code": 3672, "label": "๘", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" }
} },
{ "code": 3664, "label": "", "type": "numeric", "popup": {
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" }
]
} }
]
]
}

View File

@@ -0,0 +1,91 @@
{
"type": "numeric_row",
"name": "warang_citi",
"label": "Warang Citi",
"authors": [ "Singkiring57" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 71905, "label": "𑣡", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8537, "label": "⅙" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 71906, "label": "𑣢", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 71907, "label": "𑣣", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 8535, "label": "⅗" },
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "⅜" }
]
} },
{ "code": 71908, "label": "𑣤", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8536, "label": "⅘" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 71909, "label": "𑣥", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
} },
{ "code": 71910, "label": "𑣦", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8310, "label": "⁶" }
]
} },
{ "code": 71911, "label": "𑣧", "type": "numeric", "popup": {
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 71912, "label": "𑣨", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 71913, "label": "𑣩", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 71904, "label": "𑣠", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8319, "label": "ⁿ" },
{ "code": 8709, "label": "∅" },
{ "code": 8304, "label": "⁰" }
]
} }
]
]
}

View File

@@ -0,0 +1,35 @@
{
"type": "numeric",
"name": "western_arabic_pc",
"label": "Western Arabic (PC)",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{ "code": 55, "label": "7", "type": "numeric" },
{ "code": 56, "label": "8", "type": "numeric" },
{ "code": 57, "label": "9", "type": "numeric" },
{ "code": 45, "label": "-" }
],
[
{ "code": 52, "label": "4", "type": "numeric" },
{ "code": 53, "label": "5", "type": "numeric" },
{ "code": 54, "label": "6", "type": "numeric" },
{ "code": 32, "label": "space" }
],
[
{ "code": 49, "label": "1", "type": "numeric" },
{ "code": 50, "label": "2", "type": "numeric" },
{ "code": 51, "label": "3", "type": "numeric" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": 44, "label": "," },
{ "code": 48, "label": "0", "type": "numeric", "popup": {
"main": { "code": 43, "label": "+" }
} },
{ "code": 46, "label": "." },
{ "code": 10, "label": "enter", "type": "enter_editing" }
]
]
}

View File

@@ -0,0 +1,283 @@
{
"type": "symbols",
"name": "cjk",
"label": "CJK",
"authors": [ "waelwindows" ],
"direction": "ltr",
"modifier": "cjk",
"arrangement": [
[
{
"$": "char_width_selector",
"full": { "code": 65312, "label": "", "popup": {
"main": { "code": 64, "label": "@" }
}
},
"half": { "code": 64, "label": "@" }
},
{ "code": 12306, "label": "〒", "popup": {
"main": { "code": 12320, "label": "〠" }
}
},
{
"$": "char_width_selector",
"full": { "code": 65283, "label": "", "popup": {
"main": { "code": 35, "label": "#" },
"relevant": [
{ "code": 8470, "label": "№" }
]
}
},
"half": { "code": 35, "label": "#", "popup": {
"main": { "code": 65283, "label": "" },
"relevant": [
{ "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" }
]
}
},
{
"$": "char_width_selector",
"full": { "code": 65285, "label": "", "popup": {
"main": { "code": 37, "label": "%" },
"relevant": [
{ "code": 8240, "label": "‰" },
{ "code": 8453, "label": "℅" }
]
}
},
"half": { "code": 37, "label": "%", "popup": {
"main": { "code": 65285, "label": "" },
"relevant": [
{ "code": 8240, "label": "‰" },
{ "code": 8453, "label": "℅" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65286, "label": "", "popup": {
"main": { "code": 38, "label": "&" }
}
},
"half": { "code": 38, "label": "&", "popup": {
"main": { "code": 65286, "label": "" }
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65293, "label": "", "popup": {
"main": { "code": 65343, "label": "_" },
"relevant": [
{ "code": 45, "label": "-" },
{ "code": 95, "label": "_" },
{ "code": 8212, "label": "—" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
}
},
"half": { "code": 45, "label": "-", "popup": {
"main": { "code": 95, "label": "_" },
"relevant": [
{ "code": 65293, "label": "" },
{ "code": 65343, "label": "_" },
{ "code": 8212, "label": "—" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65291, "label": "", "popup": {
"main": { "code": 43, "label": "+" },
"relevant": [
{ "code": 177, "label": "±" }
]
}
},
"half": { "code": 43, "label": "+", "popup": {
"main": { "code": 65291, "label": "" },
"relevant": [
{ "code": 177, "label": "±" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 12300, "label": "「", "popup": {
"main": { "code": 12302, "label": "『" },
"relevant": [
{ "code": 12304, "label": "【" },
{ "code": 12310, "label": "〖" }
]
}
},
"half": { "code": 65378, "label": "「", "popup": {
"main": { "code": 12301, "label": "」" },
"relevant": [
{ "code": 12303, "label": "』" },
{ "code": 12304, "label": "【" },
{ "code": 12310, "label": "〖" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 12301, "label": "」", "popup": {
"main": { "code": 12303, "label": "』" },
"relevant": [
{ "code": 12305, "label": "】" },
{ "code": 12311, "label": "〗" }
]
}
},
"half": { "code": 65379, "label": "」", "popup": {
"main": { "code": 12301, "label": "」" },
"relevant": [
{ "code": 12303, "label": "』" },
{ "code": 12305, "label": "】" },
{ "code": 12311, "label": "〗" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65295, "label": "", "popup": {
"main": { "code": 47, "label": "/" }
}
},
"half": { "code": 47, "label": "/", "popup": {
"main": { "code": 65295, "label": "" }
}
}
}
],
[
{
"$": "char_width_selector",
"full": { "code": 65290, "label": "", "popup": {
"main": { "code": 8251, "label": "※" },
"relevant": [
{ "code": 42, "label": "*" },
{ "code": 8224, "label": "†" },
{ "code": 9733, "label": "★" },
{ "code": 8225, "label": "‡" }
]
}
},
"half": { "code": 42, "label": "*", "popup": {
"main": { "code": 65290, "label": "" },
"relevant": [
{ "code": 8251, "label": "※" },
{ "code": 8224, "label": "†" },
{ "code": 9733, "label": "★" },
{ "code": 8225, "label": "‡" }
]
}
}
},
{ "code": 34, "label": "\"", "popup": {
"main": { "code": 8221, "label": "”" },
"relevant": [
{ "code": 8222, "label": "„" },
{ "code": 8220, "label": "“" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" }
]
}
},
{ "code": 39, "label": "'", "popup": {
"main": { "code": 8217, "label": "" },
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
}
},
{
"$": "char_width_selector",
"full": { "code": 65306, "label": "", "popup": {
"main": { "code": 58, "label": ":" },
"relevant": [
{ "code": 8942, "label": "⋮" }
]
}
},
"half": { "code": 58, "label": ":", "popup": {
"main": { "code": 65306, "label": "" },
"relevant": [
{ "code": 8942, "label": "⋮" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65307, "label": "", "popup": {
"main": { "code": 59, "label": ";" }
}
},
"half": { "code": 59, "label": ";", "popup": {
"main": { "code": 65307, "label": "" }
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65281, "label": "", "popup": {
"main": { "code": 33, "label": "!" },
"relevant": [
{ "code": 161, "label": "¡" }
]
}
},
"half": { "code": 33, "label": "!", "popup": {
"main": { "code": 65281, "label": "" },
"relevant": [
{ "code": 161, "label": "¡" }
]
}
}
},
{
"$": "char_width_selector",
"full": { "code": 65311, "label": "", "popup": {
"main": { "code": 63, "label": "?" },
"relevant": [
{ "code": 191, "label": "¿" },
{ "code": 8253, "label": "‽" }
]
}
},
"half": { "code": 63, "label": "?", "popup": {
"main": { "code": 65311, "label": "" },
"relevant": [
{ "code": 191, "label": "¿" },
{ "code": 8253, "label": "‽" }
]
}
}
}
]
]
}

View File

@@ -0,0 +1,55 @@
{
"type": "symbols/mod",
"name": "cjk",
"label": "CJK",
"authors": [ "waelwindows" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -203, "label": "view_symbols2", "type": "system_gui" },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "$": "char_width_selector",
"full": { "code": 12289, "label": "、", "popup": {
"main": { "code": 44, "label": "," }
}
},
"half": { "code": 65380, "label": "、", "popup": {
"main": { "code": 44, "label": "," }
}
}
},
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 12288, "label": "空白" },
{ "code": -9701, "label": "char_width_switcher", "type": "system_gui", "popup": {
"relevant": [
{ "code": -9702, "label": "char_width_full", "type": "system_gui" },
{ "code": -9703, "label": "char_width_half", "type": "system_gui" }
]
}
},
{ "$": "char_width_selector",
"full": { "code": 12290, "label": "。", "popup": {
"main": { "code": 8230, "label": "…" },
"relevant": [
{ "code": 12539, "label": "・" },
{ "code": 46, "label": "." }
]
}
},
"half": { "code": 65377, "label": "。", "popup": {
"main": { "code": 8230, "label": "…" },
"relevant": [
{ "code": 65381, "label": "・" },
{ "code": 46, "label": "." }
]
}
}
},
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

View File

@@ -0,0 +1,109 @@
{
"type": "symbols",
"name": "western-additional-symbols",
"label": "Western (Additional symbols)",
"authors": [ "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
{"code": 37, "label": "%" , "popup": {
"main": { "code": 8240, "label": "‰" },
"relevant": [
{ "code": 8453, "label": "℅" }
]
} },
{"code": 92, "label": "\\"},
{"code":124, "label": "|"},
{"code": 61, "label": "="},
{"code": 91, "label": "["},
{"code": 93, "label": "]"},
{"code": 60, "label": "<"},
{"code": 62, "label": ">"},
{"code": 123, "label": "{"},
{"code": 125, "label": "}"}
],
[
{ "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": 95, "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": 34, "label": "\"", "popup": {
"main": { "code": 8221, "label": "”" },
"relevant": [
{ "code": 8222, "label": "„" },
{ "code": 8220, "label": "“" },
{ "code": 171, "label": "«" },
{ "code": 187, "label": "»" }
]
} },
{ "code": 39, "label": "'", "popup": {
"main": { "code": 8217, "label": "" },
"relevant": [
{ "code": 8218, "label": "" },
{ "code": 8216, "label": "" },
{ "code": 8249, "label": "" },
{ "code": 8250, "label": "" }
]
} },
{ "code": 58, "label": ":", "popup": {
"main": { "code": 8942, "label": "⋮" }
} },
{ "code": 59, "label": ";" },
{ "code": 33, "label": "!", "popup": {
"main": { "code": 161, "label": "¡" }
} },
{ "code": 63, "label": "?", "popup": {
"main": { "code": 191, "label": "¿" },
"relevant": [
{ "code": 8253, "label": "‽" }
]
} }
]
]
}

View File

@@ -0,0 +1,225 @@
{
"type": "symbols2",
"name": "cjk",
"label": "CJK",
"authors": [ "waelwindows" ],
"direction": "ltr",
"modifier": "cjk",
"arrangement": [
[
{ "$": "char_width_selector",
"full": { "code": 12316, "label": "〜", "popup": {
"main": { "code": 126, "label": "~" }
}
},
"half": { "code": 126, "label": "~", "popup": {
"main": { "code": 12316, "label": "〜" }
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65344, "label": "", "popup": {
"main": { "code": 96, "label": "`" }
}
},
"half": { "code": 96, "label": "`", "popup": {
"main": { "code": 65344, "label": "" }
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65372, "label": "", "popup": {
"main": { "code": 124, "label": "|" }
}
},
"half": { "code": 124, "label": "|", "popup": {
"main": { "code": 65372, "label": "" }
}
}
},
{ "$": "char_width_selector",
"full":
{ "code": 12539, "label": "・", "popup": {
"main": { "code": 9834, "label": "♪" },
"relevant": [
{ "code": 8226, "label": "•" },
{ "code": 9827, "label": "♣" },
{ "code": 9824, "label": "♠" },
{ "code": 9829, "label": "♥" },
{ "code": 9830, "label": "♦" }
]
} },
"half":
{ "code": 8226, "label": "•", "popup": {
"main": { "code": 9834, "label": "♪" },
"relevant": [
{ "code": 12539, "label": "・" },
{ "code": 9827, "label": "♣" },
{ "code": 9824, "label": "♠" },
{ "code": 9829, "label": "♥" },
{ "code": 9830, "label": "♦" }
]
} }
},
{ "code": 8730, "label": "√" },
{ "code": 960, "label": "π", "popup": {
"main": { "code": 928, "label": "Π" },
"relevant": [
{ "code": 969, "label": "ω" },
{ "code": 945, "label": "α" },
{ "code": 946, "label": "β" },
{ "code": 937, "label": "Ω" },
{ "code": 956, "label": "μ" }
]
} },
{ "code": 247, "label": "÷" },
{ "code": 215, "label": "×" },
{ "code": 182, "label": "¶" },
{ "code": 8710, "label": "∆" }
],
[
{ "code": -805, "label": "currency_slot_5" },
{ "code": -804, "label": "currency_slot_4" },
{ "code": -803, "label": "currency_slot_3" },
{ "code": -802, "label": "currency_slot_2" },
{ "code": 94, "label": "^", "popup": {
"main": { "code": 8593, "label": "↑" },
"relevant": [
{ "code": 8592, "label": "←" },
{ "code": 8595, "label": "↓" },
{ "code": 8594, "label": "→" }
]
} },
{ "code": 176, "label": "°", "popup": {
"main": { "code": 8242, "label": "" },
"relevant": [
{ "code": 8243, "label": "″" }
]
} },
{ "$": "char_width_selector",
"full":
{ "code": 65309, "label": "", "popup": {
"main": { "code": 8800, "label": "≠" },
"relevant": [
{ "code": 61, "label": "=" },
{ "code": 8734, "label": "∞" },
{ "code": 8776, "label": "≈" }
]
}
},
"half":
{ "code": 61, "label": "=", "popup": {
"main": { "code": 8800, "label": "≠" },
"relevant": [
{ "code": 61, "label": "=" },
{ "code": 8734, "label": "∞" },
{ "code": 8776, "label": "≈" }
]
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65371, "label": "", "popup": {
"main": { "code": 65288, "label": "" },
"relevant": [
{ "code": 123, "label": "{" },
{ "code": 12308, "label": "" },
{ "code": 12312, "label": "〘" },
{ "code": 65375, "label": "⦅" }
]
}
},
"half": { "code": 123, "label": "{", "popup": {
"main": { "code": 40, "label": "(" },
"relevant": [
{ "code": 65371, "label": "" }
]
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65373, "label": "", "popup": {
"main": { "code": 65289, "label": "" },
"relevant": [
{ "code": 125, "label": "}" },
{ "code": 12309, "label": "" },
{ "code": 12313, "label": "〙" },
{ "code": 65376, "label": "⦆" }
]
}
},
"half": { "code": 125, "label": "}", "popup": {
"main": { "code": 41, "label": ")" },
"relevant": [
{ "code": 65373, "label": "" }
]
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65340, "label": "", "popup": {
"main": { "code": 92, "label": "\\" }
}
},
"half": { "code": 92, "label": "\\", "popup": {
"main": { "code": 65340, "label": "" }
}
}
}
],
[
{ "code": 12292, "label": "〄" },
{ "code": 12293, "label": "々" },
{ "code": 12294, "label": "〆" },
{ "code": 12295, "label": "" },
{ "$": "kana_selector",
"hira": { "code": 12445, "label": "ゝ", "popup": {
"main": { "code": 12446, "label": "ゞ" },
"relevant": [
{ "code": 12541, "label": "ヽ" },
{ "code": 12542, "label": "ヾ" }
]
}
},
"kata": { "code": 12541, "label": "ヽ", "popup": {
"main": { "code": 12542, "label": "ヾ" },
"relevant": [
{ "code": 12445, "label": "ゝ" },
{ "code": 12446, "label": "ゞ" }
]
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65339, "label": "", "popup": {
"main": { "code": 91, "label": "[" },
"relevant": [
{ "code": 12314, "label": "〚" },
{ "code": 12304, "label": "【" },
{ "code": 12310, "label": "〖" }
]
}
},
"half": { "code": 91, "label": "[", "popup": {
"main": { "code": 65339, "label": "" }
}
}
},
{ "$": "char_width_selector",
"full": { "code": 65341, "label": "", "popup": {
"main": { "code": 93, "label": "]" },
"relevant": [
{ "code": 12315, "label": "〛" },
{ "code": 12305, "label": "】" },
{ "code": 12311, "label": "〗" }
]
}
},
"half": { "code": 93, "label": "]", "popup": {
"main": { "code": 65341, "label": "" }
}
}
}
]
]
}

View File

@@ -0,0 +1,45 @@
{
"type": "symbols2/mod",
"name": "cjk",
"label": "CJK",
"authors": [ "waelwindows" ],
"direction": "ltr",
"arrangement": [
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 0, "type": "placeholder" },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -201, "label": "view_characters", "type": "system_gui" },
{ "code": 12296, "label": "〈", "popup": {
"main": { "code": 12298, "label": "《" },
"relevant": [
{ "code": 8804, "label": "≤" },
{ "code": 8249, "label": "" },
{ "code": 10216, "label": "⟨" },
{ "code": 65308, "label": "" }
]
} },
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
{ "code": 12288, "label": "空白" },
{ "code": -9701, "label": "char_width_switcher", "type": "system_gui", "popup": {
"relevant": [
{ "code": -9702, "label": "char_width_full", "type": "system_gui" },
{ "code": -9703, "label": "char_width_half", "type": "system_gui" }
]
}
},
{ "code": 12297, "label": "〉", "popup": {
"main": { "code": 12299, "label": "》" },
"relevant": [
{ "code": 62, "label": ">" },
{ "code": 8805, "label": "≥" },
{ "code": 10217, "label": "⟩" },
{ "code": 65310, "label": "" }
]
} },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 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.

View File

@@ -7,32 +7,42 @@ project("florisboard")
set(CMAKE_CXX_STANDARD 17)
include_directories(.)
### ICU4C ###
include_directories(../icu4c/prebuilt/include)
set(JNI_LIBS ${CMAKE_SOURCE_DIR}/../icu4c/prebuilt/jniLibs/${ANDROID_ABI})
add_library(ICU::data STATIC IMPORTED)
set_property(TARGET ICU::data PROPERTY IMPORTED_LOCATION "${JNI_LIBS}/libicudata.a")
add_library(ICU::uc STATIC IMPORTED)
set_property(TARGET ICU::uc PROPERTY IMPORTED_LOCATION "${JNI_LIBS}/libicuuc.a")
### FlorisBoard ###
add_subdirectory(nuspell)
add_subdirectory(utils)
add_subdirectory(ime/nlp)
add_subdirectory(ime/spelling)
add_library(
# Name
florisboard-native
# Type
SHARED
# Sources
dev_patrickgold_florisboard_FlorisApplication.cpp
dev_patrickgold_florisboard_ime_nlp_SuggestionList.cpp
dev_patrickgold_florisboard_ime_spelling_SpellingDict.cpp
)
find_library(
# Save to var
log-lib
# Original name
log
)
target_compile_options(florisboard-native PRIVATE -ffunction-sections -fdata-sections -fexceptions)
target_link_libraries(
# Destination
florisboard-native
# Sources
${log-lib}
android
log
ICU::uc
ICU::data
Nuspell::nuspell
utils
ime-nlp
ime-spelling
)

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fstream>
#include <vector>
#include <jni.h>
#include <unicode/udata.h>
#include "utils/jni_utils.h"
#pragma ide diagnostic ignored "UnusedLocalVariable"
extern "C"
JNIEXPORT jint JNICALL
Java_dev_patrickgold_florisboard_FlorisApplication_00024Companion_nativeInitICUData(
JNIEnv *env,
jobject thiz,
jobject path) {
auto path_str = utils::j2std_string(env, path);
std::ifstream in_file(path_str, std::ios::in | std::ios::binary);
if (!in_file) {
return U_FILE_ACCESS_ERROR;
}
in_file.seekg(0, std::ios::end);
size_t size = in_file.tellg();
if (size <= 0) {
return U_FILE_ACCESS_ERROR;
}
in_file.seekg(0, std::ios::beg);
char *icu_data = new char[size + 1];
in_file.read(icu_data, size);
if (!in_file) {
in_file.close();
return U_FILE_ACCESS_ERROR;
}
icu_data[size] = 0;
in_file.close();
UErrorCode status = U_ZERO_ERROR;
udata_setCommonData(reinterpret_cast<void *>(icu_data), &status);
return status;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <algorithm>
#include "ime/spelling/spellingdict.h"
#include "utils/jni_utils.h"
#pragma ide diagnostic ignored "UnusedLocalVariable"
using namespace ime::spellcheck;
extern "C"
JNIEXPORT jlong JNICALL
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeInitialize(
JNIEnv *env,
jobject thiz,
jobject base_path) {
auto strBasePath = utils::j2std_string(env, base_path);
auto *spellingDict = SpellingDict::load(strBasePath);
if (spellingDict == nullptr) {
return 0L;
} else {
return reinterpret_cast<jlong>(spellingDict);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeDispose(
JNIEnv *env,
jobject thiz,
jlong native_ptr) {
auto spellingDict = reinterpret_cast<SpellingDict *>(native_ptr);
delete spellingDict;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeSpell(
JNIEnv *env,
jobject thiz,
jlong native_ptr,
jobject word) {
auto strWord = utils::j2std_string(env, word);
auto spellingDict = reinterpret_cast<SpellingDict *>(native_ptr);
auto result = spellingDict->spell(strWord);
return result;
}
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeSuggest(
JNIEnv *env,
jobject thiz,
jlong native_ptr,
jobject word,
jint limit) {
auto strWord = utils::j2std_string(env, word);
auto spellingDict = reinterpret_cast<SpellingDict *>(native_ptr);
auto result = spellingDict->suggest(strWord);
auto retSize = std::min(result.size(), (size_t)std::max(0, limit));
jclass jByteArrayClass = env->FindClass("java/nio/ByteBuffer");
jobjectArray jSuggestions = env->NewObjectArray(retSize, jByteArrayClass, nullptr);
for (int n = 0; n < retSize; n++) {
env->SetObjectArrayElement(jSuggestions, n, utils::std2j_string(env, result[n]));
}
return jSuggestions;
}

View File

@@ -0,0 +1,10 @@
add_library(
# Name
ime-spelling
# Headers
spellingdict.h
# Sources
spellingdict.cpp
)

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "spellingdict.h"
#include "utils/log.h"
using namespace ime::spellcheck;
SpellingDict::SpellingDict(const nuspell::Dictionary& dict) : dictionary(std::make_unique<nuspell::Dictionary>(dict))
{ }
SpellingDict::~SpellingDict() = default;
SpellingDict* SpellingDict::load(const std::string &basePath) {
utils::start_stdout_stderr_logger("spell-floris");
try {
auto temp = nuspell::Dictionary::load_from_path(basePath);
auto spellingDict = new SpellingDict(temp);
return spellingDict;
} catch (const nuspell::Dictionary_Loading_Error& e) {
utils::log_error("SpellingDict.load()", e.what());
return nullptr;
} catch (...) {
utils::log_error("SpellingDict.load()", "An unknown error occurred!");
return nullptr;
}
}
bool SpellingDict::spell(const std::string& word) {
bool result = dictionary->spell(word);
return result;
}
std::vector<std::string> SpellingDict::suggest(const std::string &word) {
auto result = std::vector<std::string>();
dictionary->suggest(word, result);
return result;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLORISBOARD_SPELLINGDICT_H
#define FLORISBOARD_SPELLINGDICT_H
#include "nuspell/dictionary.hxx"
#include <string>
#include <vector>
namespace ime::spellcheck {
class SpellingDict {
public:
SpellingDict(const nuspell::Dictionary& dict);
~SpellingDict();
static SpellingDict* load(const std::string& basePath);
bool spell(const std::string& word);
std::vector<std::string> suggest(const std::string& word);
private:
std::unique_ptr<nuspell::Dictionary> dictionary;
};
} // namespace ime::spellcheck
#endif // FLORISBOARD_SPELLINGDICT_H

View File

@@ -0,0 +1,10 @@
add_library(nuspell
aff_data.cxx aff_data.hxx
checker.cxx checker.hxx
suggester.cxx suggester.hxx
dictionary.cxx dictionary.hxx
unicode.hxx
utils.cxx utils.hxx
structures.hxx)
add_library(Nuspell::nuspell ALIAS nuspell)

View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NUSPELL_AFF_DATA_HXX
#define NUSPELL_AFF_DATA_HXX
#include "nuspell_export.h"
#include "structures.hxx"
#include <iosfwd>
#include <unicode/locid.h>
namespace nuspell {
inline namespace v5 {
class Encoding {
std::string name;
NUSPELL_EXPORT auto normalize_name() -> void;
public:
enum Enc_Type { SINGLEBYTE = false, UTF8 = true };
Encoding() = default;
explicit Encoding(const std::string& e) : name(e) { normalize_name(); }
explicit Encoding(std::string&& e) : name(move(e)) { normalize_name(); }
explicit Encoding(const char* e) : name(e) { normalize_name(); }
auto& operator=(const std::string& e)
{
name = e;
normalize_name();
return *this;
}
auto& operator=(std::string&& e)
{
name = move(e);
normalize_name();
return *this;
}
auto& operator=(const char* e)
{
name = e;
normalize_name();
return *this;
}
auto empty() const { return name.empty(); }
auto& value() const { return name; }
auto is_utf8() const { return name == "UTF-8"; }
auto value_or_default() const -> std::string
{
if (name.empty())
return "ISO8859-1";
else
return name;
}
operator Enc_Type() const { return is_utf8() ? UTF8 : SINGLEBYTE; }
};
enum class Flag_Type { SINGLE_CHAR, DOUBLE_CHAR, NUMBER, UTF8 };
/**
* @internal
* @brief Map between words and word_flags.
*
* Flags are stored as part of the container. Maybe for the future flags should
* be stored elsewhere (flag aliases) and this should store pointers.
*
* Does not store morphological data as is low priority feature and is out of
* scope.
*/
using Word_List = Hash_Multimap<std::string, Flag_Set>;
struct Aff_Data {
static constexpr auto HIDDEN_HOMONYM_FLAG = char16_t(-1);
static constexpr auto MAX_SUGGESTIONS = size_t(16);
// spell checking options
Word_List words;
Prefix_Table prefixes;
Suffix_Table suffixes;
bool complex_prefixes;
bool fullstrip;
bool checksharps;
bool forbid_warn;
char16_t compound_onlyin_flag;
char16_t circumfix_flag;
char16_t forbiddenword_flag;
char16_t keepcase_flag;
char16_t need_affix_flag;
char16_t warn_flag;
// compounding options
char16_t compound_flag;
char16_t compound_begin_flag;
char16_t compound_last_flag;
char16_t compound_middle_flag;
Compound_Rule_Table compound_rules;
// spell checking options
Break_Table break_table;
Substr_Replacer input_substr_replacer;
std::string ignored_chars;
icu::Locale icu_locale;
Substr_Replacer output_substr_replacer;
// suggestion options
Replacement_Table replacements;
std::vector<Similarity_Group> similarities;
std::string keyboard_closeness;
std::string try_chars;
// Phonetic_Table phonetic_table;
char16_t nosuggest_flag;
char16_t substandard_flag;
unsigned short max_compound_suggestions;
unsigned short max_ngram_suggestions;
unsigned short max_diff_factor;
bool only_max_diff;
bool no_split_suggestions;
bool suggest_with_dots;
// compounding options
unsigned short compound_min_length;
unsigned short compound_max_word_count;
char16_t compound_permit_flag;
char16_t compound_forbid_flag;
char16_t compound_root_flag;
char16_t compound_force_uppercase;
bool compound_more_suffixes;
bool compound_check_duplicate;
bool compound_check_rep;
bool compound_check_case;
bool compound_check_triple;
bool compound_simplified_triple;
bool compound_syllable_num;
unsigned short compound_syllable_max;
std::string compound_syllable_vowels;
std::vector<Compound_Pattern> compound_patterns;
// data members used only while parsing
Flag_Type flag_type;
Encoding encoding;
std::vector<Flag_Set> flag_aliases;
std::string wordchars; // deprecated?
auto parse_aff(std::istream& in) -> bool;
auto parse_dic(std::istream& in) -> bool;
auto parse_aff_dic(std::istream& aff, std::istream& dic)
{
if (parse_aff(aff))
return parse_dic(dic);
return false;
}
};
} // namespace v5
} // namespace nuspell
#endif // NUSPELL_AFF_DATA_HXX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,352 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NUSPELL_CHECKER_HXX
#define NUSPELL_CHECKER_HXX
#include "aff_data.hxx"
namespace nuspell {
inline namespace v5 {
enum Affixing_Mode {
FULL_WORD,
AT_COMPOUND_BEGIN,
AT_COMPOUND_END,
AT_COMPOUND_MIDDLE
};
struct Affixing_Result_Base {
Word_List::const_pointer root_word = {};
operator Word_List::const_pointer() const { return root_word; }
auto& operator*() const { return *root_word; }
auto operator->() const { return root_word; }
};
template <class T1 = void, class T2 = void>
struct Affixing_Result : Affixing_Result_Base {
const T1* a = {};
const T2* b = {};
Affixing_Result() = default;
Affixing_Result(Word_List::const_reference r, const T1& a, const T2& b)
: Affixing_Result_Base{&r}, a{&a}, b{&b}
{
}
};
template <class T1>
struct Affixing_Result<T1, void> : Affixing_Result_Base {
const T1* a = {};
Affixing_Result() = default;
Affixing_Result(Word_List::const_reference r, const T1& a)
: Affixing_Result_Base{&r}, a{&a}
{
}
};
template <>
struct Affixing_Result<void, void> : Affixing_Result_Base {
Affixing_Result() = default;
Affixing_Result(Word_List::const_reference r) : Affixing_Result_Base{&r}
{
}
};
struct Compounding_Result {
Word_List::const_pointer word_entry = {};
unsigned char num_words_modifier = {};
signed char num_syllable_modifier = {};
bool affixed_and_modified = {}; /**< non-zero affix */
operator Word_List::const_pointer() const { return word_entry; }
auto& operator*() const { return *word_entry; }
auto operator->() const { return word_entry; }
};
struct Checker : public Aff_Data {
enum Forceucase : bool {
FORBID_BAD_FORCEUCASE = false,
ALLOW_BAD_FORCEUCASE = true
};
enum Hidden_Homonym : bool {
ACCEPT_HIDDEN_HOMONYM = false,
SKIP_HIDDEN_HOMONYM = true
};
Checker()
: Aff_Data() // we explicity do value init so content is zeroed
{
}
auto spell_priv(std::string& s) const -> bool;
auto spell_break(std::string& s, size_t depth = 0) const -> bool;
auto spell_casing(std::string& s) const -> const Flag_Set*;
auto spell_casing_upper(std::string& s) const -> const Flag_Set*;
auto spell_casing_title(std::string& s) const -> const Flag_Set*;
auto spell_sharps(std::string& base, size_t n_pos = 0, size_t n = 0,
size_t rep = 0) const -> const Flag_Set*;
auto check_word(std::string& s, Forceucase allow_bad_forceucase = {},
Hidden_Homonym skip_hidden_homonym = {}) const
-> const Flag_Set*;
auto check_simple_word(std::string& word,
Hidden_Homonym skip_hidden_homonym = {}) const
-> const Flag_Set*;
template <Affixing_Mode m>
auto affix_NOT_valid(const Prefix& a) const;
template <Affixing_Mode m>
auto affix_NOT_valid(const Suffix& a) const;
template <Affixing_Mode m, class AffixT>
auto outer_affix_NOT_valid(const AffixT& a) const;
template <class AffixT>
auto is_circumfix(const AffixT& a) const;
template <Affixing_Mode m>
auto is_valid_inside_compound(const Flag_Set& flags) const;
template <Affixing_Mode m = FULL_WORD>
auto strip_prefix_only(std::string& s,
Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Prefix>;
template <Affixing_Mode m = FULL_WORD>
auto strip_suffix_only(std::string& s,
Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Suffix>;
template <Affixing_Mode m = FULL_WORD>
auto
strip_prefix_then_suffix(std::string& s,
Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Suffix, Prefix>;
template <Affixing_Mode m>
auto strip_pfx_then_sfx_2(const Prefix& pe, std::string& s,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<Suffix, Prefix>;
template <Affixing_Mode m = FULL_WORD>
auto
strip_suffix_then_prefix(std::string& s,
Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Prefix, Suffix>;
template <Affixing_Mode m>
auto strip_sfx_then_pfx_2(const Suffix& se, std::string& s,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<Prefix, Suffix>;
template <Affixing_Mode m = FULL_WORD>
auto strip_prefix_then_suffix_commutative(
std::string& word, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Suffix, Prefix>;
template <Affixing_Mode m = FULL_WORD>
auto strip_pfx_then_sfx_comm_2(const Prefix& pe, std::string& word,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<Suffix, Prefix>;
template <Affixing_Mode m = FULL_WORD>
auto
strip_suffix_then_suffix(std::string& s,
Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Suffix, Suffix>;
template <Affixing_Mode m>
auto strip_sfx_then_sfx_2(const Suffix& se1, std::string& s,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<Suffix, Suffix>;
template <Affixing_Mode m = FULL_WORD>
auto
strip_prefix_then_prefix(std::string& s,
Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<Prefix, Prefix>;
template <Affixing_Mode m>
auto strip_pfx_then_pfx_2(const Prefix& pe1, std::string& s,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<Prefix, Prefix>;
template <Affixing_Mode m = FULL_WORD>
auto strip_prefix_then_2_suffixes(
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<>;
template <Affixing_Mode m>
auto strip_pfx_2_sfx_3(const Prefix& pe1, const Suffix& se1,
std::string& s,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<>;
template <Affixing_Mode m = FULL_WORD>
auto strip_suffix_prefix_suffix(
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<>;
template <Affixing_Mode m>
auto strip_s_p_s_3(const Suffix& se1, const Prefix& pe1,
std::string& word,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<>;
template <Affixing_Mode m = FULL_WORD>
auto strip_2_suffixes_then_prefix(
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<>;
template <Affixing_Mode m>
auto strip_2_sfx_pfx_3(const Suffix& se1, const Suffix& se2,
std::string& word,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<>;
template <Affixing_Mode m = FULL_WORD>
auto strip_suffix_then_2_prefixes(
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<>;
template <Affixing_Mode m>
auto strip_sfx_2_pfx_3(const Suffix& se1, const Prefix& pe1,
std::string& s,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<>;
template <Affixing_Mode m = FULL_WORD>
auto strip_prefix_suffix_prefix(
std::string& word, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<>;
template <Affixing_Mode m>
auto strip_p_s_p_3(const Prefix& pe1, const Suffix& se1,
std::string& word,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<>;
template <Affixing_Mode m = FULL_WORD>
auto strip_2_prefixes_then_suffix(
std::string& word, Hidden_Homonym skip_hidden_homonym = {}) const
-> Affixing_Result<>;
template <Affixing_Mode m>
auto strip_2_pfx_sfx_3(const Prefix& pe1, const Prefix& pe2,
std::string& word,
Hidden_Homonym skip_hidden_homonym) const
-> Affixing_Result<>;
auto check_compound(std::string& word,
Forceucase allow_bad_forceucase) const
-> Compounding_Result;
template <Affixing_Mode m = AT_COMPOUND_BEGIN>
auto check_compound(std::string& word, size_t start_pos,
size_t num_part, std::string& part,
Forceucase allow_bad_forceucase) const
-> Compounding_Result;
template <Affixing_Mode m = AT_COMPOUND_BEGIN>
auto check_compound_classic(std::string& word, size_t start_pos,
size_t i, size_t num_part,
std::string& part,
Forceucase allow_bad_forceucase) const
-> Compounding_Result;
template <Affixing_Mode m = AT_COMPOUND_BEGIN>
auto check_compound_with_pattern_replacements(
std::string& word, size_t start_pos, size_t i, size_t num_part,
std::string& part, Forceucase allow_bad_forceucase) const
-> Compounding_Result;
template <Affixing_Mode m>
auto check_word_in_compound(std::string& s) const -> Compounding_Result;
auto calc_num_words_modifier(const Prefix& pfx) const -> unsigned char;
template <Affixing_Mode m>
auto calc_syllable_modifier(Word_List::const_reference we) const
-> signed char;
template <Affixing_Mode m>
auto calc_syllable_modifier(Word_List::const_reference we,
const Suffix& sfx) const -> signed char;
auto count_syllables(std::string_view word) const -> size_t;
auto check_compound_with_rules(std::string& word,
std::vector<const Flag_Set*>& words_data,
size_t start_pos, std::string& part,
Forceucase allow_bad_forceucase) const
-> Compounding_Result;
auto is_rep_similar(std::string& word) const -> bool;
};
template <Affixing_Mode m>
auto Checker::affix_NOT_valid(const Prefix& e) const
{
if (m == FULL_WORD && e.cont_flags.contains(compound_onlyin_flag))
return true;
if (m == AT_COMPOUND_END &&
!e.cont_flags.contains(compound_permit_flag))
return true;
if (m != FULL_WORD && e.cont_flags.contains(compound_forbid_flag))
return true;
return false;
}
template <Affixing_Mode m>
auto Checker::affix_NOT_valid(const Suffix& e) const
{
if (m == FULL_WORD && e.cont_flags.contains(compound_onlyin_flag))
return true;
if (m == AT_COMPOUND_BEGIN &&
!e.cont_flags.contains(compound_permit_flag))
return true;
if (m != FULL_WORD && e.cont_flags.contains(compound_forbid_flag))
return true;
return false;
}
template <Affixing_Mode m, class AffixT>
auto Checker::outer_affix_NOT_valid(const AffixT& e) const
{
if (affix_NOT_valid<m>(e))
return true;
if (e.cont_flags.contains(need_affix_flag))
return true;
return false;
}
template <class AffixT>
auto Checker::is_circumfix(const AffixT& a) const
{
return a.cont_flags.contains(circumfix_flag);
}
template <class AffixInner, class AffixOuter>
auto cross_valid_inner_outer(const AffixInner& inner, const AffixOuter& outer)
{
return inner.cont_flags.contains(outer.flag);
}
template <class Affix>
auto cross_valid_inner_outer(const Flag_Set& word_flags, const Affix& afx)
{
return word_flags.contains(afx.flag);
}
} // namespace v5
} // namespace nuspell
#endif // NUSPELL_CHECKER_HXX

View File

@@ -0,0 +1 @@
clang-format -style=file -i *.[ch]xx

View File

@@ -0,0 +1,115 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dictionary.hxx"
#include "utils.hxx"
#include <fstream>
#include <iostream>
#include <stdexcept>
using namespace std;
namespace nuspell {
inline namespace v5 {
Dictionary::Dictionary(std::istream& aff, std::istream& dic)
{
if (!parse_aff_dic(aff, dic))
throw Dictionary_Loading_Error("error parsing");
}
Dictionary::Dictionary() = default;
/**
* @brief Create a dictionary from opened files as iostreams
*
* Prefer using load_from_path(). Use this if you have a specific use case,
* like when .aff and .dic are in-memory buffers istringstream.
*
* @param aff The iostream of the .aff file
* @param dic The iostream of the .dic file
* @return Dictionary object
* @throws Dictionary_Loading_Error on error
*/
auto Dictionary::load_from_aff_dic(std::istream& aff, std::istream& dic)
-> Dictionary
{
return Dictionary(aff, dic);
}
/**
* @brief Create a dictionary from files
* @param file_path_without_extension path *without* extensions (without .dic or
* .aff)
* @return Dictionary object
* @throws Dictionary_Loading_Error on error
*/
auto Dictionary::load_from_path(const std::string& file_path_without_extension)
-> Dictionary
{
auto path = file_path_without_extension;
path += ".aff";
std::ifstream aff_file(path);
if (aff_file.fail()) {
auto err = "Aff file " + path + " not found";
throw Dictionary_Loading_Error(err);
}
path.replace(path.size() - 3, 3, "dic");
std::ifstream dic_file(path);
if (dic_file.fail()) {
auto err = "Dic file " + path + " not found";
throw Dictionary_Loading_Error(err);
}
return load_from_aff_dic(aff_file, dic_file);
}
/**
* @brief Checks if a given word is correct
* @param word any word
* @return true if correct, false otherwise
*/
auto Dictionary::spell(std::string_view word) const -> bool
{
auto ok_enc = validate_utf8(word);
if (unlikely(word.size() > 360))
return false;
if (unlikely(!ok_enc))
return false;
auto word_buf = string(word);
return spell_priv(word_buf);
}
/**
* @brief Suggests correct words for a given incorrect word
* @param[in] word incorrect word
* @param[out] out this object will be populated with the suggestions
*/
auto Dictionary::suggest(std::string_view word,
std::vector<std::string>& out) const -> void
{
out.clear();
auto ok_enc = validate_utf8(word);
if (unlikely(word.size() > 360))
return;
if (unlikely(!ok_enc))
return;
suggest_priv(word, out);
}
} // namespace v5
} // namespace nuspell

View File

@@ -0,0 +1,59 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
* @brief Dictionary spelling.
*/
#ifndef NUSPELL_DICTIONARY_HXX
#define NUSPELL_DICTIONARY_HXX
#include "suggester.hxx"
namespace nuspell {
inline namespace v5 {
/**
* @brief The only important public exception
*/
class Dictionary_Loading_Error : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
/**
* @brief The only important public class
*/
class NUSPELL_EXPORT Dictionary : private Suggester {
Dictionary(std::istream& aff, std::istream& dic);
public:
Dictionary();
auto static load_from_aff_dic(std::istream& aff, std::istream& dic)
-> Dictionary;
auto static load_from_path(
const std::string& file_path_without_extension) -> Dictionary;
auto spell(std::string_view word) const -> bool;
auto suggest(std::string_view word, std::vector<std::string>& out) const
-> void;
};
} // namespace v5
} // namespace nuspell
#endif // NUSPELL_DICTIONARY_HXX

View File

@@ -0,0 +1,18 @@
#ifndef NUSPELL_EXPORT_H
#define NUSPELL_EXPORT_H
#ifdef NUSPELL_STATIC_DEFINE
# define NUSPELL_EXPORT
#elif defined(_WIN32) || defined(__CYGWIN__)
# ifdef nuspell_EXPORTS // Define this only when building Nuspell as DLL on Windows, not when using the DLL.
# define NUSPELL_EXPORT __declspec(dllexport)
# else
# define NUSPELL_EXPORT __declspec(dllimport)
# endif
#elif __GNUC__ >= 4
# define NUSPELL_EXPORT __attribute__((visibility("default")))
#else
# define NUSPELL_EXPORT
#endif
#endif /* NUSPELL_EXPORT_H */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NUSPELL_SUGGESTER_HXX
#define NUSPELL_SUGGESTER_HXX
#include "checker.hxx"
namespace nuspell {
inline namespace v5 {
struct NUSPELL_EXPORT Suggester : public Checker {
enum High_Quality_Sugs : bool {
ALL_LOW_QUALITY_SUGS = false,
HAS_HIGH_QUALITY_SUGS = true
};
auto suggest_priv(std::string_view input_word, List_Strings& out) const
-> void;
auto suggest_low(std::string& word, List_Strings& out) const
-> High_Quality_Sugs;
auto add_sug_if_correct(std::string& word, List_Strings& out) const
-> bool;
auto uppercase_suggest(const std::string& word, List_Strings& out) const
-> void;
auto rep_suggest(std::string& word, List_Strings& out) const -> void;
auto try_rep_suggestion(std::string& word, List_Strings& out) const
-> void;
auto max_attempts_for_long_alogs(std::string_view word) const -> size_t;
auto map_suggest(std::string& word, List_Strings& out) const -> void;
auto map_suggest(std::string& word, List_Strings& out, size_t i,
size_t& remaining_attempts) const -> void;
auto adjacent_swap_suggest(std::string& word, List_Strings& out) const
-> void;
auto distant_swap_suggest(std::string& word, List_Strings& out) const
-> void;
auto keyboard_suggest(std::string& word, List_Strings& out) const
-> void;
auto extra_char_suggest(std::string& word, List_Strings& out) const
-> void;
auto forgotten_char_suggest(std::string& word, List_Strings& out) const
-> void;
auto move_char_suggest(std::string& word, List_Strings& out) const
-> void;
auto bad_char_suggest(std::string& word, List_Strings& out) const
-> void;
auto doubled_two_chars_suggest(std::string& word,
List_Strings& out) const -> void;
auto two_words_suggest(const std::string& word, List_Strings& out) const
-> void;
auto ngram_suggest(const std::string& word_u8, List_Strings& out) const
-> void;
auto expand_root_word_for_ngram(Word_List::const_reference root,
std::string_view wrong,
List_Strings& expanded_list,
std::vector<bool>& cross_affix) const
-> void;
};
} // namespace v5
} // namespace nuspell
#endif // NUSPELL_SUGGESTER_HXX

View File

@@ -0,0 +1,383 @@
/* Copyright 2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NUSPELL_UNICODE_HXX
#define NUSPELL_UNICODE_HXX
#include <string>
#include <string_view>
#include <unicode/utf16.h>
#include <unicode/utf8.h>
namespace nuspell {
inline namespace v5 {
// UTF-8, work on malformed
inline constexpr auto u8_max_cp_length = U8_MAX_LENGTH;
auto inline u8_is_cp_error(int32_t cp) -> bool { return cp < 0; }
template <class Range>
auto u8_advance_cp(const Range& str, size_t& i, int32_t& cp) -> void
{
using std::size, std::data;
#if U_ICU_VERSION_MAJOR_NUM <= 60
auto s_ptr = data(str);
int32_t idx = i;
int32_t len = size(str);
U8_NEXT(s_ptr, idx, len, cp);
i = idx;
#else
auto len = size(str);
U8_NEXT(str, i, len, cp);
#endif
}
template <class Range>
auto u8_advance_index(const Range& str, size_t& i) -> void
{
using std::size;
auto len = size(str);
U8_FWD_1(str, i, len);
}
template <class Range>
auto u8_reverse_cp(const Range& str, size_t& i, int32_t& cp) -> void
{
using std::size, std::data;
auto ptr = data(str);
int32_t idx = i;
U8_PREV(ptr, 0, idx, cp);
i = idx;
}
template <class Range>
auto u8_reverse_index(const Range& str, size_t& i) -> void
{
using std::size, std::data;
auto ptr = data(str);
int32_t idx = i;
U8_BACK_1(ptr, 0, idx);
i = idx;
}
template <class Range>
auto u8_write_cp_and_advance(Range& buf, size_t& i, int32_t cp, bool& error)
-> void
{
using std::size, std::data;
#if U_ICU_VERSION_MAJOR_NUM <= 60
auto ptr = data(buf);
int32_t idx = i;
int32_t len = size(buf);
U8_APPEND(buf, idx, len, cp, error);
i = idx;
#else
auto len = size(buf);
U8_APPEND(buf, i, len, cp, error);
#endif
}
// UTF-8, valid
template <class Range>
auto valid_u8_advance_cp(const Range& str, size_t& i, char32_t& cp) -> void
{
U8_NEXT_UNSAFE(str, i, cp);
}
template <class Range>
auto valid_u8_advance_index(const Range& str, size_t& i) -> void
{
U8_FWD_1_UNSAFE(str, i);
}
template <class Range>
auto valid_u8_reverse_cp(const Range& str, size_t& i, char32_t& cp) -> void
{
U8_PREV_UNSAFE(str, i, cp);
}
template <class Range>
auto valid_u8_reverse_index(const Range& str, size_t& i) -> void
{
U8_BACK_1_UNSAFE(str, i);
}
template <class Range>
auto valid_u8_write_cp_and_advance(Range& buf, size_t& i, char32_t cp) -> void
{
U8_APPEND_UNSAFE(buf, i, cp);
}
// UTF-16, work on malformed
inline constexpr auto u16_max_cp_length = U16_MAX_LENGTH;
auto inline u16_is_cp_error(int32_t cp) -> bool { return U_IS_SURROGATE(cp); }
template <class Range>
auto u16_advance_cp(const Range& str, size_t& i, int32_t& cp) -> void
{
using std::size;
auto len = size(str);
U16_NEXT(str, i, len, cp);
}
template <class Range>
auto u16_advance_index(const Range& str, size_t& i) -> void
{
using std::size;
auto len = size(str);
U16_FWD_1(str, i, len);
}
template <class Range>
auto u16_reverse_cp(const Range& str, size_t& i, int32_t& cp) -> void
{
U16_PREV(str, 0, i, cp);
}
template <class Range>
auto u16_reverse_index(const Range& str, size_t& i) -> void
{
U16_BACK_1(str, 0, i);
}
template <class Range>
auto u16_write_cp_and_advance(Range& buf, size_t& i, int32_t cp, bool& error)
-> void
{
using std::size;
auto len = size(buf);
U16_APPEND(buf, i, len, cp, error);
}
// UTF-16, valid
template <class Range>
auto valid_u16_advance_cp(const Range& str, size_t& i, char32_t& cp) -> void
{
U16_NEXT_UNSAFE(str, i, cp);
}
template <class Range>
auto valid_u16_advance_index(const Range& str, size_t& i) -> void
{
U16_FWD_1_UNSAFE(str, i);
}
template <class Range>
auto valid_u16_reverse_cp(const Range& str, size_t& i, char32_t& cp) -> void
{
U16_PREV_UNSAFE(str, i, cp);
}
template <class Range>
auto valid_u16_reverse_index(const Range& str, size_t& i) -> void
{
U16_BACK_1_UNSAFE(str, i);
}
template <class Range>
auto valid_u16_write_cp_and_advance(Range& buf, size_t& i, char32_t cp) -> void
{
U16_APPEND_UNSAFE(buf, i, cp);
}
// higer level funcs
struct U8_CP_Pos {
size_t begin_i = 0;
size_t end_i = begin_i;
};
class U8_Encoded_CP {
char d[u8_max_cp_length];
int sz;
public:
explicit U8_Encoded_CP(std::string_view str, U8_CP_Pos pos)
: sz(pos.end_i - pos.begin_i)
{
auto i = sz;
auto j = pos.end_i;
auto max_len = 4;
do {
d[--i] = str[--j];
} while (i && --max_len);
}
U8_Encoded_CP(char32_t cp)
{
size_t z = 0;
valid_u8_write_cp_and_advance(d, z, cp);
sz = z;
}
auto size() const noexcept -> size_t { return sz; }
auto data() const noexcept -> const char* { return d; }
operator std::string_view() const noexcept
{
return std::string_view(data(), size());
}
auto copy_to(std::string& str, size_t j) const
{
auto i = sz;
j += sz;
auto max_len = 4;
do {
str[--j] = d[--i];
} while (i && --max_len);
}
};
auto inline u8_swap_adjacent_cp(std::string& str, size_t i1, size_t i2,
size_t i3) -> size_t
{
auto cp1 = U8_Encoded_CP(str, {i1, i2});
auto cp2 = U8_Encoded_CP(str, {i2, i3});
auto new_i2 = i1 + std::size(cp2);
cp1.copy_to(str, new_i2);
cp2.copy_to(str, i1);
return new_i2;
}
auto inline u8_swap_cp(std::string& str, U8_CP_Pos pos1, U8_CP_Pos pos2)
-> std::pair<size_t, size_t>
{
using std::size;
auto cp1 = U8_Encoded_CP(str, pos1);
auto cp2 = U8_Encoded_CP(str, pos2);
auto new_p1_end_i = pos1.begin_i + size(cp2);
auto new_p2_begin_i = pos2.end_i - size(cp1);
std::char_traits<char>::move(&str[new_p1_end_i], &str[pos1.end_i],
pos2.begin_i - pos1.end_i);
cp2.copy_to(str, pos1.begin_i);
cp1.copy_to(str, new_p2_begin_i);
return {new_p1_end_i, new_p2_begin_i};
}
// bellow go func without out-parametars
// UTF-8, can be malformed, no out-parametars
struct Idx_And_Next_CP {
size_t end_i;
int32_t cp;
};
struct Idx_And_Prev_CP {
size_t begin_i;
int32_t cp;
};
struct Write_CP_Idx_and_Error {
size_t end_i;
bool error;
};
template <class Range>
[[nodiscard]] auto u8_next_cp(const Range& str, size_t i) -> Idx_And_Next_CP
{
int32_t cp;
u8_advance_cp(str, i, cp);
return {i, cp};
}
template <class Range>
[[nodiscard]] auto u8_next_index(const Range& str, size_t i) -> size_t
{
u8_advance_index(str, i);
return i;
}
template <class Range>
[[nodiscard]] auto u8_prev_cp(const Range& str, size_t i) -> Idx_And_Prev_CP
{
int32_t cp;
u8_reverse_cp(str, i, cp);
return {i, cp};
}
template <class Range>
[[nodiscard]] auto u8_prev_index(const Range& str, size_t i) -> size_t
{
u8_reverse_index(str, i);
return i;
}
template <class Range>
[[nodiscard]] auto u8_write_cp(Range& buf, size_t i, int32_t cp)
-> Write_CP_Idx_and_Error
{
bool err;
u8_write_cp_and_advance(buf, i, cp, err);
return {i, err};
}
// UTF-8, valid, no out-parametars
struct Idx_And_Next_CP_Valid {
size_t end_i;
char32_t cp;
};
struct Idx_And_Prev_CP_Valid {
size_t begin_i;
char32_t cp;
};
template <class Range>
[[nodiscard]] auto valid_u8_next_cp(const Range& str, size_t i)
-> Idx_And_Next_CP_Valid
{
char32_t cp;
valid_u8_advance_cp(str, i, cp);
return {i, cp};
}
template <class Range>
[[nodiscard]] auto valid_u8_next_index(const Range& str, size_t i) -> size_t
{
valid_u8_advance_index(str, i);
return i;
}
template <class Range>
[[nodiscard]] auto valid_u8_prev_cp(const Range& str, size_t i)
-> Idx_And_Prev_CP_Valid
{
char32_t cp;
valid_u8_reverse_cp(str, i, cp);
return {i, cp};
}
template <class Range>
[[nodiscard]] auto valid_u8_prev_index(const Range& str, size_t i) -> size_t
{
valid_u8_reverse_index(str, i);
return i;
}
template <class Range>
[[nodiscard]] auto valid_u8_write_cp(Range& buf, size_t i, int32_t cp) -> size_t
{
valid_u8_write_cp_and_advance(buf, i, cp);
return i;
}
} // namespace v5
} // namespace nuspell
#endif // NUSPELL_UNICODE_HXX

View File

@@ -0,0 +1,465 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#include "utils.hxx"
#include "unicode.hxx"
#include <algorithm>
#include <limits>
#include <unicode/uchar.h>
#include <unicode/ucnv.h>
#include <unicode/unistr.h>
#include <unicode/ustring.h>
#if ' ' != 32 || '.' != 46 || 'A' != 65 || 'Z' != 90 || 'a' != 97 || 'z' != 122
#error "Basic execution character set is not ASCII"
#endif
using namespace std;
namespace nuspell {
inline namespace v5 {
template <class SepT>
static auto& split_on_any_of_low(std::string_view s, const SepT& sep,
std::vector<std::string>& out)
{
size_t i1 = 0;
size_t i2;
do {
i2 = s.find_first_of(sep, i1);
out.emplace_back(s.substr(i1, i2 - i1));
i1 = i2 + 1; // we can only add +1 if separator is single char.
// i2 gets s.npos after the last separator.
// Length of i2-i1 will always go past the end. That is defined.
} while (i2 != s.npos);
return out;
}
/**
* @internal
* @brief Splits string on single char seperator.
*
* Consecutive separators are treated as separate and will emit empty strings.
*
* @param s string to split.
* @param sep char that acts as separator to split on.
* @param out vector where separated strings are appended.
* @return @p out.
*/
auto split(std::string_view s, char sep, std::vector<std::string>& out)
-> std::vector<std::string>&
{
return split_on_any_of_low(s, sep, out);
}
/**
* @internal
* @brief Splits string on set of single char seperators.
*
* Consecutive separators are treated as separate and will emit empty strings.
*
* @param s string to split.
* @param sep seperator(s) to split on.
* @param out vector where separated strings are appended.
* @return @p out.
*/
auto split_on_any_of(std::string_view s, const char* sep,
std::vector<std::string>& out) -> std::vector<std::string>&
{
return split_on_any_of_low(s, sep, out);
}
auto utf32_to_utf8(std::u32string_view in, std::string& out) -> void
{
out.clear();
for (size_t i = 0; i != size(in); ++i) {
auto cp = in[i];
auto enc_cp = U8_Encoded_CP(cp);
out += enc_cp;
}
}
auto utf32_to_utf8(std::u32string_view in) -> std::string
{
auto out = string();
utf32_to_utf8(in, out);
return out;
}
auto valid_utf8_to_32(std::string_view in, std::u32string& out) -> void
{
out.clear();
for (size_t i = 0; i != size(in);) {
char32_t cp;
valid_u8_advance_cp(in, i, cp);
out.push_back(cp);
}
}
auto valid_utf8_to_32(std::string_view in) -> std::u32string
{
auto out = u32string();
valid_utf8_to_32(in, out);
return out;
}
auto utf8_to_16(std::string_view in) -> std::u16string
{
auto out = u16string();
utf8_to_16(in, out);
return out;
}
bool utf8_to_16(std::string_view in, std::u16string& out)
{
int32_t len;
auto err = U_ZERO_ERROR;
u_strFromUTF8(data(out), size(out), &len, data(in), size(in), &err);
out.resize(len);
if (err == U_BUFFER_OVERFLOW_ERROR) {
err = U_ZERO_ERROR;
u_strFromUTF8(data(out), size(out), &len, data(in), size(in),
&err);
}
if (U_SUCCESS(err))
return true;
out.clear();
return false;
}
bool validate_utf8(string_view s)
{
auto err = U_ZERO_ERROR;
u_strFromUTF8(nullptr, 0, nullptr, data(s), size(s), &err);
if (err == U_INVALID_CHAR_FOUND)
return false;
return err == U_BUFFER_OVERFLOW_ERROR || U_SUCCESS(err);
}
auto static is_ascii(char c) -> bool
{
return static_cast<unsigned char>(c) <= 127;
}
auto is_all_ascii(std::string_view s) -> bool
{
return all_of(begin(s), end(s), is_ascii);
}
auto static widen_latin1(char c) -> char16_t
{
return static_cast<unsigned char>(c);
}
auto latin1_to_ucs2(std::string_view s) -> std::u16string
{
u16string ret;
latin1_to_ucs2(s, ret);
return ret;
}
auto latin1_to_ucs2(std::string_view s, std::u16string& out) -> void
{
out.resize(s.size());
transform(begin(s), end(s), begin(out), widen_latin1);
}
auto static is_surrogate_pair(char16_t c) -> bool
{
return 0xD800 <= c && c <= 0xDFFF;
}
auto is_all_bmp(std::u16string_view s) -> bool
{
return none_of(begin(s), end(s), is_surrogate_pair);
}
auto to_upper_ascii(std::string& s) -> void
{
auto& char_type = use_facet<ctype<char>>(locale::classic());
char_type.toupper(begin_ptr(s), end_ptr(s));
}
auto static utf32_to_icu(u32string_view in) -> icu::UnicodeString
{
static_assert(sizeof(UChar32) == sizeof(char32_t));
return icu::UnicodeString::fromUTF32(
reinterpret_cast<const UChar32*>(in.data()), in.size());
}
auto static icu_to_utf32(const icu::UnicodeString& in, std::u32string& out)
-> bool
{
out.resize(in.length());
auto err = U_ZERO_ERROR;
auto len =
in.toUTF32(reinterpret_cast<UChar32*>(out.data()), out.size(), err);
if (U_SUCCESS(err)) {
out.erase(len);
return true;
}
out.clear();
return false;
}
auto to_upper(std::string_view in, const icu::Locale& loc) -> std::string
{
auto out = std::string();
to_upper(in, loc, out);
return out;
}
auto to_title(std::string_view in, const icu::Locale& loc) -> std::string
{
auto out = std::string();
to_title(in, loc, out);
return out;
}
auto to_lower(std::string_view in, const icu::Locale& loc) -> std::string
{
auto out = std::string();
to_lower(in, loc, out);
return out;
}
auto to_upper(string_view in, const icu::Locale& loc, string& out) -> void
{
auto sp = icu::StringPiece(data(in), size(in));
auto us = icu::UnicodeString::fromUTF8(sp);
us.toUpper(loc);
out.clear();
us.toUTF8String(out);
}
auto to_title(string_view in, const icu::Locale& loc, string& out) -> void
{
auto sp = icu::StringPiece(data(in), size(in));
auto us = icu::UnicodeString::fromUTF8(sp);
us.toTitle(nullptr, loc);
out.clear();
us.toUTF8String(out);
}
auto to_lower(u32string_view in, const icu::Locale& loc, u32string& out) -> void
{
auto us = utf32_to_icu(in);
us.toLower(loc);
icu_to_utf32(us, out);
}
auto to_lower(string_view in, const icu::Locale& loc, string& out) -> void
{
auto sp = icu::StringPiece(data(in), size(in));
auto us = icu::UnicodeString::fromUTF8(sp);
us.toLower(loc);
out.clear();
us.toUTF8String(out);
}
auto to_lower_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void
{
auto cp = valid_u8_next_cp(s, i);
auto us = icu::UnicodeString(UChar32(cp.cp));
us.toLower(loc);
auto u8_low = string();
us.toUTF8String(u8_low);
s.replace(i, cp.end_i - i, u8_low);
}
auto to_title_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void
{
auto cp = valid_u8_next_cp(s, i);
auto us = icu::UnicodeString(UChar32(cp.cp));
us.toTitle(nullptr, loc);
auto u8_title = string();
us.toUTF8String(u8_title);
s.replace(i, cp.end_i - i, u8_title);
}
/**
* @internal
* @brief Determines casing (capitalization) type for a word.
*
* Casing is sometimes referred to as capitalization.
*
* @param s word.
* @return The casing type.
*/
auto classify_casing(string_view s) -> Casing
{
size_t upper = 0;
size_t lower = 0;
for (size_t i = 0; i != size(s);) {
char32_t c;
valid_u8_advance_cp(s, i, c);
if (u_isupper(c))
upper++;
else if (u_islower(c))
lower++;
// else neutral
}
if (upper == 0) // all lowercase, maybe with some neutral
return Casing::SMALL; // most common case
auto first_cp = valid_u8_next_cp(s, 0);
auto first_capital = u_isupper(first_cp.cp);
if (first_capital && upper == 1)
return Casing::INIT_CAPITAL; // second most common
if (lower == 0)
return Casing::ALL_CAPITAL;
if (first_capital)
return Casing::PASCAL;
else
return Casing::CAMEL;
}
/**
* @internal
* @brief Check if word[i] or word[i-1] are uppercase
*
* Check if the two chars are alphabetic and at least one of them is in
* uppercase.
*
* @return true if at least one is uppercase, false otherwise.
*/
auto has_uppercase_at_compound_word_boundary(string_view word, size_t i) -> bool
{
auto cp = valid_u8_next_cp(word, i);
auto cp_prev = valid_u8_prev_cp(word, i);
if (u_isupper(cp.cp)) {
if (u_isalpha(cp_prev.cp))
return true;
}
else if (u_isupper(cp_prev.cp) && u_isalpha(cp.cp))
return true;
return false;
}
Encoding_Converter::Encoding_Converter(const char* enc)
{
auto err = UErrorCode();
cnv = ucnv_open(enc, &err);
}
Encoding_Converter::~Encoding_Converter()
{
if (cnv)
ucnv_close(cnv);
}
Encoding_Converter::Encoding_Converter(const Encoding_Converter& other)
{
auto err = UErrorCode();
cnv = ucnv_safeClone(other.cnv, nullptr, nullptr, &err);
}
auto Encoding_Converter::operator=(const Encoding_Converter& other)
-> Encoding_Converter&
{
this->~Encoding_Converter();
auto err = UErrorCode();
cnv = ucnv_safeClone(other.cnv, nullptr, nullptr, &err);
return *this;
}
auto Encoding_Converter::to_utf8(string_view in, string& out) -> bool
{
if (ucnv_getType(cnv) == UCNV_UTF8) {
if (validate_utf8(in)) {
out = in;
return true;
}
else {
out.clear();
return false;
}
}
auto err = U_ZERO_ERROR;
auto len = ucnv_toAlgorithmic(UCNV_UTF8, cnv, out.data(), out.size(),
in.data(), in.size(), &err);
out.resize(len);
if (err == U_BUFFER_OVERFLOW_ERROR) {
err = U_ZERO_ERROR;
ucnv_toAlgorithmic(UCNV_UTF8, cnv, out.data(), out.size(),
in.data(), in.size(), &err);
}
return U_SUCCESS(err);
}
auto replace_ascii_char(string& s, char from, char to) -> void
{
for (auto i = s.find(from); i != s.npos; i = s.find(from, i + 1)) {
s[i] = to;
}
}
auto erase_chars(string& s, string_view erase_chars) -> void
{
if (erase_chars.empty())
return;
for (size_t i = 0, next_i = 0; i != size(s); i = next_i) {
valid_u8_advance_index(s, next_i);
auto enc_cp = string_view(&s[i], next_i - i);
if (erase_chars.find(enc_cp) != erase_chars.npos) {
s.erase(i, next_i - i);
next_i = i;
}
}
return;
}
/**
* @internal
* @brief Tests if word is a number.
*
* Allow numbers with dot ".", dash "-" or comma "," inbetween the digits, but
* forbids double separators such as "..", "--" and ".,".
*/
auto is_number(string_view s) -> bool
{
if (s.empty())
return false;
auto it = begin(s);
if (s[0] == '-')
++it;
while (it != end(s)) {
auto next = std::find_if(
it, end(s), [](auto c) { return c < '0' || c > '9'; });
if (next == it)
return false;
if (next == end(s))
return true;
it = next;
auto c = *it;
if (c == '.' || c == ',' || c == '-')
++it;
else
return false;
}
return false;
}
auto count_appereances_of(string_view haystack, string_view needles) -> size_t
{
auto ret = size_t(0);
for (size_t i = 0, next_i = 0; i != size(haystack); i = next_i) {
valid_u8_advance_index(haystack, next_i);
auto enc_cp = string_view(&haystack[i], next_i - i);
ret += needles.find(enc_cp) != needles.npos;
}
return ret;
}
} // namespace v5
} // namespace nuspell

View File

@@ -0,0 +1,228 @@
/* Copyright 2016-2021 Dimitrij Mijoski
*
* This file is part of Nuspell.
*
* Nuspell is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Nuspell is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NUSPELL_UTILS_HXX
#define NUSPELL_UTILS_HXX
#include "nuspell_export.h"
#include <clocale>
#include <locale>
#include <string>
#include <string_view>
#include <vector>
#if !defined(_WIN32) && \
(defined(__unix__) || defined(__unix) || \
(defined(__APPLE__) && defined(__MACH__)) || defined(__HAIKU__))
#include <unistd.h>
#endif
#include <unicode/locid.h>
#ifdef __GNUC__
#define likely(expr) __builtin_expect(!!(expr), 1)
#define unlikely(expr) __builtin_expect(!!(expr), 0)
#else
#define likely(expr) (expr)
#define unlikely(expr) (expr)
#endif
struct UConverter; // unicode/ucnv.h
namespace nuspell {
inline namespace v5 {
auto split(std::string_view s, char sep, std::vector<std::string>& out)
-> std::vector<std::string>&;
NUSPELL_EXPORT auto split_on_any_of(std::string_view s, const char* sep,
std::vector<std::string>& out)
-> std::vector<std::string>&;
NUSPELL_EXPORT auto utf32_to_utf8(std::u32string_view in, std::string& out)
-> void;
NUSPELL_EXPORT auto utf32_to_utf8(std::u32string_view in) -> std::string;
auto valid_utf8_to_32(std::string_view in, std::u32string& out) -> void;
auto valid_utf8_to_32(std::string_view in) -> std::u32string;
auto utf8_to_16(std::string_view in) -> std::u16string;
auto utf8_to_16(std::string_view in, std::u16string& out) -> bool;
auto validate_utf8(std::string_view s) -> bool;
NUSPELL_EXPORT auto is_all_ascii(std::string_view s) -> bool;
NUSPELL_EXPORT auto latin1_to_ucs2(std::string_view s) -> std::u16string;
auto latin1_to_ucs2(std::string_view s, std::u16string& out) -> void;
NUSPELL_EXPORT auto is_all_bmp(std::u16string_view s) -> bool;
auto to_upper_ascii(std::string& s) -> void;
[[nodiscard]] NUSPELL_EXPORT auto to_upper(std::string_view in,
const icu::Locale& loc)
-> std::string;
[[nodiscard]] NUSPELL_EXPORT auto to_title(std::string_view in,
const icu::Locale& loc)
-> std::string;
[[nodiscard]] NUSPELL_EXPORT auto to_lower(std::string_view in,
const icu::Locale& loc)
-> std::string;
auto to_upper(std::string_view in, const icu::Locale& loc, std::string& out)
-> void;
auto to_title(std::string_view in, const icu::Locale& loc, std::string& out)
-> void;
auto to_lower(std::u32string_view in, const icu::Locale& loc,
std::u32string& out) -> void;
auto to_lower(std::string_view in, const icu::Locale& loc, std::string& out)
-> void;
auto to_lower_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void;
auto to_title_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void;
/**
* @internal
* @brief Enum that identifies the casing type of a word.
*
* Neutral characters like numbers are ignored, so "abc" and "abc123abc" are
* both classified as small.
*/
enum class Casing : char {
SMALL,
INIT_CAPITAL,
ALL_CAPITAL,
CAMEL /**< @internal camelCase i.e. mixed case with first small */,
PASCAL /**< @internal PascalCase i.e. mixed case with first capital */
};
NUSPELL_EXPORT auto classify_casing(std::string_view s) -> Casing;
auto has_uppercase_at_compound_word_boundary(std::string_view word, size_t i)
-> bool;
class Encoding_Converter {
UConverter* cnv = nullptr;
public:
Encoding_Converter() = default;
explicit Encoding_Converter(const char* enc);
explicit Encoding_Converter(const std::string& enc)
: Encoding_Converter(enc.c_str())
{
}
~Encoding_Converter();
Encoding_Converter(const Encoding_Converter& other);
Encoding_Converter(Encoding_Converter&& other) noexcept
{
cnv = other.cnv;
cnv = nullptr;
}
auto operator=(const Encoding_Converter& other) -> Encoding_Converter&;
auto operator=(Encoding_Converter&& other) noexcept
-> Encoding_Converter&
{
std::swap(cnv, other.cnv);
return *this;
}
auto to_utf8(std::string_view in, std::string& out) -> bool;
auto valid() -> bool { return cnv != nullptr; }
};
//#if _POSIX_VERSION >= 200809L
#if defined(_POSIX_VERSION) && !defined(__NetBSD__) && !defined(__HAIKU__)
class Setlocale_To_C_In_Scope {
locale_t old_loc = nullptr;
public:
Setlocale_To_C_In_Scope()
: old_loc{uselocale(newlocale(0, "C", nullptr))}
{
}
~Setlocale_To_C_In_Scope()
{
auto new_loc = uselocale(old_loc);
if (new_loc != old_loc)
freelocale(new_loc);
}
Setlocale_To_C_In_Scope(const Setlocale_To_C_In_Scope&) = delete;
};
#else
class Setlocale_To_C_In_Scope {
std::string old_name;
#ifdef _WIN32
int old_per_thread;
#endif
public:
Setlocale_To_C_In_Scope() : old_name(setlocale(LC_ALL, nullptr))
{
#ifdef _WIN32
old_per_thread = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
#endif
auto x = setlocale(LC_ALL, "C");
if (!x)
old_name.clear();
}
~Setlocale_To_C_In_Scope()
{
#ifdef _WIN32
_configthreadlocale(old_per_thread);
if (old_per_thread == _ENABLE_PER_THREAD_LOCALE)
#endif
{
if (!old_name.empty())
setlocale(LC_ALL, old_name.c_str());
}
}
Setlocale_To_C_In_Scope(const Setlocale_To_C_In_Scope&) = delete;
};
#endif
auto replace_ascii_char(std::string& s, char from, char to) -> void;
auto erase_chars(std::string& s, std::string_view erase_chars) -> void;
NUSPELL_EXPORT auto is_number(std::string_view s) -> bool;
auto count_appereances_of(std::string_view haystack, std::string_view needles)
-> size_t;
auto inline begins_with(std::string_view haystack, std::string_view needle)
-> bool
{
return haystack.compare(0, needle.size(), needle) == 0;
}
auto inline ends_with(std::string_view haystack, std::string_view needle)
-> bool
{
return haystack.size() >= needle.size() &&
haystack.compare(haystack.size() - needle.size(), needle.size(),
needle) == 0;
}
template <class T>
auto begin_ptr(T& x)
{
return x.data();
}
template <class T>
auto end_ptr(T& x)
{
return x.data() + x.size();
}
} // namespace v5
} // namespace nuspell
#endif // NUSPELL_UTILS_HXX

View File

@@ -0,0 +1,12 @@
add_library(
# Name
utils
# Headers
jni_utils.h
log.h
# Sources
jni_utils.cpp
log.cpp
)

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "jni_utils.h"
#include "log.h"
std::string utils::j2std_string(JNIEnv *env, jobject jStr) {
auto cStr = reinterpret_cast<const char *>(env->GetDirectBufferAddress(jStr));
auto size = env->GetDirectBufferCapacity(jStr);
std::string stdStr(cStr, size);
log_debug("spell j2s", stdStr);
return stdStr;
}
jobject utils::std2j_string(JNIEnv *env, const std::string& stdStr) {
log_debug("spell s2j", stdStr);
size_t byteCount = stdStr.length();
auto cStr = stdStr.c_str();
auto buffer = env->NewDirectByteBuffer((void *) cStr, byteCount);
return buffer;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 Patrick Goldinger
* 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.
@@ -14,25 +14,17 @@
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.text.gestures
#ifndef FLORISBOARD_JNI_UTILS_H
#define FLORISBOARD_JNI_UTILS_H
/**
* Enum for declaring the distance thresholds for swipe gestures.
*/
enum class DistanceThreshold {
VERY_SHORT,
SHORT,
NORMAL,
LONG,
VERY_LONG;
#include <jni.h>
#include <string>
companion object {
fun fromString(string: String): DistanceThreshold {
return valueOf(string.uppercase())
}
}
namespace utils {
override fun toString(): String {
return super.toString().lowercase()
}
}
std::string j2std_string(JNIEnv *env, jobject jStr);
jobject std2j_string(JNIEnv *env, const std::string& in);
} // namespace utils
#endif // FLORISBOARD_JNI_UTILS_H

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android/log.h>
#include <unistd.h>
#include "log.h"
void utils::log_debug(const std::string &tag, const std::string &msg) {
__android_log_print(ANDROID_LOG_DEBUG, tag.c_str(), "%s", msg.c_str());
}
void utils::log_info(const std::string &tag, const std::string &msg) {
__android_log_print(ANDROID_LOG_INFO, tag.c_str(), "%s", msg.c_str());
}
void utils::log_warning(const std::string &tag, const std::string &msg) {
__android_log_print(ANDROID_LOG_WARN, tag.c_str(), "%s", msg.c_str());
}
void utils::log_error(const std::string &tag, const std::string &msg) {
__android_log_print(ANDROID_LOG_ERROR, tag.c_str(), "%s", msg.c_str());
}
void utils::log_wtf(const std::string &tag, const std::string &msg) {
__android_log_print(ANDROID_LOG_FATAL, tag.c_str(), "%s", msg.c_str());
}
/**
* Code below taken from here:
* https://codelab.wordpress.com/2014/11/03/how-to-use-standard-output-streams-for-logging-in-android-apps/
*/
static int pfd[2];
static pthread_t thr;
static const char *tag = "myapp";
static bool already_started = false;
static void *thread_func(void*) {
ssize_t rdsz;
char buf[2048];
while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
if (buf[rdsz - 1] == '\n') --rdsz;
buf[rdsz] = 0; /* add null-terminator */
__android_log_write(ANDROID_LOG_DEBUG, tag, buf);
}
return nullptr;
}
int utils::start_stdout_stderr_logger(const char *app_name) {
if (already_started) return 0;
already_started = true;
tag = app_name;
/* make stdout line-buffered and stderr unbuffered */
setvbuf(stdout, nullptr, _IOLBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
/* create the pipe and redirect stdout and stderr */
pipe(pfd);
dup2(pfd[1], 1);
dup2(pfd[1], 2);
/* spawn the logging thread */
if (pthread_create(&thr, nullptr, thread_func, nullptr) != 0) {
return -1;
}
pthread_detach(thr);
return 0;
}

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
#ifndef FLORISBOARD_LOG_H
#define FLORISBOARD_LOG_H
#include <string>
namespace utils {
void log_debug(const std::string& tag, const std::string& msg);
void log_info(const std::string& tag, const std::string& msg);
void log_warning(const std::string& tag, const std::string& msg);
void log_error(const std::string& tag, const std::string& msg);
void log_wtf(const std::string& tag, const std::string& msg);
int start_stdout_stderr_logger(const char *app_name);
} // namespace utils
#endif // FLORISBOARD_LOG_H

1
app/src/main/icu4c Submodule

Submodule app/src/main/icu4c added at c434a473c5

View File

@@ -16,33 +16,104 @@
package dev.patrickgold.florisboard
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
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.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.core.Preferences
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.spelling.SpellingManager
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import timber.log.Timber
import dev.patrickgold.florisboard.res.AssetManager
import dev.patrickgold.florisboard.res.FlorisRef
import dev.patrickgold.florisboard.util.AndroidVersion
import dev.patrickgold.jetpref.datastore.JetPrefApplication
import java.io.File
import kotlin.Exception
@Suppress("unused")
class FlorisApplication : Application(), CoroutineScope by MainScope() {
class FlorisApplication : JetPrefApplication() {
companion object {
private const val ICU_DATA_ASSET_PATH = "icu/icudt69l.dat"
private external fun nativeInitICUData(path: NativeStr): Int
init {
try {
System.loadLibrary("florisboard-native")
} catch (_: Exception) {
}
}
}
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
try {
Flog.install(
applicationContext = this,
isFloggingEnabled = BuildConfig.DEBUG,
flogTopics = LogTopic.ALL,
flogLevels = Flog.LEVEL_ALL,
flogOutputs = Flog.OUTPUT_CONSOLE
)
initICU()
CrashUtility.install(this)
val prefs by florisPreferenceModel()
val oldPrefs = Preferences.initDefault(this)
val assetManager = AssetManager.init(this)
SpellingManager.init(this, FlorisRef.assets("ime/spelling/config.json"))
SubtypeManager.init(this)
DictionaryManager.init(this)
ThemeManager.init(this, assetManager)
oldPrefs.initDefaultPreferences()
} catch (e: Exception) {
CrashUtility.stageException(e)
return
}
Flog.install(
applicationContext = this,
isFloggingEnabled = BuildConfig.DEBUG,
flogTopics = LogTopic.ALL,
flogLevels = Flog.LEVEL_ALL,
flogOutputs = Flog.OUTPUT_CONSOLE
)
/*Register a receiver so user config can be applied once device protracted storage is available*/
if (!UserManagerCompat.isUserUnlocked(this) && AndroidVersion.ATLEAST_N) {
registerReceiver(BootComplete(), IntentFilter(Intent.ACTION_USER_UNLOCKED))
}
}
fun initICU(): Boolean {
try {
val context = if (AndroidVersion.ATLEAST_N) {
createDeviceProtectedStorageContext()
} else {
this
}
val androidAssetManager = context.assets ?: return false
val dstDataFile = File(context.cacheDir, "icudt.dat")
dstDataFile.outputStream().use { os ->
androidAssetManager.open(ICU_DATA_ASSET_PATH).use { it.copyTo(os) }
}
val status = nativeInitICUData(dstDataFile.absolutePath.toNativeStr())
return if (status != 0) {
flogError { "Native ICU data initializing failed with error code $status!" }
false
} else {
flogInfo { "Successfully loaded ICU data!" }
true
}
} catch (e: Exception) {
flogError { e.toString() }
return false
}
}
fun init() {
CrashUtility.install(this)
val prefs = Preferences.initDefault(this)
val assetManager = AssetManager.init(this)
@@ -51,4 +122,18 @@ class FlorisApplication : Application(), CoroutineScope by MainScope() {
ThemeManager.init(this, assetManager)
prefs.initDefaultPreferences()
}
private class BootComplete : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(Intent.ACTION_USER_UNLOCKED == intent?.action){
try {
(context as FlorisApplication).unregisterReceiver(this)
context.init()
} catch (e : Exception) {
e.fillInStackTrace()
}
}
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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
import android.service.textservice.SpellCheckerService
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextInfo
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.debug.LogTopic
import dev.patrickgold.florisboard.debug.flogInfo
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.spelling.SpellingService
import kotlinx.coroutines.runBlocking
class FlorisSpellCheckerService : SpellCheckerService() {
companion object {
private const val USE_FLORIS_SUBTYPES_LOCALE: String = "zz"
}
private val dictionaryManager get() = DictionaryManager.default()
private val spellingService: SpellingService = SpellingService.globalInstance()
private val subtypeManager get() = SubtypeManager.default()
override fun onCreate() {
flogInfo(LogTopic.SPELL_EVENTS)
super.onCreate()
dictionaryManager.loadUserDictionariesIfNecessary()
}
override fun createSession(): Session {
flogInfo(LogTopic.SPELL_EVENTS)
return FlorisSpellCheckerSession()
}
override fun onDestroy() {
flogInfo(LogTopic.SPELL_EVENTS)
super.onDestroy()
}
private inner class FlorisSpellCheckerSession : Session() {
private var cachedSpellingLocale: FlorisLocale? = null
override fun onCreate() {
flogInfo(LogTopic.SPELL_EVENTS) { "Session locale: $locale" }
setupSpellingIfNecessary()
}
private fun setupSpellingIfNecessary() {
val evaluatedLocale = when (locale) {
null -> Subtype.DEFAULT.locale
USE_FLORIS_SUBTYPES_LOCALE -> (subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT).locale
else -> FlorisLocale.from(locale)
}
if (evaluatedLocale != cachedSpellingLocale) {
cachedSpellingLocale = evaluatedLocale
}
}
private fun spellMultiple(
spellingLocale: FlorisLocale,
textInfos: Array<out TextInfo>,
suggestionsLimit: Int
): Array<SuggestionsInfo> = runBlocking {
val retInfos = Array(textInfos.size) { n ->
val word = textInfos[n].text ?: ""
spellingService.spellAsync(spellingLocale, word, suggestionsLimit)
}
Array(textInfos.size) { n ->
retInfos[n].await().apply {
setCookieAndSequence(textInfos[n].cookie, textInfos[n].sequence)
}
}
}
override fun onGetSuggestions(textInfo: TextInfo?, suggestionsLimit: Int): SuggestionsInfo {
flogInfo(LogTopic.SPELL_EVENTS) { "text=${textInfo?.text}, limit=$suggestionsLimit" }
textInfo?.text ?: return SpellingService.emptySuggestionsInfo()
setupSpellingIfNecessary()
val spellingLocale = cachedSpellingLocale ?: return SpellingService.emptySuggestionsInfo()
return spellingService.spell(spellingLocale, textInfo.text, suggestionsLimit)
}
override fun onGetSuggestionsMultiple(
textInfos: Array<out TextInfo>?,
suggestionsLimit: Int,
sequentialWords: Boolean
): Array<SuggestionsInfo> {
flogInfo(LogTopic.SPELL_EVENTS)
textInfos ?: return emptyArray()
setupSpellingIfNecessary()
val spellingLocale = cachedSpellingLocale ?: return emptyArray()
return spellMultiple(spellingLocale, textInfos, suggestionsLimit)
}
override fun onGetSentenceSuggestionsMultiple(
textInfos: Array<out TextInfo>?,
suggestionsLimit: Int
): Array<SentenceSuggestionsInfo> {
flogInfo(LogTopic.SPELL_EVENTS)
// TODO: implement custom solution here instead of calling the default implementation
return super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit)
}
override fun onCancel() {
flogInfo(LogTopic.SPELL_EVENTS)
super.onCancel()
}
override fun onClose() {
flogInfo(LogTopic.SPELL_EVENTS)
super.onClose()
}
}
}

View File

@@ -0,0 +1,171 @@
/*
* 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
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.PreviewKeyboardField
import dev.patrickgold.florisboard.app.ui.components.SystemUi
import dev.patrickgold.florisboard.app.ui.res.ProvideLocalizedResources
import dev.patrickgold.florisboard.app.ui.theme.FlorisAppTheme
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.common.InputMethodUtils
import dev.patrickgold.florisboard.common.SystemSettingsObserver
import dev.patrickgold.florisboard.util.AndroidVersion
import dev.patrickgold.florisboard.util.PackageManagerUtils
import dev.patrickgold.jetpref.datastore.model.observeAsState
enum class AppTheme(val id: String) {
AUTO("auto"),
LIGHT("light"),
DARK("dark"),
AMOLED_DARK("amoled_dark"),
}
val LocalNavController = staticCompositionLocalOf<NavController> {
error("LocalNavController not initialized")
}
val LocalIsFlorisBoardEnabled = compositionLocalOf { false }
val LocalIsFlorisBoardSelected = compositionLocalOf { false }
class FlorisAppActivity : ComponentActivity() {
private val prefs by florisPreferenceModel()
private var appTheme by mutableStateOf(AppTheme.AUTO)
private var showAppIcon = true
private var resourcesContext by mutableStateOf(this as Context)
private var isFlorisBoardEnabled by mutableStateOf(false)
private var isFlorisBoardSelected by mutableStateOf(false)
private val isFlorisBoardEnabledObserver by lazy {
SystemSettingsObserver(this) {
isFlorisBoardEnabled = InputMethodUtils.checkIsFlorisboardEnabled(this@FlorisAppActivity)
}
}
private val isFlorisBoardSelectedObserver by lazy {
SystemSettingsObserver(this) {
isFlorisBoardSelected = InputMethodUtils.checkIsFlorisboardSelected(this@FlorisAppActivity)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
InputMethodUtils.startObserveIsFlorisBoardEnabled(this, isFlorisBoardEnabledObserver)
InputMethodUtils.startObserveIsFlorisBoardSelected(this, isFlorisBoardSelectedObserver)
prefs.advanced.settingsTheme.observe(this) {
appTheme = it
}
prefs.advanced.settingsLanguage.observe(this) {
val config = Configuration(resources.configuration)
config.setLocale(if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it))
resourcesContext = createConfigurationContext(config)
}
if (AndroidVersion.ATMOST_P) {
prefs.advanced.showAppIcon.observe(this) {
showAppIcon = it
}
}
WindowCompat.setDecorFitsSystemWindows(window, true)
setContent {
ProvideLocalizedResources(resourcesContext) {
FlorisAppTheme(theme = appTheme) {
Surface(color = MaterialTheme.colors.background) {
SystemUi()
AppContent()
}
}
}
}
}
override fun onPause() {
super.onPause()
// App icon visibility control was restricted in Android 10.
// See https://developer.android.com/reference/android/content/pm/LauncherApps#getActivityList(java.lang.String,%20android.os.UserHandle)
if (AndroidVersion.ATMOST_P) {
if (showAppIcon) {
PackageManagerUtils.showAppIcon(this)
} else {
PackageManagerUtils.hideAppIcon(this)
}
} else {
PackageManagerUtils.showAppIcon(this)
}
}
override fun onDestroy() {
super.onDestroy()
InputMethodUtils.stopObserveIsFlorisBoardEnabled(this, isFlorisBoardEnabledObserver)
InputMethodUtils.stopObserveIsFlorisBoardSelected(this, isFlorisBoardSelectedObserver)
}
private fun Configuration.setLocale(locale: FlorisLocale) {
return this.setLocale(locale.base)
}
@Composable
private fun AppContent() {
val isImeSetUp by prefs.internal.isImeSetUp.observeAsState()
val navController = rememberNavController()
CompositionLocalProvider(
LocalNavController provides navController,
LocalIsFlorisBoardEnabled provides isFlorisBoardEnabled,
LocalIsFlorisBoardSelected provides isFlorisBoardSelected,
) {
Column {
Routes.AppNavHost(
modifier = Modifier.weight(1.0f),
navController = navController,
startDestination = if (isImeSetUp) { Routes.Settings.Home } else { Routes.Setup.Home },
)
if (isImeSetUp) {
PreviewKeyboardField()
}
}
}
SideEffect {
navController.setOnBackPressedDispatcher(this.onBackPressedDispatcher)
}
}
}

View File

@@ -0,0 +1,370 @@
/*
* 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.prefs
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
import dev.patrickgold.jetpref.datastore.preferenceModel
fun florisPreferenceModel() = preferenceModel(AppPrefs::class, ::AppPrefs)
class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val advanced = Advanced()
inner class Advanced {
val settingsTheme = enum(
key = "advanced__settings_theme",
default = AppTheme.AUTO,
)
val settingsLanguage = string(
key = "advanced__settings_language",
default = "auto",
)
val showAppIcon = boolean(
key = "advanced__show_app_icon",
default = true,
)
val forcePrivateMode = boolean(
key = "advanced__force_private_mode",
default = false,
)
}
val clipboard = Clipboard()
inner class Clipboard {
val useInternalClipboard = boolean(
key = "clipboard__use_internal_clipboard",
default = false,
)
val syncToFloris = boolean(
key = "clipboard__sync_to_floris",
default = true,
)
val syncToSystem = boolean(
key = "clipboard__sync_to_system",
default = false,
)
val enableHistory = boolean(
key = "clipboard__enable_history",
default = false,
)
val cleanUpOld = boolean(
key = "clipboard__clean_up_old",
default = false,
)
val cleanUpAfter = int(
key = "clipboard__clean_up_after",
default = 20,
)
val limitHistorySize = boolean(
key = "clipboard__limit_history_size",
default = true,
)
val maxHistorySize = int(
key = "clipboard__max_history_size",
default = 20,
)
}
val devtools = Devtools()
inner class Devtools {
val enabled = boolean(
key = "devtools__enabled",
default = false,
)
val showHeapMemoryStats = boolean(
key = "devtools__show_heap_memory_stats",
default = false,
)
val overrideWordSuggestionsMinHeapRestriction = boolean(
key = "devtools__override_word_suggestions_min_heap_restriction",
default = false,
)
}
val gestures = Gestures()
inner class Gestures {
val swipeUp = enum(
key = "gestures__swipe_up",
default = SwipeAction.SHIFT,
)
val swipeDown = enum(
key = "gestures__swipe_down",
default = SwipeAction.HIDE_KEYBOARD,
)
val swipeLeft = enum(
key = "gestures__swipe_left",
default = SwipeAction.SWITCH_TO_NEXT_SUBTYPE,
)
val swipeRight = enum(
key = "gestures__swipe_right",
default = SwipeAction.SWITCH_TO_PREV_SUBTYPE,
)
val spaceBarSwipeUp = enum(
key = "gestures__space_bar_swipe_up",
default = SwipeAction.SWITCH_TO_CLIPBOARD_CONTEXT,
)
val spaceBarSwipeLeft = enum(
key = "gestures__space_bar_swipe_left",
default = SwipeAction.MOVE_CURSOR_LEFT,
)
val spaceBarSwipeRight = enum(
key = "gestures__space_bar_swipe_right",
default = SwipeAction.MOVE_CURSOR_RIGHT,
)
val spaceBarLongPress = enum(
key = "gestures__space_bar_long_press",
default = SwipeAction.SHOW_INPUT_METHOD_PICKER,
)
val deleteKeySwipeLeft = enum(
key = "gestures__delete_key_swipe_left",
default = SwipeAction.DELETE_CHARACTERS_PRECISELY,
)
val swipeDistanceThreshold = int(
key = "gestures__swipe_distance_threshold",
default = 32,
)
val swipeVelocityThreshold = int(
key = "gestures__swipe_velocity_threshold",
default = 1900,
)
}
val glide = Glide()
inner class Glide {
val enabled = boolean(
key = "glide__enabled",
default = false,
)
val showTrail = boolean(
key = "glide__show_trail",
default = true,
)
val trailDuration = int(
key = "glide__trail_fade_duration",
default = 200,
)
val showPreview = boolean(
key = "glide__show_preview",
default = true,
)
val previewRefreshDelay = int(
key = "glide__preview_refresh_delay",
default = 150,
)
}
val inputFeedback = InputFeedback()
inner class InputFeedback {
val audioEnabled = boolean(
key = "input_feedback__audio_enabled",
default = true,
)
val audioIgnoreSystemSettings = boolean(
key = "input_feedback__audio_ignore_system_settings",
default = false,
)
val audioVolume = int(
key = "input_feedback__audio_volume",
default = 50,
)
val audioFeatKeyPress = boolean(
key = "input_feedback__audio_feat_key_press",
default = true,
)
val audioFeatKeyLongPress = boolean(
key = "input_feedback__audio_feat_key_long_press",
default = false,
)
val audioFeatKeyRepeatedAction = boolean(
key = "input_feedback__audio_feat_key_repeated_action",
default = false,
)
val audioFeatGestureSwipe = boolean(
key = "input_feedback__audio_feat_gesture_swipe",
default = false,
)
val audioFeatGestureMovingSwipe = boolean(
key = "input_feedback__audio_feat_gesture_moving_swipe",
default = false,
)
val hapticEnabled = boolean(
key = "input_feedback__haptic_enabled",
default = true,
)
val hapticIgnoreSystemSettings = boolean(
key = "input_feedback__haptic_ignore_system_settings",
default = false,
)
val hapticUseVibrator = boolean(
key = "input_feedback__haptic_use_vibrator",
default = true,
)
val hapticVibrationDuration = int(
key = "input_feedback__haptic_vibration_duration",
default = 50,
)
val hapticVibrationStrength = int(
key = "input_feedback__haptic_vibration_strength",
default = 50,
)
val hapticFeatKeyPress = boolean(
key = "input_feedback__haptic_feat_key_press",
default = true,
)
val hapticFeatKeyLongPress = boolean(
key = "input_feedback__haptic_feat_key_long_press",
default = false,
)
val hapticFeatKeyRepeatedAction = boolean(
key = "input_feedback__haptic_feat_key_repeated_action",
default = true,
)
val hapticFeatGestureSwipe = boolean(
key = "input_feedback__haptic_feat_gesture_swipe",
default = false,
)
val hapticFeatGestureMovingSwipe = boolean(
key = "input_feedback__haptic_feat_gesture_moving_swipe",
default = true,
)
}
val internal = Internal()
inner class Internal {
val homeIsBetaToolboxCollapsed = boolean(
key = "internal__home_is_beta_toolbox_collapsed",
default = false,
)
val isImeSetUp = boolean(
key = "internal__is_ime_set_up",
default = false,
)
}
val keyboard = Keyboard()
inner class Keyboard {
val numberRow = boolean(
key = "keyboard__number_row",
default = false,
)
val hintedNumberRowEnabled = boolean(
key = "keyboard__hinted_number_row_enabled",
default = true,
)
val hintedNumberRowMode = enum(
key = "keyboard__hinted_number_row_mode",
default = KeyHintMode.SMART_PRIORITY,
)
val hintedSymbolsEnabled = boolean(
key = "keyboard__hinted_symbols_enabled",
default = true,
)
val hintedSymbolsMode = enum(
key = "keyboard__hinted_symbols_mode",
default = KeyHintMode.SMART_PRIORITY,
)
val utilityKeyEnabled = boolean(
key = "keyboard__utility_key_enabled",
default = true,
)
val utilityKeyAction = enum(
key = "keyboard__utility_key_action",
default = UtilityKeyAction.DYNAMIC_SWITCH_LANGUAGE_EMOJIS,
)
val fontSizeMultiplierPortrait = int(
key = "keyboard__font_size_multiplier_portrait",
default = 100,
)
val fontSizeMultiplierLandscape = int(
key = "keyboard__font_size_multiplier_landscape",
default = 100,
)
val oneHandedMode = string(
key = "keyboard__one_handed_mode",
default = OneHandedMode.OFF,
)
val oneHandedModeScaleFactor = int(
key = "keyboard__one_handed_mode_scale_factor",
default = 87,
)
val landscapeInputUiMode = enum(
key = "keyboard__landscape_input_ui_mode",
default = LandscapeInputUiMode.DYNAMICALLY_SHOW,
)
val heightFactorPortrait = int(
key = "keyboard__height_factor_portrait",
default = 100,
)
val heightFactorLandscape = int(
key = "keyboard__height_factor_landscape",
default = 100,
)
val keySpacingVertical = float(
key = "keyboard__key_spacing_vertical",
default = 5.0f,
)
val keySpacingHorizontal = float(
key = "keyboard__key_spacing_horizontal",
default = 2.0f,
)
val bottomOffsetPortrait = int(
key = "keyboard__bottom_offset_portrait",
default = 0,
)
val bottomOffsetLandscape = int(
key = "keyboard__bottom_offset_landscape",
default = 0,
)
val popupEnabled = boolean(
key = "keyboard__popup_enabled",
default = true,
)
val mergeHintPopupsEnabled = boolean(
key = "keyboard__merge_hint_popups_enabled",
default = false,
)
val longPressDelay = int(
key = "keyboard__long_press_delay",
default = 300,
)
val spaceBarSwitchesToCharacters = boolean(
key = "keyboard__space_bar_switches_to_characters",
default = true,
)
fun keyHintConfiguration(): KeyHintConfiguration {
return KeyHintConfiguration(
numberHintMode = when {
hintedNumberRowEnabled.get() -> hintedNumberRowMode.get()
else -> KeyHintMode.DISABLED
},
symbolHintMode = when {
hintedSymbolsEnabled.get() -> hintedSymbolsMode.get()
else -> KeyHintMode.DISABLED
},
mergeHintPopups = mergeHintPopupsEnabled.get(),
)
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import dev.patrickgold.florisboard.app.ui.settings.HomeScreen
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.clipboard.ClipboardScreen
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.setup.SetupScreen
object Routes {
object Settings {
const val Home = "settings"
const val Keyboard = "settings/keyboard"
const val InputFeedback = "settings/keyboard/input-feedback"
const val Gestures = "settings/gestures"
const val Clipboard = "settings/clipboard"
const val Advanced = "settings/advanced"
const val About = "settings/about"
const val ProjectLicense = "settings/about/project-license"
const val ThirdPartyLicenses = "settings/about/third-party-licenses"
}
object Setup {
const val Home = "setup"
}
@Composable
fun AppNavHost(
modifier: Modifier,
navController: NavHostController,
startDestination: String,
) {
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination,
) {
composable(Settings.Home) { HomeScreen() }
composable(Settings.Keyboard) { KeyboardScreen() }
composable(Settings.InputFeedback) { InputFeedbackScreen() }
composable(Settings.Gestures) { GesturesScreen() }
composable(Settings.Clipboard) { ClipboardScreen() }
composable(Settings.Advanced) { AdvancedScreen() }
composable(Settings.About) { AboutScreen() }
composable(Settings.ProjectLicense) { ProjectLicenseScreen() }
composable(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
composable(Setup.Home) { SetupScreen() }
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.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.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
@Composable
fun FlorisAppBar(
title: String,
backArrowVisible: Boolean,
actions: @Composable RowScope.() -> Unit = { }
) {
TopAppBar(
navigationIcon = backNavBtn(backArrowVisible),
title = { Text(text = title) },
actions = actions,
backgroundColor = Color.Transparent,
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

@@ -0,0 +1,49 @@
/*
* 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.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import dev.patrickgold.florisboard.app.prefs.AppPrefs
import dev.patrickgold.florisboard.app.prefs.florisPreferenceModel
import dev.patrickgold.jetpref.ui.compose.PreferenceLayout
import dev.patrickgold.jetpref.ui.compose.PreferenceUiContent
@Composable
fun FlorisScreen(
title: String,
backArrowVisible: Boolean = true,
scrollable: Boolean = true,
bottomBar: @Composable () -> Unit = { },
actions: @Composable RowScope.() -> Unit = { },
content: PreferenceUiContent<AppPrefs>,
) {
Scaffold(
topBar = { FlorisAppBar(title, backArrowVisible, actions) },
bottomBar = bottomBar,
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
PreferenceLayout(florisPreferenceModel(), scrollable = scrollable) {
content(this)
}
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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 android.widget.Toast
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TextField
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.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
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.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.common.InputMethodUtils
@Composable
fun PreviewKeyboardField(
modifier: Modifier = Modifier,
hint: String = stringRes(R.string.settings__preview_keyboard),
) {
val context = LocalContext.current
var hasFocus by remember { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
var text by remember { mutableStateOf(TextFieldValue("")) }
TextField(
modifier = modifier
.height(56.dp)
.fillMaxWidth()
.focusRequester(focusRequester)
.onFocusEvent { hasFocus = it.isFocused },
value = text,
onValueChange = { text = it },
placeholder = { Text(hint) },
trailingIcon = {
Row {
IconButton(onClick = {
if (hasFocus) focusManager.clearFocus() else focusRequester.requestFocus()
}) {
Icon(
painter = painterResource(id = when {
hasFocus -> R.drawable.ic_keyboard_arrow_down
else -> R.drawable.ic_keyboard_arrow_up
}),
contentDescription = null,
)
}
IconButton(onClick = {
if (!InputMethodUtils.showImePicker(context)) {
Toast.makeText(
context, "Error: InputMethodManager service not available!", Toast.LENGTH_SHORT
).show()
}
}) {
Icon(
painter = painterResource(id = R.drawable.ic_keyboard),
contentDescription = null,
)
}
}
},
keyboardOptions = KeyboardOptions(autoCorrect = false),
singleLine = true,
shape = RectangleShape,
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
),
)
}

View File

@@ -0,0 +1,44 @@
/*
* 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.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import dev.patrickgold.florisboard.util.AndroidVersion
@Composable
fun SystemUi() {
val systemUiController = rememberSystemUiController()
val useDarkIcons = MaterialTheme.colors.isLight
val backgroundColor = MaterialTheme.colors.background
SideEffect {
systemUiController.setStatusBarColor(
color = backgroundColor,
darkIcons = useDarkIcons,
)
if (AndroidVersion.ATLEAST_O) {
systemUiController.setNavigationBarColor(
color = backgroundColor,
darkIcons = useDarkIcons,
navigationBarContrastEnforced = true,
)
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.res
import android.content.Context
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.res.stringResource
import dev.patrickgold.florisboard.R
private val LocalResourcesContext = staticCompositionLocalOf<Context> {
error("resources context not initialized!!")
}
private val LocalAppNameString = staticCompositionLocalOf {
"FlorisBoard"
}
@Composable
fun ProvideLocalizedResources(
resourcesContext: Context,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalResourcesContext provides resourcesContext,
LocalAppNameString provides stringResource(R.string.floris_app_name),
) {
content()
}
}
@Composable
fun stringRes(
@StringRes id: Int,
vararg args: Pair<String, Any?>,
): String {
val string = LocalResourcesContext.current.resources
.getString(id)
return formatString(string, args)
}
@Composable
fun pluralsRes(
@PluralsRes id: Int,
quantity: Int,
vararg args: Pair<String, Any?>,
): String {
val string = LocalResourcesContext.current.resources
.getQuantityString(id, quantity)
return formatString(string, args)
}
@Composable
private fun formatString(
string: String,
args: Array<out Pair<String, Any?>>,
): String {
var ret = string
ret = ret.replace("{app_name}", LocalAppNameString.current)
for (arg in args) {
ret = ret.replace("{${arg.first}}", arg.second.toString())
}
return ret
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings
import android.content.Intent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.common.launchActivity
import dev.patrickgold.florisboard.common.launchUrl
import dev.patrickgold.florisboard.oldsettings.SettingsMainActivity
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.ui.compose.Preference
@Composable
fun HomeScreen() = FlorisScreen(
title = stringRes(R.string.settings__home__title),
backArrowVisible = false,
) {
val navController = LocalNavController.current
val context = LocalContext.current
val isCollapsed by prefs.internal.homeIsBetaToolboxCollapsed.observeAsState(true)
Card(modifier = Modifier.padding(16.dp)) {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Beta-access to new Settings UI",
style = MaterialTheme.typography.subtitle1,
fontWeight = FontWeight.Bold,
)
Spacer(modifier = Modifier.weight(1.0f))
IconButton(onClick = { this@FlorisScreen.prefs.internal.homeIsBetaToolboxCollapsed.set(!isCollapsed) }) {
Icon(
painter = painterResource(if (isCollapsed) {
R.drawable.ic_keyboard_arrow_down
} else {
R.drawable.ic_keyboard_arrow_up
}),
contentDescription = null,
)
}
}
if (!isCollapsed) {
Text("You are currently testing out the new Settings of FlorisBoard.\n")
Text("Especially in the first few beta releases the Settings are completely split up and some UI controls (especially sliders!!) behave buggy. With each beta release preferences will be ported until everything is re-written, then the UI and the code base will get polished.\n")
Text("If you want to give feedback on the development of the new prefs, please do so in below linked feedback thread:\n")
Button(onClick = {
launchUrl(context, "https://github.com/florisboard/florisboard/discussions/1235")
}) {
Text("Open Feedback Thread")
}
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = {
launchActivity(context, SettingsMainActivity::class) { it.flags = Intent.FLAG_ACTIVITY_NEW_TASK }
}) {
Text("Open Old Settings")
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}
Preference(
iconId = R.drawable.ic_keyboard,
title = stringRes(R.string.settings__keyboard__title),
onClick = { navController.navigate(Routes.Settings.Keyboard) },
)
Preference(
iconId = R.drawable.ic_gesture,
title = stringRes(R.string.settings__gestures__title),
onClick = { navController.navigate(Routes.Settings.Gestures) },
)
Preference(
iconId = R.drawable.ic_assignment,
title = stringRes(R.string.settings__clipboard__title),
onClick = { navController.navigate(Routes.Settings.Clipboard) },
)
Preference(
iconId = R.drawable.ic_build,
title = stringRes(R.string.settings__advanced__title),
onClick = { navController.navigate(Routes.Settings.Advanced) },
)
Preference(
iconId = R.drawable.ic_info,
title = stringRes(R.string.about__title),
onClick = { navController.navigate(Routes.Settings.About) },
)
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.about
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.res.ResourcesCompat
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.common.InputMethodUtils
import dev.patrickgold.florisboard.common.launchUrl
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
import dev.patrickgold.jetpref.ui.compose.Preference
@Composable
fun AboutScreen() = FlorisScreen(title = stringRes(R.string.about__title)) {
val navController = LocalNavController.current
val context = LocalContext.current
val appVersion = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp, bottom = 32.dp)
) {
FlorisAppIcon()
Text(
text = stringRes(R.string.floris_app_name),
fontSize = 24.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 16.dp),
)
}
Preference(
iconId = R.drawable.ic_info,
title = stringRes(R.string.about__version__title),
summary = appVersion,
onClick = {
try {
val isImeSelected = InputMethodUtils.checkIsFlorisboardSelected(context)
if (isImeSelected) {
FlorisClipboardManager.getInstance().addNewPlaintext(appVersion)
} else {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Florisboard version", appVersion)
clipboard.setPrimaryClip(clip)
}
Toast.makeText(context, R.string.about__version_copied__title, Toast.LENGTH_SHORT).show()
} catch (e: Throwable) {
Toast.makeText(
context, context.getString(R.string.about__version_copied__error, e.message), Toast.LENGTH_SHORT
).show()
}
},
)
Preference(
iconId = R.drawable.ic_history,
title = stringRes(R.string.about__changelog__title),
summary = stringRes(R.string.about__changelog__summary),
onClick = { launchUrl(context, R.string.florisboard__changelog_url, arrayOf(BuildConfig.VERSION_NAME)) },
)
Preference(
iconId = R.drawable.ic_code,
title = stringRes(R.string.about__repository__title),
summary = stringRes(R.string.about__repository__summary),
onClick = { launchUrl(context, R.string.florisboard__repo_url) },
)
Preference(
iconId = R.drawable.ic_policy,
title = stringRes(R.string.about__privacy_policy__title),
summary = stringRes(R.string.about__privacy_policy__summary),
onClick = { launchUrl(context, R.string.florisboard__privacy_policy_url) },
)
Preference(
iconId = R.drawable.ic_description,
title = stringRes(R.string.about__project_license__title),
summary = stringRes(R.string.about__project_license__summary, "license_name" to "Apache 2.0"),
onClick = { navController.navigate(Routes.Settings.ProjectLicense) },
)
Preference(
iconId = R.drawable.ic_description,
title = stringRes(id = R.string.about__third_party_licenses__title),
summary = stringRes(id = R.string.about__third_party_licenses__summary),
onClick = { navController.navigate(Routes.Settings.ThirdPartyLicenses) },
)
}
@Composable
private fun FlorisAppIcon() {
ResourcesCompat.getDrawable(
LocalContext.current.resources,
R.mipmap.floris_app_icon, LocalContext.current.theme
)?.let { drawable ->
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = "FlorisBoard App Icon",
modifier = Modifier.requiredSize(64.dp),
)
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.about
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.res.AssetManager
import dev.patrickgold.florisboard.res.FlorisRef
@Composable
fun ProjectLicenseScreen() = FlorisScreen(title = stringRes(R.string.about__project_license__title)) {
SelectionContainer(modifier = Modifier.fillMaxWidth()) {
val assetManager = AssetManager.defaultOrNull()
val licenseText = assetManager?.loadTextAsset(
FlorisRef.assets("license/project_license.txt")
)?.getOrElse {
stringRes(R.string.about__project_license__error_license_text_failed, "error_message" to (it.message ?: ""))
} ?: stringRes(
id = R.string.about__project_license__error_license_text_failed,
"error_message" to stringRes(R.string.about__project_license__error_reason_asset_manager_null)
)
Text(
text = licenseText,
fontFamily = FontFamily.Monospace,
fontSize = 10.sp,
softWrap = false,
modifier = Modifier.horizontalScroll(rememberScrollState()),
)
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.about
import android.webkit.URLUtil
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.common.launchUrl
import dev.patrickgold.jetpref.ui.compose.JetPrefAlertDialog
import dev.patrickgold.jetpref.ui.compose.Preference
data class Library(val name: String, val licenseText: String)
@Composable
fun ThirdPartyLicensesScreen() = FlorisScreen(title = stringRes(R.string.about__third_party_licenses__title)) {
val context = LocalContext.current
var dialogLibraryToShow by rememberSaveable {
mutableStateOf<Library?>(null)
}
val libraries = remember {
val list = mutableListOf<Library>()
val licensesData = context.resources
.openRawResource(R.raw.third_party_licenses)
.readBytes()
val licensesMetaDataReader = context.resources
.openRawResource(R.raw.third_party_license_metadata)
.bufferedReader()
licensesMetaDataReader.use { it.readLines() }.map { line ->
val (section, name) = line.split(" ", limit = 2)
val (startOffset, length) = section.split(":", limit = 2).map { it.toInt() }
val licenseData = licensesData.sliceArray(startOffset until startOffset + length)
val licenseText = licenseData.toString(Charsets.UTF_8)
Library(name, licenseText)
}.all { list.add(it) }
list.add(
Library("ICU4C Native C library", "https://github.com/unicode-org/icu/blob/main/icu4c/LICENSE")
)
list.sortedBy { it.name.lowercase() }.toList()
}
for (library in libraries) {
val isUrl = URLUtil.isValidUrl(library.licenseText)
Preference(
title = library.name,
onClick = {
if (isUrl) {
launchUrl(context, library.licenseText)
} else {
dialogLibraryToShow = library
}
}
)
}
if (dialogLibraryToShow != null) {
JetPrefAlertDialog(
title = dialogLibraryToShow?.name ?: "",
dismissLabel = stringRes(android.R.string.ok),
onDismiss = { dialogLibraryToShow = null },
) {
Text(dialogLibraryToShow?.licenseText ?: "")
}
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.advanced
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.common.FlorisLocale
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
import dev.patrickgold.florisboard.util.AndroidVersion
import dev.patrickgold.jetpref.ui.compose.JetPrefAlertDialog
import dev.patrickgold.jetpref.ui.compose.ListPreference
import dev.patrickgold.jetpref.ui.compose.ListPreferenceEntry
import dev.patrickgold.jetpref.ui.compose.Preference
import dev.patrickgold.jetpref.ui.compose.PreferenceGroup
import dev.patrickgold.jetpref.ui.compose.SwitchPreference
import dev.patrickgold.jetpref.ui.compose.entry
@Composable
fun AdvancedScreen() = FlorisScreen(title = stringRes(R.string.settings__advanced__title)) {
val (showDialog, setShowDialog) = remember { mutableStateOf(false) }
ListPreference(
prefs.advanced.settingsTheme,
title = stringRes(R.string.pref__advanced__settings_theme__label),
entries = listOf(
entry(
key = AppTheme.AUTO,
label = stringRes(R.string.settings__system_default),
),
entry(
key = AppTheme.LIGHT,
label = stringRes(R.string.pref__advanced__settings_theme__light),
),
entry(
key = AppTheme.DARK,
label = stringRes(R.string.pref__advanced__settings_theme__dark),
),
entry(
key = AppTheme.AMOLED_DARK,
label = stringRes(R.string.pref__advanced__settings_theme__amoled_dark),
),
),
)
ListPreference(
prefs.advanced.settingsLanguage,
title = stringRes(R.string.pref__advanced__settings_language__label),
entries = listOf(
"auto",
"ar",
"bg",
"bs",
"ca",
"ckb-IR",
"cs",
"da",
"de",
"el",
"en",
"eo",
"es",
"fa",
"fi",
"fr",
"hr",
"hu",
"in",
"it",
"iw",
"kmr-TR",
"ko-KR",
"lv-LV",
"mk",
"nds-DE",
"nl",
"no",
"pl",
"pt",
"pt-BR",
"ru",
"sk",
"sl",
"sr",
"sv",
"tr",
"uk",
"zgh",
).map {
if (it == "auto") {
entry(
key = "auto",
label = stringRes(R.string.settings__system_default),
)
} else {
FlorisLocale.fromTag(it).listEntry()
}
},
)
SwitchPreference(
prefs.advanced.showAppIcon,
title = stringRes(R.string.pref__advanced__show_app_icon__label),
summary = when {
AndroidVersion.ATLEAST_Q -> stringRes(R.string.pref__advanced__show_app_icon__summary_atleast_q)
else -> null
},
enabledIf = { AndroidVersion.ATMOST_P },
)
SwitchPreference(
prefs.advanced.forcePrivateMode,
title = stringRes(R.string.pref__advanced__force_private_mode__label),
summary = stringRes(R.string.pref__advanced__force_private_mode__summary),
)
PreferenceGroup(title = stringRes(R.string.settings__devtools__title)) {
SwitchPreference(
prefs.devtools.enabled,
title = stringRes(R.string.pref__devtools__enabled__label),
summary = stringRes(R.string.pref__devtools__enabled__summary),
)
SwitchPreference(
prefs.devtools.showHeapMemoryStats,
title = stringRes(R.string.pref__devtools__show_heap_memory_stats__label),
summary = stringRes(R.string.pref__devtools__show_heap_memory_stats__summary),
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
// TODO: remove this preference once word suggestions are re-implemented in 0.3.15
SwitchPreference(
prefs.devtools.overrideWordSuggestionsMinHeapRestriction,
title = "Override min heap size restriction for word suggestions",
summary = "This allows you to use word suggestions even if your heap size is not intended for it and can break FlorisBoard",
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.pref__devtools__clear_udm_internal_database__label),
summary = stringRes(R.string.pref__devtools__clear_udm_internal_database__summary),
onClick = { setShowDialog(true) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.pref__devtools__reset_flag__label, "flag_name" to "isImeSetUp"),
summary = stringRes(R.string.pref__devtools__reset_flag_is_ime_set_up__summary),
onClick = { prefs.internal.isImeSetUp.set(false) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
}
if (showDialog) {
JetPrefAlertDialog(
title = stringRes(R.string.assets__action__delete_confirm_title),
confirmLabel = stringRes(R.string.assets__action__delete),
onConfirm = {
DictionaryManager.default().let {
it.loadUserDictionariesIfNecessary()
it.florisUserDictionaryDao()?.deleteAll()
}
setShowDialog(false)
},
dismissLabel = stringRes(R.string.assets__action__cancel),
onDismiss = { setShowDialog(false) },
) {
Text(
text = stringRes(
R.string.assets__action__delete_confirm_message,
"database_name" to FlorisUserDictionaryDatabase.DB_FILE_NAME,
)
)
}
}
}
private fun FlorisLocale.listEntry(): ListPreferenceEntry<String> {
return entry(languageTag(), displayName(this))
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.clipboard
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.pluralsRes
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.jetpref.ui.compose.DialogSliderPreference
import dev.patrickgold.jetpref.ui.compose.PreferenceGroup
import dev.patrickgold.jetpref.ui.compose.SwitchPreference
import dev.patrickgold.jetpref.ui.compose.annotations.ExperimentalJetPrefUi
@OptIn(ExperimentalJetPrefUi::class)
@Composable
fun ClipboardScreen() = FlorisScreen(title = stringRes(R.string.settings__clipboard__title)) {
SwitchPreference(
prefs.clipboard.useInternalClipboard,
title = stringRes(R.string.pref__clipboard__use_internal_clipboard__label),
summary = stringRes(R.string.pref__clipboard__use_internal_clipboard__summary),
)
SwitchPreference(
prefs.clipboard.syncToFloris,
title = stringRes(R.string.pref__clipboard__sync_from_system_clipboard__label),
summary = stringRes(R.string.pref__clipboard__sync_from_system_clipboard__summary),
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
)
SwitchPreference(
prefs.clipboard.syncToSystem,
title = stringRes(R.string.pref__clipboard__sync_to_system_clipboard__label),
summary = stringRes(R.string.pref__clipboard__sync_to_system_clipboard__summary),
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
)
PreferenceGroup(title = stringRes(R.string.pref__clipboard__clipboard_history_title)) {
SwitchPreference(
prefs.clipboard.enableHistory,
title = stringRes(R.string.pref__clipboard__enable_clipboard_history__label),
summary = stringRes(R.string.pref__clipboard__enable_clipboard_history__summary),
)
SwitchPreference(
prefs.clipboard.cleanUpOld,
title = stringRes(R.string.pref__clipboard__clean_up_old__label),
enabledIf = { prefs.clipboard.enableHistory isEqualTo true },
)
DialogSliderPreference(
prefs.clipboard.cleanUpAfter,
title = stringRes(R.string.pref__clipboard__clean_up_after__label),
unit = pluralsRes(R.plurals.unit__minutes__written, prefs.clipboard.cleanUpAfter.get()),
min = 0,
max = 120,
stepIncrement = 5,
enabledIf = { prefs.clipboard.enableHistory isEqualTo true && prefs.clipboard.cleanUpOld isEqualTo true },
)
SwitchPreference(
prefs.clipboard.limitHistorySize,
title = stringRes(R.string.pref__clipboard__limit_history_size__label),
enabledIf = { prefs.clipboard.enableHistory isEqualTo true },
)
DialogSliderPreference(
prefs.clipboard.maxHistorySize,
title = stringRes(R.string.pref__clipboard__max_history_size__label),
unit = pluralsRes(R.plurals.unit__items__written, prefs.clipboard.maxHistorySize.get()),
min = 5,
max = 100,
stepIncrement = 5,
enabledIf = { prefs.clipboard.enableHistory isEqualTo true && prefs.clipboard.limitHistorySize isEqualTo true },
)
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.gestures
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.jetpref.ui.compose.DialogSliderPreference
import dev.patrickgold.jetpref.ui.compose.ListPreference
import dev.patrickgold.jetpref.ui.compose.PreferenceGroup
import dev.patrickgold.jetpref.ui.compose.SwitchPreference
import dev.patrickgold.jetpref.ui.compose.annotations.ExperimentalJetPrefUi
@OptIn(ExperimentalJetPrefUi::class)
@Composable
fun GesturesScreen() = FlorisScreen(title = stringRes(R.string.settings__gestures__title)) {
PreferenceGroup(title = stringRes(R.string.pref__glide__title)) {
SwitchPreference(
prefs.glide.enabled,
title = stringRes(R.string.pref__glide__enabled__label),
summary = stringRes(R.string.pref__glide__enabled__summary),
)
SwitchPreference(
prefs.glide.showTrail,
title = stringRes(R.string.pref__glide__show_trail__label),
summary = stringRes(R.string.pref__glide__show_trail__summary),
enabledIf = { prefs.glide.enabled isEqualTo true },
)
DialogSliderPreference(
prefs.glide.trailDuration,
title = stringRes(R.string.pref__glide_trail_fade_duration),
unit = stringRes(R.string.unit__milliseconds__symbol),
min = 0,
max = 500,
stepIncrement = 10,
enabledIf = { prefs.glide.enabled isEqualTo true && prefs.glide.showTrail isEqualTo true },
)
SwitchPreference(
prefs.glide.showPreview,
title = stringRes(R.string.pref__glide__show_preview),
enabledIf = { prefs.glide.enabled isEqualTo true },
)
DialogSliderPreference(
prefs.glide.previewRefreshDelay,
title = stringRes(R.string.pref__glide_preview_refresh_delay),
unit = stringRes(R.string.unit__milliseconds__symbol),
min = 50,
max = 500,
stepIncrement = 25,
enabledIf = { prefs.glide.enabled isEqualTo true && prefs.glide.showPreview isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.pref__gestures__general_title)) {
ListPreference(
prefs.gestures.swipeUp,
title = stringRes(R.string.pref__gestures__swipe_up__label),
entries = SwipeAction.generalListEntries(),
enabledIf = { prefs.glide.enabled isEqualTo false },
)
ListPreference(
prefs.gestures.swipeDown,
title = stringRes(R.string.pref__gestures__swipe_down__label),
entries = SwipeAction.generalListEntries(),
enabledIf = { prefs.glide.enabled isEqualTo false },
)
ListPreference(
prefs.gestures.swipeLeft,
title = stringRes(R.string.pref__gestures__swipe_left__label),
entries = SwipeAction.generalListEntries(),
enabledIf = { prefs.glide.enabled isEqualTo false },
)
ListPreference(
prefs.gestures.swipeRight,
title = stringRes(R.string.pref__gestures__swipe_right__label),
entries = SwipeAction.generalListEntries(),
enabledIf = { prefs.glide.enabled isEqualTo false },
)
}
PreferenceGroup(title = stringRes(R.string.pref__gestures__space_bar_title)) {
ListPreference(
prefs.gestures.spaceBarSwipeUp,
title = stringRes(R.string.pref__gestures__space_bar_swipe_up__label),
entries = SwipeAction.generalListEntries(),
)
ListPreference(
prefs.gestures.spaceBarSwipeLeft,
title = stringRes(R.string.pref__gestures__space_bar_swipe_left__label),
entries = SwipeAction.generalListEntries(),
)
ListPreference(
prefs.gestures.spaceBarSwipeRight,
title = stringRes(R.string.pref__gestures__space_bar_swipe_right__label),
entries = SwipeAction.generalListEntries(),
)
ListPreference(
prefs.gestures.spaceBarLongPress,
title = stringRes(R.string.pref__gestures__space_bar_long_press__label),
entries = SwipeAction.generalListEntries(),
)
}
PreferenceGroup(title = stringRes(R.string.pref__gestures__other_title)) {
ListPreference(
prefs.gestures.deleteKeySwipeLeft,
title = stringRes(R.string.pref__gestures__delete_key_swipe_left__label),
entries = SwipeAction.deleteSwipeListEntries(),
)
DialogSliderPreference(
prefs.gestures.swipeVelocityThreshold,
title = stringRes(R.string.pref__gestures__swipe_velocity_threshold__label),
unit = stringRes(R.string.unit__display_pixel_per_seconds__symbol),
min = 400,
max = 4000,
stepIncrement = 100,
)
DialogSliderPreference(
prefs.gestures.swipeDistanceThreshold,
title = stringRes(R.string.pref__gestures__swipe_distance_threshold__label),
unit = stringRes(R.string.unit__display_pixel__symbol),
min = 12,
max = 48,
stepIncrement = 1,
)
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.keyboard
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.ime.keyboard.InputFeedbackManager
import dev.patrickgold.jetpref.ui.compose.DialogSliderPreference
import dev.patrickgold.jetpref.ui.compose.PreferenceGroup
import dev.patrickgold.jetpref.ui.compose.SwitchPreference
import dev.patrickgold.jetpref.ui.compose.annotations.ExperimentalJetPrefUi
@OptIn(ExperimentalJetPrefUi::class)
@Composable
fun InputFeedbackScreen() = FlorisScreen(title = stringRes(R.string.settings__input_feedback__title)) {
PreferenceGroup(title = stringRes(R.string.pref__input_feedback__group_audio__label)) {
SwitchPreference(
prefs.inputFeedback.audioEnabled,
title = stringRes(R.string.pref__input_feedback__audio_enabled__label),
summary = stringRes(R.string.pref__input_feedback__audio_enabled__summary),
)
SwitchPreference(
prefs.inputFeedback.audioIgnoreSystemSettings,
title = stringRes(R.string.pref__input_feedback__audio_ignore_system_settings__label),
summary = stringRes(R.string.pref__input_feedback__audio_ignore_system_settings__summary),
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
DialogSliderPreference(
prefs.inputFeedback.audioVolume,
title = stringRes(R.string.pref__input_feedback__audio_volume__label),
unit = stringRes(R.string.unit__percent__symbol),
min = 0,
max = 100,
stepIncrement = 1,
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.audioFeatKeyPress,
title = stringRes(R.string.pref__input_feedback__audio_feat_key_press__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_key_press__summary),
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.audioFeatKeyLongPress,
title = stringRes(R.string.pref__input_feedback__audio_feat_key_long_press__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_key_long_press__summary),
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.audioFeatKeyRepeatedAction,
title = stringRes(R.string.pref__input_feedback__audio_feat_key_repeated_action__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_key_repeated_action__summary),
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.audioFeatGestureSwipe,
title = stringRes(R.string.pref__input_feedback__audio_feat_gesture_swipe__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_gesture_swipe__summary),
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.audioFeatGestureMovingSwipe,
title = stringRes(R.string.pref__input_feedback__audio_feat_gesture_moving_swipe__label),
summary = stringRes(R.string.pref__input_feedback__audio_feat_gesture_moving_swipe__label),
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.pref__input_feedback__group_haptic__label)) {
SwitchPreference(
prefs.inputFeedback.hapticEnabled,
title = stringRes(R.string.pref__input_feedback__haptic_enabled__label),
summary = stringRes(R.string.pref__input_feedback__haptic_enabled__summary),
)
SwitchPreference(
prefs.inputFeedback.hapticIgnoreSystemSettings,
title = stringRes(R.string.pref__input_feedback__haptic_ignore_system_settings__label),
summary = stringRes(R.string.pref__input_feedback__haptic_ignore_system_settings__summary),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.hapticUseVibrator,
title = stringRes(R.string.pref__input_feedback__haptic_use_vibrator__label),
summary = stringRes(R.string.pref__input_feedback__haptic_use_vibrator__summary),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
DialogSliderPreference(
prefs.inputFeedback.hapticVibrationDuration,
title = stringRes(R.string.pref__input_feedback__haptic_vibration_duration__label),
unit = stringRes(R.string.unit__milliseconds__symbol),
min = 0,
max = 100,
stepIncrement = 1,
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true && prefs.inputFeedback.hapticUseVibrator isEqualTo true },
)
DialogSliderPreference(
prefs.inputFeedback.hapticVibrationStrength,
title = stringRes(R.string.pref__input_feedback__haptic_vibration_strength__label),
summary = InputFeedbackManager.generateVibrationStrengthErrorSummary() ?:
stringRes(R.string.unit__milliseconds__symbol),
unit = stringRes(R.string.unit__milliseconds__symbol),
min = 0,
max = 100,
stepIncrement = 1,
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true && prefs.inputFeedback.hapticUseVibrator isEqualTo true && InputFeedbackManager.hasAmplitudeControl() },
)
SwitchPreference(
prefs.inputFeedback.hapticFeatKeyPress,
title = stringRes(R.string.pref__input_feedback__haptic_feat_key_press__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_key_press__summary),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.hapticFeatKeyLongPress,
title = stringRes(R.string.pref__input_feedback__haptic_feat_key_long_press__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_key_long_press__summary),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.hapticFeatKeyRepeatedAction,
title = stringRes(R.string.pref__input_feedback__haptic_feat_key_repeated_action__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_key_repeated_action__summary),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.hapticFeatGestureSwipe,
title = stringRes(R.string.pref__input_feedback__haptic_feat_gesture_swipe__label),
summary = stringRes(R.string.pref__input_feedback__any_feat_gesture_swipe__summary),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
SwitchPreference(
prefs.inputFeedback.hapticFeatGestureMovingSwipe,
title = stringRes(R.string.pref__input_feedback__haptic_feat_gesture_moving_swipe__label),
summary = stringRes(R.string.pref__input_feedback__audio_feat_gesture_moving_swipe__label),
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
)
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ui.settings.keyboard
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.jetpref.ui.compose.DialogSliderPreference
import dev.patrickgold.jetpref.ui.compose.ListPreference
import dev.patrickgold.jetpref.ui.compose.Preference
import dev.patrickgold.jetpref.ui.compose.PreferenceGroup
import dev.patrickgold.jetpref.ui.compose.SwitchPreference
import dev.patrickgold.jetpref.ui.compose.annotations.ExperimentalJetPrefUi
@OptIn(ExperimentalJetPrefUi::class)
@Composable
fun KeyboardScreen() = FlorisScreen(title = stringRes(R.string.settings__keyboard__title)) {
val navController = LocalNavController.current
SwitchPreference(
prefs.keyboard.numberRow,
title = stringRes(R.string.pref__keyboard__number_row__label),
summary = stringRes(R.string.pref__keyboard__number_row__summary),
)
ListPreference(
listPref = prefs.keyboard.hintedNumberRowMode,
switchPref = prefs.keyboard.hintedNumberRowEnabled,
title = stringRes(R.string.pref__keyboard__hinted_number_row_mode__label),
summarySwitchDisabled = stringRes(R.string.state__disabled),
entries = KeyHintMode.listEntries(),
)
ListPreference(
listPref = prefs.keyboard.hintedSymbolsMode,
switchPref = prefs.keyboard.hintedSymbolsEnabled,
title = stringRes(R.string.pref__keyboard__hinted_symbols_mode__label),
summarySwitchDisabled = stringRes(R.string.state__disabled),
entries = KeyHintMode.listEntries(),
)
SwitchPreference(
prefs.keyboard.utilityKeyEnabled,
title = stringRes(R.string.pref__keyboard__utility_key_enabled__label),
summary = stringRes(R.string.pref__keyboard__utility_key_enabled__summary),
)
ListPreference(
prefs.keyboard.utilityKeyAction,
title = stringRes(R.string.pref__keyboard__utility_key_action__label),
entries = UtilityKeyAction.listEntries(),
visibleIf = { prefs.keyboard.utilityKeyEnabled isEqualTo true },
)
DialogSliderPreference(
primaryPref = prefs.keyboard.fontSizeMultiplierPortrait,
secondaryPref = prefs.keyboard.fontSizeMultiplierLandscape,
title = stringRes(R.string.pref__keyboard__font_size_multiplier__label),
primaryLabel = stringRes(R.string.screen_orientation__portrait),
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
unit = stringRes(R.string.unit__percent__symbol),
min = 50,
max = 150,
stepIncrement = 5,
)
PreferenceGroup(title = stringRes(R.string.pref__keyboard__group_layout__label)) {
ListPreference(
prefs.keyboard.oneHandedMode,
title = stringRes(R.string.pref__keyboard__one_handed_mode__label),
entries = OneHandedMode.listEntries(),
)
DialogSliderPreference(
prefs.keyboard.oneHandedModeScaleFactor,
title = stringRes(R.string.pref__keyboard__one_handed_mode_scale_factor__label),
unit = stringRes(R.string.unit__percent__symbol),
min = 70,
max = 90,
stepIncrement = 1,
enabledIf = { prefs.keyboard.oneHandedMode isNotEqualTo OneHandedMode.OFF },
)
ListPreference(
prefs.keyboard.landscapeInputUiMode,
title = stringRes(R.string.pref__keyboard__landscape_input_ui_mode__label),
entries = LandscapeInputUiMode.listEntries(),
)
DialogSliderPreference(
primaryPref = prefs.keyboard.heightFactorPortrait,
secondaryPref = prefs.keyboard.heightFactorLandscape,
title = stringRes(R.string.pref__keyboard__height_factor__label),
primaryLabel = stringRes(R.string.screen_orientation__portrait),
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
unit = stringRes(R.string.unit__percent__symbol),
min = 50,
max = 150,
stepIncrement = 5,
)
DialogSliderPreference(
primaryPref = prefs.keyboard.keySpacingVertical,
secondaryPref = prefs.keyboard.keySpacingHorizontal,
title = stringRes(R.string.pref__keyboard__key_spacing__label),
primaryLabel = stringRes(R.string.screen_orientation__vertical),
secondaryLabel = stringRes(R.string.screen_orientation__horizontal),
unit = stringRes(R.string.unit__display_pixel__symbol),
min = 0.0f,
max = 10.0f,
stepIncrement = 0.5f,
)
DialogSliderPreference(
primaryPref = prefs.keyboard.bottomOffsetPortrait,
secondaryPref = prefs.keyboard.bottomOffsetLandscape,
title = stringRes(R.string.pref__keyboard__bottom_offset__label),
primaryLabel = stringRes(R.string.screen_orientation__portrait),
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
unit = stringRes(R.string.unit__display_pixel__symbol),
min = 0,
max = 60,
stepIncrement = 1,
)
}
PreferenceGroup(title = stringRes(R.string.pref__keyboard__group_keypress__label)) {
Preference(
title = stringRes(R.string.settings__input_feedback__title),
onClick = { navController.navigate(Routes.Settings.InputFeedback) },
)
SwitchPreference(
prefs.keyboard.popupEnabled,
title = stringRes(R.string.pref__keyboard__popup_enabled__label),
summary = stringRes(R.string.pref__keyboard__popup_enabled__summary),
)
SwitchPreference(
prefs.keyboard.mergeHintPopupsEnabled,
title = stringRes(R.string.pref__keyboard__merge_hint_popups_enabled__label),
summary = stringRes(R.string.pref__keyboard__merge_hint_popups_enabled__summary),
)
DialogSliderPreference(
prefs.keyboard.longPressDelay,
title = stringRes(R.string.pref__keyboard__long_press_delay__label),
unit = stringRes(R.string.unit__milliseconds__symbol),
min = 100,
max = 700,
stepIncrement = 10,
)
SwitchPreference(
prefs.keyboard.spaceBarSwitchesToCharacters,
title = stringRes(R.string.pref__keyboard__space_bar_switches_to_characters__label),
summary = stringRes(R.string.pref__keyboard__space_bar_switches_to_characters__summary),
)
}
}

View File

@@ -0,0 +1,255 @@
/*
* 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.setup
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalIsFlorisBoardEnabled
import dev.patrickgold.florisboard.app.LocalIsFlorisBoardSelected
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.ui.Routes
import dev.patrickgold.florisboard.app.ui.components.FlorisScreen
import dev.patrickgold.florisboard.app.ui.res.stringRes
import dev.patrickgold.florisboard.common.InputMethodUtils
import dev.patrickgold.florisboard.common.launchUrl
private object Step {
const val EnableIme: Int = 1
const val SelectIme: Int = 2
const val FinishUp: Int = 3
}
@Composable
fun SetupScreen() = FlorisScreen(
title = stringRes(R.string.setup__title),
backArrowVisible = false,
scrollable = false,
) {
val navController = LocalNavController.current
val context = LocalContext.current
val isFlorisBoardEnabled = LocalIsFlorisBoardEnabled.current
val isFlorisBoardSelected = LocalIsFlorisBoardSelected.current
var currentStep by rememberSaveable {
val initStep = when {
!isFlorisBoardEnabled -> Step.EnableIme
!isFlorisBoardSelected -> Step.SelectIme
else -> Step.FinishUp
}
mutableStateOf(initStep)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
) {
SetupText(stringRes(R.string.setup__intro_message))
Spacer(modifier = Modifier.height(16.dp))
Step(
ownStep = Step.EnableIme,
currentStep = currentStep,
title = stringRes(R.string.setup__enable_ime__title)
) {
SetupText(stringRes(R.string.setup__enable_ime__description))
SetupButton(label = stringRes(R.string.setup__enable_ime__open_settings_btn)) {
InputMethodUtils.showImeEnablerActivity(context)
}
}
Step(
ownStep = Step.SelectIme,
currentStep = currentStep,
title = stringRes(R.string.setup__select_ime__title)
) {
SetupText(stringRes(R.string.setup__select_ime__description))
SetupButton(label = stringRes(R.string.setup__select_ime__switch_keyboard_btn)) {
InputMethodUtils.showImePicker(context)
}
}
Step(
ownStep = Step.FinishUp,
currentStep = currentStep,
title = stringRes(R.string.setup__finish_up__title)
) {
SetupText(stringRes(R.string.setup__finish_up__description_p1))
SetupText(stringRes(R.string.setup__finish_up__description_p2))
SetupButton(label = stringRes(R.string.setup__finish_up__finish_btn)) {
this@FlorisScreen.prefs.internal.isImeSetUp.set(true)
navController.navigate(Routes.Settings.Home)
}
}
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
val privacyPolicyUrl = stringRes(R.string.florisboard__privacy_policy_url)
TextButton(onClick = { launchUrl(context, privacyPolicyUrl)}) {
Text(text = stringRes(R.string.setup__footer__privacy_policy))
}
Box(
modifier = Modifier
.padding(horizontal = 8.dp)
.size(12.dp, 4.dp)
.clip(CircleShape)
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.12f))
)
val repositoryUrl = stringRes(R.string.florisboard__repo_url)
TextButton(onClick = { launchUrl(context, repositoryUrl) }) {
Text(text = stringRes(R.string.setup__footer__repository))
}
}
SideEffect {
currentStep = when {
!isFlorisBoardEnabled -> Step.EnableIme
!isFlorisBoardSelected -> Step.SelectIme
else -> Step.FinishUp
}
}
}
}
@Composable
fun SetupText(text: String) {
Text(
text = text,
textAlign = TextAlign.Justify,
)
}
@Composable
fun ColumnScope.SetupButton(label: String, onClick: () -> Unit) {
Button(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp),
onClick = onClick,
) {
Text(text = label)
}
}
@Composable
internal fun ColumnScope.Step(
ownStep: Int,
currentStep: Int,
title: String,
content: @Composable ColumnScope.() -> Unit,
) {
val backgroundColor = when (ownStep) {
currentStep -> MaterialTheme.colors.primary
else -> MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
}
if (ownStep == currentStep) {
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1.0f)
) {
StepHeader(
backgroundColor = backgroundColor,
step = ownStep,
title = title,
)
Box(modifier = Modifier.padding(start = 56.dp)) {
Column(modifier = Modifier.fillMaxSize()) {
content()
}
}
}
} else {
StepHeader(
backgroundColor = backgroundColor,
step = ownStep,
title = title,
)
}
}
@Composable
fun StepHeader(
backgroundColor: Color,
contentColor: Color = contentColorFor(backgroundColor),
step: Int,
title: String,
) {
Row(
modifier = Modifier
.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.padding(end = 16.dp)
.size(40.dp)
.clip(CircleShape)
.background(backgroundColor),
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = step.toString(),
color = contentColor,
)
}
Box(
modifier = Modifier
.height(32.dp)
.weight(1.0f)
.clip(CircleShape)
.background(backgroundColor),
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = title,
color = contentColor,
)
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.theme
import androidx.compose.ui.graphics.Color
val Green500 = Color(0xFF4CAF50)
val Green700 = Color(0xFF388E3C)
val Orange700 = Color(0xFFF57C00)
val Orange900 = Color(0xFFE65100)

View File

@@ -0,0 +1,27 @@
/*
* 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.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)

View File

@@ -0,0 +1,87 @@
/*
* 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.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import dev.patrickgold.florisboard.app.AppTheme
private val AmoledDarkColorPalette = darkColors(
primary = Green500,
primaryVariant = Green700,
secondary = Orange700,
secondaryVariant = Orange900,
background = Color(0xFF000000),
surface = Color(0xFF212121),
)
private val DarkColorPalette = darkColors(
primary = Green500,
primaryVariant = Green700,
secondary = Orange700,
secondaryVariant = Orange900,
background = Color(0xFF1F1F1F),
surface = Color(0xFF212121),
)
private val LightColorPalette = lightColors(
primary = Green500,
primaryVariant = Green700,
secondary = Orange700,
secondaryVariant = Orange900,
background = Color(0xFFFFFFFF),
surface = Color(0xFFE7E7E7),
/* Other default colors to override
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
@Composable
fun FlorisAppTheme(
theme: AppTheme,
content: @Composable () -> Unit
) {
val colors = when (theme) {
AppTheme.AUTO -> when {
isSystemInDarkTheme() -> DarkColorPalette
else -> LightColorPalette
}
AppTheme.LIGHT -> LightColorPalette
AppTheme.DARK -> DarkColorPalette
AppTheme.AMOLED_DARK -> AmoledDarkColorPalette
}
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content,
)
}

View File

@@ -0,0 +1,44 @@
/*
* 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.theme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
/* Other default text styles to override
button = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
caption = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
*/
)

View File

@@ -65,12 +65,12 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineS
messageSnackbar = null
}
protected fun showMessage(@StringRes snackbarMessageResId: Int) {
fun showMessage(@StringRes snackbarMessageResId: Int) {
val snackbarMessage = resources.getString(snackbarMessageResId)
showMessage(snackbarMessage)
}
protected fun showMessage(snackbarMessage: String) {
fun showMessage(snackbarMessage: String) {
messageSnackbar?.dismiss()
messageSnackbar = Snackbar.make(binding.root, snackbarMessage, Snackbar.LENGTH_LONG).apply {
setAction(android.R.string.ok) {
@@ -79,39 +79,43 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineS
show() }
}
protected fun showError(throwable: Throwable) {
fun showError(throwable: Throwable) {
val snackbarMessage = resources.getString(R.string.assets__error__snackbar_message)
showError(snackbarMessage, throwable)
}
protected fun showError(@StringRes snackbarMessageResId: Int, throwable: Throwable) {
fun showError(@StringRes snackbarMessageResId: Int, throwable: Throwable) {
val snackbarMessage = resources.getString(snackbarMessageResId)
showError(snackbarMessage, throwable)
}
protected fun showError(snackbarMessage: String, throwable: Throwable) {
fun showError(snackbarMessage: String, throwable: Throwable) {
errorDialog?.dismiss()
errorDialog = null
errorSnackbar?.dismiss()
errorSnackbar = Snackbar.make(binding.root, snackbarMessage, Snackbar.LENGTH_LONG).apply {
setAction(R.string.assets__error__details) {
errorDialog?.dismiss()
errorDialog = AlertDialog.Builder(this@FlorisActivity).run {
setTitle(R.string.assets__error__details)
setMessage(errorThrowable?.stackTraceToString())
setPositiveButton(android.R.string.ok, null)
setNeutralButton(R.string.crash_dialog__copy_to_clipboard) { _, _ ->
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE)
if (clipboardManager != null && clipboardManager is ClipboardManager) {
clipboardManager.setPrimaryClip(ClipData.newPlainText(errorThrowable.toString(), errorThrowable.toString()))
}
}
create()
show()
}
showErrorDialog()
}
show()
}
errorThrowable = throwable
}
fun showErrorDialog(throwable: Throwable? = errorThrowable) {
errorDialog?.dismiss()
errorDialog = AlertDialog.Builder(this@FlorisActivity).run {
setTitle(R.string.assets__error__details)
setMessage(throwable?.stackTraceToString())
setPositiveButton(android.R.string.ok, null)
setNeutralButton(R.string.crash_dialog__copy_to_clipboard) { _, _ ->
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE)
if (clipboardManager != null && clipboardManager is ClipboardManager) {
clipboardManager.setPrimaryClip(ClipData.newPlainText(throwable.toString(), throwable.toString()))
}
}
create()
show()
}
}
}

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