Compare commits
107 Commits
v0.3.10-be
...
v0.3.10-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36229136ec | ||
|
|
00424055b5 | ||
|
|
cba5a756f8 | ||
|
|
fc401359a7 | ||
|
|
546dad8b71 | ||
|
|
b30e3b8093 | ||
|
|
b415afe6e4 | ||
|
|
b69be1ab46 | ||
|
|
c5cf8efe82 | ||
|
|
ae0ec65ce0 | ||
|
|
7ac3e45b34 | ||
|
|
5a71793f1a | ||
|
|
ed040ca49b | ||
|
|
c0f90a7ea4 | ||
|
|
2d9651da8c | ||
|
|
9f5a126c1f | ||
|
|
182e6c58e1 | ||
|
|
c7b36829df | ||
|
|
790fd16682 | ||
|
|
a2c9699c7e | ||
|
|
fff8e7dab9 | ||
|
|
9887f38b4f | ||
|
|
b5c2acb328 | ||
|
|
6469324572 | ||
|
|
6227e6d1a9 | ||
|
|
80bfe03c0b | ||
|
|
8a82bc713b | ||
|
|
e3137db9b4 | ||
|
|
35d351c596 | ||
|
|
2163eacfbe | ||
|
|
798f449cc1 | ||
|
|
0d2d560950 | ||
|
|
a4e31d0f50 | ||
|
|
96d2043ed8 | ||
|
|
5b3033c6da | ||
|
|
50b1f65f18 | ||
|
|
56058d2c4b | ||
|
|
aeb10293c6 | ||
|
|
7132ac2479 | ||
|
|
d688549310 | ||
|
|
a763d38304 | ||
|
|
62eb97cd16 | ||
|
|
6813616355 | ||
|
|
ee2d574f46 | ||
|
|
945a57d6d8 | ||
|
|
e62ba9d156 | ||
|
|
d3a4136050 | ||
|
|
7a6d95e250 | ||
|
|
6fe585a7aa | ||
|
|
7b25381850 | ||
|
|
409922c3e9 | ||
|
|
2acabf9c4a | ||
|
|
61f7abf43d | ||
|
|
d29c753c6d | ||
|
|
f25e20714c | ||
|
|
2fdec33b1f | ||
|
|
64f5aea163 | ||
|
|
847ed1041b | ||
|
|
74cca0bc4c | ||
|
|
534dd0a594 | ||
|
|
f84612ed75 | ||
|
|
9b2b2c06e5 | ||
|
|
cf1c18aa70 | ||
|
|
418b012550 | ||
|
|
af4016db43 | ||
|
|
efbda2a758 | ||
|
|
a7028d4c62 | ||
|
|
fd272faebd | ||
|
|
a0cbf65f24 | ||
|
|
1a4a3eb07d | ||
|
|
a24e626e00 | ||
|
|
1b86f519a0 | ||
|
|
72d15f1dc1 | ||
|
|
c53a6847fe | ||
|
|
a41c1b3493 | ||
|
|
dd03bb1ca2 | ||
|
|
a9519ceca1 | ||
|
|
ddc72042a1 | ||
|
|
a95b2a23df | ||
|
|
99187c808d | ||
|
|
653f34cb3b | ||
|
|
08eeea4eb4 | ||
|
|
7477e573a5 | ||
|
|
720a47920f | ||
|
|
d686f6f5a8 | ||
|
|
c382f0bbf8 | ||
|
|
2790052e9b | ||
|
|
218a057110 | ||
|
|
27e6d58ffc | ||
|
|
4c2c993f3f | ||
|
|
faca221699 | ||
|
|
f4d8bdbf0f | ||
|
|
aa909d3135 | ||
|
|
cdf5a566c6 | ||
|
|
807b99ae51 | ||
|
|
d93f09078e | ||
|
|
cc12798a87 | ||
|
|
02b1a1d278 | ||
|
|
d978cdf845 | ||
|
|
d5c0b11dbe | ||
|
|
2a8ba07040 | ||
|
|
f8c9a52be5 | ||
|
|
d6f5789659 | ||
|
|
e7b7df6987 | ||
|
|
7d6666f7f3 | ||
|
|
5c83583149 | ||
|
|
0fb73ece9a |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: [patrickgold]
|
||||
custom: ["https://paypal.me/devpatrickgold", "https://explorer.bitcoin.com/btc/address/1GKPJuRTZbVM7L8Kd3wtrqzc259Sjmoh9x"]
|
||||
@@ -9,7 +9,10 @@ provides some general guidelines for each type of contribution.
|
||||
|
||||
Either use the review function within Google Play or email me at
|
||||
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I
|
||||
love to hear from you!
|
||||
love to hear from you! Note, that the amount of feedback emails I get
|
||||
is overwhelmingly high - so if I don't answer or answer really late, I
|
||||
apologize - I guarantee though that I read through every email and that
|
||||
I will use every feedback to improve FlorisBoard :)
|
||||
|
||||
## Translations
|
||||
|
||||
@@ -30,23 +33,52 @@ enables both you and the dev team that we are on the same page before
|
||||
you start on working on your change. If you have any questions, feel
|
||||
free to ask for help at any time!
|
||||
|
||||
## Adding a new keyboard layout / dictionary for locale
|
||||
## Adding a new keyboard layout
|
||||
|
||||
You can now officially add layouts to FlorisBoard as described below.
|
||||
FlorisBoard's core has stabilized enough that adding new content is
|
||||
safe, although there will be some changes in the future.
|
||||
Adding a layout to FlorisBoard is very simple and does not require any
|
||||
coding skills, although you should understand the basics of the JSON
|
||||
syntax (it is very easy though by just looking at some other layout files).
|
||||
There are two main steps in adding new layouts, though the config step can
|
||||
be skipped if you only add a layout without a new default language support.
|
||||
|
||||
Currently you need to modify `app/src/main/assets/ime/config.json` to
|
||||
add the filename of the language/layout to the `characterLayouts`
|
||||
section and the `defaultSubtypes` section, making sure to include
|
||||
the language's IETF BCP 47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)).
|
||||
For example, Dutch as spoken in Belgium is `nl-be`. Use a unique value
|
||||
for `id` to avoid possible crahses caused by duplicate ids.
|
||||
### The config file (`app/src/main/assets/ime/config.json`)
|
||||
|
||||
Add the keyboard layout at `app/src/main/assets/ime/text/characters/<preferredLayout_name_here>.json`,
|
||||
with `code` referring to the characters codepoint and `label` being the
|
||||
respective unicode character.
|
||||
This file is very important, as it defines all default currency sets as
|
||||
well as all default subtypes available in the Settings Subtype UI. Note
|
||||
that you don't have to modify this file if you add a layout for an already
|
||||
pre-configured language.
|
||||
|
||||
- `currencySets`: This is a list of all currency sets, which can be chosen
|
||||
for each subtype. If you consider adding a new one, make sure that the
|
||||
first currency symbol matches the name of the currency set and also
|
||||
ensure that you have exactly 6 currency symbols. This is important as the
|
||||
symbol layouts have exactly 6 slots available to fill these defined
|
||||
currency symbols in.
|
||||
- `defaultSubtyes`: This is a list of all pre-made subtypes. Each time the
|
||||
user selects a language in the `Subtype Add`-dialog, all options configured
|
||||
here will get pre-selected. The language tag must adhere to the IETF BCP
|
||||
47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)).
|
||||
For example, Dutch as spoken in Belgium is `nl-be`. Use a unique value
|
||||
for `id` to avoid possible crashes caused by duplicate ids.
|
||||
|
||||
### Adding the layout
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
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`.
|
||||
@@ -56,12 +88,21 @@ you add. The main field is used for determining if a hint or an accent
|
||||
should take priority, so please make sure to leave main empty and just
|
||||
use relevant for accents which are not-so important.
|
||||
|
||||
For popups of non-`characters` layout, simply add the popup directly to
|
||||
each key via the `popup` field.
|
||||
|
||||
## Adding a new dictionary for a language
|
||||
|
||||
Currently the suggestions implementation is highly experimental and will
|
||||
get a major if not complete rework, so dictionaries are currently not
|
||||
accepted.
|
||||
|
||||
## Bug reporting
|
||||
|
||||
This kind of contribution is the most important, as it tells where
|
||||
FlorisBoard has flaws and thus should be improved to maximize stability
|
||||
and user experience. To make this process as smooth as possible, please
|
||||
use the premade [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
use the pre-made [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
for bug reporting. This makes it easy for us to understand what the bug
|
||||
is and how to solve it.
|
||||
|
||||
@@ -73,3 +114,10 @@ preferred way to capture logs.
|
||||
|
||||
Alternatively, you can also use ADB (Android Debug Bridge) to capture
|
||||
the error log. This is recommended for experienced users only.
|
||||
|
||||
## Donating
|
||||
|
||||
If none of the above options are feasible for you but you still want to
|
||||
show your support, you can also buy me a coffee, so I can stay up all night
|
||||
and chase away bugs or add new cool stuff :)
|
||||
See the `Sponsors` button for available options!
|
||||
|
||||
@@ -75,7 +75,7 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
|
||||
### 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, ...)
|
||||
* [x] Non-latin character layouts (Arabic, Persian, Greek, Russian (JCUKEN))
|
||||
* [x] Non-latin character layouts (Arabic, Persian, Kurdish, Greek, Russian (JCUKEN))
|
||||
* [x] Adapt to situation in app (password, url, text, etc. )
|
||||
* [x] Special character layout(s)
|
||||
* [x] Numeric layout
|
||||
|
||||
@@ -23,7 +23,7 @@ android {
|
||||
applicationId = "dev.patrickgold.florisboard"
|
||||
minSdkVersion(23)
|
||||
targetSdkVersion(30)
|
||||
versionCode(32)
|
||||
versionCode(34)
|
||||
versionName("0.3.10")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -46,7 +46,7 @@ android {
|
||||
create("beta") // Needed because by default the "beta" BuildType does not exist
|
||||
named("beta").configure {
|
||||
applicationIdSuffix = ".beta"
|
||||
versionNameSuffix = "-beta04"
|
||||
versionNameSuffix = "-beta06"
|
||||
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
|
||||
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
|
||||
|
||||
@@ -113,12 +113,11 @@
|
||||
|
||||
<provider
|
||||
android:name="dev.patrickgold.florisboard.ime.clip.provider.FlorisContentProvider"
|
||||
android:authorities="dev.patrickgold.florisboard.provider.clip"
|
||||
android:authorities="${applicationId}.provider.clip"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
</provider>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,227 +1,586 @@
|
||||
{
|
||||
"package": "dev.patrickgold.florisboard",
|
||||
"characterLayouts": {
|
||||
"qwerty": "QWERTY",
|
||||
"qwertz": "QWERTZ",
|
||||
"azerty": "AZERTY",
|
||||
"arabic": "Arabic",
|
||||
"bepo": "BÉPO",
|
||||
"bulgarian_bds": "Bulgarian (BDS)",
|
||||
"bulgarian_phonetic": "Bulgarian (Phonetic)",
|
||||
"canadian_french": "Canadian French (QWERTY)",
|
||||
"colemak": "Colemak",
|
||||
"danish": "Danish (QWERTY)",
|
||||
"dvorak": "Dvorak",
|
||||
"esperanto": "Esperanto",
|
||||
"esperanto_with_hx": "Esperanto with 'ĥ'",
|
||||
"greek": "Ελληνικά",
|
||||
"hebrew": "עברית",
|
||||
"hungarian": "Hungarian (QWERTZ)",
|
||||
"icelandic": "Icelandic (QWERTY)",
|
||||
"kurdish": "کوردی",
|
||||
"norwegian": "Norwegian (QWERTY)",
|
||||
"persian": "Persian",
|
||||
"jcuken_russian": "Russian (JCUKEN)",
|
||||
"serbian_latin": "Serbian (QWERTZ)",
|
||||
"serbian_cyrillic": "Serbian (ЉЊЕРТЗ)",
|
||||
"spanish": "Spanish (QWERTY)",
|
||||
"swedish_finnish": "Swedish/Finnish (QWERTY)",
|
||||
"swiss_german": "Swiss German (QWERTZ)",
|
||||
"swiss_french": "Swiss French (QWERTZ)",
|
||||
"swiss_italian": "Swiss Italian (QWERTZ)",
|
||||
"turkish_q": "Turkish-Q",
|
||||
"turkish_f": "Turkish-F"
|
||||
},
|
||||
"currencySets": [
|
||||
{
|
||||
"name": "azerbaijani_manat",
|
||||
"label": "Azerbaijani manat (₼)",
|
||||
"slots": [
|
||||
{ "code": 8380, "label": "₼" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bitcoin",
|
||||
"label": "Bitcoin (₿)",
|
||||
"slots": [
|
||||
{ "code": 8383, "label": "₿" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dollar",
|
||||
"label": "Dollar ($)",
|
||||
"slots": [
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" },
|
||||
{ "code": 8369, "label": "₱" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "euro",
|
||||
"label": "Euro (€)",
|
||||
"slots": [
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" },
|
||||
{ "code": 8369, "label": "₱" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "indian_rupee",
|
||||
"label": "Indian rupee (₹)",
|
||||
"slots": [
|
||||
{ "code": 8377, "label": "₹" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "iranian_rial",
|
||||
"label": "Iranian rial (﷼)",
|
||||
"slots": [
|
||||
{ "code":65020, "label": "﷼" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "israeli_new_shekel",
|
||||
"label": "Israeli new shekel (₪)",
|
||||
"slots": [
|
||||
{ "code": 8362, "label": "₪" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "kazakhstani_tenge",
|
||||
"label": "Kazakhstani tenge (₸)",
|
||||
"slots": [
|
||||
{ "code": 8380, "label": "₸" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lao_kip",
|
||||
"label": "Lao kip (₭)",
|
||||
"slots": [
|
||||
{ "code": 8365, "label": "₭" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mongolian_togrog",
|
||||
"label": "Mongolian tögrög (₮)",
|
||||
"slots": [
|
||||
{ "code": 8366, "label": "₮" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nigerian_naira",
|
||||
"label": "Nigerian naira (₦)",
|
||||
"slots": [
|
||||
{ "code": 8358, "label": "₦" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pakistani_rupee",
|
||||
"label": "Pakistani rupee (₨)",
|
||||
"slots": [
|
||||
{ "code": 8360, "label": "₨" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "paraguayan_guarani",
|
||||
"label": "Paraguayan guaraní (₲)",
|
||||
"slots": [
|
||||
{ "code": 8370, "label": "₲" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "peso",
|
||||
"label": "Peso (₱)",
|
||||
"slots": [
|
||||
{ "code": 8369, "label": "₱" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pound",
|
||||
"label": "Pound (£)",
|
||||
"slots": [
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 165, "label": "¥" },
|
||||
{ "code": 8369, "label": "₱" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "russian_ruble",
|
||||
"label": "Russian ruble (₽)",
|
||||
"slots": [
|
||||
{ "code": 8381, "label": "₽" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "south_korean_won",
|
||||
"label": "South Korean won (₩)",
|
||||
"slots": [
|
||||
{ "code": 8361, "label": "₩" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "turkish_lira",
|
||||
"label": "Turkish lira (₺)",
|
||||
"slots": [
|
||||
{ "code": 8378, "label": "₺" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ukrainian_hryvnia",
|
||||
"label": "Ukrainian hryvnia (₴)",
|
||||
"slots": [
|
||||
{ "code": 8372, "label": "₴" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "yen",
|
||||
"label": "Yen (¥)",
|
||||
"slots": [
|
||||
{ "code": 165, "label": "¥" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 8369, "label": "₱" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"defaultSubtypes": [
|
||||
{
|
||||
"id": 101,
|
||||
"languageTag": "en-US",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"languageTag": "en-UK",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "pound",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"languageTag": "en-CA",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 104,
|
||||
"languageTag": "en-AU",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 201,
|
||||
"languageTag": "de-DE",
|
||||
"preferredLayout": "qwertz"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 202,
|
||||
"languageTag": "de-AT",
|
||||
"preferredLayout": "qwertz"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 203,
|
||||
"languageTag": "de-CH",
|
||||
"preferredLayout": "swiss_german"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swiss_german"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 301,
|
||||
"languageTag": "fr-FR",
|
||||
"preferredLayout": "azerty"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "azerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 302,
|
||||
"languageTag": "fr-CA",
|
||||
"preferredLayout": "canadian_french"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "canadian_french"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 303,
|
||||
"languageTag": "fr-CH",
|
||||
"preferredLayout": "swiss_french"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swiss_french"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 401,
|
||||
"languageTag": "it-IT",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 402,
|
||||
"languageTag": "it-CH",
|
||||
"preferredLayout": "swiss_italian"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swiss_italian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 501,
|
||||
"languageTag": "es-ES",
|
||||
"preferredLayout": "spanish"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "spanish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 502,
|
||||
"languageTag": "es-US",
|
||||
"preferredLayout": "spanish"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "spanish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 503,
|
||||
"languageTag": "es-419",
|
||||
"preferredLayout": "spanish"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "spanish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 601,
|
||||
"languageTag": "pt-PT",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 602,
|
||||
"languageTag": "pt-BR",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 701,
|
||||
"languageTag": "nb-NO",
|
||||
"preferredLayout": "norwegian"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "norwegian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 702,
|
||||
"languageTag": "nn-NO",
|
||||
"preferredLayout": "norwegian"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "norwegian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 711,
|
||||
"languageTag": "sv-SE",
|
||||
"preferredLayout": "swedish_finnish"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "swedish_finnish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 721,
|
||||
"languageTag": "fi-FI",
|
||||
"preferredLayout": "swedish_finnish"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swedish_finnish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 731,
|
||||
"languageTag": "da-DK",
|
||||
"preferredLayout": "danish"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "danish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 741,
|
||||
"languageTag": "is-IS",
|
||||
"preferredLayout": "icelandic"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "icelandic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 751,
|
||||
"languageTag": "fo",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "faroese"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 801,
|
||||
"languageTag": "fa-FA",
|
||||
"preferredLayout": "persian"
|
||||
"currencySet": "iranian_rial",
|
||||
"preferred": {
|
||||
"characters": "persian",
|
||||
"symbols": "persian",
|
||||
"symbols2": "persian",
|
||||
"numericRow": "persian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 901,
|
||||
"languageTag": "ar",
|
||||
"preferredLayout": "arabic"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "arabic",
|
||||
"symbols": "eastern",
|
||||
"symbols2": "eastern",
|
||||
"numericRow": "eastern_arabic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1001,
|
||||
"languageTag": "hu",
|
||||
"preferredLayout": "hungarian"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "hungarian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1101,
|
||||
"languageTag": "eo",
|
||||
"preferredLayout": "esperanto"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "esperanto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1201,
|
||||
"languageTag": "hr",
|
||||
"preferredLayout": "qwertz"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1301,
|
||||
"languageTag": "ru",
|
||||
"preferredLayout": "jcuken_russian"
|
||||
"currencySet": "russian_ruble",
|
||||
"preferred": {
|
||||
"characters": "jcuken_russian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1401,
|
||||
"languageTag": "el",
|
||||
"preferredLayout": "greek"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "greek"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1501,
|
||||
"languageTag": "ro",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1601,
|
||||
"languageTag": "pl",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1701,
|
||||
"languageTag": "bg-bg",
|
||||
"preferredLayout": "bulgarian_phonetic"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "bulgarian_phonetic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1801,
|
||||
"languageTag": "tr",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "turkish_lira",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1901,
|
||||
"languageTag": "iw-IL",
|
||||
"preferredLayout": "hebrew"
|
||||
"currencySet": "israeli_new_shekel",
|
||||
"preferred": {
|
||||
"characters": "hebrew"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2001,
|
||||
"languageTag": "ckb",
|
||||
"preferredLayout": "kurdish"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "kurdish",
|
||||
"symbols": "eastern",
|
||||
"symbols2": "eastern",
|
||||
"numericRow": "eastern_arabic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2101,
|
||||
"languageTag": "sr-RS",
|
||||
"preferredLayout": "serbian_cyrillic"
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "serbian_cyrillic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2201,
|
||||
"languageTag": "lv-LV",
|
||||
"preferredLayout": "qwerty"
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2301,
|
||||
"languageTag": "ku",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "kurdish_kurmanci"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2601,
|
||||
"languageTag": "IPA-IPA",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "ipa",
|
||||
"symbols": "ipa",
|
||||
"symbols2": "ipa"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1
app/src/main/assets/ime/dict/data.json
Normal file
1
app/src/main/assets/ime/dict/data.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "arabic",
|
||||
"label": "Arabic",
|
||||
"authors": [ "HeiWiper" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "arabic",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "azerty",
|
||||
"label": "AZERTY",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "bepo",
|
||||
"label": "BÉPO",
|
||||
"authors": [ "salamandar" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "bulgarian_bds",
|
||||
"label": "Bulgarian (BDS)",
|
||||
"authors": [ "iorvethe" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "bulgarian_phonetic",
|
||||
"label": "Bulgarian (Phonetic)",
|
||||
"authors": [ "iorvethe" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "canadian_french",
|
||||
"label": "Canadian French (QWERTY)",
|
||||
"authors": [ "The-Quantum-Alpha" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "colemak",
|
||||
"label": "Colemak",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "danish",
|
||||
"label": "Danish (QWERTY)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "dvorak",
|
||||
"label": "Dvorak",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "dvorak",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "esperanto",
|
||||
"label": "Esperanto",
|
||||
"authors": [ "jeremiah-miller", "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "esperanto_with_hx",
|
||||
"label": "Esperanto with 'ĥ'",
|
||||
"authors": [ "jeremiah-miller", "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -4,17 +4,8 @@
|
||||
"authors": [ "GoRaN" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"ق": {
|
||||
"relevant": [
|
||||
{ "code": 1647, "label": "ٯ" }
|
||||
]
|
||||
},
|
||||
"ئ": {
|
||||
"relevant": [
|
||||
{"code": 1569, "label": "ء" }
|
||||
]
|
||||
},
|
||||
"ە": {
|
||||
|
||||
"ﻪ": {
|
||||
"relevant": [
|
||||
{ "code": 1577, "label": "ة" },
|
||||
{ "code": 1729, "label": "ـہ" }
|
||||
@@ -26,45 +17,70 @@
|
||||
{ "code": 1682, "label": "ڒ" }
|
||||
]
|
||||
},
|
||||
"ف": {
|
||||
|
||||
|
||||
"ی": {
|
||||
"relevant": [
|
||||
{ "code": 1701, "label": "ڥ" },
|
||||
{ "code": 1698, "label": "ڢ" },
|
||||
{ "code": 1700, "label": "ڤ" },
|
||||
{ "code": 1697, "label": "ڡ" }
|
||||
{ "code": 1746, "label": "ے" },
|
||||
{ "code": 1610, "label": "ي" },
|
||||
{ "code": 1744, "label": "ې" },
|
||||
{ "code": 1741, "label": "ۍ" },
|
||||
{ "code": 1742, "label": "ێ" },
|
||||
{ "code": 1597, "label": "ؽ" }
|
||||
]
|
||||
},
|
||||
"": {
|
||||
|
||||
|
||||
"ﺋ": {
|
||||
"relevant": [
|
||||
{ "code": 65163, "label": "ﺋ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 65139, "label": "ﹳ" }
|
||||
]
|
||||
},
|
||||
|
||||
"ع": {
|
||||
"relevant": [
|
||||
{ "code": 1551, "label": "؏" },
|
||||
{ "code": 1594, "label": "غ" }
|
||||
{ "code": 1551, "label": "؏" }
|
||||
]
|
||||
},
|
||||
|
||||
"ۆ": {
|
||||
"relevant": [
|
||||
{ "code": 1743, "label": "ۏ" },
|
||||
{ "code": 1735, "label": "ۇ" },
|
||||
{ "code": 1737, "label": "ۉ" },
|
||||
{ "code": 1738, "label": "ۊ" },
|
||||
{ "code": 1572, "label": "ؤ" },
|
||||
{ "code": 1739, "label": "ۋ" }
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
|
||||
"ف": {
|
||||
"relevant": [
|
||||
{ "code": 1701, "label": "ڥ" },
|
||||
{ "code": 1700, "label": "ڤ" },
|
||||
{ "code": 1698, "label": "ڢ" },
|
||||
{ "code": 1697, "label": "ڡ" }
|
||||
]
|
||||
},
|
||||
|
||||
"د": {
|
||||
"relevant": [
|
||||
{ "code": 1676, "label": "ڌ" },
|
||||
{ "code": 64390, "label": "ﮆ" },
|
||||
{ "code": 1584, "label": "ذ" },
|
||||
{ "code": 64390, "label": "ﮆ" },
|
||||
{ "code": 1774, "label": "ۮ" }
|
||||
]
|
||||
},
|
||||
"ه": {
|
||||
"ھ": {
|
||||
"relevant": [
|
||||
{ "code": 1726, "label": "ھ" }
|
||||
]
|
||||
},
|
||||
"خ": {
|
||||
"relevant": [
|
||||
{ "code": 1567, "label": "؟" }
|
||||
]
|
||||
},
|
||||
|
||||
"س": {
|
||||
"relevant": [
|
||||
{ "code": 1589, "label": "ص" }
|
||||
@@ -110,42 +126,23 @@
|
||||
{ "code": 1603, "label": "ك"}
|
||||
]
|
||||
},
|
||||
"ی": {
|
||||
"relevant": [
|
||||
{ "code": 1746, "label": "ے" },
|
||||
{ "code": 1610, "label": "ي" },
|
||||
{ "code": 1744, "label": "ې" },
|
||||
{ "code": 1741, "label": "ۍ" },
|
||||
{ "code": 1742, "label": "ێ" },
|
||||
{ "code": 1597, "label": "ؽ" }
|
||||
]
|
||||
},
|
||||
"ۆ": {
|
||||
"relevant": [
|
||||
{ "code": 1743, "label": "ۏ" },
|
||||
{ "code": 1735, "label": "ۇ" },
|
||||
{ "code": 1737, "label": "ۉ" },
|
||||
{ "code": 1738, "label": "ۊ" },
|
||||
{ "code": 1572, "label": "ؤ" },
|
||||
{ "code": 1739, "label": "ۋ" }
|
||||
]
|
||||
},
|
||||
|
||||
"~right": {
|
||||
"main": { "code": 1567, "label": "؟" },
|
||||
"relevant": [
|
||||
{ "code": 1600, "label": "ــ" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 1548, "label": "،" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 1549, "label": "؍" },
|
||||
{ "code": 1563, "label": "؛" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 42, "label": "*" },
|
||||
{ "code": 1563, "label": "؛" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 1549, "label": "؍" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 95, "label": "_" },
|
||||
{ "code": 45, "label": "-" }
|
||||
{ "code": 1600, "label": "▬" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 1548, "label": "،" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,71 +4,29 @@
|
||||
"authors": [ "PHELAT" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"ض": {
|
||||
"relevant": [
|
||||
{ "code": 1777, "label": "۱" }
|
||||
]
|
||||
},
|
||||
"ص": {
|
||||
"relevant": [
|
||||
{ "code": 1778, "label": "۲" }
|
||||
]
|
||||
},
|
||||
"ث": {
|
||||
"relevant": [
|
||||
{ "code": 1779, "label": "۳" }
|
||||
]
|
||||
},
|
||||
"ق": {
|
||||
"relevant": [
|
||||
{ "code": 1780, "label": "۴" }
|
||||
]
|
||||
},
|
||||
"ف": {
|
||||
"relevant": [
|
||||
{ "code": 1781, "label": "۵" }
|
||||
]
|
||||
},
|
||||
"غ": {
|
||||
"relevant": [
|
||||
{ "code": 1782, "label": "۶" }
|
||||
]
|
||||
},
|
||||
"ع": {
|
||||
"relevant": [
|
||||
{ "code": 1783, "label": "۷" }
|
||||
]
|
||||
},
|
||||
"ه": {
|
||||
"relevant": [
|
||||
{ "code": 1784, "label": "۸" }
|
||||
]
|
||||
},
|
||||
"خ": {
|
||||
"relevant": [
|
||||
{ "code": 1785, "label": "۹" }
|
||||
]
|
||||
},
|
||||
"ح": {
|
||||
"relevant": [
|
||||
{ "code": 1776, "label": "۰" }
|
||||
]
|
||||
},
|
||||
"ی": {
|
||||
"relevant": [
|
||||
{ "code": 1574, "label": "ئ" },
|
||||
{ "code": 1610, "label": "ي" }
|
||||
{ "code": 1610, "label": "ي" },
|
||||
{ "code": 1746, "label": "ے" }
|
||||
]
|
||||
},
|
||||
"ا": {
|
||||
"relevant": [
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1649, "label": "ٱ" },
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 1571, "label": "أ" },
|
||||
{ "code": 1573, "label": "إ" }
|
||||
]
|
||||
},
|
||||
"ه": {
|
||||
"relevant": [
|
||||
{ "code": 1729, "label": "ہ" },
|
||||
{ "code": 1728, "label": "ۀ" },
|
||||
{ "code": 1726, "label": "ھ" }
|
||||
]
|
||||
},
|
||||
"ت": {
|
||||
"relevant": [
|
||||
{ "code": 1577, "label": "ة" }
|
||||
@@ -76,8 +34,7 @@
|
||||
},
|
||||
"ک": {
|
||||
"relevant": [
|
||||
{ "code": 1706, "label": "ڪ"},
|
||||
{ "code": 1603, "label": "ك" }
|
||||
{ "code": 1706, "label": "ڪ"}
|
||||
]
|
||||
},
|
||||
"ز": {
|
||||
@@ -114,9 +71,9 @@
|
||||
"main": { "code": -255, "label": ".ir"},
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
138
app/src/main/assets/ime/text/characters/extended_popups/fo.json
Normal file
138
app/src/main/assets/ime/text/characters/extended_popups/fo.json
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "fo",
|
||||
"authors": [ "BinFlush" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"main": { "code": 225, "label": "á" },
|
||||
"relevant": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"main": { "code": 237, "label": "í" },
|
||||
"relevant": [
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"l": {
|
||||
"relevant": [
|
||||
{ "code": 322, "label": "ł" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"main": { "code": 243, "label": "ó" },
|
||||
"relevant": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"t": {
|
||||
"relevant": [
|
||||
{ "code": 254, "label": "þ" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 353, "label": "š" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"main": { "code": 250, "label": "ú" },
|
||||
"relevant": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"main": { "code": 253, "label": "ý" },
|
||||
"relevant": [
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
]
|
||||
},
|
||||
"æ": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"ð": {
|
||||
"relevant": [
|
||||
{ "code": 254, "label": "þ" }
|
||||
]
|
||||
},
|
||||
"ø": {
|
||||
"relevant": [
|
||||
{ "code": 246, "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": ".fo" },
|
||||
{ "code": -255, "label": ".dk" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "hu",
|
||||
"authors": [ "zoli111" ],
|
||||
"authors": [ "zoli111, gabik65" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
@@ -26,11 +26,6 @@
|
||||
{ "code": 337, "label": "ő" }
|
||||
]
|
||||
},
|
||||
"ö": {
|
||||
"relevant": [
|
||||
{ "code": 337, "label": "ő" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
@@ -38,11 +33,6 @@
|
||||
{ "code": 369, "label": "ű" }
|
||||
]
|
||||
},
|
||||
"ü": {
|
||||
"relevant": [
|
||||
{ "code": 369, "label": "ű" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
|
||||
132
app/src/main/assets/ime/text/characters/extended_popups/ku.json
Normal file
132
app/src/main/assets/ime/text/characters/extended_popups/ku.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "ku",
|
||||
"authors": [ "GoRaN" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"main": { "code": 231, "label": "ç" },
|
||||
"relevant": [
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 265, "label": "ĉ" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"r": {
|
||||
"main": { "code": 345, "label": "ř" }
|
||||
|
||||
},
|
||||
"g": {
|
||||
"main": { "code": 285, "label": "ĝ" }
|
||||
|
||||
|
||||
},
|
||||
"h": {
|
||||
"main": { "code": 293, "label": "ĥ" }
|
||||
}
|
||||
},
|
||||
"j": {
|
||||
"main": { "code": 309, "label": "ĵ" }
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 328, "label": "ň" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"main": { "code": 246, "label": "ö" },
|
||||
"relevant": [
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 244, "label": "ô" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"main": { "code": 219, "label": "ș" },
|
||||
"relevant": [
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 349, "label": "ŝ" },
|
||||
{ "code": 353, "label": "š" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"main": { "code": 251, "label": "û" },
|
||||
"relevant": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 253, "label": "ý" }
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 382, "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": ".krd" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
44
app/src/main/assets/ime/text/characters/faroese.json
Normal file
44
app/src/main/assets/ime/text/characters/faroese.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "faroese",
|
||||
"label": "Faroese (QWERTY)",
|
||||
"authors": [ "BinFlush" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 240, "label": "ð" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" }
|
||||
]
|
||||
]
|
||||
}
|
||||
46
app/src/main/assets/ime/text/characters/german.json
Normal file
46
app/src/main/assets/ime/text/characters/german.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "german",
|
||||
"label": "German (QWERTZ)",
|
||||
"authors": [ "mahmoudk1000" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 223, "label": "ß", "popup": {
|
||||
}, "shift": { "code": 7838, "label": "ẞ" } }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "greek",
|
||||
"label": "Ελληνικά",
|
||||
"authors": [ "tsiflimagas" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "hebrew",
|
||||
"label": "עברית",
|
||||
"authors": [ "Antony" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "hebrew",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "hungarian",
|
||||
"authors": [ "zoli111" ],
|
||||
"label": "Hungarian (QWERTZ)",
|
||||
"authors": [ "zoli111, gabik65" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,8 +15,7 @@
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
{ "code": 112, "label": "p" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
@@ -26,9 +26,7 @@
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 225, "label": "á" }
|
||||
{ "code": 108, "label": "l" }
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
@@ -37,8 +35,7 @@
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
{ "code": 109, "label": "m" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "icelandic",
|
||||
"label": "Icelandic (QWERTY)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
285
app/src/main/assets/ime/text/characters/ipa.json
Normal file
285
app/src/main/assets/ime/text/characters/ipa.json
Normal file
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "ipa",
|
||||
"label": "International Phonetic Alphabet",
|
||||
"authors": [
|
||||
"Huy-Ngo"
|
||||
],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{
|
||||
"code": 113, "label": "q"
|
||||
},
|
||||
{
|
||||
"code": 119, "label": "w",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 695, "label": "◌ʷ" },
|
||||
{ "code": 653, "label": "ʍ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 101, "label": "e",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 600, "label": "ɘ" },
|
||||
{ "code": 604, "label": "ɜ" },
|
||||
{ "code": 601, "label": "ə" },
|
||||
{ "code": 602, "label": "ɚ" },
|
||||
{ "code": 7498, "label": "◌ᵊ" },
|
||||
{ "code": 603, "label": "ɛ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 114, "label": "r",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 637, "label": "ɽ" },
|
||||
{ "code": 633, "label": "ɹ" },
|
||||
{ "code": 638, "label": "ɾ" },
|
||||
{ "code": 635, "label": "ɻ" },
|
||||
{ "code": 641, "label": "ʁ" },
|
||||
{ "code": 734, "label": "◌˞" },
|
||||
{ "code": 640, "label": "ʀ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 116, "label": "t",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 648, "label": "ʈ" },
|
||||
{ "code": 7615, "label": "◌ᶿ" },
|
||||
{ "code": 952, "label": "θ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 121, "label": "y",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 612, "label": "ɤ" },
|
||||
{ "code": 655, "label": "ʏ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 117, "label": "u",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 7551, "label": "ᵿ" },
|
||||
{ "code": 649, "label": "ʉ" },
|
||||
{ "code": 650, "label": "ʊ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 105, "label": "i",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 7574, "label": "ᵻ" },
|
||||
{ "code": 616, "label": "ɨ" },
|
||||
{ "code": 618, "label": "ɪ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 111, "label": "o",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 664, "label": "ʘ" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 606, "label": "ɞ" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 629, "label": "ɵ" },
|
||||
{ "code": 630, "label": "ɶ" },
|
||||
{ "code": 596, "label": "ɔ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 112, "label": "p"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"code": 97, "label": "a",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 594, "label": "ɒ" },
|
||||
{ "code": 592, "label": "ɐ" },
|
||||
{ "code": 593, "label": "ɑ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 115, "label": "s",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 642, "label": "ʂ" },
|
||||
{ "code": 597, "label": "ɕ" },
|
||||
{ "code": 643, "label": "ʃ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 100, "label": "d",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 598, "label": "ɖ" },
|
||||
{ "code": 599, "label": "ɗ" },
|
||||
{ "code": 240, "label": "ð" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 102, "label": "f",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 632, "label": "ɸ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 609, "label": "ɡ",
|
||||
"popup": {
|
||||
"main": { "code": 103, "label": "g" },
|
||||
"relevant": [
|
||||
{ "code": 608, "label": "ɠ" },
|
||||
{ "code": 610, "label": "ɢ" },
|
||||
{ "code": 667, "label": "ʛ" },
|
||||
{ "code": 667, "label": "ʛ" },
|
||||
{ "code": 736, "label": "◌ˠ" },
|
||||
{ "code": 611, "label": "ɣ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 104, "label": "h",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 614, "label": "ɦ" },
|
||||
{ "code": 615, "label": "ɧ" },
|
||||
{ "code": 295, "label": "ħ" },
|
||||
{ "code": 613, "label": "ɥ" },
|
||||
{ "code": 688, "label": "◌ʰ" },
|
||||
{ "code": 668, "label": "ʜ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 106, "label": "j",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 668, "label": "ʝ" },
|
||||
{ "code": 607, "label": "ɟ" },
|
||||
{ "code": 690, "label": "◌ʲ" },
|
||||
{ "code": 664, "label": "ʄ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 107, "label": "k"
|
||||
},
|
||||
{
|
||||
"code": 108, "label": "l",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 620, "label": "ɬ" },
|
||||
{ "code": 634, "label": "ɺ" },
|
||||
{ "code": 671, "label": "ʟ" },
|
||||
{ "code": 654, "label": "ʎ" },
|
||||
{ "code": 737, "label": "◌ˡ" },
|
||||
{ "code": 622, "label": "ɮ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 660, "label": "ʔ",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 661, "label": "ʕ" },
|
||||
{ "code": 674, "label": "ʢ" },
|
||||
{ "code": 740, "label": "◌ˤ" },
|
||||
{ "code": 673, "label": "ʡ" }
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"code": 122, "label": "z",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 656, "label": "ʐ" },
|
||||
{ "code": 657, "label": "ʑ" },
|
||||
{ "code": 658, "label": "ʒ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 120, "label": "x",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 739, "label": "◌ˣ" },
|
||||
{ "code": 967, "label": "χ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 99, "label": "c",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 118, "label": "v",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 651, "label": "ʋ" },
|
||||
{ "code": 652, "label": "ʌ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 98, "label": "b",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 595, "label": "ɓ" },
|
||||
{ "code": 665, "label": "ʙ" },
|
||||
{ "code": 946, "label": "β" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 110, "label": "n",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 626, "label": "ɲ" },
|
||||
{ "code": 627, "label": "ɳ" },
|
||||
{ "code": 628, "label": "ɴ" },
|
||||
{ "code": 8319, "label": "◌ⁿ" },
|
||||
{ "code": 771, "label": "◌̃" },
|
||||
{ "code": 631, "label": "ŋ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": 109, "label": "m",
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 625, "label": "ɱ" },
|
||||
{ "code": 624, "label": "ɰ" },
|
||||
{ "code": 623, "label": "ɯ" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "jcuken_russian",
|
||||
"label": "Russian (JCUKEN)",
|
||||
"authors": [ "williamtheaker" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "kurdish",
|
||||
"label": "کوردی",
|
||||
"authors": [ "GoRaN" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "kurdish",
|
||||
@@ -12,19 +13,21 @@
|
||||
{ "code": 1608, "label": "و", "popup": {
|
||||
"main": { "code": -255, "label": "وو" }
|
||||
} },
|
||||
{ "code": 1749, "label": "ﻪ", "popup": {
|
||||
"main": { "code": 1577, "label": "ة" }
|
||||
} },
|
||||
{ "code": 1749, "label": "ﻪ" },
|
||||
{ "code": 1585, "label": "ر" },
|
||||
|
||||
{ "code": 1578, "label": "ت", "popup": {
|
||||
"main": { "code": 1591, "label": "ط" }
|
||||
} },
|
||||
{ "code": 1740, "label": "ی" },
|
||||
{ "code": 1574, "label": "ﺋ", "popup": {
|
||||
"main": { "code": 1569, "label": "ء" }
|
||||
|
||||
{ "code": 1574, "label": "ﺋ"},
|
||||
|
||||
{ "code": 1593, "label": "ع", "popup": {
|
||||
"main": { "code": 1594, "label": "غ" }
|
||||
} },
|
||||
{ "code": 1593, "label": "ع" },
|
||||
{ "code": 1734, "label": "ۆ" },
|
||||
|
||||
{ "code": 1662, "label": "پ", "popup": {
|
||||
"main": { "code": 1579, "label": "ث" }
|
||||
} }
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "kurdish_kurmanci",
|
||||
"label": "Kurdî",
|
||||
"authors": [ "GoRaN" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 305, "label": "ı" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 238, "label": "î" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 351, "label": "ş" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "kurdish_standard",
|
||||
"label": "کوردی - ستاندارد",
|
||||
"authors": [ "GoRaN" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "kurdish",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "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": 1594, "label": "غ" },
|
||||
{ "code": 1593, "label": "ع"},
|
||||
{ "code": 1607, "label": "ھ" },
|
||||
{ "code": 1749, "label": "ﻪ" },
|
||||
|
||||
{ "code": 1578, "label": "ت", "popup": {
|
||||
"main": { "code": 1591, "label": "ط" }
|
||||
} },
|
||||
{ "code": 1581, "label": "ح" },
|
||||
{ "code": 1582, "label": "خ" }
|
||||
],
|
||||
[
|
||||
{ "code": 1588, "label": "ش" },
|
||||
{ "code": 1587, "label": "س" },
|
||||
{ "code": 1740, "label": "ی" },
|
||||
{ "code": 1742, "label": "ێ" },
|
||||
{ "code": 1604, "label": "ل" },
|
||||
{ "code": 1717, "label": "ڵ" },
|
||||
{ "code": 1575, "label": "ا" },
|
||||
{ "code": 1606, "label": "ن" },
|
||||
{ "code": 1580, "label": "ج" },
|
||||
{ "code": 1670, "label": "چ" }
|
||||
],
|
||||
[
|
||||
{ "code": 1576, "label": "ب" },
|
||||
{ "code": 1586, "label": "ز", "popup": {
|
||||
"main": {"code": 1592, "label": "ظ" }
|
||||
} },
|
||||
{ "code": 1585, "label": "ر" },
|
||||
{ "code": 1685, "label": "ڕ" },
|
||||
{ "code": 1583, "label": "د" },
|
||||
{ "code": -255, "label": "وو" },
|
||||
{ "code": 1608, "label": "و" },
|
||||
{ "code": 1734, "label": "ۆ" },
|
||||
{ "code": 1705, "label": "ک" },
|
||||
{ "code": 1711, "label": "گ" }
|
||||
|
||||
],
|
||||
[
|
||||
{ "code": 1600, "label": "kashida", "variation": "normal" },
|
||||
{ "code": 1574, "label": "ﺋ"},
|
||||
|
||||
{ "code": 1662, "label": "پ", "popup": {
|
||||
"main": { "code": 1579, "label": "ث" }
|
||||
} },
|
||||
{ "code": 1688, "label": "ژ" },
|
||||
{ "code": 1605, "label": "م" },
|
||||
{ "code": 1567, "label": "؟" },
|
||||
{ "code": 1548, "label": "،" },
|
||||
{ "code": 46, "label": "." }
|
||||
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "default",
|
||||
"name": "$default",
|
||||
"label": "Default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "arabic",
|
||||
"label": "Arabic",
|
||||
"authors": [ "HeiWiper" ],
|
||||
"direction": "rtl",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "dvorak",
|
||||
"label": "Dvorak",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "hebrew",
|
||||
"label": "עברית",
|
||||
"authors": [ "Antony" ],
|
||||
"direction": "rtl",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "kurdish",
|
||||
"label": "کوردی",
|
||||
"authors": [ "GoRaN" ],
|
||||
"direction": "rtl",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "persian",
|
||||
"label": "Persian",
|
||||
"authors": [ "PHELAT" ],
|
||||
"direction": "rtl",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "norwegian",
|
||||
"label": "Norwegian (QWERTY)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "persian",
|
||||
"label": "Persian",
|
||||
"authors": [ "PHELAT" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "persian",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "qwerty",
|
||||
"label": "QWERTY",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "qwertz",
|
||||
"label": "QWERTZ",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "serbian_cyrillic",
|
||||
"label": "Serbian (ЉЊЕРТЗ)",
|
||||
"authors": ["GrbavaCigla"],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "serbian_latin",
|
||||
"label": "Serbian (QWERTZ)",
|
||||
"authors": ["GrbavaCigla"],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "spanish",
|
||||
"label": "Spanish (QWERTY)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swedish_finnish",
|
||||
"label": "Swedish/Finnish (QWERTY)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swiss_french",
|
||||
"label": "Swiss French (QWERTZ)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swiss_german",
|
||||
"label": "Swiss German (QWERTZ)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swiss_italian",
|
||||
"label": "Swiss Italian (QWERTZ)",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "turkish_f",
|
||||
"label": "Turkish-F",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "turkish_q",
|
||||
"label": "Turkish-Q",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
46
app/src/main/assets/ime/text/characters/workman.json
Normal file
46
app/src/main/assets/ime/text/characters/workman.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "workman",
|
||||
"label": "Workman",
|
||||
"authors": [ "icyphox" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 59, "label": ";", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 58, "label": ":" }
|
||||
]
|
||||
}, "shift": { "code": 58, "label": ":" } }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 105, "label": "i" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "extension",
|
||||
"name": "clipboard_cursor_row",
|
||||
"label": "Clipboard Cursor Row",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "numeric_advanced",
|
||||
"name": "default",
|
||||
"name": "western_arabic",
|
||||
"label": "Western Arabic",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
91
app/src/main/assets/ime/text/numeric/row/eastern_arabic.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/eastern_arabic.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "eastern_arabic",
|
||||
"label": "Eastern Arabic",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 1633, "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": 1634, "label": "٢", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1635, "label": "٣", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1636, "label": "٤", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1637, "label": "٥", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1638, "label": "٦", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1639, "label": "٧", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1640, "label": "٨", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1641, "label": "٩", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1632, "label": "٠", "type": "numeric", "popup": {
|
||||
"main": { "code": 48, "label": "0" },
|
||||
"relevant": [
|
||||
{ "code": 8319, "label": "ⁿ" },
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8304, "label": "⁰" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
91
app/src/main/assets/ime/text/numeric/row/persian.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/persian.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "persian",
|
||||
"label": "Persian",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 1777, "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": 1778, "label": "۲", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1779, "label": "۳", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1780, "label": "۴", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1781, "label": "۵", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1782, "label": "۶", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1783, "label": "۷", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1784, "label": "۸", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1785, "label": "۹", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1776, "label": "۰", "type": "numeric", "popup": {
|
||||
"main": { "code": 48, "label": "0" },
|
||||
"relevant": [
|
||||
{ "code": 8319, "label": "ⁿ" },
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8304, "label": "⁰" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "extension",
|
||||
"name": "number_row",
|
||||
"type": "numeric_row",
|
||||
"name": "western_arabic",
|
||||
"label": "Western Arabic",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "default",
|
||||
"name": "western_arabic",
|
||||
"label": "Western Arabic",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "phone",
|
||||
"name": "default",
|
||||
"name": "telpad",
|
||||
"label": "Telephone Pad",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "phone2",
|
||||
"name": "default",
|
||||
"name": "telpad",
|
||||
"label": "Telephone Pad",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
102
app/src/main/assets/ime/text/symbols/eastern.json
Normal file
102
app/src/main/assets/ime/text/symbols/eastern.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"type": "symbols",
|
||||
"name": "eastern",
|
||||
"label": "Eastern",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "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": 1642, "label": "٪", "popup": {
|
||||
"main": { "code": 37, "label": "%" },
|
||||
"relevant": [
|
||||
{ "code": 8453, "label": "℅" },
|
||||
{ "code": 8240, "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":64830, "label": "﴾" },
|
||||
"relevant": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 123, "label": "{" }
|
||||
]
|
||||
} },
|
||||
{ "code": 41, "label": ")", "popup": {
|
||||
"main": { "code":64831, "label": "﴿" },
|
||||
"relevant": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 62, "label": ">" },
|
||||
{ "code": 125, "label": "}" }
|
||||
]
|
||||
} },
|
||||
{ "code": 47, "label": "/" }
|
||||
],
|
||||
[
|
||||
{ "code": 42, "label": "*", "popup": {
|
||||
"main": { "code": 9733, "label": "★" },
|
||||
"relevant": [
|
||||
{ "code": 1645, "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": 1563, "label": "؛", "popup": {
|
||||
"main": { "code": 59, "label": ";" }
|
||||
} },
|
||||
{ "code": 33, "label": "!", "popup": {
|
||||
"main": { "code": 161, "label": "¡" }
|
||||
} },
|
||||
{ "code": 1567, "label": "؟", "popup": {
|
||||
"main": { "code": 63, "label": "?" },
|
||||
"relevant": [
|
||||
{ "code": 191, "label": "¿" },
|
||||
{ "code": 8253, "label": "‽" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
97
app/src/main/assets/ime/text/symbols/ipa.json
Normal file
97
app/src/main/assets/ime/text/symbols/ipa.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"type": "symbols",
|
||||
"name": "ipa",
|
||||
"label": "International Phonetic Alphabet",
|
||||
"authors": [ "Huy-Ngo" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 712, "label": "ˈ", "popup": {
|
||||
"main": { "code": 716, "label": "ˌ" }
|
||||
} },
|
||||
{ "code": 720, "label": "ː", "popup": {
|
||||
"main": { "code": 721, "label": "ˑ" },
|
||||
"relevant": [
|
||||
{ "code": 774, "label": "◌̆" }
|
||||
]
|
||||
} },
|
||||
{ "code": 8599, "label": "↗︎", "popup": {
|
||||
"main": { "code": 42779, "label": "ꜛ" }
|
||||
} },
|
||||
{ "code": 8600, "label": "↘︎", "popup": {
|
||||
"main": { "code": 42780, "label": "ꜜ" }
|
||||
} },
|
||||
{ "code": 745, "label": "˩", "popup": {
|
||||
"main": { "code": 783, "label": "◌̏" }
|
||||
} },
|
||||
{ "code": 744, "label": "˨", "popup": {
|
||||
"main": { "code": 768, "label": "◌̀" },
|
||||
"relevant": [
|
||||
{ "code": 780, "label": "◌̌" }
|
||||
]
|
||||
} },
|
||||
{ "code": 743, "label": "˧", "popup": {
|
||||
"main": { "code": 772, "label": "◌̄" }
|
||||
} },
|
||||
{ "code": 742, "label": "˦", "popup": {
|
||||
"main": { "code": 769, "label": "◌́" },
|
||||
"relevant": [
|
||||
{ "code": 770, "label": "◌̂" }
|
||||
]
|
||||
} },
|
||||
{ "code": 741, "label": "˥", "popup": {
|
||||
"main": { "code": 779, "label": "◌̋" }
|
||||
} },
|
||||
{ "code": 865, "label": "◌͡", "popup": {
|
||||
"main": { "code": 8255, "label": "‿" }
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 776, "label": "◌̈" },
|
||||
{ "code": 825, "label": "◌̹", "popup": {
|
||||
"main": { "code": 855, "label": "◌͗" },
|
||||
"relevant": [
|
||||
{ "code": 796, "label": "◌̜" },
|
||||
{ "code": 849, "label": "◌͑" }
|
||||
]
|
||||
} },
|
||||
{ "code": 800, "label": "◌̠", "popup": {
|
||||
"main": { "code": 727, "label": "◌˗" }
|
||||
} },
|
||||
{ "code": 771, "label": "◌̃", "popup": {
|
||||
"main": { "code": 820, "label": "◌̴" },
|
||||
"relevant": [
|
||||
{ "code": 734, "label": "◌˞" }
|
||||
]
|
||||
} },
|
||||
{ "code": 91, "label": "[", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 11816, "label": "⸨" },
|
||||
{ "code": 10214, "label": "⟦" },
|
||||
{ "code": 10216, "label": "⟨" },
|
||||
{ "code": 10218, "label": "⟩" },
|
||||
{ "code": 123, "label": "{" }
|
||||
]
|
||||
} },
|
||||
{ "code": 93, "label": "]", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 11817, "label": "⸩" },
|
||||
{ "code": 10215, "label": "⟦" },
|
||||
{ "code": 10217, "label": "⟩" },
|
||||
{ "code": 10219, "label": "⟫" },
|
||||
{ "code": 125, "label": "}" }
|
||||
]
|
||||
} },
|
||||
{ "code": 47, "label": "/", "popup": {
|
||||
"main": { "code": 92, "label": "\\" },
|
||||
"relevant": [
|
||||
{ "code": 11005, "label": "⫽" },
|
||||
{ "code": 8214, "label": "‖" },
|
||||
{ "code": 124, "label": "|" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "symbols/mod",
|
||||
"name": "default",
|
||||
"name": "$default",
|
||||
"label": "Default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
102
app/src/main/assets/ime/text/symbols/persian.json
Normal file
102
app/src/main/assets/ime/text/symbols/persian.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"type": "symbols",
|
||||
"name": "persian",
|
||||
"label": "Persian",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "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": 1642, "label": "٪", "popup": {
|
||||
"main": { "code": 37, "label": "%" },
|
||||
"relevant": [
|
||||
{ "code": 8453, "label": "℅" },
|
||||
{ "code": 8240, "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":64830, "label": "﴾" },
|
||||
"relevant": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 123, "label": "{" }
|
||||
]
|
||||
} },
|
||||
{ "code": 41, "label": ")", "popup": {
|
||||
"main": { "code":64831, "label": "﴿" },
|
||||
"relevant": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 62, "label": ">" },
|
||||
{ "code": 125, "label": "}" }
|
||||
]
|
||||
} },
|
||||
{ "code": 1643, "label": "٫", "popup": {
|
||||
"main": { "code": 1644, "label": "٬" },
|
||||
"relevant": [
|
||||
{ "code": 42, "label": "*" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 39, "label": "'" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 171, "label": "«", "popup": {
|
||||
"main": { "code": 8249, "label": "‹" },
|
||||
"relevant": [
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 8804, "label": "≤" },
|
||||
{ "code":10216, "label": "⟨" }
|
||||
]
|
||||
} },
|
||||
{ "code": 187, "label": "»", "popup": {
|
||||
"main": { "code": 8250, "label": "›" },
|
||||
"relevant": [
|
||||
{ "code":10217, "label": "⟩" },
|
||||
{ "code": 8805, "label": "≥" },
|
||||
{ "code": 62, "label": ">" }
|
||||
]
|
||||
} },
|
||||
{ "code": 58, "label": ":", "popup": {
|
||||
"main": { "code": 8942, "label": "⋮" }
|
||||
} },
|
||||
{ "code": 1563, "label": "؛", "popup": {
|
||||
"main": { "code": 59, "label": ";" }
|
||||
} },
|
||||
{ "code": 33, "label": "!", "popup": {
|
||||
"main": { "code": 161, "label": "¡" }
|
||||
} },
|
||||
{ "code": 1567, "label": "؟", "popup": {
|
||||
"main": { "code": 63, "label": "?" },
|
||||
"relevant": [
|
||||
{ "code": 191, "label": "¿" },
|
||||
{ "code": 8253, "label": "‽" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "symbols",
|
||||
"name": "western_default",
|
||||
"name": "western",
|
||||
"label": "Western",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
@@ -9,13 +10,13 @@
|
||||
{ "code": 35, "label": "#", "popup": {
|
||||
"main": { "code": 8470, "label": "№" }
|
||||
} },
|
||||
{ "code": 36, "label": "$", "popup": {
|
||||
"main": { "code": 8364, "label": "€" },
|
||||
{ "code": -801, "label": "currency_slot_1", "popup": {
|
||||
"main": { "code": -802, "label": "currency_slot_2" },
|
||||
"relevant": [
|
||||
{ "code": 8369, "label": "₱" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
{ "code": -806, "label": "currency_slot_6" },
|
||||
{ "code": -803, "label": "currency_slot_3" },
|
||||
{ "code": -804, "label": "currency_slot_4" },
|
||||
{ "code": -805, "label": "currency_slot_5" }
|
||||
]
|
||||
} },
|
||||
{ "code": 37, "label": "%", "popup": {
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "symbols2",
|
||||
"name": "western_default",
|
||||
"name": "eastern",
|
||||
"label": "Eastern",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
@@ -36,10 +37,10 @@
|
||||
{ "code": 8710, "label": "∆" }
|
||||
],
|
||||
[
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 165, "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": [
|
||||
117
app/src/main/assets/ime/text/symbols2/ipa.json
Normal file
117
app/src/main/assets/ime/text/symbols2/ipa.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"type": "symbols2",
|
||||
"name": "ipa",
|
||||
"label": "International Phonetic Alphabet",
|
||||
"authors": [ "Huy-Ngo" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 809, "label": "◌̩", "popup": {
|
||||
"main": { "code": 781, "label": "◌̍" }
|
||||
} },
|
||||
{ "code": 815, "label": "◌̯", "popup": {
|
||||
"main": { "code": 785, "label": "◌̑" }
|
||||
} },
|
||||
{ "code": 794, "label": "◌̚" },
|
||||
{ "code": 805, "label": "◌̥", "popup": {
|
||||
"main": { "code": 778, "label": "◌̊" }
|
||||
} },
|
||||
{ "code": 828, "label": "◌̼" },
|
||||
{ "code": 827, "label": "◌̻" },
|
||||
{ "code": 804, "label": "◌̤" },
|
||||
{ "code": 797, "label": "◌̝", "popup": {
|
||||
"main": { "code": 724, "label": "◌˔" },
|
||||
"relevant": [
|
||||
{ "code": 798, "label": "◌̞" },
|
||||
{ "code": 725, "label": "◌˕" },
|
||||
{ "code": 792, "label": "◌̘" },
|
||||
{ "code": 793, "label": "◌̙" }
|
||||
]
|
||||
} },
|
||||
{ "code": 812, "label": "◌̬" },
|
||||
{ "code": 829, "label": "◌̽" },
|
||||
{ "code": 826, "label": "◌̺" }
|
||||
],
|
||||
[
|
||||
{ "code": 816, "label": "◌̰" },
|
||||
{ "code": 810, "label": "◌̪", "popup": {
|
||||
"main": { "code": 838, "label": "◌͆" }
|
||||
} },
|
||||
{ "code": 826, "label": "◌̺" },
|
||||
{ "code": 799, "label": "◌̟", "popup": {
|
||||
"main": { "code": 726, "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": 37, "label": "%", "popup": {
|
||||
"main": { "code": 8240, "label": "‰" },
|
||||
"relevant": [
|
||||
{ "code": 8453, "label": "℅" }
|
||||
]
|
||||
} },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 45, "label": "-", "popup": {
|
||||
"main": { "code": 95, "label": "_" },
|
||||
"relevant": [
|
||||
{ "code": 8212, "label": "—" },
|
||||
{ "code": 8211, "label": "–" },
|
||||
{ "code": 183, "label": "·" }
|
||||
]
|
||||
} },
|
||||
{ "code": 43, "label": "+", "popup": {
|
||||
"main": { "code": 177, "label": "±" }
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 42, "label": "*", "popup": {
|
||||
"main": { "code": 664, "label": "ʘ" },
|
||||
"relevant": [
|
||||
{ "code": 450, "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": 700, "label": "ʼ" },
|
||||
"relevant": [
|
||||
{ "code": 8218, "label": "‚" },
|
||||
{ "code": 8216, "label": "‘" },
|
||||
{ "code": 8217, "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": "‽" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "symbols2/mod",
|
||||
"name": "default",
|
||||
"name": "$default",
|
||||
"label": "Default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
83
app/src/main/assets/ime/text/symbols2/persian.json
Normal file
83
app/src/main/assets/ime/text/symbols2/persian.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"type": "symbols2",
|
||||
"name": "persian",
|
||||
"label": "Persian",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 126, "label": "~" },
|
||||
{ "code": 96, "label": "`" },
|
||||
{ "code": 124, "label": "|" },
|
||||
{ "code": 8226, "label": "•", "popup": {
|
||||
"main": { "code": 9834, "label": "♪" },
|
||||
"relevant": [
|
||||
{ "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": "¶", "popup": {
|
||||
"main": { "code": 167, "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": "″" }
|
||||
]
|
||||
} },
|
||||
{ "code": 61, "label": "=", "popup": {
|
||||
"main": { "code": 8800, "label": "≠" },
|
||||
"relevant": [
|
||||
{ "code": 8734, "label": "∞" },
|
||||
{ "code": 8776, "label": "≈" }
|
||||
]
|
||||
} },
|
||||
{ "code": 123, "label": "{", "popup": {
|
||||
"main": { "code": 40, "label": "(" }
|
||||
} },
|
||||
{ "code": 125, "label": "}", "popup": {
|
||||
"main": { "code": 41, "label": ")" }
|
||||
} },
|
||||
{ "code": 92, "label": "\\" }
|
||||
],
|
||||
[
|
||||
{ "code": 95, "label": "_" },
|
||||
{ "code": 169, "label": "©" },
|
||||
{ "code": 174, "label": "®" },
|
||||
{ "code": 8482, "label": "™" },
|
||||
{ "code": 10003, "label": "✓" },
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 93, "label": "]" }
|
||||
]
|
||||
]
|
||||
}
|
||||
83
app/src/main/assets/ime/text/symbols2/western.json
Normal file
83
app/src/main/assets/ime/text/symbols2/western.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"type": "symbols2",
|
||||
"name": "western",
|
||||
"label": "Western",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 126, "label": "~" },
|
||||
{ "code": 96, "label": "`" },
|
||||
{ "code": 124, "label": "|" },
|
||||
{ "code": 8226, "label": "•", "popup": {
|
||||
"main": { "code": 9834, "label": "♪" },
|
||||
"relevant": [
|
||||
{ "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": "¶", "popup": {
|
||||
"main": { "code": 167, "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": "″" }
|
||||
]
|
||||
} },
|
||||
{ "code": 61, "label": "=", "popup": {
|
||||
"main": { "code": 8800, "label": "≠" },
|
||||
"relevant": [
|
||||
{ "code": 8734, "label": "∞" },
|
||||
{ "code": 8776, "label": "≈" }
|
||||
]
|
||||
} },
|
||||
{ "code": 123, "label": "{", "popup": {
|
||||
"main": { "code": 40, "label": "(" }
|
||||
} },
|
||||
{ "code": 125, "label": "}", "popup": {
|
||||
"main": { "code": 41, "label": ")" }
|
||||
} },
|
||||
{ "code": 92, "label": "\\" }
|
||||
],
|
||||
[
|
||||
{ "code": 95, "label": "_" },
|
||||
{ "code": 169, "label": "©" },
|
||||
{ "code": 174, "label": "®" },
|
||||
{ "code": 8482, "label": "™" },
|
||||
{ "code": 10003, "label": "✓" },
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 93, "label": "]" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -68,6 +68,7 @@
|
||||
"extractActionButton": {
|
||||
"background": "@smartbarButton/background",
|
||||
"foreground": "@smartbarButton/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#20388E3C"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"extractActionButton": {
|
||||
"background": "@smartbarButton/background",
|
||||
"foreground": "@smartbarButton/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#20388E3C"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"extractActionButton": {
|
||||
"background": "@smartbarButton/background",
|
||||
"foreground": "@smartbarButton/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#204CAF50"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"extractActionButton": {
|
||||
"background": "@smartbarButton/background",
|
||||
"foreground": "@smartbarButton/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#204CAF50"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"extractActionButton": {
|
||||
"background": "@smartbarButton/background",
|
||||
"foreground": "@smartbarButton/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#204CAF50"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"extractActionButton": {
|
||||
"background": "@smartbarButton/background",
|
||||
"foreground": "@smartbarButton/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#204CAF50"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"smartbarButton": {
|
||||
"background": "@key/background",
|
||||
"foreground": "@key/foreground"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#200479ed"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"smartbarButton": {
|
||||
"background": "#FFFFFF",
|
||||
"foreground": "#686868"
|
||||
}
|
||||
},
|
||||
"glideTrail": {"foreground": "#205e97f6"}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import androidx.room.Room
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
@@ -109,10 +110,9 @@ class FlorisContentProvider : ContentProvider() {
|
||||
throw IllegalArgumentException("This ContentProvider does not support update.")
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private var instance: FlorisContentProvider? = null
|
||||
const val AUTHORITY = "dev.patrickgold.florisboard.provider.clip"
|
||||
const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.provider.clip"
|
||||
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY")
|
||||
val CLIPS_URI: Uri = Uri.parse("content://$AUTHORITY/clips")
|
||||
|
||||
@@ -127,6 +127,5 @@ class FlorisContentProvider : ContentProvider() {
|
||||
addURI(AUTHORITY, "clips/#", CLIP_ITEM)
|
||||
addURI(AUTHORITY, "clips", CLIPS_TABLE)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +210,45 @@ class EditorInstance private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit a word generated by a gesture.
|
||||
*/
|
||||
fun commitGesture(text: String): Boolean{
|
||||
val ic = inputConnection ?: return false
|
||||
return if (isRawInputEditor) {
|
||||
false
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
ic.finishComposingText()
|
||||
if (selection.start > 0 && getTextBeforeCursor(1).isNotBlank()) {
|
||||
ic.commitText(" ", 1)
|
||||
}
|
||||
ic.commitText(text, 1)
|
||||
isPhantomSpaceActive = true
|
||||
wasPhantomSpaceActiveLastUpdate = false
|
||||
ic.endBatchEdit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the previous word with the given [text]. Used to correct gestures.
|
||||
*/
|
||||
fun commitGestureCorrection(text: String): Boolean {
|
||||
val ic = inputConnection ?: return false
|
||||
return if (isRawInputEditor) {
|
||||
false
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
markComposingRegion(Region(this, cachedInput.getWordForIndex(-1).start, cachedInput.getWordForIndex(-1).end))
|
||||
ic.commitText(text, 1)
|
||||
markComposingRegion(null)
|
||||
isPhantomSpaceActive = true
|
||||
wasPhantomSpaceActiveLastUpdate = false
|
||||
ic.endBatchEdit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the given [ClipboardItem]. If the clip data is text (incl. HTML), it delegates to [commitText].
|
||||
|
||||
@@ -26,8 +26,11 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dev.patrickgold.florisboard.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity() {
|
||||
abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineScope by MainScope() {
|
||||
private var _binding: V? = null
|
||||
protected val binding: V
|
||||
get() = _binding!!
|
||||
@@ -54,6 +57,7 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cancel()
|
||||
_binding = null
|
||||
_prefs = null
|
||||
errorDialog?.dismiss()
|
||||
|
||||
@@ -22,9 +22,11 @@ import dev.patrickgold.florisboard.crashutility.CrashUtility
|
||||
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import timber.log.Timber
|
||||
|
||||
class FlorisApplication : Application() {
|
||||
class FlorisApplication : Application(), CoroutineScope by MainScope() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
||||
@@ -49,6 +49,7 @@ import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupLayerView
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputManager
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.CurrencySet
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
@@ -59,7 +60,6 @@ import dev.patrickgold.florisboard.util.*
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -402,6 +402,7 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
textInputManager.layoutManager.clearLayoutCache(KeyboardMode.CHARACTERS)
|
||||
isNumberRowVisible = newIsNumberRowVisible
|
||||
}
|
||||
textInputManager.layoutManager.clearLayoutCache()
|
||||
themeManager.update()
|
||||
updateOneHandedPanelVisibility()
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
@@ -746,11 +747,13 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
clipInputManager.onSubtypeChanged(newSubtype)
|
||||
}
|
||||
|
||||
fun setActiveInput(type: Int) {
|
||||
fun setActiveInput(type: Int, forceSwitchToCharacters: Boolean = false) {
|
||||
when (type) {
|
||||
R.id.text_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild = 0
|
||||
textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(KeyData.VIEW_CHARACTERS))
|
||||
if (forceSwitchToCharacters) {
|
||||
textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(KeyData.VIEW_CHARACTERS))
|
||||
}
|
||||
}
|
||||
R.id.media_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild = 1
|
||||
@@ -846,39 +849,58 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
* ime/config.json so it can be parsed. Used by [SubtypeManager] and by the prefs.
|
||||
* NOTE: this class and its corresponding json file is subject to change in future versions.
|
||||
* @property packageName The package name of this IME.
|
||||
* @property characterLayouts A map of valid layout names to use from. Each value defined
|
||||
* should have a <layout_name>.json file in ime/text/characters/ to avoid empty layouts.
|
||||
* The key is the layout name, the value is the layout label (string shown in UI).
|
||||
* @property currencySets The predefined currency sets for this IME, available for selection
|
||||
* in the Settings UI.
|
||||
* @property defaultSubtypes A list of predefined default subtypes. This subtypes are used to
|
||||
* define which locales are supported and which layout is preferred for that locale.
|
||||
* @property currencySetNames Helper list for Settings Subtype Spinner elements.
|
||||
* @property currencySetLabels Helper list for Settings Subtype Spinner elements.
|
||||
* @property defaultSubtypesLanguageCodes Helper list for Settings Subtype Spinner elements.
|
||||
* @property defaultSubtypesLanguageNames Helper list for Settings Subtype Spinner elements.
|
||||
*/
|
||||
data class ImeConfig(
|
||||
@Json(name = "package")
|
||||
val packageName: String,
|
||||
val characterLayouts: Map<String, String> = mapOf(),
|
||||
val currencySets: List<CurrencySet> = listOf(),
|
||||
val defaultSubtypes: List<DefaultSubtype> = listOf()
|
||||
) {
|
||||
val currencySetNames: List<String>
|
||||
val currencySetLabels: List<String>
|
||||
val defaultSubtypesLanguageCodes: List<String>
|
||||
val defaultSubtypesLanguageNames: List<String>
|
||||
|
||||
init {
|
||||
val tmpList = mutableListOf<Pair<String, String>>()
|
||||
for (defaultSubtype in defaultSubtypes) {
|
||||
tmpList.add(Pair(defaultSubtype.locale.toString(), defaultSubtype.locale.displayName))
|
||||
val tmpCurrencyList = mutableListOf<Pair<String, String>>()
|
||||
for (currencySet in currencySets) {
|
||||
tmpCurrencyList.add(Pair(currencySet.name, currencySet.label))
|
||||
}
|
||||
// Sort language list alphabetically by the display name of a language
|
||||
tmpList.sortBy { it.second }
|
||||
// Move selected English variants to the top of the list
|
||||
for (languageCode in listOf("en_CA", "en_AU", "en_UK", "en_US")) {
|
||||
val index: Int = tmpList.indexOfFirst { it.first == languageCode }
|
||||
// Sort currency set list alphabetically by the label of a currency set
|
||||
tmpCurrencyList.sortBy { it.second }
|
||||
// Move selected currency variants to the top of the list
|
||||
for (currencyName in listOf("euro", "dollar")) {
|
||||
val index: Int = tmpCurrencyList.indexOfFirst { it.first == currencyName }
|
||||
if (index > 0) {
|
||||
tmpList.add(0, tmpList.removeAt(index))
|
||||
tmpCurrencyList.add(0, tmpCurrencyList.removeAt(index))
|
||||
}
|
||||
}
|
||||
defaultSubtypesLanguageCodes = tmpList.map { it.first }.toList()
|
||||
defaultSubtypesLanguageNames = tmpList.map { it.second }.toList()
|
||||
currencySetNames = tmpCurrencyList.map { it.first }.toList()
|
||||
currencySetLabels = tmpCurrencyList.map { it.second }.toList()
|
||||
|
||||
val tmpSubtypeList = mutableListOf<Pair<String, String>>()
|
||||
for (defaultSubtype in defaultSubtypes) {
|
||||
tmpSubtypeList.add(Pair(defaultSubtype.locale.toString(), defaultSubtype.locale.displayName))
|
||||
}
|
||||
// Sort language list alphabetically by the display name of a language
|
||||
tmpSubtypeList.sortBy { it.second }
|
||||
// Move selected English variants to the top of the list
|
||||
for (languageCode in listOf("en_CA", "en_AU", "en_UK", "en_US")) {
|
||||
val index: Int = tmpSubtypeList.indexOfFirst { it.first == languageCode }
|
||||
if (index > 0) {
|
||||
tmpSubtypeList.add(0, tmpSubtypeList.removeAt(index))
|
||||
}
|
||||
}
|
||||
defaultSubtypesLanguageCodes = tmpSubtypeList.map { it.first }.toList()
|
||||
defaultSubtypesLanguageNames = tmpSubtypeList.map { it.second }.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ class PrefHelper(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val OLD_SUBTYPES_REGEX = """^([\-0-9]+\/[\-a-zA-Z0-9]+\/[a-zA-Z\_]+[;]*)+${'$'}""".toRegex()
|
||||
private var defaultInstance: PrefHelper? = null
|
||||
|
||||
@Synchronized
|
||||
@@ -147,8 +148,11 @@ class PrefHelper(
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_typing, true)
|
||||
//theme.dayThemeRef = "assets:ime/theme/floris_day.json"
|
||||
//theme.nightThemeRef = "assets:ime/theme/floris_night.json"
|
||||
//setPref(Keyboard.SUBTYPES, "")
|
||||
//setPref(Internal.IS_IME_SET_UP, false)
|
||||
//setPref(Localization.SUBTYPES, "-234/de-AT/euro/c=qwertz")
|
||||
val subtypes = getPref(Localization.SUBTYPES, "")
|
||||
if (subtypes.matches(OLD_SUBTYPES_REGEX)) {
|
||||
setPref(Localization.SUBTYPES, "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,6 +274,9 @@ class PrefHelper(
|
||||
companion object {
|
||||
const val ENABLED = "glide__enabled"
|
||||
const val SHOW_TRAIL = "glide__show_trail"
|
||||
const val TRAIL_DURATION = "glide__trail_fade_duration"
|
||||
const val SHOW_PREVIEW = "glide__show_preview"
|
||||
const val PREVIEW_REFRESH_DELAY = "glide__preview_refresh_delay"
|
||||
}
|
||||
|
||||
var enabled: Boolean
|
||||
@@ -278,6 +285,15 @@ class PrefHelper(
|
||||
var showTrail: Boolean
|
||||
get() = prefHelper.getPref(SHOW_TRAIL, false)
|
||||
set(v) = prefHelper.setPref(SHOW_TRAIL, v)
|
||||
var trailDuration: Int
|
||||
get() = prefHelper.getPref(TRAIL_DURATION, 200)
|
||||
set(v) = prefHelper.setPref(TRAIL_DURATION, v)
|
||||
var showPreview: Boolean
|
||||
get() = prefHelper.getPref(SHOW_PREVIEW, true)
|
||||
set(v) = prefHelper.setPref(SHOW_PREVIEW, v)
|
||||
var previewRefreshDelay: Int
|
||||
get() = prefHelper.getPref(PREVIEW_REFRESH_DELAY, 150)
|
||||
set(v) = prefHelper.setPref(PREVIEW_REFRESH_DELAY, v)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import dev.patrickgold.florisboard.ime.text.layout.LayoutType
|
||||
import dev.patrickgold.florisboard.util.LocaleUtils
|
||||
import java.util.*
|
||||
|
||||
@@ -26,44 +27,50 @@ import java.util.*
|
||||
* @property id The ID of this subtype. Although this can be any numeric value, its value
|
||||
* typically matches the one of the [DefaultSubtype] with the same locale.
|
||||
* @property locale The locale this subtype is bound to.
|
||||
* @property layout The name of the layout the user wants to use within the bounds of this subtype.
|
||||
* Must be a string which also exists in [FlorisBoard.ImeConfig.characterLayouts]. If the value is
|
||||
* not included within this list, no layout will be shown to the user.
|
||||
* @property currencySetName The currency set name to display the correct currency symbols for this subtype.
|
||||
* @property layoutMap The layout map to properly display the correct layout for each layout type.
|
||||
*/
|
||||
data class Subtype(
|
||||
var id: Int,
|
||||
var locale: Locale,
|
||||
var layout: String
|
||||
var currencySetName: String,
|
||||
var layoutMap: SubtypeLayoutMap,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Subtype to use when prefs do not contain any valid subtypes.
|
||||
*/
|
||||
val DEFAULT = Subtype(-1, Locale.ENGLISH, "qwerty")
|
||||
val DEFAULT = Subtype(
|
||||
id = -1,
|
||||
locale = Locale.ENGLISH,
|
||||
currencySetName = "\$default",
|
||||
layoutMap = SubtypeLayoutMap(characters = "qwerty")
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts the string representation of this object to a [Subtype]. Must be in the
|
||||
* following format:
|
||||
* <id>/<language_code>/<layout_name>
|
||||
* <id>/<language_code>/<currency_set_name>/c=<layout_name>
|
||||
* or
|
||||
* <id>/<language_tag>/<layout_name>
|
||||
* Eg: 101/en_US/qwerty
|
||||
* 201/de-DE/qwertz
|
||||
* If the given [string] does not match this format an [InvalidPropertiesFormatException]
|
||||
* <id>/<language_tag>/<currency_set_name>/c=<layout_name>
|
||||
* Eg: 101/en_US/dollar/c=qwerty
|
||||
* 201/de-DE/euro/c=qwertz
|
||||
* If the given [str] does not match this format an [InvalidPropertiesFormatException]
|
||||
* will be thrown.
|
||||
*/
|
||||
fun fromString(string: String): Subtype {
|
||||
val data = string.split("/")
|
||||
if (data.size != 3) {
|
||||
fun fromString(str: String): Subtype {
|
||||
val data = str.split("/")
|
||||
if (data.size != 4) {
|
||||
throw InvalidPropertiesFormatException(
|
||||
"Given string contains more or less than 3 properties..."
|
||||
"Given string contains more or less than 4 properties..."
|
||||
)
|
||||
} else {
|
||||
val locale = LocaleUtils.stringToLocale(data[1])
|
||||
return Subtype(
|
||||
data[0].toInt(),
|
||||
locale,
|
||||
data[2]
|
||||
data[2],
|
||||
SubtypeLayoutMap.fromString(data[3])
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -71,26 +78,210 @@ data class Subtype(
|
||||
|
||||
/**
|
||||
* Converts this object into its string representation. Format:
|
||||
* <id>/<language_tag>/<layout_name>
|
||||
* <id>/<language_tag>/<currency_set_name>/<layout_map>
|
||||
*/
|
||||
override fun toString(): String {
|
||||
val languageTag = locale.toLanguageTag()
|
||||
return "$id/$languageTag/$layout"
|
||||
return "$id/$languageTag/$currencySetName/$layoutMap"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Subtype
|
||||
|
||||
if (id != other.id) return false
|
||||
if (locale != other.locale) return false
|
||||
if (currencySetName != other.currencySetName) return false
|
||||
if (layoutMap != other.layoutMap) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id
|
||||
result = 31 * result + locale.hashCode()
|
||||
result = 31 * result + currencySetName.hashCode()
|
||||
result = 31 * result + layoutMap.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class SubtypeLayoutMap(
|
||||
val characters: String = CHARACTERS_DEFAULT,
|
||||
val symbols: String = SYMBOLS_DEFAULT,
|
||||
val symbols2: String = SYMBOLS2_DEFAULT,
|
||||
val numeric: String = NUMERIC_DEFAULT,
|
||||
val numericAdvanced: String = NUMERIC_ADVANCED_DEFAULT,
|
||||
val numericRow: String = NUMERIC_ROW_DEFAULT,
|
||||
val phone: String = PHONE_DEFAULT,
|
||||
val phone2: String = PHONE2_DEFAULT,
|
||||
) {
|
||||
companion object {
|
||||
private const val CHARACTERS_CODE = "c"
|
||||
private const val SYMBOLS_CODE = "s"
|
||||
private const val SYMBOLS2_CODE = "s2"
|
||||
private const val NUMERIC_CODE = "n"
|
||||
private const val NUMERIC_ADVANCED_CODE = "na"
|
||||
private const val NUMERIC_ROW_CODE = "nr"
|
||||
private const val PHONE_CODE = "p"
|
||||
private const val PHONE2_CODE = "p2"
|
||||
|
||||
private const val EQUALS = "="
|
||||
private const val DELIMITER = ","
|
||||
|
||||
private const val CHARACTERS_DEFAULT = "qwerty"
|
||||
private const val SYMBOLS_DEFAULT = "western"
|
||||
private const val SYMBOLS2_DEFAULT = "western"
|
||||
private const val NUMERIC_DEFAULT = "western_arabic"
|
||||
private const val NUMERIC_ADVANCED_DEFAULT = "western_arabic"
|
||||
private const val NUMERIC_ROW_DEFAULT = "western_arabic"
|
||||
private const val PHONE_DEFAULT = "telpad"
|
||||
private const val PHONE2_DEFAULT = "telpad"
|
||||
|
||||
fun fromString(str: String): SubtypeLayoutMap {
|
||||
var characters: String = CHARACTERS_DEFAULT
|
||||
var symbols: String = SYMBOLS_DEFAULT
|
||||
var symbols2: String = SYMBOLS2_DEFAULT
|
||||
var numeric: String = NUMERIC_DEFAULT
|
||||
var numericAdvanced: String = NUMERIC_ADVANCED_DEFAULT
|
||||
var numericRow: String = NUMERIC_ROW_DEFAULT
|
||||
var phone: String = PHONE_DEFAULT
|
||||
var phone2: String = PHONE2_DEFAULT
|
||||
for (layout in str.split(DELIMITER)) {
|
||||
val layoutSplit = layout.split(EQUALS)
|
||||
if (layoutSplit.size == 2) {
|
||||
val code = layoutSplit[0].trim()
|
||||
val layoutName = layoutSplit[1].trim()
|
||||
when (code) {
|
||||
CHARACTERS_CODE -> characters = layoutName
|
||||
SYMBOLS_CODE -> symbols = layoutName
|
||||
SYMBOLS2_CODE -> symbols2 = layoutName
|
||||
NUMERIC_CODE -> numeric = layoutName
|
||||
NUMERIC_ADVANCED_CODE -> numericAdvanced = layoutName
|
||||
NUMERIC_ROW_CODE -> numericRow = layoutName
|
||||
PHONE_CODE -> phone = layoutName
|
||||
PHONE2_CODE -> phone2 = layoutName
|
||||
}
|
||||
}
|
||||
}
|
||||
return SubtypeLayoutMap(
|
||||
characters, symbols, symbols2, numeric, numericAdvanced, numericRow, phone, phone2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(layoutType: LayoutType): String? {
|
||||
return when (layoutType) {
|
||||
LayoutType.CHARACTERS -> characters
|
||||
LayoutType.SYMBOLS -> symbols
|
||||
LayoutType.SYMBOLS2 -> symbols2
|
||||
LayoutType.NUMERIC -> numeric
|
||||
LayoutType.NUMERIC_ADVANCED -> numericAdvanced
|
||||
LayoutType.NUMERIC_ROW -> numericRow
|
||||
LayoutType.PHONE -> phone
|
||||
LayoutType.PHONE2 -> phone2
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder(128).run {
|
||||
append(CHARACTERS_CODE)
|
||||
append(EQUALS)
|
||||
append(characters)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(SYMBOLS_CODE)
|
||||
append(EQUALS)
|
||||
append(symbols)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(SYMBOLS2_CODE)
|
||||
append(EQUALS)
|
||||
append(symbols2)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(NUMERIC_ROW_CODE)
|
||||
append(EQUALS)
|
||||
append(numericRow)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(NUMERIC_CODE)
|
||||
append(EQUALS)
|
||||
append(numeric)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(NUMERIC_ADVANCED_CODE)
|
||||
append(EQUALS)
|
||||
append(numericAdvanced)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(PHONE_CODE)
|
||||
append(EQUALS)
|
||||
append(phone)
|
||||
|
||||
append(DELIMITER)
|
||||
|
||||
append(PHONE2_CODE)
|
||||
append(EQUALS)
|
||||
append(phone2)
|
||||
|
||||
toString()
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SubtypeLayoutMap
|
||||
|
||||
if (characters != other.characters) return false
|
||||
if (symbols != other.symbols) return false
|
||||
if (symbols2 != other.symbols2) return false
|
||||
if (numeric != other.numeric) return false
|
||||
if (numericAdvanced != other.numericAdvanced) return false
|
||||
if (numericRow != other.numericRow) return false
|
||||
if (phone != other.phone) return false
|
||||
if (phone2 != other.phone2) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = characters.hashCode()
|
||||
result = 31 * result + symbols.hashCode()
|
||||
result = 31 * result + symbols2.hashCode()
|
||||
result = 31 * result + numeric.hashCode()
|
||||
result = 31 * result + numericAdvanced.hashCode()
|
||||
result = 31 * result + numericRow.hashCode()
|
||||
result = 31 * result + phone.hashCode()
|
||||
result = 31 * result + phone2.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class which represents a predefined set of language and preferred layout.
|
||||
*
|
||||
* @property id The ID of this subtype.
|
||||
* @property locale The locale of this subtype. Beware its different name in json: 'languageTag'.
|
||||
* @property preferredLayout The preferred layout for this subtype's locale.
|
||||
* Must be a string which also exists in [FlorisBoard.ImeConfig.characterLayouts]. If the value is
|
||||
* not included within this list, no layout will be shown to the user if the user selects the
|
||||
* predefined layout value.
|
||||
* @property currencySetName The currency set name of this subtype. Beware its different name in json: 'currencySet'.
|
||||
* @property preferred The preferred layout map for this subtype's locale.
|
||||
*/
|
||||
data class DefaultSubtype(
|
||||
var id: Int,
|
||||
@Json(name = "languageTag")
|
||||
var locale: Locale,
|
||||
var preferredLayout: String
|
||||
@Json(name = "currencySet")
|
||||
var currencySetName: String,
|
||||
var preferred: SubtypeLayoutMap
|
||||
)
|
||||
|
||||
@@ -19,11 +19,10 @@ package dev.patrickgold.florisboard.ime.core
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import dev.patrickgold.florisboard.ime.text.key.CurrencySet
|
||||
import dev.patrickgold.florisboard.util.LocaleUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -37,7 +36,6 @@ import java.util.*
|
||||
* list of [Subtype]s. When setting this property, the given list is converted to a raw string
|
||||
* and written to prefs.
|
||||
*/
|
||||
@Suppress("SameParameterValue")
|
||||
class SubtypeManager(
|
||||
private val context: Context,
|
||||
private val prefs: PrefHelper
|
||||
@@ -65,9 +63,7 @@ class SubtypeManager(
|
||||
}
|
||||
|
||||
init {
|
||||
launch(Dispatchers.IO) {
|
||||
imeConfig = loadImeConfig(IME_CONFIG_FILE_PATH)
|
||||
}
|
||||
imeConfig = loadImeConfig(IME_CONFIG_FILE_PATH)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,24 +106,36 @@ class SubtypeManager(
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Subtype] from the given [locale] and [layoutName] and adds it to the subtype
|
||||
* Creates a [Subtype] from the given [locale] and [layoutMap] and adds it to the subtype
|
||||
* list, if it does not exist.
|
||||
*
|
||||
* @param locale The locale of the subtype to be added.
|
||||
* @param layoutName The layout name of the subtype to be added.
|
||||
* @param currencySetName The currency set name of the subtype to be added.
|
||||
* @param layoutMap The layout map of the subtype to be added.
|
||||
* @return True if the subtype was added, false otherwise. A return value of false indicates
|
||||
* that the subtype already exists.
|
||||
*/
|
||||
fun addSubtype(locale: Locale, layoutName: String): Boolean {
|
||||
fun addSubtype(locale: Locale, currencySetName: String, layoutMap: SubtypeLayoutMap): Boolean {
|
||||
return addSubtype(
|
||||
Subtype(
|
||||
(locale.hashCode() + layoutName.hashCode()),
|
||||
(locale.hashCode() + 31 * layoutMap.hashCode() + 31 * currencySetName.hashCode()),
|
||||
locale,
|
||||
layoutName
|
||||
currencySetName,
|
||||
layoutMap
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currency set from the given subtype and returns it. Falls back to a default one if the subtype does not
|
||||
* exist.
|
||||
*
|
||||
* @return The currency set or a fallback.
|
||||
*/
|
||||
fun getCurrencySet(subtypeToSearch: Subtype): CurrencySet {
|
||||
return imeConfig.currencySets.find { it.name == subtypeToSearch.currencySetName } ?: CurrencySet.default()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active subtype and returns it. If the activeSubtypeId points to a non-existent
|
||||
* subtype, this method tries to determine a new active subtype.
|
||||
@@ -193,7 +201,8 @@ class SubtypeManager(
|
||||
for (subtype in subtypeList) {
|
||||
if (subtype.id == subtypeToModify.id) {
|
||||
subtype.locale = subtypeToModify.locale
|
||||
subtype.layout = subtypeToModify.layout
|
||||
subtype.currencySetName = subtypeToModify.currencySetName
|
||||
subtype.layoutMap = subtypeToModify.layoutMap
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,5 @@ interface Asset {
|
||||
* Creates an empty Asset of type [T].
|
||||
*/
|
||||
fun empty(): T
|
||||
|
||||
/**
|
||||
* Loads an Asset of type [T] from the specified path.
|
||||
*/
|
||||
fun fromFile(context: Context, path: String): Result<T> = Result.failure(NotImplementedError())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ class AssetManager private constructor(private val applicationContext: Context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun defaultOrNull(): AssetManager? = defaultInstance
|
||||
}
|
||||
|
||||
fun deleteAsset(ref: AssetRef): Result<Unit> {
|
||||
|
||||
@@ -127,7 +127,7 @@ class PopupManager<T_KBD: View, T_KV: View>(
|
||||
}
|
||||
else -> {
|
||||
PopupExtendedView.Element.Label(
|
||||
keyView.getComputedLetter(keyView.data.popup[adjustedIndex]), adjustedIndex
|
||||
keyView.getComputedLetter(keyView.data.popup[adjustedIndex], isForDisplay = true), adjustedIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class PopupSet<T> (
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
TODO("Not yet implemented")
|
||||
return PopupSetIterator(this)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
@@ -116,4 +116,30 @@ class PopupSet<T> (
|
||||
}
|
||||
relevant = tempRelevant.toList()
|
||||
}
|
||||
|
||||
class PopupSetIterator<T> internal constructor (
|
||||
private val popupSet: PopupSet<T>
|
||||
) : Iterator<T> {
|
||||
var index = HINT_INDEX
|
||||
|
||||
override fun next(): T = popupSet[index++]
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (index == HINT_INDEX) {
|
||||
if (popupSet.getOrNull(index) != null) {
|
||||
return true
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
}
|
||||
if (index == MAIN_INDEX) {
|
||||
if (popupSet.getOrNull(index) != null) {
|
||||
return true
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
}
|
||||
return popupSet.getOrNull(index) != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import dev.patrickgold.florisboard.ime.extension.AssetSource
|
||||
import dev.patrickgold.florisboard.ime.nlp.Token
|
||||
import dev.patrickgold.florisboard.ime.nlp.toStringList
|
||||
import dev.patrickgold.florisboard.ime.text.editing.EditingKeyboardView
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.*
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
@@ -58,10 +59,13 @@ import kotlin.math.roundToLong
|
||||
class TextInputManager private constructor() : CoroutineScope by MainScope(), InputKeyEventReceiver,
|
||||
FlorisBoard.EventListener, SmartbarView.EventListener {
|
||||
|
||||
var glideSuggestionsActive: Boolean = false
|
||||
private val florisboard = FlorisBoard.getInstance()
|
||||
private val activeEditorInstance: EditorInstance
|
||||
get() = florisboard.activeEditorInstance
|
||||
|
||||
lateinit var layoutManager: LayoutManager
|
||||
private set
|
||||
private var activeKeyboardMode: KeyboardMode? = null
|
||||
private var animator: ObjectAnimator? = null
|
||||
private val keyboardViews = EnumMap<KeyboardMode, KeyboardView>(KeyboardMode::class.java)
|
||||
@@ -84,8 +88,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
)
|
||||
|
||||
var keyVariation: KeyVariation = KeyVariation.NORMAL
|
||||
val layoutManager = LayoutManager(florisboard)
|
||||
private var smartbarView: SmartbarView? = null
|
||||
internal var smartbarView: SmartbarView? = null
|
||||
|
||||
// Caps/Shift related properties
|
||||
var caps: Boolean = false
|
||||
@@ -122,6 +125,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
override fun onCreate() {
|
||||
Timber.i("onCreate()")
|
||||
|
||||
layoutManager = LayoutManager(this)
|
||||
inputEventDispatcher.keyEventReceiver = this
|
||||
var subtypes = florisboard.subtypeManager.subtypes
|
||||
if (subtypes.isEmpty()) {
|
||||
@@ -129,7 +133,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
}
|
||||
for (subtype in subtypes) {
|
||||
for (mode in KeyboardMode.values()) {
|
||||
layoutManager.preloadComputedLayout(mode, subtype, florisboard.prefs)
|
||||
layoutManager.preloadComputedLayout(mode, subtype, florisboard.prefs, florisboard.subtypeManager.getCurrencySet(subtype))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +144,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
|
||||
private suspend fun addKeyboardView(mode: KeyboardMode) {
|
||||
val keyboardView = KeyboardView(florisboard.context)
|
||||
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(mode, florisboard.activeSubtype, florisboard.prefs).await()
|
||||
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(mode, florisboard.activeSubtype, florisboard.prefs, florisboard.subtypeManager.getCurrencySet(florisboard.activeSubtype)).await()
|
||||
keyboardViews[mode] = keyboardView
|
||||
textViewFlipper?.addView(keyboardView)
|
||||
}
|
||||
@@ -321,16 +325,31 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
launch {
|
||||
if (activeEditorInstance.isComposingEnabled) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dictionaryManager.loadDictionary(AssetRef(AssetSource.Assets,"ime/dict/en.flict")).let {
|
||||
dictionaryManager.loadDictionary(AssetRef(AssetSource.Assets, "ime/dict/en.flict")).let {
|
||||
activeDictionary = it.getOrDefault(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
val keyboardView = keyboardViews[KeyboardMode.CHARACTERS]
|
||||
keyboardView?.computedLayout = layoutManager.fetchComputedLayoutAsync(KeyboardMode.CHARACTERS, newSubtype, florisboard.prefs).await()
|
||||
keyboardView?.updateVisibility()
|
||||
if (PrefHelper.getDefaultInstance(florisboard.context).glide.enabled) {
|
||||
GlideTypingManager.getInstance().setWordData(newSubtype)
|
||||
}
|
||||
// TODO: heavy load on main thread
|
||||
for (keyboardMode in KeyboardMode.values()) {
|
||||
val keyboardView = keyboardViews[keyboardMode]
|
||||
if (keyboardView != null) {
|
||||
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(
|
||||
keyboardMode,
|
||||
newSubtype,
|
||||
florisboard.prefs,
|
||||
florisboard.subtypeManager.getCurrencySet(newSubtype)
|
||||
).await()
|
||||
keyboardView.updateVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is so unfortunate... but we need to skip clearing the suggestion only one time.
|
||||
var hackyGlideSuggestionSkip = false
|
||||
|
||||
/**
|
||||
* Main logic point for processing cursor updates as well as parsing the current composing word
|
||||
@@ -367,7 +386,17 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
}
|
||||
}
|
||||
} else {
|
||||
smartbarView?.setCandidateSuggestionWords(System.nanoTime(), null)
|
||||
if (glideSuggestionsActive){
|
||||
if (hackyGlideSuggestionSkip) {
|
||||
smartbarView?.setCandidateSuggestionWords(System.nanoTime(), null)
|
||||
hackyGlideSuggestionSkip = false
|
||||
glideSuggestionsActive = false
|
||||
}else {
|
||||
hackyGlideSuggestionSkip = true
|
||||
}
|
||||
}else {
|
||||
smartbarView?.setCandidateSuggestionWords(System.nanoTime(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,7 +448,14 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
}
|
||||
|
||||
override fun onSmartbarCandidatePressed(word: String) {
|
||||
activeEditorInstance.commitCompletion(word)
|
||||
if (glideSuggestionsActive) {
|
||||
activeEditorInstance.commitGestureCorrection(word)
|
||||
glideSuggestionsActive = false
|
||||
hackyGlideSuggestionSkip = false
|
||||
smartbarView?.setCandidateSuggestionWords(System.nanoTime(), null)
|
||||
}else {
|
||||
activeEditorInstance.commitCompletion(word)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSmartbarClipboardCandidatePressed(clipboardItem: ClipboardItem) {
|
||||
@@ -459,10 +495,17 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
* Handles a [KeyCode.DELETE] event.
|
||||
*/
|
||||
private fun handleDelete() {
|
||||
isManualSelectionMode = false
|
||||
isManualSelectionModeStart = false
|
||||
isManualSelectionModeEnd = false
|
||||
activeEditorInstance.deleteBackwards()
|
||||
if (glideSuggestionsActive){
|
||||
handleDeleteWord()
|
||||
glideSuggestionsActive = false
|
||||
hackyGlideSuggestionSkip = false
|
||||
this.smartbarView?.setCandidateSuggestionWords(System.nanoTime(), null)
|
||||
}else {
|
||||
isManualSelectionMode = false
|
||||
isManualSelectionModeStart = false
|
||||
isManualSelectionModeEnd = false
|
||||
activeEditorInstance.deleteBackwards()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -716,7 +759,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
KeyCode.SPACE -> handleSpace(ev)
|
||||
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> florisboard.setActiveInput(R.id.media_input)
|
||||
KeyCode.SWITCH_TO_CLIPBOARD_CONTEXT -> florisboard.setActiveInput(R.id.clip_input)
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT -> florisboard.setActiveInput(R.id.text_input)
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT -> florisboard.setActiveInput(R.id.text_input, forceSwitchToCharacters = true)
|
||||
KeyCode.CLEAR_CLIPBOARD_HISTORY -> florisboard.florisClipboardManager?.clearHistoryWithAnimation()
|
||||
KeyCode.TOGGLE_ONE_HANDED_MODE_LEFT -> florisboard.toggleOneHandedMode(isRight = false)
|
||||
KeyCode.TOGGLE_ONE_HANDED_MODE_RIGHT -> florisboard.toggleOneHandedMode(isRight = true)
|
||||
@@ -785,4 +828,22 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
KeyCode.SHIFT -> handleShiftCancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun handleGesture(word: String) {
|
||||
activeEditorInstance.commitGesture(fixCase(word))
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a word to the current case.
|
||||
* eg if [capsLock] is true, abc -> ABC
|
||||
* if [caps] is true, abc -> Abc
|
||||
* otherwise , abc -> abc
|
||||
*/
|
||||
fun fixCase(word: String): String {
|
||||
return when {
|
||||
capsLock -> word.toUpperCase(florisboard.activeSubtype.locale)
|
||||
caps -> word.capitalize(florisboard.activeSubtype.locale)
|
||||
else -> word
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
@@ -55,6 +56,9 @@ class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener,
|
||||
cutKey = findViewById(R.id.clipboard_cut)
|
||||
copyKey = findViewById(R.id.clipboard_copy)
|
||||
pasteKey = findViewById(R.id.clipboard_paste)
|
||||
|
||||
val clipboardManager = FlorisClipboardManager.getInstance()
|
||||
pasteKey?.isEnabled = clipboardManager.canBePasted(clipboardManager.primaryClip)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
@@ -80,11 +84,8 @@ class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener,
|
||||
else -> View.GONE
|
||||
}
|
||||
copyKey?.isEnabled = isSelectionActive
|
||||
pasteKey?.isEnabled =
|
||||
florisboard?.florisClipboardManager?.hasPrimaryClip() == true &&
|
||||
florisboard.activeEditorInstance.contentMimeTypes?.any {
|
||||
florisboard.florisClipboardManager!!.primaryClip?.mimeTypes?.contains(it) ?: false
|
||||
} == true
|
||||
val clipboardManager = FlorisClipboardManager.getInstance()
|
||||
pasteKey?.isEnabled = clipboardManager.canBePasted(clipboardManager.primaryClip)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package dev.patrickgold.florisboard.ime.text.gestures
|
||||
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyView
|
||||
import dev.patrickgold.florisboard.ime.text.layout.ComputedLayout
|
||||
|
||||
/**
|
||||
* Inherit this to be able to handle gesture typing. Takes in raw pointer data, and
|
||||
* spits out what it thinks the gesture is.
|
||||
*/
|
||||
interface GlideTypingClassifier {
|
||||
/**
|
||||
* Called to notify gesture classifier that it can add a new point to the gesture.
|
||||
* @param position The position to add
|
||||
*/
|
||||
fun addGesturePoint(position: GlideTypingGesture.Detector.Position)
|
||||
|
||||
/**
|
||||
* Change the layout of the gesture classifier.
|
||||
*/
|
||||
fun setLayout(keyViews: Sequence<KeyView>, subtype: Subtype)
|
||||
|
||||
/**
|
||||
* Change the word data of the gesture classifier.
|
||||
*/
|
||||
fun setWordData(words: HashMap<String, Int>, subtype: Subtype)
|
||||
|
||||
/**
|
||||
* Process a completed gesture and find its location.
|
||||
*/
|
||||
fun initGestureFromPointerData(pointerData: GlideTypingGesture.Detector.PointerData)
|
||||
|
||||
/**
|
||||
* Generate suggestions to show to the user.
|
||||
*
|
||||
* @param maxSuggestionCount The maximum number of suggestions that are accepted.
|
||||
* @param gestureCompleted Whether the gesture is finished. (e.g to use a different algorithm for in progress words)
|
||||
*/
|
||||
fun getSuggestions(maxSuggestionCount: Int, gestureCompleted: Boolean): List<String>
|
||||
|
||||
fun clear()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package dev.patrickgold.florisboard.ime.text.gestures
|
||||
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Wrapper class which holds all enums, interfaces and classes for detecting a gesture.
|
||||
*/
|
||||
class GlideTypingGesture {
|
||||
|
||||
/**
|
||||
* Class which detects swipes based on given [MotionEvent]s. Only supports single-finger swipes
|
||||
* and ignores additional pointers provided, if any.
|
||||
*/
|
||||
class Detector(context: Context) {
|
||||
private var pointerData: PointerData = PointerData(mutableListOf(), 0)
|
||||
var velocityThreshold: VelocityThreshold = VelocityThreshold.NORMAL
|
||||
private val keySize = context.resources.getDimensionPixelSize(R.dimen.key_width).toDouble()
|
||||
private val listeners: ArrayList<Listener> = arrayListOf()
|
||||
private var pointerId: Int = -1
|
||||
|
||||
companion object {
|
||||
private const val MAX_DETECT_TIME = 500
|
||||
private const val VELOCITY_THRESHOLD = 0.65
|
||||
private val SWIPE_GESTURE_KEYS = arrayOf(KeyCode.DELETE, KeyCode.SHIFT, KeyCode.SPACE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method which evaluates if a given [event] is a gesture.
|
||||
* @return whether or not the event was interpreted as part of a gesture.
|
||||
*/
|
||||
fun onTouchEvent(event: MotionEvent, initialKeyCodes: MutableMap<Int, Int>): Boolean {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
if (pointerId != -1) {
|
||||
// if we already have another pointer, we don't care
|
||||
return false
|
||||
}
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
resetState()
|
||||
}
|
||||
val pointerIndex = event.actionIndex
|
||||
pointerId = event.getPointerId(pointerIndex)
|
||||
pointerData.apply {
|
||||
positions.add(Position(event.getX(pointerIndex), event.getY(pointerIndex)))
|
||||
startTime = System.currentTimeMillis()
|
||||
}
|
||||
return false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (pointerId != event.getPointerId(event.actionIndex)) {
|
||||
// not our pointer.
|
||||
return false
|
||||
}
|
||||
|
||||
val pointerIndex = event.findPointerIndex(pointerId)
|
||||
val pos = Position(event.getX(pointerIndex), event.getY(pointerIndex))
|
||||
pointerData.positions.add(
|
||||
pos
|
||||
)
|
||||
if (pointerData.isActuallyGesture == null) {
|
||||
// evaluate whether is actually a gesture
|
||||
val dist = pointerData.positions[0].dist(pos)
|
||||
val time = (System.currentTimeMillis() - pointerData.startTime) + 1
|
||||
if (dist > keySize && (dist / time) > VELOCITY_THRESHOLD && (initialKeyCodes[pointerId] !in SWIPE_GESTURE_KEYS)) {
|
||||
pointerData.isActuallyGesture = true
|
||||
// Let listener know all those points need to be added.
|
||||
pointerData.positions.take(pointerData.positions.size - 1).forEach { point ->
|
||||
listeners.forEach {
|
||||
it.onGestureAdd(point)
|
||||
}
|
||||
}
|
||||
} else if (time > MAX_DETECT_TIME) {
|
||||
pointerData.isActuallyGesture = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (pointerData.isActuallyGesture == true)
|
||||
pointerData.positions.last().let { point -> listeners.forEach { it.onGestureAdd(point) } }
|
||||
|
||||
return pointerData.isActuallyGesture ?: false
|
||||
}
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
if (pointerId != event.getPointerId(event.actionIndex)) {
|
||||
// not our pointer.
|
||||
return false
|
||||
}
|
||||
if (pointerData.isActuallyGesture == true) {
|
||||
listeners.forEach { listener -> listener.onGestureComplete(pointerData) }
|
||||
}
|
||||
resetState()
|
||||
return false
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
if (pointerData.isActuallyGesture == true) {
|
||||
listeners.forEach { it.onGestureCancelled() }
|
||||
}
|
||||
resetState()
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun registerListener(listener: Listener) {
|
||||
this.listeners.add(listener)
|
||||
}
|
||||
|
||||
private fun resetState() {
|
||||
pointerData.apply {
|
||||
positions.clear()
|
||||
startTime = 0
|
||||
isActuallyGesture = null
|
||||
}
|
||||
pointerId = -1
|
||||
}
|
||||
|
||||
data class PointerData(
|
||||
val positions: MutableList<Position>,
|
||||
var startTime: Long,
|
||||
var isActuallyGesture: Boolean? = null
|
||||
)
|
||||
|
||||
data class Position(
|
||||
val x: Float,
|
||||
val y: Float
|
||||
) {
|
||||
fun dist(p2: Position): Float {
|
||||
return sqrt((p2.x - x).pow(2) + (p2.y - y).pow(2))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
* Called when a gesture is complete.
|
||||
*/
|
||||
fun onGestureComplete(data: Detector.PointerData) {}
|
||||
|
||||
/**
|
||||
* Called when a point is added to a gesture.
|
||||
* Will not be called before a series of events is detected as a gesture.
|
||||
*/
|
||||
fun onGestureAdd(point: Detector.Position) {}
|
||||
|
||||
/**
|
||||
* Called to cancel a gesture
|
||||
*/
|
||||
fun onGestureCancelled() {}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package dev.patrickgold.florisboard.ime.text.gestures
|
||||
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetManager
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetRef
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetSource
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputManager
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyView
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Handles the [GlideTypingClassifier]. Basically responsible for linking [GlideTypingGesture.Detector]
|
||||
* with [GlideTypingClassifier].
|
||||
*/
|
||||
class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainScope() {
|
||||
|
||||
private var glideTypingClassifier = StatisticalGlideTypingClassifier()
|
||||
private val initialDimensions: HashMap<Subtype, Dimensions> = hashMapOf()
|
||||
private var currentDimensions: Dimensions = Dimensions(0f, 0f)
|
||||
private lateinit var prefHelper: PrefHelper
|
||||
|
||||
companion object {
|
||||
private const val MAX_SUGGESTION_COUNT = 8
|
||||
|
||||
private lateinit var glideTypingManager: GlideTypingManager
|
||||
fun getInstance(): GlideTypingManager {
|
||||
if (!this::glideTypingManager.isInitialized) {
|
||||
glideTypingManager = GlideTypingManager()
|
||||
glideTypingManager.prefHelper = PrefHelper.getDefaultInstance(FlorisBoard.getInstance().context)
|
||||
}
|
||||
return glideTypingManager
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGestureComplete(data: GlideTypingGesture.Detector.PointerData) {
|
||||
updateSuggestionsAsync(MAX_SUGGESTION_COUNT, true) {
|
||||
glideTypingClassifier.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGestureCancelled() {
|
||||
glideTypingClassifier.clear()
|
||||
}
|
||||
|
||||
private var lastTime = System.currentTimeMillis()
|
||||
override fun onGestureAdd(point: GlideTypingGesture.Detector.Position) {
|
||||
val normalized = GlideTypingGesture.Detector.Position(normalizeX(point.x), normalizeY(point.y))
|
||||
|
||||
this.glideTypingClassifier.addGesturePoint(normalized)
|
||||
|
||||
val time = System.currentTimeMillis()
|
||||
if (prefHelper.glide.showPreview && time - lastTime > prefHelper.glide.previewRefreshDelay) {
|
||||
updateSuggestionsAsync(1, false) {}
|
||||
lastTime = time
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the layout of the internal gesture classifier
|
||||
*/
|
||||
fun setLayout(keys: Sequence<KeyView>, dimensions: Dimensions) {
|
||||
glideTypingClassifier.setLayout(keys, FlorisBoard.getInstance().activeSubtype)
|
||||
initialDimensions.getOrPut(FlorisBoard.getInstance().activeSubtype, {
|
||||
dimensions
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the word data for the internal gesture classifier
|
||||
*/
|
||||
fun setWordData(subtype: Subtype) {
|
||||
launch(Dispatchers.Default) {
|
||||
// FIXME: get this info from dictionary.
|
||||
val data =
|
||||
AssetManager.default().loadAssetRaw(AssetRef(AssetSource.Assets, "ime/dict/data.json")).getOrThrow()
|
||||
val json = JSONObject(data)
|
||||
val map = hashMapOf<String, Int>()
|
||||
map.putAll(json.keys().asSequence().map { Pair(it, json.getInt(it)) })
|
||||
glideTypingClassifier.setWordData(map, subtype)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDimensions(dimensions: Dimensions) {
|
||||
this.currentDimensions = dimensions
|
||||
}
|
||||
|
||||
/**
|
||||
* To avoid constantly having to regenerate Pruners every time we switch between landscape and portrait or enable/
|
||||
* disable one handed mode, we just normalize the x, y coordinates to the same range as the original which were
|
||||
* active when the Pruner was created.
|
||||
*/
|
||||
private fun normalizeX(x: Float): Float {
|
||||
val initial = initialDimensions[FlorisBoard.getInstance().activeSubtype] ?: return x
|
||||
|
||||
return scaleRange(
|
||||
x,
|
||||
0f,
|
||||
currentDimensions.width,
|
||||
0f,
|
||||
initial.width
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* To avoid constantly having to regenerate Pruners every time we switch between landscape and portrait or enable/
|
||||
* disable one handed mode, we just normalize the x, y coordinates to the same range as the original which were
|
||||
* active when the Pruner was created.
|
||||
*/
|
||||
private fun normalizeY(y: Float): Float {
|
||||
val initial = initialDimensions[FlorisBoard.getInstance().activeSubtype] ?: return y
|
||||
|
||||
return scaleRange(
|
||||
y,
|
||||
0f,
|
||||
currentDimensions.height,
|
||||
0f,
|
||||
initial.height
|
||||
)
|
||||
}
|
||||
|
||||
private fun scaleRange(x: Float, oldMin: Float, oldMax: Float, newMin: Float, newMax: Float): Float {
|
||||
return (((x - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks gesture classifier for suggestions and then passes that on to the smartbar.
|
||||
* Also commits the most confident suggestion if [commit] is set. All happens on an async executor.
|
||||
* NB: only fetches [MAX_SUGGESTION_COUNT] suggestions.
|
||||
*
|
||||
* @param callback Called when this function completes. Takes a boolean, which indicates if suggestions
|
||||
* were successfully set.
|
||||
*/
|
||||
private fun updateSuggestionsAsync(maxSuggestionsToShow: Int, commit: Boolean, callback: (Boolean) -> Unit) {
|
||||
if (!glideTypingClassifier.ready) {
|
||||
callback.invoke(false)
|
||||
return
|
||||
}
|
||||
|
||||
launch(Dispatchers.Default) {
|
||||
// To avoid cache misses when maxSuggestions goes from 5 to 1.
|
||||
val suggestions = glideTypingClassifier.getSuggestions(MAX_SUGGESTION_COUNT, true)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val textInputManager = TextInputManager.getInstance()
|
||||
textInputManager.glideSuggestionsActive = true
|
||||
textInputManager.hackyGlideSuggestionSkip = false
|
||||
textInputManager.smartbarView?.setCandidateSuggestionWords(
|
||||
System.nanoTime(),
|
||||
suggestions.take(maxSuggestionsToShow).map { textInputManager.fixCase(it) }
|
||||
)
|
||||
textInputManager.smartbarView?.updateCandidateSuggestionCapsState()
|
||||
if (commit && suggestions.isNotEmpty()) {
|
||||
textInputManager.handleGesture(suggestions.first())
|
||||
}
|
||||
callback.invoke(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Dimensions(
|
||||
val width: Float,
|
||||
val height: Float
|
||||
)
|
||||
@@ -0,0 +1,593 @@
|
||||
package dev.patrickgold.florisboard.ime.text.gestures
|
||||
|
||||
import android.util.SparseArray
|
||||
import androidx.collection.LruCache
|
||||
import androidx.core.util.set
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyView
|
||||
import java.text.Normalizer
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* Classifies gestures by comparing them with an "ideal gesture".
|
||||
*
|
||||
* Check out Étienne Desticourt's excellent write up at https://github.com/AnySoftKeyboard/AnySoftKeyboard/pull/1870
|
||||
*/
|
||||
class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
|
||||
private val gesture = Gesture()
|
||||
private var keysByCharacter: SparseArray<KeyView> = SparseArray()
|
||||
private var words: Set<String> = setOf()
|
||||
private var wordFrequencies: Map<String, Int> = hashMapOf()
|
||||
private var keys: ArrayList<KeyView> = arrayListOf()
|
||||
private lateinit var pruner: Pruner
|
||||
private var wordDataSubtype: Subtype? = null
|
||||
private var layoutSubtype: Subtype? = null
|
||||
private var currentSubtype: Subtype? = null
|
||||
val ready: Boolean
|
||||
get() = currentSubtype == layoutSubtype && wordDataSubtype == layoutSubtype && wordDataSubtype != null
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Describes the allowed length variance in a gesture. If a gesture is too long or too short, it is immediately
|
||||
* discarded to save cycles.
|
||||
*/
|
||||
private const val PRUNING_LENGTH_THRESHOLD = 8.42
|
||||
|
||||
/**
|
||||
* describes the number of points to sample a gesture at, i.e the resolution.
|
||||
*/
|
||||
private const val SAMPLING_POINTS: Int = 200
|
||||
|
||||
/**
|
||||
* The minimum distance between points to be added to a gesture.
|
||||
*/
|
||||
private const val MIN_DIST_TO_ADD = 1000
|
||||
|
||||
/**
|
||||
* Standard deviation of the distribution of distances between the shapes of two gestures
|
||||
* representing the same word. It's expressed for normalized gestures and is therefore
|
||||
* independent of the keyboard or key size.
|
||||
*/
|
||||
private const val SHAPE_STD = 22.08f
|
||||
|
||||
/**
|
||||
* Standard deviation of the distribution of distances between the locations of two gestures
|
||||
* representing the same word. It's expressed as a factor of key radius as it's applied to
|
||||
* un-normalized gestures and is therefore dependent on the size of the keys/keyboard.
|
||||
*/
|
||||
private const val LOCATION_STD = 0.5109f
|
||||
|
||||
/**
|
||||
* This is a very small cache that caches suggestions, so that they aren't recalculated e.g when releasing
|
||||
* a pointer when the suggestions were already calculated. Avoids a lot of micro pauses.
|
||||
*/
|
||||
private const val SUGGESTION_CACHE_SIZE = 5
|
||||
|
||||
/**
|
||||
* For multiple subtypes, the pruner is cached.
|
||||
*/
|
||||
private const val PRUNER_CACHE_SIZE = 5
|
||||
}
|
||||
|
||||
override fun addGesturePoint(position: GlideTypingGesture.Detector.Position) {
|
||||
if (!gesture.isEmpty) {
|
||||
val dx = gesture.getLastX() - position.x
|
||||
val dy = gesture.getLastY() - position.y
|
||||
|
||||
if (dx * dx + dy * dy > MIN_DIST_TO_ADD) {
|
||||
gesture.addPoint(position.x, position.y)
|
||||
}
|
||||
} else {
|
||||
gesture.addPoint(position.x, position.y)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setLayout(keyViews: Sequence<KeyView>, subtype: Subtype) {
|
||||
// stop duplicate calls
|
||||
if (this.layoutSubtype == subtype) {
|
||||
return
|
||||
}
|
||||
|
||||
keysByCharacter.clear()
|
||||
keys.clear()
|
||||
keyViews.forEach {
|
||||
keysByCharacter[it.data.code] = it
|
||||
this.keys.add(it)
|
||||
}
|
||||
layoutSubtype = subtype
|
||||
initializePruner()
|
||||
}
|
||||
|
||||
override fun setWordData(words: HashMap<String, Int>, subtype: Subtype) {
|
||||
// stop duplicate calls..
|
||||
if (this.wordDataSubtype == subtype) {
|
||||
return
|
||||
}
|
||||
|
||||
this.words = words.keys
|
||||
this.wordFrequencies = words
|
||||
|
||||
this.wordDataSubtype = subtype
|
||||
initializePruner()
|
||||
}
|
||||
|
||||
private val prunerCache = LruCache<Subtype, Pruner>(PRUNER_CACHE_SIZE)
|
||||
|
||||
/**
|
||||
* Exists because Pruner requires both word data and layout are initialized,
|
||||
* however we don't know what order they're initialized in.
|
||||
*/
|
||||
private fun initializePruner() {
|
||||
if (this.layoutSubtype == null || this.wordDataSubtype != this.layoutSubtype) {
|
||||
// not yet ready
|
||||
return
|
||||
}
|
||||
val currentSubtype = this.layoutSubtype!!
|
||||
val cached = prunerCache.get(currentSubtype)
|
||||
if (cached == null) {
|
||||
this.pruner = Pruner(PRUNING_LENGTH_THRESHOLD, this.words, keysByCharacter)
|
||||
prunerCache.put(currentSubtype, this.pruner)
|
||||
} else {
|
||||
this.pruner = cached
|
||||
}
|
||||
this.currentSubtype = currentSubtype
|
||||
}
|
||||
|
||||
override fun initGestureFromPointerData(pointerData: GlideTypingGesture.Detector.PointerData) {
|
||||
for (position in pointerData.positions) {
|
||||
addGesturePoint(position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val lruSuggestionCache = LruCache<Pair<Gesture, Int>, List<String>>(SUGGESTION_CACHE_SIZE)
|
||||
override fun getSuggestions(maxSuggestionCount: Int, gestureCompleted: Boolean): List<String> {
|
||||
return when (val cached = lruSuggestionCache.get(Pair(this.gesture, maxSuggestionCount))) {
|
||||
null -> {
|
||||
val suggestions = unCachedGetSuggestions(maxSuggestionCount)
|
||||
lruSuggestionCache.put(Pair(this.gesture.clone(), maxSuggestionCount), suggestions)
|
||||
|
||||
suggestions
|
||||
}
|
||||
else -> {
|
||||
cached
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unCachedGetSuggestions(maxSuggestionCount: Int): List<String> {
|
||||
val candidates = arrayListOf<String>()
|
||||
val candidateWeights = arrayListOf<Float>()
|
||||
val key = keys.firstOrNull() ?: return listOf()
|
||||
val radius: Int = min(key.height, key.width)
|
||||
var remainingWords = pruner.pruneByExtremities(gesture, this.keys)
|
||||
val userGesture = gesture.resample(SAMPLING_POINTS)
|
||||
val normalizedUserGesture: Gesture = userGesture.normalizeByBoxSide()
|
||||
remainingWords = pruner.pruneByLength(gesture, remainingWords, keysByCharacter, keys)
|
||||
|
||||
for (i in remainingWords.indices) {
|
||||
val word = remainingWords[i]
|
||||
val idealGestures = Gesture.generateIdealGestures(word, keysByCharacter)
|
||||
|
||||
for (idealGesture in idealGestures) {
|
||||
val wordGesture = idealGesture.resample(SAMPLING_POINTS)
|
||||
val normalizedGesture: Gesture = wordGesture.normalizeByBoxSide()
|
||||
val shapeDistance = calcShapeDistance(normalizedGesture, normalizedUserGesture)
|
||||
val locationDistance = calcLocationDistance(wordGesture, userGesture)
|
||||
val shapeProbability = calcGaussianProbability(shapeDistance, 0.0f, SHAPE_STD)
|
||||
val locationProbability = calcGaussianProbability(locationDistance, 0.0f, LOCATION_STD * radius)
|
||||
val frequency = wordFrequencies[word]!!
|
||||
val confidence = 1.0f / (shapeProbability * locationProbability * frequency)
|
||||
|
||||
var candidateDistanceSortedIndex = 0
|
||||
var duplicateIndex = Int.MAX_VALUE
|
||||
|
||||
while (candidateDistanceSortedIndex < candidateWeights.size
|
||||
&& candidateWeights[candidateDistanceSortedIndex] <= confidence
|
||||
) {
|
||||
if (candidates[candidateDistanceSortedIndex].contentEquals(word)) duplicateIndex =
|
||||
candidateDistanceSortedIndex
|
||||
candidateDistanceSortedIndex++
|
||||
}
|
||||
if (candidateDistanceSortedIndex < maxSuggestionCount && candidateDistanceSortedIndex <= duplicateIndex) {
|
||||
if (duplicateIndex < Int.MAX_VALUE) {
|
||||
candidateWeights.removeAt(duplicateIndex)
|
||||
candidates.removeAt(duplicateIndex)
|
||||
}
|
||||
candidateWeights.add(candidateDistanceSortedIndex, confidence)
|
||||
candidates.add(candidateDistanceSortedIndex, word)
|
||||
if (candidateWeights.size > maxSuggestionCount) {
|
||||
candidateWeights.removeAt(maxSuggestionCount)
|
||||
candidates.removeAt(maxSuggestionCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
this.gesture.clear()
|
||||
}
|
||||
|
||||
private fun calcLocationDistance(gesture1: Gesture, gesture2: Gesture): Float {
|
||||
var totalDistance = 0.0f
|
||||
for (i in 0 until SAMPLING_POINTS) {
|
||||
val x1 = gesture1.getX(i)
|
||||
val x2 = gesture2.getX(i)
|
||||
val y1 = gesture1.getY(i)
|
||||
val y2 = gesture2.getY(i)
|
||||
val distance = abs(x1 - x2) + abs(y1 - y2)
|
||||
totalDistance += distance
|
||||
}
|
||||
return totalDistance / SAMPLING_POINTS / 2
|
||||
|
||||
}
|
||||
|
||||
private fun calcGaussianProbability(value: Float, mean: Float, standardDeviation: Float): Float {
|
||||
val factor = 1.0 / (standardDeviation * sqrt(2 * PI))
|
||||
val exponent = ((value - mean) / standardDeviation).toDouble().pow(2.0)
|
||||
val probability = factor * exp(-1.0 / 2 * exponent)
|
||||
return probability.toFloat()
|
||||
}
|
||||
|
||||
private fun calcShapeDistance(gesture1: Gesture, gesture2: Gesture): Float {
|
||||
var distance: Float
|
||||
var totalDistance = 0.0f
|
||||
for (i in 0 until SAMPLING_POINTS) {
|
||||
val x1 = gesture1.getX(i)
|
||||
val x2 = gesture2.getX(i)
|
||||
val y1 = gesture1.getY(i)
|
||||
val y2 = gesture2.getY(i)
|
||||
distance = Gesture.distance(x1, y1, x2, y2)
|
||||
totalDistance += distance
|
||||
}
|
||||
return totalDistance
|
||||
}
|
||||
|
||||
class Pruner(
|
||||
/**
|
||||
* The length difference between a user gesture and a word gesture above which a word will
|
||||
* be pruned.
|
||||
*/
|
||||
private val lengthThreshold: Double, words: Set<String>, keysByCharacter: SparseArray<KeyView>
|
||||
) {
|
||||
|
||||
/** A tree that provides fast access to words based on their first and last letter. */
|
||||
private val wordTree = HashMap<Pair<Int, Int>, ArrayList<String>>()
|
||||
|
||||
/**
|
||||
* Finds the words whose start and end letter are closest to the start and end points of the
|
||||
* user gesture.
|
||||
*
|
||||
* @param userGesture The current user gesture.
|
||||
* @param keys The keys on the keyboard.
|
||||
* @return A list of likely words.
|
||||
*/
|
||||
fun pruneByExtremities(
|
||||
userGesture: Gesture, keys: Iterable<KeyView>
|
||||
): ArrayList<String> {
|
||||
val remainingWords = ArrayList<String>()
|
||||
val startX = userGesture.getFirstX()
|
||||
val startY = userGesture.getFirstY()
|
||||
val endX = userGesture.getLastX()
|
||||
val endY = userGesture.getLastY()
|
||||
val startKeys = findNClosestKeys(startX, startY, 2, keys)
|
||||
val endKeys = findNClosestKeys(endX, endY, 2, keys)
|
||||
for (startKey in startKeys) {
|
||||
for (endKey in endKeys) {
|
||||
val keyPair = Pair(startKey, endKey)
|
||||
val wordsForKeys = wordTree[keyPair]
|
||||
if (wordsForKeys != null) {
|
||||
remainingWords.addAll(wordsForKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
return remainingWords
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the words whose ideal gesture length is within a certain threshold of the user
|
||||
* gesture's length.
|
||||
*
|
||||
* @param userGesture The current user gesture.
|
||||
* @param words A list of words to consider.
|
||||
* @return A list of words that remained after pruning the input list by length.
|
||||
*/
|
||||
fun pruneByLength(
|
||||
userGesture: Gesture,
|
||||
words: ArrayList<String>,
|
||||
keysByCharacter: SparseArray<KeyView>,
|
||||
keys: List<KeyView>
|
||||
): ArrayList<String> {
|
||||
val remainingWords = ArrayList<String>()
|
||||
|
||||
val key = keys.firstOrNull() ?: return arrayListOf()
|
||||
val radius = min(key.height, key.width)
|
||||
val userLength = userGesture.getLength()
|
||||
for (word in words) {
|
||||
val idealGestures = Gesture.generateIdealGestures(word, keysByCharacter)
|
||||
for (idealGesture in idealGestures) {
|
||||
val wordIdealLength = idealGesture.getLength()
|
||||
if (abs(userLength - wordIdealLength) < lengthThreshold * radius) {
|
||||
remainingWords.add(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
return remainingWords
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getFirstKeyLastKey(
|
||||
word: String, keysByCharacter: SparseArray<KeyView>
|
||||
): Pair<Int, Int>? {
|
||||
val firstLetter = word[0]
|
||||
val lastLetter = word[word.length - 1]
|
||||
val firstBaseChar = Normalizer.normalize(firstLetter.toString(), Normalizer.Form.NFD)[0]
|
||||
val lastBaseChar = Normalizer.normalize(lastLetter.toString(), Normalizer.Form.NFD)[0]
|
||||
return when {
|
||||
keysByCharacter.indexOfKey(firstBaseChar.toInt()) < 0 || keysByCharacter.indexOfKey(lastBaseChar.toInt()) < 0 -> {
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
val firstKey = keysByCharacter[firstBaseChar.toInt()]
|
||||
val lastKey = keysByCharacter[lastBaseChar.toInt()]
|
||||
Pair(firstKey.data.code, lastKey.data.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a chosen number of keys closest to a given point on the keyboard.
|
||||
*
|
||||
* @param x X coordinate of the point.
|
||||
* @param y Y coordinate of the point.
|
||||
* @param n The number of keys to return.
|
||||
* @param keys The keys of the keyboard.
|
||||
* @return A list of the n closest keys.
|
||||
*/
|
||||
private fun findNClosestKeys(
|
||||
x: Float, y: Float, n: Int, keys: Iterable<KeyView>
|
||||
): Iterable<Int> {
|
||||
val keyDistances = HashMap<KeyView, Float>()
|
||||
for (key in keys) {
|
||||
val distance = Gesture.distance(key.centerX, key.centerY, x, y)
|
||||
keyDistances[key] = distance
|
||||
}
|
||||
|
||||
return keyDistances.entries.sortedWith { c1, c2 -> c1.value.compareTo(c2.value) }.take(n)
|
||||
.map { it.key.data.code }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
for (word in words) {
|
||||
val keyPair = getFirstKeyLastKey(word, keysByCharacter)
|
||||
keyPair?.let {
|
||||
wordTree.getOrPut(keyPair, { arrayListOf() }).add(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Gesture(private val xs: FloatArray, private val ys: FloatArray, private var size: Int) {
|
||||
val isEmpty: Boolean
|
||||
get() = size == 0
|
||||
|
||||
constructor() : this(FloatArray(MAX_SIZE), FloatArray(MAX_SIZE), 0)
|
||||
|
||||
companion object {
|
||||
private const val MAX_SIZE = 300
|
||||
|
||||
fun generateIdealGestures(word: String, keysByCharacter: SparseArray<KeyView>): List<Gesture> {
|
||||
val idealGesture = Gesture()
|
||||
val idealGestureWithLoops = Gesture()
|
||||
var previousLetter = '\u0000'
|
||||
|
||||
// Add points for each key
|
||||
for (c in word) {
|
||||
val lc = Character.toLowerCase(c)
|
||||
var key = keysByCharacter[lc.toInt()]
|
||||
if (key == null) {
|
||||
// Try finding the base character instead, e.g., the "e" key instead of "é"
|
||||
val baseCharacter: Char = Normalizer.normalize(lc.toString(), Normalizer.Form.NFD)[0]
|
||||
key = keysByCharacter[baseCharacter.toInt()]
|
||||
if (key == null) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// We adda little loop on the key for duplicate letters
|
||||
// so that we can differentiate words like pool and poll, lull and lul, etc...
|
||||
if (previousLetter == lc) {
|
||||
// bottom right
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.centerX + key.width / 4.0f, key.centerY + key.height / 4.0f
|
||||
)
|
||||
// top right
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.centerX + key.width / 4.0f, key.centerY - key.height / 4.0f
|
||||
)
|
||||
// top left
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.centerX - key.width / 4.0f, key.centerY - key.height / 4.0f
|
||||
)
|
||||
// bottom left
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.centerX - key.width / 4.0f, key.centerY + key.height / 4.0f
|
||||
)
|
||||
idealGesture.addPoint(key.centerX, key.centerY)
|
||||
} else {
|
||||
idealGesture.addPoint(key.centerX, key.centerY)
|
||||
idealGestureWithLoops.addPoint(key.centerX, key.centerY)
|
||||
}
|
||||
previousLetter = lc
|
||||
}
|
||||
return listOf(idealGesture, idealGestureWithLoops)
|
||||
}
|
||||
|
||||
fun distance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
|
||||
return sqrt((x1 - x2).pow(2) + (y1 - y2).pow(2))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun addPoint(x: Float, y: Float) {
|
||||
if (size >= MAX_SIZE) {
|
||||
return
|
||||
}
|
||||
xs[size] = x
|
||||
ys[size] = y
|
||||
size += 1
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resamples the gesture into a new gesture with the chosen number of points by oversampling
|
||||
* it.
|
||||
*
|
||||
* @param numPoints The number of points that the new gesture will have. Must be superior to
|
||||
* the number of points in the current gesture.
|
||||
* @return An oversampled copy of the gesture.
|
||||
*/
|
||||
fun resample(numPoints: Int): Gesture {
|
||||
val interpointDistance = (getLength() / numPoints)
|
||||
val resampledGesture = Gesture()
|
||||
resampledGesture.addPoint(xs[0], ys[0])
|
||||
var lastX = xs[0]
|
||||
var lastY = ys[0]
|
||||
var newX: Float
|
||||
var newY: Float
|
||||
var cumulativeError = 0.0f
|
||||
|
||||
// otherwise nothing happens if size is only 1:
|
||||
if (this.size == 1) {
|
||||
for (i in 0 until SAMPLING_POINTS) {
|
||||
resampledGesture.addPoint(xs[0], ys[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (i in 0 until size - 1) {
|
||||
// We calculate the unit vector from the two points we're between in the actual
|
||||
// gesture
|
||||
var dx = xs[i + 1] - xs[i]
|
||||
var dy = ys[i + 1] - ys[i]
|
||||
val norm = sqrt(dx.pow(2.0f) + dy.pow(2.0f))
|
||||
dx /= norm
|
||||
dy /= norm
|
||||
|
||||
// The number of evenly sampled points that fit between the two actual points
|
||||
var numNewPoints = norm / interpointDistance
|
||||
|
||||
// The number of point that'd fit between the two actual points is often not round,
|
||||
// which means we'll get an increasingly large error as we resample the gesture
|
||||
// and round down that number. To compensate for this we keep track of the error
|
||||
// and add additional points when it gets too large.
|
||||
cumulativeError += numNewPoints - numNewPoints.toInt()
|
||||
if (cumulativeError > 1) {
|
||||
numNewPoints = (numNewPoints.toInt() + cumulativeError.toInt()).toFloat()
|
||||
cumulativeError %= 1
|
||||
}
|
||||
for (j in 0 until numNewPoints.toInt()) {
|
||||
newX = lastX + dx * interpointDistance
|
||||
newY = lastY + dy * interpointDistance
|
||||
lastX = newX
|
||||
lastY = newY
|
||||
resampledGesture.addPoint(newX, newY)
|
||||
}
|
||||
}
|
||||
return resampledGesture
|
||||
}
|
||||
|
||||
fun normalizeByBoxSide(): Gesture {
|
||||
|
||||
val normalizedGesture = Gesture()
|
||||
|
||||
var maxX = -1.0f
|
||||
var maxY = -1.0f
|
||||
var minX = 10000.0f
|
||||
var minY = 10000.0f
|
||||
|
||||
for (i in 0 until size) {
|
||||
maxX = max(xs[i], maxX)
|
||||
maxY = max(ys[i], maxY)
|
||||
minX = min(xs[i], minX)
|
||||
minY = min(ys[i], minY)
|
||||
}
|
||||
|
||||
val width = maxX - minX
|
||||
val height = maxY - minY
|
||||
val longestSide = max(width, height)
|
||||
|
||||
val centroidX = (width / 2 + minX) / longestSide
|
||||
val centroidY = (height / 2 + minY) / longestSide
|
||||
|
||||
for (i in 0 until size) {
|
||||
val x = xs[i] / longestSide - centroidX
|
||||
val y = ys[i] / longestSide - centroidY
|
||||
normalizedGesture.addPoint(x, y)
|
||||
}
|
||||
|
||||
return normalizedGesture
|
||||
}
|
||||
|
||||
fun getFirstX(): Float = xs[0]
|
||||
fun getFirstY(): Float = ys[0]
|
||||
fun getLastX(): Float = xs[size - 1]
|
||||
fun getLastY(): Float = ys[size - 1]
|
||||
|
||||
fun getLength(): Float {
|
||||
var length = 0f
|
||||
for (i in 1 until size) {
|
||||
val previousX = xs[i - 1]
|
||||
val previousY = ys[i - 1]
|
||||
val currentX = xs[i]
|
||||
val currentY = ys[i]
|
||||
length += distance(previousX, previousY, currentX, currentY)
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
this.size = 0
|
||||
}
|
||||
|
||||
fun getX(i: Int): Float = xs[i]
|
||||
fun getY(i: Int): Float = ys[i]
|
||||
|
||||
fun clone(): Gesture {
|
||||
return Gesture(xs.clone(), ys.clone(), size)
|
||||
}
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Gesture
|
||||
|
||||
if (this.size != other.size) return false
|
||||
|
||||
for (i in 0 until size) {
|
||||
if (xs[i] != other.xs[i] || ys[i] != other.ys[i]) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = xs.contentHashCode()
|
||||
result = 31 * result + ys.contentHashCode()
|
||||
result = 31 * result + size
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,9 +19,8 @@ package dev.patrickgold.florisboard.ime.text.gestures
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import dev.patrickgold.florisboard.R
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.atan
|
||||
import kotlin.math.atan2
|
||||
|
||||
/**
|
||||
* Wrapper class which holds all enums, interfaces and classes for detecting a swipe gesture.
|
||||
@@ -188,16 +187,7 @@ abstract class SwipeGesture {
|
||||
* +y
|
||||
*/
|
||||
private fun angle(diffX: Double, diffY: Double): Double {
|
||||
val tmpAngle = abs(360 * atan(diffY / diffX) / (2 * PI))
|
||||
return if (diffX < 0 && diffY >= 0) {
|
||||
180.0f - tmpAngle
|
||||
} else if (diffX < 0 && diffY < 0) {
|
||||
180.0f + tmpAngle
|
||||
} else if (diffX >= 0 && diffY < 0) {
|
||||
360.0f - tmpAngle
|
||||
} else {
|
||||
tmpAngle
|
||||
}
|
||||
return (Math.toDegrees(atan2(diffY, diffX)) + 360) % 360
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.ime.text.key
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
class CurrencySet(
|
||||
val name: String,
|
||||
val label: String,
|
||||
private val slots: List<KeyData>
|
||||
) {
|
||||
companion object {
|
||||
fun isCurrencySlot(keyCode: Int): Boolean {
|
||||
return when (keyCode) {
|
||||
KeyCode.CURRENCY_SLOT_1,
|
||||
KeyCode.CURRENCY_SLOT_2,
|
||||
KeyCode.CURRENCY_SLOT_3,
|
||||
KeyCode.CURRENCY_SLOT_4,
|
||||
KeyCode.CURRENCY_SLOT_5,
|
||||
KeyCode.CURRENCY_SLOT_6 -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun default(): CurrencySet = CurrencySet(
|
||||
name = "\$default",
|
||||
label = "Default",
|
||||
slots = listOf(
|
||||
KeyData(code = 36, label = "$"),
|
||||
KeyData(code = 162, label = "¢"),
|
||||
KeyData(code = 8364, label = "€"),
|
||||
KeyData(code = 163, label = "£"),
|
||||
KeyData(code = 165, label = "¥"),
|
||||
KeyData(code = 8369, label = "₱")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getSlot(keyCode: Int): KeyData? {
|
||||
val slot = abs(keyCode) - abs(KeyCode.CURRENCY_SLOT_1)
|
||||
return slots.getOrNull(slot)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${CurrencySet::class.simpleName} { name=$name, label\"$label\", slots=$slots }"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user