Compare commits
86 Commits
v0.3.12
...
v0.3.13-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9e2563739 | ||
|
|
87bb098445 | ||
|
|
da1944bedf | ||
|
|
d4a92e0d46 | ||
|
|
0fa6c1f235 | ||
|
|
260b1ba5ca | ||
|
|
f0799a6a0e | ||
|
|
155238946a | ||
|
|
45f91cf40c | ||
|
|
94f5b56b6a | ||
|
|
46db467073 | ||
|
|
17dde536d9 | ||
|
|
be67bf4b84 | ||
|
|
8f142548fe | ||
|
|
a68f439f39 | ||
|
|
7a0892bb36 | ||
|
|
8457390156 | ||
|
|
72be3898c1 | ||
|
|
d35bf5af63 | ||
|
|
04d3af6484 | ||
|
|
26920e4a98 | ||
|
|
7419966b51 | ||
|
|
58b832c6c3 | ||
|
|
99f2ec1879 | ||
|
|
4249f9ef86 | ||
|
|
60107ae299 | ||
|
|
6a95a865fa | ||
|
|
9e32589af5 | ||
|
|
6133e225e1 | ||
|
|
348c143d92 | ||
|
|
ce00785ffe | ||
|
|
78cdce750d | ||
|
|
f3f95ae282 | ||
|
|
018885eb30 | ||
|
|
c6c8a76dd6 | ||
|
|
3cae8b7230 | ||
|
|
814c8de0c2 | ||
|
|
32fe175b48 | ||
|
|
b901f6de8d | ||
|
|
fe9ba3246c | ||
|
|
71a39f0fc1 | ||
|
|
f7556898e1 | ||
|
|
578539f5d0 | ||
|
|
7c28c7fbea | ||
|
|
88bcadff81 | ||
|
|
25e25dfbf0 | ||
|
|
ba3dc0178d | ||
|
|
91e7f424bb | ||
|
|
b89f791eb0 | ||
|
|
ad3a0425ab | ||
|
|
7cf52ecf3e | ||
|
|
b1ef18f4fd | ||
|
|
b74af5bbe9 | ||
|
|
b8aa4bbfc4 | ||
|
|
e024ac9272 | ||
|
|
c5fa027a8e | ||
|
|
b6ec2b25be | ||
|
|
a756b59c60 | ||
|
|
8687ce55ed | ||
|
|
1ac6985dd0 | ||
|
|
986b4a878f | ||
|
|
1ef38fe7f3 | ||
|
|
bcad0af35e | ||
|
|
b5b89fde4f | ||
|
|
be1fc710ed | ||
|
|
aa55fd3070 | ||
|
|
a132462466 | ||
|
|
df393ff607 | ||
|
|
88a6f436ef | ||
|
|
ee8f44d816 | ||
|
|
0308ec355f | ||
|
|
3ac14f8a2a | ||
|
|
2b087b76dc | ||
|
|
1715e5ddfa | ||
|
|
6cc17161a5 | ||
|
|
5d1c20617b | ||
|
|
d9efa48c9c | ||
|
|
dedd4cb7f0 | ||
|
|
42b147b656 | ||
|
|
47ce490d6c | ||
|
|
5563a1cadd | ||
|
|
7beb2e5ef6 | ||
|
|
f00da13cba | ||
|
|
bfed1747f7 | ||
|
|
abb4b104fa | ||
|
|
b69b1caa72 |
@@ -9,7 +9,7 @@ insert_final_newline = true
|
||||
max_line_length = 120
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.har,*.json}]
|
||||
[{*.har,*.json,*yml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.kt]
|
||||
|
||||
4
.github/workflows/android.yml
vendored
4
.github/workflows/android.yml
vendored
@@ -16,6 +16,8 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Setup CMake and Ninja
|
||||
uses: lukka/get-cmake@v3.20.1
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
@@ -25,7 +27,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean assemble
|
||||
run: ./gradlew clean assembleDebug
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-debug.apk
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,5 +41,8 @@ captures/
|
||||
*.jks
|
||||
crowdin.properties
|
||||
|
||||
# C++
|
||||
.cxx/
|
||||
|
||||
# AndroidX Room schema JSONs
|
||||
/app/schemas/
|
||||
|
||||
@@ -96,6 +96,7 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
|
||||
* [x] User dictionary manager (system and internal)
|
||||
|
||||
### Other useful features
|
||||
* [x] Support for Android 11+ inline autofill API
|
||||
* [x] One-handed mode
|
||||
* [x] Clipboard/cursor tools
|
||||
* [x] Clipboard manager/history
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
plugins {
|
||||
id("com.android.application") version "4.2.0"
|
||||
id("com.android.application") version "4.2.1"
|
||||
kotlin("android") version "1.5.0"
|
||||
kotlin("kapt") version "1.5.0"
|
||||
kotlin("plugin.serialization") version "1.5.0"
|
||||
@@ -24,8 +24,8 @@ android {
|
||||
applicationId = "dev.patrickgold.florisboard"
|
||||
minSdkVersion(23)
|
||||
targetSdkVersion(30)
|
||||
versionCode(43)
|
||||
versionName("0.3.12")
|
||||
versionCode(45)
|
||||
versionName("0.3.13")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -38,17 +38,33 @@ android {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags("-std=c++17", "-fexceptions", "-frtti")
|
||||
arguments("-DANDROID_STL=c++_static")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("debug").configure {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
|
||||
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_debug")
|
||||
resValue("mipmap", "floris_app_icon_round", "@mipmap/ic_app_icon_debug_round")
|
||||
resValue("string", "floris_app_name", "FlorisBoard Debug")
|
||||
@@ -57,7 +73,7 @@ android {
|
||||
create("beta") // Needed because by default the "beta" BuildType does not exist
|
||||
named("beta").configure {
|
||||
applicationIdSuffix = ".beta"
|
||||
versionNameSuffix = "-beta06"
|
||||
versionNameSuffix = "-beta02"
|
||||
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
|
||||
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
|
||||
@@ -89,6 +105,7 @@ android {
|
||||
dependencies {
|
||||
implementation("androidx.activity", "activity-ktx", "1.2.1")
|
||||
implementation("androidx.appcompat", "appcompat", "1.2.0")
|
||||
implementation("androidx.autofill", "autofill", "1.1.0")
|
||||
implementation("androidx.core", "core-ktx", "1.3.2")
|
||||
implementation("androidx.fragment", "fragment-ktx", "1.3.0")
|
||||
implementation("androidx.preference", "preference-ktx", "1.1.1")
|
||||
|
||||
@@ -34,13 +34,10 @@
|
||||
android:name="dev.patrickgold.florisboard.ime.core.FlorisBoard"
|
||||
android:label="@string/floris_app_name"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD">
|
||||
<meta-data
|
||||
android:name="android.view.im"
|
||||
android:resource="@xml/method"/>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
|
||||
</service>
|
||||
|
||||
<!-- Settings Activity -->
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"package": "dev.patrickgold.florisboard",
|
||||
"composers": [
|
||||
{ "$": "appender" },
|
||||
{ "$": "hangul-unicode" }
|
||||
],
|
||||
"currencySets": [
|
||||
{
|
||||
"name": "azerbaijani_manat",
|
||||
@@ -246,6 +250,7 @@
|
||||
{
|
||||
"id": 101,
|
||||
"languageTag": "en-US",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -254,6 +259,7 @@
|
||||
{
|
||||
"id": 102,
|
||||
"languageTag": "en-UK",
|
||||
"composer": "appender",
|
||||
"currencySet": "pound",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -262,6 +268,7 @@
|
||||
{
|
||||
"id": 103,
|
||||
"languageTag": "en-CA",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -270,6 +277,7 @@
|
||||
{
|
||||
"id": 104,
|
||||
"languageTag": "en-AU",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -278,6 +286,7 @@
|
||||
{
|
||||
"id": 201,
|
||||
"languageTag": "de-DE",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
@@ -286,6 +295,7 @@
|
||||
{
|
||||
"id": 202,
|
||||
"languageTag": "de-AT",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
@@ -294,14 +304,27 @@
|
||||
{
|
||||
"id": 203,
|
||||
"languageTag": "de-CH",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swiss_german"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 204,
|
||||
"languageTag": "de-DE-neobone",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "neo2",
|
||||
"symbols": "neo2",
|
||||
"numericRow": "neo2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 301,
|
||||
"languageTag": "fr-FR",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "azerty"
|
||||
@@ -310,6 +333,7 @@
|
||||
{
|
||||
"id": 302,
|
||||
"languageTag": "fr-CA",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "canadian_french"
|
||||
@@ -318,6 +342,7 @@
|
||||
{
|
||||
"id": 303,
|
||||
"languageTag": "fr-CH",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swiss_french"
|
||||
@@ -326,6 +351,7 @@
|
||||
{
|
||||
"id": 401,
|
||||
"languageTag": "it-IT",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -334,6 +360,7 @@
|
||||
{
|
||||
"id": 402,
|
||||
"languageTag": "it-CH",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swiss_italian"
|
||||
@@ -342,6 +369,7 @@
|
||||
{
|
||||
"id": 501,
|
||||
"languageTag": "es-ES",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "spanish"
|
||||
@@ -350,6 +378,7 @@
|
||||
{
|
||||
"id": 502,
|
||||
"languageTag": "es-US",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "spanish"
|
||||
@@ -358,6 +387,7 @@
|
||||
{
|
||||
"id": 503,
|
||||
"languageTag": "es-419",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "spanish"
|
||||
@@ -366,6 +396,7 @@
|
||||
{
|
||||
"id": 601,
|
||||
"languageTag": "pt-PT",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -374,6 +405,7 @@
|
||||
{
|
||||
"id": 602,
|
||||
"languageTag": "pt-BR",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -382,6 +414,7 @@
|
||||
{
|
||||
"id": 701,
|
||||
"languageTag": "nb-NO",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "norwegian"
|
||||
@@ -390,6 +423,7 @@
|
||||
{
|
||||
"id": 702,
|
||||
"languageTag": "nn-NO",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "norwegian"
|
||||
@@ -398,6 +432,7 @@
|
||||
{
|
||||
"id": 711,
|
||||
"languageTag": "sv-SE",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "swedish_finnish"
|
||||
@@ -406,6 +441,7 @@
|
||||
{
|
||||
"id": 721,
|
||||
"languageTag": "fi-FI",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "swedish_finnish"
|
||||
@@ -414,6 +450,7 @@
|
||||
{
|
||||
"id": 731,
|
||||
"languageTag": "da-DK",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "danish"
|
||||
@@ -422,6 +459,7 @@
|
||||
{
|
||||
"id": 741,
|
||||
"languageTag": "is-IS",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "icelandic"
|
||||
@@ -430,6 +468,7 @@
|
||||
{
|
||||
"id": 751,
|
||||
"languageTag": "fo",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "faroese"
|
||||
@@ -438,6 +477,7 @@
|
||||
{
|
||||
"id": 801,
|
||||
"languageTag": "fa-FA",
|
||||
"composer": "appender",
|
||||
"currencySet": "iranian_rial",
|
||||
"preferred": {
|
||||
"characters": "persian",
|
||||
@@ -449,6 +489,7 @@
|
||||
{
|
||||
"id": 901,
|
||||
"languageTag": "ar",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "arabic",
|
||||
@@ -460,6 +501,7 @@
|
||||
{
|
||||
"id": 1001,
|
||||
"languageTag": "hu",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "hungarian"
|
||||
@@ -468,6 +510,7 @@
|
||||
{
|
||||
"id": 1101,
|
||||
"languageTag": "eo",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "esperanto"
|
||||
@@ -476,6 +519,7 @@
|
||||
{
|
||||
"id": 1201,
|
||||
"languageTag": "hr",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
@@ -484,6 +528,7 @@
|
||||
{
|
||||
"id": 1301,
|
||||
"languageTag": "ru",
|
||||
"composer": "appender",
|
||||
"currencySet": "russian_ruble",
|
||||
"preferred": {
|
||||
"characters": "jcuken_russian"
|
||||
@@ -492,6 +537,7 @@
|
||||
{
|
||||
"id": 1351,
|
||||
"languageTag": "uk",
|
||||
"composer": "appender",
|
||||
"currencySet": "ukrainian_hryvnia",
|
||||
"preferred": {
|
||||
"characters": "jcuken_ukrainian"
|
||||
@@ -500,6 +546,7 @@
|
||||
{
|
||||
"id": 1401,
|
||||
"languageTag": "el",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "greek"
|
||||
@@ -508,6 +555,7 @@
|
||||
{
|
||||
"id": 1501,
|
||||
"languageTag": "ro",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -516,6 +564,7 @@
|
||||
{
|
||||
"id": 1601,
|
||||
"languageTag": "pl",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -524,6 +573,7 @@
|
||||
{
|
||||
"id": 1701,
|
||||
"languageTag": "bg-bg",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "bulgarian_phonetic"
|
||||
@@ -532,6 +582,7 @@
|
||||
{
|
||||
"id": 1801,
|
||||
"languageTag": "tr",
|
||||
"composer": "appender",
|
||||
"currencySet": "turkish_lira",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -540,6 +591,7 @@
|
||||
{
|
||||
"id": 1901,
|
||||
"languageTag": "iw-IL",
|
||||
"composer": "appender",
|
||||
"currencySet": "israeli_new_shekel",
|
||||
"preferred": {
|
||||
"characters": "hebrew"
|
||||
@@ -548,6 +600,7 @@
|
||||
{
|
||||
"id": 2001,
|
||||
"languageTag": "ckb",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "kurdish",
|
||||
@@ -559,6 +612,7 @@
|
||||
{
|
||||
"id": 2101,
|
||||
"languageTag": "sr-RS",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "serbian_cyrillic"
|
||||
@@ -567,6 +621,7 @@
|
||||
{
|
||||
"id": 2201,
|
||||
"languageTag": "lv-LV",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwerty"
|
||||
@@ -575,6 +630,7 @@
|
||||
{
|
||||
"id": 2301,
|
||||
"languageTag": "ku",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "kurdish_kurmanci"
|
||||
@@ -583,6 +639,7 @@
|
||||
{
|
||||
"id": 2501,
|
||||
"languageTag": "ca",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "catalan"
|
||||
@@ -591,6 +648,7 @@
|
||||
{
|
||||
"id": 2601,
|
||||
"languageTag": "IPA-IPA",
|
||||
"composer": "appender",
|
||||
"currencySet": "dollar",
|
||||
"preferred": {
|
||||
"characters": "ipa",
|
||||
@@ -601,6 +659,7 @@
|
||||
{
|
||||
"id": 2701,
|
||||
"languageTag": "sk",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
@@ -609,10 +668,20 @@
|
||||
{
|
||||
"id": 2801,
|
||||
"languageTag": "cs",
|
||||
"composer": "appender",
|
||||
"currencySet": "euro",
|
||||
"preferred": {
|
||||
"characters": "qwertz"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2900,
|
||||
"languageTag": "ko",
|
||||
"composer": "hangul-unicode",
|
||||
"currencySet": "south_korean_won",
|
||||
"preferred": {
|
||||
"characters": "korean"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
61
app/src/main/assets/ime/text/characters/bone.json
Normal file
61
app/src/main/assets/ime/text/characters/bone.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "bone",
|
||||
"label": "Bone",
|
||||
"authors": [ "ostrya" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "neo2",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 106, "label": "j" },
|
||||
{ "$": "auto_text_key", "code": 100, "label": "d" },
|
||||
{ "$": "auto_text_key", "code": 117, "label": "u" },
|
||||
{ "$": "auto_text_key", "code": 97, "label": "a" },
|
||||
{ "$": "auto_text_key", "code": 120, "label": "x" },
|
||||
{ "$": "auto_text_key", "code": 112, "label": "p" },
|
||||
{ "$": "auto_text_key", "code": 104, "label": "h" },
|
||||
{ "$": "auto_text_key", "code": 108, "label": "l" },
|
||||
{ "$": "auto_text_key", "code": 109, "label": "m" },
|
||||
{ "$": "auto_text_key", "code": 119, "label": "w" },
|
||||
{ "$": "case_selector",
|
||||
"lower": {
|
||||
"code": 223, "label": "ß", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 180, "label": "´" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"upper": {
|
||||
"code": 7838, "label": "ẞ", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 180, "label": "´" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 99, "label": "c" },
|
||||
{ "$": "auto_text_key", "code": 116, "label": "t" },
|
||||
{ "$": "auto_text_key", "code": 105, "label": "i" },
|
||||
{ "$": "auto_text_key", "code": 101, "label": "e" },
|
||||
{ "$": "auto_text_key", "code": 111, "label": "o" },
|
||||
{ "$": "auto_text_key", "code": 98, "label": "b" },
|
||||
{ "$": "auto_text_key", "code": 110, "label": "n" },
|
||||
{ "$": "auto_text_key", "code": 114, "label": "r" },
|
||||
{ "$": "auto_text_key", "code": 115, "label": "s" },
|
||||
{ "$": "auto_text_key", "code": 103, "label": "g" },
|
||||
{ "$": "auto_text_key", "code": 113, "label": "q" }
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 102, "label": "f" },
|
||||
{ "$": "auto_text_key", "code": 118, "label": "v" },
|
||||
{ "$": "auto_text_key", "code": 252, "label": "ü" },
|
||||
{ "$": "auto_text_key", "code": 228, "label": "ä" },
|
||||
{ "$": "auto_text_key", "code": 246, "label": "ö" },
|
||||
{ "$": "auto_text_key", "code": 121, "label": "y" },
|
||||
{ "$": "auto_text_key", "code": 122, "label": "z" },
|
||||
{ "$": "auto_text_key", "code": 107, "label": "k" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "de-DE-neobone",
|
||||
"authors": [ "ostrya" ],
|
||||
"mapping": {
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".ch" },
|
||||
{ "code": -255, "label": ".de" },
|
||||
{ "code": -255, "label": ".at" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
]
|
||||
},
|
||||
"ι": {
|
||||
"main": { "$": "auto_text_key", "code": 943, "label": "ί" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 912, "label": "ΐ" },
|
||||
{ "$": "auto_text_key", "code": 970, "label": "ϊ" },
|
||||
{ "$": "auto_text_key", "code": 943, "label": "ί" }
|
||||
{ "$": "auto_text_key", "code": 970, "label": "ϊ" }
|
||||
]
|
||||
},
|
||||
"ο": {
|
||||
@@ -32,10 +32,10 @@
|
||||
]
|
||||
},
|
||||
"υ": {
|
||||
"main": { "$": "auto_text_key", "code": 973, "label": "ύ" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 944, "label": "ΰ" },
|
||||
{ "$": "auto_text_key", "code": 971, "label": "ϋ" },
|
||||
{ "$": "auto_text_key", "code": 973, "label": "ύ" }
|
||||
{ "$": "auto_text_key", "code": 971, "label": "ϋ" }
|
||||
]
|
||||
},
|
||||
"ω": {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "ko",
|
||||
"authors": [ "patrickgold", "Hayleia" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"ㅂ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12611, "label": "ㅃ" }
|
||||
]
|
||||
},
|
||||
"ㅈ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12617, "label": "ㅉ" }
|
||||
]
|
||||
},
|
||||
"ㄷ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12600, "label": "ㄸ" }
|
||||
]
|
||||
},
|
||||
"ㄱ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12594, "label": "ㄲ" }
|
||||
]
|
||||
},
|
||||
"ㅅ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12614, "label": "ㅆ" }
|
||||
]
|
||||
},
|
||||
"ㅐ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12626, "label": "ㅒ" }
|
||||
]
|
||||
},
|
||||
"ㅔ": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 12630, "label": "ㅖ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,27 +28,27 @@
|
||||
{ "code": 1508, "label": "פ" }
|
||||
],
|
||||
[
|
||||
{ "code": 1513, "label": "ף" },
|
||||
{ "code": 1491, "label": "ך" },
|
||||
{ "code": 1490, "label": "ל" },
|
||||
{ "code": 1499, "label": "ח" },
|
||||
{ "code": 1506, "label": "י" },
|
||||
{ "code": 1497, "label": "ע" },
|
||||
{ "code": 1495, "label": "כ" },
|
||||
{ "code": 1500, "label": "ג" },
|
||||
{ "code": 1498, "label": "ד" },
|
||||
{ "code": 1507, "label": "ש" }
|
||||
{ "code": 1513, "label": "ש" },
|
||||
{ "code": 1491, "label": "ד" },
|
||||
{ "code": 1490, "label": "ג" },
|
||||
{ "code": 1499, "label": "כ" },
|
||||
{ "code": 1506, "label": "ע" },
|
||||
{ "code": 1497, "label": "י" },
|
||||
{ "code": 1495, "label": "ח" },
|
||||
{ "code": 1500, "label": "ל" },
|
||||
{ "code": 1498, "label": "ך" },
|
||||
{ "code": 1507, "label": "ף" }
|
||||
],
|
||||
[
|
||||
{ "code": 1494, "label": "ץ" },
|
||||
{ "code": 1505, "label": "ת" },
|
||||
{ "code": 1489, "label": "צ" },
|
||||
{ "code": 1492, "label": "מ" },
|
||||
{ "code": 1494, "label": "ז" },
|
||||
{ "code": 1505, "label": "ס" },
|
||||
{ "code": 1489, "label": "ב" },
|
||||
{ "code": 1492, "label": "ה" },
|
||||
{ "code": 1504, "label": "נ" },
|
||||
{ "code": 1502, "label": "ה" },
|
||||
{ "code": 1510, "label": "ב" },
|
||||
{ "code": 1514, "label": "ס" },
|
||||
{ "code": 1509, "label": "ז" }
|
||||
{ "code": 1502, "label": "מ" },
|
||||
{ "code": 1510, "label": "צ" },
|
||||
{ "code": 1514, "label": "ת" },
|
||||
{ "code": 1509, "label": "ץ" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
62
app/src/main/assets/ime/text/characters/korean.json
Normal file
62
app/src/main/assets/ime/text/characters/korean.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "korean",
|
||||
"label": "South Korean standard",
|
||||
"authors": [ "patrickgold", "Hayleia" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12610, "label": "ㅂ" },
|
||||
"upper": { "code": 12611, "label": "ㅃ" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12616, "label": "ㅈ" },
|
||||
"upper": { "code": 12617, "label": "ㅉ" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12599, "label": "ㄷ" },
|
||||
"upper": { "code": 12600, "label": "ㄸ" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12593, "label": "ㄱ" },
|
||||
"upper": { "code": 12594, "label": "ㄲ" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12613, "label": "ㅅ" },
|
||||
"upper": { "code": 12614, "label": "ㅆ" }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 12635, "label": "ㅛ"},
|
||||
{ "$": "auto_text_key", "code": 12629, "label": "ㅕ"},
|
||||
{ "$": "auto_text_key", "code": 12625, "label": "ㅑ"},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12624, "label": "ㅐ" },
|
||||
"upper": { "code": 12626, "label": "ㅒ" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 12628, "label": "ㅔ" },
|
||||
"upper": { "code": 12630, "label": "ㅖ" }
|
||||
}
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 12609, "label": "ㅁ"},
|
||||
{ "$": "auto_text_key", "code": 12596, "label": "ㄴ"},
|
||||
{ "$": "auto_text_key", "code": 12615, "label": "ㅇ"},
|
||||
{ "$": "auto_text_key", "code": 12601, "label": "ㄹ"},
|
||||
{ "$": "auto_text_key", "code": 12622, "label": "ㅎ"},
|
||||
{ "$": "auto_text_key", "code": 12631, "label": "ㅗ"},
|
||||
{ "$": "auto_text_key", "code": 12627, "label": "ㅓ"},
|
||||
{ "$": "auto_text_key", "code": 12623, "label": "ㅏ"},
|
||||
{ "$": "auto_text_key", "code": 12643, "label": "ㅣ"}
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 12619, "label": "ㅋ"},
|
||||
{ "$": "auto_text_key", "code": 12620, "label": "ㅌ"},
|
||||
{ "$": "auto_text_key", "code": 12618, "label": "ㅊ"},
|
||||
{ "$": "auto_text_key", "code": 12621, "label": "ㅍ"},
|
||||
{ "$": "auto_text_key", "code": 12640, "label": "ㅠ"},
|
||||
{ "$": "auto_text_key", "code": 12636, "label": "ㅜ"},
|
||||
{ "$": "auto_text_key", "code": 12641, "label": "ㅡ"}
|
||||
]
|
||||
]
|
||||
}
|
||||
53
app/src/main/assets/ime/text/characters/mod/neo2.json
Normal file
53
app/src/main/assets/ime/text/characters/mod/neo2.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "neo2",
|
||||
"label": "Neo2",
|
||||
"authors": [ "ostrya" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -1, "label": "shift", "type": "modifier" },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "$": "variation_selector",
|
||||
"default": { "code": 44, "label": ",", "groupId": 1,
|
||||
"popup": {
|
||||
"main": { "code": 34, "label": "\"" },
|
||||
"relevant": [
|
||||
{ "code": 8211, "label": "–" }
|
||||
]
|
||||
} },
|
||||
"email": { "code": 64, "label": "@", "groupId": 1,
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 44, "label": "," }
|
||||
]
|
||||
} },
|
||||
"uri": { "code": 47, "label": "/", "groupId": 1,
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 44, "label": "," }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "variation_selector",
|
||||
"default": { "code": 46, "label": ".", "groupId": 2,
|
||||
"popup": {
|
||||
"relevant": [
|
||||
{ "code": 183, "label": "·" },
|
||||
{ "code": 39, "label": "'" }
|
||||
]
|
||||
} },
|
||||
"email": { "code": 46, "label": ".", "groupId": 2 },
|
||||
"uri": { "code": 46, "label": ".", "groupId": 2 }
|
||||
},
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
61
app/src/main/assets/ime/text/characters/neo2.json
Normal file
61
app/src/main/assets/ime/text/characters/neo2.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "neo2",
|
||||
"label": "Neo2",
|
||||
"authors": [ "ostrya" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "neo2",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 120, "label": "x" },
|
||||
{ "$": "auto_text_key", "code": 118, "label": "v" },
|
||||
{ "$": "auto_text_key", "code": 108, "label": "l" },
|
||||
{ "$": "auto_text_key", "code": 99, "label": "c" },
|
||||
{ "$": "auto_text_key", "code": 119, "label": "w" },
|
||||
{ "$": "auto_text_key", "code": 107, "label": "k" },
|
||||
{ "$": "auto_text_key", "code": 104, "label": "h" },
|
||||
{ "$": "auto_text_key", "code": 103, "label": "g" },
|
||||
{ "$": "auto_text_key", "code": 102, "label": "f" },
|
||||
{ "$": "auto_text_key", "code": 113, "label": "q" },
|
||||
{ "$": "case_selector",
|
||||
"lower": {
|
||||
"code": 223, "label": "ß", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 180, "label": "´" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"upper": {
|
||||
"code": 7838, "label": "ẞ", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 180, "label": "´" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 117, "label": "u" },
|
||||
{ "$": "auto_text_key", "code": 105, "label": "i" },
|
||||
{ "$": "auto_text_key", "code": 97, "label": "a" },
|
||||
{ "$": "auto_text_key", "code": 101, "label": "e" },
|
||||
{ "$": "auto_text_key", "code": 111, "label": "o" },
|
||||
{ "$": "auto_text_key", "code": 115, "label": "s" },
|
||||
{ "$": "auto_text_key", "code": 110, "label": "n" },
|
||||
{ "$": "auto_text_key", "code": 114, "label": "r" },
|
||||
{ "$": "auto_text_key", "code": 116, "label": "t" },
|
||||
{ "$": "auto_text_key", "code": 100, "label": "d" },
|
||||
{ "$": "auto_text_key", "code": 121, "label": "y" }
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 252, "label": "ü" },
|
||||
{ "$": "auto_text_key", "code": 246, "label": "ö" },
|
||||
{ "$": "auto_text_key", "code": 228, "label": "ä" },
|
||||
{ "$": "auto_text_key", "code": 112, "label": "p" },
|
||||
{ "$": "auto_text_key", "code": 122, "label": "z" },
|
||||
{ "$": "auto_text_key", "code": 98, "label": "b" },
|
||||
{ "$": "auto_text_key", "code": 109, "label": "m" },
|
||||
{ "$": "auto_text_key", "code": 106, "label": "j" }
|
||||
]
|
||||
]
|
||||
}
|
||||
91
app/src/main/assets/ime/text/numeric/row/bengali.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/bengali.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "bengali",
|
||||
"label": "Bengali",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 2535, "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": 2536, "label": "২", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2537, "label": "৩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2538, "label": "৪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2539, "label": "৫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2540, "label": "৬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2541, "label": "৭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2542, "label": "৮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2543, "label": "৯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2534, "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/devanagari.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/devanagari.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "devanagari",
|
||||
"label": "Devanagari",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 2047, "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": 2048, "label": "२", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2049, "label": "३", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2050, "label": "४", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2051, "label": "५", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2052, "label": "६", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2053, "label": "७", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2054, "label": "८", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2055, "label": "९", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2046, "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/gujarati.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/gujarati.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "gujarati",
|
||||
"label": "Gujarati",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 2791, "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": 2792, "label": "૨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2793, "label": "૩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2794, "label": "૪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2795, "label": "૫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2796, "label": "૬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2797, "label": "૭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2798, "label": "૮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2799, "label": "૯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2790, "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/gurmukhi.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/gurmukhi.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "gurmukhi",
|
||||
"label": "Gurmukhi",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 2663, "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": 2664, "label": "੨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2665, "label": "੩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2666, "label": "੪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2667, "label": "੫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2668, "label": "੬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2669, "label": "੭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2670, "label": "੮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2671, "label": "੯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2662, "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/kannada.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/kannada.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "kannada",
|
||||
"label": "Kannada",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 3303, "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": 3304, "label": "೨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3305, "label": "೩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3306, "label": "೪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3307, "label": "೫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3308, "label": "೬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3309, "label": "೭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3310, "label": "೮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3311, "label": "೯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3302, "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/malayalam.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/malayalam.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "malayalam",
|
||||
"label": "Malayalam",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 3431, "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": 3432, "label": "൨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3433, "label": "൩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3434, "label": "൪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3435, "label": "൫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3436, "label": "൬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3437, "label": "൭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3438, "label": "൮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3439, "label": "൯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3430, "label": "൦", "type": "numeric", "popup": {
|
||||
"main": { "code": 48, "label": "0" },
|
||||
"relevant": [
|
||||
{ "code": 8319, "label": "ⁿ" },
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8304, "label": "⁰" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
80
app/src/main/assets/ime/text/numeric/row/neo2.json
Normal file
80
app/src/main/assets/ime/text/numeric/row/neo2.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "neo2",
|
||||
"label": "Neo2",
|
||||
"authors": [ "ostrya" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 49, "label": "1", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 176, "label": "°" },
|
||||
{ "code": 185, "label": "¹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 50, "label": "2", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 167, "label": "§" },
|
||||
{ "code": 178, "label": "²" }
|
||||
]
|
||||
} },
|
||||
{ "code": 51, "label": "3", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 8467, "label": "ℓ" },
|
||||
{ "code": 179, "label": "³" }
|
||||
]
|
||||
} },
|
||||
{ "code": 52, "label": "4", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 187, "label": "»" },
|
||||
{ "code": 8250, "label": "›" }
|
||||
]
|
||||
} },
|
||||
{ "code": 53, "label": "5", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 171, "label": "«" },
|
||||
{ "code": 8249, "label": "‹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 54, "label": "6", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 162, "label": "¢" }
|
||||
]
|
||||
} },
|
||||
{ "code": 55, "label": "7", "type": "numeric", "popup": {
|
||||
"main": { "code": -801, "label": "currency_slot_1" },
|
||||
"relevant": [
|
||||
{ "code": -802, "label": "currency_slot_2" },
|
||||
{ "code": -803, "label": "currency_slot_3" },
|
||||
{ "code": -804, "label": "currency_slot_4" },
|
||||
{ "code": -805, "label": "currency_slot_5" },
|
||||
{ "code": -806, "label": "currency_slot_6" }
|
||||
]
|
||||
} },
|
||||
{ "code": 56, "label": "8", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 8222, "label": "„" },
|
||||
{ "code": 8218, "label": "‚" }
|
||||
]
|
||||
} },
|
||||
{ "code": 57, "label": "9", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 8220, "label": "“" },
|
||||
{ "code": 8216, "label": "‘" }
|
||||
]
|
||||
} },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 8221, "label": "”" },
|
||||
{ "code": 8217, "label": "’" }
|
||||
]
|
||||
} },
|
||||
{ "code": 45, "label": "-", "type": "numeric", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 8212, "label": "—" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
91
app/src/main/assets/ime/text/numeric/row/oriya.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/oriya.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "oriya",
|
||||
"label": "Odia",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 2919, "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": 2920, "label": "୨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2921, "label": "୩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2922, "label": "୪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2923, "label": "୫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2924, "label": "୬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2925, "label": "୭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2926, "label": "୮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2927, "label": "୯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 2918, "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/tamil.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/tamil.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "tamil",
|
||||
"label": "Tamil",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 3047, "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": 3048, "label": "௨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3049, "label": "௩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3050, "label": "௪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3051, "label": "௫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3052, "label": "௬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3053, "label": "௭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3054, "label": "௮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3055, "label": "௯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3046, "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/telugu.json
Normal file
91
app/src/main/assets/ime/text/numeric/row/telugu.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"type": "numeric_row",
|
||||
"name": "telugu",
|
||||
"label": "Telugu",
|
||||
"authors": [ "yashpalgoyal1304" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 3175, "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": 3176, "label": "౨", "type": "numeric", "popup": {
|
||||
"main": { "code": 50, "label": "2" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3177, "label": "౩", "type": "numeric", "popup": {
|
||||
"main": { "code": 51, "label": "3" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3178, "label": "౪", "type": "numeric", "popup": {
|
||||
"main": { "code": 52, "label": "4" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" },
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3179, "label": "౫", "type": "numeric", "popup": {
|
||||
"main": { "code": 53, "label": "5" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8309, "label": "⁵" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3180, "label": "౬", "type": "numeric", "popup": {
|
||||
"main": { "code": 54, "label": "6" },
|
||||
"relevant": [
|
||||
{ "code": 8310, "label": "⁶" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3181, "label": "౭", "type": "numeric", "popup": {
|
||||
"main": { "code": 55, "label": "7" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" },
|
||||
{ "code": 8311, "label": "⁷" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3182, "label": "౮", "type": "numeric", "popup": {
|
||||
"main": { "code": 56, "label": "8" },
|
||||
"relevant": [
|
||||
{ "code": 8312, "label": "⁸" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3183, "label": "౯", "type": "numeric", "popup": {
|
||||
"main": { "code": 57, "label": "9" },
|
||||
"relevant": [
|
||||
{ "code": 8313, "label": "⁹" }
|
||||
]
|
||||
} },
|
||||
{ "code": 3174, "label": "౦", "type": "numeric", "popup": {
|
||||
"main": { "code": 48, "label": "0" },
|
||||
"relevant": [
|
||||
{ "code": 8319, "label": "ⁿ" },
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8304, "label": "⁰" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
22
app/src/main/assets/ime/text/symbols/mod/neo2.json
Normal file
22
app/src/main/assets/ime/text/symbols/mod/neo2.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"type": "symbols/mod",
|
||||
"name": "neo2",
|
||||
"label": "Neo2",
|
||||
"authors": [ "ostrya" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -203, "label": "view_symbols2", "type": "system_gui" },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -201, "label": "view_characters", "type": "system_gui" },
|
||||
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
46
app/src/main/assets/ime/text/symbols/neo2.json
Normal file
46
app/src/main/assets/ime/text/symbols/neo2.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"type": "symbols",
|
||||
"name": "neo2",
|
||||
"label": "Neo2",
|
||||
"authors": [ "ostrya" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "neo2",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 8230, "label": "…" },
|
||||
{ "code": 95, "label": "_" },
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 94, "label": "^" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 62, "label": ">" },
|
||||
{ "code": 61, "label": "=" },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 383, "label": "ſ" }
|
||||
],
|
||||
[
|
||||
{ "code": 92, "label": "\\" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 123, "label": "{" },
|
||||
{ "code": 125, "label": "}" },
|
||||
{ "code": 42, "label": "*" },
|
||||
{ "code": 63, "label": "?" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 64, "label": "@" }
|
||||
],
|
||||
[
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 124, "label": "|" },
|
||||
{ "code": 126, "label": "~" },
|
||||
{ "code": 96, "label": "`" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 59, "label": ";" }
|
||||
]
|
||||
]
|
||||
}
|
||||
38
app/src/main/cpp/CMakeLists.txt
Normal file
38
app/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
# For more information about using CMake with Android Studio, read the
|
||||
# documentation: https://d.android.com/studio/projects/add-native-code.html
|
||||
|
||||
cmake_minimum_required(VERSION 3.10.2)
|
||||
|
||||
project("florisboard")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_subdirectory(ime/nlp)
|
||||
|
||||
add_library(
|
||||
# Name
|
||||
florisboard-native
|
||||
|
||||
# Type
|
||||
SHARED
|
||||
|
||||
# Sources
|
||||
dev_patrickgold_florisboard_ime_nlp_SuggestionList.cpp
|
||||
)
|
||||
|
||||
find_library(
|
||||
# Save to var
|
||||
log-lib
|
||||
|
||||
# Original name
|
||||
log
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
# Destination
|
||||
florisboard-native
|
||||
|
||||
# Sources
|
||||
${log-lib}
|
||||
ime-nlp
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include "ime/nlp/suggestion_list.h"
|
||||
|
||||
#pragma ide diagnostic ignored "UnusedLocalVariable"
|
||||
|
||||
using namespace ime::nlp;
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeInitialize(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jint max_size) {
|
||||
auto *suggestionList = new SuggestionList(max_size);
|
||||
return reinterpret_cast<jlong>(suggestionList);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeDispose(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
suggestionList->clear();
|
||||
delete suggestionList;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeAdd(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jstring word,
|
||||
jint freq) {
|
||||
const char *cWord = env->GetStringUTFChars(word, nullptr);
|
||||
word_t stdWord = word_t(cWord);
|
||||
env->ReleaseStringUTFChars(word, cWord);
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->add(std::move(stdWord), freq);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeClear(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
suggestionList->clear();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeContains(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jstring element) {
|
||||
const char *cWord = env->GetStringUTFChars(element, nullptr);
|
||||
const word_t stdWord = word_t(cWord);
|
||||
env->ReleaseStringUTFChars(element, cWord);
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->containsWord(stdWord);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeGetOrNull(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jint index) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
auto weightedToken = suggestionList->get(index);
|
||||
if (weightedToken == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return env->NewStringUTF(weightedToken->data.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeSize(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->size();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeGetIsPrimaryTokenAutoInsert(
|
||||
JNIEnv *env, jobject thiz, jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->isPrimaryTokenAutoInsert;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeSetIsPrimaryTokenAutoInsert(
|
||||
JNIEnv *env, jobject thiz, jlong native_ptr, jboolean v) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
suggestionList->isPrimaryTokenAutoInsert = v;
|
||||
}
|
||||
0
app/src/main/cpp/ime/dummy
Normal file
0
app/src/main/cpp/ime/dummy
Normal file
13
app/src/main/cpp/ime/nlp/CMakeLists.txt
Normal file
13
app/src/main/cpp/ime/nlp/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
add_library(
|
||||
# Name
|
||||
ime-nlp
|
||||
|
||||
# Headers
|
||||
nlp.h
|
||||
token.h
|
||||
suggestion_list.h
|
||||
|
||||
# Sources
|
||||
token.cpp
|
||||
suggestion_list.cpp
|
||||
)
|
||||
32
app/src/main/cpp/ime/nlp/nlp.h
Normal file
32
app/src/main/cpp/ime/nlp/nlp.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_NLP_H
|
||||
#define FLORISBOARD_NLP_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
typedef std::string word_t;
|
||||
typedef uint16_t freq_t;
|
||||
|
||||
static const freq_t FREQ_VALUE_MASK = 0xFF;
|
||||
static const freq_t FREQ_POSSIBLY_OFFENSIVE = 0x01;
|
||||
|
||||
} // namespace ime::nlp
|
||||
|
||||
#endif // FLORISBOARD_NLP_H
|
||||
98
app/src/main/cpp/ime/nlp/suggestion_list.cpp
Normal file
98
app/src/main/cpp/ime/nlp/suggestion_list.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "suggestion_list.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace ime::nlp;
|
||||
|
||||
SuggestionList::SuggestionList(size_t _maxSize) :
|
||||
maxSize(_maxSize), internalSize(0), tokens(_maxSize), isPrimaryTokenAutoInsert(false)
|
||||
{ }
|
||||
|
||||
SuggestionList::~SuggestionList() = default;
|
||||
|
||||
bool SuggestionList::add(word_t &&word, freq_t &&freq) {
|
||||
auto entryIndex = indexOfWord(word);
|
||||
if (entryIndex.has_value()) {
|
||||
// Word exists already
|
||||
auto entry = tokens[entryIndex.value()];
|
||||
if (entry.freq < freq) {
|
||||
// Need to update freq
|
||||
entry.freq = freq;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (internalSize < maxSize) {
|
||||
tokens[internalSize++] = WeightedToken(std::move(word), freq);
|
||||
} else {
|
||||
auto last = tokens[internalSize - 1];
|
||||
if (last.freq < freq) {
|
||||
tokens[internalSize - 1] = WeightedToken(std::move(word), freq);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(tokens.begin(), tokens.begin() + internalSize, std::greater<>());
|
||||
return true;
|
||||
}
|
||||
|
||||
void SuggestionList::clear() {
|
||||
internalSize = 0;
|
||||
isPrimaryTokenAutoInsert = false;
|
||||
}
|
||||
|
||||
bool SuggestionList::contains(const WeightedToken &element) const {
|
||||
return indexOf(element).has_value();
|
||||
}
|
||||
|
||||
bool SuggestionList::containsWord(const word_t &word) const {
|
||||
return indexOfWord(word).has_value();
|
||||
}
|
||||
|
||||
const WeightedToken *SuggestionList::get(size_t index) const {
|
||||
if (index < 0 || index >= internalSize) return nullptr;
|
||||
return &tokens[index];
|
||||
}
|
||||
|
||||
std::optional<size_t> SuggestionList::indexOf(const WeightedToken &element) const {
|
||||
for (size_t n = 0; n < internalSize; n++) {
|
||||
if (element == tokens[n]) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<size_t> SuggestionList::indexOfWord(const word_t &word) const {
|
||||
for (size_t n = 0; n < internalSize; n++) {
|
||||
if (word == tokens[n].data) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SuggestionList::isEmpty() const {
|
||||
return internalSize == 0;
|
||||
}
|
||||
|
||||
size_t SuggestionList::size() const {
|
||||
return internalSize;
|
||||
}
|
||||
51
app/src/main/cpp/ime/nlp/suggestion_list.h
Normal file
51
app/src/main/cpp/ime/nlp/suggestion_list.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_SUGGESTION_LIST_H
|
||||
#define FLORISBOARD_SUGGESTION_LIST_H
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "token.h"
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
class SuggestionList {
|
||||
public:
|
||||
SuggestionList(size_t _maxSize);
|
||||
~SuggestionList();
|
||||
|
||||
bool add(word_t &&word, freq_t &&freq);
|
||||
void clear();
|
||||
bool contains(const WeightedToken &element) const;
|
||||
bool containsWord(const word_t &word) const;
|
||||
const WeightedToken *get(size_t index) const;
|
||||
std::optional<size_t> indexOf(const WeightedToken &element) const;
|
||||
std::optional<size_t> indexOfWord(const word_t &word) const;
|
||||
bool isEmpty() const;
|
||||
size_t size() const;
|
||||
|
||||
bool isPrimaryTokenAutoInsert;
|
||||
|
||||
private:
|
||||
std::vector<WeightedToken> tokens;
|
||||
size_t internalSize;
|
||||
size_t maxSize;
|
||||
};
|
||||
|
||||
} // namespace ime::nlp
|
||||
|
||||
#endif // FLORISBOARD_SUGGESTION_LIST_H
|
||||
61
app/src/main/cpp/ime/nlp/token.cpp
Normal file
61
app/src/main/cpp/ime/nlp/token.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "token.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
Token::Token() : data() {}
|
||||
Token::Token(word_t &&_data) : data(std::move(_data)) {}
|
||||
|
||||
bool operator==(const Token &t1, const Token &t2) {
|
||||
return t1.data == t2.data;
|
||||
}
|
||||
|
||||
bool operator!=(const Token &t1, const Token &t2) {
|
||||
return !(t1 == t2);
|
||||
}
|
||||
|
||||
WeightedToken::WeightedToken() : Token(), freq(0) {}
|
||||
WeightedToken::WeightedToken(word_t &&_data, freq_t _freq) : Token(std::move(_data)), freq(_freq) {}
|
||||
|
||||
bool operator==(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.data == t2.data && t1.freq == t2.freq;
|
||||
}
|
||||
|
||||
bool operator!=(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return !(t1 == t2);
|
||||
}
|
||||
|
||||
bool operator<(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq < t2.freq;
|
||||
}
|
||||
|
||||
bool operator<=(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq <= t2.freq;
|
||||
}
|
||||
|
||||
bool operator>(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq > t2.freq;
|
||||
}
|
||||
|
||||
bool operator>=(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq >= t2.freq;
|
||||
}
|
||||
|
||||
} // namespace ime::nlp
|
||||
51
app/src/main/cpp/ime/nlp/token.h
Normal file
51
app/src/main/cpp/ime/nlp/token.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_TOKEN_H
|
||||
#define FLORISBOARD_TOKEN_H
|
||||
|
||||
#include "nlp.h"
|
||||
#include <string>
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
class Token {
|
||||
public:
|
||||
word_t data;
|
||||
Token();
|
||||
Token(word_t &&_data);
|
||||
|
||||
friend bool operator==(const Token &t1, const Token &t2);
|
||||
friend bool operator!=(const Token &t1, const Token &t2);
|
||||
};
|
||||
|
||||
class WeightedToken : public Token {
|
||||
public:
|
||||
freq_t freq;
|
||||
WeightedToken();
|
||||
WeightedToken(word_t &&_data, freq_t _freq);
|
||||
|
||||
friend bool operator==(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator!=(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator<(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator<=(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator>(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator>=(const WeightedToken &t1, const WeightedToken &t2);
|
||||
};
|
||||
|
||||
} // namespace ime::nlp
|
||||
|
||||
#endif // FLORISBOARD_TOKEN_H
|
||||
@@ -53,6 +53,7 @@ abstract class CrashUtility private constructor() {
|
||||
private const val UNHANDLED_STACKTRACE_FILE_EXT = "stacktrace"
|
||||
|
||||
private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)
|
||||
private var stagedException: Throwable? = null
|
||||
|
||||
/**
|
||||
* Installs the CrashUtility crash handler for the given package [context]. Also registers
|
||||
@@ -148,6 +149,22 @@ abstract class CrashUtility private constructor() {
|
||||
return true
|
||||
}
|
||||
|
||||
fun stageException(e: Throwable?) {
|
||||
if (stagedException == null) {
|
||||
stagedException = e
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
fun handleStagedButUnhandledExceptions() {
|
||||
val e = stagedException ?: return
|
||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
if (handler is UncaughtExceptionHandler) {
|
||||
stagedException = null
|
||||
handler.uncaughtException(null, e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns all unhandled stacktrace files.
|
||||
*
|
||||
@@ -362,7 +379,6 @@ abstract class CrashUtility private constructor() {
|
||||
flogInfo(LogTopic.CRASH_UTILITY) {
|
||||
"Detected application crash, executing custom crash handler."
|
||||
}
|
||||
thread ?: return
|
||||
throwable ?: return
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val stacktrace = Log.getStackTraceString(throwable)
|
||||
|
||||
@@ -35,6 +35,7 @@ import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ItemType
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputManager
|
||||
import dev.patrickgold.florisboard.ime.text.composing.Composer
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@@ -124,6 +125,9 @@ class EditorInstance private constructor(
|
||||
newSelStart: Int, newSelEnd: Int,
|
||||
candidatesStart: Int, candidatesEnd: Int
|
||||
) {
|
||||
if (newSelStart == oldSelStart && newSelEnd == oldSelEnd) {
|
||||
return
|
||||
}
|
||||
// The Android Framework allows that start can be greater than end in some cases. To prevent bugs in the Floris
|
||||
// input logic, we swap start and end here if this should really be the case.
|
||||
if (newSelEnd < newSelStart) {
|
||||
@@ -131,12 +135,12 @@ class EditorInstance private constructor(
|
||||
} else {
|
||||
selection.update(newSelStart, newSelEnd)
|
||||
}
|
||||
cachedInput.update()
|
||||
if (isPhantomSpaceActive && wasPhantomSpaceActiveLastUpdate) {
|
||||
isPhantomSpaceActive = false
|
||||
} else if (isPhantomSpaceActive && !wasPhantomSpaceActiveLastUpdate) {
|
||||
wasPhantomSpaceActiveLastUpdate = true
|
||||
}
|
||||
cachedInput.update()
|
||||
if (isComposingEnabled && candidatesStart >= 0 && candidatesEnd >= 0) {
|
||||
shouldReevaluateComposingSuggestions = true
|
||||
}
|
||||
@@ -172,6 +176,26 @@ class EditorInstance private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper, replacing a call to inputConnection.commitText with text composition in mind.
|
||||
*/
|
||||
fun doCommitText(text: String): Pair<Boolean, String> {
|
||||
val ic = inputConnection ?: return Pair(false, "")
|
||||
val composer: Composer = FlorisBoard.getInstance().composer
|
||||
return if (text.length != 1) {
|
||||
Pair(ic.commitText(text, 1), text)
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
ic.finishComposingText()
|
||||
val previous = getTextBeforeCursor(composer.toRead)
|
||||
val (rm, finalText) = composer.getActions(previous, text[0])
|
||||
if (rm != 0) ic.deleteSurroundingText(rm, 0)
|
||||
ic.commitText(finalText, 1)
|
||||
ic.endBatchEdit()
|
||||
Pair(true, finalText)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the given [text] to this editor instance and adjusts both the cursor position and
|
||||
* composing region, if any.
|
||||
@@ -186,7 +210,7 @@ class EditorInstance private constructor(
|
||||
fun commitText(text: String): Boolean {
|
||||
val ic = inputConnection ?: return false
|
||||
return if (isRawInputEditor || selection.isSelectionMode || !isComposingEnabled) {
|
||||
ic.commitText(text, 1)
|
||||
doCommitText(text).first
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
val isWordComponent = CachedInput.isWordComponent(text)
|
||||
@@ -199,8 +223,8 @@ class EditorInstance private constructor(
|
||||
}
|
||||
!isPhantomSpace && isWordComponent -> {
|
||||
ic.finishComposingText()
|
||||
ic.commitText(text, 1)
|
||||
ic.setComposingRegion(cachedInput.currentWord.start, cachedInput.currentWord.end + text.length)
|
||||
val finalText = doCommitText(text).second
|
||||
ic.setComposingRegion(cachedInput.currentWord.start, cachedInput.currentWord.end + finalText.length)
|
||||
}
|
||||
else -> {
|
||||
ic.finishComposingText()
|
||||
@@ -1001,8 +1025,8 @@ class CachedInput(private val editorInstance: EditorInstance) {
|
||||
private set
|
||||
|
||||
companion object {
|
||||
private const val CACHED_TEXT_N_CHARS_BEFORE_CURSOR: Int = 192
|
||||
private const val CACHED_TEXT_N_CHARS_AFTER_CURSOR: Int = 64
|
||||
private const val CACHED_TEXT_N_CHARS_BEFORE_CURSOR: Int = 128
|
||||
private const val CACHED_TEXT_N_CHARS_AFTER_CURSOR: Int = 48
|
||||
|
||||
private val WORD_EVAL_REGEX = """[^\p{L}\']""".toRegex()
|
||||
private val WORD_SPLIT_REGEX_EN = """((?<=$WORD_EVAL_REGEX)|(?=$WORD_EVAL_REGEX))""".toRegex()
|
||||
|
||||
@@ -25,19 +25,35 @@ import android.inputmethodservice.ExtractEditText
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.view.*
|
||||
import android.util.Size
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.Gravity
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InlineSuggestionsRequest
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.inline.InlinePresentationSpec
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.lifecycle.*
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.crashutility.CrashUtility
|
||||
import dev.patrickgold.florisboard.debug.*
|
||||
import dev.patrickgold.florisboard.ime.clip.ClipboardInputManager
|
||||
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
@@ -47,15 +63,26 @@ 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.composing.Composer
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.*
|
||||
import dev.patrickgold.florisboard.ime.text.key.CurrencySet
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.setup.SetupActivity
|
||||
import dev.patrickgold.florisboard.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import dev.patrickgold.florisboard.util.AppVersionUtils
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
import dev.patrickgold.florisboard.util.debugSummarize
|
||||
import dev.patrickgold.florisboard.util.findViewWithType
|
||||
import dev.patrickgold.florisboard.util.refreshLayoutOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
@@ -75,6 +102,9 @@ 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.
|
||||
*
|
||||
* All inline suggestion code has been added based on this demo autofill IME provided by Android directly:
|
||||
* https://cs.android.com/android/platform/superproject/+/master:development/samples/AutofillKeyboard/src/com/example/android/autofillkeyboard/AutofillImeService.java
|
||||
*/
|
||||
class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager.OnPrimaryClipChangedListener,
|
||||
ThemeManager.OnThemeUpdatedListener {
|
||||
@@ -109,37 +139,40 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
private var vibrator: Vibrator? = null
|
||||
|
||||
private var internalBatchNestingLevel: Int = 0
|
||||
private val internalSelectionCache = object {
|
||||
var selectionCatchCount: Int = 0
|
||||
var oldSelStart: Int = -1
|
||||
var oldSelEnd: Int = -1
|
||||
var newSelStart: Int = -1
|
||||
var newSelEnd: Int = -1
|
||||
var candidatesStart: Int = -1
|
||||
var candidatesEnd: Int = -1
|
||||
}
|
||||
|
||||
var activeEditorInstance: EditorInstance = EditorInstance.default()
|
||||
|
||||
val subtypeManager: SubtypeManager get() = SubtypeManager.default()
|
||||
val composer: Composer get() = subtypeManager.imeConfig.composers[subtypeManager.imeConfig.composerNames.indexOf(activeSubtype.composerName)]
|
||||
lateinit var activeSubtype: Subtype
|
||||
private var currentThemeIsNight: Boolean = false
|
||||
private var currentThemeResId: Int = 0
|
||||
private var isNumberRowVisible: Boolean = false
|
||||
private var isWindowShown: Boolean = false
|
||||
|
||||
val textInputManager: TextInputManager
|
||||
val mediaInputManager: MediaInputManager
|
||||
val clipInputManager: ClipboardInputManager
|
||||
private var responseState = ResponseState.RESET
|
||||
private var pendingResponse: Runnable? = null
|
||||
private val handler: Handler = Handler(Looper.getMainLooper())
|
||||
|
||||
lateinit var textInputManager: TextInputManager
|
||||
lateinit var mediaInputManager: MediaInputManager
|
||||
lateinit var clipInputManager: ClipboardInputManager
|
||||
|
||||
var isClipboardContextMenuShown = false
|
||||
|
||||
init {
|
||||
florisboardInstance = this
|
||||
// MUST WRAP all code within Service init in try..catch to prevent any crash loops
|
||||
try {
|
||||
florisboardInstance = this
|
||||
|
||||
textInputManager = TextInputManager.getInstance()
|
||||
mediaInputManager = MediaInputManager.getInstance()
|
||||
clipInputManager = ClipboardInputManager.getInstance()
|
||||
textInputManager = TextInputManager.getInstance()
|
||||
mediaInputManager = MediaInputManager.getInstance()
|
||||
clipInputManager = ClipboardInputManager.getInstance()
|
||||
|
||||
System.loadLibrary("florisboard-native")
|
||||
} catch (e: Exception) {
|
||||
CrashUtility.stageException(e)
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var asyncExecutor: ExecutorService
|
||||
@@ -172,54 +205,51 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
/*if (BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(
|
||||
StrictMode.ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectNetwork() // or .detectAll() for all detectable problems
|
||||
.penaltyLog()
|
||||
.build()
|
||||
)
|
||||
StrictMode.setVmPolicy(
|
||||
StrictMode.VmPolicy.Builder()
|
||||
.detectLeakedSqlLiteObjects()
|
||||
.detectLeakedClosableObjects()
|
||||
.penaltyLog()
|
||||
.penaltyDeath()
|
||||
.build()
|
||||
)
|
||||
}*/
|
||||
flogInfo(LogTopic.IMS_EVENTS)
|
||||
serviceLifecycleDispatcher.onServicePreSuperOnCreate()
|
||||
// MUST WRAP all code within Service onCreate() in try..catch to prevent any crash loops
|
||||
try {
|
||||
// Additional try..catch wrapper as the event listeners chain or the super.onCreate() method could crash
|
||||
// and lead to a crash loop
|
||||
try {
|
||||
// "Main" try..catch block
|
||||
flogInfo(LogTopic.IMS_EVENTS)
|
||||
serviceLifecycleDispatcher.onServicePreSuperOnCreate()
|
||||
|
||||
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
||||
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
||||
prefs.sync()
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
||||
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
||||
prefs.sync()
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
|
||||
currentThemeIsNight = themeManager.activeTheme.isNightTheme
|
||||
currentThemeResId = getDayNightBaseThemeId(currentThemeIsNight)
|
||||
isNumberRowVisible = prefs.keyboard.numberRow
|
||||
setTheme(currentThemeResId)
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
currentThemeIsNight = themeManager.activeTheme.isNightTheme
|
||||
currentThemeResId = getDayNightBaseThemeId(currentThemeIsNight)
|
||||
isNumberRowVisible = prefs.keyboard.numberRow
|
||||
setTheme(currentThemeResId)
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
|
||||
asyncExecutor = Executors.newSingleThreadExecutor()
|
||||
florisClipboardManager = FlorisClipboardManager.getInstance().also {
|
||||
it.initialize(this)
|
||||
it.addPrimaryClipChangedListener(this)
|
||||
asyncExecutor = Executors.newSingleThreadExecutor()
|
||||
florisClipboardManager = FlorisClipboardManager.getInstance().also {
|
||||
it.initialize(this)
|
||||
it.addPrimaryClipChangedListener(this)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
super.onCreate() // MUST CALL even if exception thrown or crash loop is imminent
|
||||
CrashUtility.stageException(e)
|
||||
return
|
||||
}
|
||||
// Code executed here indicates no crashes occurred, so we execute the onCreate() event as normal
|
||||
super.onCreate()
|
||||
eventListeners.toList().forEach { it?.onCreate() }
|
||||
} catch (e: Exception) {
|
||||
CrashUtility.stageException(e)
|
||||
}
|
||||
|
||||
super.onCreate()
|
||||
eventListeners.toList().forEach { it?.onCreate() }
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateInputView(): View? {
|
||||
flogInfo(LogTopic.IMS_EVENTS)
|
||||
CrashUtility.handleStagedButUnhandledExceptions()
|
||||
|
||||
updateThemeContext(currentThemeResId)
|
||||
|
||||
@@ -333,6 +363,11 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
flogInfo(LogTopic.IMS_EVENTS)
|
||||
|
||||
super.onStartInput(attribute, restarting)
|
||||
responseState = if (responseState == ResponseState.RECEIVE_RESPONSE) {
|
||||
ResponseState.START_INPUT
|
||||
} else {
|
||||
ResponseState.RESET
|
||||
}
|
||||
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
|
||||
}
|
||||
|
||||
@@ -353,6 +388,10 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
|
||||
if (finishingInput) {
|
||||
activeEditorInstance = EditorInstance.default()
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
textInputManager.smartbarView?.clearInlineSuggestions()
|
||||
}
|
||||
}
|
||||
|
||||
super.onFinishInputView(finishingInput)
|
||||
@@ -366,6 +405,71 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
super.onFinishInput()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? {
|
||||
return if (prefs.smartbar.enabled && prefs.suggestion.api30InlineSuggestionsEnabled) {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"Creating inline suggestions request because Smartbar and inline suggestions are enabled."
|
||||
}
|
||||
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(themeContext)
|
||||
InlinePresentationSpec.Builder(
|
||||
Size(
|
||||
inputView?.desiredInlineSuggestionsMinWidth ?: 0,
|
||||
inputView?.desiredInlineSuggestionsMinHeight ?: 0
|
||||
),
|
||||
Size(
|
||||
inputView?.desiredInlineSuggestionsMaxWidth ?: 0,
|
||||
inputView?.desiredInlineSuggestionsMaxHeight ?: 0
|
||||
)
|
||||
).let { spec ->
|
||||
spec.setStyle(stylesBundle)
|
||||
InlineSuggestionsRequest.Builder(listOf(spec.build())).let { request ->
|
||||
request.setMaxSuggestionCount(6)
|
||||
request.build()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"Ignoring inline suggestions request because Smartbar and/or inline suggestions are disabled."
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"Received inline suggestions response with ${response.inlineSuggestions.size} suggestion(s) provided."
|
||||
}
|
||||
textInputManager.smartbarView?.clearInlineSuggestions()
|
||||
postPendingResponse(response)
|
||||
return true
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun cancelPendingResponse() {
|
||||
pendingResponse?.let {
|
||||
handler.removeCallbacks(it)
|
||||
pendingResponse = null
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun postPendingResponse(response: InlineSuggestionsResponse) {
|
||||
cancelPendingResponse()
|
||||
val inlineSuggestions = response.inlineSuggestions
|
||||
responseState = ResponseState.RECEIVE_RESPONSE
|
||||
pendingResponse = Runnable {
|
||||
pendingResponse = null
|
||||
if (responseState == ResponseState.START_INPUT && inlineSuggestions.isEmpty()) {
|
||||
textInputManager.smartbarView?.clearInlineSuggestions()
|
||||
} else {
|
||||
textInputManager.smartbarView?.showInlineSuggestions(inlineSuggestions)
|
||||
}
|
||||
responseState = ResponseState.RESET
|
||||
}.also { handler.post(it) }
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
super.onWindowShown()
|
||||
if (isWindowShown) {
|
||||
@@ -383,10 +487,10 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
isNumberRowVisible = newIsNumberRowVisible
|
||||
}
|
||||
themeManager.update()
|
||||
updateOneHandedPanelVisibility()
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
onSubtypeChanged(activeSubtype)
|
||||
setActiveInput(R.id.text_input)
|
||||
updateOneHandedPanelVisibility()
|
||||
|
||||
if (prefs.devtools.enabled && prefs.devtools.showHeapMemoryStats) {
|
||||
devtoolsOverlaySyncJob?.cancel()
|
||||
@@ -445,24 +549,6 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
*/
|
||||
fun endInternalBatchEdit() {
|
||||
internalBatchNestingLevel = (internalBatchNestingLevel - 1).coerceAtLeast(0)
|
||||
if (internalBatchNestingLevel == 0) {
|
||||
internalSelectionCache.apply {
|
||||
if (selectionCatchCount > 0) {
|
||||
onUpdateSelection(
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd,
|
||||
candidatesStart, candidatesEnd
|
||||
)
|
||||
selectionCatchCount = 0
|
||||
oldSelStart = -1
|
||||
oldSelEnd = -1
|
||||
newSelStart = -1
|
||||
newSelEnd = -1
|
||||
candidatesStart = -1
|
||||
candidatesEnd = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateSelection(
|
||||
@@ -476,27 +562,13 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
candidatesStart, candidatesEnd
|
||||
)
|
||||
|
||||
if (internalBatchNestingLevel == 0) {
|
||||
flogInfo(LogTopic.IMS_EVENTS) { "$oldSelStart, $oldSelEnd, $newSelStart, $newSelEnd, $candidatesStart, $candidatesEnd" }
|
||||
activeEditorInstance.onUpdateSelection(
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd,
|
||||
candidatesStart, candidatesEnd
|
||||
)
|
||||
eventListeners.toList().forEach { it?.onUpdateSelection() }
|
||||
} else {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"$oldSelStart, $oldSelEnd, $newSelStart, $newSelEnd, $candidatesStart, $candidatesEnd: caught due to internal batch level of $internalBatchNestingLevel!"
|
||||
}
|
||||
if (internalSelectionCache.selectionCatchCount++ == 0) {
|
||||
internalSelectionCache.oldSelStart = oldSelStart
|
||||
internalSelectionCache.oldSelEnd = oldSelEnd
|
||||
}
|
||||
internalSelectionCache.newSelStart = newSelStart
|
||||
internalSelectionCache.newSelEnd = newSelEnd
|
||||
internalSelectionCache.candidatesStart = candidatesStart
|
||||
internalSelectionCache.candidatesEnd = candidatesEnd
|
||||
}
|
||||
flogInfo(LogTopic.IMS_EVENTS) { "$oldSelStart, $oldSelEnd, $newSelStart, $newSelEnd, $candidatesStart, $candidatesEnd" }
|
||||
activeEditorInstance.onUpdateSelection(
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd,
|
||||
candidatesStart, candidatesEnd
|
||||
)
|
||||
eventListeners.toList().forEach { it?.onUpdateSelection() }
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
@@ -871,6 +943,10 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
fun onSubtypeChanged(newSubtype: Subtype) {}
|
||||
}
|
||||
|
||||
private enum class ResponseState {
|
||||
RESET, RECEIVE_RESPONSE, START_INPUT
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class which holds the base information for this IME. Matches the structure of
|
||||
* ime/config.json so it can be parsed. Used by [SubtypeManager] and by the prefs.
|
||||
@@ -889,15 +965,34 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
|
||||
data class ImeConfig(
|
||||
@SerialName("package")
|
||||
val packageName: String,
|
||||
@SerialName("composers")
|
||||
val composers: List<Composer> = listOf(),
|
||||
@SerialName("currencySets")
|
||||
val currencySets: List<CurrencySet> = listOf(),
|
||||
@SerialName("defaultSubtypes")
|
||||
val defaultSubtypes: List<DefaultSubtype> = listOf()
|
||||
) {
|
||||
@Transient var currencySetNames: List<String> = listOf()
|
||||
@Transient var currencySetLabels: List<String> = listOf()
|
||||
@Transient var composerNames: List<String> = listOf()
|
||||
@Transient var composerLabels: List<String> = listOf()
|
||||
@Transient var defaultSubtypesLanguageCodes: List<String> = listOf()
|
||||
@Transient var defaultSubtypesLanguageNames: List<String> = listOf()
|
||||
|
||||
init {
|
||||
val tmpComposerList = composers.map { Pair(it.name, it.label) }.toMutableList()
|
||||
// Sort composer list alphabetically by the label of a composer
|
||||
tmpComposerList.sortBy { it.second }
|
||||
// Move selected composers to the top of the list
|
||||
for (composerName in listOf("appender")) {
|
||||
val index: Int = tmpComposerList.indexOfFirst { it.first == composerName }
|
||||
if (index > 0) {
|
||||
tmpComposerList.add(0, tmpComposerList.removeAt(index))
|
||||
}
|
||||
}
|
||||
composerNames = tmpComposerList.map { it.first }.toList()
|
||||
composerLabels = tmpComposerList.map { it.second }.toList()
|
||||
|
||||
val tmpCurrencyList = mutableListOf<Pair<String, String>>()
|
||||
for (currencySet in currencySets) {
|
||||
tmpCurrencyList.add(Pair(currencySet.name, currencySet.label))
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.forEach
|
||||
import androidx.core.util.set
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
@@ -36,8 +39,9 @@ class InputEventDispatcher private constructor(
|
||||
private val repeatableKeyCodes: IntArray
|
||||
) : InputKeyEventSender {
|
||||
private val channel: Channel<InputKeyEvent> = Channel(channelCapacity)
|
||||
private val scope: CoroutineScope = CoroutineScope(defaultDispatcher + SupervisorJob())
|
||||
private val pressedKeys: HashMap<Int, PressedKeyInfo> = hashMapOf()
|
||||
private val mainScope: CoroutineScope = CoroutineScope(mainDispatcher + SupervisorJob())
|
||||
private val defaultScope: CoroutineScope = CoroutineScope(defaultDispatcher + SupervisorJob())
|
||||
private val pressedKeys: SparseArray<PressedKeyInfo> = SparseArray()
|
||||
var lastKeyEventDown: InputKeyEvent? = null
|
||||
private set
|
||||
var lastKeyEventUp: InputKeyEvent? = null
|
||||
@@ -69,16 +73,26 @@ class InputEventDispatcher private constructor(
|
||||
*/
|
||||
fun new(
|
||||
channelCapacity: Int = DEFAULT_CHANNEL_CAPACITY,
|
||||
mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
|
||||
mainDispatcher: CoroutineDispatcher = Dispatchers.Main.immediate,
|
||||
defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
repeatableKeyCodes: IntArray = intArrayOf()
|
||||
): InputEventDispatcher = InputEventDispatcher(
|
||||
channelCapacity, mainDispatcher, defaultDispatcher, repeatableKeyCodes.clone()
|
||||
)
|
||||
|
||||
private fun <T> SparseArray<T>.removeAndReturn(key: Int): T? {
|
||||
val elem = get(key)
|
||||
return if (elem == null) {
|
||||
null
|
||||
} else {
|
||||
remove(key)
|
||||
elem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
scope.launch(defaultDispatcher) {
|
||||
defaultScope.launch {
|
||||
for (ev in channel) {
|
||||
if (!isActive) break
|
||||
val startTime = System.nanoTime()
|
||||
@@ -87,11 +101,11 @@ class InputEventDispatcher private constructor(
|
||||
}
|
||||
when (ev.action) {
|
||||
InputKeyEvent.Action.DOWN -> {
|
||||
if (pressedKeys.containsKey(ev.data.code)) continue
|
||||
if (pressedKeys.indexOfKey(ev.data.code) >= 0) continue
|
||||
pressedKeys[ev.data.code] = PressedKeyInfo(
|
||||
eventTimeDown = ev.eventTime,
|
||||
repeatKeyPressJob = if (!repeatableKeyCodes.contains(ev.data.code)) { null } else {
|
||||
scope.launch(defaultDispatcher) {
|
||||
defaultScope.launch {
|
||||
delay(600)
|
||||
while (isActive) {
|
||||
channel.send(InputKeyEvent.repeat(ev.data))
|
||||
@@ -108,7 +122,7 @@ class InputEventDispatcher private constructor(
|
||||
}
|
||||
}
|
||||
InputKeyEvent.Action.DOWN_UP -> {
|
||||
pressedKeys.remove(ev.data.code)?.repeatKeyPressJob?.cancel()
|
||||
pressedKeys.removeAndReturn(ev.data.code)?.repeatKeyPressJob?.cancel()
|
||||
withContext(mainDispatcher) {
|
||||
keyEventReceiver?.onInputKeyDown(ev)
|
||||
keyEventReceiver?.onInputKeyUp(ev)
|
||||
@@ -119,7 +133,7 @@ class InputEventDispatcher private constructor(
|
||||
}
|
||||
}
|
||||
InputKeyEvent.Action.UP -> {
|
||||
pressedKeys.remove(ev.data.code)?.repeatKeyPressJob?.cancel()
|
||||
pressedKeys.removeAndReturn(ev.data.code)?.repeatKeyPressJob?.cancel()
|
||||
withContext(mainDispatcher) {
|
||||
keyEventReceiver?.onInputKeyUp(ev)
|
||||
}
|
||||
@@ -128,14 +142,14 @@ class InputEventDispatcher private constructor(
|
||||
}
|
||||
}
|
||||
InputKeyEvent.Action.REPEAT -> {
|
||||
if (pressedKeys.containsKey(ev.data.code)) {
|
||||
if (pressedKeys.indexOfKey(ev.data.code) >= 0) {
|
||||
withContext(mainDispatcher) {
|
||||
keyEventReceiver?.onInputKeyRepeat(ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
InputKeyEvent.Action.CANCEL -> {
|
||||
pressedKeys.remove(ev.data.code)?.repeatKeyPressJob?.cancel()
|
||||
pressedKeys.removeAndReturn(ev.data.code)?.repeatKeyPressJob?.cancel()
|
||||
withContext(mainDispatcher) {
|
||||
keyEventReceiver?.onInputKeyCancel(ev)
|
||||
}
|
||||
@@ -145,16 +159,13 @@ class InputEventDispatcher private constructor(
|
||||
Timber.d("Time elapsed: ${(System.nanoTime() - startTime) / 1_000_000}")
|
||||
}
|
||||
}
|
||||
val pressedKeysIterator = pressedKeys.iterator()
|
||||
while (pressedKeysIterator.hasNext()) {
|
||||
pressedKeysIterator.next().value.repeatKeyPressJob?.cancel()
|
||||
pressedKeysIterator.remove()
|
||||
}
|
||||
pressedKeys.forEach { _, value -> value.repeatKeyPressJob?.cancel() }
|
||||
pressedKeys.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(ev: InputKeyEvent) {
|
||||
scope.launch(mainDispatcher) {
|
||||
mainScope.launch {
|
||||
channel.send(ev)
|
||||
}
|
||||
}
|
||||
@@ -167,7 +178,7 @@ class InputEventDispatcher private constructor(
|
||||
* @return True if the given [code] is currently down, false otherwise.
|
||||
*/
|
||||
fun isPressed(code: Int): Boolean {
|
||||
return pressedKeys.containsKey(code)
|
||||
return pressedKeys.indexOfKey(code) >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +186,8 @@ class InputEventDispatcher private constructor(
|
||||
*/
|
||||
fun close() {
|
||||
keyEventReceiver = null
|
||||
scope.cancel()
|
||||
mainScope.cancel()
|
||||
defaultScope.cancel()
|
||||
}
|
||||
|
||||
data class PressedKeyInfo(
|
||||
|
||||
@@ -22,9 +22,11 @@ import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import android.text.TextPaint
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Size
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
@@ -56,6 +58,15 @@ class InputView : LinearLayout {
|
||||
var shouldGiveAdditionalSpace: Boolean = false
|
||||
private set
|
||||
|
||||
var desiredInlineSuggestionsMinWidth: Int = 0
|
||||
private set
|
||||
var desiredInlineSuggestionsMinHeight: Int = 0
|
||||
private set
|
||||
var desiredInlineSuggestionsMaxWidth: Int = 0
|
||||
private set
|
||||
var desiredInlineSuggestionsMaxHeight: Int = 0
|
||||
private set
|
||||
|
||||
var mainViewFlipper: ViewFlipper? = null
|
||||
private set
|
||||
var oneHandedCtrlPanelStart: ViewGroup? = null
|
||||
@@ -123,7 +134,7 @@ class InputView : LinearLayout {
|
||||
baseTextInputHeight += additionalHeight
|
||||
}
|
||||
val smartbarDisabled = !prefs.smartbar.enabled ||
|
||||
tim.keyVariation == KeyVariation.PASSWORD && prefs.keyboard.numberRow
|
||||
tim.keyVariation == KeyVariation.PASSWORD && prefs.keyboard.numberRow && !prefs.suggestion.api30InlineSuggestionsEnabled
|
||||
if (smartbarDisabled) {
|
||||
baseHeight = baseTextInputHeight
|
||||
baseSmartbarHeight = 0.0f
|
||||
@@ -143,6 +154,14 @@ class InputView : LinearLayout {
|
||||
context
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
desiredInlineSuggestionsMinWidth = width / 3
|
||||
desiredInlineSuggestionsMinHeight = desiredSmartbarHeight.toInt()
|
||||
desiredInlineSuggestionsMaxWidth = (width / 1.5).toInt()
|
||||
desiredInlineSuggestionsMaxHeight = desiredSmartbarHeight.toInt()
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(baseHeight.roundToInt(), MeasureSpec.EXACTLY))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
/**
|
||||
* Type alias for a native pointer.
|
||||
*/
|
||||
typealias NativePtr = Long
|
||||
|
||||
/**
|
||||
* Constant value for a native null pointer.
|
||||
*/
|
||||
const val NATIVE_NULLPTR: NativePtr = 0
|
||||
|
||||
/**
|
||||
* Generic interface for a native instance object. Defines the basic
|
||||
* methods which each native instance wrapper should define and be able
|
||||
* to handle to.
|
||||
*/
|
||||
interface NativeInstanceWrapper {
|
||||
/**
|
||||
* Returns the native pointer of this instance. The returned pointer
|
||||
* is only valid if [dispose] has not been previously called.
|
||||
*
|
||||
* @return The native null pointer for this instance.
|
||||
*/
|
||||
fun nativePtr(): NativePtr
|
||||
|
||||
/**
|
||||
* Deletes the native object and frees allocated resources. After
|
||||
* invoking this method one MUST NOT touch this instance ever again.
|
||||
*/
|
||||
fun dispose()
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.DistanceThreshold
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.VelocityThreshold
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.CandidateView
|
||||
@@ -129,7 +130,9 @@ class Preferences(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val OLD_SUBTYPES_REGEX = """^([\-0-9]+/[\-a-zA-Z0-9]+/[a-zA-Z_]+[;]*)+${'$'}""".toRegex()
|
||||
// old settings are id/language/layout and id/language/currencySet/layout
|
||||
// new settings have composer
|
||||
private val OLD_SUBTYPES_REGEX = """^([\-0-9]+/[\-a-zA-Z0-9]+(/[a-zA-Z_]+)?/[a-zA-Z_]+[;]*)+${'$'}""".toRegex()
|
||||
private var defaultInstance: Preferences? = null
|
||||
|
||||
@Synchronized
|
||||
@@ -397,6 +400,7 @@ class Preferences(
|
||||
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 MERGE_HINT_POPUPS_ENABLED = "keyboard__merge_hint_popups_enabled"
|
||||
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"
|
||||
@@ -447,6 +451,9 @@ class Preferences(
|
||||
var longPressDelay: Int = 0
|
||||
get() = prefs.getPref(LONG_PRESS_DELAY, 300)
|
||||
private set
|
||||
var mergeHintPopupsEnabled: Boolean
|
||||
get() = prefs.getPref(MERGE_HINT_POPUPS_ENABLED, false)
|
||||
set(v) = prefs.setPref(MERGE_HINT_POPUPS_ENABLED, v)
|
||||
var numberRow: Boolean
|
||||
get() = prefs.getPref(NUMBER_ROW, false)
|
||||
set(v) = prefs.setPref(NUMBER_ROW, v)
|
||||
@@ -485,6 +492,10 @@ class Preferences(
|
||||
var vibrationStrength: Int = 0
|
||||
get() = prefs.getPref(VIBRATION_STRENGTH, -1)
|
||||
private set
|
||||
|
||||
fun getKeyHintConfiguration(): KeyHintConfiguration {
|
||||
return KeyHintConfiguration(hintedSymbolsMode, hintedNumberRowMode, mergeHintPopupsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,14 +533,18 @@ class Preferences(
|
||||
*/
|
||||
class Suggestion(private val prefs: Preferences) {
|
||||
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 USE_PREV_WORDS = "suggestion__use_prev_words"
|
||||
const val API30_INLINE_SUGGESTIONS_ENABLED = "suggestion__api30_inline_suggestions_enabled"
|
||||
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 USE_PREV_WORDS = "suggestion__use_prev_words"
|
||||
}
|
||||
|
||||
var api30InlineSuggestionsEnabled: Boolean
|
||||
get() = prefs.getPref(API30_INLINE_SUGGESTIONS_ENABLED, true)
|
||||
set(v) = prefs.setPref(API30_INLINE_SUGGESTIONS_ENABLED, v)
|
||||
var blockPossiblyOffensive: Boolean
|
||||
get() = prefs.getPref(BLOCK_POSSIBLY_OFFENSIVE, true)
|
||||
set(v) = prefs.setPref(BLOCK_POSSIBLY_OFFENSIVE, v)
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import dev.patrickgold.florisboard.ime.text.composing.Appender
|
||||
import dev.patrickgold.florisboard.ime.text.composing.Composer
|
||||
import dev.patrickgold.florisboard.ime.text.layout.LayoutType
|
||||
import dev.patrickgold.florisboard.util.LocaleUtils
|
||||
import kotlinx.serialization.*
|
||||
@@ -32,12 +34,14 @@ 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 composerName The composer name to composer characters the way they should.
|
||||
* @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(
|
||||
val id: Int,
|
||||
val locale: Locale,
|
||||
val composerName: String,
|
||||
val currencySetName: String,
|
||||
val layoutMap: SubtypeLayoutMap,
|
||||
) {
|
||||
@@ -50,6 +54,7 @@ data class Subtype(
|
||||
val DEFAULT = Subtype(
|
||||
id = -1,
|
||||
locale = Locale.ENGLISH,
|
||||
composerName = Appender.name,
|
||||
currencySetName = "\$default",
|
||||
layoutMap = SubtypeLayoutMap(characters = "qwerty")
|
||||
)
|
||||
@@ -67,17 +72,29 @@ data class Subtype(
|
||||
*/
|
||||
fun fromString(str: String): Subtype {
|
||||
val data = str.split("/")
|
||||
if (data.size != 4) {
|
||||
throw InvalidPropertiesFormatException(
|
||||
"Given string contains more or less than 4 properties..."
|
||||
)
|
||||
} else {
|
||||
val locale = LocaleUtils.stringToLocale(data[1])
|
||||
return Subtype(
|
||||
data[0].toInt(),
|
||||
locale,
|
||||
data[2],
|
||||
SubtypeLayoutMap.fromString(data[3])
|
||||
when (data.size) {
|
||||
4 -> {
|
||||
val locale = LocaleUtils.stringToLocale(data[1])
|
||||
return Subtype(
|
||||
data[0].toInt(),
|
||||
locale,
|
||||
Appender.name,
|
||||
data[2],
|
||||
SubtypeLayoutMap.fromString(data[3])
|
||||
)
|
||||
}
|
||||
5 -> {
|
||||
val locale = LocaleUtils.stringToLocale(data[1])
|
||||
return Subtype(
|
||||
data[0].toInt(),
|
||||
locale,
|
||||
data[2],
|
||||
data[3],
|
||||
SubtypeLayoutMap.fromString(data[4])
|
||||
)
|
||||
}
|
||||
else -> throw InvalidPropertiesFormatException(
|
||||
"Given string contains more or less than 5 properties..."
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,6 +103,7 @@ data class Subtype(
|
||||
init {
|
||||
var result = id
|
||||
result = 31 * result + locale.hashCode()
|
||||
result = 31 * result + composerName.hashCode()
|
||||
result = 31 * result + currencySetName.hashCode()
|
||||
result = 31 * result + layoutMap.hashCode()
|
||||
_hashCode = result
|
||||
@@ -93,11 +111,11 @@ data class Subtype(
|
||||
|
||||
/**
|
||||
* Converts this object into its string representation. Format:
|
||||
* <id>/<language_tag>/<currency_set_name>/<layout_map>
|
||||
* <id>/<language_tag>/<composer_name>/<currency_set_name>/<layout_map>
|
||||
*/
|
||||
override fun toString(): String {
|
||||
val languageTag = locale.toLanguageTag()
|
||||
return "$id/$languageTag/$currencySetName/$layoutMap"
|
||||
return "$id/$languageTag/$composerName/$currencySetName/$layoutMap"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,6 +135,7 @@ data class Subtype(
|
||||
|
||||
if (id != other.id) return false
|
||||
if (locale != other.locale) return false
|
||||
if (composerName != other.composerName) return false
|
||||
if (currencySetName != other.currencySetName) return false
|
||||
if (layoutMap != other.layoutMap) return false
|
||||
|
||||
@@ -310,6 +329,8 @@ data class DefaultSubtype(
|
||||
@Serializable(with = LocaleSerializer::class)
|
||||
@SerialName("languageTag")
|
||||
var locale: Locale,
|
||||
@SerialName("composer")
|
||||
var composerName: String,
|
||||
@SerialName("currencySet")
|
||||
var currencySetName: String,
|
||||
var preferred: SubtypeLayoutMap
|
||||
|
||||
@@ -112,16 +112,18 @@ class SubtypeManager(
|
||||
* list, if it does not exist.
|
||||
*
|
||||
* @param locale The locale of the subtype to be added.
|
||||
* @param composerName The composer name of the subtype to be added.
|
||||
* @param currencySetName The currency set name of the subtype to be added.
|
||||
* @param layoutMap The layout map of the subtype to be added.
|
||||
* @return True if the subtype was added, false otherwise. A return value of false indicates
|
||||
* that the subtype already exists.
|
||||
*/
|
||||
fun addSubtype(locale: Locale, currencySetName: String, layoutMap: SubtypeLayoutMap): Boolean {
|
||||
fun addSubtype(locale: Locale, composerName: String, currencySetName: String, layoutMap: SubtypeLayoutMap): Boolean {
|
||||
return addSubtype(
|
||||
Subtype(
|
||||
(locale.hashCode() + 31 * layoutMap.hashCode() + 31 * currencySetName.hashCode()),
|
||||
locale,
|
||||
composerName,
|
||||
currencySetName,
|
||||
layoutMap
|
||||
)
|
||||
|
||||
@@ -17,40 +17,35 @@
|
||||
package dev.patrickgold.florisboard.ime.dictionary
|
||||
|
||||
import dev.patrickgold.florisboard.ime.extension.Asset
|
||||
import dev.patrickgold.florisboard.ime.nlp.LanguageModel
|
||||
import dev.patrickgold.florisboard.ime.nlp.MutableLanguageModel
|
||||
import dev.patrickgold.florisboard.ime.nlp.Token
|
||||
import dev.patrickgold.florisboard.ime.nlp.WeightedToken
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionList
|
||||
import dev.patrickgold.florisboard.ime.nlp.Word
|
||||
|
||||
/**
|
||||
* Standardized dictionary interface for interacting with dictionaries.
|
||||
*/
|
||||
interface Dictionary<T : Any, F : Comparable<F>> : Asset {
|
||||
val languageModel: LanguageModel<T, F>
|
||||
|
||||
interface Dictionary : Asset {
|
||||
/**
|
||||
* Gets token predictions based on the given [precedingTokens] and the [currentToken]. The
|
||||
* length of the returned list is limited to [maxSuggestionCount]. Note that the returned list
|
||||
* may at any time give back less items than [maxSuggestionCount] indicates.
|
||||
*/
|
||||
fun getTokenPredictions(
|
||||
precedingTokens: List<Token<T>>,
|
||||
currentToken: Token<T>?,
|
||||
precedingTokens: List<Word>,
|
||||
currentToken: Word?,
|
||||
maxSuggestionCount: Int,
|
||||
allowPossiblyOffensive: Boolean
|
||||
): List<WeightedToken<T, F>>
|
||||
allowPossiblyOffensive: Boolean,
|
||||
destSuggestionList: SuggestionList
|
||||
)
|
||||
|
||||
fun getDate(): Long
|
||||
|
||||
fun getVersion(): Int
|
||||
}
|
||||
|
||||
interface MutableDictionary<T : Any, F : Comparable<F>> : Dictionary<T, F> {
|
||||
override val languageModel: MutableLanguageModel<T, F>
|
||||
|
||||
interface MutableDictionary : Dictionary {
|
||||
fun trainTokenPredictions(
|
||||
precedingTokens: List<Token<T>>,
|
||||
lastToken: Token<T>
|
||||
precedingTokens: List<Word>,
|
||||
lastToken: Word
|
||||
)
|
||||
|
||||
fun setDate(date: Int)
|
||||
|
||||
@@ -19,18 +19,27 @@ package dev.patrickgold.florisboard.ime.dictionary
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dev.patrickgold.florisboard.ime.core.Preferences
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetRef
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionList
|
||||
import dev.patrickgold.florisboard.ime.nlp.Word
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* TODO: document
|
||||
*/
|
||||
class DictionaryManager private constructor(context: Context) {
|
||||
class DictionaryManager private constructor(
|
||||
context: Context,
|
||||
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
|
||||
) {
|
||||
private val applicationContext: WeakReference<Context> = WeakReference(context.applicationContext ?: context)
|
||||
private val prefs get() = Preferences.default()
|
||||
|
||||
private val dictionaryCache: MutableMap<String, Dictionary<String, Int>> = mutableMapOf()
|
||||
private val dictionaryCache: MutableMap<String, Dictionary> = mutableMapOf()
|
||||
|
||||
private var florisUserDictionaryDatabase: FlorisUserDictionaryDatabase? = null
|
||||
private var systemUserDictionaryDatabase: SystemUserDictionaryDatabase? = null
|
||||
@@ -56,25 +65,54 @@ class DictionaryManager private constructor(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDictionary(ref: AssetRef): Result<Dictionary<String, Int>> {
|
||||
dictionaryCache[ref.toString()]?.let {
|
||||
return Result.success(it)
|
||||
inline fun suggest(
|
||||
currentWord: Word,
|
||||
preceidingWords: List<Word>,
|
||||
subtype: Subtype,
|
||||
allowPossiblyOffensive: Boolean,
|
||||
maxSuggestionCount: Int,
|
||||
block: (suggestions: SuggestionList) -> Unit
|
||||
) {
|
||||
val suggestions = SuggestionList.new(maxSuggestionCount)
|
||||
queryUserDictionary(currentWord, subtype.locale, suggestions)
|
||||
block(suggestions)
|
||||
suggestions.dispose()
|
||||
}
|
||||
|
||||
fun prepareDictionaries(subtype: Subtype) {
|
||||
// TODO: Implement this
|
||||
}
|
||||
|
||||
fun queryUserDictionary(word: Word, locale: Locale, destSuggestionList: SuggestionList) {
|
||||
val florisDao = florisUserDictionaryDao()
|
||||
val systemDao = systemUserDictionaryDao()
|
||||
if (florisDao == null && systemDao == null) {
|
||||
return
|
||||
}
|
||||
if (ref.path.endsWith(".flict")) {
|
||||
// Assume this is a Flictionary
|
||||
applicationContext.get()?.let {
|
||||
Flictionary.load(it, ref).onSuccess { flict ->
|
||||
dictionaryCache[ref.toString()] = flict
|
||||
return Result.success(flict)
|
||||
}.onFailure { err ->
|
||||
Timber.i(err)
|
||||
return Result.failure(err)
|
||||
if (prefs.dictionary.enableFlorisUserDictionary) {
|
||||
florisDao?.query(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
destSuggestionList.add(entry.word, entry.freq)
|
||||
}
|
||||
}
|
||||
florisDao?.queryShortcut(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
destSuggestionList.add(entry.word, entry.freq)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prefs.dictionary.enableSystemUserDictionary) {
|
||||
systemDao?.query(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
destSuggestionList.add(entry.word, entry.freq)
|
||||
}
|
||||
}
|
||||
systemDao?.queryShortcut(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
destSuggestionList.add(entry.word, entry.freq)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Result.failure(Exception("Unable to determine supported type for given AssetRef!"))
|
||||
}
|
||||
return Result.failure(Exception("If this message is ever thrown, something is completely broken..."))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
||||
@@ -30,15 +30,15 @@ import kotlin.jvm.Throws
|
||||
* This class accepts binary dictionary files of the type "flict" as defined in here:
|
||||
* https://github.com/florisboard/dictionary-tools/blob/main/flictionary.md
|
||||
*/
|
||||
/**
|
||||
class Flictionary private constructor(
|
||||
override val name: String,
|
||||
override val label: String,
|
||||
override val authors: List<String>,
|
||||
private val date: Long,
|
||||
private val version: Int,
|
||||
private val headerStr: String,
|
||||
override val languageModel: LanguageModel<String, Int>
|
||||
) : Dictionary<String, Int> {
|
||||
private val headerStr: String
|
||||
) : Dictionary {
|
||||
companion object {
|
||||
private const val VERSION_0 = 0x0
|
||||
|
||||
@@ -427,3 +427,4 @@ fun InputStream.readNext(b: ByteArray, off: Int, len: Int): Int {
|
||||
}
|
||||
return lenRead
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -131,7 +131,7 @@ interface UserDictionaryDatabase {
|
||||
fun reset()
|
||||
|
||||
fun importCombinedList(context: Context, uri: Uri): Result<Unit> {
|
||||
return ExternalContentUtils.readFromUri(context, uri,2048) { src ->
|
||||
return ExternalContentUtils.readFromUri(context, uri,6_192_000) { src ->
|
||||
var isFirstLine = true
|
||||
src.forEachLine { line ->
|
||||
if (isFirstLine) {
|
||||
|
||||
@@ -22,10 +22,8 @@ import dev.patrickgold.florisboard.ime.keyboard.CaseSelector
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.keyboard.VariationSelector
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.AutoTextKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.BasicTextKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.MultiTextKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.composing.*
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -55,6 +53,12 @@ class AssetManager private constructor(val applicationContext: Context) {
|
||||
subclass(MultiTextKeyData::class, MultiTextKeyData.serializer())
|
||||
default { BasicTextKeyData.serializer() }
|
||||
}
|
||||
polymorphic(Composer::class) {
|
||||
subclass(Appender::class, Appender.serializer())
|
||||
subclass(HangulUnicode::class, HangulUnicode.serializer())
|
||||
subclass(WithRules::class, WithRules.serializer())
|
||||
default { Appender.serializer() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ abstract class KeyboardView : View, ThemeManager.OnThemeUpdatedListener {
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
init {
|
||||
layoutDirection = LAYOUT_DIRECTION_LTR
|
||||
mainScope.launch {
|
||||
for (event in touchEventChannel) {
|
||||
if (!isActive) break
|
||||
|
||||
@@ -28,7 +28,8 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.Preferences
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.HINTS_DISABLED
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -39,8 +40,7 @@ import kotlinx.coroutines.MainScope
|
||||
*
|
||||
* @property florisboard Reference to instance of core class [FlorisBoard].
|
||||
* @property emojiKeyboardView Reference to the parent [EmojiKeyboardView].
|
||||
* @property data The data the current key represents. Is used to determine rendering and possible
|
||||
* behaviour when events occur.
|
||||
* @property key The current key. Is used to determine rendering and possible behaviour when events occur.
|
||||
*/
|
||||
@SuppressLint("ViewConstructor")
|
||||
class EmojiKeyView(
|
||||
@@ -104,8 +104,8 @@ class EmojiKeyView(
|
||||
(parent as RecyclerView)
|
||||
.requestDisallowInterceptTouchEvent(true)
|
||||
emojiKeyboardView.isScrollBlocked = true
|
||||
emojiKeyboardView.popupManager.show(key, KeyHintMode.DISABLED)
|
||||
emojiKeyboardView.popupManager.extend(key, KeyHintMode.DISABLED)
|
||||
emojiKeyboardView.popupManager.show(key, HINTS_DISABLED)
|
||||
emojiKeyboardView.popupManager.extend(key, HINTS_DISABLED)
|
||||
florisboard?.keyPressVibrate()
|
||||
florisboard?.keyPressSound()
|
||||
}, delayMillis.toLong())
|
||||
@@ -174,7 +174,7 @@ class EmojiKeyView(
|
||||
|
||||
canvas ?: return
|
||||
|
||||
if (key.computedPopups.isNotEmpty()) {
|
||||
if (key.computedPopups.getPopupKeys(HINTS_DISABLED).isNotEmpty()) {
|
||||
triangleDrawable?.draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
/**
|
||||
* Represents the root node to a n-gram tree.
|
||||
*/
|
||||
open class NgramTree(
|
||||
sameOrderChildren: MutableList<NgramNode> = mutableListOf(),
|
||||
higherOrderChildren: MutableList<NgramNode> = mutableListOf()
|
||||
) : NgramNode(0, '?', -1, sameOrderChildren, higherOrderChildren)
|
||||
|
||||
/**
|
||||
* A node of a n-gram tree, which holds the character it represents, the corresponding frequency,
|
||||
* a pre-computed string representing all parent characters and the current one as well as child
|
||||
* nodes, one for the same order n-gram nodes and one for the higher order n-gram nodes.
|
||||
*/
|
||||
open class NgramNode(
|
||||
val order: Int,
|
||||
val char: Char,
|
||||
val freq: Int,
|
||||
val sameOrderChildren: MutableList<NgramNode> = mutableListOf(),
|
||||
val higherOrderChildren: MutableList<NgramNode> = mutableListOf()
|
||||
) {
|
||||
companion object {
|
||||
const val FREQ_CHARACTER = -1
|
||||
const val FREQ_WORD_MIN = 0
|
||||
const val FREQ_WORD_MAX = 255
|
||||
const val FREQ_WORD_FILLER = -2
|
||||
const val FREQ_IS_POSSIBLY_OFFENSIVE = 0
|
||||
}
|
||||
|
||||
val isCharacter: Boolean
|
||||
get() = freq == FREQ_CHARACTER
|
||||
|
||||
val isWord: Boolean
|
||||
get() = freq in FREQ_WORD_MIN..FREQ_WORD_MAX
|
||||
|
||||
val isWordFiller: Boolean
|
||||
get() = freq == FREQ_WORD_FILLER
|
||||
|
||||
val isPossiblyOffensive: Boolean
|
||||
get() = freq == FREQ_IS_POSSIBLY_OFFENSIVE
|
||||
|
||||
fun findWord(word: String): NgramNode? {
|
||||
var currentNode = this
|
||||
for ((pos, char) in word.withIndex()) {
|
||||
val childNode = if (pos == 0) {
|
||||
currentNode.higherOrderChildren.find { it.char == char }
|
||||
} else {
|
||||
currentNode.sameOrderChildren.find { it.char == char }
|
||||
}
|
||||
if (childNode != null) {
|
||||
currentNode = childNode
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return if (currentNode.isWord || currentNode.isWordFiller) {
|
||||
currentNode
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows to search for a given [input] word with a given [maxEditDistance] and
|
||||
* adds all matches in the trie to the [list].
|
||||
*/
|
||||
fun listSimilarWords(
|
||||
input: String,
|
||||
list: StagedSuggestionList<String, Int>,
|
||||
word: StringBuilder,
|
||||
allowPossiblyOffensive: Boolean,
|
||||
maxEditDistance: Int,
|
||||
deletionCost: Int = 0,
|
||||
insertionCost: Int = 0,
|
||||
substitutionCost: Int = 0,
|
||||
pos: Int = -1
|
||||
) {
|
||||
if (pos > -1) {
|
||||
word.append(char)
|
||||
}
|
||||
val costSum = deletionCost + insertionCost + substitutionCost
|
||||
if (pos > -1 && (pos + 1 == input.length) && isWord && ((isPossiblyOffensive && allowPossiblyOffensive)
|
||||
|| !isPossiblyOffensive)) {
|
||||
// Using shift right instead of divide by 2^(costSum) as it is mathematically the
|
||||
// same but faster.
|
||||
if (list.canAdd(freq shr costSum)) {
|
||||
list.add(word.toString(), freq shr costSum)
|
||||
}
|
||||
}
|
||||
if (pos <= -1) {
|
||||
for (childNode in higherOrderChildren) {
|
||||
childNode.listSimilarWords(
|
||||
input, list, word, allowPossiblyOffensive, maxEditDistance, 0, 0, 0, 0
|
||||
)
|
||||
}
|
||||
} else if (maxEditDistance == costSum) {
|
||||
if (pos + 1 < input.length) {
|
||||
sameOrderChildren.find { it.char == input[pos + 1] }?.listSimilarWords(
|
||||
input, list, word, allowPossiblyOffensive, maxEditDistance,
|
||||
deletionCost, insertionCost, substitutionCost, pos + 1
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Delete
|
||||
if (pos + 2 < input.length) {
|
||||
sameOrderChildren.find { it.char == input[pos + 2] }?.listSimilarWords(
|
||||
input, list, word, allowPossiblyOffensive, maxEditDistance,
|
||||
deletionCost + 1, insertionCost, substitutionCost, pos + 2
|
||||
)
|
||||
}
|
||||
for (childNode in sameOrderChildren) {
|
||||
if (pos + 1 < input.length && childNode.char == input[pos + 1]) {
|
||||
childNode.listSimilarWords(
|
||||
input, list, word, allowPossiblyOffensive, maxEditDistance,
|
||||
deletionCost, insertionCost, substitutionCost, pos + 1
|
||||
)
|
||||
} else {
|
||||
// Insert
|
||||
childNode.listSimilarWords(
|
||||
input, list, word, allowPossiblyOffensive, maxEditDistance,
|
||||
deletionCost, insertionCost + 1, substitutionCost, pos
|
||||
)
|
||||
if (pos + 1 < input.length) {
|
||||
// Substitute
|
||||
childNode.listSimilarWords(
|
||||
input, list, word, allowPossiblyOffensive, maxEditDistance,
|
||||
deletionCost, insertionCost, substitutionCost + 1, pos + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pos > -1) {
|
||||
word.deleteAt(word.lastIndex)
|
||||
}
|
||||
}
|
||||
|
||||
fun listAllSameOrderWords(list: StagedSuggestionList<String, Int>, word: StringBuilder, allowPossiblyOffensive: Boolean) {
|
||||
word.append(char)
|
||||
if (isWord && ((isPossiblyOffensive && allowPossiblyOffensive) || !isPossiblyOffensive)) {
|
||||
if (list.canAdd(freq)) {
|
||||
list.add(word.toString(), freq)
|
||||
}
|
||||
}
|
||||
for (childNode in sameOrderChildren) {
|
||||
childNode.listAllSameOrderWords(list, word, allowPossiblyOffensive)
|
||||
}
|
||||
word.deleteAt(word.lastIndex)
|
||||
}
|
||||
}
|
||||
|
||||
open class FlorisLanguageModel(
|
||||
initTreeObj: NgramTree? = null
|
||||
) : LanguageModel<String, Int> {
|
||||
protected val ngramTree: NgramTree = initTreeObj ?: NgramTree()
|
||||
|
||||
override fun getNgram(vararg tokens: String): Ngram<String, Int> {
|
||||
val ngramOut = getNgramOrNull(*tokens)
|
||||
if (ngramOut != null) {
|
||||
return ngramOut
|
||||
} else {
|
||||
throw NullPointerException("No n-gram found matching the given tokens: $tokens")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNgram(ngram: Ngram<String, Int>): Ngram<String, Int> {
|
||||
val ngramOut = getNgramOrNull(ngram)
|
||||
if (ngramOut != null) {
|
||||
return ngramOut
|
||||
} else {
|
||||
throw NullPointerException("No n-gram found matching the given ngram: $ngram")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNgramOrNull(vararg tokens: String): Ngram<String, Int>? {
|
||||
var currentNode: NgramNode = ngramTree
|
||||
for (token in tokens) {
|
||||
val childNode = currentNode.findWord(token)
|
||||
if (childNode != null) {
|
||||
currentNode = childNode
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return Ngram(tokens.toList().map { Token(it) }, currentNode.freq)
|
||||
}
|
||||
|
||||
override fun getNgramOrNull(ngram: Ngram<String, Int>): Ngram<String, Int>? {
|
||||
return getNgramOrNull(*ngram.tokens.toStringList().toTypedArray())
|
||||
}
|
||||
|
||||
override fun hasNgram(ngram: Ngram<String, Int>, doMatchFreq: Boolean): Boolean {
|
||||
val result = getNgramOrNull(ngram)
|
||||
return if (result != null) {
|
||||
if (doMatchFreq) {
|
||||
ngram.freq == result.freq
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun matchAllNgrams(
|
||||
ngram: Ngram<String, Int>,
|
||||
maxEditDistance: Int,
|
||||
maxTokenCount: Int,
|
||||
allowPossiblyOffensive: Boolean
|
||||
): List<WeightedToken<String, Int>> {
|
||||
val ngramList = mutableListOf<WeightedToken<String, Int>>()
|
||||
var currentNode: NgramNode = ngramTree
|
||||
for ((t, token) in ngram.tokens.withIndex()) {
|
||||
val word = token.data
|
||||
if (t + 1 >= ngram.tokens.size) {
|
||||
if (word.isNotEmpty()) {
|
||||
// The last word is not complete, so find all possible words and sort
|
||||
val splitWord = mutableListOf<Char>()
|
||||
var splitNode: NgramNode? = currentNode
|
||||
for ((pos, char) in word.withIndex()) {
|
||||
val node = if (pos == 0) {
|
||||
splitNode?.higherOrderChildren?.find { it.char == char }
|
||||
} else {
|
||||
splitNode?.sameOrderChildren?.find { it.char == char }
|
||||
}
|
||||
splitWord.add(char)
|
||||
splitNode = node
|
||||
if (node == null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (splitNode != null) {
|
||||
// Input thus far is valid
|
||||
val wordNodes = StagedSuggestionList<String, Int>(maxTokenCount)
|
||||
val strBuilder = StringBuilder().append(word.substring(0, word.length - 1))
|
||||
splitNode.listAllSameOrderWords(wordNodes, strBuilder, allowPossiblyOffensive)
|
||||
ngramList.addAll(wordNodes)
|
||||
}
|
||||
if (ngramList.size < maxTokenCount) {
|
||||
val wordNodes = StagedSuggestionList<String, Int>(maxTokenCount)
|
||||
val strBuilder = StringBuilder()
|
||||
currentNode.listSimilarWords(word, wordNodes, strBuilder, allowPossiblyOffensive, maxEditDistance)
|
||||
ngramList.addAll(wordNodes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val node = currentNode.findWord(word)
|
||||
if (node == null) {
|
||||
return ngramList
|
||||
} else {
|
||||
currentNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
return ngramList
|
||||
}
|
||||
|
||||
fun toFlorisMutableLanguageModel(): FlorisMutableLanguageModel = FlorisMutableLanguageModel(ngramTree)
|
||||
}
|
||||
|
||||
open class FlorisMutableLanguageModel(
|
||||
initTreeObj: NgramTree? = null
|
||||
) : MutableLanguageModel<String, Int>, FlorisLanguageModel(initTreeObj) {
|
||||
override fun deleteNgram(ngram: Ngram<String, Int>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun insertNgram(ngram: Ngram<String, Int>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun updateNgram(ngram: Ngram<String, Int>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
fun toFlorisLanguageModel(): FlorisLanguageModel = FlorisLanguageModel(ngramTree)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
/**
|
||||
* Abstract interface for a language model. Can house any n-grams with a minimum order of one.
|
||||
*/
|
||||
interface LanguageModel<T : Any, F : Comparable<F>> {
|
||||
/**
|
||||
* Tries to get the n-gram for the passed [tokens]. Throws a NPE if no match could be found.
|
||||
*/
|
||||
@Throws(NullPointerException::class)
|
||||
fun getNgram(vararg tokens: T): Ngram<T, F>
|
||||
|
||||
/**
|
||||
* Tries to get the n-gram for the passed [ngram], whereas the frequency is ignored while
|
||||
* searching. Throws a NPE if no match could be found.
|
||||
*/
|
||||
@Throws(NullPointerException::class)
|
||||
fun getNgram(ngram: Ngram<T, F>): Ngram<T, F>
|
||||
|
||||
/**
|
||||
* Tries to get the n-gram for the passed [tokens]. Returns null if no match could be found.
|
||||
*/
|
||||
fun getNgramOrNull(vararg tokens: T): Ngram<T, F>?
|
||||
|
||||
/**
|
||||
* Tries to get the n-gram for the passed [ngram], whereas the frequency is ignored while
|
||||
* searching. Returns null if no match could be found.
|
||||
*/
|
||||
fun getNgramOrNull(ngram: Ngram<T, F>): Ngram<T, F>?
|
||||
|
||||
/**
|
||||
* Checks if a given [ngram] exists within this model. If [doMatchFreq] is set to true, the
|
||||
* frequency is also matched.
|
||||
*/
|
||||
fun hasNgram(ngram: Ngram<T, F>, doMatchFreq: Boolean = false): Boolean
|
||||
|
||||
/**
|
||||
* Matches all n-grams which match the given [ngram], whereas the last item in the n-gram is
|
||||
* is used to search for predictions.
|
||||
*/
|
||||
fun matchAllNgrams(
|
||||
ngram: Ngram<T, F>,
|
||||
maxEditDistance: Int,
|
||||
maxTokenCount: Int,
|
||||
allowPossiblyOffensive: Boolean
|
||||
): List<WeightedToken<T, F>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable version of [LanguageModel].
|
||||
*/
|
||||
interface MutableLanguageModel<T : Any, F : Comparable<F>> : LanguageModel<T, F> {
|
||||
fun deleteNgram(ngram: Ngram<T, F>)
|
||||
|
||||
fun insertNgram(ngram: Ngram<T, F>)
|
||||
|
||||
fun updateNgram(ngram: Ngram<T, F>)
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
/**
|
||||
* Abstract interface representing a n-gram of tokens. Each n-gram instance can be assigned a
|
||||
* unique frequency [freq].
|
||||
*/
|
||||
open class Ngram<T : Any, F : Comparable<F>>(_tokens: List<Token<T>>, _freq: F) {
|
||||
companion object {
|
||||
/** Constant order value for unigrams. */
|
||||
const val ORDER_UNIGRAM: Int = 1
|
||||
|
||||
/** Constant order value for bigrams. */
|
||||
const val ORDER_BIGRAM: Int = 2
|
||||
|
||||
/** Constant order value for trigrams. */
|
||||
const val ORDER_TRIGRAM: Int = 3
|
||||
}
|
||||
|
||||
init {
|
||||
if (_tokens.size < ORDER_UNIGRAM) {
|
||||
throw Exception("A n-gram must contain at least 1 token!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of tokens for this n-gram. The length of this list is guaranteed to be matching
|
||||
* [order].
|
||||
*/
|
||||
val tokens: List<Token<T>> = _tokens
|
||||
|
||||
/**
|
||||
* The frequency value of this n-gram.
|
||||
*/
|
||||
val freq: F = _freq
|
||||
|
||||
/**
|
||||
* The order of this n-gram (1, 2, 3, ...).
|
||||
*/
|
||||
val order: Int
|
||||
get() = tokens.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract interface representing a token used in [Ngram].
|
||||
*/
|
||||
open class Token<T : Any>(_data: T) {
|
||||
/**
|
||||
* The data of this token.
|
||||
*/
|
||||
val data: T = _data
|
||||
|
||||
override fun toString(): String {
|
||||
return "Token(\"$data\")"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return data.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Token<*>
|
||||
|
||||
if (data != other.data) return false
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [Token] but allows to add a frequency value [freq].
|
||||
*/
|
||||
open class WeightedToken<T : Any, F : Comparable<F>>(_data: T, _freq: F) : Token<T>(_data) {
|
||||
/**
|
||||
* The frequency of this weighed token.
|
||||
*/
|
||||
val freq: F = _freq
|
||||
|
||||
override fun toString(): String {
|
||||
return "WeightedToken(\"$data\", $freq)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return data.hashCode() + 31 * freq.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as WeightedToken<*, *>
|
||||
|
||||
if (data != other.data || freq != other.freq) return false
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of tokens carrying [CharSequence] data to a list of [CharSequence].
|
||||
*/
|
||||
fun List<Token<CharSequence>>.toCharSequenceList(): List<CharSequence> {
|
||||
return this.map { it.data }
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of tokens carrying [String] data to a list of [String].
|
||||
*/
|
||||
fun List<Token<String>>.toStringList(): List<String> {
|
||||
return this.map { it.data }
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
class StagedSuggestionList<T : Any, F : Comparable<F>>(
|
||||
private val maxSize: Int
|
||||
) : Collection<WeightedToken<T, F>> {
|
||||
private val internalArray: Array<WeightedToken<T, F>?> = Array(maxSize) { null }
|
||||
private var internalSize: Int = 0
|
||||
|
||||
override val size: Int
|
||||
get() = internalSize
|
||||
|
||||
fun add(token: T, freq: F): Boolean {
|
||||
if (internalSize < maxSize) {
|
||||
internalArray[internalSize++] = WeightedToken(token, freq)
|
||||
internalArray.sortByDescending { it?.freq }
|
||||
return true
|
||||
} else {
|
||||
if (internalArray.last()!!.freq < freq) {
|
||||
internalArray[internalArray.lastIndex] = WeightedToken(token, freq)
|
||||
internalArray.sortByDescending { it?.freq }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun canAdd(freq: F): Boolean {
|
||||
return internalSize < maxSize || internalArray.last()!!.freq < freq
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
for (n in internalArray.indices) {
|
||||
internalArray[n] = null
|
||||
}
|
||||
internalSize = 0
|
||||
}
|
||||
|
||||
override fun contains(element: WeightedToken<T, F>): Boolean = internalArray.contains(element)
|
||||
|
||||
override fun containsAll(elements: Collection<WeightedToken<T, F>>): Boolean {
|
||||
elements.forEach { if (!contains(it)) return false }
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(IndexOutOfBoundsException::class)
|
||||
operator fun get(index: Int): WeightedToken<T, F> {
|
||||
val element = getOrNull(index)
|
||||
if (element == null) {
|
||||
throw IndexOutOfBoundsException("The specified index $index is not within the bounds of this list!")
|
||||
} else {
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrNull(index: Int): WeightedToken<T, F>? {
|
||||
return internalArray.getOrNull(index)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean = internalSize <= 0
|
||||
|
||||
override fun iterator(): Iterator<WeightedToken<T, F>> {
|
||||
return StagedIterator(this)
|
||||
}
|
||||
|
||||
class StagedIterator<T : Any, F : Comparable<F>> internal constructor (
|
||||
private val stagedSuggestionList: StagedSuggestionList<T, F>
|
||||
) : Iterator<WeightedToken<T, F>> {
|
||||
var index = 0
|
||||
|
||||
override fun next(): WeightedToken<T, F> = stagedSuggestionList[index++]
|
||||
|
||||
override fun hasNext(): Boolean = stagedSuggestionList.getOrNull(index) != null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
import dev.patrickgold.florisboard.ime.core.NativeInstanceWrapper
|
||||
import dev.patrickgold.florisboard.ime.core.NativePtr
|
||||
|
||||
@JvmInline
|
||||
value class SuggestionList private constructor(
|
||||
private val _nativePtr: NativePtr
|
||||
) : Collection<String>, NativeInstanceWrapper {
|
||||
companion object {
|
||||
fun new(maxSize: Int): SuggestionList {
|
||||
val nativePtr = nativeInitialize(maxSize)
|
||||
return SuggestionList(nativePtr)
|
||||
}
|
||||
|
||||
external fun nativeInitialize(maxSize: Int): NativePtr
|
||||
external fun nativeDispose(nativePtr: NativePtr)
|
||||
|
||||
external fun nativeAdd(nativePtr: NativePtr, word: Word, freq: Freq): Boolean
|
||||
external fun nativeClear(nativePtr: NativePtr)
|
||||
external fun nativeContains(nativePtr: NativePtr, element: Word): Boolean
|
||||
external fun nativeGetOrNull(nativePtr: NativePtr, index: Int): Word?
|
||||
external fun nativeGetIsPrimaryTokenAutoInsert(nativePtr: NativePtr): Boolean
|
||||
external fun nativeSetIsPrimaryTokenAutoInsert(nativePtr: NativePtr, v: Boolean)
|
||||
external fun nativeSize(nativePtr: NativePtr): Int
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = nativeSize(_nativePtr)
|
||||
|
||||
fun add(word: Word, freq: Freq): Boolean {
|
||||
return nativeAdd(_nativePtr, word, freq)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
nativeClear(_nativePtr)
|
||||
}
|
||||
|
||||
override fun contains(element: Word): Boolean {
|
||||
return nativeContains(_nativePtr, element)
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<Word>): Boolean {
|
||||
elements.forEach { if (!contains(it)) return false }
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(IndexOutOfBoundsException::class)
|
||||
operator fun get(index: Int): Word {
|
||||
val element = getOrNull(index)
|
||||
if (element == null) {
|
||||
throw IndexOutOfBoundsException("The specified index $index is not within the bounds of this list!")
|
||||
} else {
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrNull(index: Int): Word? {
|
||||
return nativeGetOrNull(_nativePtr, index)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean = size <= 0
|
||||
|
||||
val isPrimaryTokenAutoInsert: Boolean
|
||||
get() = nativeGetIsPrimaryTokenAutoInsert(_nativePtr)
|
||||
|
||||
override fun iterator(): Iterator<Word> {
|
||||
return SuggestionListIterator(this)
|
||||
}
|
||||
|
||||
override fun nativePtr(): NativePtr {
|
||||
return _nativePtr
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
nativeDispose(_nativePtr)
|
||||
}
|
||||
|
||||
class SuggestionListIterator internal constructor (
|
||||
private val suggestionList: SuggestionList
|
||||
) : Iterator<Word> {
|
||||
var index = 0
|
||||
|
||||
override fun next(): Word = suggestionList[index++]
|
||||
|
||||
override fun hasNext(): Boolean = suggestionList.getOrNull(index) != null
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
class TextProcessor {
|
||||
data class Word(
|
||||
val word: String,
|
||||
val isPossiblyOffensive: Boolean = false
|
||||
)
|
||||
}
|
||||
typealias Word = String
|
||||
typealias Freq = Int
|
||||
|
||||
const val NATIVE_NULLPTR = 0
|
||||
@@ -18,6 +18,7 @@ package dev.patrickgold.florisboard.ime.onehanded
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Resources
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.ImageButton
|
||||
@@ -96,8 +97,7 @@ class OneHandedPanel : LinearLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val florisboard = florisboard ?: return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
val width = (florisboard.inputView?.measuredWidth ?: 0) *
|
||||
val width = (Resources.getSystem().displayMetrics.widthPixels) *
|
||||
((100 - prefs.keyboard.oneHandedModeScaleFactor) / 100.0f)
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(width.toInt(), MeasureSpec.EXACTLY), heightMeasureSpec)
|
||||
}
|
||||
|
||||
@@ -82,16 +82,19 @@ class PopupManager<V : View>(
|
||||
* Helper function to create a element for the extended popup and preconfigure it.
|
||||
*
|
||||
* @param key Reference to the key currently controlling the popup.
|
||||
* @param keyHintConfiguration The key hint configuration to use.
|
||||
* @param adjustedIndex The index of the key in the key data popup array.
|
||||
* @return A preconfigured extended popup element.
|
||||
*/
|
||||
private fun createElement(
|
||||
key: Key,
|
||||
keyHintConfiguration: KeyHintConfiguration,
|
||||
adjustedIndex: Int
|
||||
): PopupExtendedView.Element {
|
||||
return when (key) {
|
||||
is TextKey -> {
|
||||
when (key.computedPopups[adjustedIndex].code) {
|
||||
val popupKey = key.computedPopups.getPopupKeys(keyHintConfiguration)[adjustedIndex]
|
||||
when (popupKey.code) {
|
||||
KeyCode.SETTINGS -> {
|
||||
getDrawable(keyboardView.context, R.drawable.ic_settings)?.let {
|
||||
PopupExtendedView.Element.Icon(it, adjustedIndex)
|
||||
@@ -114,7 +117,7 @@ class PopupManager<V : View>(
|
||||
}
|
||||
KeyCode.URI_COMPONENT_TLD -> {
|
||||
PopupExtendedView.Element.Tld(
|
||||
key.computedPopups[adjustedIndex].asString(isForDisplay = true), adjustedIndex
|
||||
popupKey.asString(isForDisplay = true), adjustedIndex
|
||||
)
|
||||
}
|
||||
KeyCode.TOGGLE_ONE_HANDED_MODE_LEFT,
|
||||
@@ -125,14 +128,15 @@ class PopupManager<V : View>(
|
||||
}
|
||||
else -> {
|
||||
PopupExtendedView.Element.Label(
|
||||
key.computedPopups[adjustedIndex].asString(isForDisplay = true), adjustedIndex
|
||||
popupKey.asString(isForDisplay = true), adjustedIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is EmojiKey -> {
|
||||
val popupKey = key.computedPopups.getPopupKeys(keyHintConfiguration)[adjustedIndex]
|
||||
PopupExtendedView.Element.Label(
|
||||
key.computedPopups[adjustedIndex].asString(isForDisplay = true), adjustedIndex
|
||||
popupKey.asString(isForDisplay = true), adjustedIndex
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
@@ -141,6 +145,14 @@ class PopupManager<V : View>(
|
||||
}
|
||||
}
|
||||
|
||||
fun isSuitableForPopups(key: Key): Boolean {
|
||||
return if (key is TextKey) {
|
||||
key.computedData.code > KeyCode.SPACE && key.computedData.code != KeyCode.MULTIPLE_CODE_POINTS
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates all attributes required by both the normal and the extended popup, regardless of
|
||||
* the passed [key]'s code.
|
||||
@@ -179,11 +191,10 @@ class PopupManager<V : View>(
|
||||
* key code is equal to or less than [KeyCode.SPACE].
|
||||
*
|
||||
* @param key Reference to the key currently controlling the popup.
|
||||
* @param keyHintConfiguration The key hint configuration to use.
|
||||
*/
|
||||
fun show(key: Key, keyHintMode: KeyHintMode) {
|
||||
if (key is TextKey && key.computedData.code <= KeyCode.SPACE && key.computedData.code != KeyCode.MULTIPLE_CODE_POINTS) {
|
||||
return
|
||||
}
|
||||
fun show(key: Key, keyHintConfiguration: KeyHintConfiguration) {
|
||||
if (!isSuitableForPopups(key)) return
|
||||
|
||||
calc(key)
|
||||
|
||||
@@ -200,8 +211,8 @@ class PopupManager<V : View>(
|
||||
}
|
||||
labelTextSize = keyPopupTextSize
|
||||
shouldIndicateExtendedPopups = when (key) {
|
||||
is TextKey -> key.computedPopups.size(keyHintMode) > 0
|
||||
is EmojiKey -> key.computedPopups.isNotEmpty()
|
||||
is TextKey -> key.computedPopups.getPopupKeys(keyHintConfiguration).isNotEmpty()
|
||||
is EmojiKey -> key.computedPopups.getPopupKeys(keyHintConfiguration).isNotEmpty()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -228,9 +239,10 @@ class PopupManager<V : View>(
|
||||
* K K ... K K
|
||||
*
|
||||
* @param key Reference to the key currently controlling the popup.
|
||||
* @param keyHintConfiguration The key hint configuration to use.
|
||||
*/
|
||||
fun extend(key: Key, keyHintMode: KeyHintMode) {
|
||||
if (key is TextKey && key.computedData.code <= KeyCode.SPACE && key.computedData.code != KeyCode.MULTIPLE_CODE_POINTS
|
||||
fun extend(key: Key, keyHintConfiguration: KeyHintConfiguration) {
|
||||
if (key is TextKey && !isSuitableForPopups(key)
|
||||
&& !exceptionsForKeyCodes.contains(key.computedData.code)) {
|
||||
return
|
||||
}
|
||||
@@ -245,8 +257,8 @@ class PopupManager<V : View>(
|
||||
|
||||
// Determine key counts for each row
|
||||
val n = when (key) {
|
||||
is TextKey -> key.computedPopups.size(keyHintMode)
|
||||
is EmojiKey -> key.computedPopups.size(keyHintMode)
|
||||
is TextKey -> key.computedPopups.getPopupKeys(keyHintConfiguration).size
|
||||
is EmojiKey -> key.computedPopups.getPopupKeys(keyHintConfiguration).size
|
||||
else -> 0
|
||||
}
|
||||
when {
|
||||
@@ -303,43 +315,38 @@ class PopupManager<V : View>(
|
||||
val uiIndices = IntRange(0, (n - 1).coerceAtLeast(0))
|
||||
if (key is TextKey) {
|
||||
popupIndices = IntArray(n) { 0 }
|
||||
when (keyHintMode) {
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
|
||||
key.computedPopups.main != null -> {
|
||||
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
if (key.computedPopups.hint != null) when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
|
||||
}
|
||||
}
|
||||
key.computedPopups.hint != null -> when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
|
||||
else -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
|
||||
val popupKeys = key.computedPopups.getPopupKeys(keyHintConfiguration)
|
||||
when (popupKeys.prioritized.size) {
|
||||
// only one key: use initial position
|
||||
1 -> {
|
||||
popupIndices[initUiIndex] = PopupKeys.FIRST_PRIORITIZED
|
||||
}
|
||||
// two keys: use initial position and one to the right if available, otherwise one to the left
|
||||
2 -> {
|
||||
popupIndices[initUiIndex] = PopupKeys.FIRST_PRIORITIZED
|
||||
when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupKeys.SECOND_PRIORITIZED
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupKeys.SECOND_PRIORITIZED
|
||||
}
|
||||
}
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> when {
|
||||
key.computedPopups.hint != null -> {
|
||||
popupIndices[initUiIndex] = PopupSet.HINT_INDEX
|
||||
if (key.computedPopups.main != null) when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.MAIN_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.MAIN_INDEX
|
||||
// two keys: use initial position and one to either sides if available
|
||||
// otherwise two to the right or two to the left with decreasing priority
|
||||
3 -> {
|
||||
popupIndices[initUiIndex] = PopupKeys.FIRST_PRIORITIZED
|
||||
when {
|
||||
initUiIndex + 1 < n && initUiIndex - 1 >= 0 -> {
|
||||
popupIndices[initUiIndex + 1] = PopupKeys.SECOND_PRIORITIZED
|
||||
popupIndices[initUiIndex - 1] = PopupKeys.THIRD_PRIORITIZED
|
||||
}
|
||||
initUiIndex + 2 < n -> {
|
||||
popupIndices[initUiIndex + 1] = PopupKeys.SECOND_PRIORITIZED
|
||||
popupIndices[initUiIndex + 2] = PopupKeys.THIRD_PRIORITIZED
|
||||
}
|
||||
initUiIndex - 2 >= 0 -> {
|
||||
popupIndices[initUiIndex - 1] = PopupKeys.SECOND_PRIORITIZED
|
||||
popupIndices[initUiIndex - 2] = PopupKeys.THIRD_PRIORITIZED
|
||||
}
|
||||
}
|
||||
key.computedPopups.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
}
|
||||
KeyHintMode.ENABLED_SMART_PRIORITY -> when {
|
||||
key.computedPopups.main != null -> {
|
||||
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
if (key.computedPopups.hint != null) when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
|
||||
}
|
||||
}
|
||||
key.computedPopups.hint != null -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
|
||||
}
|
||||
KeyHintMode.DISABLED -> when {
|
||||
key.computedPopups.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
}
|
||||
}
|
||||
var offset = 0
|
||||
@@ -360,7 +367,7 @@ class PopupManager<V : View>(
|
||||
for (uiIndex in uiIndices) {
|
||||
val rowIndex = if (uiIndex < row1count && row1count > 0) { 1 } else { 0 }
|
||||
popupViewExt.properties.elements[rowIndex].add(
|
||||
createElement(key, popupIndices[uiIndex])
|
||||
createElement(key, keyHintConfiguration, popupIndices[uiIndex])
|
||||
)
|
||||
}
|
||||
|
||||
@@ -468,17 +475,18 @@ class PopupManager<V : View>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the [KeyData] of the currently active key. May be either the key of the popup preview
|
||||
* Gets the [TextKeyData] of the currently active key. May be either the key of the popup preview
|
||||
* or one of the keys in extended popup, if shown. Returns null if [key] is not a subclass of [TextKey].
|
||||
*
|
||||
* @param key Reference to the key currently controlling the popup.
|
||||
* @return The [KeyData] object of the currently active key or null.
|
||||
* @param keyHintConfiguration The key hint configuration to be used.
|
||||
* @return The [TextKeyData] object of the currently active key or null.
|
||||
*/
|
||||
fun getActiveKeyData(key: Key): TextKeyData? {
|
||||
fun getActiveKeyData(key: Key, keyHintConfiguration: KeyHintConfiguration): TextKeyData? {
|
||||
return if (key is TextKey) {
|
||||
val element = popupViewExt.properties.getElementOrNull()
|
||||
if (element != null) {
|
||||
key.computedPopups.getOrNull(element.adjustedIndex) ?: key.computedData
|
||||
key.computedPopups.getPopupKeys(keyHintConfiguration).getOrNull(element.adjustedIndex) ?: key.computedData
|
||||
} else {
|
||||
key.computedData
|
||||
}
|
||||
@@ -498,7 +506,7 @@ class PopupManager<V : View>(
|
||||
return if (key is EmojiKey) {
|
||||
val element = popupViewExt.properties.getElementOrNull()
|
||||
if (element != null) {
|
||||
key.computedPopups.getOrNull(element.adjustedIndex) ?: key.computedData
|
||||
key.computedPopups.getPopupKeys(HINTS_DISABLED).getOrNull(element.adjustedIndex) ?: key.computedData
|
||||
} else {
|
||||
key.computedData
|
||||
}
|
||||
|
||||
@@ -17,43 +17,215 @@
|
||||
package dev.patrickgold.florisboard.ime.popup
|
||||
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.BasicTextKeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextComputingEvaluator
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* A popup set for a single key. This set describes, if the key has a [hint] character,
|
||||
* a [main] character and other [relevant] popups.
|
||||
* A popup set for a single key. This set describes, if the key has a [main] character and other [relevant] popups.
|
||||
*
|
||||
* Note, that a hint character should **never** be set in a json extended popup file, rather it
|
||||
* Note that a hint character cannot and should not be set in a json extended popup file, rather it
|
||||
* should only be dynamically set by the LayoutManager.
|
||||
*
|
||||
* The order in which these defined popups will be shown depends on the current [KeyHintMode],
|
||||
* al well as the calculations made by the KeyPopupManager.
|
||||
*
|
||||
* The popup set can be accessed like an array with the addition that negative indexes defined
|
||||
* within this companion object are allowed (as long as the corresponding [hint] or [main]
|
||||
* character is *not* null).
|
||||
* The order in which these defined popups will be shown depends on the current [KeyHintConfiguration].
|
||||
*/
|
||||
@Serializable
|
||||
open class PopupSet<T : KeyData>(
|
||||
open val hint: T? = null,
|
||||
open val main: T? = null,
|
||||
open val relevant: List<T> = listOf()
|
||||
) {
|
||||
private val popupKeys: PopupKeys<T> by lazy {
|
||||
PopupKeys(null, listOfNotNull(main), relevant)
|
||||
}
|
||||
|
||||
open fun getPopupKeys(keyHintConfiguration: KeyHintConfiguration): PopupKeys<T> {
|
||||
return popupKeys
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MutablePopupSet<T : KeyData>(
|
||||
override var main: T? = null,
|
||||
override val relevant: ArrayList<T> = arrayListOf(),
|
||||
var symbolHint: T? = null,
|
||||
var numberHint: T? = null,
|
||||
private val symbolPopups: ArrayList<T> = arrayListOf(),
|
||||
private val numberPopups: ArrayList<T> = arrayListOf(),
|
||||
private val configCache: MutableMap<KeyHintConfiguration, PopupKeys<T>> = mutableMapOf()
|
||||
) : PopupSet<T>(main, relevant) {
|
||||
|
||||
fun clear() {
|
||||
symbolHint = null
|
||||
numberHint = null
|
||||
main = null
|
||||
relevant.clear()
|
||||
symbolPopups.clear()
|
||||
numberPopups.clear()
|
||||
configCache.clear()
|
||||
}
|
||||
|
||||
override fun getPopupKeys(keyHintConfiguration: KeyHintConfiguration): PopupKeys<T> {
|
||||
return configCache.getOrPut(keyHintConfiguration) {
|
||||
initPopupList(keyHintConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initPopupList(keyHintConfiguration: KeyHintConfiguration): PopupKeys<T> {
|
||||
val localMain = main
|
||||
val localRelevant = relevant
|
||||
val localSymbolHint = symbolHint
|
||||
val localNumberHint = numberHint
|
||||
if (localSymbolHint != null && keyHintConfiguration.symbolHintMode != KeyHintMode.DISABLED) {
|
||||
if (localNumberHint != null && keyHintConfiguration.numberHintMode != KeyHintMode.DISABLED) {
|
||||
val hintPopups = if (keyHintConfiguration.mergeHintPopups) { symbolPopups + numberPopups } else { listOf() }
|
||||
return when (keyHintConfiguration.symbolHintMode) {
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when (keyHintConfiguration.numberHintMode) {
|
||||
// when both hints are present in accent priority, always have a non-hint key first if possible
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
|
||||
localMain != null -> PopupKeys(localSymbolHint, listOf(localMain, localSymbolHint, localNumberHint), localRelevant + hintPopups)
|
||||
localRelevant.isNotEmpty() -> PopupKeys(localSymbolHint, listOf(localRelevant[0], localSymbolHint, localNumberHint), localRelevant.subList(1, localRelevant.size) + hintPopups)
|
||||
else -> PopupKeys(localSymbolHint, listOf(localSymbolHint, localNumberHint), hintPopups)
|
||||
}
|
||||
// hint priority of number hint wins and overrules accent priority of symbol hint
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> PopupKeys(localSymbolHint, listOfNotNull(localNumberHint, localMain, localSymbolHint), localRelevant + hintPopups)
|
||||
// due to smart priority of number hint, main wins if it exists, otherwise number hint overrules accent priority of symbol hint
|
||||
else -> PopupKeys(localSymbolHint, listOfNotNull(localMain, localNumberHint, localSymbolHint), localRelevant + hintPopups)
|
||||
}
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> when (keyHintConfiguration.symbolHintMode) {
|
||||
// when both hints are present in hint priority, symbol hint wins
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> PopupKeys(localSymbolHint, listOfNotNull(localSymbolHint, localNumberHint, localMain), localRelevant + hintPopups)
|
||||
// hint priority of symbol hint wins, and overrules potential accent priority of number hint
|
||||
else -> PopupKeys(localSymbolHint, listOfNotNull(localSymbolHint, localMain, localNumberHint), localRelevant + hintPopups)
|
||||
}
|
||||
else -> when (keyHintConfiguration.numberHintMode) {
|
||||
// smart priority of symbol hint wins, and overrules accent priority of number hint
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> PopupKeys(localSymbolHint, listOfNotNull(localMain, localSymbolHint, localNumberHint), localRelevant + hintPopups)
|
||||
// hint priority of number hint wins
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> PopupKeys(localSymbolHint, listOfNotNull(localNumberHint, localMain, localSymbolHint), localRelevant + hintPopups)
|
||||
// when both hints are in smart priority, always have main first if possible
|
||||
else -> PopupKeys(localSymbolHint, listOfNotNull(localMain, localSymbolHint, localNumberHint), localRelevant + hintPopups)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val hintPopups = if (keyHintConfiguration.mergeHintPopups) { symbolPopups } else { listOf() }
|
||||
return when (keyHintConfiguration.symbolHintMode) {
|
||||
// in accent priority, always show a non-hint key first if possible
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
|
||||
localMain != null -> PopupKeys(localSymbolHint, listOf(localMain, localSymbolHint), localRelevant + hintPopups)
|
||||
localRelevant.isNotEmpty() -> PopupKeys(localSymbolHint, listOf(localRelevant[0], localSymbolHint), localRelevant.subList(1, localRelevant.size) + hintPopups)
|
||||
else -> PopupKeys(localSymbolHint, listOf(localSymbolHint), hintPopups)
|
||||
}
|
||||
// in hint priority, always show hint first
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> PopupKeys(localSymbolHint, listOfNotNull(localSymbolHint, localMain), localRelevant + hintPopups)
|
||||
// in smart priority, show main first if possible
|
||||
else -> PopupKeys(localSymbolHint, listOfNotNull(localMain, localSymbolHint), localRelevant + hintPopups)
|
||||
}
|
||||
}
|
||||
} else if (localNumberHint != null && keyHintConfiguration.numberHintMode != KeyHintMode.DISABLED) {
|
||||
val hintPopups = if (keyHintConfiguration.mergeHintPopups) { numberPopups } else { listOf() }
|
||||
return when (keyHintConfiguration.numberHintMode) {
|
||||
// in accent priority, always show a non-hint key first if possible
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
|
||||
localMain != null -> PopupKeys(localNumberHint, listOf(localMain, localNumberHint), localRelevant + hintPopups)
|
||||
localRelevant.isNotEmpty() -> PopupKeys(localNumberHint, listOf(localRelevant[0], localNumberHint), localRelevant.subList(1, localRelevant.size) + hintPopups)
|
||||
else -> PopupKeys(localNumberHint, listOf(localNumberHint), hintPopups)
|
||||
}
|
||||
// in hint priority, always show hint first
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> PopupKeys(localNumberHint, listOfNotNull(localNumberHint, localMain), localRelevant + hintPopups)
|
||||
// in smart priority, show main first if possible
|
||||
else -> PopupKeys(localNumberHint, listOfNotNull(localMain, localNumberHint), localRelevant + hintPopups)
|
||||
}
|
||||
} else {
|
||||
// if no hints shall be shown, use main first if possible
|
||||
return PopupKeys(null, listOfNotNull(localMain), localRelevant)
|
||||
}
|
||||
}
|
||||
|
||||
fun merge(other: PopupSet<T>, evaluator: TextComputingEvaluator) {
|
||||
mergeInternal(other, evaluator, relevant, true)
|
||||
}
|
||||
|
||||
fun mergeSymbolHint(hintPopups: PopupSet<T>, evaluator: TextComputingEvaluator) {
|
||||
mergeInternal(hintPopups, evaluator, symbolPopups)
|
||||
}
|
||||
|
||||
fun mergeNumberHint(hintPopups: PopupSet<T>, evaluator: TextComputingEvaluator) {
|
||||
mergeInternal(hintPopups, evaluator, numberPopups)
|
||||
}
|
||||
|
||||
private fun mergeInternal(other: PopupSet<T>, evaluator: TextComputingEvaluator, targetList: MutableList<T>, useMain: Boolean = false) {
|
||||
other.relevant.forEach {
|
||||
val data = it.computeTextKeyData(evaluator) as? T
|
||||
if (data != null) {
|
||||
targetList.add(data)
|
||||
}
|
||||
}
|
||||
other.main?.let {
|
||||
val data = it.computeTextKeyData(evaluator) as? T
|
||||
if (data != null) {
|
||||
if (useMain && main == null) {
|
||||
main = data
|
||||
} else {
|
||||
targetList.add(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fully configured collection of popup keys. It contains a list of keys to be prioritized
|
||||
* during rendering (ordered by relevance descending) by showing those keys close to the
|
||||
* popup spawning point.
|
||||
*
|
||||
* The keys contain a separate [hint] key to ease rendering the hint label, but the hint, if
|
||||
* present, also occurs in the [prioritized] list.
|
||||
*
|
||||
* The popup keys can be accessed like an array with the addition that negative indexes defined
|
||||
* within this companion object are allowed (as long as the corresponding [prioritized] list
|
||||
* contains the corresponding amount of keys.
|
||||
*/
|
||||
class PopupKeys<T>(
|
||||
val hint: T?,
|
||||
val prioritized: List<T>,
|
||||
val other: List<T>
|
||||
) : Collection<T> {
|
||||
companion object {
|
||||
const val HINT_INDEX: Int = -2
|
||||
const val MAIN_INDEX: Int = -1
|
||||
const val FIRST_PRIORITIZED = -1
|
||||
const val SECOND_PRIORITIZED = -2
|
||||
const val THIRD_PRIORITIZED = -3
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = if (hint != null) { 1 } else { 0 } + if (main != null) { 1 } else { 0 } + relevant.size
|
||||
get() = prioritized.size + other.size
|
||||
|
||||
fun size(keyHintMode: KeyHintMode): Int {
|
||||
return if (keyHintMode == KeyHintMode.DISABLED && hint != null) {
|
||||
size - 1
|
||||
} else {
|
||||
size
|
||||
override fun contains(element: T): Boolean {
|
||||
return prioritized.contains(element) || other.contains(element)
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<T>): Boolean {
|
||||
return (prioritized + other).containsAll(elements)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return prioritized.isEmpty() && other.isEmpty()
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
return (prioritized + other).listIterator()
|
||||
}
|
||||
|
||||
fun getOrNull(index: Int): T? {
|
||||
if (index >= other.size || index < -prioritized.size) {
|
||||
return null
|
||||
}
|
||||
return when (index) {
|
||||
FIRST_PRIORITIZED -> prioritized[0]
|
||||
SECOND_PRIORITIZED -> prioritized[1]
|
||||
THIRD_PRIORITIZED -> prioritized[2]
|
||||
else -> other.getOrNull(index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,125 +233,10 @@ open class PopupSet<T : KeyData>(
|
||||
val item = getOrNull(index)
|
||||
if (item == null) {
|
||||
throw IndexOutOfBoundsException(
|
||||
"Specified index $index is not an valid entry in this PopupSet!"
|
||||
"Specified index $index is not an valid entry in this PopupKeys!"
|
||||
)
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrNull(index: Int): T? {
|
||||
if (index >= relevant.size || index < HINT_INDEX) {
|
||||
return null
|
||||
}
|
||||
return when (index) {
|
||||
HINT_INDEX -> hint
|
||||
MAIN_INDEX -> main
|
||||
else -> relevant.getOrNull(index)
|
||||
}
|
||||
}
|
||||
|
||||
override fun contains(element: T): Boolean {
|
||||
return hint == element || main == element || relevant.contains(element)
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<T>): Boolean {
|
||||
for (element in elements) {
|
||||
if (!contains(element)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
return PopupSetIterator(this)
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return size == 0
|
||||
}
|
||||
|
||||
class PopupSetIterator<T : KeyData> internal constructor (
|
||||
private val popupSet: PopupSet<T>
|
||||
) : Iterator<T> {
|
||||
var index = HINT_INDEX
|
||||
|
||||
override fun next(): T = popupSet[index++]
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (index == HINT_INDEX) {
|
||||
if (popupSet.getOrNull(index) != null) {
|
||||
return true
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
}
|
||||
if (index == MAIN_INDEX) {
|
||||
if (popupSet.getOrNull(index) != null) {
|
||||
return true
|
||||
} else {
|
||||
index++
|
||||
}
|
||||
}
|
||||
return popupSet.getOrNull(index) != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MutablePopupSet<T : KeyData>(
|
||||
override var hint: T? = null,
|
||||
override var main: T? = null,
|
||||
override val relevant: ArrayList<T> = arrayListOf()
|
||||
) : PopupSet<T>(hint, main, relevant) {
|
||||
fun clear() {
|
||||
hint = null
|
||||
main = null
|
||||
relevant.clear()
|
||||
}
|
||||
|
||||
fun merge(other: PopupSet<T>) {
|
||||
relevant.addAll(other.relevant)
|
||||
other.hint?.let {
|
||||
if (hint == null) {
|
||||
hint = it
|
||||
} else {
|
||||
relevant.add(it)
|
||||
}
|
||||
}
|
||||
other.main?.let {
|
||||
if (main == null) {
|
||||
main = it
|
||||
} else {
|
||||
relevant.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun merge(other: PopupSet<T>, evaluator: TextComputingEvaluator) {
|
||||
other.relevant.forEach {
|
||||
val data = it.computeTextKeyData(evaluator) as? T
|
||||
if (data != null) {
|
||||
relevant.add(data)
|
||||
}
|
||||
}
|
||||
other.hint?.let {
|
||||
if (hint == null) {
|
||||
hint = it
|
||||
} else {
|
||||
relevant.add(it)
|
||||
}
|
||||
}
|
||||
other.main?.let {
|
||||
val data = it.computeTextKeyData(evaluator) as? T
|
||||
if (data != null) {
|
||||
if (main == null) {
|
||||
main = data
|
||||
} else {
|
||||
relevant.add(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,10 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.debug.*
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.core.*
|
||||
import dev.patrickgold.florisboard.ime.dictionary.Dictionary
|
||||
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetManager
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetRef
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetSource
|
||||
import dev.patrickgold.florisboard.ime.nlp.Token
|
||||
import dev.patrickgold.florisboard.ime.nlp.toStringList
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.*
|
||||
@@ -42,7 +39,6 @@ import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.SmartbarView
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONArray
|
||||
import java.util.*
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
/**
|
||||
@@ -74,8 +70,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
lateinit var textKeyboardIconSet: TextKeyboardIconSet
|
||||
private set
|
||||
private var textViewGroup: LinearLayout? = null
|
||||
private val dictionaryManager: DictionaryManager = DictionaryManager.default()
|
||||
private var activeDictionary: Dictionary<String, Int>? = null
|
||||
private val dictionaryManager: DictionaryManager get() = DictionaryManager.default()
|
||||
val inputEventDispatcher: InputEventDispatcher = InputEventDispatcher.new(
|
||||
repeatableKeyCodes = intArrayOf(
|
||||
KeyCode.ARROW_DOWN,
|
||||
@@ -420,11 +415,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
override fun onSubtypeChanged(newSubtype: Subtype) {
|
||||
launch {
|
||||
if (activeEditorInstance.isComposingEnabled) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dictionaryManager.loadDictionary(AssetRef(AssetSource.Assets, "ime/dict/en.flict")).let {
|
||||
activeDictionary = it.getOrDefault(null)
|
||||
}
|
||||
}
|
||||
dictionaryManager.prepareDictionaries(newSubtype)
|
||||
}
|
||||
if (prefs.glide.enabled) {
|
||||
GlideTypingManager.getInstance().setWordData(newSubtype)
|
||||
@@ -447,28 +438,24 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
if (activeEditorInstance.isComposingEnabled && !inputEventDispatcher.isPressed(KeyCode.DELETE) && !isGlidePostEffect) {
|
||||
if (activeEditorInstance.shouldReevaluateComposingSuggestions) {
|
||||
activeEditorInstance.shouldReevaluateComposingSuggestions = false
|
||||
activeDictionary?.let {
|
||||
launch(Dispatchers.Default) {
|
||||
val startTime = System.nanoTime()
|
||||
val suggestions = queryUserDictionary(
|
||||
activeEditorInstance.cachedInput.currentWord.text,
|
||||
florisboard.activeSubtype.locale
|
||||
).toMutableList()
|
||||
suggestions.addAll(it.getTokenPredictions(
|
||||
precedingTokens = listOf(),
|
||||
currentToken = Token(activeEditorInstance.cachedInput.currentWord.text),
|
||||
maxSuggestionCount = 16,
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive
|
||||
).toStringList())
|
||||
if (BuildConfig.DEBUG) {
|
||||
val elapsed = (System.nanoTime() - startTime) / 1000.0
|
||||
flogInfo { "sugg fetch time: $elapsed us" }
|
||||
}
|
||||
launch(Dispatchers.Default) {
|
||||
val startTime = System.nanoTime()
|
||||
dictionaryManager.suggest(
|
||||
currentWord = activeEditorInstance.cachedInput.currentWord.text,
|
||||
preceidingWords = listOf(),
|
||||
subtype = florisboard.activeSubtype,
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive,
|
||||
maxSuggestionCount = 16
|
||||
) { suggestions ->
|
||||
withContext(Dispatchers.Main) {
|
||||
smartbarView?.setCandidateSuggestionWords(startTime, suggestions)
|
||||
smartbarView?.updateCandidateSuggestionCapsState()
|
||||
}
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
val elapsed = (System.nanoTime() - startTime) / 1000.0
|
||||
flogInfo { "sugg fetch time: $elapsed us" }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
smartbarView?.setCandidateSuggestionWords(System.nanoTime(), null)
|
||||
@@ -480,40 +467,6 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
|
||||
smartbarView?.onPrimaryClipChanged()
|
||||
}
|
||||
|
||||
private fun queryUserDictionary(word: String, locale: Locale): Set<String> {
|
||||
val florisDao = dictionaryManager.florisUserDictionaryDao()
|
||||
val systemDao = dictionaryManager.systemUserDictionaryDao()
|
||||
if (florisDao == null && systemDao == null) {
|
||||
return setOf()
|
||||
}
|
||||
val retList = mutableSetOf<String>()
|
||||
if (prefs.dictionary.enableFlorisUserDictionary) {
|
||||
florisDao?.query(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
retList.add(entry.word)
|
||||
}
|
||||
}
|
||||
florisDao?.queryShortcut(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
retList.add(entry.word)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prefs.dictionary.enableSystemUserDictionary) {
|
||||
systemDao?.query(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
retList.add(entry.word)
|
||||
}
|
||||
}
|
||||
systemDao?.queryShortcut(word, locale)?.let {
|
||||
for (entry in it) {
|
||||
retList.add(entry.word)
|
||||
}
|
||||
}
|
||||
}
|
||||
return retList
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current caps state according to the [EditorInstance.cursorCapsMode], while
|
||||
* respecting [capsLock] property and the correction.autoCapitalization preference.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.patrickgold.florisboard.ime.text.composing
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
interface Composer {
|
||||
val name: String
|
||||
val label: String
|
||||
val toRead: Int
|
||||
|
||||
fun getActions(s: String, c: Char): Pair<Int, String>
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("appender")
|
||||
class Appender : Composer {
|
||||
companion object {
|
||||
const val name = "appender"
|
||||
}
|
||||
override val name: String = Appender.name
|
||||
override val label: String = "Appender"
|
||||
override val toRead: Int = 0
|
||||
|
||||
override fun getActions(s: String, c: Char): Pair<Int, String> {
|
||||
return Pair(0, "$c")
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("with-rules")
|
||||
class WithRules(
|
||||
override val name: String,
|
||||
override val label: String,
|
||||
val rules: JsonObject
|
||||
) : Composer {
|
||||
override val toRead: Int = rules.keys.toList().sortedBy { it.length }.reversed()[0].length - 1
|
||||
|
||||
@Transient val ruleOrder: List<String> = rules.keys.toList().sortedBy { it.length }.reversed()
|
||||
@Transient val ruleMap: Map<String, String> = rules.entries.map { Pair(it.key, (it.value as JsonPrimitive).content) }.toMap()
|
||||
|
||||
override fun getActions(s: String, c: Char): Pair<Int, String> {
|
||||
val str = "${s}$c"
|
||||
for (key in ruleOrder) {
|
||||
if (str.endsWith(key)) {
|
||||
return Pair(key.length-1, ruleMap.getValue(key))
|
||||
}
|
||||
}
|
||||
return Pair(0, "$c")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package dev.patrickgold.florisboard.ime.text.composing
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("hangul-unicode")
|
||||
class HangulUnicode : Composer {
|
||||
override val name: String = "hangul-unicode"
|
||||
override val label: String = "Hangul Unicode"
|
||||
override val toRead: Int = 1
|
||||
|
||||
// Initial consonants, ordered for syllable creation
|
||||
private val initials = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"
|
||||
// Medial vowels, ordered for syllable creation
|
||||
private val medials = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"
|
||||
// Final consonants (including none), ordered for syllable creation
|
||||
private val finals = "_ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ"
|
||||
|
||||
private val medialComp = mapOf(
|
||||
'ㅗ' to listOfNotNull("ㅏㅐㅣ", "ㅘㅙㅚ"),
|
||||
'ㅜ' to listOfNotNull("ㅓㅔㅣ", "ㅝㅞㅟ"),
|
||||
'ㅡ' to listOfNotNull("ㅣ", "ㅢ"),
|
||||
)
|
||||
|
||||
private val finalComp = mapOf(
|
||||
'ㄱ' to listOfNotNull("ㅅ", "ㄳ"),
|
||||
'ㄴ' to listOfNotNull("ㅈㅎ", "ㄵㄶ"),
|
||||
'ㄹ' to listOfNotNull("ㄱㅁㅂㅅㅌㅍㅎ", "ㄺㄻㄼㄽㄾㄿㅀ"),
|
||||
'ㅂ' to listOfNotNull("ㅅ", "ㅄ"),
|
||||
)
|
||||
|
||||
private fun reverseComp(map: Map<Char, List<String>>): Map<Char, List<Char>> {
|
||||
val ret = mutableMapOf<Char, List<Char>>()
|
||||
for ((first, v) in map) {
|
||||
val (seconds, comps) = v
|
||||
for (i in seconds.indices) {
|
||||
ret[comps[i]] = listOf(first, seconds[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
private val finalCompRev = reverseComp(finalComp)
|
||||
private val medialCompRev = reverseComp(medialComp)
|
||||
|
||||
private fun syllable(ini: Int, med: Int, fin:Int): Char {
|
||||
return (ini*588 + med*28 + fin + 44032).toChar()
|
||||
}
|
||||
|
||||
private fun syllableBlocks(syllOrd: Int): List<Int> {
|
||||
val initial = (syllOrd-44032)/588
|
||||
val medial = (syllOrd-44032-initial*588)/28
|
||||
val fin = (syllOrd-44032)%28
|
||||
return listOf(initial, medial, fin)
|
||||
}
|
||||
|
||||
override fun getActions(s: String, c: Char): Pair<Int, String> {
|
||||
// s is "at least the last 1 character of what's currently here"
|
||||
if (s.isEmpty()) {
|
||||
return Pair(0, ""+c)
|
||||
}
|
||||
val lastChar = s.last()
|
||||
val lastOrd = lastChar.toInt()
|
||||
|
||||
if (lastChar in initials && c in medials) {
|
||||
return Pair(1, "${syllable(initials.indexOf(lastChar), medials.indexOf(c), 0)}")
|
||||
} else if (lastOrd in 44032..55203) { // syllable
|
||||
val (ini, med, fin) = syllableBlocks(lastOrd)
|
||||
|
||||
// underscore is a sentinel in the "finals" string
|
||||
if (c == '_')
|
||||
return Pair(0, ""+c)
|
||||
|
||||
// if there is no final and the new char is a final, merge
|
||||
if (fin == 0 && c in finals)
|
||||
return Pair(1, "${syllable(ini, med, finals.indexOf(c))}")
|
||||
|
||||
// if there is already a final but it is mergeable with the new char into a composed final, merge
|
||||
if ((finals[fin] in finalComp) && c in finalComp[finals[fin]]!![0]) {
|
||||
val tple = finalComp[finals[fin]]
|
||||
return Pair(1, "${syllable(ini, med, finals.indexOf(tple!![1][tple[0].indexOf(c)]))}")
|
||||
}
|
||||
|
||||
// if there is a simple final and the new char is a medial, split the old syllable
|
||||
if (fin != 0 && finals[fin] !in finalCompRev && c in medials)
|
||||
return Pair(1, "${syllable(ini, med, 0)}${syllable(initials.indexOf(finals[fin]), medials.indexOf(c), 0)}")
|
||||
|
||||
// if there is a composed final and the new char is a medial, split the old final
|
||||
if (finals[fin] in finalCompRev && c in medials)
|
||||
return Pair(1, "${syllable(ini, med, finals.indexOf(finalCompRev.getValue(finals[fin])[0]))}${syllable(initials.indexOf(finalCompRev.getValue(finals[fin])[1]), medials.indexOf(c), 0)}")
|
||||
|
||||
// if no final yet, and current medial can be composed with new char, merge
|
||||
if (medials[med] in medialComp && c in medialComp.getValue(medials[med])[0] && fin == 0) {
|
||||
val tple = medialComp[medials[med]]
|
||||
return Pair(1, "${syllable(ini, medials.indexOf(tple!![1][tple[0].indexOf(c)]), 0)}")
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(0, ""+c)
|
||||
}
|
||||
}
|
||||
@@ -105,10 +105,12 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
|
||||
textInputManager.isGlidePostEffect = true
|
||||
textInputManager.smartbarView?.setCandidateSuggestionWords(
|
||||
time,
|
||||
suggestions.subList(
|
||||
// FIXME
|
||||
/*suggestions.subList(
|
||||
1.coerceAtMost(min(commit.compareTo(false), suggestions.size)),
|
||||
maxSuggestionsToShow.coerceAtMost(suggestions.size)
|
||||
).map { textInputManager.fixCase(it) }
|
||||
).map { textInputManager.fixCase(it) }*/
|
||||
null
|
||||
)
|
||||
textInputManager.smartbarView?.updateCandidateSuggestionCapsState()
|
||||
if (commit && suggestions.isNotEmpty()) {
|
||||
|
||||
@@ -92,10 +92,13 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
|
||||
override fun setLayout(keyViews: List<TextKey>, subtype: Subtype) {
|
||||
// stop duplicate calls
|
||||
if (layoutSubtype == subtype) {
|
||||
if (layoutSubtype == subtype && keys == keyViews) {
|
||||
return
|
||||
}
|
||||
|
||||
// if only layout changed but not subtype
|
||||
val layoutChanged = layoutSubtype == subtype
|
||||
|
||||
keysByCharacter.clear()
|
||||
keys.clear()
|
||||
keyViews.forEach {
|
||||
@@ -105,7 +108,13 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
layoutSubtype = subtype
|
||||
distanceThresholdSquared = (keyViews.first().visibleBounds.width() / 4)
|
||||
distanceThresholdSquared *= distanceThresholdSquared
|
||||
initializePruner()
|
||||
|
||||
if (
|
||||
(wordDataSubtype == layoutSubtype)
|
||||
|| layoutChanged // should force a re-initialize
|
||||
) {
|
||||
initializePruner(layoutChanged)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setWordData(words: HashMap<String, Int>, subtype: Subtype) {
|
||||
@@ -118,7 +127,9 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
this.wordFrequencies = words
|
||||
|
||||
this.wordDataSubtype = subtype
|
||||
initializePruner()
|
||||
if (wordDataSubtype == layoutSubtype) {
|
||||
initializePruner(false)
|
||||
}
|
||||
}
|
||||
|
||||
private val prunerCache = LruCache<Subtype, Pruner>(PRUNER_CACHE_SIZE)
|
||||
@@ -127,13 +138,12 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
* Exists because Pruner requires both word data and layout are initialized,
|
||||
* however we don't know what order they're initialized in.
|
||||
*/
|
||||
private fun initializePruner() {
|
||||
if (this.layoutSubtype == null || this.wordDataSubtype != this.layoutSubtype) {
|
||||
// not yet ready
|
||||
return
|
||||
}
|
||||
private fun initializePruner(invalidateCache: Boolean) {
|
||||
val currentSubtype = this.layoutSubtype!!
|
||||
val cached = prunerCache.get(currentSubtype)
|
||||
val cached = when {
|
||||
invalidateCache -> null
|
||||
else -> prunerCache.get(currentSubtype)
|
||||
}
|
||||
if (cached == null) {
|
||||
this.pruner = Pruner(PRUNING_LENGTH_THRESHOLD, this.words, keysByCharacter)
|
||||
prunerCache.put(currentSubtype, this.pruner)
|
||||
@@ -361,7 +371,12 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
): Iterable<Int> {
|
||||
val keyDistances = HashMap<TextKey, Float>()
|
||||
for (key in keys) {
|
||||
val distance = Gesture.distance(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat(), x, y)
|
||||
val distance = Gesture.distance(
|
||||
key.visibleBounds.centerX().toFloat(),
|
||||
key.visibleBounds.centerY().toFloat(),
|
||||
x,
|
||||
y
|
||||
)
|
||||
keyDistances[key] = distance
|
||||
}
|
||||
|
||||
@@ -413,26 +428,39 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
|
||||
if (previousLetter == lc) {
|
||||
// bottom right
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.visibleBounds.centerX() + key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() + key.visibleBounds.height() / 4.0f
|
||||
key.visibleBounds.centerX() + key.visibleBounds.width() / 4.0f,
|
||||
key.visibleBounds.centerY() + key.visibleBounds.height() / 4.0f
|
||||
)
|
||||
// top right
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.visibleBounds.centerX() + key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() - key.visibleBounds.height() / 4.0f
|
||||
key.visibleBounds.centerX() + key.visibleBounds.width() / 4.0f,
|
||||
key.visibleBounds.centerY() - key.visibleBounds.height() / 4.0f
|
||||
)
|
||||
// top left
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.visibleBounds.centerX() - key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() - key.visibleBounds.height() / 4.0f
|
||||
key.visibleBounds.centerX() - key.visibleBounds.width() / 4.0f,
|
||||
key.visibleBounds.centerY() - key.visibleBounds.height() / 4.0f
|
||||
)
|
||||
// bottom left
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.visibleBounds.centerX() - key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() + key.visibleBounds.height() / 4.0f
|
||||
key.visibleBounds.centerX() - key.visibleBounds.width() / 4.0f,
|
||||
key.visibleBounds.centerY() + key.visibleBounds.height() / 4.0f
|
||||
)
|
||||
hasLoops = true
|
||||
|
||||
idealGesture.addPoint(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat())
|
||||
idealGesture.addPoint(
|
||||
key.visibleBounds.centerX().toFloat(),
|
||||
key.visibleBounds.centerY().toFloat()
|
||||
)
|
||||
} else {
|
||||
idealGesture.addPoint(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat())
|
||||
idealGestureWithLoops.addPoint(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat())
|
||||
idealGesture.addPoint(
|
||||
key.visibleBounds.centerX().toFloat(),
|
||||
key.visibleBounds.centerY().toFloat()
|
||||
)
|
||||
idealGestureWithLoops.addPoint(
|
||||
key.visibleBounds.centerX().toFloat(),
|
||||
key.visibleBounds.centerY().toFloat()
|
||||
)
|
||||
}
|
||||
previousLetter = lc
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.view.VelocityTracker
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.debug.LogTopic
|
||||
import dev.patrickgold.florisboard.debug.flogDebug
|
||||
import dev.patrickgold.florisboard.ime.view.Pointer
|
||||
import dev.patrickgold.florisboard.ime.view.PointerMap
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.atan2
|
||||
@@ -67,7 +69,8 @@ abstract class SwipeGesture {
|
||||
* @property listener The listener to report detected swipes to.
|
||||
*/
|
||||
class Detector(private val context: Context, private val listener: Listener) {
|
||||
private var pointerDataMap: MutableMap<Int, PointerData> = mutableMapOf()
|
||||
var isEnabled: Boolean = true
|
||||
private var pointerMap: PointerMap<GesturePointer> = PointerMap { GesturePointer() }
|
||||
private var thresholdSpeed: Double = numericValue(context, VelocityThreshold.NORMAL)
|
||||
private var thresholdWidth: Double = numericValue(context, DistanceThreshold.NORMAL)
|
||||
private var unitWidth: Double = thresholdWidth / 4.0
|
||||
@@ -93,99 +96,92 @@ abstract class SwipeGesture {
|
||||
* trigger, regardless of the distance from the previous event. Defaults to false.
|
||||
* @return True if the given [event] is a gesture, false otherwise.
|
||||
*/
|
||||
fun onTouchEvent(event: MotionEvent, alwaysTriggerOnMove: Boolean = false): Boolean {
|
||||
try {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
resetState()
|
||||
velocityTracker.clear()
|
||||
}
|
||||
velocityTracker.addMovement(event)
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
pointerDataMap[pointerId] = PointerData().apply {
|
||||
firstX = event.getX(pointerIndex)
|
||||
firstY = event.getY(pointerIndex)
|
||||
lastX = firstX
|
||||
lastY = firstY
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
velocityTracker.addMovement(event)
|
||||
for (pointerIndex in 0 until event.pointerCount) {
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
pointerDataMap[pointerId]?.apply {
|
||||
val absDiffX = event.getX(pointerIndex) - firstX
|
||||
val absDiffY = event.getY(pointerIndex) - firstY
|
||||
val relDiffX = event.getX(pointerIndex) - lastX
|
||||
val relDiffY = event.getY(pointerIndex) - lastY
|
||||
return if (alwaysTriggerOnMove || abs(relDiffX) > (thresholdWidth / 2.0) || abs(relDiffY) > (thresholdWidth / 2.0)) {
|
||||
lastX = event.getX(pointerIndex)
|
||||
lastY = event.getY(pointerIndex)
|
||||
val direction = detectDirection(relDiffX.toDouble(), relDiffY.toDouble())
|
||||
val newAbsUnitCountX = (absDiffX / unitWidth).toInt()
|
||||
val newAbsUnitCountY = (absDiffY / unitWidth).toInt()
|
||||
val relUnitCountX = newAbsUnitCountX - absUnitCountX
|
||||
val relUnitCountY = newAbsUnitCountY - absUnitCountY
|
||||
absUnitCountX = newAbsUnitCountX
|
||||
absUnitCountY = newAbsUnitCountY
|
||||
listener.onSwipe(Event(
|
||||
direction = direction,
|
||||
type = Type.TOUCH_MOVE,
|
||||
pointerId,
|
||||
absUnitCountX,
|
||||
absUnitCountY,
|
||||
relUnitCountX,
|
||||
relUnitCountY
|
||||
))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
velocityTracker.addMovement(event)
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
pointerDataMap.remove(pointerId)?.apply {
|
||||
val absDiffX = event.getX(pointerIndex) - firstX
|
||||
val absDiffY = event.getY(pointerIndex) - firstY
|
||||
velocityTracker.computeCurrentVelocity(1000)
|
||||
val velocityX = ViewLayoutUtils.convertDpToPixel(velocityTracker.getXVelocity(pointerId), context)
|
||||
val velocityY = ViewLayoutUtils.convertDpToPixel(velocityTracker.getYVelocity(pointerId), context)
|
||||
flogDebug(LogTopic.GESTURES) { "Velocity: $velocityX $velocityY dp/s" }
|
||||
return if ((abs(absDiffX) > thresholdWidth || abs(absDiffY) > thresholdWidth) && (abs(velocityX) > thresholdSpeed || abs(velocityY) > thresholdSpeed)) {
|
||||
val direction = detectDirection(absDiffX.toDouble(), absDiffY.toDouble())
|
||||
absUnitCountX = (absDiffX / unitWidth).toInt()
|
||||
absUnitCountY = (absDiffY / unitWidth).toInt()
|
||||
listener.onSwipe(Event(
|
||||
direction = direction,
|
||||
type = Type.TOUCH_UP,
|
||||
pointerId,
|
||||
absUnitCountX,
|
||||
absUnitCountY,
|
||||
absUnitCountX,
|
||||
absUnitCountY
|
||||
))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
resetState()
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
return false
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
fun onTouchEvent(event: MotionEvent) {
|
||||
if (!isEnabled) return
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
resetState()
|
||||
velocityTracker.clear()
|
||||
}
|
||||
velocityTracker.addMovement(event)
|
||||
}
|
||||
|
||||
fun onTouchDown(event: MotionEvent, pointer: Pointer) {
|
||||
if (!isEnabled) return
|
||||
pointerMap.add(pointer.id, pointer.index)?.let { gesturePointer ->
|
||||
gesturePointer.firstX = event.getX(pointer.index)
|
||||
gesturePointer.firstY = event.getY(pointer.index)
|
||||
gesturePointer.lastX = gesturePointer.firstX
|
||||
gesturePointer.lastY = gesturePointer.firstY
|
||||
}
|
||||
}
|
||||
|
||||
fun onTouchMove(event: MotionEvent, pointer: Pointer, alwaysTriggerOnMove: Boolean): Boolean {
|
||||
if (!isEnabled) return false
|
||||
pointerMap.findById(pointer.id)?.let { gesturePointer ->
|
||||
gesturePointer.index = pointer.index
|
||||
val absDiffX = event.getX(pointer.index) - gesturePointer.firstX
|
||||
val absDiffY = event.getY(pointer.index) - gesturePointer.firstY
|
||||
val relDiffX = event.getX(pointer.index) - gesturePointer.lastX
|
||||
val relDiffY = event.getY(pointer.index) - gesturePointer.lastY
|
||||
return if (alwaysTriggerOnMove || abs(relDiffX) > (thresholdWidth / 2.0) || abs(relDiffY) > (thresholdWidth / 2.0)) {
|
||||
gesturePointer.lastX = event.getX(pointer.index)
|
||||
gesturePointer.lastY = event.getY(pointer.index)
|
||||
val direction = detectDirection(relDiffX.toDouble(), relDiffY.toDouble())
|
||||
val newAbsUnitCountX = (absDiffX / unitWidth).toInt()
|
||||
val newAbsUnitCountY = (absDiffY / unitWidth).toInt()
|
||||
val relUnitCountX = newAbsUnitCountX - gesturePointer.absUnitCountX
|
||||
val relUnitCountY = newAbsUnitCountY - gesturePointer.absUnitCountY
|
||||
gesturePointer.absUnitCountX = newAbsUnitCountX
|
||||
gesturePointer.absUnitCountY = newAbsUnitCountY
|
||||
listener.onSwipe(Event(
|
||||
direction = direction,
|
||||
type = Type.TOUCH_MOVE,
|
||||
pointer.id,
|
||||
gesturePointer.absUnitCountX,
|
||||
gesturePointer.absUnitCountY,
|
||||
relUnitCountX,
|
||||
relUnitCountY
|
||||
))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onTouchUp(event: MotionEvent, pointer: Pointer): Boolean {
|
||||
if (!isEnabled) return false
|
||||
pointerMap.findById(pointer.id)?.let { gesturePointer ->
|
||||
val absDiffX = event.getX(pointer.index) - gesturePointer.firstX
|
||||
val absDiffY = event.getY(pointer.index) - gesturePointer.firstY
|
||||
velocityTracker.computeCurrentVelocity(1000)
|
||||
val velocityX = ViewLayoutUtils.convertDpToPixel(velocityTracker.getXVelocity(pointer.id), context)
|
||||
val velocityY = ViewLayoutUtils.convertDpToPixel(velocityTracker.getYVelocity(pointer.id), context)
|
||||
flogDebug(LogTopic.GESTURES) { "Velocity: $velocityX $velocityY dp/s" }
|
||||
pointerMap.removeById(pointer.id)
|
||||
return if ((abs(absDiffX) > thresholdWidth || abs(absDiffY) > thresholdWidth) && (abs(velocityX) > thresholdSpeed || abs(velocityY) > thresholdSpeed)) {
|
||||
val direction = detectDirection(absDiffX.toDouble(), absDiffY.toDouble())
|
||||
gesturePointer.absUnitCountX = (absDiffX / unitWidth).toInt()
|
||||
gesturePointer.absUnitCountY = (absDiffY / unitWidth).toInt()
|
||||
listener.onSwipe(Event(
|
||||
direction = direction,
|
||||
type = Type.TOUCH_UP,
|
||||
pointer.id,
|
||||
gesturePointer.absUnitCountX,
|
||||
gesturePointer.absUnitCountY,
|
||||
gesturePointer.absUnitCountX,
|
||||
gesturePointer.absUnitCountY
|
||||
))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onTouchCancel(event: MotionEvent, pointer: Pointer) {
|
||||
if (!isEnabled) return
|
||||
pointerMap.removeById(pointer.id)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,16 +218,26 @@ abstract class SwipeGesture {
|
||||
* Resets the state.
|
||||
*/
|
||||
private fun resetState() {
|
||||
pointerDataMap.clear()
|
||||
pointerMap.clear()
|
||||
}
|
||||
|
||||
class PointerData {
|
||||
class GesturePointer : Pointer() {
|
||||
var firstX: Float = 0.0f
|
||||
var firstY: Float = 0.0f
|
||||
var lastX: Float = 0.0f
|
||||
var lastY: Float = 0.0f
|
||||
var absUnitCountX: Int = 0
|
||||
var absUnitCountY: Int = 0
|
||||
|
||||
override fun reset() {
|
||||
super.reset()
|
||||
firstX = 0.0f
|
||||
firstY = 0.0f
|
||||
lastX = 0.0f
|
||||
lastY = 0.0f
|
||||
absUnitCountX = 0
|
||||
absUnitCountY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2021 ostrya
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.text.key
|
||||
|
||||
data class KeyHintConfiguration(
|
||||
val symbolHintMode: KeyHintMode,
|
||||
val numberHintMode: KeyHintMode,
|
||||
val mergeHintPopups: Boolean
|
||||
)
|
||||
|
||||
val HINTS_DISABLED: KeyHintConfiguration = KeyHintConfiguration(KeyHintMode.DISABLED, KeyHintMode.DISABLED, false)
|
||||
@@ -19,6 +19,7 @@ package dev.patrickgold.florisboard.ime.text.keyboard
|
||||
import dev.patrickgold.florisboard.ime.keyboard.Key
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.popup.MutablePopupSet
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupMapping
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupSet
|
||||
import dev.patrickgold.florisboard.ime.text.key.*
|
||||
|
||||
@@ -26,7 +27,8 @@ class TextKey(override val data: KeyData) : Key(data) {
|
||||
var computedData: TextKeyData = TextKeyData.UNSPECIFIED
|
||||
private set
|
||||
val computedPopups: MutablePopupSet<TextKeyData> = MutablePopupSet()
|
||||
var computedHint: TextKeyData? = null
|
||||
var computedSymbolHint: TextKeyData? = null
|
||||
var computedNumberHint: TextKeyData? = null
|
||||
|
||||
fun compute(evaluator: TextComputingEvaluator) {
|
||||
val keyboardMode = evaluator.getKeyboard().mode
|
||||
@@ -44,10 +46,7 @@ class TextKey(override val data: KeyData) : Key(data) {
|
||||
} else {
|
||||
computedData = computed
|
||||
computedPopups.clear()
|
||||
computedPopups.hint = computedHint?.computeTextKeyData(evaluator)
|
||||
if (computed is BasicTextKeyData && computed.popup != null) {
|
||||
computedPopups.merge(computed.popup, evaluator)
|
||||
}
|
||||
mergePopups(computed, evaluator, computedPopups::merge)
|
||||
if (keyboardMode == KeyboardMode.CHARACTERS || keyboardMode == KeyboardMode.NUMERIC_ADVANCED ||
|
||||
keyboardMode == KeyboardMode.SYMBOLS || keyboardMode == KeyboardMode.SYMBOLS2) {
|
||||
val extLabel = when (computed.groupId) {
|
||||
@@ -97,6 +96,9 @@ class TextKey(override val data: KeyData) : Key(data) {
|
||||
keySpecificPopupSet?.let { merge(it, evaluator) }
|
||||
popupSet?.let { merge(it, evaluator) }
|
||||
}
|
||||
if (computed.type == KeyType.CHARACTER) {
|
||||
addComputedHints(computed.code, evaluator, extendedPopups, extendedPopupsDefault)
|
||||
}
|
||||
}
|
||||
isEnabled = evaluator.evaluateEnabled(computed)
|
||||
isVisible = true
|
||||
@@ -150,4 +152,48 @@ class TextKey(override val data: KeyData) : Key(data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addComputedHints(
|
||||
keyCode: Int,
|
||||
evaluator: TextComputingEvaluator,
|
||||
extendedPopups: PopupMapping?,
|
||||
extendedPopupsDefault: PopupMapping?
|
||||
) {
|
||||
val symbolHint = computedSymbolHint
|
||||
if (symbolHint != null) {
|
||||
val evaluatedSymbolHint = symbolHint.computeTextKeyData(evaluator)
|
||||
if (symbolHint.code != keyCode) {
|
||||
computedPopups.symbolHint = evaluatedSymbolHint
|
||||
mergePopups(evaluatedSymbolHint, evaluator, computedPopups::mergeSymbolHint)
|
||||
val hintSpecificPopupSet =
|
||||
extendedPopups?.get(KeyVariation.ALL)?.get(symbolHint.label) ?: extendedPopupsDefault?.get(
|
||||
KeyVariation.ALL
|
||||
)?.get(symbolHint.label)
|
||||
hintSpecificPopupSet?.let { computedPopups.mergeSymbolHint(it, evaluator) }
|
||||
}
|
||||
}
|
||||
val numericHint = computedNumberHint
|
||||
if (numericHint != null) {
|
||||
val evaluatedNumberHint = numericHint.computeTextKeyData(evaluator)
|
||||
if (numericHint.code != keyCode) {
|
||||
computedPopups.numberHint = evaluatedNumberHint
|
||||
mergePopups(evaluatedNumberHint, evaluator, computedPopups::mergeNumberHint)
|
||||
val hintSpecificPopupSet =
|
||||
extendedPopups?.get(KeyVariation.ALL)?.get(numericHint.label) ?: extendedPopupsDefault?.get(
|
||||
KeyVariation.ALL
|
||||
)?.get(numericHint.label)
|
||||
hintSpecificPopupSet?.let { computedPopups.mergeNumberHint(it, evaluator) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mergePopups(
|
||||
keyData: TextKeyData?,
|
||||
evaluator: TextComputingEvaluator,
|
||||
merge: (popups: PopupSet<TextKeyData>, evaluator: TextComputingEvaluator) -> Unit
|
||||
) {
|
||||
if (keyData is BasicTextKeyData && keyData.popup != null) {
|
||||
merge(keyData.popup, evaluator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,202 +67,202 @@ interface TextKeyData : KeyData {
|
||||
|
||||
companion object {
|
||||
/** Predefined key data for [KeyCode.ARROW_DOWN] */
|
||||
val ARROW_DOWN = AutoTextKeyData(
|
||||
val ARROW_DOWN = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.ARROW_DOWN,
|
||||
label = "arrow_down"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.ARROW_LEFT] */
|
||||
val ARROW_LEFT = AutoTextKeyData(
|
||||
val ARROW_LEFT = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.ARROW_LEFT,
|
||||
label = "arrow_left"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.ARROW_RIGHT] */
|
||||
val ARROW_RIGHT = AutoTextKeyData(
|
||||
val ARROW_RIGHT = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.ARROW_RIGHT,
|
||||
label = "arrow_right"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.ARROW_UP] */
|
||||
val ARROW_UP = AutoTextKeyData(
|
||||
val ARROW_UP = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.ARROW_UP,
|
||||
label = "arrow_up"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.CLIPBOARD_COPY] */
|
||||
val CLIPBOARD_COPY = AutoTextKeyData(
|
||||
val CLIPBOARD_COPY = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.CLIPBOARD_COPY,
|
||||
label = "clipboard_copy"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.CLIPBOARD_CUT] */
|
||||
val CLIPBOARD_CUT = AutoTextKeyData(
|
||||
val CLIPBOARD_CUT = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.CLIPBOARD_CUT,
|
||||
label = "clipboard_cut"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.CLIPBOARD_PASTE] */
|
||||
val CLIPBOARD_PASTE = AutoTextKeyData(
|
||||
val CLIPBOARD_PASTE = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.CLIPBOARD_PASTE,
|
||||
label = "clipboard_paste"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.CLIPBOARD_SELECT] */
|
||||
val CLIPBOARD_SELECT = AutoTextKeyData(
|
||||
val CLIPBOARD_SELECT = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.CLIPBOARD_SELECT,
|
||||
label = "clipboard_select"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.CLIPBOARD_SELECT_ALL] */
|
||||
val CLIPBOARD_SELECT_ALL = AutoTextKeyData(
|
||||
val CLIPBOARD_SELECT_ALL = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.CLIPBOARD_SELECT_ALL,
|
||||
label = "clipboard_select_all"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.DELETE] */
|
||||
val DELETE = AutoTextKeyData(
|
||||
val DELETE = BasicTextKeyData(
|
||||
type = KeyType.ENTER_EDITING,
|
||||
code = KeyCode.DELETE,
|
||||
label = "delete"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.DELETE_WORD] */
|
||||
val DELETE_WORD = AutoTextKeyData(
|
||||
val DELETE_WORD = BasicTextKeyData(
|
||||
type = KeyType.ENTER_EDITING,
|
||||
code = KeyCode.DELETE_WORD,
|
||||
label = "delete_word"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.INTERNAL_BATCH_EDIT] */
|
||||
val INTERNAL_BATCH_EDIT = AutoTextKeyData(
|
||||
val INTERNAL_BATCH_EDIT = BasicTextKeyData(
|
||||
type = KeyType.FUNCTION,
|
||||
code = KeyCode.INTERNAL_BATCH_EDIT,
|
||||
label = "internal_batch_edit"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.MOVE_START_OF_LINE] */
|
||||
val MOVE_START_OF_LINE = AutoTextKeyData(
|
||||
val MOVE_START_OF_LINE = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.MOVE_START_OF_LINE,
|
||||
label = "move_start_of_line"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.MOVE_END_OF_LINE] */
|
||||
val MOVE_END_OF_LINE = AutoTextKeyData(
|
||||
val MOVE_END_OF_LINE = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.MOVE_END_OF_LINE,
|
||||
label = "move_end_of_line"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.MOVE_START_OF_PAGE] */
|
||||
val MOVE_START_OF_PAGE = AutoTextKeyData(
|
||||
val MOVE_START_OF_PAGE = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.MOVE_START_OF_PAGE,
|
||||
label = "move_start_of_page"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.MOVE_END_OF_PAGE] */
|
||||
val MOVE_END_OF_PAGE = AutoTextKeyData(
|
||||
val MOVE_END_OF_PAGE = BasicTextKeyData(
|
||||
type = KeyType.NAVIGATION,
|
||||
code = KeyCode.MOVE_END_OF_PAGE,
|
||||
label = "move_end_of_page"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.REDO] */
|
||||
val REDO = AutoTextKeyData(
|
||||
val REDO = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.REDO,
|
||||
label = "redo"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.SHOW_INPUT_METHOD_PICKER] */
|
||||
val SHOW_INPUT_METHOD_PICKER = AutoTextKeyData(
|
||||
val SHOW_INPUT_METHOD_PICKER = BasicTextKeyData(
|
||||
type = KeyType.FUNCTION,
|
||||
code = KeyCode.SHOW_INPUT_METHOD_PICKER,
|
||||
label = "show_input_method_picker"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.SWITCH_TO_TEXT_CONTEXT] */
|
||||
val SWITCH_TO_TEXT_CONTEXT = AutoTextKeyData(
|
||||
val SWITCH_TO_TEXT_CONTEXT = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.SWITCH_TO_TEXT_CONTEXT,
|
||||
label = "switch_to_text_context"
|
||||
)
|
||||
/** Predefined key data for [KeyCode.SWITCH_TO_CLIPBOARD_CONTEXT] */
|
||||
val SWITCH_TO_CLIPBOARD_CONTEXT = AutoTextKeyData(
|
||||
val SWITCH_TO_CLIPBOARD_CONTEXT = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.SWITCH_TO_CLIPBOARD_CONTEXT,
|
||||
label = "switch_to_clipboard_context"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.SHIFT] */
|
||||
val SHIFT = AutoTextKeyData(
|
||||
val SHIFT = BasicTextKeyData(
|
||||
type = KeyType.MODIFIER,
|
||||
code = KeyCode.SHIFT,
|
||||
label = "shift"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.SHIFT_LOCK] */
|
||||
val SHIFT_LOCK = AutoTextKeyData(
|
||||
val SHIFT_LOCK = BasicTextKeyData(
|
||||
type = KeyType.MODIFIER,
|
||||
code = KeyCode.SHIFT_LOCK,
|
||||
label = "shift_lock"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.SPACE] */
|
||||
val SPACE = AutoTextKeyData(
|
||||
val SPACE = BasicTextKeyData(
|
||||
type = KeyType.CHARACTER,
|
||||
code = KeyCode.SPACE,
|
||||
label = "space"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.UNDO] */
|
||||
val UNDO = AutoTextKeyData(
|
||||
val UNDO = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.UNDO,
|
||||
label = "undo"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.UNSPECIFIED] */
|
||||
val UNSPECIFIED = AutoTextKeyData(
|
||||
val UNSPECIFIED = BasicTextKeyData(
|
||||
type = KeyType.UNSPECIFIED,
|
||||
code = KeyCode.UNSPECIFIED,
|
||||
label = "unspecified"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.VIEW_CHARACTERS] */
|
||||
val VIEW_CHARACTERS = AutoTextKeyData(
|
||||
val VIEW_CHARACTERS = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.VIEW_CHARACTERS,
|
||||
label = "view_characters"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.VIEW_SYMBOLS] */
|
||||
val VIEW_SYMBOLS = AutoTextKeyData(
|
||||
val VIEW_SYMBOLS = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.VIEW_SYMBOLS,
|
||||
label = "view_symbols"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.VIEW_SYMBOLS2] */
|
||||
val VIEW_SYMBOLS2 = AutoTextKeyData(
|
||||
val VIEW_SYMBOLS2 = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.VIEW_SYMBOLS2,
|
||||
label = "view_symbols2"
|
||||
)
|
||||
|
||||
/** Predefined key data for [KeyCode.VIEW_NUMERIC_ADVANCED] */
|
||||
val VIEW_NUMERIC_ADVANCED = AutoTextKeyData(
|
||||
val VIEW_NUMERIC_ADVANCED = BasicTextKeyData(
|
||||
type = KeyType.SYSTEM_GUI,
|
||||
code = KeyCode.VIEW_NUMERIC_ADVANCED,
|
||||
label = "view_numeric_advanced"
|
||||
|
||||
@@ -18,13 +18,14 @@ package dev.patrickgold.florisboard.ime.text.keyboard
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.PaintDrawable
|
||||
import android.os.Handler
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.debug.*
|
||||
import dev.patrickgold.florisboard.ime.core.*
|
||||
@@ -37,14 +38,18 @@ import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
|
||||
import dev.patrickgold.florisboard.ime.text.key.*
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeValue
|
||||
import dev.patrickgold.florisboard.ime.view.Pointer
|
||||
import dev.patrickgold.florisboard.ime.view.PointerMap
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
import dev.patrickgold.florisboard.util.cancelAll
|
||||
import dev.patrickgold.florisboard.util.postDelayed
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture.Listener {
|
||||
private var computedKeyboard: TextKeyboard? = null
|
||||
private var iconSet: TextKeyboardIconSet? = null
|
||||
@@ -105,17 +110,13 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
private var isPreviewMode: Boolean = false
|
||||
private var isLoadingPlaceholderKeyboard: Boolean = false
|
||||
|
||||
private var initialKey: TextKey? = null
|
||||
private var activeKey: TextKey? = null
|
||||
private var activePointerId: Int? = null
|
||||
private val longPressHandler: Handler = Handler(context.mainLooper)
|
||||
private var keyHintConfiguration: KeyHintConfiguration = prefs.keyboard.getKeyHintConfiguration()
|
||||
private val pointerMap: PointerMap<TouchPointer> = PointerMap { TouchPointer() }
|
||||
private val popupManager: PopupManager<TextKeyboardView>
|
||||
|
||||
private var initSelectionStart: Int = 0
|
||||
private var initSelectionEnd: Int = 0
|
||||
private var isGliding: Boolean = false
|
||||
private var hasTriggeredGestureMove: Boolean = false
|
||||
private var shouldBlockNextUp: Boolean = false
|
||||
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
|
||||
|
||||
private val glideTypingDetector = GlideTypingGesture.Detector(context)
|
||||
@@ -127,8 +128,9 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
|
||||
val desiredKey: TextKey = TextKey(data = TextKeyData.UNSPECIFIED)
|
||||
|
||||
private var keyBackgroundDrawable: PaintDrawable = PaintDrawable().apply {
|
||||
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
|
||||
private var keyBackgroundDrawable: MaterialShapeDrawable = MaterialShapeDrawable().also {
|
||||
it.setCornerSize(ViewLayoutUtils.convertDpToPixel(6.0f, context))
|
||||
//it.shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS
|
||||
}
|
||||
|
||||
private var backgroundDrawable: PaintDrawable = PaintDrawable()
|
||||
@@ -171,6 +173,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
flogError(LogTopic.TEXT_KEYBOARD_VIEW) { "PopupLayerView is null, cannot show popups!" }
|
||||
}
|
||||
popupManager = PopupManager(this, popupLayerView)
|
||||
swipeGestureDetector.isEnabled = !isSmartbarKeyboardView
|
||||
|
||||
setWillNotDraw(false)
|
||||
}
|
||||
@@ -223,285 +226,329 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
}
|
||||
|
||||
override fun onTouchEventInternal(event: MotionEvent) {
|
||||
if (prefs.glide.enabled &&
|
||||
computedKeyboard?.mode == KeyboardMode.CHARACTERS &&
|
||||
glideTypingDetector.onTouchEvent(event, initialKey) &&
|
||||
event.actionMasked != MotionEvent.ACTION_UP
|
||||
) {
|
||||
if (activePointerId != null) {
|
||||
val pointerIndex = event.actionIndex
|
||||
onTouchCancelInternal(event, pointerIndex, activePointerId!!)
|
||||
flogDebug { "event=$event" }
|
||||
val dispatcher = florisboard?.textInputManager?.inputEventDispatcher ?: return
|
||||
swipeGestureDetector.onTouchEvent(event)
|
||||
|
||||
if (prefs.glide.enabled && computedKeyboard?.mode == KeyboardMode.CHARACTERS) {
|
||||
val glidePointer = pointerMap.findById(0)
|
||||
if (glideTypingDetector.onTouchEvent(event, glidePointer?.initialKey)) {
|
||||
for (pointer in pointerMap) {
|
||||
if (pointer.activeKey != null) {
|
||||
onTouchCancelInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||
pointerMap.clear()
|
||||
}
|
||||
isGliding = true
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
isGliding = true
|
||||
invalidate()
|
||||
return
|
||||
}
|
||||
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
dispatcher.send(InputKeyEvent.down(TextKeyData.INTERNAL_BATCH_EDIT))
|
||||
keyHintConfiguration = prefs.keyboard.getKeyHintConfiguration()
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
val pointer = pointerMap.add(pointerId, pointerIndex)
|
||||
if (pointer != null) {
|
||||
swipeGestureDetector.onTouchDown(event, pointer)
|
||||
onTouchDownInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
if (activePointerId != null) {
|
||||
onTouchUpInternal(event, pointerIndex, activePointerId!!)
|
||||
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = false)
|
||||
} else {
|
||||
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = true)
|
||||
val oldPointer = pointerMap.findById(pointerId)
|
||||
if (oldPointer != null) {
|
||||
swipeGestureDetector.onTouchCancel(event, oldPointer)
|
||||
onTouchCancelInternal(event, oldPointer)
|
||||
pointerMap.removeById(oldPointer.id)
|
||||
}
|
||||
// Search for active character keys and cancel them
|
||||
for (pointer in pointerMap) {
|
||||
val activeKey = pointer.activeKey
|
||||
if (activeKey != null && popupManager.isSuitableForPopups(activeKey)) {
|
||||
swipeGestureDetector.onTouchCancel(event, pointer)
|
||||
onTouchCancelInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
val pointer = pointerMap.add(pointerId, pointerIndex)
|
||||
if (pointer != null) {
|
||||
swipeGestureDetector.onTouchDown(event, pointer)
|
||||
onTouchDownInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
for (pointerIndex in 0 until event.pointerCount) {
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
if (activePointerId == pointerId) {
|
||||
onTouchMoveInternal(event, pointerIndex, pointerId)
|
||||
break // No need to continue looping at this point as multi-touch is not supported
|
||||
val pointer = pointerMap.findById(pointerId)
|
||||
if (pointer != null) {
|
||||
pointer.index = pointerIndex
|
||||
val alwaysTriggerOnMove = (pointer.hasTriggeredGestureMove
|
||||
&& (pointer.initialKey?.computedData?.code == KeyCode.DELETE
|
||||
&& prefs.gestures.deleteKeySwipeLeft == SwipeAction.DELETE_CHARACTERS_PRECISELY
|
||||
|| pointer.initialKey?.computedData?.code == KeyCode.SPACE))
|
||||
if (swipeGestureDetector.onTouchMove(event, pointer, alwaysTriggerOnMove) || pointer.hasTriggeredGestureMove) {
|
||||
pointer.longPressJob?.cancel()
|
||||
pointer.longPressJob = null
|
||||
pointer.hasTriggeredGestureMove = true
|
||||
pointer.activeKey?.let { activeKey ->
|
||||
activeKey.isPressed = false
|
||||
florisboard!!.textInputManager.inputEventDispatcher.let { dispatcher ->
|
||||
if (dispatcher.isPressed(activeKey.computedData.code)) {
|
||||
dispatcher.send(InputKeyEvent.cancel(activeKey.computedData))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onTouchMoveInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
if (activePointerId == pointerId) {
|
||||
onTouchUpInternal(event, pointerIndex, pointerId)
|
||||
}
|
||||
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
||||
florisboard?.let {
|
||||
if (it.textInputManager.inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
|
||||
if (initialKey?.computedData?.code == KeyCode.SHIFT && activeKey?.computedData?.code == KeyCode.SHIFT) {
|
||||
it.textInputManager.inputEventDispatcher.send(InputKeyEvent.up(TextKeyData.SHIFT))
|
||||
} else {
|
||||
it.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(TextKeyData.SHIFT))
|
||||
val pointer = pointerMap.findById(pointerId)
|
||||
if (pointer != null) {
|
||||
if (swipeGestureDetector.onTouchUp(event, pointer) || pointer.hasTriggeredGestureMove || pointer.shouldBlockNextUp) {
|
||||
if (pointer.hasTriggeredGestureMove && pointer.initialKey?.computedData?.code == KeyCode.DELETE) {
|
||||
florisboard!!.textInputManager.isGlidePostEffect = false
|
||||
florisboard!!.activeEditorInstance.apply {
|
||||
if (selection.isSelectionMode) {
|
||||
deleteBackwards()
|
||||
}
|
||||
}
|
||||
}
|
||||
onTouchCancelInternal(event, pointer)
|
||||
} else {
|
||||
onTouchUpInternal(event, pointer)
|
||||
}
|
||||
activePointerId = null
|
||||
pointerMap.removeById(pointer.id)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val pointerIndex = event.actionIndex
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
onTouchCancelInternal(event, pointerIndex, pointerId)
|
||||
florisboard?.let {
|
||||
if (it.textInputManager.inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
|
||||
it.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(TextKeyData.SHIFT))
|
||||
for (pointer in pointerMap) {
|
||||
if (pointer.id == pointerId) {
|
||||
pointer.index = pointerIndex
|
||||
if (swipeGestureDetector.onTouchUp(event, pointer) || pointer.hasTriggeredGestureMove || pointer.shouldBlockNextUp) {
|
||||
if (pointer.hasTriggeredGestureMove && pointer.initialKey?.computedData?.code == KeyCode.DELETE) {
|
||||
florisboard!!.textInputManager.isGlidePostEffect = false
|
||||
florisboard!!.activeEditorInstance.apply {
|
||||
if (selection.isSelectionMode) {
|
||||
deleteBackwards()
|
||||
}
|
||||
}
|
||||
}
|
||||
onTouchCancelInternal(event, pointer)
|
||||
} else {
|
||||
onTouchUpInternal(event, pointer)
|
||||
}
|
||||
} else {
|
||||
swipeGestureDetector.onTouchCancel(event, pointer)
|
||||
onTouchCancelInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
activePointerId = null
|
||||
pointerMap.clear()
|
||||
dispatcher.send(InputKeyEvent.up(TextKeyData.INTERNAL_BATCH_EDIT))
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
for (pointer in pointerMap) {
|
||||
swipeGestureDetector.onTouchCancel(event, pointer)
|
||||
onTouchCancelInternal(event, pointer)
|
||||
}
|
||||
pointerMap.clear()
|
||||
dispatcher.send(InputKeyEvent.up(TextKeyData.INTERNAL_BATCH_EDIT))
|
||||
}
|
||||
}
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "initialKey: ${initialKey?.computedData?.label} activeKey: ${activeKey?.computedData?.label}" }
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun onTouchDownInternal(event: MotionEvent, pointerIndex: Int, pointerId: Int, resetInitialKey: Boolean) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "index=$pointerIndex id=$pointerId event=$event" }
|
||||
val florisboard = florisboard ?: return
|
||||
private fun onTouchDownInternal(event: MotionEvent, pointer: TouchPointer) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "pointer=$pointer" }
|
||||
|
||||
activePointerId = pointerId
|
||||
val key = computedKeyboard?.getKeyForPos(
|
||||
event.getX(pointerIndex).roundToInt(), event.getY(pointerIndex).roundToInt()
|
||||
event.getX(pointer.index).roundToInt(), event.getY(pointer.index).roundToInt()
|
||||
)
|
||||
if (key != null && key.isEnabled) {
|
||||
var keyHintMode = KeyHintMode.DISABLED
|
||||
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED && key.computedPopups.hint?.type == KeyType.NUMERIC) {
|
||||
keyHintMode = prefs.keyboard.hintedNumberRowMode
|
||||
florisboard!!.textInputManager.inputEventDispatcher.let { dispatcher ->
|
||||
dispatcher.send(InputKeyEvent.down(key.computedData))
|
||||
}
|
||||
if (prefs.keyboard.hintedSymbolsMode != KeyHintMode.DISABLED && key.computedPopups.hint?.type == KeyType.CHARACTER) {
|
||||
keyHintMode = prefs.keyboard.hintedSymbolsMode
|
||||
if (prefs.keyboard.popupEnabled && popupManager.isSuitableForPopups(key)) {
|
||||
popupManager.show(key, keyHintConfiguration)
|
||||
}
|
||||
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.down(key.computedData))
|
||||
if (prefs.keyboard.popupEnabled) {
|
||||
popupManager.show(key, keyHintMode)
|
||||
}
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound(key.computedData)
|
||||
florisboard!!.keyPressVibrate()
|
||||
florisboard!!.keyPressSound(key.computedData)
|
||||
key.isPressed = true
|
||||
if (resetInitialKey) {
|
||||
initialKey = key
|
||||
if (pointer.initialKey == null) {
|
||||
pointer.initialKey = key
|
||||
}
|
||||
activeKey = key
|
||||
val delayMillis = prefs.keyboard.longPressDelay.toLong()
|
||||
when (key.computedData.code) {
|
||||
KeyCode.SPACE -> {
|
||||
initSelectionStart = florisboard.activeEditorInstance.selection.start
|
||||
initSelectionEnd = florisboard.activeEditorInstance.selection.end
|
||||
longPressHandler.postDelayed((delayMillis * 2.5f).toLong()) {
|
||||
pointer.activeKey = key
|
||||
pointer.longPressJob = mainScope.launch {
|
||||
val delayMillis = prefs.keyboard.longPressDelay.toLong()
|
||||
when (key.computedData.code) {
|
||||
KeyCode.SPACE -> {
|
||||
initSelectionStart = florisboard!!.activeEditorInstance.selection.start
|
||||
initSelectionEnd = florisboard!!.activeEditorInstance.selection.end
|
||||
delay((delayMillis * 2.5f).toLong())
|
||||
when (prefs.gestures.spaceBarLongPress) {
|
||||
SwipeAction.NO_ACTION,
|
||||
SwipeAction.INSERT_SPACE -> {
|
||||
}
|
||||
else -> {
|
||||
florisboard.executeSwipeAction(prefs.gestures.spaceBarLongPress)
|
||||
shouldBlockNextUp = true
|
||||
florisboard!!.executeSwipeAction(prefs.gestures.spaceBarLongPress)
|
||||
pointer.shouldBlockNextUp = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode.SHIFT -> {
|
||||
longPressHandler.postDelayed((delayMillis * 2.5).toLong()) {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(TextKeyData.SHIFT_LOCK))
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound(key.computedData)
|
||||
KeyCode.SHIFT -> {
|
||||
delay((delayMillis * 2.5f).toLong())
|
||||
florisboard!!.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(TextKeyData.SHIFT_LOCK))
|
||||
florisboard!!.keyPressVibrate()
|
||||
florisboard!!.keyPressSound(key.computedData)
|
||||
}
|
||||
}
|
||||
KeyCode.LANGUAGE_SWITCH -> {
|
||||
longPressHandler.postDelayed((delayMillis * 2.0).toLong()) {
|
||||
shouldBlockNextUp = true
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(TextKeyData.SHOW_INPUT_METHOD_PICKER))
|
||||
KeyCode.LANGUAGE_SWITCH -> {
|
||||
delay((delayMillis * 2.0f).toLong())
|
||||
pointer.shouldBlockNextUp = true
|
||||
florisboard!!.textInputManager.inputEventDispatcher.let { dispatcher ->
|
||||
dispatcher.send(InputKeyEvent.downUp(TextKeyData.SHOW_INPUT_METHOD_PICKER))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
longPressHandler.postDelayed(delayMillis) {
|
||||
if (key.computedPopups.isNotEmpty()) {
|
||||
popupManager.extend(key, keyHintMode)
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound(key.computedData)
|
||||
else -> {
|
||||
delay(delayMillis)
|
||||
if (popupManager.isSuitableForPopups(key) && key.computedPopups.getPopupKeys(keyHintConfiguration).isNotEmpty()) {
|
||||
popupManager.extend(key, keyHintConfiguration)
|
||||
florisboard!!.keyPressVibrate()
|
||||
florisboard!!.keyPressSound(key.computedData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSmartbarKeyboardView) {
|
||||
swipeGestureDetector.onTouchEvent(event)
|
||||
}
|
||||
} else {
|
||||
if (resetInitialKey) {
|
||||
initialKey = null
|
||||
}
|
||||
activeKey = null
|
||||
pointer.activeKey = null
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun onTouchMoveInternal(event: MotionEvent, pointerIndex: Int, pointerId: Int) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "index=$pointerIndex id=$pointerId event=$event" }
|
||||
val florisboard = florisboard ?: return
|
||||
private fun onTouchMoveInternal(event: MotionEvent, pointer: TouchPointer) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "pointer=$pointer" }
|
||||
|
||||
val key = activeKey
|
||||
if (key != null) {
|
||||
if (!isSmartbarKeyboardView) {
|
||||
val alwaysTriggerOnMove = (hasTriggeredGestureMove
|
||||
&& (initialKey?.computedData?.code == KeyCode.DELETE
|
||||
&& prefs.gestures.deleteKeySwipeLeft == SwipeAction.DELETE_CHARACTERS_PRECISELY
|
||||
|| initialKey?.computedData?.code == KeyCode.SPACE || (initialKey?.computedData?.code == KeyCode.SHIFT && activeKey?.computedData?.code == KeyCode.SPACE)))
|
||||
if (swipeGestureDetector.onTouchEvent(event, alwaysTriggerOnMove) || hasTriggeredGestureMove) {
|
||||
longPressHandler.cancelAll()
|
||||
hasTriggeredGestureMove = true
|
||||
initialKey?.let {
|
||||
if (it.computedData.code != KeyCode.SHIFT && florisboard.textInputManager.inputEventDispatcher.isPressed(it.computedData.code)) {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(it.computedData))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
val initialKey = pointer.initialKey
|
||||
val activeKey = pointer.activeKey
|
||||
if (initialKey != null && activeKey != null) {
|
||||
if (popupManager.isShowingExtendedPopup) {
|
||||
if (!popupManager.propagateMotionEvent(key, event, pointerIndex)) {
|
||||
onTouchCancelInternal(event, pointerIndex, pointerId)
|
||||
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = false)
|
||||
if (!popupManager.propagateMotionEvent(activeKey, event, pointer.index)) {
|
||||
onTouchCancelInternal(event, pointer)
|
||||
onTouchDownInternal(event, pointer)
|
||||
}
|
||||
} else {
|
||||
if ((event.getX(pointerIndex) < key.visibleBounds.left - 0.1f * key.visibleBounds.width())
|
||||
|| (event.getX(pointerIndex) > key.visibleBounds.right + 0.1f * key.visibleBounds.width())
|
||||
|| (event.getY(pointerIndex) < key.visibleBounds.top - 0.35f * key.visibleBounds.height())
|
||||
|| (event.getY(pointerIndex) > key.visibleBounds.bottom + 0.35f * key.visibleBounds.height())
|
||||
if ((event.getX(pointer.index) < activeKey.visibleBounds.left - 0.1f * activeKey.visibleBounds.width())
|
||||
|| (event.getX(pointer.index) > activeKey.visibleBounds.right + 0.1f * activeKey.visibleBounds.width())
|
||||
|| (event.getY(pointer.index) < activeKey.visibleBounds.top - 0.35f * activeKey.visibleBounds.height())
|
||||
|| (event.getY(pointer.index) > activeKey.visibleBounds.bottom + 0.35f * activeKey.visibleBounds.height())
|
||||
) {
|
||||
onTouchCancelInternal(event, pointerIndex, pointerId)
|
||||
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = false)
|
||||
onTouchCancelInternal(event, pointer)
|
||||
onTouchDownInternal(event, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun onTouchUpInternal(event: MotionEvent, pointerIndex: Int, pointerId: Int) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "index=$pointerIndex id=$pointerId event=$event" }
|
||||
val florisboard = florisboard ?: return
|
||||
private fun onTouchUpInternal(event: MotionEvent, pointer: TouchPointer) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "pointer=$pointer" }
|
||||
pointer.longPressJob?.cancel()
|
||||
pointer.longPressJob = null
|
||||
|
||||
longPressHandler.cancelAll()
|
||||
val key = activeKey
|
||||
if (key != null) {
|
||||
if (swipeGestureDetector.onTouchEvent(event) || hasTriggeredGestureMove || shouldBlockNextUp) {
|
||||
if (hasTriggeredGestureMove && initialKey?.computedData?.code == KeyCode.DELETE) {
|
||||
florisboard.textInputManager.isGlidePostEffect = false
|
||||
florisboard.activeEditorInstance.apply {
|
||||
if (selection.isSelectionMode) {
|
||||
deleteBackwards()
|
||||
val initialKey = pointer.initialKey
|
||||
val activeKey = pointer.activeKey
|
||||
if (initialKey != null && activeKey != null) {
|
||||
activeKey.isPressed = false
|
||||
florisboard!!.textInputManager.inputEventDispatcher.let { dispatcher ->
|
||||
if (popupManager.isSuitableForPopups(activeKey)) {
|
||||
val retData = popupManager.getActiveKeyData(activeKey, keyHintConfiguration)
|
||||
if (retData != null && !pointer.hasTriggeredGestureMove) {
|
||||
if (retData == activeKey.computedData) {
|
||||
dispatcher.send(InputKeyEvent.up(activeKey.computedData))
|
||||
} else {
|
||||
if (dispatcher.isPressed(activeKey.computedData.code)) {
|
||||
dispatcher.send(InputKeyEvent.cancel(activeKey.computedData))
|
||||
}
|
||||
dispatcher.send(InputKeyEvent.downUp(retData))
|
||||
}
|
||||
}
|
||||
}
|
||||
onTouchCancelInternal(event, pointerIndex, pointerId)
|
||||
return
|
||||
}
|
||||
key.isPressed = false
|
||||
if (activeKey?.computedData?.code != KeyCode.SHIFT) {
|
||||
val retData = popupManager.getActiveKeyData(key)
|
||||
if (retData != null) {
|
||||
if (retData == key.computedData) {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.up(key.computedData))
|
||||
} else {
|
||||
if (florisboard.textInputManager.inputEventDispatcher.isPressed(key.computedData.code)) {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
|
||||
if (dispatcher.isPressed(activeKey.computedData.code)) {
|
||||
dispatcher.send(InputKeyEvent.cancel(activeKey.computedData))
|
||||
}
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(retData))
|
||||
}
|
||||
popupManager.hide()
|
||||
} else {
|
||||
if (florisboard.textInputManager.inputEventDispatcher.isPressed(key.computedData.code)) {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
|
||||
if (dispatcher.isPressed(activeKey.computedData.code)) {
|
||||
if (pointer.hasTriggeredGestureMove) {
|
||||
dispatcher.send(InputKeyEvent.cancel(activeKey.computedData))
|
||||
} else {
|
||||
dispatcher.send(InputKeyEvent.up(activeKey.computedData))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pointer.activeKey = null
|
||||
}
|
||||
popupManager.hide()
|
||||
activePointerId = null
|
||||
hasTriggeredGestureMove = false
|
||||
shouldBlockNextUp = false
|
||||
invalidate()
|
||||
pointer.hasTriggeredGestureMove = false
|
||||
pointer.shouldBlockNextUp = false
|
||||
}
|
||||
|
||||
private fun onTouchCancelInternal(event: MotionEvent, pointerIndex: Int, pointerId: Int) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "index=$pointerIndex id=$pointerId event=$event" }
|
||||
private fun onTouchCancelInternal(event: MotionEvent, pointer: TouchPointer) {
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "pointer=$pointer" }
|
||||
val florisboard = florisboard ?: return
|
||||
pointer.longPressJob?.cancel()
|
||||
pointer.longPressJob = null
|
||||
|
||||
longPressHandler.cancelAll()
|
||||
val key = activeKey
|
||||
if (key != null) {
|
||||
key.isPressed = false
|
||||
if (activeKey?.computedData?.code != KeyCode.SHIFT) {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
|
||||
val activeKey = pointer.activeKey
|
||||
if (activeKey != null) {
|
||||
activeKey.isPressed = false
|
||||
florisboard.textInputManager.inputEventDispatcher.let { dispatcher ->
|
||||
dispatcher.send(InputKeyEvent.cancel(activeKey.computedData))
|
||||
}
|
||||
if (popupManager.isSuitableForPopups(activeKey)) {
|
||||
popupManager.hide()
|
||||
}
|
||||
pointer.activeKey = null
|
||||
}
|
||||
popupManager.hide()
|
||||
activePointerId = null
|
||||
hasTriggeredGestureMove = false
|
||||
shouldBlockNextUp = false
|
||||
invalidate()
|
||||
pointer.hasTriggeredGestureMove = false
|
||||
pointer.shouldBlockNextUp = false
|
||||
}
|
||||
|
||||
override fun onSwipe(event: SwipeGesture.Event): Boolean {
|
||||
val florisboard = florisboard ?: return false
|
||||
val initialKey = initialKey ?: return false
|
||||
if (activePointerId != event.pointerId) return false
|
||||
val pointer = pointerMap.findById(event.pointerId) ?: return false
|
||||
val initialKey = pointer.initialKey ?: return false
|
||||
val activeKey = pointer.activeKey ?: return false
|
||||
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW)
|
||||
|
||||
return when (initialKey.computedData.code) {
|
||||
KeyCode.DELETE -> handleDeleteSwipe(event)
|
||||
KeyCode.SPACE -> handleSpaceSwipe(event)
|
||||
else -> when {
|
||||
initialKey.computedData.code == KeyCode.SHIFT && activeKey?.computedData?.code == KeyCode.SPACE &&
|
||||
initialKey.computedData.code == KeyCode.SHIFT && activeKey.computedData.code == KeyCode.SPACE &&
|
||||
event.type == SwipeGesture.Type.TOUCH_MOVE -> handleSpaceSwipe(event)
|
||||
initialKey.computedData.code == KeyCode.SHIFT && activeKey?.computedData?.code != KeyCode.SHIFT &&
|
||||
initialKey.computedData.code == KeyCode.SHIFT && activeKey.computedData.code != KeyCode.SHIFT &&
|
||||
event.type == SwipeGesture.Type.TOUCH_UP -> {
|
||||
activeKey?.let {
|
||||
activeKey.let {
|
||||
florisboard.textInputManager.inputEventDispatcher.send(
|
||||
InputKeyEvent.up(popupManager.getActiveKeyData(it) ?: it.computedData)
|
||||
InputKeyEvent.up(popupManager.getActiveKeyData(it, prefs.keyboard.getKeyHintConfiguration()) ?: it.computedData)
|
||||
)
|
||||
}
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(TextKeyData.SHIFT))
|
||||
true
|
||||
}
|
||||
initialKey.computedData.code > KeyCode.SPACE && !popupManager.isShowingExtendedPopup -> when {
|
||||
!prefs.glide.enabled && !hasTriggeredGestureMove -> when (event.type) {
|
||||
!prefs.glide.enabled && !pointer.hasTriggeredGestureMove -> when (event.type) {
|
||||
SwipeGesture.Type.TOUCH_UP -> {
|
||||
val swipeAction = when (event.direction) {
|
||||
SwipeGesture.Direction.UP -> prefs.gestures.swipeUp
|
||||
@@ -528,6 +575,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
|
||||
private fun handleDeleteSwipe(event: SwipeGesture.Event): Boolean {
|
||||
val florisboard = florisboard ?: return false
|
||||
val pointer = pointerMap.findById(event.pointerId) ?: return false
|
||||
|
||||
return when (event.type) {
|
||||
SwipeGesture.Type.TOUCH_MOVE -> when (prefs.gestures.deleteKeySwipeLeft) {
|
||||
@@ -540,7 +588,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
(selection.end + event.absUnitCountX + 1).coerceIn(0, selection.end),
|
||||
selection.end
|
||||
)
|
||||
shouldBlockNextUp = true
|
||||
pointer.shouldBlockNextUp = true
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -550,7 +598,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
florisboard.activeEditorInstance.apply {
|
||||
leftAppendWordToSelection()
|
||||
}
|
||||
shouldBlockNextUp = true
|
||||
pointer.shouldBlockNextUp = true
|
||||
true
|
||||
}
|
||||
SwipeGesture.Direction.RIGHT -> {
|
||||
@@ -558,7 +606,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
florisboard.activeEditorInstance.apply {
|
||||
leftPopWordFromSelection()
|
||||
}
|
||||
shouldBlockNextUp = true
|
||||
pointer.shouldBlockNextUp = true
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
@@ -580,47 +628,66 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
|
||||
private fun handleSpaceSwipe(event: SwipeGesture.Event): Boolean {
|
||||
val florisboard = florisboard ?: return false
|
||||
val pointer = pointerMap.findById(event.pointerId) ?: return false
|
||||
|
||||
return when (event.type) {
|
||||
SwipeGesture.Type.TOUCH_MOVE -> when (event.direction) {
|
||||
SwipeGesture.Direction.LEFT -> {
|
||||
if (prefs.gestures.spaceBarSwipeLeft == SwipeAction.MOVE_CURSOR_LEFT) {
|
||||
abs(event.relUnitCountX).let {
|
||||
val count = if (!hasTriggeredGestureMove) { it - 1 } else { it }
|
||||
val count = if (!pointer.hasTriggeredGestureMove) { it - 1 } else { it }
|
||||
if (count > 0) {
|
||||
florisboard.keyPressVibrate(isMovingGestureEffect = true)
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(
|
||||
TextKeyData.ARROW_LEFT, count))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
|
||||
}
|
||||
true
|
||||
}
|
||||
SwipeGesture.Direction.RIGHT -> {
|
||||
if (prefs.gestures.spaceBarSwipeRight == SwipeAction.MOVE_CURSOR_RIGHT) {
|
||||
abs(event.relUnitCountX).let {
|
||||
val count = if (!hasTriggeredGestureMove) { it - 1 } else { it }
|
||||
val count = if (!pointer.hasTriggeredGestureMove) { it - 1 } else { it }
|
||||
if (count > 0) {
|
||||
florisboard.keyPressVibrate(isMovingGestureEffect = true)
|
||||
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(
|
||||
TextKeyData.ARROW_RIGHT, count))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeRight)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> true // To prevent the popup display of nearby keys
|
||||
}
|
||||
SwipeGesture.Type.TOUCH_UP -> {
|
||||
if (event.absUnitCountY.times(-1) > 6) {
|
||||
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeUp)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
SwipeGesture.Type.TOUCH_UP -> when (event.direction) {
|
||||
SwipeGesture.Direction.LEFT -> {
|
||||
prefs.gestures.spaceBarSwipeLeft.let {
|
||||
if (it != SwipeAction.MOVE_CURSOR_LEFT) {
|
||||
florisboard.executeSwipeAction(it)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
SwipeGesture.Direction.RIGHT -> {
|
||||
prefs.gestures.spaceBarSwipeRight.let {
|
||||
if (it != SwipeAction.MOVE_CURSOR_RIGHT) {
|
||||
florisboard.executeSwipeAction(it)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (event.absUnitCountY < -6) {
|
||||
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeUp)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -702,9 +769,10 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
var spaceKey: TextKey? = null
|
||||
if (isRecomputingRequested) {
|
||||
isRecomputingRequested = false
|
||||
val keyHintConfiguration = prefs.keyboard.getKeyHintConfiguration()
|
||||
for (key in keyboard.keys()) {
|
||||
key.compute(internalComputingEvaluator)
|
||||
computeLabelsAndDrawables(key)
|
||||
computeLabelsAndDrawables(key, keyHintConfiguration)
|
||||
if (key.computedData.code == KeyCode.SPACE) {
|
||||
spaceKey = key
|
||||
}
|
||||
@@ -738,9 +806,9 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
spaceKey.visibleLabelBounds.width().toFloat(),
|
||||
spaceKey.visibleLabelBounds.height().toFloat(),
|
||||
spaceKey.label ?: "X",
|
||||
fontSizeMultiplier.coerceAtMost(1.0)
|
||||
fontSizeMultiplier
|
||||
)
|
||||
labelPaintSpaceTextSize = labelPaint.textSize
|
||||
labelPaintSpaceTextSize = labelPaint.textSize.coerceAtMost(resources.getDimension(R.dimen.key_space_textSize))
|
||||
}
|
||||
|
||||
setTextSizeFor(
|
||||
@@ -826,9 +894,9 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
}
|
||||
|
||||
if (isPreviewMode) {
|
||||
backgroundDrawable.apply {
|
||||
setBounds(0, 0, measuredWidth, measuredHeight)
|
||||
draw(canvas)
|
||||
backgroundDrawable.let {
|
||||
it.setBounds(0, 0, measuredWidth, measuredHeight)
|
||||
it.draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,8 +985,8 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
}
|
||||
}
|
||||
|
||||
keyBackgroundDrawable.apply {
|
||||
setBounds(
|
||||
keyBackgroundDrawable.let {
|
||||
it.setBounds(
|
||||
key.visibleBounds.left,
|
||||
if (isBorderless) {
|
||||
(key.visibleBounds.top + key.visibleBounds.height() * 0.12).toInt()
|
||||
@@ -932,19 +1000,19 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
key.visibleBounds.bottom
|
||||
}
|
||||
)
|
||||
elevation = if (shouldShowBorder) 4.0f else 0.0f
|
||||
paint.color = keyBackground.toSolidColor().color
|
||||
draw(canvas)
|
||||
it.fillColor = ColorStateList.valueOf(keyBackground.toSolidColor().color)
|
||||
//it.elevation = if (shouldShowBorder) 6.0f else 0.0f
|
||||
it.draw(canvas)
|
||||
}
|
||||
|
||||
val label = key.label
|
||||
if (label != null) {
|
||||
labelPaint.apply {
|
||||
color = keyForeground.toSolidColor().color
|
||||
labelPaint.let {
|
||||
it.color = keyForeground.toSolidColor().color
|
||||
if (computedKeyboard?.mode == KeyboardMode.CHARACTERS && key.computedData.code == KeyCode.SPACE) {
|
||||
alpha = 120
|
||||
it.alpha = 120
|
||||
}
|
||||
textSize = when (key.computedData.code) {
|
||||
it.textSize = when (key.computedData.code) {
|
||||
KeyCode.SPACE -> labelPaintSpaceTextSize
|
||||
KeyCode.VIEW_CHARACTERS,
|
||||
KeyCode.VIEW_SYMBOLS,
|
||||
@@ -954,28 +1022,28 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
else -> labelPaintTextSize
|
||||
}
|
||||
val centerX = key.visibleLabelBounds.exactCenterX()
|
||||
val centerY = key.visibleLabelBounds.exactCenterY() + (labelPaint.textSize - labelPaint.descent()) / 2
|
||||
val centerY = key.visibleLabelBounds.exactCenterY() + (it.textSize - it.descent()) / 2
|
||||
if (label.contains("\n")) {
|
||||
// Even if more lines may be existing only the first 2 are shown
|
||||
val labelLines = label.split("\n")
|
||||
val verticalAdjustment = key.visibleBounds.height() * 0.18f
|
||||
canvas.drawText(labelLines[0], centerX, centerY - verticalAdjustment, labelPaint)
|
||||
canvas.drawText(labelLines[1], centerX, centerY + verticalAdjustment, labelPaint)
|
||||
canvas.drawText(labelLines[0], centerX, centerY - verticalAdjustment, it)
|
||||
canvas.drawText(labelLines[1], centerX, centerY + verticalAdjustment, it)
|
||||
} else {
|
||||
canvas.drawText(label, centerX, centerY, labelPaint)
|
||||
canvas.drawText(label, centerX, centerY, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val hintedLabel = key.hintedLabel
|
||||
if (hintedLabel != null) {
|
||||
hintedLabelPaint.apply {
|
||||
color = keyForeground.toSolidColor().color
|
||||
alpha = 170
|
||||
textSize = hintedLabelPaintTextSize
|
||||
hintedLabelPaint.let {
|
||||
it.color = keyForeground.toSolidColor().color
|
||||
it.alpha = 170
|
||||
it.textSize = hintedLabelPaintTextSize
|
||||
val centerX = key.visibleBounds.left + key.visibleBounds.width() * 5.0f / 6.0f
|
||||
val centerY = key.visibleBounds.top + key.visibleBounds.height() * 1.0f / 6.0f + (hintedLabelPaint.textSize - hintedLabelPaint.descent()) / 2
|
||||
canvas.drawText(hintedLabel, centerX, centerY, hintedLabelPaint)
|
||||
val centerY = key.visibleBounds.top + key.visibleBounds.height() * 1.0f / 6.0f + (it.textSize - it.descent()) / 2
|
||||
canvas.drawText(hintedLabel, centerX, centerY, it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,7 +1060,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
/**
|
||||
* Computes the labels and drawables needed to draw the key.
|
||||
*/
|
||||
private fun computeLabelsAndDrawables(key: TextKey) {
|
||||
private fun computeLabelsAndDrawables(key: TextKey, keyHintConfiguration: KeyHintConfiguration) {
|
||||
// Reset attributes first to avoid invalid states if not updated
|
||||
key.label = null
|
||||
key.hintedLabel = null
|
||||
@@ -1003,12 +1071,8 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
&& data.code != KeyCode.HALF_SPACE && data.code != KeyCode.KESHIDA || data.type == KeyType.NUMERIC
|
||||
) {
|
||||
key.label = data.asString(isForDisplay = true)
|
||||
val hint = key.computedPopups.hint
|
||||
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED && hint?.type == KeyType.NUMERIC) {
|
||||
key.hintedLabel = hint.asString(isForDisplay = true)
|
||||
}
|
||||
if (prefs.keyboard.hintedSymbolsMode != KeyHintMode.DISABLED && hint?.type == KeyType.CHARACTER) {
|
||||
key.hintedLabel = hint.asString(isForDisplay = true)
|
||||
key.computedPopups.getPopupKeys(keyHintConfiguration).hint?.asString(isForDisplay = true).let {
|
||||
key.hintedLabel = it
|
||||
}
|
||||
} else {
|
||||
when (data.code) {
|
||||
@@ -1202,4 +1266,26 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private class TouchPointer : Pointer() {
|
||||
var initialKey: TextKey? = null
|
||||
var activeKey: TextKey? = null
|
||||
var longPressJob: Job? = null
|
||||
var hasTriggeredGestureMove: Boolean = false
|
||||
var shouldBlockNextUp: Boolean = false
|
||||
|
||||
override fun reset() {
|
||||
super.reset()
|
||||
initialKey = null
|
||||
activeKey = null
|
||||
longPressJob?.cancel()
|
||||
longPressJob = null
|
||||
hasTriggeredGestureMove = false
|
||||
shouldBlockNextUp = false
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${TouchPointer::class.simpleName} { id=$id, index=$index, initialKey=$initialKey, activeKey=$activeKey }"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,18 @@ class LayoutManager {
|
||||
|
||||
val mainLayout = loadLayoutAsync(main).await().getOrNull()
|
||||
val modifierToLoad = if (mainLayout?.modifier != null) {
|
||||
LTN(LayoutType.CHARACTERS_MOD, mainLayout.modifier)
|
||||
val layoutType = when (mainLayout.type) {
|
||||
LayoutType.SYMBOLS -> {
|
||||
LayoutType.SYMBOLS_MOD
|
||||
}
|
||||
LayoutType.SYMBOLS2 -> {
|
||||
LayoutType.SYMBOLS2_MOD
|
||||
}
|
||||
else -> {
|
||||
LayoutType.CHARACTERS_MOD
|
||||
}
|
||||
}
|
||||
LTN(layoutType, mainLayout.modifier)
|
||||
} else {
|
||||
modifier
|
||||
}
|
||||
@@ -207,22 +218,21 @@ class LayoutManager {
|
||||
// Add hints to keys
|
||||
if (keyboardMode == KeyboardMode.CHARACTERS) {
|
||||
val symbolsComputedArrangement = computeKeyboardAsync(KeyboardMode.SYMBOLS, subtype).await().arrangement
|
||||
val minRow = if (prefs.keyboard.numberRow) { 1 } else { 0 }
|
||||
// number row hint always happens on first row
|
||||
if (prefs.keyboard.hintedNumberRowMode != KeyHintMode.DISABLED) {
|
||||
val row = computedArrangement[0]
|
||||
val symbolRow = symbolsComputedArrangement[0]
|
||||
addRowHints(row, symbolRow, KeyType.NUMERIC)
|
||||
}
|
||||
// all other symbols are added bottom-aligned
|
||||
val rOffset = computedArrangement.size - symbolsComputedArrangement.size
|
||||
for ((r, row) in computedArrangement.withIndex()) {
|
||||
if (r >= (3 + minRow) || r < minRow) {
|
||||
if (r < rOffset) {
|
||||
continue
|
||||
}
|
||||
val symbolRow = symbolsComputedArrangement.getOrNull(r - minRow)
|
||||
val symbolRow = symbolsComputedArrangement.getOrNull(r - rOffset)
|
||||
if (symbolRow != null) {
|
||||
for ((k, key) in row.withIndex()) {
|
||||
val symbol = symbolRow.getOrNull(k)?.data?.computeTextKeyData(DefaultTextComputingEvaluator)
|
||||
val type = (key.data as? TextKeyData)?.type ?: KeyType.UNSPECIFIED
|
||||
if (r == minRow && type == KeyType.CHARACTER && symbol?.type == KeyType.NUMERIC) {
|
||||
key.computedHint = symbol
|
||||
} else if (r > minRow && type == KeyType.CHARACTER && symbol?.type == KeyType.CHARACTER) {
|
||||
key.computedHint = symbol
|
||||
}
|
||||
}
|
||||
addRowHints(row, symbolRow, KeyType.CHARACTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,6 +250,27 @@ class LayoutManager {
|
||||
)
|
||||
}
|
||||
|
||||
private fun addRowHints(main: Array<TextKey>, hint: Array<TextKey>, hintType: KeyType) {
|
||||
for ((k,key) in main.withIndex()) {
|
||||
val hintKey = hint.getOrNull(k)?.data?.computeTextKeyData(DefaultTextComputingEvaluator)
|
||||
if (hintKey?.type != hintType) {
|
||||
continue
|
||||
}
|
||||
|
||||
when (hintType) {
|
||||
KeyType.CHARACTER -> {
|
||||
key.computedSymbolHint = hintKey
|
||||
}
|
||||
KeyType.NUMERIC -> {
|
||||
key.computedNumberHint = hintKey
|
||||
}
|
||||
else -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a layout for [keyboardMode] based on the given [subtype] and returns it.
|
||||
*
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.core.graphics.ColorUtils
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionList
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeValue
|
||||
@@ -100,7 +101,7 @@ class CandidateView : View, ThemeManager.OnThemeUpdatedListener {
|
||||
themeManager = ThemeManager.defaultOrNull()
|
||||
themeManager?.registerOnThemeUpdatedListener(this)
|
||||
florisClipboardManager = FlorisClipboardManager.getInstanceOrNull()
|
||||
updateCandidates(candidates)
|
||||
recomputeCandidates()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
@@ -113,7 +114,7 @@ class CandidateView : View, ThemeManager.OnThemeUpdatedListener {
|
||||
velocityTracker = null
|
||||
}
|
||||
|
||||
fun updateCandidates(newCandidates: List<String>?) {
|
||||
fun updateCandidates(newCandidates: SuggestionList?) {
|
||||
candidates.clear()
|
||||
if (newCandidates != null) {
|
||||
candidates.addAll(newCandidates)
|
||||
|
||||
@@ -17,17 +17,25 @@
|
||||
package dev.patrickgold.florisboard.ime.text.smartbar
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.Size
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.widget.inline.InlineContentView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.children
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.databinding.SmartbarBinding
|
||||
import dev.patrickgold.florisboard.debug.*
|
||||
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.Preferences
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionList
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
@@ -38,6 +46,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
@@ -64,6 +73,7 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
binding.quickActionToggle.rotation = if (v) 180.0f else 0.0f
|
||||
field = v
|
||||
}
|
||||
private var isShowingInlineSuggestions: Boolean = false
|
||||
|
||||
private lateinit var binding: SmartbarBinding
|
||||
private var indexedActionStartArea: MutableList<Int> = mutableListOf()
|
||||
@@ -217,7 +227,6 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
* editor instance, etc. Passes the evaluated attributes to [configureFeatureVisibility].
|
||||
*/
|
||||
fun updateSmartbarState() {
|
||||
//binding.clipboardCursorRow.updateVisibility()
|
||||
binding.candidates.updateDisplaySettings(prefs.suggestion.displayMode, prefs.suggestion.clipboardContentTimeout * 1_000)
|
||||
when (florisboard) {
|
||||
null -> configureFeatureVisibility(
|
||||
@@ -236,21 +245,22 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
KeyboardMode.EDITING -> R.id.back_button
|
||||
else -> R.id.quick_action_toggle
|
||||
},
|
||||
mainAreaId = when (florisboard.textInputManager.keyVariation) {
|
||||
KeyVariation.PASSWORD -> R.id.number_row
|
||||
else -> when (isQuickActionsVisible) {
|
||||
true -> R.id.quick_actions
|
||||
else -> when (florisboard.textInputManager.getActiveKeyboardMode()) {
|
||||
KeyboardMode.EDITING -> null
|
||||
KeyboardMode.NUMERIC,
|
||||
KeyboardMode.PHONE,
|
||||
KeyboardMode.PHONE2 -> R.id.clipboard_cursor_row
|
||||
else -> when {
|
||||
florisboard.activeEditorInstance.isComposingEnabled &&
|
||||
florisboard.activeEditorInstance.selection.isCursorMode
|
||||
-> R.id.candidates
|
||||
else -> R.id.clipboard_cursor_row
|
||||
}
|
||||
mainAreaId = when {
|
||||
isQuickActionsVisible -> R.id.quick_actions
|
||||
isShowingInlineSuggestions -> R.id.inline_suggestions
|
||||
florisboard.textInputManager.keyVariation == KeyVariation.PASSWORD -> {
|
||||
if (!prefs.keyboard.numberRow) R.id.number_row else null
|
||||
}
|
||||
else -> when (florisboard.textInputManager.getActiveKeyboardMode()) {
|
||||
KeyboardMode.EDITING -> null
|
||||
KeyboardMode.NUMERIC,
|
||||
KeyboardMode.PHONE,
|
||||
KeyboardMode.PHONE2 -> R.id.clipboard_cursor_row
|
||||
else -> when {
|
||||
florisboard.activeEditorInstance.isComposingEnabled &&
|
||||
florisboard.activeEditorInstance.selection.isCursorMode
|
||||
-> R.id.candidates
|
||||
else -> R.id.clipboard_cursor_row
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -275,7 +285,7 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
}
|
||||
}
|
||||
|
||||
fun setCandidateSuggestionWords(suggestionInitDate: Long, suggestions: List<String>?) {
|
||||
fun setCandidateSuggestionWords(suggestionInitDate: Long, suggestions: SuggestionList?) {
|
||||
if (suggestionInitDate > lastSuggestionInitDate) {
|
||||
lastSuggestionInitDate = suggestionInitDate
|
||||
binding.candidates.updateCandidates(suggestions)
|
||||
@@ -286,6 +296,68 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the inline suggestions and triggers thw Smartbar update cycle.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun clearInlineSuggestions() {
|
||||
updateInlineSuggestionStrip(listOf())
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the given inline suggestions. Once all provided views are ready, the suggestions
|
||||
* strip is updated and the Smartbar update cycle is triggered.
|
||||
*
|
||||
* @param inlineSuggestions A collection of inline suggestions to be inflated and shown.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun showInlineSuggestions(inlineSuggestions: Collection<InlineSuggestion>) {
|
||||
if (inlineSuggestions.isEmpty()) {
|
||||
updateInlineSuggestionStrip(listOf())
|
||||
} else {
|
||||
val suggestionMap: TreeMap<Int, InlineContentView?> = TreeMap()
|
||||
for ((i, inlineSuggestion) in inlineSuggestions.withIndex()) {
|
||||
val size = Size(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
try {
|
||||
inlineSuggestion.inflate(context, size, context.mainExecutor) { suggestionView ->
|
||||
flogDebug { "New inline suggestion view ready" }
|
||||
suggestionMap[i] = suggestionView
|
||||
if (suggestionMap.size >= inlineSuggestions.size) {
|
||||
updateInlineSuggestionStrip(suggestionMap.values)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
flogWarning { "Failed to inflate inline suggestion: $e" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the suggestion strip with given inline content views and triggers the Smartbar
|
||||
* update cycle.
|
||||
*
|
||||
* @param suggestionViews A collection of inline content views to show.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun updateInlineSuggestionStrip(suggestionViews: Collection<InlineContentView?>) {
|
||||
flogDebug { "Updating the inline suggestion strip with ${suggestionViews.size} items" }
|
||||
binding.inlineSuggestionsStrip.removeAllViews()
|
||||
if (suggestionViews.isEmpty()) {
|
||||
isShowingInlineSuggestions = false
|
||||
return
|
||||
} else {
|
||||
for (suggestionView in suggestionViews) {
|
||||
if (suggestionView == null) {
|
||||
continue
|
||||
}
|
||||
binding.inlineSuggestionsStrip.addView(suggestionView)
|
||||
}
|
||||
isShowingInlineSuggestions = true
|
||||
}
|
||||
updateSmartbarState()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
||||
val heightSize = MeasureSpec.getSize(heightMeasureSpec).toFloat()
|
||||
|
||||
@@ -16,14 +16,26 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.autofill.inline.UiVersions
|
||||
import androidx.autofill.inline.common.ImageViewStyle
|
||||
import androidx.autofill.inline.common.TextViewStyle
|
||||
import androidx.autofill.inline.common.ViewStyle
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.Preferences
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetManager
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetRef
|
||||
@@ -305,6 +317,82 @@ class ThemeManager private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new inline suggestion UI bundle based on the attributes of the given [theme].
|
||||
*
|
||||
* @param context The context of the parent view/controller.
|
||||
* @param theme The theme from which the color attributes should be fetched. Defaults to [activeTheme].
|
||||
*
|
||||
* @return A bundle containing all necessary attributes for the inline suggestion views to properly display.
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun createInlineSuggestionUiStyleBundle(context: Context, theme: Theme = activeTheme): Bundle {
|
||||
val bgColor = theme.getAttr(Theme.Attr.SMARTBAR_BUTTON_BACKGROUND).toSolidColor().color
|
||||
val fgColor = theme.getAttr(Theme.Attr.SMARTBAR_BUTTON_FOREGROUND).toSolidColor().color
|
||||
val bgDrawableId = R.drawable.chip_background
|
||||
val stylesBuilder = UiVersions.newStylesBuilder()
|
||||
val style = InlineSuggestionUi.newStyleBuilder()
|
||||
.setSingleIconChipStyle(
|
||||
ViewStyle.Builder()
|
||||
.setBackground(
|
||||
Icon.createWithResource(context, bgDrawableId).setTint(bgColor)
|
||||
)
|
||||
.setPadding(0, 0, 0, 0)
|
||||
.build()
|
||||
)
|
||||
.setChipStyle(
|
||||
ViewStyle.Builder()
|
||||
.setBackground(
|
||||
Icon.createWithResource(context, bgDrawableId).setTint(bgColor)
|
||||
)
|
||||
.setPadding(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_bottom).toInt(),
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.setStartIconStyle(
|
||||
ImageViewStyle.Builder()
|
||||
.setLayoutMargin(0, 0, 0, 0)
|
||||
.build()
|
||||
)
|
||||
.setTitleStyle(
|
||||
TextViewStyle.Builder()
|
||||
.setLayoutMargin(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_bottom).toInt(),
|
||||
)
|
||||
.setTextColor(fgColor)
|
||||
.setTextSize(16f)
|
||||
.build()
|
||||
)
|
||||
.setSubtitleStyle(
|
||||
TextViewStyle.Builder()
|
||||
.setLayoutMargin(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_bottom).toInt(),
|
||||
)
|
||||
.setTextColor(ColorUtils.setAlphaComponent(fgColor, 150))
|
||||
.setTextSize(14f)
|
||||
.build()
|
||||
)
|
||||
.setEndIconStyle(
|
||||
ImageViewStyle.Builder()
|
||||
.setLayoutMargin(0, 0, 0, 0)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
stylesBuilder.addStyle(style)
|
||||
return stylesBuilder.build()
|
||||
}
|
||||
|
||||
data class RemoteColors(
|
||||
val packageName: String,
|
||||
val colorPrimary: ThemeValue.SolidColor?,
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.view
|
||||
|
||||
import androidx.annotation.RestrictTo
|
||||
|
||||
/**
|
||||
* A simple helper object managing touch pointer objects. This class is designed to hold
|
||||
* at max [capacity] at once. It tries to reduce the need to recreate objects and to resize
|
||||
* arrays by creating a fixed-size list and by reusing pointers. This map supports iterating
|
||||
* over all active pointers.
|
||||
*
|
||||
* @property capacity The capacity of this map, determining the maximum number of pointers this
|
||||
* map can hold at once. This value must be greater than or equal to one. Should a smaller capacity
|
||||
* be passed, automatically the minimum capacity `1` is assumed.
|
||||
* @param init The initializer for each pointer. Note that [Pointer.reset] is called before
|
||||
* storing the new object, to ensure that this pointer is not initialized with some pointer data.
|
||||
*/
|
||||
class PointerMap<P : Pointer>(val capacity: Int = 4, init: (Int) -> P) : Iterable<P> {
|
||||
/**
|
||||
* The internal list of pointers, is not intended for public access.
|
||||
*/
|
||||
private val pointers: List<P> = List(capacity.coerceAtLeast(1)) { i ->
|
||||
init(i).also { pointer -> pointer.reset() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new pointer with given [id] and [index] and returns it. If this map is already at max
|
||||
* capacity, null is returned and the pointer could not be added.
|
||||
*
|
||||
* @param id The id of the pointer to add.
|
||||
* @param index The index of the pointer to add.
|
||||
*
|
||||
* @return The newly added pointer or null if the map is already full.
|
||||
*/
|
||||
fun add(id: Int, index: Int): P? {
|
||||
for (pointer in pointers) {
|
||||
if (pointer.isNotUsed) {
|
||||
pointer.id = id
|
||||
pointer.index = index
|
||||
return pointer
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears this map and resets all pointers.
|
||||
*/
|
||||
fun clear() {
|
||||
for (pointer in pointers) {
|
||||
pointer.reset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a pointer by given [id].
|
||||
*
|
||||
* @param id The id of the pointer which should be found.
|
||||
*
|
||||
* @return The pointer with given [id] or null.
|
||||
*/
|
||||
fun findById(id: Int): P? {
|
||||
for (pointer in pointers) {
|
||||
if (pointer.id == id) {
|
||||
return pointer
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer from the internal array based on the internal array index. This method
|
||||
* is intended to be used only by the [PointerIterator].
|
||||
*
|
||||
* @param index
|
||||
*
|
||||
* @return The pointer for given index or null, excluding unused pointers.
|
||||
*/
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
||||
fun get(index: Int): P? {
|
||||
val pointer = pointers.getOrNull(index)
|
||||
if (pointer != null && pointer.isUsed) {
|
||||
return pointer
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<P> {
|
||||
return PointerIterator(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a pointer with given [id] and returns a boolean result.
|
||||
*
|
||||
* @param id The id of the pointer to remove. If the id is not existent, noting happens.
|
||||
*
|
||||
* @return True if a pointer was removed, false otherwise.
|
||||
*/
|
||||
fun removeById(id: Int): Boolean {
|
||||
for (pointer in pointers) {
|
||||
if (pointer.id == id) {
|
||||
pointer.reset()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this map (only counting active pointers). This value is anywhere
|
||||
* between 0 and [capacity].
|
||||
*/
|
||||
val size: Int
|
||||
get() = pointers.count { it.isUsed }
|
||||
}
|
||||
|
||||
class PointerIterator<P : Pointer>(private val pointerMap: PointerMap<P>) : Iterator<P> {
|
||||
private var index: Int = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
do {
|
||||
if (pointerMap.get(index) != null) {
|
||||
return true
|
||||
}
|
||||
} while (++index < pointerMap.capacity)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun next(): P {
|
||||
return pointerMap.get(index++)!!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract touch pointer definition.
|
||||
*/
|
||||
abstract class Pointer {
|
||||
companion object {
|
||||
const val UNUSED_P: Int = -1
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of this pointer, corresponds to the motion event this pointer originated.
|
||||
*/
|
||||
var id: Int = UNUSED_P
|
||||
|
||||
/**
|
||||
* The index of this pointer, corresponds to the motion event this pointer originated.
|
||||
*/
|
||||
var index: Int = UNUSED_P
|
||||
|
||||
/**
|
||||
* True if this pointer is used and active, false otherwise.
|
||||
*/
|
||||
val isUsed: Boolean
|
||||
get() = id >= 0
|
||||
|
||||
/**
|
||||
* False if this pointer is used and active, true otherwise.
|
||||
*/
|
||||
val isNotUsed: Boolean
|
||||
get() = !isUsed
|
||||
|
||||
/**
|
||||
* Resets this pointer to be used again.
|
||||
*/
|
||||
open fun reset() {
|
||||
id = UNUSED_P
|
||||
index = UNUSED_P
|
||||
}
|
||||
}
|
||||
@@ -17,17 +17,23 @@
|
||||
package dev.patrickgold.florisboard.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.databinding.AboutActivityBinding
|
||||
import dev.patrickgold.florisboard.ime.clip.FlorisClipboardManager
|
||||
import dev.patrickgold.florisboard.util.AppVersionUtils
|
||||
import dev.patrickgold.florisboard.util.checkIfImeIsSelected
|
||||
|
||||
class AboutActivity : AppCompatActivity() {
|
||||
private lateinit var binding: AboutActivityBinding
|
||||
@@ -43,7 +49,22 @@ class AboutActivity : AppCompatActivity() {
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
// Set app version string
|
||||
binding.appVersion.text = "v" + AppVersionUtils.getRawVersionName(this)
|
||||
val appVersion = "v" + AppVersionUtils.getRawVersionName(this)
|
||||
binding.appVersion.text = appVersion
|
||||
|
||||
// Set setOnLongClickListener for copying the version string
|
||||
binding.headArea.setOnLongClickListener {
|
||||
val isImeSelected = checkIfImeIsSelected(this)
|
||||
if (isImeSelected) {
|
||||
FlorisClipboardManager.getInstance().addNewPlaintext(appVersion)
|
||||
} else {
|
||||
val clipboard: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Florisboard version", appVersion)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
Toast.makeText(this, R.string.about__version_copied__title, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
|
||||
// Set onClickListeners for buttons
|
||||
binding.privacyPolicyButton.setOnClickListener {
|
||||
|
||||
@@ -98,6 +98,9 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
dialogView.languageSpinner.setOnSelectedListener { pos ->
|
||||
val selectedCode = subtypeManager.imeConfig.defaultSubtypesLanguageCodes[pos]
|
||||
val defaultSubtype = subtypeManager.getDefaultSubtypeForLocale(LocaleUtils.stringToLocale(selectedCode)) ?: return@setOnSelectedListener
|
||||
dialogView.composerSpinner.setSelection(
|
||||
subtypeManager.imeConfig.composerNames.indexOf(defaultSubtype.composerName).coerceAtLeast(0)
|
||||
)
|
||||
dialogView.currencySetSpinner.setSelection(
|
||||
subtypeManager.imeConfig.currencySetNames.indexOf(defaultSubtype.currencySetName).coerceAtLeast(0)
|
||||
)
|
||||
@@ -107,6 +110,9 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
)
|
||||
}
|
||||
}
|
||||
dialogView.composerSpinner.initItems(
|
||||
labels = subtypeManager.imeConfig.composerLabels
|
||||
)
|
||||
dialogView.currencySetSpinner.initItems(
|
||||
labels = subtypeManager.imeConfig.currencySetLabels
|
||||
)
|
||||
@@ -129,6 +135,7 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
// already exists.
|
||||
activeDialogWindow?.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
|
||||
val languageCode = subtypeManager.imeConfig.defaultSubtypesLanguageCodes[dialogView.languageSpinner.selectedItemPosition]
|
||||
val composerName = subtypeManager.imeConfig.composerNames[dialogView.composerSpinner.selectedItemPosition]
|
||||
val currencySetName = subtypeManager.imeConfig.currencySetNames[dialogView.currencySetSpinner.selectedItemPosition]
|
||||
val layoutMap = SubtypeLayoutMap(
|
||||
characters = layoutManager.getMetaNameListFor(LayoutType.CHARACTERS)[dialogView.charactersLayoutSpinner.selectedItemPosition],
|
||||
@@ -140,7 +147,7 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
phone = layoutManager.getMetaNameListFor(LayoutType.PHONE)[dialogView.phoneLayoutSpinner.selectedItemPosition],
|
||||
phone2 = layoutManager.getMetaNameListFor(LayoutType.PHONE2)[dialogView.phone2LayoutSpinner.selectedItemPosition],
|
||||
)
|
||||
val success = subtypeManager.addSubtype(LocaleUtils.stringToLocale(languageCode), currencySetName, layoutMap)
|
||||
val success = subtypeManager.addSubtype(LocaleUtils.stringToLocale(languageCode), composerName, currencySetName, layoutMap)
|
||||
if (!success) {
|
||||
dialogView.errorBox.setText(R.string.settings__localization__subtype_error_already_exists)
|
||||
dialogView.errorBox.visibility = View.VISIBLE
|
||||
@@ -161,6 +168,11 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
keys = subtypeManager.imeConfig.defaultSubtypesLanguageCodes,
|
||||
defaultSelectedKey = subtype.locale.toString()
|
||||
)
|
||||
dialogView.composerSpinner.initItems(
|
||||
labels = subtypeManager.imeConfig.composerLabels,
|
||||
keys = subtypeManager.imeConfig.composerNames,
|
||||
defaultSelectedKey = subtype.composerName
|
||||
)
|
||||
dialogView.currencySetSpinner.initItems(
|
||||
labels = subtypeManager.imeConfig.currencySetLabels,
|
||||
keys = subtypeManager.imeConfig.currencySetNames,
|
||||
@@ -180,6 +192,7 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
setView(dialogView.root)
|
||||
setPositiveButton(R.string.settings__localization__subtype_apply) { _, _ ->
|
||||
val languageCode = subtypeManager.imeConfig.defaultSubtypesLanguageCodes[dialogView.languageSpinner.selectedItemPosition]
|
||||
val composerName = subtypeManager.imeConfig.composerNames[dialogView.composerSpinner.selectedItemPosition]
|
||||
val currencySetName = subtypeManager.imeConfig.currencySetNames[dialogView.currencySetSpinner.selectedItemPosition]
|
||||
val layoutMap = SubtypeLayoutMap(
|
||||
characters = layoutManager.getMetaNameListFor(LayoutType.CHARACTERS)[dialogView.charactersLayoutSpinner.selectedItemPosition],
|
||||
@@ -194,6 +207,7 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
|
||||
subtypeManager.modifySubtypeWithSameId(Subtype(
|
||||
id = subtype.id,
|
||||
locale = LocaleUtils.stringToLocale(languageCode),
|
||||
composerName = composerName,
|
||||
currencySetName = currencySetName,
|
||||
layoutMap = layoutMap
|
||||
))
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package dev.patrickgold.florisboard.settings.fragments
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
@@ -27,6 +28,9 @@ import dev.patrickgold.florisboard.settings.UdmActivity
|
||||
class TypingInnerFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.prefs_typing)
|
||||
findPreference<Preference>(Preferences.Suggestion.API30_INLINE_SUGGESTIONS_ENABLED)?.let {
|
||||
it.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
||||
|
||||
@@ -25,7 +25,11 @@ object LocaleUtils {
|
||||
return when {
|
||||
string.contains(DELIMITER) -> {
|
||||
val lc = string.split(DELIMITER)
|
||||
Locale(lc[0], lc[1])
|
||||
if (lc.size == 3) {
|
||||
Locale(lc[0], lc[1], lc[2])
|
||||
} else {
|
||||
Locale(lc[0], lc[1])
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Locale(string)
|
||||
|
||||
41
app/src/main/res/drawable/chip_background.xml
Normal file
41
app/src/main/res/drawable/chip_background.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2020 The Android Open Source Project
|
||||
~ Copyright (C) 2021 Patrick Goldinger
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#20000000">
|
||||
<item
|
||||
android:bottom="4dp"
|
||||
android:left="4dp"
|
||||
android:right="4dp"
|
||||
android:shape="rectangle"
|
||||
android:top="4dp">
|
||||
<shape>
|
||||
<corners android:radius="32dp" />
|
||||
<solid android:color="#1F000000" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="5dp"
|
||||
android:left="5dp"
|
||||
android:right="5dp"
|
||||
android:shape="rectangle"
|
||||
android:top="5dp">
|
||||
<shape>
|
||||
<corners android:radius="32dp" />
|
||||
<solid android:color="#FFFFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
10
app/src/main/res/drawable/ripple_click.xml
Normal file
10
app/src/main/res/drawable/ripple_click.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/colorPrimary">
|
||||
<item android:id="@android:id/background">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="15dp" />
|
||||
<solid android:color="@color/windowBackground" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -18,30 +18,39 @@
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:background="@mipmap/floris_app_icon"
|
||||
<LinearLayout
|
||||
android:id="@+id/head_area"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:elevation="8dp"
|
||||
android:contentDescription="@string/about__app_icon_content_description"/>
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/ripple_click">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/floris_app_name"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"/>
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@mipmap/floris_app_icon"
|
||||
android:contentDescription="@string/about__app_icon_content_description"
|
||||
android:elevation="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="italic"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/floris_app_name"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="italic" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/license_button"
|
||||
|
||||
@@ -42,6 +42,26 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text="@string/settings__localization__subtype_composer" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/composer_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -112,8 +132,8 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings__localization__subtype_currency_set"
|
||||
android:paddingHorizontal="8dp"/>
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text="@string/settings__localization__subtype_currency_set" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/currency_set_spinner"
|
||||
|
||||
@@ -43,6 +43,18 @@
|
||||
android:id="@+id/candidates"
|
||||
style="@style/SmartbarContainer"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/inline_suggestions"
|
||||
style="@style/SmartbarContainer">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/inline_suggestions_strip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"/>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/quick_actions"
|
||||
style="@style/SmartbarContainer">
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">المزيد من الخيارات</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">المساعدة والملاحظات</string>
|
||||
<string name="settings__help" comment="General label for help buttons in Settings">مساعدة</string>
|
||||
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">الصفحة الرئيسية</string>
|
||||
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">الرئيسية</string>
|
||||
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">لوحة المفاتيح</string>
|
||||
<string name="settings__navigation__typing" comment="Long-press hint of bottom nav item Typing in Settings">الكتابة</string>
|
||||
<string name="settings__navigation__theme" comment="Long-press hint of bottom nav item Theme in Settings">المظهر</string>
|
||||
@@ -52,7 +52,7 @@
|
||||
<string name="settings__home__title" comment="Title of the Home fragment">مرحبا بكم في %s</string>
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">لم يتم تمكين FlorisBoard في النظام وبالتالي لن يكون متاحًا كطريقة إدخال في منتقي الإدخال. انقر هنا لحل هذه المشكلة.</string>
|
||||
<string name="settings__home__ime_not_selected" comment="Warning message shown in Home fragment when FlorisBoard is not selected as the default keyboard">لم يتم اختيار FlorisBoard كطريقة الإدخال الافتراضية. انقر هنا لحل هذه المشكلة.</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">شكرا على تجربة FlorisBoard! هذا المشروع لا يزال في قيد التطوير وبالتالي يفتقد بعض الميزات. إذا وجدت أي أخطاء أو تريد تقديم اقتراح ، فالرجاء مراجعة المشروع على GitHub وطرح مشكلتك. هذا يساعد في جعل FlorisBoard أفضل. شكرا جزيلا!</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">شكرا على تجربة FlorisBoard! هذا المشروع لا يزال قيد التطوير وبالتالي يفتقد بعض الميزات. إذا وجدت أي أخطاء أو تريد تقديم اقتراح ، فالرجاء مراجعة المشروع على GitHub وطرح مشكلتك. هذا يساعد في جعل FlorisBoard أفضل. شكرا جزيلا!</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">اللغات وتخطيطات لوحة المفاتيح</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">يبدو أنك لم تقم بإختيار أية أنواع فرعية. كبديل، سيتم استخدام النوع الفرعي English / QWERTY!</string>
|
||||
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">إضافة</string>
|
||||
@@ -102,9 +102,9 @@
|
||||
<string name="settings__theme_editor__type_label" comment="Label of type input">نوع</string>
|
||||
<string name="settings__theme_editor__add_group_dialog_title" comment="Title of the add group dialog in the theme editor">إضافة مجموعة</string>
|
||||
<string name="settings__theme_editor__edit_group_dialog_title" comment="Title of the edit group dialog in the theme editor">تحرير المجموعة</string>
|
||||
<string name="settings__theme_editor__add_attr_dialog_title" comment="Title of the add attribute dialog in the theme editor">اضافة الخاصيه</string>
|
||||
<string name="settings__theme_editor__add_attr_dialog_title" comment="Title of the add attribute dialog in the theme editor">إضافة خاصية</string>
|
||||
<string name="settings__theme_editor__edit_attr_dialog_title" comment="Title of the edit attribute dialog in the theme editor">تحرير الصفة</string>
|
||||
<string name="settings__theme_editor__edit_theme_name_dialog_title" comment="Title of the edit theme name dialog in the theme editor">تحرير اسم الثيم</string>
|
||||
<string name="settings__theme_editor__edit_theme_name_dialog_title" comment="Title of the edit theme name dialog in the theme editor">تحرير اسم السمة</string>
|
||||
<string name="settings__theme_editor__value_type_reference" comment="Theme value type">المرجع</string>
|
||||
<string name="settings__theme_editor__value_type_reference_group" comment="Theme value type sub-field">مجموعة</string>
|
||||
<string name="settings__theme_editor__value_type_reference_attr" comment="Theme value type sub-field">الخاصية</string>
|
||||
@@ -115,8 +115,8 @@
|
||||
<string name="settings__theme_editor__value_type_on_off_state" comment="Theme value type sub-field">الحالة</string>
|
||||
<string name="settings__theme_editor__value_type_other" comment="Theme value type">أخرى</string>
|
||||
<string name="settings__theme_editor__value_type_other_text" comment="Theme value type sub-field">النص</string>
|
||||
<string name="settings__theme_editor__value_preview_content_description" comment="Theme value preview content description">معاينو قيم الثيم</string>
|
||||
<string name="settings__theme_editor__error_theme_label_empty" comment="Error text for an empty theme label">يرجى ادخال اسم المظهر.</string>
|
||||
<string name="settings__theme_editor__value_preview_content_description" comment="Theme value preview content description">معاينة قيم السمة</string>
|
||||
<string name="settings__theme_editor__error_theme_label_empty" comment="Error text for an empty theme label">يرجى إدخال اسم المظهر.</string>
|
||||
<string name="settings__theme_editor__error_group_name" comment="Error text for an invalid group name">الرجاء إدخال اسم مجموعة يحتوي فقط على أحرف (a-z و / أو A-Z) ، والنقطتان (:) للتجميع الفرعي أو بالإضافة إلى الأرقام (0–9) ، والمده (~) والتسطير (_) للمفتاح ضع الكلمة المناسبة.</string>
|
||||
<string name="settings__theme_editor__error_group_name_empty" comment="Error text for an empty group name">الرجاء إدخال اسم المجموعة.</string>
|
||||
<string name="settings__theme_editor__error_group_name_already_exists" comment="Error text for a duplicate group name">اسم هذه المجموعة موجود بالفعل في هذا الموضوع. الرجاء اختيار واحد آخر.</string>
|
||||
@@ -156,8 +156,8 @@
|
||||
<string name="pref__keyboard__group_keys__label" comment="Preference group title">المفاتيح</string>
|
||||
<string name="pref__keyboard__number_row__label" comment="Preference title">صف الأعداد</string>
|
||||
<string name="pref__keyboard__number_row__summary" comment="Preference summary">إظهار صف الأعداد فوق تخطيط الحروف</string>
|
||||
<string name="pref__keyboard__hinted_number_row_mode__label" comment="Preference title">تلميح لصف الأعداد</string>
|
||||
<string name="pref__keyboard__hinted_symbols_mode__label" comment="Preference title">تلميح للرموز</string>
|
||||
<string name="pref__keyboard__hinted_number_row_mode__label" comment="Preference title">تلميحات صف الأعداد</string>
|
||||
<string name="pref__keyboard__hinted_symbols_mode__label" comment="Preference title">تلميحات للرموز</string>
|
||||
<string name="pref__keyboard__hint_mode__disabled" comment="Preference value">معطّل</string>
|
||||
<string name="pref__keyboard__hint_mode__enabled_hint_priority" comment="Preference value">مفعّل (الأولوية للتلميح)</string>
|
||||
<string name="pref__keyboard__hint_mode__enabled_accent_priority" comment="Preference value">مفعّل (الأولوية للحروف الإضافية)</string>
|
||||
@@ -169,9 +169,11 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">تغيير اللغة</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">تبديل تطبيق لوحة المفاتيح</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">ديناميكي: قم بالتبديل إلى الرموز التعبيرية / تبديل اللغة</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">عرض ايضا الرموز المنبثقة</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">أضف النوافذ المنبثقة للرمز إلى التخطيط الافتراضي</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">مضاعف حجم الخط (عمودي)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">مضاعف حجم الخط (أفقي)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">نظام التخطيط</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">التخطيط</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label" comment="Preference title">وضع اليد الواحدة</string>
|
||||
<string name="pref__keyboard__one_handed_mode__off" comment="Preference value">إيقاف</string>
|
||||
<string name="pref__keyboard__one_handed_mode__right" comment="Preference value">وضع اليد اليمنى</string>
|
||||
@@ -190,7 +192,7 @@
|
||||
<string name="pref__keyboard__height_factor__tall" comment="Preference value">طويل</string>
|
||||
<string name="pref__keyboard__height_factor__extra_tall" comment="Preference value">طويل جدا</string>
|
||||
<string name="pref__keyboard__height_factor__custom" comment="Preference value">مخصص</string>
|
||||
<string name="pref__keyboard__height_factor_custom__label" comment="Preference title">قيمة ارتفاع لوحة المفاتيح المخصصة</string>
|
||||
<string name="pref__keyboard__height_factor_custom__label" comment="Preference title">قيمة مخصصة لأرتفاع لوحة المفاتيح</string>
|
||||
<string name="pref__keyboard__bottom_offset_portrait__label" comment="Preference title">الإزاحة السفلية (عمودي)</string>
|
||||
<string name="pref__keyboard__bottom_offset_landscape__label" comment="Preference title">الإزاحة السفلية (عمودي)</string>
|
||||
<string name="pref__keyboard__key_spacing_vertical__label" comment="Preference title">تباعد المفاتيح (عموديًا)</string>
|
||||
@@ -199,8 +201,8 @@
|
||||
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">الصوت عند ضغط المفتاح</string>
|
||||
<string name="pref__keyboard__sound_volume__label" comment="Preference title">حجم الصوت عند ضغط المفتاح</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label" comment="Preference title">إهتزاز عند ضغط المفتاح</string>
|
||||
<string name="pref__keyboard__vibration_duration__label" comment="Preference title">إهتزاز عند الضغط على الزر</string>
|
||||
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">شدة الإهتزاز عند ضغط المفتاح</string>
|
||||
<string name="pref__keyboard__vibration_duration__label" comment="Preference title">اهتزاز عند الضغط على المفتاح</string>
|
||||
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">شدة الاهتزاز عند ضغط على المفتاح</string>
|
||||
<string name="pref__keyboard__popup_visible__label" comment="Preference title">رؤية النافذة المنبثقة</string>
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">إظهار النافذة المنبثقة عندما تضغط على مفتاح</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">مدة الضغط المطوّل على المفتاح</string>
|
||||
@@ -275,7 +277,7 @@
|
||||
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">مفتاح التحويل</string>
|
||||
<string name="pref__gestures__swipe_action__redo" comment="Preference value for swipe action">إعادة</string>
|
||||
<string name="pref__gestures__swipe_action__undo" comment="Preference value for swipe action">تراجع</string>
|
||||
<string name="pref__gestures__swipe_action__show_input_method_picker" comment="Preference value for swipe action">إظهار طريقة الإدخال</string>
|
||||
<string name="pref__gestures__swipe_action__show_input_method_picker" comment="Preference value for swipe action">إظهار مبدل طريقة الإدخال</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_keyboard" comment="Preference value for swipe action">التبديل إلى لوحة المفاتيح السابقة</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_subtype" comment="Preference value for swipe action">التبديل إلى النوع الفرعي السابق</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_next_subtype" comment="Preference value for swipe action">التبديل إلى النوع الفرعي التالي</string>
|
||||
@@ -286,7 +288,7 @@
|
||||
<string name="pref__gestures__space_bar_swipe_up__label" comment="Preference title">السحب للفوق من خلال مفتاح المسافة</string>
|
||||
<string name="pref__gestures__space_bar_swipe_left__label" comment="Preference title">السحب لليسار من خلال مفتاح المسافة</string>
|
||||
<string name="pref__gestures__space_bar_swipe_right__label" comment="Preference title">السحب لليمين من خلال مفتاح المسافة</string>
|
||||
<string name="pref__gestures__space_bar_long_press__label" comment="Preference title">الضغط المطول على شريط المسافه</string>
|
||||
<string name="pref__gestures__space_bar_long_press__label" comment="Preference title">الضغط المطول على شريط المسافة</string>
|
||||
<string name="pref__gestures__delete_key_swipe_left__label" comment="Preference title">السحب لليسار من خلال مفتاح الحذف</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__label" comment="Preference title">عتبة سرعة السحب</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_slow" comment="Preference value for swipe velocity threshold">بطيئة جداً</string>
|
||||
@@ -304,13 +306,13 @@
|
||||
<string name="pref__advanced__settings_theme__label" comment="Label of Settings theme preference in Advanced">إعدادات المظهر</string>
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">فاتح</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">داكن</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">إظهار أيقونة البرنامج في صفحة الهاتف الرئيسية</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">إظهار أيقونة البرنامج في درج التطبيقات</string>
|
||||
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">فرض الوضع الخاص</string>
|
||||
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">سيتم تعطيل أي ميزات تعمل مؤقتًا باستعمال بيانات الإدخال الخاصة بك</string>
|
||||
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">تفعيل أدوات المطور</string>
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">أدوات مصممة خصيصًا لتصحيح الأخطاء واستكشاف العلل وإصلاحها</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">إظهار إحصائيات كومة الذاكرة</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">يعرض طبقة تراكبية في الزاوية العلوية اليمنى لاستخدام ذاكرة الكومة والحجم الأقصى لها</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">يعرض طبقة تراكبية في الزاوية العلوية اليمنى على لوحة المفاتيح لاستخدام ذاكرة الكومة والحجم الأقصى لها</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">مسح قاعدة بيانات القاموس الداخلي للمستخدم</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">يمسح كل الكلمات من جدول قاعدة بيانات القاموس</string>
|
||||
<!-- About UI strings -->
|
||||
@@ -341,7 +343,7 @@
|
||||
<string name="assets__action__delete_confirm_message">هل أنت متأكد من حذف \"%s\"؟ لا يمكن التراجع عن هذا الإجراء.</string>
|
||||
<string name="assets__action__edit">تعديل</string>
|
||||
<string name="assets__action__export">تصدير</string>
|
||||
<string name="assets__action__import">إستيراد</string>
|
||||
<string name="assets__action__import">استيراد</string>
|
||||
<string name="assets__action__no">كلا</string>
|
||||
<string name="assets__action__save">حفظ</string>
|
||||
<string name="assets__action__yes">موافق</string>
|
||||
@@ -361,7 +363,7 @@
|
||||
<string name="setup__welcome__trust" comment="Paragraph in Welcome fragment in Setup">مصدر التعليمات البرمجية لـ FlorisBoard متاح للجميع ، لذا يمكنك بسهولة مراجعة ما يفعله FlorisBoard في الخلفية. يرجى مراجعة الرابط الخاص بالمستودع أدناه.</string>
|
||||
<string name="setup__welcome__contribute" comment="Paragraph in Welcome fragment in Setup">شيء أخير قبل بدء الإعداد - إذا واجهت أي أخطاء / أعطال / مشكلات في FlorisBoard أو كان لديك طلب ميزة - توجه إلى مستودع GitHub الموجود في الرابط أدناه واطرح مشكلة. هذا يساعد في تحسين التجربة لجميع المستخدمين!</string>
|
||||
<string name="setup__welcome__outro" comment="Paragraph in Welcome fragment in Setup">لبدء الإعداد إضغط على <i>التالي</i>.</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">تفعيل FlorisBoard</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">قم بتفعيل FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">يتطلب Android تمكين كل لوحة مفاتيح مخصصة يدويًا قبل أن تتمكن من استخدامها. إضغط على الزر أدناه للتوجه إلى <i>اللغات و الإدخال</i> الإعدادات, ثم تأكد من إختيار \'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_after_enabled" comment="Description of state in Enable IME fragment after user enabled">تم تفعيل FlorisBoard بنجاح. للمواصلة إضغط على <i>التالي</i>!</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">فتح إعدادات اللغات و الإدخال</string>
|
||||
|
||||
@@ -212,9 +212,13 @@
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Предложения</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Речник</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Използване на системния потребителски речник</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Предлагат се думи от системния потребителски речник</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Управление на системния потребителски речник</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Използване на вътрешния потребителски речник</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Управление на вътрешния потребителски речник</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Преглед, добавяне и премахване на думи от системния потребителски речник</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Използване на потребителския речник към приложението</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Предлагат се думи от потребителския речник към приложението</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Управление на потребителския речник към приложението</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Преглед, добавяне и премахване на думи от потребителския речник към приложението</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Корекции</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Автоматични главни букви</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Думите се изписват с главни букви въз основа на текущия контекст</string>
|
||||
@@ -222,11 +226,12 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Клавишът Caps Lock да остане включен при преместване в друго текстово поле</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Точка при двоен интервал</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Двукратното докосване на интервала вмъква точка, последвана от интервал</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Вътрешен потребителски речник</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Потребителски речник към приложението</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Системен потребителски речник</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Честота: %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Честота: %d | ключова дума: %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">За всички езици</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Отваряне на системни настройки</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Потребителският речник е изнесен!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Потребителският речник е внесен!</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Добавяне на дума</string>
|
||||
@@ -240,6 +245,7 @@
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Ключова дума (по избор)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Ключовата дума съдържа недействителни знаци.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Код на езика (по избор)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Кодът на езика не отговаря на синтаксиса. Трябва да бъде или само език (bg), или език и държава (bg_BG), или език, държава и азбука (bg_BG-cyrillic).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Жестове и писане чрез плъзгане</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Въвеждане чрез плъзгане</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Въвеждане чрез плъзгане</string>
|
||||
@@ -305,7 +311,7 @@
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Инструменти, в помощ при за отстраняване на дефекти</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Статистика за динамичната памет</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Показва се информация за динамичната памет в горния десен ъгъл</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Изтриване на данните за вътрешния речник</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Изтриване на данните от речника към приложението</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Премахва всички думи от таблицата с речника</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Относно</string>
|
||||
|
||||
@@ -210,6 +210,15 @@
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Schnellzugriffsleiste einschalten</string>
|
||||
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Wird über der Tastatur angezeigt</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Vorschläge</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Wörterbuch</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">System-Wörterbuch aktivieren</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Wörter vorschlagen, die im System-Benutzerwörterbuch gespeichert sind</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">System-Benutzerwörterbuch verwalten</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Hinzufügen, ansehen und bearbeiten von Einträgen für das System-Benutzerwörterbuch</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Internes Wörterbuch aktivieren</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Wörter vorschlagen, die im internen Benutzerwörterbuch gespeichert sind</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Internes Benutzerwörterbuch verwalten</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Hinzufügen, ansehen und bearbeiten von Einträgen für das interne Benutzerwörterbuch</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Korrekturen</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Autom. Groß-/Kleinschreibung</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Automatisches Großschreiben je nach aktuellem Kontext</string>
|
||||
@@ -217,6 +226,14 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Die festgestellte Umschalttaste bleibt auch beim Wechsel in ein anderes Textfeld aktiviert</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Doppeltes Leerzeichen durch Punkt ersetzen</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Doppeltes Tippen auf die Leertaste fügt Punkt und ein Leerzeichen ein</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Internes Benutzerwörterbuch</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">System-Benutzerwörterbuch</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Für alle Sprachen</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Benutzerwörterbuch erfolgreich importiert!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Benutzerwörterbuch erfolgreich exportiert!</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Wort</string>
|
||||
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Dieses Wort enthält ungültige Zeichen.</string>
|
||||
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Bitte eine gültige Zahl innerhalb der Grenzen eintragen!</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gesten & Glide Typing</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Glide Typing</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Glide Typing aktivieren</string>
|
||||
@@ -279,6 +296,7 @@
|
||||
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Privaten Modus erzwingen</string>
|
||||
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Wird alle Funktionen deaktivieren, die vorübergehend mit deinen Eingabedaten arbeiten</string>
|
||||
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Entwickler-Werkzeuge ein/aus</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Interne Benutzerwörterbuch-Datenbank leeren</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Über</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">App-Icon von FlorisBoard</string>
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
<string name="settings__localization__subtype_characters_layout" comment="Label for layout dropdown in subtype dialog">Διάταξη χαρακτήρων</string>
|
||||
<string name="settings__localization__subtype_symbols_layout" comment="Label for layout dropdown in subtype dialog">Πρωτεύουσα διάταξη συμβόλων</string>
|
||||
<string name="settings__localization__subtype_symbols2_layout" comment="Label for layout dropdown in subtype dialog">Δευτερεύουσα διάταξη συμβόλων</string>
|
||||
<string name="settings__localization__subtype_currency_set" comment="Label for currency set dropdown in subtype dialog. 'set' is used as a noun here and can be compared to a group of elements (in this case currency symbols).">Σύνολο συναλλαγμάτων</string>
|
||||
<string name="settings__localization__subtype_numeric_layout" comment="Label for layout dropdown in subtype dialog">Αριθμητική διάταξη</string>
|
||||
<string name="settings__localization__subtype_numeric_advanced_layout" comment="Label for layout dropdown in subtype dialog">(Προχωρημένη) αριθμητική διάταξη</string>
|
||||
<string name="settings__localization__subtype_numeric_row_layout" comment="Label for layout dropdown in subtype dialog">Διάταξη σειράς αριθμών</string>
|
||||
@@ -85,6 +86,7 @@
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Επιλεγμένο θέμα</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Προσαρμογή χρωμάτων στην εφαρμογή</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Τα χρώματα του θέματος προσαρμόζονται σε αυτά της τρέχουσας εφαρμογής, εάν αυτή η εφαρμογή το υποστηρίζει.</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">Πηγές της Εφαρμογής FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Εσωτερικός αποθηκευτικός χώρος</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Εξωτερικός Προμηθευτής</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager activity for day theme">Διαχειριστής Θέματος (Μέρα)</string>
|
||||
@@ -107,6 +109,8 @@
|
||||
<string name="settings__theme_editor__value_type_reference_group" comment="Theme value type sub-field">Ομάδα</string>
|
||||
<string name="settings__theme_editor__value_type_reference_attr" comment="Theme value type sub-field">Χαρακτηριστικό</string>
|
||||
<string name="settings__theme_editor__value_type_solid_color" comment="Theme value type">Συμπαγές χρώμα</string>
|
||||
<string name="settings__theme_editor__value_type_lin_grad" comment="Theme value type">Γραμμικό ντεγκραντέ</string>
|
||||
<string name="settings__theme_editor__value_type_rad_grad" comment="Theme value type">Ακτινικό ντεγκραντέ</string>
|
||||
<string name="settings__theme_editor__value_type_on_off" comment="Theme value type">Εναλλαγή</string>
|
||||
<string name="settings__theme_editor__value_type_on_off_state" comment="Theme value type sub-field">Κατάσταση</string>
|
||||
<string name="settings__theme_editor__value_type_other" comment="Theme value type">Άλλο</string>
|
||||
@@ -131,6 +135,7 @@
|
||||
<string name="settings__theme__group_smartbarButton" comment="Theme group label">Πλήκτρο έξυπνης μπάρας</string>
|
||||
<string name="settings__theme__group_extractEditLayout" comment="Theme group label">Διάταξη πλήρους οθόνης τοπίου</string>
|
||||
<string name="settings__theme__group_extractActionButton" comment="Theme group label">Πλήκτρο δράσης πλήρους οθόνης τοπίου</string>
|
||||
<string name="settings__theme__group_glideTrail" comment="Theme group label">Διαδρομή ολίσθησης</string>
|
||||
<string name="settings__theme__group_custom" comment="Theme group label (%s is custom group name)">Προσαρμοσμένη ομάδα (%s)</string>
|
||||
<string name="settings__theme__attr_background" comment="Theme attribute label">Χρώμα φόντου</string>
|
||||
<string name="settings__theme__attr_backgroundActive" comment="Theme attribute label">Χρώμα φόντου (ενεργό)</string>
|
||||
@@ -157,10 +162,15 @@
|
||||
<string name="pref__keyboard__hint_mode__enabled_hint_priority" comment="Preference value">Ενεργοποιημένο (Προτεραιότητα των υπονοούμενων)</string>
|
||||
<string name="pref__keyboard__hint_mode__enabled_accent_priority" comment="Preference value">Ενεργοποιημένο (Προτεραιότητα του τονισμού)</string>
|
||||
<string name="pref__keyboard__hint_mode__enabled_smart_priority" comment="Preference value">Ενεργοποιημένο (Έξυπνη προτεραιοποίηση)</string>
|
||||
<string name="pref__keyboard__utility_key_enabled__label" comment="Preference title">Εμφάνιση πλήκτρου χρηστικότητας</string>
|
||||
<string name="pref__keyboard__utility_key_enabled__summary" comment="Preference summary">Εμφανίζει ένα διαμορφώσιμο πλήκτρο χρηστικότητας δίπλα στο πλήκτρο κενού</string>
|
||||
<string name="pref__keyboard__utility_key_action__label" comment="Preference title">Ενέργεια πλήκτρου χρηστικότητας</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_to_emojis" comment="Preference value">Εναλλαγή σε emoji</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Εναλλαγή γλώσσας</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Εναλλαγή εφαρμογής πληκτρολογίου</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Δυναμικό: Εναλλαγή σε emojis / Εναλλαγή γλώσσας</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Επιπλέον εμφάνιση αναδυόμενων πλήκτρων συμβόλων</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Προσθήκη των αναδυόμενων πλήκτρων συμβόλων στην προεπιλεγμένη διάταξη</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Πολλαπλασιαστής μεγέθους γραμματοσειράς (πορτραίτο)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Πολλαπλασιαστής μεγέθους γραμματοσειράς (τοπίο)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Διάταξη</string>
|
||||
@@ -183,6 +193,10 @@
|
||||
<string name="pref__keyboard__height_factor__extra_tall" comment="Preference value">Πολύ-ψηλό</string>
|
||||
<string name="pref__keyboard__height_factor__custom" comment="Preference value">Προσαρμοσμένο</string>
|
||||
<string name="pref__keyboard__height_factor_custom__label" comment="Preference title">Προσαρμοσμένη τιμή ύψους πληκτρολογίου</string>
|
||||
<string name="pref__keyboard__bottom_offset_portrait__label" comment="Preference title">Μετατόπιση βάσης (πορτρέτο)</string>
|
||||
<string name="pref__keyboard__bottom_offset_landscape__label" comment="Preference title">Μετατόπιση βάσης (τοπίο)</string>
|
||||
<string name="pref__keyboard__key_spacing_vertical__label" comment="Preference title">Απόσταση μεταξύ πλήκτρων (κάθετα)</string>
|
||||
<string name="pref__keyboard__key_spacing_horizontal__label" comment="Preference title">Απόσταση μεταξύ πλήκτρων (οριζόντια)</string>
|
||||
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Πάτημα πλήκτρου</string>
|
||||
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">Ήχος κατά το πάτημα πλήκτρου</string>
|
||||
<string name="pref__keyboard__sound_volume__label" comment="Preference title">Ένταση ήχου κατά το πάτημα πλήκτρου</string>
|
||||
@@ -193,10 +207,20 @@
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Επιπλέον εμφάνιση πλήκτρου όταν πατήσετε ένα πλήκτρο</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Καθυστέρηση παρατεταμένου πατήματος πλήκτρου</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Το πλήκτρο διαστήματος επιστρέφει στη διάταξη χαρακτήρων</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Πατώντας κενό επαναφέρεστε αυτόματα στους χαρακτήρες όταν βρίσκεστε στα σύμβολα ή στους αριθμούς</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Εμπειρία πληκτρολόγησης</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Ενεργοποίηση Έξυπνης Μπάρας</string>
|
||||
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Εμφάνιση στην κορυφή του πληκτρολογίου</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Προτάσεις</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Λεξικό</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Ενεργοποίηση λεξικού χρήστη στο σύστημα</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Προτάσεις λέξεων αποθηκευμένων στο λεξικό χρήστη στο σύστημα</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Διαχείριση λεξικού χρήστη στο σύστημα</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Προσθήκη, προβολή, και αφαίρεση εισαγωγών για το λεξικό χρήστη στο σύστημα</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Ενεργοποίηση εσωτερικού λεξικού χρήστη</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Προτάσεις λέξεων αποθηκευμένων στο εσωτερικό λεξικό χρήστη</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Διαχείριση εσωτερικού λεξικού χρήστη</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Προσθήκη, προβολή, και αφαίρεση εισαγωγών για το εσωτερικό λεξικό χρήστη</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Διορθώσεις</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Αυτόματη χρήση κεφαλαίων</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Κεφαλαιοποίηση λέξεων βάσει του παρόντος περιεχομένου εισαγωγής</string>
|
||||
@@ -204,13 +228,38 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Τα κεφαλαία θα παραμείνουν ενεργοποιημένα όταν μεταβείτε σε διαφορετικό πεδίο κειμένου</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Τελεία με διπλό-κενό</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Το διπλό πάτημα του πλήκτρου διαστήματος εισάγει μία τελεία ακολουθούμενη από ένα διάστημα</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Εσωτερικό Λεξικό Χρήστη</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Λεξικό Χρήστη στο Σύστημα</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Συχνότητα: %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Συχνότητα: %d | Συντόμευση: %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Για όλες τις γλώσσες</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Άνοιγμα περιβάλλοντος διαχειριστή συστήματος</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Η εισαγωγή λεξικού χρήστη ήταν επιτυχής!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Η εξαγωγή λεξικού χρήστη ήταν επιτυχής!</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Προσθήκη καταχώρισης λέξεως</string>
|
||||
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Επεξεργασία καταχώρισης λέξεως</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Λέξη</string>
|
||||
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Παρακαλώ εισάγετε μία λέξη!</string>
|
||||
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Αυτή η λέξη περιέχει μη έγκυρους χαρακτήρες.</string>
|
||||
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Συχνότητα (μεταξύ %d και %d)</string>
|
||||
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Παρακαλώ εισάγετε μία τιμή συχνότητας!</string>
|
||||
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Παρακαλώ εισάγετε έναν έγκυρο αριθμό εντός των ορισμένων ορίων!</string>
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Συντόμευση (προαιρετική)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Αυτή η συντόμευση περιέχει μη έγκυρους χαρακτήρες.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Κωδικός γλώσσας (προαιρετικός)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Αυτός ο κωδικός γλώσσας δεν ανταποκρίνεται στην προβλεπόμενη σύνταξη. Ο κωδικός πρέπει να είναι είτε μία γλώσσα μόνο (όπως en), είτε μία γλώσσα και μία χώρα (όπως en_US) είτε μία γλώσσα, χώρα, και κείμενο (όπως en_US-κείμενο).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Κινήσεις & Πληκτρολόγηση με ολίσθηση</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Πληκτρολόγηση με ολίσθηση</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Ενεργοποίηση πληκτρολόγησης με ολίσθηση</string>
|
||||
<string name="pref__glide__enabled__summary" comment="Preference summary">Πληκτρολογήστε μία λέξη με ολίσθηση του δαχτύλου μέσα από τα γράμματά της</string>
|
||||
<string name="pref__glide__show_trail__label" comment="Preference title">Εμφάνιση διαδρομής ολίσθησης</string>
|
||||
<string name="pref__glide__show_trail__summary" comment="Preference summary">Θα εξαφανίζεται μετά από κάθε λέξη</string>
|
||||
<string name="pref__gestures__general_title" comment="Preference group title">Γενικές χειρονομίες</string>
|
||||
<string name="pref__gestures__space_bar_title" comment="Preference group title">Χειρονομίες πλήκτρου διαστήματος</string>
|
||||
<string name="pref__gestures__other_title" comment="Preference group title">Άλλες κινήσεις / Όρια κινήσεων</string>
|
||||
<string name="pref__gestures__swipe_action__no_action" comment="Preference value for swipe action">Καμία ενέργεια</string>
|
||||
<string name="pref__gestures__swipe_action__cycle_to_previous_keyboard_mode" comment="Preference value for swipe action">Εναλλαγή στην προηγούμενη λειτουργία πληκτρολογίου</string>
|
||||
<string name="pref__gestures__swipe_action__cycle_to_next_keyboard_mode" comment="Preference value for swipe action">Εναλλαγή στην επόμενη λειτουργία πληκτρολογίου</string>
|
||||
<string name="pref__gestures__swipe_action__delete_characters_precisely" comment="Preference value for swipe action">Διαγραφή χαρακτήρων με ακρίβεια</string>
|
||||
<string name="pref__gestures__swipe_action__delete_word" comment="Preference value for swipe action">Διαγραφή της τρέχουσας λέξης</string>
|
||||
<string name="pref__gestures__swipe_action__delete_words_precisely" comment="Preference value for swipe action">Διαγραφή λέξεων με ακρίβεια</string>
|
||||
@@ -260,6 +309,12 @@
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Εμφάνιση του εικονιδίου της εφαρμογής στον εκκινητή</string>
|
||||
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Εξαναγκασμός ιδιωτικής λειτουργίας</string>
|
||||
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Θα απενεργοποιήσει χαρακτηριστικά που χρησιμοποιούν προσωρινά τα δεδομένα που εισάγετε</string>
|
||||
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Ενεργοποίηση εργαλείων προγραμματιστή</string>
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Εργαλεία σχεδιασμένα συγκεκριμένα για αποσφαλμάτωση και επίλυση προβλημάτων</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Εμφάνιση στατιστικών μνήμης σωρού</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Δημιουργεί ένα επικάλυμμα με τη χρήση της μνήμης σωρού και του μέγιστου μεγέθους στην πάνω-δεξιά γωνία</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Διαγραφή βάσης δεδομένων εσωτερικού λεξικού χρήστη</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Διαγράφει όλες τις λέξεις από τον πίνακα δεδομένων του λεξικού</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Σχετικά με</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Εικονίδιο εφαρμογής του FlorisBoard</string>
|
||||
@@ -275,6 +330,7 @@
|
||||
<string name="assets__file__name">Όνομα</string>
|
||||
<string name="assets__file__source">Πηγή</string>
|
||||
<string name="assets__action__add">Προσθήκη</string>
|
||||
<string name="assets__action__apply">Εφαρμογή</string>
|
||||
<string name="assets__action__cancel">Ακύρωση</string>
|
||||
<string name="assets__action__cancel_confirm_title">Επιβεβαίωση ακύρωσης</string>
|
||||
<string name="assets__action__cancel_confirm_message">Είστε βέβαιοι ότι θέλετε να απορρίψετε τυχούσες μη αποθηκευμένες αλλαγές; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί εφόσον εκτελεστεί.</string>
|
||||
@@ -314,6 +370,12 @@
|
||||
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Η ρύθμιση ολοκληρώθηκε!</string>
|
||||
<!-- Crash Dialog strings -->
|
||||
<string name="crash_dialog__title" comment="Title of crash dialog">Αναφορά σφάλματος FlorisBoard</string>
|
||||
<string name="crash_dialog__description" comment="Description of crash dialog">Συγγνώμη για την ταλαιπωρία, αλλά το FlorisBoard έχει τερματιστεί απρόοπτα εξαιτίας ενός απρόσμενου σφάλματος.</string>
|
||||
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Εάν επιθυμείτε να αναφέρετε αυτό το σφάλμα, πρώτα ελέγξτε αν το πρόβλημά σας δεν έχει ακόμα αναφερθεί στην παρακολούθηση προβλημάτων στο GitHub.\nΕάν δεν έχει αναφερθεί, αντιγράψτε την καταγραφή του τερματισμού που έχει προκύψει και ανοίξτε ένα νέο θέμα. Χρησιμοποιήστε το πρότυπο \"%s\" και συμπληρώσει την περιγραφή, τα βήματα για αναπαραγωγή, και αντιγράψτε την καταγραφή του τερματισμού στο τέλος. Αυτό βοηθάει να γίνει το FlorisBoard καλύτερο και πιο σταθερό για όλους. Ευχαριστώ!</string>
|
||||
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Αντιγραφή στο πρόχειρο συστήματος</string>
|
||||
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Αντιγράφηκε στο πρόχειρο συστήματος</string>
|
||||
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Αδυναμία αντιγραφής στο πρόχειρο συστήματος: Η πραγμάτωση διαχειριστή προχείρου δε βρέθηκε</string>
|
||||
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Άνοιγμα παρακολούθησης θεμάτων (github.com)</string>
|
||||
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Κλείσιμο</string>
|
||||
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Αναφορές σφάλματος FlorisBoard</string>
|
||||
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">Το FlorisBoard έχει σταματήσει να λειτουργεί…</string>
|
||||
@@ -327,6 +389,7 @@
|
||||
<string name="clip__pin_item">Καρφίτσωμα στοιχείου</string>
|
||||
<string name="clip__delete_item">Διαγραφή</string>
|
||||
<string name="clip__paste_item">Επικόλληση</string>
|
||||
<string name="clip__back_to_text_input">Πίσω στην εισαγωγή κειμένου</string>
|
||||
<string name="clip__cant_paste">Η εφαρμογή αυτή δεν επιτρέπει την επικόλληση του περιεχομένου.</string>
|
||||
<string name="pref__clipboard__clipboard_category__label">Πρόχειρο</string>
|
||||
<string name="pref__clipboard__use_internal_clipboard___label">Χρήση εσωτερικού προχείρου</string>
|
||||
@@ -344,5 +407,8 @@
|
||||
<string name="pref__clipboard__max_history_size__label">Μέγ. μέγεθος ιστορικού</string>
|
||||
<string name="pref__unit_items">" στοιχεία"</string>
|
||||
<!-- glide strings -->
|
||||
<string name="pref__glide_trail_fade_duration">Διάρκεια εξασθένησης διαδρομής ολίσθησης</string>
|
||||
<string name="pref__glide_preview_refresh_delay">Προεπισκόπηση καθυστέρησης ανανέωσης</string>
|
||||
<string name="pref__glide__show_preview">Εμφάνιση προεπισκόπησης κατά την πληκτρολόγηση με ολίσθηση</string>
|
||||
<string name="pref__glide_trail_max_length">Μέγιστο μήκος διαδρομής ολίσθησης</string>
|
||||
</resources>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Tema oscuro</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Tema seleccionado</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Adaptar colores a la aplicación</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Los colores del tema se adaptan a los de la aplicación actual, si la aplicación de destino lo admite.</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Los colores del teclado se adaptan a los de la aplicación actual, si la aplicación de destino lo admite.</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">Activos de la aplicación FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Almacenamiento interno</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Almacenamiento externo</string>
|
||||
@@ -123,9 +123,9 @@
|
||||
<string name="settings__theme_editor__error_attr_name" comment="Error text for an invalid attribute name">Por favor, ingresa un nombre que contenga solo las letras a-z y/o A-Z.</string>
|
||||
<string name="settings__theme_editor__error_attr_name_empty" comment="Error text for an empty attribute name">Por favor, introduzca un nombre al atributo.</string>
|
||||
<string name="settings__theme_editor__error_attr_name_already_exists" comment="Error text for a duplicate attribute name">Este nombre de atributo ya existe en este grupo. Por favor, escribe otro.</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Ventana & sistema</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Ventana y sistema</string>
|
||||
<string name="settings__theme__group_keyboard" comment="Theme group label">Teclado</string>
|
||||
<string name="settings__theme__group_key" comment="Theme group label">Tecla</string>
|
||||
<string name="settings__theme__group_key" comment="Theme group label">Teclas</string>
|
||||
<string name="settings__theme__group_key_specific" comment="Theme group label (%s is specific modifier)">Tecla (%s)</string>
|
||||
<string name="settings__theme__group_media" comment="Theme group label">Contexto de multimedia</string>
|
||||
<string name="settings__theme__group_oneHanded" comment="Theme group label">Una mano</string>
|
||||
@@ -146,7 +146,7 @@
|
||||
<string name="settings__theme__attr_showBorder" comment="Theme attribute label">Ver borde</string>
|
||||
<string name="settings__theme__attr_colorPrimary" comment="Theme attribute label">Color principal</string>
|
||||
<string name="settings__theme__attr_colorPrimaryDark" comment="Theme attribute label">Color principal (oscuro)</string>
|
||||
<string name="settings__theme__attr_colorAccent" comment="Theme attribute label">Color principal</string>
|
||||
<string name="settings__theme__attr_colorAccent" comment="Theme attribute label">Color del énfasis</string>
|
||||
<string name="settings__theme__attr_navBarColor" comment="Theme attribute label">Color de la barra de navegación</string>
|
||||
<string name="settings__theme__attr_navBarLight" comment="Theme attribute label">Color principal de la barra de navegación en modo oscuro</string>
|
||||
<string name="settings__theme__attr_semiTransparentColor" comment="Theme attribute label">Color semitransparente</string>
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Cambiar idioma</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Cambiar aplicación de teclado</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dinámico: cambiar a emojis / cambiar de idioma</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Mostrar también ventanas emergentes de símbolos</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Añadir las ventanas emergentes de símbolos al diseño predeterminado</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Multiplicador del tamaño de la fuente (vertical)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Multiplicador del tamaño de la fuente (horizontal)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Distribución</string>
|
||||
@@ -207,19 +209,48 @@
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">La barra de espacio cambia a la disposición de los caracteres</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Al pulsar la barra de espacio, se vuelve automáticamente a los caracteres cuando se trata de símbolos o números</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Experiencia de escritura</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Habilitar barra inteligente</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Barra inteligente</string>
|
||||
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Se mostrará en la parte superior del teclado</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Sugerencias</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Diccionario</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Habilitar diccionario personal del sistema</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Sugerir palabras almacenadas en el diccionario personal del sistema</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Administrar diccionario personal del sistema</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Añadir, ver y remover entradas para el diccionario personal del sistema</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Habilitar diccionario personal interno</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Sugerir palabras almacenadas en el diccionario personal interno</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Administrar diccionario personal interno</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Añadir, ver y remover entradas para el diccionario personal interno</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Correcciones</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Mayúsculas automáticas</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Poner en mayúsculas las palabras según el contexto de entrada actual</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__label" comment="Preference title">Recordar el estado de bloqueo de las mayúsculas</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__label" comment="Preference title">Recordar bloqueo de mayúsculas</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">El bloqueo de las mayúsculas se mantendrá cuando se pase a otro campo de texto</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Punto y doble espacio</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Al presionar dos veces en la barra espaciadora se inserta un punto seguido de un espacio</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos & escritura deslizante</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Escritura deslizante</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Habilitar escritura por gesto</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Pulsar espacio dos veces para insertar un punto seguido de un espacio</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Diccionario personal interno</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Diccionario personal del sistema</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Frecuencia: %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Frecuencia: %d | Atajo: %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Para todos los idiomas</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Abrir IU del administrador del sistema</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">¡Diccionario personal importado satisfactoriamente!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">¡Diccionario personal exportado satisfactoriamente!</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Añadir palabra</string>
|
||||
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Editar palabra</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Palabra</string>
|
||||
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">¡Por favor, ingresa una palabra!</string>
|
||||
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Esta palabra contiene caracteres inválidos.</string>
|
||||
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Frecuencia (entre %d y %d)</string>
|
||||
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">¡Por favor, ingresa un valor de frecuencia!</string>
|
||||
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">¡Por favor, ingresa un número válido dentro de los valores especificados!</string>
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Atajo (opcional)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Este atajo contiene caracteres inválidos.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Código del idioma (opcional)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Este código de idioma no se ajusta a la sintaxis esperada. El código debe ser solo un idioma (como en), un idioma y país (como en_US) o un idioma, país y script (como en_US-script).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos y escritura deslizante</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Escritura por gestos</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Escritura por gestos</string>
|
||||
<string name="pref__glide__enabled__summary" comment="Preference summary">Escriba una palabra deslizando su dedo a través de las letras</string>
|
||||
<string name="pref__glide__show_trail__label" comment="Preference title">Mostrar recorrido del gesto</string>
|
||||
<string name="pref__glide__show_trail__summary" comment="Preference summary">Desaparecerá después de cada palabra</string>
|
||||
@@ -282,6 +313,8 @@
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Herramientas diseñadas específicamente para depurar y detectar problemas</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Mostrar estadísticas de memoria</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Sobreponer el uso de la memoria y el tamaño maximo en la esquina superior derecha</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Eliminar palabras del diccionario interno</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Eliminar todas las palabras del diccionario</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Acerca de</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Icono de la aplicación de FlorisBoard</string>
|
||||
@@ -297,6 +330,7 @@
|
||||
<string name="assets__file__name">Nombre</string>
|
||||
<string name="assets__file__source">Fuente</string>
|
||||
<string name="assets__action__add">Agregar</string>
|
||||
<string name="assets__action__apply">Aplicar</string>
|
||||
<string name="assets__action__cancel">Cancelar</string>
|
||||
<string name="assets__action__cancel_confirm_title">Confirmar cancelación</string>
|
||||
<string name="assets__action__cancel_confirm_message">¿Está seguro de que desea descartar los cambios no guardados? Esta acción no se puede deshacer una vez ejecutada.</string>
|
||||
@@ -359,7 +393,7 @@
|
||||
<string name="clip__cant_paste">Esta aplicación no permite pegar el contenido.</string>
|
||||
<string name="pref__clipboard__clipboard_category__label">Portapapeles</string>
|
||||
<string name="pref__clipboard__use_internal_clipboard___label">Usar portapapeles interno</string>
|
||||
<string name="pref__clipboard__use_internal_clipboard_title__summary">Usar portapapeles interno en vez de el portapapeles del sistema</string>
|
||||
<string name="pref__clipboard__use_internal_clipboard_title__summary">Usar portapapeles interno en vez del portapapeles del sistema</string>
|
||||
<string name="pref__clipboard__sync_from_system_clipboard__label">Sincronizar portapapeles del sistema</string>
|
||||
<string name="pref__clipboard__keyboard_sync_from_system_clipboard__summary">Las actualizaciones del portapapeles del sistema también actualizan el portapapeles de Floris</string>
|
||||
<string name="pref__clipboard__sync_to_system_clipboard__label">Sincronizar al portapapeles del sistema</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">تغییر زبان</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">تغییر برنامه کیبورد</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">متغیر: تغییر به شکلکها / تغییر زبان</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">همچنین پاپ آپ نماد ها را نشان بده</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">افزودن نمادهای پاپ آپ به چیدمان پیشفرض</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">اندازه فونت چند برابری(عمودی)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">اندازه فونت چند برابری(افقی)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">چیدمان</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Changer de langue</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Changer d\'application de clavier</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dynamique : Changer vers emojis / Changer de langue</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Afficher également les popups de symboles</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Ajouter les popups de symbole à la disposition par défaut</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Multiplicateur de la taille de la police (portrait)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Multiplicateur de la taille de la police (paysage)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Disposition</string>
|
||||
@@ -216,9 +218,9 @@
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Gérer le dictionnaire d\'utilisateur système</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Ajouter, voir et supprimer des entrées du dictionnaire d\'utilisateur système</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Activer le dictionnaire d\'utilisateur interne</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Suggérer des mots du dictionnaire d\'utilisateur interne</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Gérer le dictionnaire d\'utilisateur interne</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Ajouter, voir et supprimer des entrées du dictionnaire d\'utilisateur interne</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Suggérer des mots stockés dans le dictionnaire utilisateur interne</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Gérer le dictionnaire utilisateur interne</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Ajouter, voir et supprimer des entrées du dictionnaire utilisateur interne</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Corrections</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Auto-capitalisation</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Capitaliser les mots en fonction du contexte de saisie actuel</string>
|
||||
@@ -226,14 +228,14 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Le verrouillage des majuscules reste activé lorsque l\'on passe à un autre champ de texte</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Point de double espace</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">En tappant deux fois sur la barre espace, insère un point suivi d\'un espace</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dictionnaire d\'utilisateur interne</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Dictionnaire d\'utilisateur système</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dictionnaire utilisateur interne</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Dictionnaire utilisateur système</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Fréquence : %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Fréquence : %d | Raccourci : %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Pour toutes les langues</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Ouvrir l\'interface de gestion du système</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Dictionnaire d\'utilisateur importé avec succès !</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Dictionnaire d\'utilisateur exporté avec succès !</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Dictionnaire utilisateur importé avec succès !</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Dictionnaire utilisateur exporté avec succès !</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Ajouter un mot</string>
|
||||
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Modifier un mot</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Mot</string>
|
||||
@@ -245,7 +247,7 @@
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Raccourci (facultatif)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Ce raccourci contient des caractères non valides.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Code de langue (facultatif)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Ce code de langue n\'est pas conforme à la syntaxe attendue. Le code doit être une langue uniquement (comme en), une langue et un pays (comme en_US) ou une langue, un pays et un script (comme en_US-script).</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Ce code de langue n\'est pas conforme à la syntaxe attendue. Le code doit être une langue uniquement (comme fr), une langue et un pays (comme fr_FR) ou une langue, un pays et un script (comme fr_FR-script).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestes & Saisie en glissant</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Saisie en glissant</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Activer la saisie en glissant</string>
|
||||
@@ -328,6 +330,7 @@
|
||||
<string name="assets__file__name">Nom</string>
|
||||
<string name="assets__file__source">Source</string>
|
||||
<string name="assets__action__add">Ajouter</string>
|
||||
<string name="assets__action__apply">Appliquer</string>
|
||||
<string name="assets__action__cancel">Annuler</string>
|
||||
<string name="assets__action__cancel_confirm_title">Confirmer l\'annulation</string>
|
||||
<string name="assets__action__cancel_confirm_message">Êtes-vous sûrs de vouloir abandonner tout changement non sauvegardé ? Cette action ne peut être annulée après son exécution.</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Cambia lingua</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Cambia app di tastiera</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dinamico: Passa agli emoji / Cambia lingua</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Mostra anche i popup dei simboli</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Aggiungi i popup dei simboli alla disposizione predefinita</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Moltiplicatore della dimensione del testo (ritratto)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Moltiplicatore della dimensione del testo (panorama)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Layout</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Mainīt valodu</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Mainīt tastatūras lietotni</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Mainīgs: pārslēgties uz sajūtu zīmēm (emoji) / pārslēgt valodu</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Rādīt arī zīmju uzlecošos logus</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Pievienot zīmju izlecošos logus noklusējuma izkārtojumam</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Fonta izmēra reizinātājs (statenisks novietojums)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Fonta izmēra reizinātājs (līmenisks novietojums)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Izkārtojums</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">FlorisBoard</string>
|
||||
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Pauze</string>
|
||||
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Pauzeren</string>
|
||||
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Wacht</string>
|
||||
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Doorlooppuntjes-symbool. Geeft aan dat meer letters gebruikt kunnen worden door de toets langer in te drukken, wanneer zichtbaar.</string>
|
||||
<!-- One-handed strings -->
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Taal wijzigen</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Wissel toetsenbord</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dynamisch: Wissel naar emoji\'s / Wissel taal</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Toon ook symboolpop-ups</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Voeg symboolpop-ups toe aan de standaardindeling</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Factor lettergrootte (portret-modus)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Factor lettergrootte (landschapsmodus)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Indeling</string>
|
||||
@@ -210,6 +212,15 @@
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Schakel Slimme Balk in</string>
|
||||
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Word getoond boven het toetsenbord</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Suggesties</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Woordenboek</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Activeer het persoonlijke woordenboek van het systeem</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Stel woorden voor uit het persoonlijke woordenboek van het systeem</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Beheer persoonlijke woordenboek van het systeem</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Voeg toe, bekijk of verwijder woorden uit het persoonlijke woordenboek van het systeem</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Schakel intern persoonlijke woordenboek in</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Stel woorden voor uit het interne persoonlijke woordenboek</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Beheer interne persoonlijke woordenboek</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Voeg toe, bekijk of verwijder woorden uit het interne persoonlijke woordenboek</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Verbeteringen</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Auto-kapitalisatie</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Woorden kapitaliseren op basis van de huidige invoercontext</string>
|
||||
@@ -217,6 +228,26 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">CapsLock blijft aan bij het verplaatsen naar een ander tekstveld</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Punt na twee spaties</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Twee keer tikken op spatiebalk voegt een punt toe gevolgd door een spatie</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Intern persoonlijk woordenboek</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Persoonlijk woordenboek van het systeem</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Frequentie: %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Frequentie: %d | Afkorting: %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Voor alle talen</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Open systeembeheer UI</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Persoonlijk woordenboek met succes geïmporteerd!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Persoonlijk woordenboek met succces geëxporteerd!</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Woord invoeren</string>
|
||||
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Woord bewerken</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Woord</string>
|
||||
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Geef een woord in.</string>
|
||||
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Dit woord bevat ongeldige tekens.</string>
|
||||
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Frequentie (tussen %d en %d)</string>
|
||||
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Geef een frequentie in.</string>
|
||||
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Geef een geldig nummer binnen het gespecifieerde bereik in.</string>
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Afkorting (optioneel)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Deze afkorting bevat ongeldige tekens.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Taalcode (optioneel)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Deze taalcode komt niet overeen met de verwachte syntax. De code moet ofwel enkel de taal (bv. \"en\"), een taal en een lan (bv. \"en_US\") of een taal, land en script (bv. \"en_US-script\") bevatten.</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gebaren & Vegen</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Vegend typen</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Glijdend typen inschakelen</string>
|
||||
@@ -282,6 +313,8 @@
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Hulpmiddelen specifiek voor debuggen en problemen verhelpen</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Toon heap memory stats</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Toont het heap-memorygebruik en maximale grootte in de rechterbovenhoek</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Wis database van het interne persoonlijke woordenboek</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Wis alle woorden van de woordenlijst in de datatabel</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Over</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">App-pictogram van FlorisBoard</string>
|
||||
@@ -297,6 +330,7 @@
|
||||
<string name="assets__file__name">Naam</string>
|
||||
<string name="assets__file__source">Bron</string>
|
||||
<string name="assets__action__add">Toevoegen</string>
|
||||
<string name="assets__action__apply">Toepassen</string>
|
||||
<string name="assets__action__cancel">Annuleren</string>
|
||||
<string name="assets__action__cancel_confirm_title">Annuleren bevestigen</string>
|
||||
<string name="assets__action__cancel_confirm_message">Weet je zeker dat je onopgeslagen wijzigingen wil verwerpen? Deze actie kan niet ongedaan gemaakt worden.</string>
|
||||
@@ -337,6 +371,7 @@
|
||||
<!-- Crash Dialog strings -->
|
||||
<string name="crash_dialog__title" comment="Title of crash dialog">Foutenrapport FlorisBoard</string>
|
||||
<string name="crash_dialog__description" comment="Description of crash dialog">Sorry voor het ongemak, maar FlorisBoard is gecrashed omwille van een onverwachte fout.</string>
|
||||
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Als u deze fout wilt rapporteren, kijk dan eerst in de issuetracker op GitHub of uw crash nog niet gerapporteerd is.\nAls dat niet het geval is, kopieer dan de gegenereerde crashlog en open een nieuwe issue. Gebruik het \"%s\"-sjabloon en vul de beschrijving in, de stappen om te reproduceren, en plak het gegenereerde crash log aan het eind. Dit helpt om FlorisBoard beter en stabieler te maken voor iedereen. Bedankt!</string>
|
||||
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopieer naar systeemklembord</string>
|
||||
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Gekopieerd naar systeemklembord</string>
|
||||
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Kan niet kopiëren naar systeemklembord: Klembordbeheerinstantie niet gevonden</string>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title" comment="Title of Settings">Configurações</string>
|
||||
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">Mais opções</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Ajuda & feedback</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Ajuda e feedback</string>
|
||||
<string name="settings__help" comment="General label for help buttons in Settings">Ajuda</string>
|
||||
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">Início</string>
|
||||
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">Teclado</string>
|
||||
@@ -53,7 +53,7 @@
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">O FlorisBoard não está ativado no sistema e, portanto, não estará disponível para ser selecionado no alternador de teclado. Clique aqui para resolver este problema.</string>
|
||||
<string name="settings__home__ime_not_selected" comment="Warning message shown in Home fragment when FlorisBoard is not selected as the default keyboard">O FlorisBoard não foi selecionado como o teclado padrão. Clique aqui para resolver este problema.</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">Obrigado por experimentar o FlorisBoard! Este projeto ainda está em alpha e, portanto, faltando recursos. Se você encontrar algum bug ou quiser fazer uma sugestão, por favor, confira o repo no GitHub e crie um issue. Isso ajuda a tornar o FlorisBoard melhor. Obrigado!</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">Idiomas & Formatos do teclado</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">Idiomas e Formatos de Teclado</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">Parece que você não configurou nenhum formato de digitação. Como alternativa, será utilizado o formato Inglês/QWERTY!</string>
|
||||
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">Adicionar</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Adicionar formato de digitação</string>
|
||||
@@ -123,7 +123,7 @@
|
||||
<string name="settings__theme_editor__error_attr_name" comment="Error text for an invalid attribute name">Digite um nome de atributo que contenha apenas as letras a-z e/ou A-Z.</string>
|
||||
<string name="settings__theme_editor__error_attr_name_empty" comment="Error text for an empty attribute name">Por favor, digite um nome para o atributo.</string>
|
||||
<string name="settings__theme_editor__error_attr_name_already_exists" comment="Error text for a duplicate attribute name">Esse nome de atributo já existe dentro deste grupo. Por favor, especifique outro.</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Janela & Sistema</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Janela e Sistema</string>
|
||||
<string name="settings__theme__group_keyboard" comment="Theme group label">Teclado</string>
|
||||
<string name="settings__theme__group_key" comment="Theme group label">Tecla</string>
|
||||
<string name="settings__theme__group_key_specific" comment="Theme group label (%s is specific modifier)">Tecla (%s)</string>
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Trocar idioma</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Trocar aplicativo de teclado</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dinâmico: Mudar para emojis / Trocar idioma</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Também mostrar popups de símbolo</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Adicionar os popups de símbolo ao layout padrão</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Multiplicador de tamanho da fonte (retrato)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Multiplicador de tamanho da fonte (paisagem)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Layout</string>
|
||||
@@ -204,7 +206,7 @@
|
||||
<string name="pref__keyboard__popup_visible__label" comment="Preference title">Visibilidade do PopUp</string>
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Mostrar popup quando pressionar uma tecla</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Atraso ao pressionar e segurar uma tecla</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Barra de espaço muda de volta para o layout de caracteres</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Barra de espaço alterna para o layout de caracteres</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Apertar e segurar a barra de espaço muda automaticamente para os caracteres quando em símbolos ou números</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Experiência de digitação</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Ativar barra inteligente</string>
|
||||
@@ -246,7 +248,7 @@
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Este atalho contém caracteres inválidos.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Código de idioma (opcional)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Este código de idioma não está de acordo com a sintaxe esperada. O código deve ser apenas uma língua (como en), uma língua e um país (como en_US) ou uma língua, país e script (como en_US-script).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos & Digitação deslizante</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos e Digitação deslizante</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Digitação deslizante</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Ativar digitação deslizante</string>
|
||||
<string name="pref__glide__enabled__summary" comment="Preference summary">Digitar uma palavra deslizando o dedo através de suas letras</string>
|
||||
@@ -358,9 +360,9 @@
|
||||
<string name="setup__welcome__contribute" comment="Paragraph in Welcome fragment in Setup">Uma última coisa antes de iniciar a configuração - se você encontrar alguns bugs/travamentos/problemas com o FlorisBoard ou quiser fazer alguma solicitação de recurso - vá até o repositório do GitHub vinculado abaixo e crie um issue. Isso ajuda a melhorar a experiência de todos os usuários!</string>
|
||||
<string name="setup__welcome__outro" comment="Paragraph in Welcome fragment in Setup">Para iniciar a configuração, clique em <i>PRÓXIMO</i>.</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">Ativar FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">O Android exige que todo teclado personalizado tenha que ser ativado manualmente antes de usá-lo. Clique no botão abaixo para ir as configurações de <i>Idioma & Entrada</i>, em seguida, certifique-se de ativar o \'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">O Android exige que todo teclado personalizado tenha que ser ativado manualmente antes de usá-lo. Clique no botão abaixo para ir as configurações de <i>Idioma e Entrada</i>, em seguida, certifique-se de ativar o \'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_after_enabled" comment="Description of state in Enable IME fragment after user enabled">FlorisBoard foi ativado com sucesso. Para continuar clique em <i>PRÓXIMO</i>!</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Abrir configurações de Idioma & Entrada</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Abrir configurações de Idioma e Entrada</string>
|
||||
<string name="setup__make_default__title" comment="Title of Make IME default fragment in Setup">Selecionar FlorisBoard como padrão</string>
|
||||
<string name="setup__make_default__text_before_switch" comment="Description of state in Make IME default fragment before user switched">O FlorisBoard agora está ativado em seu sistema. Para usá-lo ativamente, selecione o FlorisBoard como teclado padrão na caixa de seleção de teclado!</string>
|
||||
<string name="setup__make_default__text_after_switch" comment="Description of state in Make IME default fragment after user switched">O FlorisBoard foi selecionado como teclado padrão com sucesso!</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Mudar idioma</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Mudar a aplicação de teclado</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dinâmica: mudar para emojis / mudar idioma</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Mostrar popup para símbolos</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Adicionar popup de símbolos à disposição padrão</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Multiplicador do tamanho do tipo de letra (vertical)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Multiplicador do tamanho do tipo de letra (horizontal)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Disposição</string>
|
||||
@@ -210,6 +212,15 @@
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Ativar barra inteligente</string>
|
||||
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Mostrar na parte superior do teclado</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Sugestões</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Dicionário</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Ativar dicionário do sistema</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Sugerir palavras guardadas no dicionário do sistema</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Gerir dicionários do sistema</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Adicionar, ver e remover entradas ao dicionário do sistema</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Ativar dicionário do utilizador</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Sugerir palavras guardadas no dicionário do utilizador</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Gerir dicionários do utilizador</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Adicionar, ver e remover entradas ao dicionário do utilizador</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Correções</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Maiúsculas automáticas</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Colocar letras maiúsculas tendo em conta o contexto das palavras</string>
|
||||
@@ -217,6 +228,26 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Manter CapsLock quando se muda para outro campo de texto</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Duplo espaço insere ponto final</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Ao tocar 2 vezes na barra de espaços, inserir um ponto final e um espaço</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dicionário do utilizador</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Dicionário do sistema</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Frequência: %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Frequência: %d | Atalho: %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Para todos os idiomas</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Abrir gestor do sistema</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Dicionário do utilizador importado com sucesso!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Dicionário do utilizador exportado com sucesso!</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Adicionar palavra</string>
|
||||
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Editar palavra</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Palavra</string>
|
||||
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Introduza uma palavra!</string>
|
||||
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Esta palavra tem caracteres inválidos.</string>
|
||||
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Frequência (entre %d e %d)</string>
|
||||
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Introduza um valor para a frequência!</string>
|
||||
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Introduza um número válido dentro dos limites definidos!</string>
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Atalho (opcional)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Este atalho tem caracteres inválidos.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Código do idioma (opcional)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">O código de idioma indicado não é coincidente com a sintaxe esperada. O código deve um idioma (pt), um idioma e um país (pt_PT) ou um idioma, um país e um script (pt_PT-script).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos e deslize</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Digitar ao deslizar</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Ativar escrita por egstos</string>
|
||||
@@ -281,6 +312,8 @@
|
||||
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Ativar ferramentas de programador</string>
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Ferramentas criadas para efeitos de depuração e resolução de erros</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Mostrar estatísticas de memória \'heap\'</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Limpar base de dados do dicionário do utilizador</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Remover todas as palavras da base de dados do dicionário</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Sobre o FlorisBoard</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Ícone do FlorisBoard</string>
|
||||
@@ -296,6 +329,7 @@
|
||||
<string name="assets__file__name">Nome</string>
|
||||
<string name="assets__file__source">Fonte</string>
|
||||
<string name="assets__action__add">Adicionar</string>
|
||||
<string name="assets__action__apply">Aplicar</string>
|
||||
<string name="assets__action__cancel">Cancelar</string>
|
||||
<string name="assets__action__cancel_confirm_title">Confirmar</string>
|
||||
<string name="assets__action__cancel_confirm_message">Tem a certeza de que deseja descartar as alterações? Esta ação não pode ser revertida.</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Сменить язык</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Переключить приложение клавиатуры</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Динамическое: переключиться на эмодзи / переключить язык</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Символы в всплывающей подсказке</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Включать символы в всплывающую подсказку раскладки по умолчанию</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Мультипликатор размера шрифта (портрет)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Мультипликатор размера шрифта (ландшафт)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Внешний вид</string>
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Dili değiştir</string>
|
||||
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Klavye uygulamasını değiştir</string>
|
||||
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dinamik: Emojilerle değiştir / Dili değiştir</string>
|
||||
<string name="pref__keyboard__merge_hint_popups_enabled__label">Sembol açılır pencerelerini de göster</string>
|
||||
<string name="pref__keyboard_merge_hint_popups_enabled__summary">Sembol açılır pencerelerini varsayılan düzene ekle</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Yazıtipi boyutu çarpanı (dikey)</string>
|
||||
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Yazıtipi boyutu çarpanı (yatay)</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Yerleşim</string>
|
||||
@@ -210,6 +212,15 @@
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Akıllı Çubuğu Etkinleştir</string>
|
||||
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Klavyenin üstünde gösterilir</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Öneriler</string>
|
||||
<string name="pref__dictionary__title" comment="Preference group title">Sözlük</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Kullanıcı sözlüğünü etkinleştir</string>
|
||||
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Kullanıcı sözlüğünde saklanan kelimeleri öner</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Kullanıcı sözlüğünü yönet</string>
|
||||
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Kullanıcı sözlüğüne kelime ekle, çıkar veya görüntüle</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Dahili kullanıcı sözlüğünü etkinleştir</string>
|
||||
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Dahili kullanıcı sözlüğünde saklanan kelimeleri öner</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Dahili kullanıcı sözlüğünü yönet</string>
|
||||
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Dahili kullanıcı sözlüğüne kelime ekle, çıkar veya görüntüle</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Düzeltmeler</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Otomatik olarak büyük harf yap</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">İçeriğe göre kelimelerin ilk harfini büyüt</string>
|
||||
@@ -217,6 +228,26 @@
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Başka bir yazım alanına geçerken büyük harf kilidini açık tut</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Çift boşlukta nokta</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Boşluk tuşuna 2 defa basmak bir nokta ve ardından bir boşluk koyar</string>
|
||||
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dahili Kullanıcı Sözlüğü</string>
|
||||
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Kullanıcı Sözlüğü</string>
|
||||
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Sıklık: %d</string>
|
||||
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Sıklık: %d | Kısayol: %s</string>
|
||||
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Tüm diller için</string>
|
||||
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Sistem yöneticisi arayüzünü aç</string>
|
||||
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Kullanıcı sözlüğü başarıyla içe aktarıldı!</string>
|
||||
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Kullanıcı sözlüğü başarıyla dışa aktarıldı!</string>
|
||||
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Yeni sözcük ekle</string>
|
||||
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Sözcüğü düzenle</string>
|
||||
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Sözcük</string>
|
||||
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Lütfen bir sözcük girin!</string>
|
||||
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Bu sözcük geçersiz karakterler içeriyor.</string>
|
||||
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Sıklık (%d ve %d arasında)</string>
|
||||
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Lütfen bir sıklık değeri girin!</string>
|
||||
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Lütfen sınırlar dahilinde geçerli bir sayı girin!</string>
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Kısayol (seçmeli)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Bu kısayol geçersiz karakterler içeriyor.</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Dil kodu (seçmeli)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Bu dil kodu beklenen sözdizimine uymuyor. Kod ya sadece bir dil olmalı (tr gibi), ya hem dil hem de ülke kodu olmalı (tr_TR gibi), ya da hem dil hem ülke hem de betik olmalıdır (tr_TR-betik gibi).</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Jestler & Kaydırarak yazma</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Kaydırarak Yazma</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Kaydırarak yazmayı etkinleştir</string>
|
||||
@@ -282,6 +313,8 @@
|
||||
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Hata ayıklama ve sorun giderme için özel tasarlanmış araçlar</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Küme bellek istatistiklerini göster</string>
|
||||
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Küme bellek kullanımını ve en fazla boyutunu sağ üst köşede gösterir</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Dahili kullanıcı sözlüğü veritabanını temizle</string>
|
||||
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Sözlük veritabanı tablosundaki tüm sözcükleri temizler</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Hakkında</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">FlorisBoard’un uygulama simgesi</string>
|
||||
@@ -297,6 +330,7 @@
|
||||
<string name="assets__file__name">İsim</string>
|
||||
<string name="assets__file__source">Kaynak</string>
|
||||
<string name="assets__action__add">Ekle</string>
|
||||
<string name="assets__action__apply">Uygula</string>
|
||||
<string name="assets__action__cancel">İptal</string>
|
||||
<string name="assets__action__cancel_confirm_title">İptali onayla</string>
|
||||
<string name="assets__action__cancel_confirm_message">Kaydedilmemiş değişiklikleri iptal etmek istediğine emin misin? Bu işlem geri alınamaz.</string>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<dimen name="key_textHintSize">10sp</dimen>
|
||||
<dimen name="key_numeric_textSize">12sp</dimen>
|
||||
<dimen name="key_popup_textSize">21sp</dimen>
|
||||
<dimen name="key_space_textSize">12sp</dimen>
|
||||
<dimen name="emoji_key_textSize">22sp</dimen>
|
||||
<dimen name="devtools_memory_overlay_textSize">8sp</dimen>
|
||||
|
||||
@@ -56,16 +57,32 @@
|
||||
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
|
||||
<dimen name="suggestion_chip_bg_padding_start">13dp</dimen>
|
||||
<dimen name="suggestion_chip_bg_padding_top">0dp</dimen>
|
||||
<dimen name="suggestion_chip_bg_padding_end">13dp</dimen>
|
||||
<dimen name="suggestion_chip_bg_padding_bottom">0dp</dimen>
|
||||
|
||||
<dimen name="suggestion_chip_fg_title_margin_start">4dp</dimen>
|
||||
<dimen name="suggestion_chip_fg_title_margin_top">0dp</dimen>
|
||||
<dimen name="suggestion_chip_fg_title_margin_end">4dp</dimen>
|
||||
<dimen name="suggestion_chip_fg_title_margin_bottom">0dp</dimen>
|
||||
|
||||
<dimen name="suggestion_chip_fg_subtitle_margin_start">4dp</dimen>
|
||||
<dimen name="suggestion_chip_fg_subtitle_margin_top">0dp</dimen>
|
||||
<dimen name="suggestion_chip_fg_subtitle_margin_end">4dp</dimen>
|
||||
<dimen name="suggestion_chip_fg_subtitle_margin_bottom">0dp</dimen>
|
||||
|
||||
<dimen name="clipboard_text_item_pin_margin" >25dp</dimen>
|
||||
|
||||
<dimen name="gesture_distance_threshold_very_short">24dp</dimen>
|
||||
<dimen name="gesture_distance_threshold_short">28dp</dimen>
|
||||
<dimen name="gesture_distance_threshold_normal">32dp</dimen>
|
||||
<dimen name="gesture_distance_threshold_long">36dp</dimen>
|
||||
<dimen name="gesture_distance_threshold_very_long">40dp</dimen>
|
||||
<dimen name="clipboard_text_item_pin_margin" >25dp</dimen>
|
||||
|
||||
<integer name="gesture_velocity_threshold_very_slow">8000</integer>
|
||||
<integer name="gesture_velocity_threshold_slow">11000</integer>
|
||||
<integer name="gesture_velocity_threshold_normal">14000</integer>
|
||||
<integer name="gesture_velocity_threshold_fast">17000</integer>
|
||||
<integer name="gesture_velocity_threshold_very_fast">20000</integer>
|
||||
<integer name="gesture_velocity_threshold_very_slow">7000</integer>
|
||||
<integer name="gesture_velocity_threshold_slow">10000</integer>
|
||||
<integer name="gesture_velocity_threshold_normal">13000</integer>
|
||||
<integer name="gesture_velocity_threshold_fast">16000</integer>
|
||||
<integer name="gesture_velocity_threshold_very_fast">19000</integer>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user