Compare commits
168 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 | ||
|
|
b2ec115505 | ||
|
|
d5c0b11dbe | ||
|
|
2a8ba07040 | ||
|
|
f8c9a52be5 | ||
|
|
670e6ca5e1 | ||
|
|
f2403d00e5 | ||
|
|
224d3e00e3 | ||
|
|
e89a374ce0 | ||
|
|
538e2dd9a2 | ||
|
|
1d3d85c211 | ||
|
|
d6121baca9 | ||
|
|
d6f5789659 | ||
|
|
e7b7df6987 | ||
|
|
8c0337d6c9 | ||
|
|
563a4a919d | ||
|
|
7d6666f7f3 | ||
|
|
2f0d607d02 | ||
|
|
65ae6c2b66 | ||
|
|
14513ec0f1 | ||
|
|
3c58144a3d | ||
|
|
d65b706f78 | ||
|
|
9d820677db | ||
|
|
69c52c00f6 | ||
|
|
c8cf256577 | ||
|
|
386a0999c4 | ||
|
|
d4ef2ea827 | ||
|
|
381ec68e6c | ||
|
|
a5706167b2 | ||
|
|
660871d6c8 | ||
|
|
6607ad1739 | ||
|
|
55c1bc05f2 | ||
|
|
7eb7f0ef80 | ||
|
|
78e5e417ce | ||
|
|
ffbf7f8ea7 | ||
|
|
27cc4897c3 | ||
|
|
e5111a8efe | ||
|
|
80fd5ca84a | ||
|
|
e8f2c6ce74 | ||
|
|
5676cbf18e | ||
|
|
2bdaea6189 | ||
|
|
da2287a739 | ||
|
|
86042bb1e1 | ||
|
|
c99673ff1d | ||
|
|
8b89b27fb0 | ||
|
|
b56c976fa0 | ||
|
|
08889fdc60 | ||
|
|
e8d657e81c | ||
|
|
bfcea8b718 | ||
|
|
7f07686b6c | ||
|
|
e4ecc63b9d | ||
|
|
aacb33bd5d | ||
|
|
a0aa446988 | ||
|
|
fe086ed6d8 | ||
|
|
64ddd0f421 | ||
|
|
40fe72e33c | ||
|
|
b229970ec3 | ||
|
|
ec32c211f1 | ||
|
|
e66b8a052a | ||
|
|
4a22c2698c | ||
|
|
ae95bbd7c4 | ||
|
|
0bdeeaa340 | ||
|
|
92a885a34c | ||
|
|
bc2f03a920 | ||
|
|
f60827b634 | ||
|
|
dcf81b27a0 | ||
|
|
0d8601cb15 | ||
|
|
ecf3c6bf27 | ||
|
|
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!
|
||||
|
||||
24
README.md
24
README.md
@@ -1,14 +1,15 @@
|
||||
<img align="left" width="80" height="80"
|
||||
src="fastlane/metadata/android/en-US/images/icon.png" alt="App icon">
|
||||
|
||||
# FlorisBoard [](https://github.com/florisboard/florisboard/releases) [](https://crowdin.florisboard.patrickgold.dev) 
|
||||
# FlorisBoard [](https://crowdin.florisboard.patrickgold.dev) 
|
||||
|
||||
**FlorisBoard** is a free and open-source keyboard for Android 6.0+
|
||||
devices. It aims at being modern, user-friendly and customizable while
|
||||
fully respecting your privacy. Currently in alpha/early-beta state.
|
||||
fully respecting your privacy. Currently in early-beta state.
|
||||
|
||||
## Public Alpha Test Programme
|
||||
Wanna try it out on your device? Use one of the following options:
|
||||
### Stable [](https://github.com/florisboard/florisboard/releases/latest)
|
||||
|
||||
Releases on this track are in general stable and ready for everyday use, except for features marked as experimental. Use one of the following options to receive FlorisBoard's stable releases:
|
||||
|
||||
_A. Get it on F-Droid_:
|
||||
|
||||
@@ -36,6 +37,16 @@ for and download FlorisBoard without prior joining the alpha group.
|
||||
|
||||
_C. Use the APK provided in the release section of this repo_
|
||||
|
||||
### Beta [](https://github.com/florisboard/florisboard/releases)
|
||||
|
||||
Releases on this track are also in general stable and should be ready for everyday use, though crashes and bugs are more likely to occur. Use releases from this track if you want to get new features faster and give feedback for brand-new stuff. Options to get beta releases:
|
||||
|
||||
_A. IzzySoft's repo for F-Droid_:
|
||||
|
||||
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge">](https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard.beta)
|
||||
|
||||
_B. Use the APK provided in the release section of this repo_
|
||||
|
||||
### Giving feedback
|
||||
If you want to give feedback to FlorisBoard, there are several ways to
|
||||
do so, as listed [here](CONTRIBUTING.md#giving-general-feedback).
|
||||
@@ -63,8 +74,8 @@ 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, ...)
|
||||
* [x] Non-latin character layouts (Arabic, Persian, Greek, Russian (JCUKEN))
|
||||
Hungarian, Croatian, Polish, Romanian, Colemak, Dvorak, Turkish-Q, Turkish-F, ...)
|
||||
* [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
|
||||
@@ -86,6 +97,7 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
|
||||
### Other useful features
|
||||
* [x] One-handed mode
|
||||
* [x] Clipboard/cursor tools
|
||||
* [x] Clipboard manager/history
|
||||
* [x] Integrated number row / symbols in character layouts
|
||||
* [x] Gesture support
|
||||
* [x] Full integration in IME service list of Android (xml/method)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
plugins {
|
||||
id("com.android.application") version "4.1.2"
|
||||
kotlin("android") version "1.4.30"
|
||||
kotlin("kapt") version "1.4.30"
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -21,7 +23,7 @@ android {
|
||||
applicationId = "dev.patrickgold.florisboard"
|
||||
minSdkVersion(23)
|
||||
targetSdkVersion(30)
|
||||
versionCode(29)
|
||||
versionCode(34)
|
||||
versionName("0.3.10")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -44,7 +46,7 @@ android {
|
||||
create("beta") // Needed because by default the "beta" BuildType does not exist
|
||||
named("beta").configure {
|
||||
applicationIdSuffix = ".beta"
|
||||
versionNameSuffix = "-beta01"
|
||||
versionNameSuffix = "-beta06"
|
||||
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
|
||||
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
|
||||
@@ -72,6 +74,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.activity", "activity-ktx", "1.2.1")
|
||||
implementation("androidx.appcompat", "appcompat", "1.2.0")
|
||||
@@ -88,6 +91,8 @@ dependencies {
|
||||
implementation("com.jaredrummler", "colorpicker", "1.1.0")
|
||||
implementation("com.jakewharton.timber", "timber", "4.7.1")
|
||||
implementation("com.nambimobile.widgets", "expandable-fab", "1.0.2")
|
||||
implementation("androidx.room", "room-runtime", "2.2.6")
|
||||
kapt("androidx.room", "room-compiler","2.2.6")
|
||||
|
||||
testImplementation("junit", "junit", "4.13.1")
|
||||
testImplementation("org.mockito", "mockito-inline", "3.7.7")
|
||||
|
||||
@@ -111,6 +111,13 @@
|
||||
android:label="@string/crash_dialog__title"
|
||||
android:theme="@style/CrashDialogTheme"/>
|
||||
|
||||
<provider
|
||||
android:name="dev.patrickgold.florisboard.ime.clip.provider.FlorisContentProvider"
|
||||
android:authorities="${applicationId}.provider.clip"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,225 +1,586 @@
|
||||
{
|
||||
"package": "dev.patrickgold.florisboard",
|
||||
"characterLayouts": {
|
||||
"qwerty": "QWERTY",
|
||||
"qwertz": "QWERTZ",
|
||||
"azerty": "AZERTY",
|
||||
"bepo": "BÉPO",
|
||||
"bulgarian_bds": "Bulgarian (BDS)",
|
||||
"bulgarian_phonetic": "Bulgarian (Phonetic)",
|
||||
"spanish": "Spanish (QWERTY)",
|
||||
"norwegian": "Norwegian (QWERTY)",
|
||||
"swedish_finnish": "Swedish/Finnish (QWERTY)",
|
||||
"danish": "Danish (QWERTY)",
|
||||
"icelandic": "Icelandic (QWERTY)",
|
||||
"swiss_german": "Swiss German (QWERTZ)",
|
||||
"swiss_french": "Swiss French (QWERTZ)",
|
||||
"swiss_italian": "Swiss Italian (QWERTZ)",
|
||||
"hungarian": "Hungarian (QWERTZ)",
|
||||
"persian": "Persian",
|
||||
"arabic": "Arabic",
|
||||
"esperanto": "Esperanto",
|
||||
"esperanto_with_hx": "Esperanto with 'ĥ'",
|
||||
"colemak": "Colemak",
|
||||
"dvorak": "Dvorak",
|
||||
"jcuken_russian": "Russian (JCUKEN)",
|
||||
"canadian_french": "Canadian French (QWERTY)",
|
||||
"greek": "Ελληνικά",
|
||||
"hebrew": "עברית",
|
||||
"serbian_latin": "Serbian (QWERTZ)",
|
||||
"serbian_cyrillic": "Serbian (ЉЊЕРТЗ)",
|
||||
"kurdish": "کوردی"
|
||||
},
|
||||
"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": [
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"~enter": {
|
||||
"main": { "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
"relevant": [
|
||||
{ "code": -216, "label": "toggle_one_handed_mode_right", "type": "system_gui" }
|
||||
{ "code": -216, "label": "toggle_one_handed_mode_right", "type": "system_gui" },
|
||||
{ "code": -214, "label": "switch_to_clipboard_context", "type": "system_gui"}
|
||||
]
|
||||
},
|
||||
"~left": {
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,7 @@
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 382, "label": "ž" }
|
||||
]
|
||||
"main": { "code": 382, "label": "ž" }
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
@@ -50,9 +48,9 @@
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".eu" },
|
||||
{ "code": -255, "label": ".rs" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,46 +1,100 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "tr",
|
||||
"authors": [ "kisekinopureya" ],
|
||||
"authors": [ "kisekinopureya", "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 226, "label": "â" }
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 225, "label": "á" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"main": { "code": 231, "label": "ç" },
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 601, "label": "ə" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
]
|
||||
},
|
||||
"g": {
|
||||
"relevant": [
|
||||
{ "code": 287, "label": "ğ" }
|
||||
]
|
||||
"main": { "code": 287, "label": "ğ" }
|
||||
},
|
||||
"i": {
|
||||
"main": { "code": 305, "label": "ı" },
|
||||
"relevant": [
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 305, "label": "ı" }
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"ı": {
|
||||
"main": { "code": 105, "label": "i" },
|
||||
"relevant": [
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 328, "label": "ň" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"main": { "code": 246, "label": "ö" },
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 244, "label": "ô" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"main": { "code": 351, "label": "ş" },
|
||||
"relevant": [
|
||||
{ "code": 351, "label": "ş" }
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 353, "label": "š" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"main": { "code": 252, "label": "ü" },
|
||||
"relevant": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 251, "label": "û" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 253, "label": "ý" }
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 382, "label": "ž" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
@@ -67,9 +121,9 @@
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".tr" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "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": [
|
||||
|
||||
47
app/src/main/assets/ime/text/characters/turkish_f.json
Normal file
47
app/src/main/assets/ime/text/characters/turkish_f.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "turkish_f",
|
||||
"label": "Turkish-F",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 287, "label": "ğ" },
|
||||
{ "code": 305, "label": "ı" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" }
|
||||
],
|
||||
[
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 351, "label": "ş" }
|
||||
],
|
||||
[
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 120, "label": "x" }
|
||||
]
|
||||
]
|
||||
}
|
||||
47
app/src/main/assets/ime/text/characters/turkish_q.json
Normal file
47
app/src/main/assets/ime/text/characters/turkish_q.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "turkish_q",
|
||||
"label": "Turkish-Q",
|
||||
"authors": [ "patrickgold" ],
|
||||
"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": 287, "label": "ğ" },
|
||||
{ "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": 351, "label": "ş" },
|
||||
{ "code": 105, "label": "i" }
|
||||
],
|
||||
[
|
||||
{ "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": 246, "label": "ö" },
|
||||
{ "code": 231, "label": "ç" }
|
||||
]
|
||||
]
|
||||
}
|
||||
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": [
|
||||
@@ -10,7 +11,8 @@
|
||||
{ "code": -20, "label": "arrow_left", "type": "navigation" },
|
||||
{ "code": -21, "label": "arrow_right", "type": "navigation" },
|
||||
{ "code": -131, "label": "clipboard_cut", "type": "enter_editing" },
|
||||
{ "code": -132, "label": "clipboard_paste", "type": "enter_editing" }
|
||||
{ "code": -132, "label": "clipboard_paste", "type": "enter_editing" },
|
||||
{ "code": -214, "label": "switch_to_clipboard_context", "type": "system_gui"}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
}
|
||||
}
|
||||
|
||||
65
app/src/main/assets/ime/theme/gboard_night.json
Normal file
65
app/src/main/assets/ime/theme/gboard_night.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "gboard_night",
|
||||
"label": "Gboard Night",
|
||||
"authors": [ "Netscaping" ],
|
||||
"isNightTheme": true,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#5e97f6",
|
||||
"colorPrimaryDark": "#4285f4",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "false",
|
||||
"semiTransparentColor": "#20FFFFFF",
|
||||
"textColor": "#FFFFFF"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#292e33"
|
||||
},
|
||||
"key": {
|
||||
"background": "#484c4f",
|
||||
"backgroundPressed": "#5e5e60",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "true"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#BDBDBD"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "#373c41",
|
||||
"foreground": "#9b9da0"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#373c41",
|
||||
"backgroundActive": "#5a5e60",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "transparent",
|
||||
"foreground": "#d4d5d6",
|
||||
"foregroundAlt": "#73FFFFFF"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "#FFFFFF",
|
||||
"foreground": "#686868"
|
||||
},
|
||||
"glideTrail": {"foreground": "#205e97f6"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ItemType
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
|
||||
class ClipboardHistoryItemAdapter(
|
||||
private val dataSet: ArrayDeque<FlorisClipboardManager.TimedClipData>,
|
||||
private val pins: ArrayDeque<ClipboardItem>
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
class ClipboardHistoryTextViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val textView: TextView = view.findViewById(R.id.clipboard_history_item_text)
|
||||
}
|
||||
|
||||
class ClipboardHistoryImageViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val imgView: ImageView = view.findViewById(R.id.clipboard_history_item_img)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_SIZE: Int = 256
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position < pins.size) {
|
||||
// is a pin
|
||||
pins[position].type.value
|
||||
}else {
|
||||
// regular history item
|
||||
dataSet[position - pins.size].data.type.value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
// Create a new view, which defines the UI of the list item
|
||||
val vh = when (viewType) {
|
||||
ItemType.IMAGE.value -> {
|
||||
val view = LayoutInflater.from(viewGroup.context)
|
||||
.inflate(R.layout.clipboard_history_item_image, viewGroup, false)
|
||||
|
||||
ClipboardHistoryImageViewHolder(view)
|
||||
}
|
||||
ItemType.TEXT.value -> {
|
||||
val view = LayoutInflater.from(viewGroup.context)
|
||||
.inflate(R.layout.clipboard_history_item_text, viewGroup, false)
|
||||
|
||||
ClipboardHistoryTextViewHolder(view)
|
||||
}
|
||||
else -> null
|
||||
}!!
|
||||
val clipboardInputManager = ClipboardInputManager.getInstance()
|
||||
(vh.itemView as ClipboardHistoryItemView).keyboardView = clipboardInputManager.getClipboardHistoryView()
|
||||
return vh
|
||||
}
|
||||
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (viewHolder) {
|
||||
is ClipboardHistoryTextViewHolder -> {
|
||||
var text = if (position < pins.size) {
|
||||
(viewHolder.itemView as ClipboardHistoryItemView).setPinned()
|
||||
pins[position].text
|
||||
}else {
|
||||
(viewHolder.itemView as ClipboardHistoryItemView).setUnpinned()
|
||||
dataSet[position - pins.size].data.text
|
||||
}
|
||||
if (text!!.length > MAX_SIZE) {
|
||||
text = text.subSequence(0 until MAX_SIZE).toString() + "..."
|
||||
}
|
||||
viewHolder.textView.text = text
|
||||
}
|
||||
|
||||
is ClipboardHistoryImageViewHolder -> {
|
||||
val uri = if (position < pins.size) {
|
||||
(viewHolder.itemView as ClipboardHistoryItemView).setPinned()
|
||||
pins[position].uri
|
||||
}else {
|
||||
(viewHolder.itemView as ClipboardHistoryItemView).setUnpinned()
|
||||
dataSet[position - pins.size].data.uri
|
||||
}
|
||||
|
||||
|
||||
viewHolder.imgView.clipToOutline = true
|
||||
viewHolder.imgView.visibility = GONE
|
||||
// For very large images, this can take a bit
|
||||
FlorisClipboardManager.getInstance().executor.execute {
|
||||
val resolver = FlorisBoard.getInstance().context.contentResolver
|
||||
val inputStream = resolver.openInputStream(uri!!)
|
||||
|
||||
val drawable = Drawable.createFromStream(inputStream, "clipboard URI")
|
||||
viewHolder.itemView.post {
|
||||
viewHolder.imgView.setImageDrawable(drawable)
|
||||
viewHolder.imgView.visibility = VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun getItemCount() = pins.size + dataSet.size
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
|
||||
class ClipboardHistoryItemView: ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
|
||||
lateinit var keyboardView: ClipboardHistoryView
|
||||
constructor(context: Context) : this(context, null as AttributeSet?)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
private var popupManager: ClipboardPopupManager? = null
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
popupManager = ClipboardPopupManager(keyboardView, FlorisBoard.getInstance().popupLayerView, this)
|
||||
|
||||
setOnClickListener{
|
||||
onClickItem()
|
||||
}
|
||||
|
||||
setOnLongClickListener{
|
||||
onLongClickItem()
|
||||
}
|
||||
|
||||
val themeManager = ThemeManager.default()
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
background.setTint(theme.getAttr(Theme.Attr.KEY_BACKGROUND).toSolidColor().color)
|
||||
val pin = findViewById<ImageView>(R.id.clipboard_pin).drawable
|
||||
pin?.setTint(theme.getAttr(Theme.Attr.KEY_FOREGROUND).toSolidColor().color)
|
||||
}
|
||||
|
||||
|
||||
private fun onLongClickItem() : Boolean {
|
||||
popupManager?.show(this)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onClickItem(){
|
||||
val position = ClipboardInputManager.getInstance().getPositionOfView(this)
|
||||
val instance = FlorisClipboardManager.getInstance()
|
||||
val canPaste = instance.canBePasted(instance.peekHistoryOrPin(position))
|
||||
if (canPaste) {
|
||||
instance.pasteItem(position)
|
||||
}else {
|
||||
Toast.makeText(context, context.getString(R.string.clip__cant_paste), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun setPinned() {
|
||||
val view = findViewById<TextView>(R.id.clipboard_history_item_text)
|
||||
view?.run {
|
||||
val params = layoutParams as LayoutParams
|
||||
params.marginEnd = resources.getDimensionPixelSize(R.dimen.clipboard_text_item_pin_margin)
|
||||
layoutParams = params
|
||||
}
|
||||
findViewById<ImageView>(R.id.clipboard_pin).visibility = VISIBLE
|
||||
invalidate()
|
||||
val themeManager = ThemeManager.default()
|
||||
onThemeUpdated(themeManager.activeTheme)
|
||||
}
|
||||
|
||||
fun setUnpinned(){
|
||||
val view = findViewById<TextView>(R.id.clipboard_history_item_text)
|
||||
// if text view, also update margin.
|
||||
view?.run {
|
||||
val params = layoutParams as LayoutParams
|
||||
params.marginEnd = 0
|
||||
layoutParams = params
|
||||
invalidate()
|
||||
}
|
||||
findViewById<ImageView>(R.id.clipboard_pin).visibility = INVISIBLE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
class ClipboardHistoryView : LinearLayout, FlorisBoard.EventListener,
|
||||
ThemeManager.OnThemeUpdatedListener {
|
||||
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
|
||||
var backButton: ImageButton? = null
|
||||
private set
|
||||
|
||||
var clipText: TextView? = null
|
||||
private set
|
||||
|
||||
var clipboardBar: LinearLayout? = null
|
||||
private set
|
||||
|
||||
private var clipboardHistory: RecyclerView? = null
|
||||
|
||||
private var clearAll: ImageButton? = null
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
florisboard?.addEventListener(this)
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
backButton = findViewById(R.id.back_to_keyboard_button)
|
||||
clipText = findViewById(R.id.clipboard_text)
|
||||
clipboardBar = findViewById(R.id.clipboard_bar)
|
||||
clipboardHistory = findViewById(R.id.clipboard_history_items)
|
||||
clearAll = findViewById(R.id.clear_clipboard_history)
|
||||
|
||||
onApplyThemeAttributes()
|
||||
// lord alone knows why it doesn't work without this..
|
||||
onThemeUpdated(themeManager.activeTheme)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
florisboard?.removeEventListener(this)
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
val fgColor = theme.getAttr(Theme.Attr.KEY_FOREGROUND).toSolidColor().color
|
||||
clipText?.setTextColor(fgColor)
|
||||
backButton?.drawable?.setTint(fgColor)
|
||||
clearAll?.setColorFilter(fgColor)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val height = florisboard?.inputView?.desiredMediaKeyboardViewHeight ?: 0.0f
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height.roundToInt(), MeasureSpec.EXACTLY))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.InputKeyEvent
|
||||
import dev.patrickgold.florisboard.ime.core.InputView
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* Handles the clipboard view and allows for communication between UI and logic.
|
||||
*/
|
||||
class ClipboardInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
FlorisBoard.EventListener{
|
||||
|
||||
private val florisboard = FlorisBoard.getInstance()
|
||||
private var repeatedKeyPressHandler: Handler? = null
|
||||
private var recyclerView: RecyclerView? = null
|
||||
private var adapter: ClipboardHistoryItemAdapter? = null
|
||||
|
||||
companion object {
|
||||
private var instance: ClipboardInputManager? = null
|
||||
|
||||
@Synchronized
|
||||
fun getInstance(): ClipboardInputManager {
|
||||
if (instance == null) {
|
||||
instance = ClipboardInputManager()
|
||||
}
|
||||
return instance!!
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
florisboard.addEventListener(this)
|
||||
}
|
||||
|
||||
override fun onCreateInputView() {
|
||||
super.onCreateInputView()
|
||||
repeatedKeyPressHandler = Handler(florisboard.context.mainLooper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new input view has been registered. Used to initialize all media-relevant
|
||||
* views and layouts.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onRegisterInputView(inputView: InputView) {
|
||||
|
||||
launch(Dispatchers.Default) {
|
||||
|
||||
inputView.findViewById<ImageButton>(R.id.back_to_keyboard_button)
|
||||
.setOnTouchListener { view, event -> onButtonPressEvent(view, event) }
|
||||
|
||||
inputView.findViewById<ImageButton>(R.id.clear_clipboard_history)
|
||||
.setOnTouchListener { view, event -> onButtonPressEvent(view, event) }
|
||||
|
||||
recyclerView = inputView.findViewById(R.id.clipboard_history_items)
|
||||
|
||||
if (BuildConfig.DEBUG && adapter == null) {
|
||||
error("initClipboard() not called")
|
||||
}
|
||||
|
||||
recyclerView!!.adapter = adapter
|
||||
val manager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
|
||||
recyclerView!!.layoutManager = manager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean-up of resources and stopping all coroutines.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
|
||||
cancel()
|
||||
instance = null
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the [ClipboardHistoryView]
|
||||
*/
|
||||
fun getClipboardHistoryView() : ClipboardHistoryView{
|
||||
return FlorisBoard.getInstance().inputView?.mainViewFlipper?.getChildAt(2) as ClipboardHistoryView
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter position of the view, i.e the position that the item is displayed at (including pins and
|
||||
* history items).
|
||||
*
|
||||
* @param view The ClipboardHistoryItemView whose position is to be determined.
|
||||
* @return The adapter position of the view
|
||||
*/
|
||||
fun getPositionOfView(view: View): Int {
|
||||
return recyclerView?.getChildLayoutPosition(view)!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify adapter that an item was inserted.
|
||||
*
|
||||
* @param position The position the item was inserted at
|
||||
*/
|
||||
fun notifyItemInserted(position: Int) = adapter?.notifyItemInserted(position)
|
||||
|
||||
/**
|
||||
* Notify adapter that an item was removed
|
||||
* @param position The position the item was removed from
|
||||
*/
|
||||
fun notifyItemRemoved(position: Int) = adapter?.notifyItemRemoved(position)
|
||||
|
||||
/**
|
||||
* Notify adapter that an item range was removed.
|
||||
* @param start The index the range starts at (inclusive)
|
||||
* @param numberOfItems The number of items removed
|
||||
*/
|
||||
fun notifyItemRangeRemoved(start: Int, numberOfItems: Int) = adapter?.notifyItemRangeRemoved(start, numberOfItems)
|
||||
|
||||
/**
|
||||
* Notify adapter that an item was moved
|
||||
* @param from The original position
|
||||
* @param to The final position
|
||||
*/
|
||||
fun notifyItemMoved(from: Int, to: Int) = adapter?.notifyItemMoved(from, to)
|
||||
|
||||
/**
|
||||
* Notify adapter that an item was changed.
|
||||
*
|
||||
* @param i The position of the item
|
||||
*/
|
||||
fun notifyItemChanged(i: Int) = adapter?.notifyItemChanged(i)
|
||||
|
||||
/**
|
||||
* Handles clicks on the back to keyboard button.
|
||||
*/
|
||||
private fun onButtonPressEvent(view: View, event: MotionEvent?): Boolean {
|
||||
|
||||
event ?: return false
|
||||
val data = when (view.id) {
|
||||
R.id.back_to_keyboard_button -> KeyData(code = KeyCode.SWITCH_TO_TEXT_CONTEXT)
|
||||
R.id.clear_clipboard_history -> KeyData(code = KeyCode.CLEAR_CLIPBOARD_HISTORY)
|
||||
else -> null
|
||||
}!!
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound(data)
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.down(data))
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.up(data))
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(data))
|
||||
}
|
||||
}
|
||||
|
||||
// MUST return false here so the background selector for showing a transparent bg works
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* [recyclerView] will be linked to [dataSet] and [pins] when initialized.
|
||||
*
|
||||
* @param dataSet the data set to link to
|
||||
* @param pins The pins to link to
|
||||
*/
|
||||
fun initClipboard(dataSet: ArrayDeque<FlorisClipboardManager.TimedClipData>, pins: ArrayDeque<ClipboardItem>) {
|
||||
this.adapter = ClipboardHistoryItemAdapter(dataSet = dataSet, pins= pins)
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays an animation of all items moving off the the clipboard from the top.
|
||||
*
|
||||
* @param start The index to start at (to ignore pins)
|
||||
* @param size The size of the clipboard
|
||||
* @return The time in millis till the last animation will complete.
|
||||
*/
|
||||
fun clearClipboardWithAnimation(start: Int, size: Int): Long {
|
||||
// list of views to animate
|
||||
val views = arrayListOf<View>()
|
||||
for(i in 0 until size){
|
||||
recyclerView?.findViewHolderForLayoutPosition(i + start)?.let {
|
||||
views.add(it.itemView)
|
||||
}
|
||||
}
|
||||
|
||||
// animate the views
|
||||
var delay = 1L
|
||||
for (view in views) {
|
||||
delay += (10 * delay.toDouble().pow(0.1)).toLong()
|
||||
val an = view.animate().translationX(1500f)
|
||||
an.startDelay = delay
|
||||
an.duration = 250
|
||||
}
|
||||
|
||||
// a little while later we reset the views so they can be reused.
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
for (view in views) {
|
||||
view.translationX = 0f
|
||||
}
|
||||
}, 450 + delay)
|
||||
|
||||
return 280 + delay
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Space
|
||||
import android.widget.TextView
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupLayerView
|
||||
import kotlin.math.max
|
||||
|
||||
class ClipboardPopupManager(private val keyboardView: ClipboardHistoryView,
|
||||
private val popupLayerView: PopupLayerView?,
|
||||
private val clipboardHistoryItem: ClipboardHistoryItemView) {
|
||||
|
||||
private val popupView: ClipboardPopupView = LayoutInflater.from(keyboardView.context).inflate(R.layout.clip_popup_layout, null) as ClipboardPopupView
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
private var xOffset = 0
|
||||
private var yOffset = 0
|
||||
|
||||
|
||||
init {
|
||||
popupLayerView?.addView(popupView)
|
||||
}
|
||||
|
||||
|
||||
private fun pinButtonListener() {
|
||||
val pos = ClipboardInputManager.getInstance().getPositionOfView(clipboardHistoryItem)
|
||||
val pinned = FlorisClipboardManager.getInstance().isPinned(pos)
|
||||
if (pinned) {
|
||||
FlorisClipboardManager.getInstance().unpinClip(pos)
|
||||
hide()
|
||||
} else {
|
||||
FlorisClipboardManager.getInstance().pinClip(pos)
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a popup.
|
||||
*/
|
||||
fun show(view: ClipboardHistoryItemView) {
|
||||
val pinButton = popupView.findViewById<LinearLayout>(R.id.pin_clip_item)
|
||||
pinButton.setOnClickListener {
|
||||
pinButtonListener()
|
||||
}
|
||||
|
||||
val pos = ClipboardInputManager.getInstance().getPositionOfView(clipboardHistoryItem)
|
||||
val pinned = FlorisClipboardManager.getInstance().isPinned(pos)
|
||||
|
||||
if (pinned) {
|
||||
pinButton.findViewById<TextView>(R.id.pin_clip_item_text).text = view.context.getString(R.string.clip__unpin_item)
|
||||
}
|
||||
|
||||
val delete = popupView.findViewById<LinearLayout>(R.id.remove_from_history)
|
||||
delete.setOnClickListener {
|
||||
FlorisClipboardManager.getInstance().removeClip(pos)
|
||||
hide()
|
||||
}
|
||||
|
||||
val clipboardManager = FlorisClipboardManager.getInstance()
|
||||
val clipItem = clipboardManager.peekHistoryOrPin(pos)
|
||||
val pasteShouldBeEnabled = FlorisClipboardManager.getInstance().canBePasted(clipItem)
|
||||
// the clipboard item has any of the supported mime types of the editor OR is plain text.
|
||||
|
||||
val paste = popupView.findViewById<LinearLayout>(R.id.paste_clip_item)
|
||||
if (pasteShouldBeEnabled) {
|
||||
paste.setOnClickListener {
|
||||
FlorisClipboardManager.getInstance().pasteItem(pos)
|
||||
hide()
|
||||
}
|
||||
popupView.findViewById<Space>(R.id.paste_clip_item_space).visibility = VISIBLE
|
||||
paste.visibility = VISIBLE
|
||||
}else {
|
||||
popupView.findViewById<Space>(R.id.paste_clip_item_space).visibility = GONE
|
||||
paste.visibility = GONE
|
||||
}
|
||||
|
||||
FlorisBoard.getInstance().isClipboardContextMenuShown = true
|
||||
popupLayerView?.clipboardPopupManager = this
|
||||
popupLayerView?.intercept = popupView
|
||||
calc(view)
|
||||
|
||||
popupView.properties.let {
|
||||
it.width = this.width
|
||||
it.height = this.height
|
||||
it.xOffset = this.xOffset
|
||||
it.yOffset = this.yOffset
|
||||
}
|
||||
popupView.show(keyboardView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate sizes of popup.
|
||||
*/
|
||||
private fun calc(view: ClipboardHistoryItemView) {
|
||||
val widthMeasureSpec: Int = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.AT_MOST)
|
||||
val heightMeasureSpec: Int = View.MeasureSpec.makeMeasureSpec(100000, View.MeasureSpec.AT_MOST)
|
||||
popupView.invalidate()
|
||||
popupView.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
width = view.width * 4 / 5
|
||||
height = popupView.measuredHeight
|
||||
xOffset = view.x.toInt() + (view.width - width) / 2
|
||||
// y offset is either where the top of the item is OR if the top is off screen, the top of the keyboard.
|
||||
yOffset = max(view.y.toInt() - keyboardView.height - height / 2 - 20, keyboardView.y.toInt() - keyboardView.height - height / 2 - 20)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a popup.
|
||||
*/
|
||||
fun hide() {
|
||||
popupView.hide()
|
||||
popupLayerView?.intercept = null
|
||||
popupLayerView?.clipboardPopupManager = null
|
||||
FlorisBoard.getInstance().isClipboardContextMenuShown = false
|
||||
|
||||
popupView.apply {
|
||||
visibility = GONE
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.PaintDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
|
||||
class ClipboardPopupView: LinearLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
private var backgroundDrawable: PaintDrawable = PaintDrawable().apply {
|
||||
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
|
||||
}
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
|
||||
val properties: Properties = Properties(
|
||||
width = 0,
|
||||
height = 0,
|
||||
xOffset = 0,
|
||||
yOffset = 0
|
||||
)
|
||||
private val isShowing: Boolean
|
||||
get() = visibility == VISIBLE
|
||||
|
||||
init {
|
||||
visibility = GONE
|
||||
background = backgroundDrawable
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
backgroundDrawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
|
||||
}
|
||||
|
||||
this.findViewById<ImageView>(R.id.pin_clip_item_icon).drawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.WINDOW_TEXT_COLOR).toSolidColor().color)
|
||||
}
|
||||
|
||||
|
||||
this.findViewById<ImageView>(R.id.remove_from_history_icon).drawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.WINDOW_TEXT_COLOR).toSolidColor().color)
|
||||
}
|
||||
|
||||
this.findViewById<ImageView>(R.id.paste_clip_item_icon).drawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.WINDOW_TEXT_COLOR).toSolidColor().color)
|
||||
}
|
||||
|
||||
if (isShowing) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyProperties(anchor: View) {
|
||||
val anchorCoords = IntArray(2)
|
||||
anchor.getLocationInWindow(anchorCoords)
|
||||
val anchorX = anchorCoords[0]
|
||||
val anchorY = anchorCoords[1] + anchor.measuredHeight
|
||||
when (val lp = layoutParams) {
|
||||
is FrameLayout.LayoutParams -> lp.apply {
|
||||
width = properties.width
|
||||
height = properties.height
|
||||
setMargins(
|
||||
anchorX + properties.xOffset,
|
||||
anchorY + properties.yOffset,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
|
||||
setMargins(
|
||||
anchorX + properties.xOffset,
|
||||
anchorY + properties.yOffset,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isShowing) {
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(anchor: View) {
|
||||
applyProperties(anchor)
|
||||
visibility = VISIBLE
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
visibility = GONE
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
data class Properties(
|
||||
var width: Int,
|
||||
var height: Int,
|
||||
var xOffset: Int,
|
||||
var yOffset: Int
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
package dev.patrickgold.florisboard.ime.clip
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.*
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
import dev.patrickgold.florisboard.util.cancelAll
|
||||
import dev.patrickgold.florisboard.util.postAtScheduledRate
|
||||
import timber.log.Timber
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import kotlin.collections.ArrayDeque
|
||||
|
||||
/**
|
||||
* [FlorisClipboardManager] manages the clipboard and clipboard history.
|
||||
*
|
||||
* Also just going to document how all the classes here work.
|
||||
*
|
||||
* [FlorisClipboardManager] handles storage and retrieval of clipboard items. All manipulation of the
|
||||
* clipboard goes through here.
|
||||
*
|
||||
* [ClipboardInputManager] handles the input view and allows for communication between UI and logic
|
||||
*
|
||||
* [ClipboardHistoryView] is the view representing the clipboard context. Only does some theme stuff.
|
||||
*
|
||||
* [ClipboardHistoryItemView] is the view representing an item in the clipboard history (either image or text). Only
|
||||
* does UI stuff.
|
||||
*
|
||||
* [ClipboardHistoryItemAdapter] is the recyclerview adapter that backs the clipboard history.
|
||||
*
|
||||
* [ClipboardPopupManager] handles the popups for each [ClipboardHistoryItemView] (each item has its own popup manager)
|
||||
*
|
||||
* [ClipboardPopupView] is the view representing a popup displayed when long pressing on a clipboard history item.
|
||||
*/
|
||||
class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryClipChangedListener, Closeable {
|
||||
|
||||
private lateinit var pinsDao: PinnedClipboardItemDao
|
||||
lateinit var executor: ExecutorService
|
||||
|
||||
// Using ArrayDeque because it's "technically" the correct data structure (I think).
|
||||
// Newest stored first, oldest stored last.
|
||||
private var history: ArrayDeque<TimedClipData> = ArrayDeque()
|
||||
private var pins: ArrayDeque<ClipboardItem> = ArrayDeque()
|
||||
private var current: ClipboardItem? = null
|
||||
private var onPrimaryClipChangedListeners: ArrayList<OnPrimaryClipChangedListener> = arrayListOf()
|
||||
private lateinit var systemClipboardManager: ClipboardManager
|
||||
private lateinit var handler: Handler
|
||||
private lateinit var prefHelper: PrefHelper
|
||||
|
||||
data class TimedClipData(val data: ClipboardItem, val timeUTC: Long)
|
||||
|
||||
interface OnPrimaryClipChangedListener {
|
||||
fun onPrimaryClipChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: FlorisClipboardManager? = null
|
||||
|
||||
// 1 minute
|
||||
private const val INTERVAL = 60 * 1000L
|
||||
|
||||
@Synchronized
|
||||
fun getInstance(): FlorisClipboardManager {
|
||||
if (instance == null) {
|
||||
instance = FlorisClipboardManager()
|
||||
}
|
||||
return instance!!
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getInstanceOrNull(): FlorisClipboardManager? = instance
|
||||
|
||||
/**
|
||||
* Taken from ClipboardDescription.java from the AOSP
|
||||
*
|
||||
* Helper to compare two MIME types, where one may be a pattern.
|
||||
* @param concreteType A fully-specified MIME type.
|
||||
* @param desiredType A desired MIME type that may be a pattern such as * / *.
|
||||
* @return Returns true if the two MIME types match.
|
||||
*/
|
||||
fun compareMimeTypes(concreteType: String, desiredType: String): Boolean {
|
||||
val typeLength = desiredType.length
|
||||
if (typeLength == 3 && desiredType == "*/*") {
|
||||
return true
|
||||
}
|
||||
val slashpos = desiredType.indexOf('/')
|
||||
if (slashpos > 0) {
|
||||
if (typeLength == slashpos + 2 && desiredType[slashpos + 1] == '*') {
|
||||
if (desiredType.regionMatches(0, concreteType, 0, slashpos + 1)) {
|
||||
return true
|
||||
}
|
||||
} else if (desiredType == concreteType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new item to the clipboard history (if enabled).
|
||||
*/
|
||||
fun updateHistory(newData: ClipboardItem) {
|
||||
val clipboardPrefs = prefHelper.clipboard
|
||||
|
||||
if (clipboardPrefs.enableHistory) {
|
||||
if (clipboardPrefs.limitHistorySize) {
|
||||
var numRemoved = 0
|
||||
while (history.size >= clipboardPrefs.maxHistorySize) {
|
||||
numRemoved += 1
|
||||
history.removeLast().data.close()
|
||||
}
|
||||
ClipboardInputManager.getInstance().notifyItemRangeRemoved(history.size, numRemoved)
|
||||
}
|
||||
|
||||
|
||||
val timed = TimedClipData(newData, System.currentTimeMillis())
|
||||
history.addFirst(timed)
|
||||
ClipboardInputManager.getInstance().notifyItemInserted(pins.size)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Used so that [onPrimaryClipChanged] knows whether it was called by [changeCurrent] (and hence shouldn't update
|
||||
* history)
|
||||
*/
|
||||
private var shouldUpdateHistory = true
|
||||
|
||||
/**
|
||||
* Changes current clipboard item. WITHOUT updating the history.
|
||||
*/
|
||||
fun changeCurrent(newData: ClipboardItem, closePrevious: Boolean) {
|
||||
if (prefHelper.clipboard.enableInternal) {
|
||||
if (closePrevious) current?.close()
|
||||
current = newData
|
||||
val isEqual = when (newData.type) {
|
||||
ItemType.TEXT -> newData.text == systemClipboardManager.primaryClip?.getItemAt(0)?.text
|
||||
ItemType.IMAGE -> newData.uri == systemClipboardManager.primaryClip?.getItemAt(0)?.uri
|
||||
}
|
||||
if (prefHelper.clipboard.syncToSystem && !isEqual)
|
||||
systemClipboardManager.setPrimaryClip(newData.toClipData())
|
||||
} else {
|
||||
shouldUpdateHistory = false
|
||||
systemClipboardManager.setPrimaryClip(newData.toClipData())
|
||||
}
|
||||
onPrimaryClipChangedListeners.forEach { it.onPrimaryClipChanged() }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change the current text on clipboard, update history (if enabled).
|
||||
*
|
||||
*/
|
||||
fun addNewClip(newData: ClipboardItem) {
|
||||
updateHistory(newData)
|
||||
// If history is disabled, this new item will replace the old one and hence should be closed.
|
||||
changeCurrent(newData, !prefHelper.clipboard.enableHistory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps some plaintext in a ClipData and calls [addNewClip]
|
||||
*/
|
||||
fun addNewPlaintext(newText: String) {
|
||||
val newData = ClipboardItem(null, ItemType.TEXT, null, newText, ClipboardItem.TEXT_PLAIN)
|
||||
addNewClip(newData)
|
||||
}
|
||||
|
||||
val primaryClip: ClipboardItem?
|
||||
get() = if (prefHelper.clipboard.enableInternal) {
|
||||
current
|
||||
} else {
|
||||
systemClipboardManager.primaryClip?.let { ClipboardItem.fromClipData(it, false) }
|
||||
}
|
||||
|
||||
fun peekHistory(index: Int): ClipboardItem? {
|
||||
return history.getOrNull(index)?.data
|
||||
}
|
||||
|
||||
fun addPrimaryClipChangedListener(listener: OnPrimaryClipChangedListener) {
|
||||
onPrimaryClipChangedListeners.add(listener)
|
||||
}
|
||||
|
||||
fun removePrimaryClipChangedListener(listener: OnPrimaryClipChangedListener) {
|
||||
onPrimaryClipChangedListeners.remove(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by system clipboard when the contents are changed
|
||||
*/
|
||||
override fun onPrimaryClipChanged() {
|
||||
// Run on async thread to avoid blocking.
|
||||
if (systemClipboardManager.primaryClip?.getItemAt(0)?.text == null &&
|
||||
systemClipboardManager.primaryClip?.getItemAt(0)?.uri == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val isEqual = when (primaryClip?.type) {
|
||||
ItemType.TEXT -> primaryClip?.text == systemClipboardManager.primaryClip?.getItemAt(0)?.text
|
||||
ItemType.IMAGE -> primaryClip?.uri == systemClipboardManager.primaryClip?.getItemAt(0)?.uri
|
||||
null -> false
|
||||
}
|
||||
systemClipboardManager.primaryClip?.let {
|
||||
if (prefHelper.clipboard.enableInternal) {
|
||||
// In the event that the internal clipboard is enabled, sync to internal clipboard is enabled
|
||||
// and the item is not already in internal clipboard, add it.
|
||||
if (prefHelper.clipboard.syncToFloris && !isEqual) {
|
||||
addNewClip(ClipboardItem.fromClipData(it, true))
|
||||
}
|
||||
} else if (prefHelper.clipboard.enableHistory) {
|
||||
// in the event history is enabled, and it should be updated it is updated
|
||||
if (shouldUpdateHistory) {
|
||||
updateHistory(ClipboardItem.fromClipData(it, false))
|
||||
} else {
|
||||
shouldUpdateHistory = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun hasPrimaryClip(): Boolean {
|
||||
return this.primaryClip != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up.
|
||||
*
|
||||
* Sets [instance] to null for GC. Unregisters the system clipboard listener, cancels clipboard clean ups.
|
||||
*/
|
||||
override fun close() {
|
||||
systemClipboardManager.removePrimaryClipChangedListener(this)
|
||||
handler.cancelAll()
|
||||
instance = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the floris clipboard manager. Exists to avoid dependency loop due to reference
|
||||
* to [FlorisBoard.context]
|
||||
*
|
||||
* Sets up the clipboard cleanup task, links the recycler view in clipInputManager to [history].
|
||||
*
|
||||
* @param context Required to register as an onPrimaryClipChangedListener of ClipboardManager
|
||||
*/
|
||||
fun initialize(context: Context) {
|
||||
|
||||
this.systemClipboardManager = (context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager)
|
||||
systemClipboardManager.addPrimaryClipChangedListener(this)
|
||||
|
||||
prefHelper = PrefHelper.getDefaultInstance(context)
|
||||
|
||||
val cleanUpClipboard = Runnable {
|
||||
|
||||
if (!prefHelper.clipboard.cleanUpOld) {
|
||||
return@Runnable
|
||||
}
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
var numToPop = 0
|
||||
val expiryTime = prefHelper.clipboard.cleanUpAfter * 60 * 1000
|
||||
for (item in history.asReversed()) {
|
||||
if (item.timeUTC + expiryTime < currentTime) {
|
||||
numToPop += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
for (i in 0 until numToPop) {
|
||||
history.removeLast().data.close()
|
||||
}
|
||||
ClipboardInputManager.getInstance().notifyItemRangeRemoved(pins.size + history.size, numToPop)
|
||||
}
|
||||
FlorisBoard.getInstance().clipInputManager.initClipboard(this.history, this.pins)
|
||||
handler = Handler(Looper.getMainLooper())
|
||||
prefHelper
|
||||
handler.postAtScheduledRate(0, INTERVAL, cleanUpClipboard)
|
||||
executor = FlorisBoard.getInstance().asyncExecutor
|
||||
executor.execute {
|
||||
pinsDao = PinnedItemsDatabase.getInstance().clipboardItemDao()
|
||||
pinsDao.getAll().toCollection(this.pins)
|
||||
FlorisContentProvider.getInstance().initIfNotAlready()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the history with an animation.
|
||||
*/
|
||||
fun clearHistoryWithAnimation() {
|
||||
val clipInputManager = FlorisBoard.getInstance().clipInputManager
|
||||
val delay = clipInputManager.clearClipboardWithAnimation(pins.size, history.size)
|
||||
|
||||
handler.postDelayed({
|
||||
val size = history.size
|
||||
for (item in history) {
|
||||
item.data.close()
|
||||
}
|
||||
history.clear()
|
||||
clipInputManager.notifyItemRangeRemoved(pins.size, size)
|
||||
}, delay)
|
||||
}
|
||||
|
||||
fun pinClip(adapterPos: Int) {
|
||||
val clipInputManager = FlorisBoard.getInstance().clipInputManager
|
||||
val pin = history.removeAt(adapterPos - pins.size)
|
||||
pins.addFirst(pin.data)
|
||||
clipInputManager.notifyItemMoved(adapterPos, 0)
|
||||
clipInputManager.notifyItemChanged(0)
|
||||
|
||||
executor.execute {
|
||||
val uid = pinsDao.insert(pin.data)
|
||||
pin.data.uid = uid
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item at a particular [adapterPos] (i.e the position the item is displayed at.)
|
||||
*/
|
||||
fun peekHistoryOrPin(adapterPos: Int): ClipboardItem {
|
||||
return when {
|
||||
adapterPos < pins.size -> pins[adapterPos]
|
||||
else -> history[adapterPos - pins.size].data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun isPinned(position: Int): Boolean {
|
||||
return when {
|
||||
position < pins.size -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun unpinClip(adapterPos: Int) {
|
||||
val clipInputManager = FlorisBoard.getInstance().clipInputManager
|
||||
val item = pins.removeAt(adapterPos)
|
||||
|
||||
val clipboardPrefs = prefHelper.clipboard
|
||||
if (clipboardPrefs.limitHistorySize) {
|
||||
var numRemoved = 0
|
||||
while (history.size >= clipboardPrefs.maxHistorySize) {
|
||||
numRemoved += 1
|
||||
history.removeLast().data.close()
|
||||
}
|
||||
ClipboardInputManager.getInstance().notifyItemRangeRemoved(history.size, numRemoved)
|
||||
}
|
||||
|
||||
val timed = TimedClipData(item, System.currentTimeMillis())
|
||||
history.addFirst(timed)
|
||||
|
||||
clipInputManager.notifyItemMoved(adapterPos, pins.size)
|
||||
clipInputManager.notifyItemChanged(pins.size)
|
||||
|
||||
executor.execute {
|
||||
pinsDao.delete(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeClip(pos: Int) {
|
||||
when {
|
||||
pos < pins.size -> {
|
||||
val item = pins.removeAt(pos)
|
||||
executor.execute {
|
||||
Timber.d("removing pin")
|
||||
pinsDao.delete(item)
|
||||
}
|
||||
item.close()
|
||||
}
|
||||
else -> {
|
||||
history.removeAt(pos - pins.size).data.close()
|
||||
}
|
||||
}
|
||||
val clipboardInputManager = ClipboardInputManager.getInstance()
|
||||
clipboardInputManager.notifyItemRemoved(pos)
|
||||
}
|
||||
|
||||
|
||||
fun pasteItem(pos: Int) {
|
||||
val item = peekHistoryOrPin(pos)
|
||||
FlorisBoard.getInstance().activeEditorInstance.commitClipboardItem(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the editor can accept the clip item, else false.
|
||||
*/
|
||||
fun canBePasted(clipItem: ClipboardItem?): Boolean {
|
||||
if (clipItem == null) return false
|
||||
|
||||
return clipItem.mimeTypes.contains("text/plain") || FlorisBoard.getInstance().activeEditorInstance.contentMimeTypes?.any { editorType ->
|
||||
clipItem.mimeTypes.any { clipType ->
|
||||
if (editorType != null) {
|
||||
compareMimeTypes(clipType, editorType)
|
||||
}else { false }
|
||||
}
|
||||
} == true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.patrickgold.florisboard.ime.clip.provider
|
||||
|
||||
import android.net.Uri
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Backend class which is used by [FlorisContentProvider] to serve content.
|
||||
*/
|
||||
class FileStorage private constructor() {
|
||||
|
||||
|
||||
companion object {
|
||||
private const val BUF_SIZE = 1024 * 8
|
||||
private var instance: FileStorage? = null
|
||||
private var offset = 0
|
||||
|
||||
|
||||
fun getInstance() : FileStorage {
|
||||
if (this.instance == null){
|
||||
this.instance = FileStorage()
|
||||
}
|
||||
return instance!!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clones a content URI to internal storage.
|
||||
* @param uri The URI
|
||||
* @return the file's name which is a unique long
|
||||
*/
|
||||
@Synchronized
|
||||
fun cloneURI(uri: Uri) : Long {
|
||||
val context = FlorisBoard.getInstance().context
|
||||
// nanoTime + the number of items created so that it's unique.
|
||||
val name = (System.nanoTime() + offset)
|
||||
|
||||
// Just a normal copy from input stream to output stream.
|
||||
val source = context.contentResolver.openInputStream(uri)!!
|
||||
val sink = File(context.filesDir, name.toString()).outputStream()
|
||||
var nread = 0L
|
||||
val buf = ByteArray(BUF_SIZE)
|
||||
var n: Int
|
||||
while (source.read(buf).also { n = it } > 0) {
|
||||
sink.write(buf, 0, n)
|
||||
nread += n.toLong()
|
||||
}
|
||||
|
||||
source.close()
|
||||
sink.close()
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the file corresponding to an id.
|
||||
*/
|
||||
fun deleteById(id: Long) {
|
||||
Timber.d("Cleaning up $id")
|
||||
val file = File(FlorisBoard.getInstance().filesDir, id.toString())
|
||||
file.delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file address of an id.
|
||||
*/
|
||||
fun getAddress(id: Long): String {
|
||||
return FlorisBoard.getInstance().filesDir.toString() + "/$id"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package dev.patrickgold.florisboard.ime.clip.provider
|
||||
|
||||
import android.content.*
|
||||
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
|
||||
import java.util.concurrent.ExecutorService
|
||||
|
||||
/**
|
||||
* Allows apps to access images on the clipboard.
|
||||
*
|
||||
* This is sometimes called by the UI thread, so all functions are non blocking.
|
||||
* Database accesses are performed async.
|
||||
*/
|
||||
class FlorisContentProvider : ContentProvider() {
|
||||
private lateinit var fileUriDao: FileUriDao
|
||||
private val mimeTypes: HashMap<Long, Array<String>> = hashMapOf()
|
||||
private lateinit var executor: ExecutorService
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
instance = this
|
||||
return true
|
||||
}
|
||||
|
||||
fun initIfNotAlready(){
|
||||
if (this::fileUriDao.isInitialized){
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
fileUriDao = Room.databaseBuilder(
|
||||
context!!,
|
||||
FileUriDatabase::class.java, "fileuridb"
|
||||
).build().fileUriDao()
|
||||
|
||||
executor = FlorisBoard.getInstance().asyncExecutor
|
||||
for (fileUri in fileUriDao.getAll()) {
|
||||
mimeTypes[fileUri.fileName] = fileUri.mimeTypes
|
||||
}
|
||||
}
|
||||
|
||||
override fun query(
|
||||
uri: Uri,
|
||||
projection: Array<out String>?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<out String>?,
|
||||
sortOrder: String?
|
||||
): Cursor? {
|
||||
// just return nothing, nothing should call this function at all.
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String {
|
||||
return when (matcher.match(uri)) {
|
||||
CLIP_ITEM -> mimeTypes.getOrElse(ContentUris.parseId(uri), { throw IllegalArgumentException("Don't have this item!") })[0]
|
||||
CLIPS_TABLE -> "vnd.android.cursor.dir/$AUTHORITY.clip"
|
||||
else -> throw IllegalArgumentException("Don't know what this is $uri")
|
||||
}
|
||||
}
|
||||
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {
|
||||
val id = ContentUris.parseId(uri)
|
||||
val path = File(FileStorage.getInstance().getAddress(id))
|
||||
|
||||
// Nothing has permission to write anyway.
|
||||
return ParcelFileDescriptor.open(path, ParcelFileDescriptor.MODE_READ_ONLY)
|
||||
}
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri {
|
||||
when (matcher.match(uri)){
|
||||
CLIPS_TABLE -> {
|
||||
val id = FileStorage.getInstance().cloneURI(Uri.parse(values?.getAsString("uri")))
|
||||
val mimes = values?.getAsString("mimetypes")?.split(",")?.toTypedArray()
|
||||
mimes?.let {
|
||||
mimeTypes[id] = mimes
|
||||
executor.execute {
|
||||
Timber.d("Inserted file uri $id")
|
||||
fileUriDao.insert(FileUri(id, mimes))
|
||||
}
|
||||
}
|
||||
|
||||
return ContentUris.withAppendedId(CLIPS_URI, id)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Don't know what this is $uri")
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
|
||||
when (matcher.match(uri)){
|
||||
CLIP_ITEM -> {
|
||||
val id = ContentUris.parseId(uri)
|
||||
FileStorage.getInstance().deleteById(id)
|
||||
mimeTypes.remove(id)
|
||||
context?.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
executor.execute {
|
||||
fileUriDao.delete(id)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
else -> throw IllegalArgumentException("Don't know what this is $uri")
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
|
||||
throw IllegalArgumentException("This ContentProvider does not support update.")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: FlorisContentProvider? = null
|
||||
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")
|
||||
|
||||
fun getInstance(): FlorisContentProvider {
|
||||
return instance!!
|
||||
}
|
||||
|
||||
private const val CLIPS_TABLE = 1
|
||||
private const val CLIP_ITEM = 0
|
||||
|
||||
val matcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
|
||||
addURI(AUTHORITY, "clips/#", CLIP_ITEM)
|
||||
addURI(AUTHORITY, "clips", CLIPS_TABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package dev.patrickgold.florisboard.ime.clip.provider
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ContentValues
|
||||
import android.net.Uri
|
||||
import android.provider.BaseColumns
|
||||
import androidx.room.*
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import java.io.Closeable
|
||||
|
||||
|
||||
enum class ItemType(val value: Int) {
|
||||
TEXT(1),
|
||||
IMAGE(2);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value : Int) : ItemType {
|
||||
return values().first { it.value == value }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents an item on the clipboard.
|
||||
* The URI stored belongs to FlorisContentProvider, not whatever app copied the image
|
||||
*
|
||||
* If type == ItemType.IMAGE there must be a uri set
|
||||
* if type == ItemType.TEXT there must be a text set
|
||||
*/
|
||||
@Entity(tableName = "pins")
|
||||
data class ClipboardItem(
|
||||
/** Only used for pins */
|
||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name=BaseColumns._ID, index=true) var uid: Long?,
|
||||
val type: ItemType,
|
||||
val uri: Uri?,
|
||||
val text: String?,
|
||||
val mimeTypes: Array<String>) : Closeable{
|
||||
|
||||
/**
|
||||
* Creates a new ClipData which has the same contents as this.
|
||||
*/
|
||||
fun toClipData(): ClipData {
|
||||
return when (type) {
|
||||
ItemType.IMAGE -> {
|
||||
ClipData.newUri(FlorisBoard.getInstance().context.contentResolver, "Clipboard data", uri)
|
||||
}
|
||||
ItemType.TEXT -> {
|
||||
ClipData.newPlainText("Clipboard data", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the content provider to delete this URI. If not an image, is a noop
|
||||
*/
|
||||
override fun close() {
|
||||
if (type == ItemType.IMAGE) {
|
||||
FlorisBoard.getInstance().context.contentResolver.delete(this.uri!!, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ClipboardItem
|
||||
|
||||
if (uid != other.uid) return false
|
||||
if (type != other.type) return false
|
||||
if (uri != other.uri) return false
|
||||
if (text != other.text) return false
|
||||
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = uid.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
result = 31 * result + (uri?.hashCode() ?: 0)
|
||||
result = 31 * result + (text?.hashCode() ?: 0)
|
||||
result = 31 * result + mimeTypes.contentHashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
fun stringRepresentation(): String {
|
||||
return when {
|
||||
uri != null -> "(Image) $uri"
|
||||
text != null -> text
|
||||
else -> "#ERROR"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* So that every item doesn't have to allocate its own array.
|
||||
*/
|
||||
val TEXT_PLAIN = arrayOf("text/plain")
|
||||
|
||||
/**
|
||||
* Returns a new ClipboardItem based on a ClipData
|
||||
*
|
||||
* @param data The ClipData to clone.
|
||||
* @param cloneUri Whether to store the image using [FlorisContentProvider].
|
||||
*/
|
||||
fun fromClipData(data: ClipData, cloneUri: Boolean) : ClipboardItem {
|
||||
|
||||
val type = when {
|
||||
data.getItemAt(0)?.uri != null -> ItemType.IMAGE
|
||||
data.getItemAt(0)?.text != null -> ItemType.TEXT
|
||||
else -> null
|
||||
}!!
|
||||
|
||||
val uri = if (type == ItemType.IMAGE) {
|
||||
if (data.getItemAt(0).uri.authority == FlorisContentProvider.CONTENT_URI.authority || !cloneUri){
|
||||
data.getItemAt(0).uri
|
||||
}else {
|
||||
val values = ContentValues().apply{
|
||||
put("uri", data.getItemAt(0).uri.toString())
|
||||
put("mimetypes", data.description.filterMimeTypes("*/*").joinToString(","))
|
||||
}
|
||||
FlorisBoard.getInstance().context.contentResolver.insert(FlorisContentProvider.CLIPS_URI, values)
|
||||
}
|
||||
} else { null }
|
||||
|
||||
val text = data.getItemAt(0).text?.toString()
|
||||
val mimeTypes = when (type) {
|
||||
ItemType.IMAGE -> {
|
||||
(0 until data.description.mimeTypeCount).map {
|
||||
data.description.getMimeType(it)
|
||||
}.toTypedArray()
|
||||
}
|
||||
ItemType.TEXT -> { TEXT_PLAIN }
|
||||
}
|
||||
|
||||
return ClipboardItem(null, type, uri, text, mimeTypes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Converters {
|
||||
@TypeConverter
|
||||
fun uriFromString(value: String?): Uri? {
|
||||
return Uri.parse(value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringFromUri(value: Uri?): String {
|
||||
return value.toString()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun itemTypeToInt(value: ItemType?): Int? {
|
||||
return value?.value
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun intToItemType(value: Int?): ItemType? {
|
||||
return value?.let { ItemType.fromInt(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Only works because the string array is a mimetype.
|
||||
* DOES NOT USE A GENERALIZED FORMAT.
|
||||
*/
|
||||
@TypeConverter
|
||||
fun mimeTypesToString(mimeTypes: Array<String>): String {
|
||||
return mimeTypes.joinToString(",")
|
||||
}
|
||||
@TypeConverter
|
||||
fun stringToMimeTypes(value: String): Array<String> {
|
||||
return value.split(",").toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Dao
|
||||
interface PinnedClipboardItemDao {
|
||||
@Query("SELECT * FROM pins")
|
||||
fun getAll(): List<ClipboardItem>
|
||||
|
||||
@Insert
|
||||
fun insert(item: ClipboardItem) : Long
|
||||
|
||||
@Delete
|
||||
fun delete(item: ClipboardItem)
|
||||
}
|
||||
|
||||
@Database(entities = [ClipboardItem::class], version = 1)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class PinnedItemsDatabase : RoomDatabase() {
|
||||
abstract fun clipboardItemDao() : PinnedClipboardItemDao
|
||||
|
||||
companion object {
|
||||
private var instance: PinnedItemsDatabase? = null
|
||||
|
||||
fun getInstance(): PinnedItemsDatabase {
|
||||
|
||||
if (instance == null) {
|
||||
instance = Room.databaseBuilder(
|
||||
FlorisBoard.getInstance().context,
|
||||
PinnedItemsDatabase::class.java,
|
||||
"pins").build()
|
||||
}
|
||||
|
||||
return instance!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(tableName = "file_uris")
|
||||
data class FileUri(
|
||||
@PrimaryKey @ColumnInfo(name=BaseColumns._ID, index=true) val fileName: Long,
|
||||
val mimeTypes: Array<String>
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as FileUri
|
||||
|
||||
if (fileName != other.fileName) return false
|
||||
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = 31 + fileName.hashCode()
|
||||
result = 31 * result + mimeTypes.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface FileUriDao {
|
||||
@Query("SELECT * FROM file_uris WHERE ${BaseColumns._ID} == (:uid)")
|
||||
fun getById(uid: Long) : FileUri
|
||||
|
||||
@Query("DELETE FROM file_uris WHERE ${BaseColumns._ID} == (:id)")
|
||||
fun delete(id: Long)
|
||||
|
||||
@Insert
|
||||
fun insert(vararg fileUris: FileUri)
|
||||
|
||||
@Query("SELECT COUNT(*) FROM file_uris WHERE ${BaseColumns._ID} == (:id)")
|
||||
fun numberWithId(id: Long): Int
|
||||
|
||||
@Query("SELECT * FROM file_uris")
|
||||
fun getAll(): List<FileUri>
|
||||
}
|
||||
|
||||
@Database(entities = [FileUri::class], version = 1)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class FileUriDatabase : RoomDatabase() {
|
||||
abstract fun fileUriDao() : FileUriDao
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import android.content.ClipDescription
|
||||
import android.content.Intent
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
@@ -26,6 +28,12 @@ import android.view.KeyEvent
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputConnection
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat
|
||||
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ItemType
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Class which holds information relevant to an editor instance like the [cachedInput], [selection],
|
||||
@@ -36,10 +44,12 @@ class EditorInstance private constructor(
|
||||
private val ims: InputMethodService?,
|
||||
val imeOptions: ImeOptions,
|
||||
val inputAttributes: InputAttributes,
|
||||
val packageName: String
|
||||
val packageName: String,
|
||||
private val editorInfo: EditorInfo
|
||||
) {
|
||||
val cachedInput: CachedInput = CachedInput(this)
|
||||
var contentMimeTypes: Array<out String?>? = null
|
||||
private val florisClipboardManager: FlorisClipboardManager = FlorisClipboardManager.getInstance()
|
||||
val cursorCapsMode: InputAttributes.CapsMode
|
||||
get() {
|
||||
val ic = inputConnection ?: return InputAttributes.CapsMode.NONE
|
||||
@@ -75,7 +85,8 @@ class EditorInstance private constructor(
|
||||
ims = null,
|
||||
imeOptions = ImeOptions.fromImeOptionsInt(EditorInfo.IME_NULL),
|
||||
inputAttributes = InputAttributes.fromInputTypeInt(InputType.TYPE_NULL),
|
||||
packageName = "undefined"
|
||||
packageName = "undefined",
|
||||
editorInfo = EditorInfo()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,7 +96,8 @@ class EditorInstance private constructor(
|
||||
ims = ims,
|
||||
imeOptions = ImeOptions.fromImeOptionsInt(editorInfo.imeOptions),
|
||||
inputAttributes = InputAttributes.fromInputTypeInt(editorInfo.inputType),
|
||||
packageName = editorInfo.packageName
|
||||
packageName = editorInfo.packageName,
|
||||
editorInfo = editorInfo
|
||||
).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
contentMimeTypes = editorInfo.contentMimeTypes
|
||||
@@ -198,6 +210,80 @@ 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].
|
||||
* If the item has a content URI (and the EditText supports it), the item is committed as rich data.
|
||||
* This allows for committing (e.g) images.
|
||||
*
|
||||
* @param item The ClipboardItem to commit
|
||||
* @return True on success, false if something went wrong.
|
||||
*/
|
||||
fun commitClipboardItem(item: ClipboardItem): Boolean {
|
||||
val mimeTypes = item.mimeTypes
|
||||
return when (item.type){
|
||||
ItemType.IMAGE -> {
|
||||
val inputContentInfo = InputContentInfoCompat(
|
||||
item.uri!!,
|
||||
ClipDescription("clipboard image", mimeTypes),
|
||||
null
|
||||
)
|
||||
val ic = inputConnection ?: return false
|
||||
ic.finishComposingText()
|
||||
var flags = 0
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
flags = flags or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
|
||||
}else {
|
||||
FlorisBoard.getInstance().context.grantUriPermission(editorInfo.packageName, item.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
InputConnectionCompat.commitContent(ic, editorInfo, inputContentInfo, flags, null)
|
||||
}
|
||||
ItemType.TEXT -> {
|
||||
commitText(item.text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes a backward delete on this editor's text. If a text selection is active, all
|
||||
* characters inside this selection will be removed, else only the left-most character from
|
||||
@@ -230,12 +316,14 @@ class EditorInstance private constructor(
|
||||
ic.beginBatchEdit()
|
||||
markComposingRegion(null)
|
||||
|
||||
getWordsInString(cachedInput.rawText.substring(0,
|
||||
(selection.start - cachedInput.offset).coerceAtLeast(0))).run {
|
||||
get(size - n.coerceAtLeast(0)).range
|
||||
}.run {
|
||||
ic.setSelection(first + cachedInput.offset, selection.start)
|
||||
}
|
||||
try {
|
||||
getWordsInString(cachedInput.rawText.substring(0,
|
||||
(selection.start - cachedInput.offset).coerceAtLeast(0))).run {
|
||||
get(size - n.coerceAtLeast(0)).range
|
||||
}.run {
|
||||
ic.setSelection(first + cachedInput.offset, selection.start)
|
||||
}
|
||||
} catch (e: Exception) {}
|
||||
|
||||
ic.commitText("", 1)
|
||||
|
||||
@@ -355,15 +443,11 @@ class EditorInstance private constructor(
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performClipboardCut(): Boolean {
|
||||
Timber.d("performClipboardCut")
|
||||
isPhantomSpaceActive = false
|
||||
wasPhantomSpaceActiveLastUpdate = false
|
||||
val ic = inputConnection ?: return false
|
||||
if (isRawInputEditor) {
|
||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_X, meta(ctrl = true))
|
||||
} else {
|
||||
ic.performContextMenuAction(android.R.id.cut)
|
||||
}
|
||||
return true
|
||||
florisClipboardManager.addNewPlaintext(selection.text)
|
||||
return sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,17 +457,11 @@ class EditorInstance private constructor(
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performClipboardCopy(): Boolean {
|
||||
Timber.d("performClipboardCopy")
|
||||
isPhantomSpaceActive = false
|
||||
wasPhantomSpaceActiveLastUpdate = false
|
||||
val ic = inputConnection ?: return false
|
||||
if (isRawInputEditor) {
|
||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_C, meta(ctrl = true)) &&
|
||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
|
||||
} else {
|
||||
ic.performContextMenuAction(android.R.id.copy)
|
||||
selection.updateAndNotify(selection.end, selection.end)
|
||||
}
|
||||
return true
|
||||
florisClipboardManager.addNewPlaintext(selection.text)
|
||||
return selection.updateAndNotify(selection.end, selection.end)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,13 +473,8 @@ class EditorInstance private constructor(
|
||||
fun performClipboardPaste(): Boolean {
|
||||
isPhantomSpaceActive = false
|
||||
wasPhantomSpaceActiveLastUpdate = false
|
||||
val ic = inputConnection ?: return false
|
||||
if (isRawInputEditor) {
|
||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_V, meta(ctrl = true))
|
||||
} else {
|
||||
ic.performContextMenuAction(android.R.id.paste)
|
||||
}
|
||||
return true
|
||||
Timber.d("Before commit clip data")
|
||||
return commitClipboardItem(florisClipboardManager.primaryClip!!)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
@@ -42,12 +41,15 @@ import androidx.lifecycle.*
|
||||
import com.squareup.moshi.Json
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.clip.ClipboardInputManager
|
||||
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
|
||||
import dev.patrickgold.florisboard.ime.media.MediaInputManager
|
||||
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
|
||||
@@ -58,6 +60,8 @@ import dev.patrickgold.florisboard.util.*
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* Variable which holds the current [FlorisBoard] instance. To get this instance from another
|
||||
@@ -69,7 +73,7 @@ private var florisboardInstance: FlorisBoard? = null
|
||||
* Core class responsible to link together both the text and media input managers as well as
|
||||
* managing the one-handed UI.
|
||||
*/
|
||||
class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPrimaryClipChangedListener,
|
||||
class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager.OnPrimaryClipChangedListener,
|
||||
ThemeManager.OnThemeUpdatedListener {
|
||||
|
||||
lateinit var prefs: PrefHelper
|
||||
@@ -89,7 +93,7 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
|
||||
private var audioManager: AudioManager? = null
|
||||
var imeManager:InputMethodManager? = null
|
||||
var clipboardManager: ClipboardManager? = null
|
||||
var florisClipboardManager: FlorisClipboardManager? = null
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
private var vibrator: Vibrator? = null
|
||||
private val osHandler = Handler()
|
||||
@@ -116,14 +120,20 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
|
||||
val textInputManager: TextInputManager
|
||||
val mediaInputManager: MediaInputManager
|
||||
val clipInputManager: ClipboardInputManager
|
||||
|
||||
var isClipboardContextMenuShown = false
|
||||
|
||||
init {
|
||||
florisboardInstance = this
|
||||
|
||||
textInputManager = TextInputManager.getInstance()
|
||||
mediaInputManager = MediaInputManager.getInstance()
|
||||
clipInputManager = ClipboardInputManager.getInstance()
|
||||
}
|
||||
|
||||
lateinit var asyncExecutor: ExecutorService
|
||||
|
||||
companion object {
|
||||
private const val IME_ID: String = "dev.patrickgold.florisboard/.ime.core.FlorisBoard"
|
||||
private const val IME_ID_BETA: String = "dev.patrickgold.florisboard.beta/dev.patrickgold.florisboard.ime.core.FlorisBoard"
|
||||
@@ -209,13 +219,10 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
)
|
||||
}*/
|
||||
Timber.i("onCreate()")
|
||||
|
||||
serviceLifecycleDispatcher.onServicePreSuperOnCreate()
|
||||
|
||||
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
||||
clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
clipboardManager?.addPrimaryClipChangedListener(this)
|
||||
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
||||
prefs = PrefHelper.getDefaultInstance(this)
|
||||
prefs.initDefaultPreferences()
|
||||
@@ -231,6 +238,11 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
|
||||
asyncExecutor = Executors.newSingleThreadExecutor()
|
||||
florisClipboardManager = FlorisClipboardManager.getInstance()
|
||||
florisClipboardManager!!.initialize(context)
|
||||
florisClipboardManager?.addPrimaryClipChangedListener(this)
|
||||
|
||||
super.onCreate()
|
||||
eventListeners.toList().forEach { it?.onCreate() }
|
||||
}
|
||||
@@ -282,7 +294,8 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
Timber.i("onDestroy()")
|
||||
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
clipboardManager?.removePrimaryClipChangedListener(this)
|
||||
florisClipboardManager!!.removePrimaryClipChangedListener(this)
|
||||
florisClipboardManager!!.close()
|
||||
osHandler.removeCallbacksAndMessages(null)
|
||||
florisboardInstance = null
|
||||
|
||||
@@ -340,7 +353,6 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
|
||||
override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
|
||||
Timber.i("onStartInput($attribute, $restarting)")
|
||||
|
||||
super.onStartInput(attribute, restarting)
|
||||
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
|
||||
}
|
||||
@@ -390,6 +402,7 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
textInputManager.layoutManager.clearLayoutCache(KeyboardMode.CHARACTERS)
|
||||
isNumberRowVisible = newIsNumberRowVisible
|
||||
}
|
||||
textInputManager.layoutManager.clearLayoutCache()
|
||||
themeManager.update()
|
||||
updateOneHandedPanelVisibility()
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
@@ -568,6 +581,7 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
val inputView = this.inputView ?: return
|
||||
val inputWindowView = this.inputWindowView ?: return
|
||||
// TODO: Check also if the keyboard is currently suppressed by a hardware keyboard
|
||||
|
||||
if (!isInputViewShown) {
|
||||
outInsets?.contentTopInsets = inputWindowView.height
|
||||
outInsets?.visibleTopInsets = inputWindowView.height
|
||||
@@ -576,6 +590,11 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
val visibleTopY = inputWindowView.height - inputView.measuredHeight
|
||||
outInsets?.contentTopInsets = visibleTopY
|
||||
outInsets?.visibleTopInsets = visibleTopY
|
||||
|
||||
if (isClipboardContextMenuShown) {
|
||||
outInsets?.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME
|
||||
outInsets?.touchableRegion?.setEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -725,16 +744,23 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
private fun onSubtypeChanged(newSubtype: Subtype) {
|
||||
textInputManager.onSubtypeChanged(newSubtype)
|
||||
mediaInputManager.onSubtypeChanged(newSubtype)
|
||||
clipInputManager.onSubtypeChanged(newSubtype)
|
||||
}
|
||||
|
||||
fun setActiveInput(type: Int) {
|
||||
fun setActiveInput(type: Int, forceSwitchToCharacters: Boolean = false) {
|
||||
when (type) {
|
||||
R.id.text_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild = 0
|
||||
if (forceSwitchToCharacters) {
|
||||
textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(KeyData.VIEW_CHARACTERS))
|
||||
}
|
||||
}
|
||||
R.id.media_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild = 1
|
||||
}
|
||||
R.id.clip_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,39 +849,58 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, ClipboardManager.OnPri
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.content.res.Configuration
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
import dev.patrickgold.florisboard.R
|
||||
@@ -47,6 +46,10 @@ class InputView : LinearLayout {
|
||||
private set
|
||||
var desiredMediaKeyboardViewHeight: Float = resources.getDimension(R.dimen.mediaKeyboardView_baseHeight)
|
||||
private set
|
||||
var heightFactor: Float = 1.0f
|
||||
private set
|
||||
var shouldGiveAdditionalSpace: Boolean = false
|
||||
private set
|
||||
|
||||
var mainViewFlipper: ViewFlipper? = null
|
||||
private set
|
||||
@@ -76,7 +79,7 @@ class InputView : LinearLayout {
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val heightFactor = when (resources.configuration.orientation) {
|
||||
heightFactor = when (resources.configuration.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> 1.0f
|
||||
else -> if (prefs.keyboard.oneHandedMode != OneHandedMode.OFF) {
|
||||
prefs.keyboard.oneHandedModeScaleFactor / 100.0f
|
||||
@@ -98,12 +101,12 @@ class InputView : LinearLayout {
|
||||
var baseSmartbarHeight = 0.16129f * baseHeight
|
||||
var baseTextInputHeight = baseHeight - baseSmartbarHeight
|
||||
val tim = florisboard.textInputManager
|
||||
val shouldGiveAdditionalSpace = prefs.keyboard.numberRow &&
|
||||
shouldGiveAdditionalSpace = prefs.keyboard.numberRow &&
|
||||
!(tim.getActiveKeyboardMode() == KeyboardMode.NUMERIC ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE2)
|
||||
if (shouldGiveAdditionalSpace) {
|
||||
val additionalHeight = desiredTextKeyboardViewHeight * 0.18f
|
||||
val additionalHeight = baseTextInputHeight * 0.25f
|
||||
baseHeight += additionalHeight
|
||||
baseTextInputHeight += additionalHeight
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.VelocityThreshold
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.CandidateView
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.util.TimeUtil
|
||||
import dev.patrickgold.florisboard.util.VersionName
|
||||
@@ -53,6 +54,7 @@ class PrefHelper(
|
||||
val smartbar = Smartbar(this)
|
||||
val suggestion = Suggestion(this)
|
||||
val theme = Theme(this)
|
||||
val clipboard = Clipboard(this)
|
||||
|
||||
/**
|
||||
* Checks the cache if an entry for [key] exists, else calls [getPrefInternal] to retrieve the
|
||||
@@ -122,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
|
||||
@@ -145,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, "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,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
|
||||
@@ -276,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)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,27 +328,28 @@ class PrefHelper(
|
||||
class Keyboard(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val BOTTOM_OFFSET_PORTRAIT = "keyboard__bottom_offset_portrait"
|
||||
const val BOTTOM_OFFSET_LANDSCAPE = "keyboard__bottom_offset_landscape"
|
||||
const val FONT_SIZE_MULTIPLIER_PORTRAIT = "keyboard__font_size_multiplier_portrait"
|
||||
const val FONT_SIZE_MULTIPLIER_LANDSCAPE = "keyboard__font_size_multiplier_landscape"
|
||||
const val HEIGHT_FACTOR = "keyboard__height_factor"
|
||||
const val HEIGHT_FACTOR_CUSTOM = "keyboard__height_factor_custom"
|
||||
const val HINTED_NUMBER_ROW_MODE = "keyboard__hinted_number_row_mode"
|
||||
const val HINTED_SYMBOLS_MODE = "keyboard__hinted_symbols_mode"
|
||||
const val KEY_SPACING_HORIZONTAL = "keyboard__key_spacing_horizontal"
|
||||
const val KEY_SPACING_VERTICAL = "keyboard__key_spacing_vertical"
|
||||
const val LANDSCAPE_INPUT_UI_MODE = "keyboard__landscape_input_ui_mode"
|
||||
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
|
||||
const val NUMBER_ROW = "keyboard__number_row"
|
||||
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
|
||||
const val ONE_HANDED_MODE_SCALE_FACTOR = "keyboard__one_handed_mode_scale_factor"
|
||||
const val POPUP_ENABLED = "keyboard__popup_enabled"
|
||||
const val SOUND_ENABLED = "keyboard__sound_enabled"
|
||||
const val SOUND_VOLUME = "keyboard__sound_volume"
|
||||
const val UTILITY_KEY_ACTION = "keyboard__utility_key_action"
|
||||
const val UTILITY_KEY_ENABLED = "keyboard__utility_key_enabled"
|
||||
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
|
||||
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
|
||||
const val BOTTOM_OFFSET_LANDSCAPE = "keyboard__bottom_offset_landscape"
|
||||
const val FONT_SIZE_MULTIPLIER_PORTRAIT = "keyboard__font_size_multiplier_portrait"
|
||||
const val FONT_SIZE_MULTIPLIER_LANDSCAPE = "keyboard__font_size_multiplier_landscape"
|
||||
const val HEIGHT_FACTOR = "keyboard__height_factor"
|
||||
const val HEIGHT_FACTOR_CUSTOM = "keyboard__height_factor_custom"
|
||||
const val HINTED_NUMBER_ROW_MODE = "keyboard__hinted_number_row_mode"
|
||||
const val HINTED_SYMBOLS_MODE = "keyboard__hinted_symbols_mode"
|
||||
const val KEY_SPACING_HORIZONTAL = "keyboard__key_spacing_horizontal"
|
||||
const val KEY_SPACING_VERTICAL = "keyboard__key_spacing_vertical"
|
||||
const val LANDSCAPE_INPUT_UI_MODE = "keyboard__landscape_input_ui_mode"
|
||||
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
|
||||
const val NUMBER_ROW = "keyboard__number_row"
|
||||
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
|
||||
const val ONE_HANDED_MODE_SCALE_FACTOR = "keyboard__one_handed_mode_scale_factor"
|
||||
const val POPUP_ENABLED = "keyboard__popup_enabled"
|
||||
const val SOUND_ENABLED = "keyboard__sound_enabled"
|
||||
const val SOUND_VOLUME = "keyboard__sound_volume"
|
||||
const val SPACE_BAR_SWITCHES_TO_CHARACTERS = "keyboard__space_bar_switches_to_characters"
|
||||
const val UTILITY_KEY_ACTION = "keyboard__utility_key_action"
|
||||
const val UTILITY_KEY_ENABLED = "keyboard__utility_key_enabled"
|
||||
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
|
||||
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
|
||||
}
|
||||
|
||||
var bottomOffsetPortrait: Int = 0
|
||||
@@ -388,6 +407,9 @@ class PrefHelper(
|
||||
var soundVolume: Int = 0
|
||||
get() = prefHelper.getPref(SOUND_VOLUME, -1)
|
||||
private set
|
||||
var spaceBarSwitchesToCharacters: Boolean
|
||||
get() = prefHelper.getPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, true)
|
||||
set(v) = prefHelper.setPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, v)
|
||||
var utilityKeyAction: UtilityKeyAction
|
||||
get() = UtilityKeyAction.fromString(prefHelper.getPref(UTILITY_KEY_ACTION, UtilityKeyAction.DYNAMIC_SWITCH_LANGUAGE_EMOJIS.toString()))
|
||||
set(v) = prefHelper.setPref(UTILITY_KEY_ACTION, v)
|
||||
@@ -439,20 +461,28 @@ class PrefHelper(
|
||||
class Suggestion(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val BLOCK_POSSIBLY_OFFENSIVE = "suggestion__block_possibly_offensive"
|
||||
const val CLIPBOARD_CONTENT_ENABLED = "suggestion__clipboard_content_enabled"
|
||||
const val CLIPBOARD_CONTENT_TIMEOUT = "suggestion__clipboard_content_timeout"
|
||||
const val DISPLAY_MODE = "suggestion__display_mode"
|
||||
const val ENABLED = "suggestion__enabled"
|
||||
const val SUGGEST_CLIPBOARD_CONTENT = "suggestion__suggest_clipboard_content"
|
||||
const val USE_PREV_WORDS = "suggestion__use_prev_words"
|
||||
}
|
||||
|
||||
var blockPossiblyOffensive: Boolean
|
||||
get() = prefHelper.getPref(BLOCK_POSSIBLY_OFFENSIVE, true)
|
||||
set(v) = prefHelper.setPref(BLOCK_POSSIBLY_OFFENSIVE, v)
|
||||
var clipboardContentEnabled: Boolean
|
||||
get() = prefHelper.getPref(CLIPBOARD_CONTENT_ENABLED, false)
|
||||
set(v) = prefHelper.setPref(CLIPBOARD_CONTENT_ENABLED, v)
|
||||
var clipboardContentTimeout: Int
|
||||
get() = prefHelper.getPref(CLIPBOARD_CONTENT_TIMEOUT, 30)
|
||||
set(v) = prefHelper.setPref(CLIPBOARD_CONTENT_TIMEOUT, v)
|
||||
var displayMode: CandidateView.DisplayMode
|
||||
get() = CandidateView.DisplayMode.fromString(prefHelper.getPref(DISPLAY_MODE, CandidateView.DisplayMode.DYNAMIC_SCROLLABLE.toString()))
|
||||
set(v) = prefHelper.setPref(DISPLAY_MODE, v)
|
||||
var enabled: Boolean
|
||||
get() = prefHelper.getPref(ENABLED, true)
|
||||
set(v) = prefHelper.setPref(ENABLED, v)
|
||||
var suggestClipboardContent: Boolean
|
||||
get() = prefHelper.getPref(SUGGEST_CLIPBOARD_CONTENT, false)
|
||||
set(v) = prefHelper.setPref(SUGGEST_CLIPBOARD_CONTENT, v)
|
||||
var usePrevWords: Boolean
|
||||
get() = prefHelper.getPref(USE_PREV_WORDS, true)
|
||||
set(v) = prefHelper.setPref(USE_PREV_WORDS, v)
|
||||
@@ -494,4 +524,52 @@ class PrefHelper(
|
||||
get() = prefHelper.getPref(SUNSET_TIME, TimeUtil.encode(18, 0))
|
||||
set(v) = prefHelper.setPref(SUNSET_TIME, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for clipboard preferences
|
||||
*/
|
||||
class Clipboard(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ENABLE_INTERNAL = "clipboard__enable_internal"
|
||||
const val SYNC_TO_SYSTEM = "clipboard__sync_to_system"
|
||||
const val SYNC_TO_FLORIS = "clipboard__sync_to_floris"
|
||||
const val ENABLE_HISTORY = "clipboard__enable_history"
|
||||
const val CLEAN_UP_OLD = "clipboard__clean_up_old"
|
||||
const val LIMIT_HISTORY_SIZE = "clipboard__limit_history_size"
|
||||
const val CLEAN_UP_AFTER = "clipboard__clean_up_after"
|
||||
const val MAX_HISTORY_SIZE = "clipboard__max_history_size"
|
||||
}
|
||||
|
||||
var enableInternal: Boolean
|
||||
get() = prefHelper.getPref(ENABLE_INTERNAL, false)
|
||||
set(v) = prefHelper.setPref(ENABLE_INTERNAL, v)
|
||||
|
||||
var syncToSystem: Boolean
|
||||
get() = prefHelper.getPref(SYNC_TO_SYSTEM, false)
|
||||
set(v) = prefHelper.setPref(SYNC_TO_SYSTEM, v)
|
||||
|
||||
var syncToFloris: Boolean
|
||||
get() = prefHelper.getPref(SYNC_TO_FLORIS, true)
|
||||
set(v) = prefHelper.setPref(SYNC_TO_FLORIS, v)
|
||||
|
||||
var enableHistory: Boolean
|
||||
get() = prefHelper.getPref(ENABLE_HISTORY, false)
|
||||
set(v) = prefHelper.setPref(ENABLE_HISTORY, v)
|
||||
|
||||
var cleanUpOld: Boolean
|
||||
get() = prefHelper.getPref(CLEAN_UP_OLD, false)
|
||||
set(v) = prefHelper.setPref(CLEAN_UP_OLD, v)
|
||||
|
||||
var limitHistorySize: Boolean
|
||||
get() = prefHelper.getPref(LIMIT_HISTORY_SIZE, true)
|
||||
set(v) = prefHelper.setPref(LIMIT_HISTORY_SIZE, v)
|
||||
|
||||
var cleanUpAfter: Int
|
||||
get() = prefHelper.getPref(CLEAN_UP_AFTER, 20)
|
||||
set(v) = prefHelper.setPref(CLEAN_UP_AFTER, v)
|
||||
|
||||
var maxHistorySize: Int
|
||||
get() = prefHelper.getPref(MAX_HISTORY_SIZE, 20)
|
||||
set(v) = prefHelper.setPref(MAX_HISTORY_SIZE, 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
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user