mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-07-01 22:50:32 +00:00
Compare commits
257 commits
v3.0-alpha
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
1d248ba083 | ||
|
9d1bdd9eb3 | ||
|
134161c4e2 | ||
|
fb62783c08 | ||
|
d2217f099a | ||
|
4f58e5d013 | ||
|
10a5eab3bc | ||
|
8d876a15f0 | ||
|
945a700b71 | ||
|
53a899794e | ||
|
c2068224a0 | ||
|
9193c95c2b | ||
|
76ebf99921 | ||
|
77a728e390 | ||
|
7b0c511857 | ||
|
e062efb3d4 | ||
|
d356f9f54b | ||
|
9549389be7 | ||
|
2dc838798d | ||
|
24a2eddc1f | ||
|
ef3191a2eb | ||
|
e430d13c4a | ||
|
9c97a6b9bf | ||
|
a37de668c0 | ||
|
63dad1549e | ||
|
f06a553d2c | ||
|
e9e3bdac17 | ||
|
d5cd18ecaa | ||
|
871ac110ad | ||
|
49c9d77978 | ||
|
79726f1a9d | ||
|
f2ec441f45 | ||
|
62f82d15cf | ||
|
f8d3795302 | ||
|
9cec401e1e | ||
|
83ff9b3345 | ||
|
8ae241b032 | ||
|
80ba394b95 | ||
|
52744b7427 | ||
|
af5c41c83c | ||
|
e21168b1d3 | ||
|
11f45a6209 | ||
|
c8322dd4a2 | ||
|
896e207c5f | ||
|
5cd184e5c2 | ||
|
e98c3210dc | ||
|
7cec6b148c | ||
|
bd85498810 | ||
|
51a863d840 | ||
|
defec4a27f | ||
|
ccc287c4ea | ||
|
48f6c21b57 | ||
|
9efe534c03 | ||
|
16ce183942 | ||
|
867438fdc0 | ||
|
0787a79de4 | ||
|
97aec851e4 | ||
|
7dbf5ea86d | ||
|
d951a1dbdd | ||
|
20d4704090 | ||
|
d0787201ae | ||
|
0a961a68db | ||
|
7499c38e13 | ||
|
f81f6a7f7d | ||
|
07ea14ea16 | ||
|
32099748e5 | ||
|
538a26a9b8 | ||
|
8284adb32c | ||
|
180bd179c5 | ||
|
12c1cb0cd2 | ||
|
ec2bbb461d | ||
|
38bbcd9a83 | ||
|
8a55f57ccf | ||
|
e01714bbee | ||
|
3fcea486be | ||
|
e4e99a7e5d | ||
|
c321c2c684 | ||
|
7dc6279d87 | ||
|
ad71b49c04 | ||
|
09eecc0502 | ||
|
424df5fb0d | ||
|
b903e10b12 | ||
|
e6a412750d | ||
|
7b91221339 | ||
|
f0d4aaa9c3 | ||
|
0e86978df3 | ||
|
82bea7facf | ||
|
4d441e5bdf | ||
|
92b1907c61 | ||
|
37821ff8ad | ||
|
dbeddcd658 | ||
|
31a8761bfa | ||
|
120734ff41 | ||
|
0d5159c2d7 | ||
|
b44dd29b0c | ||
|
7da068145f | ||
|
deec4d1f98 | ||
|
e21c135b90 | ||
|
154f7c3a1e | ||
|
ed540466e9 | ||
|
05ea8b7f76 | ||
|
bf7e0542f5 | ||
|
c4e7c84608 | ||
|
f72e8f41f4 | ||
|
69540b8d9f | ||
|
d1120807d3 | ||
|
9dbce40fd7 | ||
|
82e6d8a5cb | ||
|
175b5ea197 | ||
|
917edee918 | ||
|
ead8fb36cb | ||
|
3f51bd4da5 | ||
|
7bc74810b1 | ||
|
e25300d832 | ||
|
e034065236 | ||
|
954a27b7c9 | ||
|
b1b357d6b8 | ||
|
e32a0c8e98 | ||
|
d9a779a66e | ||
|
18549151b3 | ||
|
9d38471f72 | ||
|
4289e487e9 | ||
|
27a2300631 | ||
|
900dfa1b9c | ||
|
9709c0d0a2 | ||
|
960f058b7e | ||
|
4ecf185431 | ||
|
e45f0660a2 | ||
|
e7ccf72fc5 | ||
|
e154001d44 | ||
|
44558ceeaa | ||
|
aa8068b5d2 | ||
|
466ecfb78c | ||
|
731c6cdd5e | ||
|
199f177c2d | ||
|
66c3dd7a81 | ||
|
9c9fe392d1 | ||
|
c33c2c5823 | ||
|
4d91702073 | ||
|
a0f77c1392 | ||
|
35df3e7bae | ||
|
f48438f30a | ||
|
c96eec601d | ||
|
c9059f3616 | ||
|
2fe87eea9b | ||
|
c4386df186 | ||
|
95d4bfe97c | ||
|
880c7eaf33 | ||
|
b5837c3380 | ||
|
e6ec1c7bca | ||
|
549675d8d7 | ||
|
a1e05c847e | ||
|
1f8a94f219 | ||
|
3c36033acb | ||
|
97db67d7eb | ||
|
a3dff524cb | ||
|
366ee5ae28 | ||
|
91b177d204 | ||
|
4f356086d7 | ||
|
60a5fe1e03 | ||
|
d8bf27f180 | ||
|
2a7ac3cf79 | ||
|
875491a0e1 | ||
|
9f06394a1a | ||
|
ad375cc3a3 | ||
|
011bc96ec9 | ||
|
da62457c90 | ||
|
38547b0c81 | ||
|
8b36ff1c54 | ||
|
5eff3b992b | ||
|
b5dece2ff4 | ||
|
1d441a8ca6 | ||
|
9c727f342d | ||
|
bedb9d1517 | ||
|
54c2c364a0 | ||
|
106a74d749 | ||
|
322f8f9712 | ||
|
6d9f69a4b6 | ||
|
14b5439a97 | ||
|
01c0cd9de2 | ||
|
e60efba59d | ||
|
5b32118b08 | ||
|
69bcca0a22 | ||
|
49ed863a7e | ||
|
46f9227615 | ||
|
7e59bcc799 | ||
|
d9f17733d9 | ||
|
d15a97ccba | ||
|
da7ab05920 | ||
|
d87ed8e53d | ||
|
1012386c8c | ||
|
7748ed75fe | ||
|
c32b3bada4 | ||
|
e042adc5b8 | ||
|
901e745158 | ||
|
91554b02eb | ||
|
fe7f1a1b38 | ||
|
8fddf94121 | ||
|
003ec854ab | ||
|
00ae92318d | ||
|
e4cd58a722 | ||
|
f4b4705e81 | ||
|
d4960c73dc | ||
|
22eb48ff91 | ||
|
6995266bd1 | ||
|
087f87e95c | ||
|
8edea4f7c5 | ||
|
d79c84d7df | ||
|
57deb82ca7 | ||
|
7a57f5a24f | ||
|
452770566c | ||
|
8247366bdd | ||
|
3dbd9c6ed9 | ||
|
6bbce0b5ca | ||
|
ac805a9286 | ||
|
8932fc84e1 | ||
|
525c4e59b6 | ||
|
a3fcce26a7 | ||
|
58778b1f23 | ||
|
fbfff03541 | ||
|
a1f991088d | ||
|
10af5def2b | ||
|
a745c92e05 | ||
|
b26ba76221 | ||
|
1b1dbd4006 | ||
|
f06521c8ec | ||
|
0847bac3d5 | ||
|
f5bc89b91d | ||
|
c0b14635fd | ||
|
b600431af9 | ||
|
66a07eb8d2 | ||
|
88a7f41038 | ||
|
2fe0937ead | ||
|
efaddf6c51 | ||
|
9e91e7562b | ||
|
7228fa06d1 | ||
|
a3bada8d25 | ||
|
9b7eaa4cf2 | ||
|
7571890551 | ||
|
cb70553484 | ||
|
8298542c39 | ||
|
a9e5f879d8 | ||
|
a6b6d1b659 | ||
|
ba88129641 | ||
|
55259b2915 | ||
|
d3401e5c04 | ||
|
18a328cd2b | ||
|
d05a59e4ed | ||
|
bf713d6967 | ||
|
e9a2a7ebb1 | ||
|
2b8c39b125 | ||
|
c47da4203f | ||
|
e1f02dab31 | ||
|
a4d96a12a9 | ||
|
15c1526895 | ||
|
fa9ac20d39 | ||
|
912ba45d5e |
439 changed files with 14202 additions and 13138 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 140
|
||||||
|
tab_width = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[{*.markdown,*.md}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
indent_size = 2
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
.gradle
|
.gradle
|
||||||
|
.kotlin
|
||||||
local.properties
|
local.properties
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Gemfile
|
Gemfile
|
||||||
|
@ -8,5 +9,6 @@ build
|
||||||
app/build
|
app/build
|
||||||
app/release
|
app/release
|
||||||
app/.cxx
|
app/.cxx
|
||||||
|
app/.attach_*
|
||||||
fastlane/Appfile
|
fastlane/Appfile
|
||||||
tools/*.txt
|
tools/*.txt
|
||||||
|
|
23
README.md
23
README.md
|
@ -14,7 +14,6 @@ Does not use internet permission, and thus is 100% offline.
|
||||||
* [Translations](#translations)
|
* [Translations](#translations)
|
||||||
* [To Community Creation](#to-community)
|
* [To Community Creation](#to-community)
|
||||||
* [Code Contribution](CONTRIBUTING.md)
|
* [Code Contribution](CONTRIBUTING.md)
|
||||||
- [To-do](#to-do)
|
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
- [Credits](#credits)
|
- [Credits](#credits)
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ Does not use internet permission, and thus is 100% offline.
|
||||||
</ul>
|
</ul>
|
||||||
<li>Clipboard history</li>
|
<li>Clipboard history</li>
|
||||||
<li>One-handed mode</li>
|
<li>One-handed mode</li>
|
||||||
<li>Split keyboard (only available if the screen is large enough)</li>
|
<li>Split keyboard</li>
|
||||||
<li>Number pad</li>
|
<li>Number pad</li>
|
||||||
<li>Backup and restore your settings and learned word / history data</li>
|
<li>Backup and restore your settings and learned word / history data</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -88,26 +87,6 @@ You can share your themes, layouts and dictionaries with other people:
|
||||||
## Code Contribution
|
## Code Contribution
|
||||||
See [Contribution Guidelines](CONTRIBUTING.md)
|
See [Contribution Guidelines](CONTRIBUTING.md)
|
||||||
|
|
||||||
# To-do
|
|
||||||
__Planned features and improvements:__
|
|
||||||
* Improve support for modifier keys (_alt_, _ctrl_, _meta_ and _fn_), some ideas:
|
|
||||||
* keep modifier keys on with long press
|
|
||||||
* keep modifier keys on until the next key press
|
|
||||||
* use sliding input
|
|
||||||
* Less complicated addition of new keyboard languages (e.g. #519)
|
|
||||||
* Additional and customizable key swipe functionality
|
|
||||||
* Some functionality will not be possible when using glide typing
|
|
||||||
* Add and enable emoji dictionaries by default (if available for language)
|
|
||||||
* Clearer / more intuitive arrangement of settings
|
|
||||||
* Maybe hide some less used settings by default (similar to color customization)
|
|
||||||
* Make use of the `.com` key in URL fields (currently only available for tablets)
|
|
||||||
* With language-dependent TLDs
|
|
||||||
* [Bug fixes](https://github.com/Helium314/HeliBoard/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
||||||
|
|
||||||
__What will _not_ be added:__
|
|
||||||
* Dictionaries for more languages (you can still download them)
|
|
||||||
* Anything that requires additional permissions, unless there is a _very_ good reason
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
HeliBoard (as a fork of OpenBoard) is licensed under GNU General Public License v3.0.
|
HeliBoard (as a fork of OpenBoard) is licensed under GNU General Public License v3.0.
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
kotlin("android")
|
kotlin("android")
|
||||||
kotlin("plugin.serialization") version "2.0.21"
|
kotlin("plugin.serialization") version "2.1.21"
|
||||||
kotlin("plugin.compose") version "2.0.0"
|
kotlin("plugin.compose") version "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
buildToolsVersion = "34.0.0"
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "helium314.keyboard"
|
applicationId = "helium314.keyboard"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 3000
|
versionCode = 3201
|
||||||
versionName = "3.0-alpha1"
|
versionName = "3.2"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters.clear()
|
abiFilters.clear()
|
||||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
||||||
}
|
}
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -36,14 +35,23 @@ android {
|
||||||
isJniDebuggable = false
|
isJniDebuggable = false
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
|
// "normal" debug has minify for smaller APK to fit the GitHub 25 MB limit when zipped
|
||||||
|
// and for better performance in case users want to install a debug APK
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isJniDebuggable = false
|
isJniDebuggable = false
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
}
|
}
|
||||||
create("runTests") { // build variant for running tests on CI that skips tests known to fail
|
create("runTests") { // build variant for running tests on CI that skips tests known to fail
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = false
|
||||||
isJniDebuggable = false
|
isJniDebuggable = false
|
||||||
}
|
}
|
||||||
|
create("debugNoMinify") { // for faster builds in IDE
|
||||||
|
isDebuggable = true
|
||||||
|
isMinifyEnabled = false
|
||||||
|
isJniDebuggable = false
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
applicationIdSuffix = ".debug"
|
||||||
|
}
|
||||||
base.archivesBaseName = "HeliBoard_" + defaultConfig.versionName
|
base.archivesBaseName = "HeliBoard_" + defaultConfig.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +66,9 @@ android {
|
||||||
path = File("src/main/jni/Android.mk")
|
path = File("src/main/jni/Android.mk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ndkVersion = "26.2.11394342"
|
ndkVersion = "28.0.13004108"
|
||||||
|
|
||||||
packagingOptions {
|
packaging {
|
||||||
jniLibs {
|
jniLibs {
|
||||||
// shrinks APK by 3 MB, zipped size unchanged
|
// shrinks APK by 3 MB, zipped size unchanged
|
||||||
useLegacyPackaging = true
|
useLegacyPackaging = true
|
||||||
|
@ -96,29 +104,29 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// androidx
|
// androidx
|
||||||
implementation("androidx.core:core-ktx:1.13.1")
|
implementation("androidx.core:core-ktx:1.16.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
implementation("androidx.recyclerview:recyclerview:1.4.0")
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
|
||||||
implementation("androidx.autofill:autofill:1.1.0")
|
implementation("androidx.autofill:autofill:1.1.0")
|
||||||
|
implementation("androidx.viewpager2:viewpager2:1.1.0")
|
||||||
|
|
||||||
// kotlin
|
// kotlin
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
|
||||||
|
|
||||||
// compose
|
// compose
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||||
implementation(platform("androidx.compose:compose-bom:2025.02.00"))
|
implementation(platform("androidx.compose:compose-bom:2025.05.00"))
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
implementation("androidx.navigation:navigation-compose:2.8.8")
|
implementation("androidx.navigation:navigation-compose:2.9.0")
|
||||||
implementation("sh.calvin.reorderable:reorderable:2.4.3") // for easier re-ordering
|
implementation("sh.calvin.reorderable:reorderable:2.4.3") // for easier re-ordering
|
||||||
implementation("com.github.skydoves:colorpicker-compose:1.1.2") // for user-defined colors
|
implementation("com.github.skydoves:colorpicker-compose:1.1.2") // for user-defined colors
|
||||||
|
|
||||||
// test
|
// test
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
testImplementation("org.mockito:mockito-core:5.15.2")
|
testImplementation("org.mockito:mockito-core:5.17.0")
|
||||||
testImplementation("org.robolectric:robolectric:4.12.1")
|
testImplementation("org.robolectric:robolectric:4.14.1")
|
||||||
testImplementation("androidx.test:runner:1.6.2")
|
testImplementation("androidx.test:runner:1.6.2")
|
||||||
testImplementation("androidx.test:core:1.6.1")
|
testImplementation("androidx.test:core:1.6.1")
|
||||||
}
|
}
|
||||||
|
|
9
app/src/debugNoMinify/res/values/strings.xml
Normal file
9
app/src/debugNoMinify/res/values/strings.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="english_ime_name" translatable="false">HeliBoard debug</string>
|
||||||
|
<string name="spell_checker_service_name" translatable="false">HeliBoard debug Spell Checker</string>
|
||||||
|
<string name="ime_settings" translatable="false">HeliBoard debug Settings</string>
|
||||||
|
</resources>
|
|
@ -98,7 +98,15 @@ SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||||
<queries>
|
<queries>
|
||||||
<!-- To detect other IMEs -->
|
<!-- To detect other IMEs -->
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.view.InputMethod"/>
|
<!-- changed to * as it's supposed to help with finding other keyboard apps, see https://github.com/Helium314/HeliBoard/issues/1340 -->
|
||||||
|
<!--<action android:name="android.view.InputMethod" />-->
|
||||||
|
<action android:name="*" />
|
||||||
|
</intent>
|
||||||
|
|
||||||
|
<!-- To detect names of installed apps -->
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,114 +1,116 @@
|
||||||
|
main,af,exp
|
||||||
main,ar,
|
main,ar,
|
||||||
|
main,ar,exp
|
||||||
main,hy,
|
main,hy,
|
||||||
main,as,
|
main,as,
|
||||||
|
main,bn_BD,exp
|
||||||
main,bn,
|
main,bn,
|
||||||
|
main,bn,exp
|
||||||
main,eu,
|
main,eu,
|
||||||
main,be,
|
main,be,
|
||||||
main,bg,
|
main,bg,
|
||||||
|
main,bg,exp
|
||||||
main,ca,
|
main,ca,
|
||||||
|
main,ca,exp
|
||||||
main,hr,
|
main,hr,
|
||||||
|
main,hr,exp
|
||||||
main,cs,
|
main,cs,
|
||||||
|
main,cs,exp
|
||||||
main,da,
|
main,da,
|
||||||
|
main,da,exp
|
||||||
main,nl,
|
main,nl,
|
||||||
|
main,nl,exp
|
||||||
main,en_AU,
|
main,en_AU,
|
||||||
|
main,en_CA,exp
|
||||||
main,en_GB,
|
main,en_GB,
|
||||||
|
main,en_GB,exp
|
||||||
main,en_US,
|
main,en_US,
|
||||||
|
main,en_US,exp
|
||||||
|
symbols,en,exp
|
||||||
emoji,en,
|
emoji,en,
|
||||||
main,eo,
|
main,eo,
|
||||||
|
main,eo,exp
|
||||||
|
main,et,exp
|
||||||
main,fi,
|
main,fi,
|
||||||
|
main,fi,exp
|
||||||
emoji,fr,
|
emoji,fr,
|
||||||
|
symbols,fr,exp
|
||||||
main,fr,
|
main,fr,
|
||||||
|
main,fr,exp
|
||||||
main,gl,
|
main,gl,
|
||||||
|
main,gl,exp
|
||||||
main,ka,
|
main,ka,
|
||||||
|
main,de_AT,exp
|
||||||
|
main,de_CH,
|
||||||
main,de,
|
main,de,
|
||||||
|
main,de,exp
|
||||||
main,gom,
|
main,gom,
|
||||||
main,el,
|
main,el,
|
||||||
main,gu,
|
main,gu,
|
||||||
main,he,
|
main,he,
|
||||||
main,iw,
|
main,iw,
|
||||||
|
main,he,exp
|
||||||
main,hi,
|
main,hi,
|
||||||
main,hi_ZZ,
|
main,hi_ZZ,
|
||||||
main,hu,
|
main,hu,
|
||||||
|
main,hu,exp
|
||||||
|
main,is,exp
|
||||||
|
main,id,exp
|
||||||
main,it,
|
main,it,
|
||||||
|
main,it,exp
|
||||||
|
main,kab,exp
|
||||||
main,kn,
|
main,kn,
|
||||||
main,ks,
|
main,ks,
|
||||||
|
main,kk,exp
|
||||||
main,km,
|
main,km,
|
||||||
main,la,
|
main,la,
|
||||||
main,lv,
|
main,lv,
|
||||||
|
main,lv,exp
|
||||||
main,lt,
|
main,lt,
|
||||||
|
main,lt,exp
|
||||||
main,lb,
|
main,lb,
|
||||||
main,mai,
|
main,mai,
|
||||||
|
addon,ml_ZZ,exp
|
||||||
main,ml,
|
main,ml,
|
||||||
main,mr,
|
main,mr,
|
||||||
|
main,ne,exp
|
||||||
main,nb,
|
main,nb,
|
||||||
|
main,nb,exp
|
||||||
main,or,
|
main,or,
|
||||||
|
main,pms,exp
|
||||||
main,pl,
|
main,pl,
|
||||||
|
main,pl,exp
|
||||||
main,pt_BR,
|
main,pt_BR,
|
||||||
main,pt_PT,
|
main,pt_PT,
|
||||||
|
main,pt_PT,exp
|
||||||
main,pa,
|
main,pa,
|
||||||
main,ro,
|
main,ro,
|
||||||
|
main,ro,exp
|
||||||
emoji,ru,
|
emoji,ru,
|
||||||
main,ru,
|
main,ru,
|
||||||
|
main,ru,exp
|
||||||
main,sa,
|
main,sa,
|
||||||
main,sat,
|
main,sat,
|
||||||
main,sr_ZZ,
|
main,sr_ZZ,
|
||||||
main,sr,
|
main,sr,
|
||||||
main,sd,
|
main,sd,
|
||||||
|
main,sk,exp
|
||||||
main,sl,
|
main,sl,
|
||||||
|
main,sl,exp
|
||||||
main,es,
|
main,es,
|
||||||
|
main,es,exp
|
||||||
main,zgh_ZZ,
|
main,zgh_ZZ,
|
||||||
main,zgh,
|
main,zgh,
|
||||||
main,sv,
|
main,sv,
|
||||||
|
main,sv,exp
|
||||||
main,ta,
|
main,ta,
|
||||||
main,te,
|
main,te,
|
||||||
main,tok,
|
main,tok,
|
||||||
main,tcy,
|
main,tcy,
|
||||||
main,tr,
|
main,tr,
|
||||||
|
main,tr,exp
|
||||||
emoji,uk,
|
emoji,uk,
|
||||||
main,uk,
|
main,uk,
|
||||||
main,ur,
|
|
||||||
main,af,exp
|
|
||||||
main,ar,exp
|
|
||||||
main,bn_BD,exp
|
|
||||||
main,bn,exp
|
|
||||||
main,bg,exp
|
|
||||||
main,ca,exp
|
|
||||||
main,hr,exp
|
|
||||||
main,cs,exp
|
|
||||||
main,da,exp
|
|
||||||
main,nl,exp
|
|
||||||
main,en_GB,exp
|
|
||||||
main,en_US,exp
|
|
||||||
symbols,en,exp
|
|
||||||
main,eo,exp
|
|
||||||
main,et,exp
|
|
||||||
main,fi,exp
|
|
||||||
symbols,fr,exp
|
|
||||||
main,fr,exp
|
|
||||||
main,gl,exp
|
|
||||||
main,de_AT,exp
|
|
||||||
main,de,exp
|
|
||||||
main,he,exp
|
|
||||||
main,hu,exp
|
|
||||||
main,is,exp
|
|
||||||
main,id,exp
|
|
||||||
main,it,exp
|
|
||||||
main,kab,exp
|
|
||||||
main,kk,exp
|
|
||||||
main,lv,exp
|
|
||||||
main,lt,exp
|
|
||||||
addon,ml_ZZ,exp
|
|
||||||
main,ne,exp
|
|
||||||
main,nb,exp
|
|
||||||
main,pms,exp
|
|
||||||
main,pl,exp
|
|
||||||
main,pt_PT,exp
|
|
||||||
main,ro,exp
|
|
||||||
main,ru,exp
|
|
||||||
main,sk,exp
|
|
||||||
main,sl,exp
|
|
||||||
main,es,exp
|
|
||||||
main,sv,exp
|
|
||||||
main,tr,exp
|
|
||||||
main,uk,exp
|
main,uk,exp
|
||||||
|
main,ur,
|
||||||
main,vi,exp
|
main,vi,exp
|
||||||
|
|
|
85
app/src/main/assets/emoji/ACTIVITIES.txt
Normal file
85
app/src/main/assets/emoji/ACTIVITIES.txt
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
🎃
|
||||||
|
🎄
|
||||||
|
🎆
|
||||||
|
🎇
|
||||||
|
🧨
|
||||||
|
✨
|
||||||
|
🎈
|
||||||
|
🎉
|
||||||
|
🎊
|
||||||
|
🎋
|
||||||
|
🎍
|
||||||
|
🎎
|
||||||
|
🎏
|
||||||
|
🎐
|
||||||
|
🎑
|
||||||
|
🧧
|
||||||
|
🎀
|
||||||
|
🎁
|
||||||
|
🎗️
|
||||||
|
🎟️
|
||||||
|
🎫
|
||||||
|
🎖️
|
||||||
|
🏆
|
||||||
|
🏅
|
||||||
|
🥇
|
||||||
|
🥈
|
||||||
|
🥉
|
||||||
|
⚽
|
||||||
|
⚾
|
||||||
|
🥎
|
||||||
|
🏀
|
||||||
|
🏐
|
||||||
|
🏈
|
||||||
|
🏉
|
||||||
|
🎾
|
||||||
|
🥏
|
||||||
|
🎳
|
||||||
|
🏏
|
||||||
|
🏑
|
||||||
|
🏒
|
||||||
|
🥍
|
||||||
|
🏓
|
||||||
|
🏸
|
||||||
|
🥊
|
||||||
|
🥋
|
||||||
|
🥅
|
||||||
|
⛳
|
||||||
|
⛸️
|
||||||
|
🎣
|
||||||
|
🤿
|
||||||
|
🎽
|
||||||
|
🎿
|
||||||
|
🛷
|
||||||
|
🥌
|
||||||
|
🎯
|
||||||
|
🪀
|
||||||
|
🪁
|
||||||
|
🔫
|
||||||
|
🎱
|
||||||
|
🔮
|
||||||
|
🪄
|
||||||
|
🎮
|
||||||
|
🕹️
|
||||||
|
🎰
|
||||||
|
🎲
|
||||||
|
🧩
|
||||||
|
🧸
|
||||||
|
🪅
|
||||||
|
🪩
|
||||||
|
🪆
|
||||||
|
♠️
|
||||||
|
♥️
|
||||||
|
♦️
|
||||||
|
♣️
|
||||||
|
♟️
|
||||||
|
🃏
|
||||||
|
🀄
|
||||||
|
🎴
|
||||||
|
🎭
|
||||||
|
🖼️
|
||||||
|
🎨
|
||||||
|
🧵
|
||||||
|
🪡
|
||||||
|
🧶
|
||||||
|
🪢
|
159
app/src/main/assets/emoji/ANIMALS_AND_NATURE.txt
Normal file
159
app/src/main/assets/emoji/ANIMALS_AND_NATURE.txt
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
🐵
|
||||||
|
🐒
|
||||||
|
🦍
|
||||||
|
🦧
|
||||||
|
🐶
|
||||||
|
🐕
|
||||||
|
🦮
|
||||||
|
🐕🦺
|
||||||
|
🐩
|
||||||
|
🐺
|
||||||
|
🦊
|
||||||
|
🦝
|
||||||
|
🐱
|
||||||
|
🐈
|
||||||
|
🐈⬛
|
||||||
|
🦁
|
||||||
|
🐯
|
||||||
|
🐅
|
||||||
|
🐆
|
||||||
|
🐴
|
||||||
|
🫎
|
||||||
|
🫏
|
||||||
|
🐎
|
||||||
|
🦄
|
||||||
|
🦓
|
||||||
|
🦌
|
||||||
|
🦬
|
||||||
|
🐮
|
||||||
|
🐂
|
||||||
|
🐃
|
||||||
|
🐄
|
||||||
|
🐷
|
||||||
|
🐖
|
||||||
|
🐗
|
||||||
|
🐽
|
||||||
|
🐏
|
||||||
|
🐑
|
||||||
|
🐐
|
||||||
|
🐪
|
||||||
|
🐫
|
||||||
|
🦙
|
||||||
|
🦒
|
||||||
|
🐘
|
||||||
|
🦣
|
||||||
|
🦏
|
||||||
|
🦛
|
||||||
|
🐭
|
||||||
|
🐁
|
||||||
|
🐀
|
||||||
|
🐹
|
||||||
|
🐰
|
||||||
|
🐇
|
||||||
|
🐿️
|
||||||
|
🦫
|
||||||
|
🦔
|
||||||
|
🦇
|
||||||
|
🐻
|
||||||
|
🐻❄️
|
||||||
|
🐨
|
||||||
|
🐼
|
||||||
|
🦥
|
||||||
|
🦦
|
||||||
|
🦨
|
||||||
|
🦘
|
||||||
|
🦡
|
||||||
|
🐾
|
||||||
|
🦃
|
||||||
|
🐔
|
||||||
|
🐓
|
||||||
|
🐣
|
||||||
|
🐤
|
||||||
|
🐥
|
||||||
|
🐦
|
||||||
|
🐧
|
||||||
|
🕊️
|
||||||
|
🦅
|
||||||
|
🦆
|
||||||
|
🦢
|
||||||
|
🦉
|
||||||
|
🦤
|
||||||
|
🪶
|
||||||
|
🦩
|
||||||
|
🦚
|
||||||
|
🦜
|
||||||
|
🪽
|
||||||
|
🐦⬛
|
||||||
|
🪿
|
||||||
|
🐦🔥
|
||||||
|
🐸
|
||||||
|
🐊
|
||||||
|
🐢
|
||||||
|
🦎
|
||||||
|
🐍
|
||||||
|
🐲
|
||||||
|
🐉
|
||||||
|
🦕
|
||||||
|
🦖
|
||||||
|
🐳
|
||||||
|
🐋
|
||||||
|
🐬
|
||||||
|
🦭
|
||||||
|
🐟
|
||||||
|
🐠
|
||||||
|
🐡
|
||||||
|
🦈
|
||||||
|
🐙
|
||||||
|
🐚
|
||||||
|
🪸
|
||||||
|
🪼
|
||||||
|
🦀
|
||||||
|
🦞
|
||||||
|
🦐
|
||||||
|
🦑
|
||||||
|
🦪
|
||||||
|
🐌
|
||||||
|
🦋
|
||||||
|
🐛
|
||||||
|
🐜
|
||||||
|
🐝
|
||||||
|
🪲
|
||||||
|
🐞
|
||||||
|
🦗
|
||||||
|
🪳
|
||||||
|
🕷️
|
||||||
|
🕸️
|
||||||
|
🦂
|
||||||
|
🦟
|
||||||
|
🪰
|
||||||
|
🪱
|
||||||
|
🦠
|
||||||
|
💐
|
||||||
|
🌸
|
||||||
|
💮
|
||||||
|
🪷
|
||||||
|
🏵️
|
||||||
|
🌹
|
||||||
|
🥀
|
||||||
|
🌺
|
||||||
|
🌻
|
||||||
|
🌼
|
||||||
|
🌷
|
||||||
|
🪻
|
||||||
|
🌱
|
||||||
|
🪴
|
||||||
|
🌲
|
||||||
|
🌳
|
||||||
|
🌴
|
||||||
|
🌵
|
||||||
|
🌾
|
||||||
|
🌿
|
||||||
|
☘️
|
||||||
|
🍀
|
||||||
|
🍁
|
||||||
|
🍂
|
||||||
|
🍃
|
||||||
|
🪹
|
||||||
|
🪺
|
||||||
|
🍄
|
||||||
|
|
25
app/src/main/assets/emoji/EMOTICONS.txt
Normal file
25
app/src/main/assets/emoji/EMOTICONS.txt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
:-)
|
||||||
|
;-)
|
||||||
|
:-(
|
||||||
|
:-!
|
||||||
|
:-$
|
||||||
|
B-)
|
||||||
|
=-O
|
||||||
|
:-P
|
||||||
|
:O
|
||||||
|
:-*
|
||||||
|
:-D
|
||||||
|
:\'(
|
||||||
|
:-\\
|
||||||
|
O:-)
|
||||||
|
:-[
|
||||||
|
(╯°
|
||||||
|
□°)
|
||||||
|
╯︵
|
||||||
|
┻━┻
|
||||||
|
¯\\_
|
||||||
|
(ツ)
|
||||||
|
_/¯
|
||||||
|
┬─┬
|
||||||
|
︵ /(
|
||||||
|
.□.\\
|
270
app/src/main/assets/emoji/FLAGS.txt
Normal file
270
app/src/main/assets/emoji/FLAGS.txt
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
🏁
|
||||||
|
🚩
|
||||||
|
🎌
|
||||||
|
🏴
|
||||||
|
🏳️
|
||||||
|
🏳️🌈
|
||||||
|
🏳️⚧️
|
||||||
|
🏴☠️
|
||||||
|
🇦🇨
|
||||||
|
🇦🇩
|
||||||
|
🇦🇪
|
||||||
|
🇦🇫
|
||||||
|
🇦🇬
|
||||||
|
🇦🇮
|
||||||
|
🇦🇱
|
||||||
|
🇦🇲
|
||||||
|
🇦🇴
|
||||||
|
🇦🇶
|
||||||
|
🇦🇷
|
||||||
|
🇦🇸
|
||||||
|
🇦🇹
|
||||||
|
🇦🇺
|
||||||
|
🇦🇼
|
||||||
|
🇦🇽
|
||||||
|
🇦🇿
|
||||||
|
🇧🇦
|
||||||
|
🇧🇧
|
||||||
|
🇧🇩
|
||||||
|
🇧🇪
|
||||||
|
🇧🇫
|
||||||
|
🇧🇬
|
||||||
|
🇧🇭
|
||||||
|
🇧🇮
|
||||||
|
🇧🇯
|
||||||
|
🇧🇱
|
||||||
|
🇧🇲
|
||||||
|
🇧🇳
|
||||||
|
🇧🇴
|
||||||
|
🇧🇶
|
||||||
|
🇧🇷
|
||||||
|
🇧🇸
|
||||||
|
🇧🇹
|
||||||
|
🇧🇻
|
||||||
|
🇧🇼
|
||||||
|
🇧🇾
|
||||||
|
🇧🇿
|
||||||
|
🇨🇦
|
||||||
|
🇨🇨
|
||||||
|
🇨🇩
|
||||||
|
🇨🇫
|
||||||
|
🇨🇬
|
||||||
|
🇨🇭
|
||||||
|
🇨🇮
|
||||||
|
🇨🇰
|
||||||
|
🇨🇱
|
||||||
|
🇨🇲
|
||||||
|
🇨🇳
|
||||||
|
🇨🇴
|
||||||
|
🇨🇵
|
||||||
|
🇨🇶
|
||||||
|
🇨🇷
|
||||||
|
🇨🇺
|
||||||
|
🇨🇻
|
||||||
|
🇨🇼
|
||||||
|
🇨🇽
|
||||||
|
🇨🇾
|
||||||
|
🇨🇿
|
||||||
|
🇩🇪
|
||||||
|
🇩🇬
|
||||||
|
🇩🇯
|
||||||
|
🇩🇰
|
||||||
|
🇩🇲
|
||||||
|
🇩🇴
|
||||||
|
🇩🇿
|
||||||
|
🇪🇦
|
||||||
|
🇪🇨
|
||||||
|
🇪🇪
|
||||||
|
🇪🇬
|
||||||
|
🇪🇭
|
||||||
|
🇪🇷
|
||||||
|
🇪🇸
|
||||||
|
🇪🇹
|
||||||
|
🇪🇺
|
||||||
|
🇫🇮
|
||||||
|
🇫🇯
|
||||||
|
🇫🇰
|
||||||
|
🇫🇲
|
||||||
|
🇫🇴
|
||||||
|
🇫🇷
|
||||||
|
🇬🇦
|
||||||
|
🇬🇧
|
||||||
|
🇬🇩
|
||||||
|
🇬🇪
|
||||||
|
🇬🇫
|
||||||
|
🇬🇬
|
||||||
|
🇬🇭
|
||||||
|
🇬🇮
|
||||||
|
🇬🇱
|
||||||
|
🇬🇲
|
||||||
|
🇬🇳
|
||||||
|
🇬🇵
|
||||||
|
🇬🇶
|
||||||
|
🇬🇷
|
||||||
|
🇬🇸
|
||||||
|
🇬🇹
|
||||||
|
🇬🇺
|
||||||
|
🇬🇼
|
||||||
|
🇬🇾
|
||||||
|
🇭🇰
|
||||||
|
🇭🇲
|
||||||
|
🇭🇳
|
||||||
|
🇭🇷
|
||||||
|
🇭🇹
|
||||||
|
🇭🇺
|
||||||
|
🇮🇨
|
||||||
|
🇮🇩
|
||||||
|
🇮🇪
|
||||||
|
🇮🇱
|
||||||
|
🇮🇲
|
||||||
|
🇮🇳
|
||||||
|
🇮🇴
|
||||||
|
🇮🇶
|
||||||
|
🇮🇷
|
||||||
|
🇮🇸
|
||||||
|
🇮🇹
|
||||||
|
🇯🇪
|
||||||
|
🇯🇲
|
||||||
|
🇯🇴
|
||||||
|
🇯🇵
|
||||||
|
🇰🇪
|
||||||
|
🇰🇬
|
||||||
|
🇰🇭
|
||||||
|
🇰🇮
|
||||||
|
🇰🇲
|
||||||
|
🇰🇳
|
||||||
|
🇰🇵
|
||||||
|
🇰🇷
|
||||||
|
🇰🇼
|
||||||
|
🇰🇾
|
||||||
|
🇰🇿
|
||||||
|
🇱🇦
|
||||||
|
🇱🇧
|
||||||
|
🇱🇨
|
||||||
|
🇱🇮
|
||||||
|
🇱🇰
|
||||||
|
🇱🇷
|
||||||
|
🇱🇸
|
||||||
|
🇱🇹
|
||||||
|
🇱🇺
|
||||||
|
🇱🇻
|
||||||
|
🇱🇾
|
||||||
|
🇲🇦
|
||||||
|
🇲🇨
|
||||||
|
🇲🇩
|
||||||
|
🇲🇪
|
||||||
|
🇲🇫
|
||||||
|
🇲🇬
|
||||||
|
🇲🇭
|
||||||
|
🇲🇰
|
||||||
|
🇲🇱
|
||||||
|
🇲🇲
|
||||||
|
🇲🇳
|
||||||
|
🇲🇴
|
||||||
|
🇲🇵
|
||||||
|
🇲🇶
|
||||||
|
🇲🇷
|
||||||
|
🇲🇸
|
||||||
|
🇲🇹
|
||||||
|
🇲🇺
|
||||||
|
🇲🇻
|
||||||
|
🇲🇼
|
||||||
|
🇲🇽
|
||||||
|
🇲🇾
|
||||||
|
🇲🇿
|
||||||
|
🇳🇦
|
||||||
|
🇳🇨
|
||||||
|
🇳🇪
|
||||||
|
🇳🇫
|
||||||
|
🇳🇬
|
||||||
|
🇳🇮
|
||||||
|
🇳🇱
|
||||||
|
🇳🇴
|
||||||
|
🇳🇵
|
||||||
|
🇳🇷
|
||||||
|
🇳🇺
|
||||||
|
🇳🇿
|
||||||
|
🇴🇲
|
||||||
|
🇵🇦
|
||||||
|
🇵🇪
|
||||||
|
🇵🇫
|
||||||
|
🇵🇬
|
||||||
|
🇵🇭
|
||||||
|
🇵🇰
|
||||||
|
🇵🇱
|
||||||
|
🇵🇲
|
||||||
|
🇵🇳
|
||||||
|
🇵🇷
|
||||||
|
🇵🇸
|
||||||
|
🇵🇹
|
||||||
|
🇵🇼
|
||||||
|
🇵🇾
|
||||||
|
🇶🇦
|
||||||
|
🇷🇪
|
||||||
|
🇷🇴
|
||||||
|
🇷🇸
|
||||||
|
🇷🇺
|
||||||
|
🇷🇼
|
||||||
|
🇸🇦
|
||||||
|
🇸🇧
|
||||||
|
🇸🇨
|
||||||
|
🇸🇩
|
||||||
|
🇸🇪
|
||||||
|
🇸🇬
|
||||||
|
🇸🇭
|
||||||
|
🇸🇮
|
||||||
|
🇸🇯
|
||||||
|
🇸🇰
|
||||||
|
🇸🇱
|
||||||
|
🇸🇲
|
||||||
|
🇸🇳
|
||||||
|
🇸🇴
|
||||||
|
🇸🇷
|
||||||
|
🇸🇸
|
||||||
|
🇸🇹
|
||||||
|
🇸🇻
|
||||||
|
🇸🇽
|
||||||
|
🇸🇾
|
||||||
|
🇸🇿
|
||||||
|
🇹🇦
|
||||||
|
🇹🇨
|
||||||
|
🇹🇩
|
||||||
|
🇹🇫
|
||||||
|
🇹🇬
|
||||||
|
🇹🇭
|
||||||
|
🇹🇯
|
||||||
|
🇹🇰
|
||||||
|
🇹🇱
|
||||||
|
🇹🇲
|
||||||
|
🇹🇳
|
||||||
|
🇹🇴
|
||||||
|
🇹🇷
|
||||||
|
🇹🇹
|
||||||
|
🇹🇻
|
||||||
|
🇹🇼
|
||||||
|
🇹🇿
|
||||||
|
🇺🇦
|
||||||
|
🇺🇬
|
||||||
|
🇺🇲
|
||||||
|
🇺🇳
|
||||||
|
🇺🇸
|
||||||
|
🇺🇾
|
||||||
|
🇺🇿
|
||||||
|
🇻🇦
|
||||||
|
🇻🇨
|
||||||
|
🇻🇪
|
||||||
|
🇻🇬
|
||||||
|
🇻🇮
|
||||||
|
🇻🇳
|
||||||
|
🇻🇺
|
||||||
|
🇼🇫
|
||||||
|
🇼🇸
|
||||||
|
🇽🇰
|
||||||
|
🇾🇪
|
||||||
|
🇾🇹
|
||||||
|
🇿🇦
|
||||||
|
🇿🇲
|
||||||
|
🇿🇼
|
||||||
|
🏴
|
||||||
|
🏴
|
||||||
|
🏴
|
131
app/src/main/assets/emoji/FOOD_AND_DRINK.txt
Normal file
131
app/src/main/assets/emoji/FOOD_AND_DRINK.txt
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
🍇
|
||||||
|
🍈
|
||||||
|
🍉
|
||||||
|
🍊
|
||||||
|
🍋
|
||||||
|
🍋🟩
|
||||||
|
🍌
|
||||||
|
🍍
|
||||||
|
🥭
|
||||||
|
🍎
|
||||||
|
🍏
|
||||||
|
🍐
|
||||||
|
🍑
|
||||||
|
🍒
|
||||||
|
🍓
|
||||||
|
🫐
|
||||||
|
🥝
|
||||||
|
🍅
|
||||||
|
🫒
|
||||||
|
🥥
|
||||||
|
🥑
|
||||||
|
🍆
|
||||||
|
🥔
|
||||||
|
🥕
|
||||||
|
🌽
|
||||||
|
🌶️
|
||||||
|
🫑
|
||||||
|
🥒
|
||||||
|
🥬
|
||||||
|
🥦
|
||||||
|
🧄
|
||||||
|
🧅
|
||||||
|
🥜
|
||||||
|
🫘
|
||||||
|
🌰
|
||||||
|
🫚
|
||||||
|
🫛
|
||||||
|
🍄🟫
|
||||||
|
|
||||||
|
🍞
|
||||||
|
🥐
|
||||||
|
🥖
|
||||||
|
🫓
|
||||||
|
🥨
|
||||||
|
🥯
|
||||||
|
🥞
|
||||||
|
🧇
|
||||||
|
🧀
|
||||||
|
🍖
|
||||||
|
🍗
|
||||||
|
🥩
|
||||||
|
🥓
|
||||||
|
🍔
|
||||||
|
🍟
|
||||||
|
🍕
|
||||||
|
🌭
|
||||||
|
🥪
|
||||||
|
🌮
|
||||||
|
🌯
|
||||||
|
🫔
|
||||||
|
🥙
|
||||||
|
🧆
|
||||||
|
🥚
|
||||||
|
🍳
|
||||||
|
🥘
|
||||||
|
🍲
|
||||||
|
🫕
|
||||||
|
🥣
|
||||||
|
🥗
|
||||||
|
🍿
|
||||||
|
🧈
|
||||||
|
🧂
|
||||||
|
🥫
|
||||||
|
🍱
|
||||||
|
🍘
|
||||||
|
🍙
|
||||||
|
🍚
|
||||||
|
🍛
|
||||||
|
🍜
|
||||||
|
🍝
|
||||||
|
🍠
|
||||||
|
🍢
|
||||||
|
🍣
|
||||||
|
🍤
|
||||||
|
🍥
|
||||||
|
🥮
|
||||||
|
🍡
|
||||||
|
🥟
|
||||||
|
🥠
|
||||||
|
🥡
|
||||||
|
🍦
|
||||||
|
🍧
|
||||||
|
🍨
|
||||||
|
🍩
|
||||||
|
🍪
|
||||||
|
🎂
|
||||||
|
🍰
|
||||||
|
🧁
|
||||||
|
🥧
|
||||||
|
🍫
|
||||||
|
🍬
|
||||||
|
🍭
|
||||||
|
🍮
|
||||||
|
🍯
|
||||||
|
🍼
|
||||||
|
🥛
|
||||||
|
☕
|
||||||
|
🫖
|
||||||
|
🍵
|
||||||
|
🍶
|
||||||
|
🍾
|
||||||
|
🍷
|
||||||
|
🍸
|
||||||
|
🍹
|
||||||
|
🍺
|
||||||
|
🍻
|
||||||
|
🥂
|
||||||
|
🥃
|
||||||
|
🫗
|
||||||
|
🥤
|
||||||
|
🧋
|
||||||
|
🧃
|
||||||
|
🧉
|
||||||
|
🧊
|
||||||
|
🥢
|
||||||
|
🍽️
|
||||||
|
🍴
|
||||||
|
🥄
|
||||||
|
🔪
|
||||||
|
🫙
|
||||||
|
🏺
|
264
app/src/main/assets/emoji/OBJECTS.txt
Normal file
264
app/src/main/assets/emoji/OBJECTS.txt
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
👓
|
||||||
|
🕶️
|
||||||
|
🥽
|
||||||
|
🥼
|
||||||
|
🦺
|
||||||
|
👔
|
||||||
|
👕
|
||||||
|
👖
|
||||||
|
🧣
|
||||||
|
🧤
|
||||||
|
🧥
|
||||||
|
🧦
|
||||||
|
👗
|
||||||
|
👘
|
||||||
|
🥻
|
||||||
|
🩱
|
||||||
|
🩲
|
||||||
|
🩳
|
||||||
|
👙
|
||||||
|
👚
|
||||||
|
🪭
|
||||||
|
👛
|
||||||
|
👜
|
||||||
|
👝
|
||||||
|
🛍️
|
||||||
|
🎒
|
||||||
|
🩴
|
||||||
|
👞
|
||||||
|
👟
|
||||||
|
🥾
|
||||||
|
🥿
|
||||||
|
👠
|
||||||
|
👡
|
||||||
|
🩰
|
||||||
|
👢
|
||||||
|
🪮
|
||||||
|
👑
|
||||||
|
👒
|
||||||
|
🎩
|
||||||
|
🎓
|
||||||
|
🧢
|
||||||
|
🪖
|
||||||
|
⛑️
|
||||||
|
📿
|
||||||
|
💄
|
||||||
|
💍
|
||||||
|
💎
|
||||||
|
🔇
|
||||||
|
🔈
|
||||||
|
🔉
|
||||||
|
🔊
|
||||||
|
📢
|
||||||
|
📣
|
||||||
|
📯
|
||||||
|
🔔
|
||||||
|
🔕
|
||||||
|
🎼
|
||||||
|
🎵
|
||||||
|
🎶
|
||||||
|
🎙️
|
||||||
|
🎚️
|
||||||
|
🎛️
|
||||||
|
🎤
|
||||||
|
🎧
|
||||||
|
📻
|
||||||
|
🎷
|
||||||
|
🪗
|
||||||
|
🎸
|
||||||
|
🎹
|
||||||
|
🎺
|
||||||
|
🎻
|
||||||
|
🪕
|
||||||
|
🥁
|
||||||
|
🪘
|
||||||
|
🪇
|
||||||
|
🪈
|
||||||
|
|
||||||
|
📱
|
||||||
|
📲
|
||||||
|
☎️
|
||||||
|
📞
|
||||||
|
📟
|
||||||
|
📠
|
||||||
|
🔋
|
||||||
|
🪫
|
||||||
|
🔌
|
||||||
|
💻
|
||||||
|
🖥️
|
||||||
|
🖨️
|
||||||
|
⌨️
|
||||||
|
🖱️
|
||||||
|
🖲️
|
||||||
|
💽
|
||||||
|
💾
|
||||||
|
💿
|
||||||
|
📀
|
||||||
|
🧮
|
||||||
|
🎥
|
||||||
|
🎞️
|
||||||
|
📽️
|
||||||
|
🎬
|
||||||
|
📺
|
||||||
|
📷
|
||||||
|
📸
|
||||||
|
📹
|
||||||
|
📼
|
||||||
|
🔍
|
||||||
|
🔎
|
||||||
|
🕯️
|
||||||
|
💡
|
||||||
|
🔦
|
||||||
|
🏮
|
||||||
|
🪔
|
||||||
|
📔
|
||||||
|
📕
|
||||||
|
📖
|
||||||
|
📗
|
||||||
|
📘
|
||||||
|
📙
|
||||||
|
📚
|
||||||
|
📓
|
||||||
|
📒
|
||||||
|
📃
|
||||||
|
📜
|
||||||
|
📄
|
||||||
|
📰
|
||||||
|
🗞️
|
||||||
|
📑
|
||||||
|
🔖
|
||||||
|
🏷️
|
||||||
|
💰
|
||||||
|
🪙
|
||||||
|
💴
|
||||||
|
💵
|
||||||
|
💶
|
||||||
|
💷
|
||||||
|
💸
|
||||||
|
💳
|
||||||
|
🧾
|
||||||
|
💹
|
||||||
|
✉️
|
||||||
|
📧
|
||||||
|
📨
|
||||||
|
📩
|
||||||
|
📤
|
||||||
|
📥
|
||||||
|
📦
|
||||||
|
📫
|
||||||
|
📪
|
||||||
|
📬
|
||||||
|
📭
|
||||||
|
📮
|
||||||
|
🗳️
|
||||||
|
✏️
|
||||||
|
✒️
|
||||||
|
🖋️
|
||||||
|
🖊️
|
||||||
|
🖌️
|
||||||
|
🖍️
|
||||||
|
📝
|
||||||
|
💼
|
||||||
|
📁
|
||||||
|
📂
|
||||||
|
🗂️
|
||||||
|
📅
|
||||||
|
📆
|
||||||
|
🗒️
|
||||||
|
🗓️
|
||||||
|
📇
|
||||||
|
📈
|
||||||
|
📉
|
||||||
|
📊
|
||||||
|
📋
|
||||||
|
📌
|
||||||
|
📍
|
||||||
|
📎
|
||||||
|
🖇️
|
||||||
|
📏
|
||||||
|
📐
|
||||||
|
✂️
|
||||||
|
🗃️
|
||||||
|
🗄️
|
||||||
|
🗑️
|
||||||
|
🔒
|
||||||
|
🔓
|
||||||
|
🔏
|
||||||
|
🔐
|
||||||
|
🔑
|
||||||
|
🗝️
|
||||||
|
🔨
|
||||||
|
🪓
|
||||||
|
⛏️
|
||||||
|
⚒️
|
||||||
|
🛠️
|
||||||
|
🗡️
|
||||||
|
⚔️
|
||||||
|
💣
|
||||||
|
🪃
|
||||||
|
🏹
|
||||||
|
🛡️
|
||||||
|
🪚
|
||||||
|
🔧
|
||||||
|
🪛
|
||||||
|
🔩
|
||||||
|
⚙️
|
||||||
|
🗜️
|
||||||
|
⚖️
|
||||||
|
🦯
|
||||||
|
🔗
|
||||||
|
⛓️💥
|
||||||
|
⛓️
|
||||||
|
🪝
|
||||||
|
🧰
|
||||||
|
🧲
|
||||||
|
🪜
|
||||||
|
|
||||||
|
⚗️
|
||||||
|
🧪
|
||||||
|
🧫
|
||||||
|
🧬
|
||||||
|
🔬
|
||||||
|
🔭
|
||||||
|
📡
|
||||||
|
💉
|
||||||
|
🩸
|
||||||
|
💊
|
||||||
|
🩹
|
||||||
|
🩼
|
||||||
|
🩺
|
||||||
|
🩻
|
||||||
|
🚪
|
||||||
|
🛗
|
||||||
|
🪞
|
||||||
|
🪟
|
||||||
|
🛏️
|
||||||
|
🛋️
|
||||||
|
🪑
|
||||||
|
🚽
|
||||||
|
🪠
|
||||||
|
🚿
|
||||||
|
🛁
|
||||||
|
🪤
|
||||||
|
🪒
|
||||||
|
🧴
|
||||||
|
🧷
|
||||||
|
🧹
|
||||||
|
🧺
|
||||||
|
🧻
|
||||||
|
🪣
|
||||||
|
🧼
|
||||||
|
🫧
|
||||||
|
🪥
|
||||||
|
🧽
|
||||||
|
🧯
|
||||||
|
🛒
|
||||||
|
🚬
|
||||||
|
⚰️
|
||||||
|
🪦
|
||||||
|
⚱️
|
||||||
|
🧿
|
||||||
|
🪬
|
||||||
|
🗿
|
||||||
|
🪧
|
||||||
|
🪪
|
386
app/src/main/assets/emoji/PEOPLE_AND_BODY.txt
Normal file
386
app/src/main/assets/emoji/PEOPLE_AND_BODY.txt
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
👋 👋🏻 👋🏼 👋🏽 👋🏾 👋🏿
|
||||||
|
🤚 🤚🏻 🤚🏼 🤚🏽 🤚🏾 🤚🏿
|
||||||
|
🖐️ 🖐🏻 🖐🏼 🖐🏽 🖐🏾 🖐🏿
|
||||||
|
✋ ✋🏻 ✋🏼 ✋🏽 ✋🏾 ✋🏿
|
||||||
|
🖖 🖖🏻 🖖🏼 🖖🏽 🖖🏾 🖖🏿
|
||||||
|
🫱 🫱🏻 🫱🏼 🫱🏽 🫱🏾 🫱🏿
|
||||||
|
🫲 🫲🏻 🫲🏼 🫲🏽 🫲🏾 🫲🏿
|
||||||
|
🫳 🫳🏻 🫳🏼 🫳🏽 🫳🏾 🫳🏿
|
||||||
|
🫴 🫴🏻 🫴🏼 🫴🏽 🫴🏾 🫴🏿
|
||||||
|
🫷 🫷🏻 🫷🏼 🫷🏽 🫷🏾 🫷🏿
|
||||||
|
🫸 🫸🏻 🫸🏼 🫸🏽 🫸🏾 🫸🏿
|
||||||
|
👌 👌🏻 👌🏼 👌🏽 👌🏾 👌🏿
|
||||||
|
🤌 🤌🏻 🤌🏼 🤌🏽 🤌🏾 🤌🏿
|
||||||
|
🤏 🤏🏻 🤏🏼 🤏🏽 🤏🏾 🤏🏿
|
||||||
|
✌️ ✌🏻 ✌🏼 ✌🏽 ✌🏾 ✌🏿
|
||||||
|
🤞 🤞🏻 🤞🏼 🤞🏽 🤞🏾 🤞🏿
|
||||||
|
🫰 🫰🏻 🫰🏼 🫰🏽 🫰🏾 🫰🏿
|
||||||
|
🤟 🤟🏻 🤟🏼 🤟🏽 🤟🏾 🤟🏿
|
||||||
|
🤘 🤘🏻 🤘🏼 🤘🏽 🤘🏾 🤘🏿
|
||||||
|
🤙 🤙🏻 🤙🏼 🤙🏽 🤙🏾 🤙🏿
|
||||||
|
👈 👈🏻 👈🏼 👈🏽 👈🏾 👈🏿
|
||||||
|
👉 👉🏻 👉🏼 👉🏽 👉🏾 👉🏿
|
||||||
|
👆 👆🏻 👆🏼 👆🏽 👆🏾 👆🏿
|
||||||
|
🖕 🖕🏻 🖕🏼 🖕🏽 🖕🏾 🖕🏿
|
||||||
|
👇 👇🏻 👇🏼 👇🏽 👇🏾 👇🏿
|
||||||
|
☝️ ☝🏻 ☝🏼 ☝🏽 ☝🏾 ☝🏿
|
||||||
|
🫵 🫵🏻 🫵🏼 🫵🏽 🫵🏾 🫵🏿
|
||||||
|
👍 👍🏻 👍🏼 👍🏽 👍🏾 👍🏿
|
||||||
|
👎 👎🏻 👎🏼 👎🏽 👎🏾 👎🏿
|
||||||
|
✊ ✊🏻 ✊🏼 ✊🏽 ✊🏾 ✊🏿
|
||||||
|
👊 👊🏻 👊🏼 👊🏽 👊🏾 👊🏿
|
||||||
|
🤛 🤛🏻 🤛🏼 🤛🏽 🤛🏾 🤛🏿
|
||||||
|
🤜 🤜🏻 🤜🏼 🤜🏽 🤜🏾 🤜🏿
|
||||||
|
👏 👏🏻 👏🏼 👏🏽 👏🏾 👏🏿
|
||||||
|
🙌 🙌🏻 🙌🏼 🙌🏽 🙌🏾 🙌🏿
|
||||||
|
🫶 🫶🏻 🫶🏼 🫶🏽 🫶🏾 🫶🏿
|
||||||
|
👐 👐🏻 👐🏼 👐🏽 👐🏾 👐🏿
|
||||||
|
🤲 🤲🏻 🤲🏼 🤲🏽 🤲🏾 🤲🏿
|
||||||
|
🤝 🤝🏻 🤝🏼 🤝🏽 🤝🏾 🤝🏿
|
||||||
|
🙏 🙏🏻 🙏🏼 🙏🏽 🙏🏾 🙏🏿
|
||||||
|
✍️ ✍🏻 ✍🏼 ✍🏽 ✍🏾 ✍🏿
|
||||||
|
💅 💅🏻 💅🏼 💅🏽 💅🏾 💅🏿
|
||||||
|
🤳 🤳🏻 🤳🏼 🤳🏽 🤳🏾 🤳🏿
|
||||||
|
💪 💪🏻 💪🏼 💪🏽 💪🏾 💪🏿
|
||||||
|
🦾
|
||||||
|
🦿
|
||||||
|
🦵 🦵🏻 🦵🏼 🦵🏽 🦵🏾 🦵🏿
|
||||||
|
🦶 🦶🏻 🦶🏼 🦶🏽 🦶🏾 🦶🏿
|
||||||
|
👂 👂🏻 👂🏼 👂🏽 👂🏾 👂🏿
|
||||||
|
🦻 🦻🏻 🦻🏼 🦻🏽 🦻🏾 🦻🏿
|
||||||
|
👃 👃🏻 👃🏼 👃🏽 👃🏾 👃🏿
|
||||||
|
🧠
|
||||||
|
🫀
|
||||||
|
🫁
|
||||||
|
🦷
|
||||||
|
🦴
|
||||||
|
👀
|
||||||
|
👁️
|
||||||
|
👅
|
||||||
|
👄
|
||||||
|
🫦
|
||||||
|
👶 👶🏻 👶🏼 👶🏽 👶🏾 👶🏿
|
||||||
|
🧒 🧒🏻 🧒🏼 🧒🏽 🧒🏾 🧒🏿
|
||||||
|
👦 👦🏻 👦🏼 👦🏽 👦🏾 👦🏿
|
||||||
|
👧 👧🏻 👧🏼 👧🏽 👧🏾 👧🏿
|
||||||
|
🧑 🧑🏻 🧑🏼 🧑🏽 🧑🏾 🧑🏿
|
||||||
|
👱 👱🏻 👱🏼 👱🏽 👱🏾 👱🏿
|
||||||
|
👨 👨🏻 👨🏼 👨🏽 👨🏾 👨🏿
|
||||||
|
🧔 🧔🏻 🧔🏼 🧔🏽 🧔🏾 🧔🏿
|
||||||
|
🧔♂️ 🧔🏻♂️ 🧔🏼♂️ 🧔🏽♂️ 🧔🏾♂️ 🧔🏿♂️
|
||||||
|
🧔♀️ 🧔🏻♀️ 🧔🏼♀️ 🧔🏽♀️ 🧔🏾♀️ 🧔🏿♀️
|
||||||
|
👨🦰 👨🏻🦰 👨🏼🦰 👨🏽🦰 👨🏾🦰 👨🏿🦰
|
||||||
|
👨🦱 👨🏻🦱 👨🏼🦱 👨🏽🦱 👨🏾🦱 👨🏿🦱
|
||||||
|
👨🦳 👨🏻🦳 👨🏼🦳 👨🏽🦳 👨🏾🦳 👨🏿🦳
|
||||||
|
👨🦲 👨🏻🦲 👨🏼🦲 👨🏽🦲 👨🏾🦲 👨🏿🦲
|
||||||
|
👩 👩🏻 👩🏼 👩🏽 👩🏾 👩🏿
|
||||||
|
👩🦰 👩🏻🦰 👩🏼🦰 👩🏽🦰 👩🏾🦰 👩🏿🦰
|
||||||
|
🧑🦰 🧑🏻🦰 🧑🏼🦰 🧑🏽🦰 🧑🏾🦰 🧑🏿🦰
|
||||||
|
👩🦱 👩🏻🦱 👩🏼🦱 👩🏽🦱 👩🏾🦱 👩🏿🦱
|
||||||
|
🧑🦱 🧑🏻🦱 🧑🏼🦱 🧑🏽🦱 🧑🏾🦱 🧑🏿🦱
|
||||||
|
👩🦳 👩🏻🦳 👩🏼🦳 👩🏽🦳 👩🏾🦳 👩🏿🦳
|
||||||
|
🧑🦳 🧑🏻🦳 🧑🏼🦳 🧑🏽🦳 🧑🏾🦳 🧑🏿🦳
|
||||||
|
👩🦲 👩🏻🦲 👩🏼🦲 👩🏽🦲 👩🏾🦲 👩🏿🦲
|
||||||
|
🧑🦲 🧑🏻🦲 🧑🏼🦲 🧑🏽🦲 🧑🏾🦲 🧑🏿🦲
|
||||||
|
👱♀️ 👱🏻♀️ 👱🏼♀️ 👱🏽♀️ 👱🏾♀️ 👱🏿♀️
|
||||||
|
👱♂️ 👱🏻♂️ 👱🏼♂️ 👱🏽♂️ 👱🏾♂️ 👱🏿♂️
|
||||||
|
🧓 🧓🏻 🧓🏼 🧓🏽 🧓🏾 🧓🏿
|
||||||
|
👴 👴🏻 👴🏼 👴🏽 👴🏾 👴🏿
|
||||||
|
👵 👵🏻 👵🏼 👵🏽 👵🏾 👵🏿
|
||||||
|
🙍 🙍🏻 🙍🏼 🙍🏽 🙍🏾 🙍🏿
|
||||||
|
🙍♂️ 🙍🏻♂️ 🙍🏼♂️ 🙍🏽♂️ 🙍🏾♂️ 🙍🏿♂️
|
||||||
|
🙍♀️ 🙍🏻♀️ 🙍🏼♀️ 🙍🏽♀️ 🙍🏾♀️ 🙍🏿♀️
|
||||||
|
🙎 🙎🏻 🙎🏼 🙎🏽 🙎🏾 🙎🏿
|
||||||
|
🙎♂️ 🙎🏻♂️ 🙎🏼♂️ 🙎🏽♂️ 🙎🏾♂️ 🙎🏿♂️
|
||||||
|
🙎♀️ 🙎🏻♀️ 🙎🏼♀️ 🙎🏽♀️ 🙎🏾♀️ 🙎🏿♀️
|
||||||
|
🙅 🙅🏻 🙅🏼 🙅🏽 🙅🏾 🙅🏿
|
||||||
|
🙅♂️ 🙅🏻♂️ 🙅🏼♂️ 🙅🏽♂️ 🙅🏾♂️ 🙅🏿♂️
|
||||||
|
🙅♀️ 🙅🏻♀️ 🙅🏼♀️ 🙅🏽♀️ 🙅🏾♀️ 🙅🏿♀️
|
||||||
|
🙆 🙆🏻 🙆🏼 🙆🏽 🙆🏾 🙆🏿
|
||||||
|
🙆♂️ 🙆🏻♂️ 🙆🏼♂️ 🙆🏽♂️ 🙆🏾♂️ 🙆🏿♂️
|
||||||
|
🙆♀️ 🙆🏻♀️ 🙆🏼♀️ 🙆🏽♀️ 🙆🏾♀️ 🙆🏿♀️
|
||||||
|
💁 💁🏻 💁🏼 💁🏽 💁🏾 💁🏿
|
||||||
|
💁♂️ 💁🏻♂️ 💁🏼♂️ 💁🏽♂️ 💁🏾♂️ 💁🏿♂️
|
||||||
|
💁♀️ 💁🏻♀️ 💁🏼♀️ 💁🏽♀️ 💁🏾♀️ 💁🏿♀️
|
||||||
|
🙋 🙋🏻 🙋🏼 🙋🏽 🙋🏾 🙋🏿
|
||||||
|
🙋♂️ 🙋🏻♂️ 🙋🏼♂️ 🙋🏽♂️ 🙋🏾♂️ 🙋🏿♂️
|
||||||
|
🙋♀️ 🙋🏻♀️ 🙋🏼♀️ 🙋🏽♀️ 🙋🏾♀️ 🙋🏿♀️
|
||||||
|
🧏 🧏🏻 🧏🏼 🧏🏽 🧏🏾 🧏🏿
|
||||||
|
🧏♂️ 🧏🏻♂️ 🧏🏼♂️ 🧏🏽♂️ 🧏🏾♂️ 🧏🏿♂️
|
||||||
|
🧏♀️ 🧏🏻♀️ 🧏🏼♀️ 🧏🏽♀️ 🧏🏾♀️ 🧏🏿♀️
|
||||||
|
🙇 🙇🏻 🙇🏼 🙇🏽 🙇🏾 🙇🏿
|
||||||
|
🙇♂️ 🙇🏻♂️ 🙇🏼♂️ 🙇🏽♂️ 🙇🏾♂️ 🙇🏿♂️
|
||||||
|
🙇♀️ 🙇🏻♀️ 🙇🏼♀️ 🙇🏽♀️ 🙇🏾♀️ 🙇🏿♀️
|
||||||
|
🤦 🤦🏻 🤦🏼 🤦🏽 🤦🏾 🤦🏿
|
||||||
|
🤦♂️ 🤦🏻♂️ 🤦🏼♂️ 🤦🏽♂️ 🤦🏾♂️ 🤦🏿♂️
|
||||||
|
🤦♀️ 🤦🏻♀️ 🤦🏼♀️ 🤦🏽♀️ 🤦🏾♀️ 🤦🏿♀️
|
||||||
|
🤷 🤷🏻 🤷🏼 🤷🏽 🤷🏾 🤷🏿
|
||||||
|
🤷♂️ 🤷🏻♂️ 🤷🏼♂️ 🤷🏽♂️ 🤷🏾♂️ 🤷🏿♂️
|
||||||
|
🤷♀️ 🤷🏻♀️ 🤷🏼♀️ 🤷🏽♀️ 🤷🏾♀️ 🤷🏿♀️
|
||||||
|
🧑⚕️ 🧑🏻⚕️ 🧑🏼⚕️ 🧑🏽⚕️ 🧑🏾⚕️ 🧑🏿⚕️
|
||||||
|
👨⚕️ 👨🏻⚕️ 👨🏼⚕️ 👨🏽⚕️ 👨🏾⚕️ 👨🏿⚕️
|
||||||
|
👩⚕️ 👩🏻⚕️ 👩🏼⚕️ 👩🏽⚕️ 👩🏾⚕️ 👩🏿⚕️
|
||||||
|
🧑🎓 🧑🏻🎓 🧑🏼🎓 🧑🏽🎓 🧑🏾🎓 🧑🏿🎓
|
||||||
|
👨🎓 👨🏻🎓 👨🏼🎓 👨🏽🎓 👨🏾🎓 👨🏿🎓
|
||||||
|
👩🎓 👩🏻🎓 👩🏼🎓 👩🏽🎓 👩🏾🎓 👩🏿🎓
|
||||||
|
🧑🏫 🧑🏻🏫 🧑🏼🏫 🧑🏽🏫 🧑🏾🏫 🧑🏿🏫
|
||||||
|
👨🏫 👨🏻🏫 👨🏼🏫 👨🏽🏫 👨🏾🏫 👨🏿🏫
|
||||||
|
👩🏫 👩🏻🏫 👩🏼🏫 👩🏽🏫 👩🏾🏫 👩🏿🏫
|
||||||
|
🧑⚖️ 🧑🏻⚖️ 🧑🏼⚖️ 🧑🏽⚖️ 🧑🏾⚖️ 🧑🏿⚖️
|
||||||
|
👨⚖️ 👨🏻⚖️ 👨🏼⚖️ 👨🏽⚖️ 👨🏾⚖️ 👨🏿⚖️
|
||||||
|
👩⚖️ 👩🏻⚖️ 👩🏼⚖️ 👩🏽⚖️ 👩🏾⚖️ 👩🏿⚖️
|
||||||
|
🧑🌾 🧑🏻🌾 🧑🏼🌾 🧑🏽🌾 🧑🏾🌾 🧑🏿🌾
|
||||||
|
👨🌾 👨🏻🌾 👨🏼🌾 👨🏽🌾 👨🏾🌾 👨🏿🌾
|
||||||
|
👩🌾 👩🏻🌾 👩🏼🌾 👩🏽🌾 👩🏾🌾 👩🏿🌾
|
||||||
|
🧑🍳 🧑🏻🍳 🧑🏼🍳 🧑🏽🍳 🧑🏾🍳 🧑🏿🍳
|
||||||
|
👨🍳 👨🏻🍳 👨🏼🍳 👨🏽🍳 👨🏾🍳 👨🏿🍳
|
||||||
|
👩🍳 👩🏻🍳 👩🏼🍳 👩🏽🍳 👩🏾🍳 👩🏿🍳
|
||||||
|
🧑🔧 🧑🏻🔧 🧑🏼🔧 🧑🏽🔧 🧑🏾🔧 🧑🏿🔧
|
||||||
|
👨🔧 👨🏻🔧 👨🏼🔧 👨🏽🔧 👨🏾🔧 👨🏿🔧
|
||||||
|
👩🔧 👩🏻🔧 👩🏼🔧 👩🏽🔧 👩🏾🔧 👩🏿🔧
|
||||||
|
🧑🏭 🧑🏻🏭 🧑🏼🏭 🧑🏽🏭 🧑🏾🏭 🧑🏿🏭
|
||||||
|
👨🏭 👨🏻🏭 👨🏼🏭 👨🏽🏭 👨🏾🏭 👨🏿🏭
|
||||||
|
👩🏭 👩🏻🏭 👩🏼🏭 👩🏽🏭 👩🏾🏭 👩🏿🏭
|
||||||
|
🧑💼 🧑🏻💼 🧑🏼💼 🧑🏽💼 🧑🏾💼 🧑🏿💼
|
||||||
|
👨💼 👨🏻💼 👨🏼💼 👨🏽💼 👨🏾💼 👨🏿💼
|
||||||
|
👩💼 👩🏻💼 👩🏼💼 👩🏽💼 👩🏾💼 👩🏿💼
|
||||||
|
🧑🔬 🧑🏻🔬 🧑🏼🔬 🧑🏽🔬 🧑🏾🔬 🧑🏿🔬
|
||||||
|
👨🔬 👨🏻🔬 👨🏼🔬 👨🏽🔬 👨🏾🔬 👨🏿🔬
|
||||||
|
👩🔬 👩🏻🔬 👩🏼🔬 👩🏽🔬 👩🏾🔬 👩🏿🔬
|
||||||
|
🧑💻 🧑🏻💻 🧑🏼💻 🧑🏽💻 🧑🏾💻 🧑🏿💻
|
||||||
|
👨💻 👨🏻💻 👨🏼💻 👨🏽💻 👨🏾💻 👨🏿💻
|
||||||
|
👩💻 👩🏻💻 👩🏼💻 👩🏽💻 👩🏾💻 👩🏿💻
|
||||||
|
🧑🎤 🧑🏻🎤 🧑🏼🎤 🧑🏽🎤 🧑🏾🎤 🧑🏿🎤
|
||||||
|
👨🎤 👨🏻🎤 👨🏼🎤 👨🏽🎤 👨🏾🎤 👨🏿🎤
|
||||||
|
👩🎤 👩🏻🎤 👩🏼🎤 👩🏽🎤 👩🏾🎤 👩🏿🎤
|
||||||
|
🧑🎨 🧑🏻🎨 🧑🏼🎨 🧑🏽🎨 🧑🏾🎨 🧑🏿🎨
|
||||||
|
👨🎨 👨🏻🎨 👨🏼🎨 👨🏽🎨 👨🏾🎨 👨🏿🎨
|
||||||
|
👩🎨 👩🏻🎨 👩🏼🎨 👩🏽🎨 👩🏾🎨 👩🏿🎨
|
||||||
|
🧑✈️ 🧑🏻✈️ 🧑🏼✈️ 🧑🏽✈️ 🧑🏾✈️ 🧑🏿✈️
|
||||||
|
👨✈️ 👨🏻✈️ 👨🏼✈️ 👨🏽✈️ 👨🏾✈️ 👨🏿✈️
|
||||||
|
👩✈️ 👩🏻✈️ 👩🏼✈️ 👩🏽✈️ 👩🏾✈️ 👩🏿✈️
|
||||||
|
🧑🚀 🧑🏻🚀 🧑🏼🚀 🧑🏽🚀 🧑🏾🚀 🧑🏿🚀
|
||||||
|
👨🚀 👨🏻🚀 👨🏼🚀 👨🏽🚀 👨🏾🚀 👨🏿🚀
|
||||||
|
👩🚀 👩🏻🚀 👩🏼🚀 👩🏽🚀 👩🏾🚀 👩🏿🚀
|
||||||
|
🧑🚒 🧑🏻🚒 🧑🏼🚒 🧑🏽🚒 🧑🏾🚒 🧑🏿🚒
|
||||||
|
👨🚒 👨🏻🚒 👨🏼🚒 👨🏽🚒 👨🏾🚒 👨🏿🚒
|
||||||
|
👩🚒 👩🏻🚒 👩🏼🚒 👩🏽🚒 👩🏾🚒 👩🏿🚒
|
||||||
|
👮 👮🏻 👮🏼 👮🏽 👮🏾 👮🏿
|
||||||
|
👮♂️ 👮🏻♂️ 👮🏼♂️ 👮🏽♂️ 👮🏾♂️ 👮🏿♂️
|
||||||
|
👮♀️ 👮🏻♀️ 👮🏼♀️ 👮🏽♀️ 👮🏾♀️ 👮🏿♀️
|
||||||
|
🕵️ 🕵🏻 🕵🏼 🕵🏽 🕵🏾 🕵🏿
|
||||||
|
🕵️♂️ 🕵🏻♂️ 🕵🏼♂️ 🕵🏽♂️ 🕵🏾♂️ 🕵🏿♂️
|
||||||
|
🕵️♀️ 🕵🏻♀️ 🕵🏼♀️ 🕵🏽♀️ 🕵🏾♀️ 🕵🏿♀️
|
||||||
|
💂 💂🏻 💂🏼 💂🏽 💂🏾 💂🏿
|
||||||
|
💂♂️ 💂🏻♂️ 💂🏼♂️ 💂🏽♂️ 💂🏾♂️ 💂🏿♂️
|
||||||
|
💂♀️ 💂🏻♀️ 💂🏼♀️ 💂🏽♀️ 💂🏾♀️ 💂🏿♀️
|
||||||
|
🥷 🥷🏻 🥷🏼 🥷🏽 🥷🏾 🥷🏿
|
||||||
|
👷 👷🏻 👷🏼 👷🏽 👷🏾 👷🏿
|
||||||
|
👷♂️ 👷🏻♂️ 👷🏼♂️ 👷🏽♂️ 👷🏾♂️ 👷🏿♂️
|
||||||
|
👷♀️ 👷🏻♀️ 👷🏼♀️ 👷🏽♀️ 👷🏾♀️ 👷🏿♀️
|
||||||
|
🫅 🫅🏻 🫅🏼 🫅🏽 🫅🏾 🫅🏿
|
||||||
|
🤴 🤴🏻 🤴🏼 🤴🏽 🤴🏾 🤴🏿
|
||||||
|
👸 👸🏻 👸🏼 👸🏽 👸🏾 👸🏿
|
||||||
|
👳 👳🏻 👳🏼 👳🏽 👳🏾 👳🏿
|
||||||
|
👳♂️ 👳🏻♂️ 👳🏼♂️ 👳🏽♂️ 👳🏾♂️ 👳🏿♂️
|
||||||
|
👳♀️ 👳🏻♀️ 👳🏼♀️ 👳🏽♀️ 👳🏾♀️ 👳🏿♀️
|
||||||
|
👲 👲🏻 👲🏼 👲🏽 👲🏾 👲🏿
|
||||||
|
🧕 🧕🏻 🧕🏼 🧕🏽 🧕🏾 🧕🏿
|
||||||
|
🤵 🤵🏻 🤵🏼 🤵🏽 🤵🏾 🤵🏿
|
||||||
|
🤵♂️ 🤵🏻♂️ 🤵🏼♂️ 🤵🏽♂️ 🤵🏾♂️ 🤵🏿♂️
|
||||||
|
🤵♀️ 🤵🏻♀️ 🤵🏼♀️ 🤵🏽♀️ 🤵🏾♀️ 🤵🏿♀️
|
||||||
|
👰 👰🏻 👰🏼 👰🏽 👰🏾 👰🏿
|
||||||
|
👰♂️ 👰🏻♂️ 👰🏼♂️ 👰🏽♂️ 👰🏾♂️ 👰🏿♂️
|
||||||
|
👰♀️ 👰🏻♀️ 👰🏼♀️ 👰🏽♀️ 👰🏾♀️ 👰🏿♀️
|
||||||
|
🤰 🤰🏻 🤰🏼 🤰🏽 🤰🏾 🤰🏿
|
||||||
|
🫃 🫃🏻 🫃🏼 🫃🏽 🫃🏾 🫃🏿
|
||||||
|
🫄 🫄🏻 🫄🏼 🫄🏽 🫄🏾 🫄🏿
|
||||||
|
🤱 🤱🏻 🤱🏼 🤱🏽 🤱🏾 🤱🏿
|
||||||
|
👩🍼 👩🏻🍼 👩🏼🍼 👩🏽🍼 👩🏾🍼 👩🏿🍼
|
||||||
|
👨🍼 👨🏻🍼 👨🏼🍼 👨🏽🍼 👨🏾🍼 👨🏿🍼
|
||||||
|
🧑🍼 🧑🏻🍼 🧑🏼🍼 🧑🏽🍼 🧑🏾🍼 🧑🏿🍼
|
||||||
|
👼 👼🏻 👼🏼 👼🏽 👼🏾 👼🏿
|
||||||
|
🎅 🎅🏻 🎅🏼 🎅🏽 🎅🏾 🎅🏿
|
||||||
|
🤶 🤶🏻 🤶🏼 🤶🏽 🤶🏾 🤶🏿
|
||||||
|
🧑🎄 🧑🏻🎄 🧑🏼🎄 🧑🏽🎄 🧑🏾🎄 🧑🏿🎄
|
||||||
|
🦸 🦸🏻 🦸🏼 🦸🏽 🦸🏾 🦸🏿
|
||||||
|
🦸♂️ 🦸🏻♂️ 🦸🏼♂️ 🦸🏽♂️ 🦸🏾♂️ 🦸🏿♂️
|
||||||
|
🦸♀️ 🦸🏻♀️ 🦸🏼♀️ 🦸🏽♀️ 🦸🏾♀️ 🦸🏿♀️
|
||||||
|
🦹 🦹🏻 🦹🏼 🦹🏽 🦹🏾 🦹🏿
|
||||||
|
🦹♂️ 🦹🏻♂️ 🦹🏼♂️ 🦹🏽♂️ 🦹🏾♂️ 🦹🏿♂️
|
||||||
|
🦹♀️ 🦹🏻♀️ 🦹🏼♀️ 🦹🏽♀️ 🦹🏾♀️ 🦹🏿♀️
|
||||||
|
🧙 🧙🏻 🧙🏼 🧙🏽 🧙🏾 🧙🏿
|
||||||
|
🧙♂️ 🧙🏻♂️ 🧙🏼♂️ 🧙🏽♂️ 🧙🏾♂️ 🧙🏿♂️
|
||||||
|
🧙♀️ 🧙🏻♀️ 🧙🏼♀️ 🧙🏽♀️ 🧙🏾♀️ 🧙🏿♀️
|
||||||
|
🧚 🧚🏻 🧚🏼 🧚🏽 🧚🏾 🧚🏿
|
||||||
|
🧚♂️ 🧚🏻♂️ 🧚🏼♂️ 🧚🏽♂️ 🧚🏾♂️ 🧚🏿♂️
|
||||||
|
🧚♀️ 🧚🏻♀️ 🧚🏼♀️ 🧚🏽♀️ 🧚🏾♀️ 🧚🏿♀️
|
||||||
|
🧛 🧛🏻 🧛🏼 🧛🏽 🧛🏾 🧛🏿
|
||||||
|
🧛♂️ 🧛🏻♂️ 🧛🏼♂️ 🧛🏽♂️ 🧛🏾♂️ 🧛🏿♂️
|
||||||
|
🧛♀️ 🧛🏻♀️ 🧛🏼♀️ 🧛🏽♀️ 🧛🏾♀️ 🧛🏿♀️
|
||||||
|
🧜 🧜🏻 🧜🏼 🧜🏽 🧜🏾 🧜🏿
|
||||||
|
🧜♂️ 🧜🏻♂️ 🧜🏼♂️ 🧜🏽♂️ 🧜🏾♂️ 🧜🏿♂️
|
||||||
|
🧜♀️ 🧜🏻♀️ 🧜🏼♀️ 🧜🏽♀️ 🧜🏾♀️ 🧜🏿♀️
|
||||||
|
🧝 🧝🏻 🧝🏼 🧝🏽 🧝🏾 🧝🏿
|
||||||
|
🧝♂️ 🧝🏻♂️ 🧝🏼♂️ 🧝🏽♂️ 🧝🏾♂️ 🧝🏿♂️
|
||||||
|
🧝♀️ 🧝🏻♀️ 🧝🏼♀️ 🧝🏽♀️ 🧝🏾♀️ 🧝🏿♀️
|
||||||
|
🧞
|
||||||
|
🧞♂️
|
||||||
|
🧞♀️
|
||||||
|
🧟
|
||||||
|
🧟♂️
|
||||||
|
🧟♀️
|
||||||
|
🧌
|
||||||
|
💆 💆🏻 💆🏼 💆🏽 💆🏾 💆🏿
|
||||||
|
💆♂️ 💆🏻♂️ 💆🏼♂️ 💆🏽♂️ 💆🏾♂️ 💆🏿♂️
|
||||||
|
💆♀️ 💆🏻♀️ 💆🏼♀️ 💆🏽♀️ 💆🏾♀️ 💆🏿♀️
|
||||||
|
💇 💇🏻 💇🏼 💇🏽 💇🏾 💇🏿
|
||||||
|
💇♂️ 💇🏻♂️ 💇🏼♂️ 💇🏽♂️ 💇🏾♂️ 💇🏿♂️
|
||||||
|
💇♀️ 💇🏻♀️ 💇🏼♀️ 💇🏽♀️ 💇🏾♀️ 💇🏿♀️
|
||||||
|
🚶 🚶🏻 🚶🏼 🚶🏽 🚶🏾 🚶🏿
|
||||||
|
🚶♂️ 🚶🏻♂️ 🚶🏼♂️ 🚶🏽♂️ 🚶🏾♂️ 🚶🏿♂️
|
||||||
|
🚶♀️ 🚶🏻♀️ 🚶🏼♀️ 🚶🏽♀️ 🚶🏾♀️ 🚶🏿♀️
|
||||||
|
🚶➡️ 🚶🏻➡️ 🚶🏼➡️ 🚶🏽➡️ 🚶🏾➡️ 🚶🏿➡️
|
||||||
|
🚶♀️➡️ 🚶🏻♀️➡️ 🚶🏼♀️➡️ 🚶🏽♀️➡️ 🚶🏾♀️➡️ 🚶🏿♀️➡️
|
||||||
|
🚶♂️➡️ 🚶🏻♂️➡️ 🚶🏼♂️➡️ 🚶🏽♂️➡️ 🚶🏾♂️➡️ 🚶🏿♂️➡️
|
||||||
|
🧍 🧍🏻 🧍🏼 🧍🏽 🧍🏾 🧍🏿
|
||||||
|
🧍♂️ 🧍🏻♂️ 🧍🏼♂️ 🧍🏽♂️ 🧍🏾♂️ 🧍🏿♂️
|
||||||
|
🧍♀️ 🧍🏻♀️ 🧍🏼♀️ 🧍🏽♀️ 🧍🏾♀️ 🧍🏿♀️
|
||||||
|
🧎 🧎🏻 🧎🏼 🧎🏽 🧎🏾 🧎🏿
|
||||||
|
🧎♂️ 🧎🏻♂️ 🧎🏼♂️ 🧎🏽♂️ 🧎🏾♂️ 🧎🏿♂️
|
||||||
|
🧎♀️ 🧎🏻♀️ 🧎🏼♀️ 🧎🏽♀️ 🧎🏾♀️ 🧎🏿♀️
|
||||||
|
🧎➡️ 🧎🏻➡️ 🧎🏼➡️ 🧎🏽➡️ 🧎🏾➡️ 🧎🏿➡️
|
||||||
|
🧎♀️➡️ 🧎🏻♀️➡️ 🧎🏼♀️➡️ 🧎🏽♀️➡️ 🧎🏾♀️➡️ 🧎🏿♀️➡️
|
||||||
|
🧎♂️➡️ 🧎🏻♂️➡️ 🧎🏼♂️➡️ 🧎🏽♂️➡️ 🧎🏾♂️➡️ 🧎🏿♂️➡️
|
||||||
|
🧑🦯 🧑🏻🦯 🧑🏼🦯 🧑🏽🦯 🧑🏾🦯 🧑🏿🦯
|
||||||
|
🧑🦯➡️ 🧑🏻🦯➡️ 🧑🏼🦯➡️ 🧑🏽🦯➡️ 🧑🏾🦯➡️ 🧑🏿🦯➡️
|
||||||
|
👨🦯 👨🏻🦯 👨🏼🦯 👨🏽🦯 👨🏾🦯 👨🏿🦯
|
||||||
|
👨🦯➡️ 👨🏻🦯➡️ 👨🏼🦯➡️ 👨🏽🦯➡️ 👨🏾🦯➡️ 👨🏿🦯➡️
|
||||||
|
👩🦯 👩🏻🦯 👩🏼🦯 👩🏽🦯 👩🏾🦯 👩🏿🦯
|
||||||
|
👩🦯➡️ 👩🏻🦯➡️ 👩🏼🦯➡️ 👩🏽🦯➡️ 👩🏾🦯➡️ 👩🏿🦯➡️
|
||||||
|
🧑🦼 🧑🏻🦼 🧑🏼🦼 🧑🏽🦼 🧑🏾🦼 🧑🏿🦼
|
||||||
|
🧑🦼➡️ 🧑🏻🦼➡️ 🧑🏼🦼➡️ 🧑🏽🦼➡️ 🧑🏾🦼➡️ 🧑🏿🦼➡️
|
||||||
|
👨🦼 👨🏻🦼 👨🏼🦼 👨🏽🦼 👨🏾🦼 👨🏿🦼
|
||||||
|
👨🦼➡️ 👨🏻🦼➡️ 👨🏼🦼➡️ 👨🏽🦼➡️ 👨🏾🦼➡️ 👨🏿🦼➡️
|
||||||
|
👩🦼 👩🏻🦼 👩🏼🦼 👩🏽🦼 👩🏾🦼 👩🏿🦼
|
||||||
|
👩🦼➡️ 👩🏻🦼➡️ 👩🏼🦼➡️ 👩🏽🦼➡️ 👩🏾🦼➡️ 👩🏿🦼➡️
|
||||||
|
🧑🦽 🧑🏻🦽 🧑🏼🦽 🧑🏽🦽 🧑🏾🦽 🧑🏿🦽
|
||||||
|
🧑🦽➡️ 🧑🏻🦽➡️ 🧑🏼🦽➡️ 🧑🏽🦽➡️ 🧑🏾🦽➡️ 🧑🏿🦽➡️
|
||||||
|
👨🦽 👨🏻🦽 👨🏼🦽 👨🏽🦽 👨🏾🦽 👨🏿🦽
|
||||||
|
👨🦽➡️ 👨🏻🦽➡️ 👨🏼🦽➡️ 👨🏽🦽➡️ 👨🏾🦽➡️ 👨🏿🦽➡️
|
||||||
|
👩🦽 👩🏻🦽 👩🏼🦽 👩🏽🦽 👩🏾🦽 👩🏿🦽
|
||||||
|
👩🦽➡️ 👩🏻🦽➡️ 👩🏼🦽➡️ 👩🏽🦽➡️ 👩🏾🦽➡️ 👩🏿🦽➡️
|
||||||
|
🏃 🏃🏻 🏃🏼 🏃🏽 🏃🏾 🏃🏿
|
||||||
|
🏃♂️ 🏃🏻♂️ 🏃🏼♂️ 🏃🏽♂️ 🏃🏾♂️ 🏃🏿♂️
|
||||||
|
🏃♀️ 🏃🏻♀️ 🏃🏼♀️ 🏃🏽♀️ 🏃🏾♀️ 🏃🏿♀️
|
||||||
|
🏃➡️ 🏃🏻➡️ 🏃🏼➡️ 🏃🏽➡️ 🏃🏾➡️ 🏃🏿➡️
|
||||||
|
🏃♀️➡️ 🏃🏻♀️➡️ 🏃🏼♀️➡️ 🏃🏽♀️➡️ 🏃🏾♀️➡️ 🏃🏿♀️➡️
|
||||||
|
🏃♂️➡️ 🏃🏻♂️➡️ 🏃🏼♂️➡️ 🏃🏽♂️➡️ 🏃🏾♂️➡️ 🏃🏿♂️➡️
|
||||||
|
💃 💃🏻 💃🏼 💃🏽 💃🏾 💃🏿
|
||||||
|
🕺 🕺🏻 🕺🏼 🕺🏽 🕺🏾 🕺🏿
|
||||||
|
🕴️ 🕴🏻 🕴🏼 🕴🏽 🕴🏾 🕴🏿
|
||||||
|
👯
|
||||||
|
👯♂️
|
||||||
|
👯♀️
|
||||||
|
🧖 🧖🏻 🧖🏼 🧖🏽 🧖🏾 🧖🏿
|
||||||
|
🧖♂️ 🧖🏻♂️ 🧖🏼♂️ 🧖🏽♂️ 🧖🏾♂️ 🧖🏿♂️
|
||||||
|
🧖♀️ 🧖🏻♀️ 🧖🏼♀️ 🧖🏽♀️ 🧖🏾♀️ 🧖🏿♀️
|
||||||
|
🧗 🧗🏻 🧗🏼 🧗🏽 🧗🏾 🧗🏿
|
||||||
|
🧗♂️ 🧗🏻♂️ 🧗🏼♂️ 🧗🏽♂️ 🧗🏾♂️ 🧗🏿♂️
|
||||||
|
🧗♀️ 🧗🏻♀️ 🧗🏼♀️ 🧗🏽♀️ 🧗🏾♀️ 🧗🏿♀️
|
||||||
|
🤺
|
||||||
|
🏇 🏇🏻 🏇🏼 🏇🏽 🏇🏾 🏇🏿
|
||||||
|
⛷️
|
||||||
|
🏂 🏂🏻 🏂🏼 🏂🏽 🏂🏾 🏂🏿
|
||||||
|
🏌️ 🏌🏻 🏌🏼 🏌🏽 🏌🏾 🏌🏿
|
||||||
|
🏌️♂️ 🏌🏻♂️ 🏌🏼♂️ 🏌🏽♂️ 🏌🏾♂️ 🏌🏿♂️
|
||||||
|
🏌️♀️ 🏌🏻♀️ 🏌🏼♀️ 🏌🏽♀️ 🏌🏾♀️ 🏌🏿♀️
|
||||||
|
🏄 🏄🏻 🏄🏼 🏄🏽 🏄🏾 🏄🏿
|
||||||
|
🏄♂️ 🏄🏻♂️ 🏄🏼♂️ 🏄🏽♂️ 🏄🏾♂️ 🏄🏿♂️
|
||||||
|
🏄♀️ 🏄🏻♀️ 🏄🏼♀️ 🏄🏽♀️ 🏄🏾♀️ 🏄🏿♀️
|
||||||
|
🚣 🚣🏻 🚣🏼 🚣🏽 🚣🏾 🚣🏿
|
||||||
|
🚣♂️ 🚣🏻♂️ 🚣🏼♂️ 🚣🏽♂️ 🚣🏾♂️ 🚣🏿♂️
|
||||||
|
🚣♀️ 🚣🏻♀️ 🚣🏼♀️ 🚣🏽♀️ 🚣🏾♀️ 🚣🏿♀️
|
||||||
|
🏊 🏊🏻 🏊🏼 🏊🏽 🏊🏾 🏊🏿
|
||||||
|
🏊♂️ 🏊🏻♂️ 🏊🏼♂️ 🏊🏽♂️ 🏊🏾♂️ 🏊🏿♂️
|
||||||
|
🏊♀️ 🏊🏻♀️ 🏊🏼♀️ 🏊🏽♀️ 🏊🏾♀️ 🏊🏿♀️
|
||||||
|
⛹️ ⛹🏻 ⛹🏼 ⛹🏽 ⛹🏾 ⛹🏿
|
||||||
|
⛹️♂️ ⛹🏻♂️ ⛹🏼♂️ ⛹🏽♂️ ⛹🏾♂️ ⛹🏿♂️
|
||||||
|
⛹️♀️ ⛹🏻♀️ ⛹🏼♀️ ⛹🏽♀️ ⛹🏾♀️ ⛹🏿♀️
|
||||||
|
🏋️ 🏋🏻 🏋🏼 🏋🏽 🏋🏾 🏋🏿
|
||||||
|
🏋️♂️ 🏋🏻♂️ 🏋🏼♂️ 🏋🏽♂️ 🏋🏾♂️ 🏋🏿♂️
|
||||||
|
🏋️♀️ 🏋🏻♀️ 🏋🏼♀️ 🏋🏽♀️ 🏋🏾♀️ 🏋🏿♀️
|
||||||
|
🚴 🚴🏻 🚴🏼 🚴🏽 🚴🏾 🚴🏿
|
||||||
|
🚴♂️ 🚴🏻♂️ 🚴🏼♂️ 🚴🏽♂️ 🚴🏾♂️ 🚴🏿♂️
|
||||||
|
🚴♀️ 🚴🏻♀️ 🚴🏼♀️ 🚴🏽♀️ 🚴🏾♀️ 🚴🏿♀️
|
||||||
|
🚵 🚵🏻 🚵🏼 🚵🏽 🚵🏾 🚵🏿
|
||||||
|
🚵♂️ 🚵🏻♂️ 🚵🏼♂️ 🚵🏽♂️ 🚵🏾♂️ 🚵🏿♂️
|
||||||
|
🚵♀️ 🚵🏻♀️ 🚵🏼♀️ 🚵🏽♀️ 🚵🏾♀️ 🚵🏿♀️
|
||||||
|
🤸 🤸🏻 🤸🏼 🤸🏽 🤸🏾 🤸🏿
|
||||||
|
🤸♂️ 🤸🏻♂️ 🤸🏼♂️ 🤸🏽♂️ 🤸🏾♂️ 🤸🏿♂️
|
||||||
|
🤸♀️ 🤸🏻♀️ 🤸🏼♀️ 🤸🏽♀️ 🤸🏾♀️ 🤸🏿♀️
|
||||||
|
🤼
|
||||||
|
🤼♂️
|
||||||
|
🤼♀️
|
||||||
|
🤽 🤽🏻 🤽🏼 🤽🏽 🤽🏾 🤽🏿
|
||||||
|
🤽♂️ 🤽🏻♂️ 🤽🏼♂️ 🤽🏽♂️ 🤽🏾♂️ 🤽🏿♂️
|
||||||
|
🤽♀️ 🤽🏻♀️ 🤽🏼♀️ 🤽🏽♀️ 🤽🏾♀️ 🤽🏿♀️
|
||||||
|
🤾 🤾🏻 🤾🏼 🤾🏽 🤾🏾 🤾🏿
|
||||||
|
🤾♂️ 🤾🏻♂️ 🤾🏼♂️ 🤾🏽♂️ 🤾🏾♂️ 🤾🏿♂️
|
||||||
|
🤾♀️ 🤾🏻♀️ 🤾🏼♀️ 🤾🏽♀️ 🤾🏾♀️ 🤾🏿♀️
|
||||||
|
🤹 🤹🏻 🤹🏼 🤹🏽 🤹🏾 🤹🏿
|
||||||
|
🤹♂️ 🤹🏻♂️ 🤹🏼♂️ 🤹🏽♂️ 🤹🏾♂️ 🤹🏿♂️
|
||||||
|
🤹♀️ 🤹🏻♀️ 🤹🏼♀️ 🤹🏽♀️ 🤹🏾♀️ 🤹🏿♀️
|
||||||
|
🧘 🧘🏻 🧘🏼 🧘🏽 🧘🏾 🧘🏿
|
||||||
|
🧘♂️ 🧘🏻♂️ 🧘🏼♂️ 🧘🏽♂️ 🧘🏾♂️ 🧘🏿♂️
|
||||||
|
🧘♀️ 🧘🏻♀️ 🧘🏼♀️ 🧘🏽♀️ 🧘🏾♀️ 🧘🏿♀️
|
||||||
|
🛀 🛀🏻 🛀🏼 🛀🏽 🛀🏾 🛀🏿
|
||||||
|
🛌 🛌🏻 🛌🏼 🛌🏽 🛌🏾 🛌🏿
|
||||||
|
🧑🤝🧑 🧑🏻🤝🧑🏻 🧑🏻🤝🧑🏼 🧑🏻🤝🧑🏽 🧑🏻🤝🧑🏾 🧑🏻🤝🧑🏿 🧑🏼🤝🧑🏻 🧑🏼🤝🧑🏼 🧑🏼🤝🧑🏽 🧑🏼🤝🧑🏾 🧑🏼🤝🧑🏿 🧑🏽🤝🧑🏻 🧑🏽🤝🧑🏼 🧑🏽🤝🧑🏽 🧑🏽🤝🧑🏾 🧑🏽🤝🧑🏿 🧑🏾🤝🧑🏻 🧑🏾🤝🧑🏼 🧑🏾🤝🧑🏽 🧑🏾🤝🧑🏾 🧑🏾🤝🧑🏿 🧑🏿🤝🧑🏻 🧑🏿🤝🧑🏼 🧑🏿🤝🧑🏽 🧑🏿🤝🧑🏾 🧑🏿🤝🧑🏿
|
||||||
|
👭 👭🏻 👭🏼 👭🏽 👭🏾 👭🏿
|
||||||
|
👫 👫🏻 👫🏼 👫🏽 👫🏾 👫🏿
|
||||||
|
👬 👬🏻 👬🏼 👬🏽 👬🏾 👬🏿
|
||||||
|
💏 💏🏻 💏🏼 💏🏽 💏🏾 💏🏿
|
||||||
|
👩❤️💋👨 👩🏻❤️💋👨🏻 👩🏻❤️💋👨🏼 👩🏻❤️💋👨🏽 👩🏻❤️💋👨🏾 👩🏻❤️💋👨🏿 👩🏼❤️💋👨🏻 👩🏼❤️💋👨🏼 👩🏼❤️💋👨🏽 👩🏼❤️💋👨🏾 👩🏼❤️💋👨🏿 👩🏽❤️💋👨🏻 👩🏽❤️💋👨🏼 👩🏽❤️💋👨🏽 👩🏽❤️💋👨🏾 👩🏽❤️💋👨🏿 👩🏾❤️💋👨🏻 👩🏾❤️💋👨🏼 👩🏾❤️💋👨🏽 👩🏾❤️💋👨🏾 👩🏾❤️💋👨🏿 👩🏿❤️💋👨🏻 👩🏿❤️💋👨🏼 👩🏿❤️💋👨🏽 👩🏿❤️💋👨🏾 👩🏿❤️💋👨🏿
|
||||||
|
👨❤️💋👨 👨🏻❤️💋👨🏻 👨🏻❤️💋👨🏼 👨🏻❤️💋👨🏽 👨🏻❤️💋👨🏾 👨🏻❤️💋👨🏿 👨🏼❤️💋👨🏻 👨🏼❤️💋👨🏼 👨🏼❤️💋👨🏽 👨🏼❤️💋👨🏾 👨🏼❤️💋👨🏿 👨🏽❤️💋👨🏻 👨🏽❤️💋👨🏼 👨🏽❤️💋👨🏽 👨🏽❤️💋👨🏾 👨🏽❤️💋👨🏿 👨🏾❤️💋👨🏻 👨🏾❤️💋👨🏼 👨🏾❤️💋👨🏽 👨🏾❤️💋👨🏾 👨🏾❤️💋👨🏿 👨🏿❤️💋👨🏻 👨🏿❤️💋👨🏼 👨🏿❤️💋👨🏽 👨🏿❤️💋👨🏾 👨🏿❤️💋👨🏿
|
||||||
|
👩❤️💋👩 👩🏻❤️💋👩🏻 👩🏻❤️💋👩🏼 👩🏻❤️💋👩🏽 👩🏻❤️💋👩🏾 👩🏻❤️💋👩🏿 👩🏼❤️💋👩🏻 👩🏼❤️💋👩🏼 👩🏼❤️💋👩🏽 👩🏼❤️💋👩🏾 👩🏼❤️💋👩🏿 👩🏽❤️💋👩🏻 👩🏽❤️💋👩🏼 👩🏽❤️💋👩🏽 👩🏽❤️💋👩🏾 👩🏽❤️💋👩🏿 👩🏾❤️💋👩🏻 👩🏾❤️💋👩🏼 👩🏾❤️💋👩🏽 👩🏾❤️💋👩🏾 👩🏾❤️💋👩🏿 👩🏿❤️💋👩🏻 👩🏿❤️💋👩🏼 👩🏿❤️💋👩🏽 👩🏿❤️💋👩🏾 👩🏿❤️💋👩🏿
|
||||||
|
💑 💑🏻 💑🏼 💑🏽 💑🏾 💑🏿
|
||||||
|
👩❤️👨 👩🏻❤️👨🏻 👩🏻❤️👨🏼 👩🏻❤️👨🏽 👩🏻❤️👨🏾 👩🏻❤️👨🏿 👩🏼❤️👨🏻 👩🏼❤️👨🏼 👩🏼❤️👨🏽 👩🏼❤️👨🏾 👩🏼❤️👨🏿 👩🏽❤️👨🏻 👩🏽❤️👨🏼 👩🏽❤️👨🏽 👩🏽❤️👨🏾 👩🏽❤️👨🏿 👩🏾❤️👨🏻 👩🏾❤️👨🏼 👩🏾❤️👨🏽 👩🏾❤️👨🏾 👩🏾❤️👨🏿 👩🏿❤️👨🏻 👩🏿❤️👨🏼 👩🏿❤️👨🏽 👩🏿❤️👨🏾 👩🏿❤️👨🏿
|
||||||
|
👨❤️👨 👨🏻❤️👨🏻 👨🏻❤️👨🏼 👨🏻❤️👨🏽 👨🏻❤️👨🏾 👨🏻❤️👨🏿 👨🏼❤️👨🏻 👨🏼❤️👨🏼 👨🏼❤️👨🏽 👨🏼❤️👨🏾 👨🏼❤️👨🏿 👨🏽❤️👨🏻 👨🏽❤️👨🏼 👨🏽❤️👨🏽 👨🏽❤️👨🏾 👨🏽❤️👨🏿 👨🏾❤️👨🏻 👨🏾❤️👨🏼 👨🏾❤️👨🏽 👨🏾❤️👨🏾 👨🏾❤️👨🏿 👨🏿❤️👨🏻 👨🏿❤️👨🏼 👨🏿❤️👨🏽 👨🏿❤️👨🏾 👨🏿❤️👨🏿
|
||||||
|
👩❤️👩 👩🏻❤️👩🏻 👩🏻❤️👩🏼 👩🏻❤️👩🏽 👩🏻❤️👩🏾 👩🏻❤️👩🏿 👩🏼❤️👩🏻 👩🏼❤️👩🏼 👩🏼❤️👩🏽 👩🏼❤️👩🏾 👩🏼❤️👩🏿 👩🏽❤️👩🏻 👩🏽❤️👩🏼 👩🏽❤️👩🏽 👩🏽❤️👩🏾 👩🏽❤️👩🏿 👩🏾❤️👩🏻 👩🏾❤️👩🏼 👩🏾❤️👩🏽 👩🏾❤️👩🏾 👩🏾❤️👩🏿 👩🏿❤️👩🏻 👩🏿❤️👩🏼 👩🏿❤️👩🏽 👩🏿❤️👩🏾 👩🏿❤️👩🏿
|
||||||
|
👨👩👦
|
||||||
|
👨👩👧
|
||||||
|
👨👩👧👦
|
||||||
|
👨👩👦👦
|
||||||
|
👨👩👧👧
|
||||||
|
👨👨👦
|
||||||
|
👨👨👧
|
||||||
|
👨👨👧👦
|
||||||
|
👨👨👦👦
|
||||||
|
👨👨👧👧
|
||||||
|
👩👩👦
|
||||||
|
👩👩👧
|
||||||
|
👩👩👧👦
|
||||||
|
👩👩👦👦
|
||||||
|
👩👩👧👧
|
||||||
|
👨👦
|
||||||
|
👨👦👦
|
||||||
|
👨👧
|
||||||
|
👨👧👦
|
||||||
|
👨👧👧
|
||||||
|
👩👦
|
||||||
|
👩👦👦
|
||||||
|
👩👧
|
||||||
|
👩👧👦
|
||||||
|
👩👧👧
|
||||||
|
🗣️
|
||||||
|
👤
|
||||||
|
👥
|
||||||
|
🫂
|
||||||
|
👪
|
||||||
|
🧑🧑🧒
|
||||||
|
🧑🧑🧒🧒
|
||||||
|
🧑🧒
|
||||||
|
🧑🧒🧒
|
||||||
|
👣
|
||||||
|
|
169
app/src/main/assets/emoji/SMILEYS_AND_EMOTION.txt
Normal file
169
app/src/main/assets/emoji/SMILEYS_AND_EMOTION.txt
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
😀
|
||||||
|
😃
|
||||||
|
😄
|
||||||
|
😁
|
||||||
|
😆
|
||||||
|
😅
|
||||||
|
🤣
|
||||||
|
😂
|
||||||
|
🙂
|
||||||
|
🙃
|
||||||
|
🫠
|
||||||
|
😉
|
||||||
|
😊
|
||||||
|
😇
|
||||||
|
🥰
|
||||||
|
😍
|
||||||
|
🤩
|
||||||
|
😘
|
||||||
|
😗
|
||||||
|
☺️
|
||||||
|
😚
|
||||||
|
😙
|
||||||
|
🥲
|
||||||
|
😋
|
||||||
|
😛
|
||||||
|
😜
|
||||||
|
🤪
|
||||||
|
😝
|
||||||
|
🤑
|
||||||
|
🤗
|
||||||
|
🤭
|
||||||
|
🫢
|
||||||
|
🫣
|
||||||
|
🤫
|
||||||
|
🤔
|
||||||
|
🫡
|
||||||
|
🤐
|
||||||
|
🤨
|
||||||
|
😐
|
||||||
|
😑
|
||||||
|
😶
|
||||||
|
🫥
|
||||||
|
😶🌫️
|
||||||
|
😏
|
||||||
|
😒
|
||||||
|
🙄
|
||||||
|
😬
|
||||||
|
😮💨
|
||||||
|
🤥
|
||||||
|
🫨
|
||||||
|
🙂↔️
|
||||||
|
🙂↕️
|
||||||
|
😌
|
||||||
|
😔
|
||||||
|
😪
|
||||||
|
🤤
|
||||||
|
😴
|
||||||
|
|
||||||
|
😷
|
||||||
|
🤒
|
||||||
|
🤕
|
||||||
|
🤢
|
||||||
|
🤮
|
||||||
|
🤧
|
||||||
|
🥵
|
||||||
|
🥶
|
||||||
|
🥴
|
||||||
|
😵
|
||||||
|
😵💫
|
||||||
|
🤯
|
||||||
|
🤠
|
||||||
|
🥳
|
||||||
|
🥸
|
||||||
|
😎
|
||||||
|
🤓
|
||||||
|
🧐
|
||||||
|
😕
|
||||||
|
🫤
|
||||||
|
😟
|
||||||
|
🙁
|
||||||
|
☹️
|
||||||
|
😮
|
||||||
|
😯
|
||||||
|
😲
|
||||||
|
😳
|
||||||
|
🥺
|
||||||
|
🥹
|
||||||
|
😦
|
||||||
|
😧
|
||||||
|
😨
|
||||||
|
😰
|
||||||
|
😥
|
||||||
|
😢
|
||||||
|
😭
|
||||||
|
😱
|
||||||
|
😖
|
||||||
|
😣
|
||||||
|
😞
|
||||||
|
😓
|
||||||
|
😩
|
||||||
|
😫
|
||||||
|
🥱
|
||||||
|
😤
|
||||||
|
😡
|
||||||
|
😠
|
||||||
|
🤬
|
||||||
|
😈
|
||||||
|
👿
|
||||||
|
💀
|
||||||
|
☠️
|
||||||
|
💩
|
||||||
|
🤡
|
||||||
|
👹
|
||||||
|
👺
|
||||||
|
👻
|
||||||
|
👽
|
||||||
|
👾
|
||||||
|
🤖
|
||||||
|
😺
|
||||||
|
😸
|
||||||
|
😹
|
||||||
|
😻
|
||||||
|
😼
|
||||||
|
😽
|
||||||
|
🙀
|
||||||
|
😿
|
||||||
|
😾
|
||||||
|
🙈
|
||||||
|
🙉
|
||||||
|
🙊
|
||||||
|
💌
|
||||||
|
💘
|
||||||
|
💝
|
||||||
|
💖
|
||||||
|
💗
|
||||||
|
💓
|
||||||
|
💞
|
||||||
|
💕
|
||||||
|
💟
|
||||||
|
❣️
|
||||||
|
💔
|
||||||
|
❤️🔥
|
||||||
|
❤️🩹
|
||||||
|
❤️
|
||||||
|
🩷
|
||||||
|
🧡
|
||||||
|
💛
|
||||||
|
💚
|
||||||
|
💙
|
||||||
|
🩵
|
||||||
|
💜
|
||||||
|
🤎
|
||||||
|
🖤
|
||||||
|
🩶
|
||||||
|
🤍
|
||||||
|
💋
|
||||||
|
💯
|
||||||
|
💢
|
||||||
|
💥
|
||||||
|
💫
|
||||||
|
💦
|
||||||
|
💨
|
||||||
|
🕳️
|
||||||
|
💬
|
||||||
|
👁️🗨️
|
||||||
|
🗨️
|
||||||
|
🗯️
|
||||||
|
💭
|
||||||
|
💤
|
224
app/src/main/assets/emoji/SYMBOLS.txt
Normal file
224
app/src/main/assets/emoji/SYMBOLS.txt
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
🏧
|
||||||
|
🚮
|
||||||
|
🚰
|
||||||
|
♿
|
||||||
|
🚹
|
||||||
|
🚺
|
||||||
|
🚻
|
||||||
|
🚼
|
||||||
|
🚾
|
||||||
|
🛂
|
||||||
|
🛃
|
||||||
|
🛄
|
||||||
|
🛅
|
||||||
|
⚠️
|
||||||
|
🚸
|
||||||
|
⛔
|
||||||
|
🚫
|
||||||
|
🚳
|
||||||
|
🚭
|
||||||
|
🚯
|
||||||
|
🚱
|
||||||
|
🚷
|
||||||
|
📵
|
||||||
|
🔞
|
||||||
|
☢️
|
||||||
|
☣️
|
||||||
|
⬆️
|
||||||
|
↗️
|
||||||
|
➡️
|
||||||
|
↘️
|
||||||
|
⬇️
|
||||||
|
↙️
|
||||||
|
⬅️
|
||||||
|
↖️
|
||||||
|
↕️
|
||||||
|
↔️
|
||||||
|
↩️
|
||||||
|
↪️
|
||||||
|
⤴️
|
||||||
|
⤵️
|
||||||
|
🔃
|
||||||
|
🔄
|
||||||
|
🔙
|
||||||
|
🔚
|
||||||
|
🔛
|
||||||
|
🔜
|
||||||
|
🔝
|
||||||
|
🛐
|
||||||
|
⚛️
|
||||||
|
🕉️
|
||||||
|
✡️
|
||||||
|
☸️
|
||||||
|
☯️
|
||||||
|
✝️
|
||||||
|
☦️
|
||||||
|
☪️
|
||||||
|
☮️
|
||||||
|
🕎
|
||||||
|
🔯
|
||||||
|
🪯
|
||||||
|
♈
|
||||||
|
♉
|
||||||
|
♊
|
||||||
|
♋
|
||||||
|
♌
|
||||||
|
♍
|
||||||
|
♎
|
||||||
|
♏
|
||||||
|
♐
|
||||||
|
♑
|
||||||
|
♒
|
||||||
|
♓
|
||||||
|
⛎
|
||||||
|
🔀
|
||||||
|
🔁
|
||||||
|
🔂
|
||||||
|
▶️
|
||||||
|
⏩
|
||||||
|
⏭️
|
||||||
|
⏯️
|
||||||
|
◀️
|
||||||
|
⏪
|
||||||
|
⏮️
|
||||||
|
🔼
|
||||||
|
⏫
|
||||||
|
🔽
|
||||||
|
⏬
|
||||||
|
⏸️
|
||||||
|
⏹️
|
||||||
|
⏺️
|
||||||
|
⏏️
|
||||||
|
🎦
|
||||||
|
🔅
|
||||||
|
🔆
|
||||||
|
📶
|
||||||
|
🛜
|
||||||
|
📳
|
||||||
|
📴
|
||||||
|
♀️
|
||||||
|
♂️
|
||||||
|
⚧️
|
||||||
|
✖️
|
||||||
|
➕
|
||||||
|
➖
|
||||||
|
➗
|
||||||
|
🟰
|
||||||
|
♾️
|
||||||
|
‼️
|
||||||
|
⁉️
|
||||||
|
❓
|
||||||
|
❔
|
||||||
|
❕
|
||||||
|
❗
|
||||||
|
〰️
|
||||||
|
💱
|
||||||
|
💲
|
||||||
|
⚕️
|
||||||
|
♻️
|
||||||
|
⚜️
|
||||||
|
🔱
|
||||||
|
📛
|
||||||
|
🔰
|
||||||
|
⭕
|
||||||
|
✅
|
||||||
|
☑️
|
||||||
|
✔️
|
||||||
|
❌
|
||||||
|
❎
|
||||||
|
➰
|
||||||
|
➿
|
||||||
|
〽️
|
||||||
|
✳️
|
||||||
|
✴️
|
||||||
|
❇️
|
||||||
|
©️
|
||||||
|
®️
|
||||||
|
™️
|
||||||
|
|
||||||
|
#️⃣
|
||||||
|
*️⃣
|
||||||
|
0️⃣
|
||||||
|
1️⃣
|
||||||
|
2️⃣
|
||||||
|
3️⃣
|
||||||
|
4️⃣
|
||||||
|
5️⃣
|
||||||
|
6️⃣
|
||||||
|
7️⃣
|
||||||
|
8️⃣
|
||||||
|
9️⃣
|
||||||
|
🔟
|
||||||
|
🔠
|
||||||
|
🔡
|
||||||
|
🔢
|
||||||
|
🔣
|
||||||
|
🔤
|
||||||
|
🅰️
|
||||||
|
🆎
|
||||||
|
🅱️
|
||||||
|
🆑
|
||||||
|
🆒
|
||||||
|
🆓
|
||||||
|
ℹ️
|
||||||
|
🆔
|
||||||
|
Ⓜ️
|
||||||
|
🆕
|
||||||
|
🆖
|
||||||
|
🅾️
|
||||||
|
🆗
|
||||||
|
🅿️
|
||||||
|
🆘
|
||||||
|
🆙
|
||||||
|
🆚
|
||||||
|
🈁
|
||||||
|
🈂️
|
||||||
|
🈷️
|
||||||
|
🈶
|
||||||
|
🈯
|
||||||
|
🉐
|
||||||
|
🈹
|
||||||
|
🈚
|
||||||
|
🈲
|
||||||
|
🉑
|
||||||
|
🈸
|
||||||
|
🈴
|
||||||
|
🈳
|
||||||
|
㊗️
|
||||||
|
㊙️
|
||||||
|
🈺
|
||||||
|
🈵
|
||||||
|
🔴
|
||||||
|
🟠
|
||||||
|
🟡
|
||||||
|
🟢
|
||||||
|
🔵
|
||||||
|
🟣
|
||||||
|
🟤
|
||||||
|
⚫
|
||||||
|
⚪
|
||||||
|
🟥
|
||||||
|
🟧
|
||||||
|
🟨
|
||||||
|
🟩
|
||||||
|
🟦
|
||||||
|
🟪
|
||||||
|
🟫
|
||||||
|
⬛
|
||||||
|
⬜
|
||||||
|
◼️
|
||||||
|
◻️
|
||||||
|
◾
|
||||||
|
◽
|
||||||
|
▪️
|
||||||
|
▫️
|
||||||
|
🔶
|
||||||
|
🔷
|
||||||
|
🔸
|
||||||
|
🔹
|
||||||
|
🔺
|
||||||
|
🔻
|
||||||
|
💠
|
||||||
|
🔘
|
||||||
|
🔳
|
||||||
|
🔲
|
218
app/src/main/assets/emoji/TRAVEL_AND_PLACES.txt
Normal file
218
app/src/main/assets/emoji/TRAVEL_AND_PLACES.txt
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
🌍
|
||||||
|
🌎
|
||||||
|
🌏
|
||||||
|
🌐
|
||||||
|
🗺️
|
||||||
|
🗾
|
||||||
|
🧭
|
||||||
|
🏔️
|
||||||
|
⛰️
|
||||||
|
🌋
|
||||||
|
🗻
|
||||||
|
🏕️
|
||||||
|
🏖️
|
||||||
|
🏜️
|
||||||
|
🏝️
|
||||||
|
🏞️
|
||||||
|
🏟️
|
||||||
|
🏛️
|
||||||
|
🏗️
|
||||||
|
🧱
|
||||||
|
🪨
|
||||||
|
🪵
|
||||||
|
🛖
|
||||||
|
🏘️
|
||||||
|
🏚️
|
||||||
|
🏠
|
||||||
|
🏡
|
||||||
|
🏢
|
||||||
|
🏣
|
||||||
|
🏤
|
||||||
|
🏥
|
||||||
|
🏦
|
||||||
|
🏨
|
||||||
|
🏩
|
||||||
|
🏪
|
||||||
|
🏫
|
||||||
|
🏬
|
||||||
|
🏭
|
||||||
|
🏯
|
||||||
|
🏰
|
||||||
|
💒
|
||||||
|
🗼
|
||||||
|
🗽
|
||||||
|
⛪
|
||||||
|
🕌
|
||||||
|
🛕
|
||||||
|
🕍
|
||||||
|
⛩️
|
||||||
|
🕋
|
||||||
|
⛲
|
||||||
|
⛺
|
||||||
|
🌁
|
||||||
|
🌃
|
||||||
|
🏙️
|
||||||
|
🌄
|
||||||
|
🌅
|
||||||
|
🌆
|
||||||
|
🌇
|
||||||
|
🌉
|
||||||
|
♨️
|
||||||
|
🎠
|
||||||
|
🛝
|
||||||
|
🎡
|
||||||
|
🎢
|
||||||
|
💈
|
||||||
|
🎪
|
||||||
|
🚂
|
||||||
|
🚃
|
||||||
|
🚄
|
||||||
|
🚅
|
||||||
|
🚆
|
||||||
|
🚇
|
||||||
|
🚈
|
||||||
|
🚉
|
||||||
|
🚊
|
||||||
|
🚝
|
||||||
|
🚞
|
||||||
|
🚋
|
||||||
|
🚌
|
||||||
|
🚍
|
||||||
|
🚎
|
||||||
|
🚐
|
||||||
|
🚑
|
||||||
|
🚒
|
||||||
|
🚓
|
||||||
|
🚔
|
||||||
|
🚕
|
||||||
|
🚖
|
||||||
|
🚗
|
||||||
|
🚘
|
||||||
|
🚙
|
||||||
|
🛻
|
||||||
|
🚚
|
||||||
|
🚛
|
||||||
|
🚜
|
||||||
|
🏎️
|
||||||
|
🏍️
|
||||||
|
🛵
|
||||||
|
🦽
|
||||||
|
🦼
|
||||||
|
🛺
|
||||||
|
🚲
|
||||||
|
🛴
|
||||||
|
🛹
|
||||||
|
🛼
|
||||||
|
🚏
|
||||||
|
🛣️
|
||||||
|
🛤️
|
||||||
|
🛢️
|
||||||
|
⛽
|
||||||
|
🛞
|
||||||
|
🚨
|
||||||
|
🚥
|
||||||
|
🚦
|
||||||
|
🛑
|
||||||
|
🚧
|
||||||
|
⚓
|
||||||
|
🛟
|
||||||
|
⛵
|
||||||
|
🛶
|
||||||
|
🚤
|
||||||
|
🛳️
|
||||||
|
⛴️
|
||||||
|
🛥️
|
||||||
|
🚢
|
||||||
|
✈️
|
||||||
|
🛩️
|
||||||
|
🛫
|
||||||
|
🛬
|
||||||
|
🪂
|
||||||
|
💺
|
||||||
|
🚁
|
||||||
|
🚟
|
||||||
|
🚠
|
||||||
|
🚡
|
||||||
|
🛰️
|
||||||
|
🚀
|
||||||
|
🛸
|
||||||
|
🛎️
|
||||||
|
🧳
|
||||||
|
⌛
|
||||||
|
⏳
|
||||||
|
⌚
|
||||||
|
⏰
|
||||||
|
⏱️
|
||||||
|
⏲️
|
||||||
|
🕰️
|
||||||
|
🕛
|
||||||
|
🕧
|
||||||
|
🕐
|
||||||
|
🕜
|
||||||
|
🕑
|
||||||
|
🕝
|
||||||
|
🕒
|
||||||
|
🕞
|
||||||
|
🕓
|
||||||
|
🕟
|
||||||
|
🕔
|
||||||
|
🕠
|
||||||
|
🕕
|
||||||
|
🕡
|
||||||
|
🕖
|
||||||
|
🕢
|
||||||
|
🕗
|
||||||
|
🕣
|
||||||
|
🕘
|
||||||
|
🕤
|
||||||
|
🕙
|
||||||
|
🕥
|
||||||
|
🕚
|
||||||
|
🕦
|
||||||
|
🌑
|
||||||
|
🌒
|
||||||
|
🌓
|
||||||
|
🌔
|
||||||
|
🌕
|
||||||
|
🌖
|
||||||
|
🌗
|
||||||
|
🌘
|
||||||
|
🌙
|
||||||
|
🌚
|
||||||
|
🌛
|
||||||
|
🌜
|
||||||
|
🌡️
|
||||||
|
☀️
|
||||||
|
🌝
|
||||||
|
🌞
|
||||||
|
🪐
|
||||||
|
⭐
|
||||||
|
🌟
|
||||||
|
🌠
|
||||||
|
🌌
|
||||||
|
☁️
|
||||||
|
⛅
|
||||||
|
⛈️
|
||||||
|
🌤️
|
||||||
|
🌥️
|
||||||
|
🌦️
|
||||||
|
🌧️
|
||||||
|
🌨️
|
||||||
|
🌩️
|
||||||
|
🌪️
|
||||||
|
🌫️
|
||||||
|
🌬️
|
||||||
|
🌀
|
||||||
|
🌈
|
||||||
|
🌂
|
||||||
|
☂️
|
||||||
|
☔
|
||||||
|
⛱️
|
||||||
|
⚡
|
||||||
|
❄️
|
||||||
|
☃️
|
||||||
|
⛄
|
||||||
|
☄️
|
||||||
|
🔥
|
||||||
|
💧
|
||||||
|
🌊
|
12
app/src/main/assets/emoji/minApi.txt
Normal file
12
app/src/main/assets/emoji/minApi.txt
Normal file
File diff suppressed because one or more lines are too long
28
app/src/main/assets/layouts/main/akan.txt
Normal file
28
app/src/main/assets/layouts/main/akan.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ɛ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ɔ x
|
||||||
|
c ¢
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
29
app/src/main/assets/layouts/main/bemba.txt
Normal file
29
app/src/main/assets/layouts/main/bemba.txt
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
ŋ
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
x
|
||||||
|
c
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
31
app/src/main/assets/layouts/main/central_kurdish.txt
Normal file
31
app/src/main/assets/layouts/main/central_kurdish.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
ق
|
||||||
|
و
|
||||||
|
ە
|
||||||
|
ر
|
||||||
|
ت
|
||||||
|
ی
|
||||||
|
ێ
|
||||||
|
ئ
|
||||||
|
ۆ
|
||||||
|
پ
|
||||||
|
|
||||||
|
ا
|
||||||
|
س
|
||||||
|
ش
|
||||||
|
د
|
||||||
|
ف
|
||||||
|
ھ|ه
|
||||||
|
ژ
|
||||||
|
ل
|
||||||
|
ک
|
||||||
|
گ
|
||||||
|
|
||||||
|
ز
|
||||||
|
ع
|
||||||
|
ح
|
||||||
|
ج
|
||||||
|
چ
|
||||||
|
خ
|
||||||
|
ب
|
||||||
|
ن
|
||||||
|
م
|
28
app/src/main/assets/layouts/main/dagbani.txt
Normal file
28
app/src/main/assets/layouts/main/dagbani.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
q
|
||||||
|
w
|
||||||
|
ɛ e
|
||||||
|
r ¢
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
ɔ o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
ɣ g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
ʒ z
|
||||||
|
x x
|
||||||
|
c
|
||||||
|
v
|
||||||
|
b
|
||||||
|
ŋ n
|
||||||
|
m
|
28
app/src/main/assets/layouts/main/ewe.txt
Normal file
28
app/src/main/assets/layouts/main/ewe.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ɛ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
ɣ y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ɔ x
|
||||||
|
c ¢
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
28
app/src/main/assets/layouts/main/ga.txt
Normal file
28
app/src/main/assets/layouts/main/ga.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ɛ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ɔ x
|
||||||
|
ŋ c ¢
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
28
app/src/main/assets/layouts/main/hausa.txt
Normal file
28
app/src/main/assets/layouts/main/hausa.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ẹ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ọ x
|
||||||
|
c
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n ₦
|
||||||
|
m
|
28
app/src/main/assets/layouts/main/igbo.txt
Normal file
28
app/src/main/assets/layouts/main/igbo.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ṅ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ọ x
|
||||||
|
c
|
||||||
|
ụ v
|
||||||
|
b
|
||||||
|
n ₦
|
||||||
|
m
|
28
app/src/main/assets/layouts/main/kikuyu.txt
Normal file
28
app/src/main/assets/layouts/main/kikuyu.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ĩ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ũ x
|
||||||
|
c
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
55
app/src/main/assets/layouts/main/korean_phonetic.json
Normal file
55
app/src/main/assets/layouts/main/korean_phonetic.json
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{ "label": "\u3147" },
|
||||||
|
{ "label": "\u3161" },
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3156" },
|
||||||
|
"default": { "label": "\u3154", "popup": { "main": { "label": "\u3156" } } }
|
||||||
|
},
|
||||||
|
{ "label": "\u3139" },
|
||||||
|
{ "label": "\u314c" },
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3152" },
|
||||||
|
"default": { "label": "\u3150", "popup": { "main": { "label": "\u3152" } } }
|
||||||
|
},
|
||||||
|
{ "label": "\u315c" },
|
||||||
|
{ "label": "\u3163" },
|
||||||
|
{ "label": "\u3157" },
|
||||||
|
{ "label": "\u314d" }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "label": "\u314f" },
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3146" },
|
||||||
|
"default": { "label": "\u3145", "popup": { "main": { "label": "\u3146" } } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3138" },
|
||||||
|
"default": { "label": "\u3137", "popup": { "main": { "label": "\u3138" } } }
|
||||||
|
},
|
||||||
|
{ "label": "\u3151" },
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3132" },
|
||||||
|
"default": { "label": "\u3131", "popup": { "main": { "label": "\u3132" } } }
|
||||||
|
},
|
||||||
|
{ "label": "\u314e" },
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3149" },
|
||||||
|
"default": { "label": "\u3148", "popup": { "main": { "label": "\u3149" } } }
|
||||||
|
},
|
||||||
|
{ "label": "\u314b" },
|
||||||
|
{ "label": "\u315b" }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ "label": "\u3155" },
|
||||||
|
{ "label": "\u3160" },
|
||||||
|
{ "label": "\u314a" },
|
||||||
|
{ "label": "\u3153" },
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "\u3143" },
|
||||||
|
"default": { "label": "\u3142", "popup": { "main": { "label": "\u3143" } } }
|
||||||
|
},
|
||||||
|
{ "label": "\u3134" },
|
||||||
|
{ "label": "\u3141" }
|
||||||
|
]
|
||||||
|
]
|
28
app/src/main/assets/layouts/main/lingala.txt
Normal file
28
app/src/main/assets/layouts/main/lingala.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
q
|
||||||
|
w
|
||||||
|
ɛ e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
ɔ o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
x
|
||||||
|
c
|
||||||
|
̌ v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
29
app/src/main/assets/layouts/main/luganda.txt
Normal file
29
app/src/main/assets/layouts/main/luganda.txt
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
ŋ
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
x
|
||||||
|
c
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
|
@ -8,7 +8,7 @@
|
||||||
ш
|
ш
|
||||||
щ
|
щ
|
||||||
з
|
з
|
||||||
х
|
х ъ [ {
|
||||||
|
|
||||||
ф
|
ф
|
||||||
ы
|
ы
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
л
|
л
|
||||||
д
|
д
|
||||||
ж
|
ж
|
||||||
э
|
э э́ ] }
|
||||||
|
|
||||||
я
|
я
|
||||||
ч
|
ч
|
||||||
|
|
34
app/src/main/assets/layouts/main/russian_extended.txt
Normal file
34
app/src/main/assets/layouts/main/russian_extended.txt
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
й
|
||||||
|
ц
|
||||||
|
у
|
||||||
|
к
|
||||||
|
е
|
||||||
|
н
|
||||||
|
г
|
||||||
|
ш
|
||||||
|
щ
|
||||||
|
з
|
||||||
|
х [ {
|
||||||
|
ъ ] }
|
||||||
|
|
||||||
|
ф
|
||||||
|
ы
|
||||||
|
в
|
||||||
|
а
|
||||||
|
п
|
||||||
|
р
|
||||||
|
о
|
||||||
|
л
|
||||||
|
д
|
||||||
|
ж
|
||||||
|
э э́
|
||||||
|
|
||||||
|
я
|
||||||
|
ч
|
||||||
|
с
|
||||||
|
м
|
||||||
|
и
|
||||||
|
т
|
||||||
|
ь
|
||||||
|
б <
|
||||||
|
ю >
|
28
app/src/main/assets/layouts/main/sesotho.txt
Normal file
28
app/src/main/assets/layouts/main/sesotho.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
š s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
x
|
||||||
|
c
|
||||||
|
v
|
||||||
|
b
|
||||||
|
n
|
||||||
|
m
|
|
@ -8,7 +8,8 @@
|
||||||
ш
|
ш
|
||||||
щ
|
щ
|
||||||
з
|
з
|
||||||
х
|
х [ {
|
||||||
|
ї ] }
|
||||||
|
|
||||||
ф
|
ф
|
||||||
і
|
і
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
л
|
л
|
||||||
д
|
д
|
||||||
ж
|
ж
|
||||||
є
|
є ' "
|
||||||
|
|
||||||
я
|
я
|
||||||
ч
|
ч
|
||||||
|
@ -30,4 +31,4 @@
|
||||||
т
|
т
|
||||||
ь
|
ь
|
||||||
б <
|
б <
|
||||||
ю >
|
ю > ґ
|
||||||
|
|
35
app/src/main/assets/layouts/main/ukrainian_extended.txt
Normal file
35
app/src/main/assets/layouts/main/ukrainian_extended.txt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
й
|
||||||
|
ц
|
||||||
|
у
|
||||||
|
к
|
||||||
|
е
|
||||||
|
н
|
||||||
|
г
|
||||||
|
ш
|
||||||
|
щ
|
||||||
|
з
|
||||||
|
х [ {
|
||||||
|
ї ] }
|
||||||
|
|
||||||
|
ф
|
||||||
|
і
|
||||||
|
в
|
||||||
|
а
|
||||||
|
п
|
||||||
|
р
|
||||||
|
о
|
||||||
|
л
|
||||||
|
д
|
||||||
|
ж
|
||||||
|
є ' "
|
||||||
|
' "
|
||||||
|
|
||||||
|
я
|
||||||
|
ч
|
||||||
|
с
|
||||||
|
м
|
||||||
|
и
|
||||||
|
т
|
||||||
|
ь
|
||||||
|
б <
|
||||||
|
ю > ґ
|
28
app/src/main/assets/layouts/main/yoruba.txt
Normal file
28
app/src/main/assets/layouts/main/yoruba.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
ẹ q
|
||||||
|
w
|
||||||
|
e
|
||||||
|
r
|
||||||
|
t
|
||||||
|
y
|
||||||
|
u
|
||||||
|
i
|
||||||
|
o
|
||||||
|
p
|
||||||
|
|
||||||
|
a
|
||||||
|
s
|
||||||
|
d
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
|
||||||
|
z
|
||||||
|
ọ x
|
||||||
|
c
|
||||||
|
ṣ v
|
||||||
|
b
|
||||||
|
n ₦
|
||||||
|
m
|
44
app/src/main/assets/layouts/number_row/number_row.json
Normal file
44
app/src/main/assets/layouts/number_row/number_row.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "!" },
|
||||||
|
"default": { "label": "1", "popup": { "relevant": [{ "label": "¹" }, { "label": "½" }, { "label": "⅓" }, { "label": "¼" }, { "label": "⅛" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "@" },
|
||||||
|
"default": { "label": "2", "popup": { "relevant": [{ "label": "²" }, { "label": "⅔" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "#" },
|
||||||
|
"default": { "label": "3", "popup": { "relevant": [{ "label": "³" }, { "label": "¾" }, { "label": "⅜" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "$" },
|
||||||
|
"default": { "label": "4", "popup": { "relevant": [{ "label": "⁴" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "%" },
|
||||||
|
"default": { "label": "5", "popup": { "relevant": [{ "label": "⁵" }, { "label": "⅝" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "^" },
|
||||||
|
"default": { "label": "6", "popup": { "relevant": [{ "label": "⁶" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "&" },
|
||||||
|
"default": { "label": "7", "popup": { "relevant": [{ "label": "⁷" }, { "label": "⅞" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "*" },
|
||||||
|
"default": { "label": "8", "popup": { "relevant": [{ "label": "⁸" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": "(" },
|
||||||
|
"default": { "label": "9", "popup": { "relevant": [{ "label": "⁹" }] } }
|
||||||
|
},
|
||||||
|
{ "$": "shift_state_selector",
|
||||||
|
"manualOrLocked": { "label": ")" },
|
||||||
|
"default": { "label": "0", "popup": { "relevant": [{ "label": "⁰" }, { "label": "ⁿ" }, { "label": "∅" }] } }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
|
@ -7,4 +7,4 @@
|
||||||
7 ⁷ ⅞
|
7 ⁷ ⅞
|
||||||
8 ⁸
|
8 ⁸
|
||||||
9 ⁹
|
9 ⁹
|
||||||
0 ⁰ ⁿ ∅
|
0 ⁰ ⁿ ∅
|
39
app/src/main/assets/locale_key_texts/ckb.txt
Normal file
39
app/src/main/assets/locale_key_texts/ckb.txt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[popup_keys]
|
||||||
|
ق ٯ
|
||||||
|
و وو
|
||||||
|
ە ة ﻪ ـہ
|
||||||
|
ر ڕ ڒ ࢪ
|
||||||
|
ت ط
|
||||||
|
ی ي ې ۍ
|
||||||
|
ێ ؽ
|
||||||
|
ئ ء ﺋ
|
||||||
|
ۆ ؤ ۏ ۊ ۋ ۉ ۇ
|
||||||
|
پ ث
|
||||||
|
ا أ إ آ ٱ
|
||||||
|
س ص
|
||||||
|
ش ض
|
||||||
|
د ۮ ڌ ﮆ
|
||||||
|
ف ڤ ڡ
|
||||||
|
ھ ھ
|
||||||
|
ژ ━|ـ
|
||||||
|
ل ڵ
|
||||||
|
ک ك ڪ
|
||||||
|
گ غ
|
||||||
|
ز ظ
|
||||||
|
ع ؏
|
||||||
|
ب ى
|
||||||
|
punctuation !autoColumnOrder!8 \؟ ! ، ٫ ؍ : ؛ ; : | - @ _ # * ٪ & ^
|
||||||
|
« „ “ ”
|
||||||
|
» ‚ ‘ ’ ‹ ›
|
||||||
|
|
||||||
|
[labels]
|
||||||
|
alphabet: ئپگ
|
||||||
|
symbol: ٣٢١؟
|
||||||
|
comma: ،
|
||||||
|
question: ؟
|
||||||
|
|
||||||
|
[number_row]
|
||||||
|
١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ٠
|
||||||
|
|
||||||
|
[tlds]
|
||||||
|
iq krd
|
|
@ -1,9 +1,19 @@
|
||||||
[popup_keys]
|
[popup_keys]
|
||||||
е ё
|
е ё е́ ѣ
|
||||||
ь ъ
|
ф ѳ
|
||||||
|
ы ы́
|
||||||
|
а а́
|
||||||
|
о о́
|
||||||
|
я я́
|
||||||
|
и и́
|
||||||
|
ь ъ ы
|
||||||
|
ю ю́
|
||||||
' ’ ‚ ‘ › ‹
|
' ’ ‚ ‘ › ‹
|
||||||
" ” „ “ » «
|
" ” „ “ » «
|
||||||
|
|
||||||
|
і ы
|
||||||
|
є э э́
|
||||||
|
|
||||||
[labels]
|
[labels]
|
||||||
alphabet: АБВ
|
alphabet: АБВ
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
[popup_keys]
|
[popup_keys]
|
||||||
|
е е́
|
||||||
г ґ
|
г ґ
|
||||||
ь
|
ф ѳ
|
||||||
і ї
|
і ї
|
||||||
' ’ ‚ ‘
|
а а́
|
||||||
" ” „ “
|
о о́
|
||||||
|
я я́
|
||||||
|
и и́ і ї
|
||||||
|
г ґ
|
||||||
|
ю ю́
|
||||||
|
' ’ ‚ ‘ › ‹
|
||||||
|
" ” „ “ » «
|
||||||
|
|
||||||
|
ы і ї
|
||||||
|
э є
|
||||||
|
|
||||||
[labels]
|
[labels]
|
||||||
alphabet: АБВ
|
alphabet: АБВ
|
||||||
|
|
|
@ -70,7 +70,12 @@ public class ProximityInfo {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
computeNearestNeighbors();
|
computeNearestNeighbors();
|
||||||
mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
|
try {
|
||||||
|
mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e(TAG, "could not create proximity info", e);
|
||||||
|
mNativeProximityInfo = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long mNativeProximityInfo;
|
private long mNativeProximityInfo;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import android.view.MotionEvent
|
||||||
import helium314.keyboard.accessibility.AccessibilityLongPressTimer.LongPressTimerCallback
|
import helium314.keyboard.accessibility.AccessibilityLongPressTimer.LongPressTimerCallback
|
||||||
import helium314.keyboard.keyboard.*
|
import helium314.keyboard.keyboard.*
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
|
import helium314.keyboard.latin.utils.SubtypeLocaleUtils.displayName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a delegate that can be registered in [MainKeyboardView] to enhance
|
* This class represents a delegate that can be registered in [MainKeyboardView] to enhance
|
||||||
|
@ -86,9 +86,7 @@ class MainKeyboardAccessibilityDelegate(
|
||||||
* @param keyboard The new keyboard.
|
* @param keyboard The new keyboard.
|
||||||
*/
|
*/
|
||||||
private fun announceKeyboardLanguage(keyboard: Keyboard) {
|
private fun announceKeyboardLanguage(keyboard: Keyboard) {
|
||||||
val languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(
|
sendWindowStateChanged(keyboard.mId.mSubtype.rawSubtype.displayName())
|
||||||
keyboard.mId.mSubtype.rawSubtype)
|
|
||||||
sendWindowStateChanged(languageText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package helium314.keyboard.compat
|
||||||
|
|
||||||
|
import android.app.KeyguardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
fun isDeviceLocked(context: Context): Boolean {
|
||||||
|
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||||
|
keyguardManager.isDeviceLocked
|
||||||
|
else
|
||||||
|
keyguardManager.isKeyguardLocked
|
||||||
|
}
|
|
@ -29,30 +29,18 @@ import java.util.*
|
||||||
* cursor: we'll start after this.
|
* cursor: we'll start after this.
|
||||||
* @param initialText The text that has already been combined so far.
|
* @param initialText The text that has already been combined so far.
|
||||||
*/
|
*/
|
||||||
class CombinerChain(initialText: String) {
|
class CombinerChain(initialText: String, combiningSpec: String) {
|
||||||
// The already combined text, as described above
|
// The already combined text, as described above
|
||||||
private val mCombinedText = StringBuilder(initialText)
|
private val mCombinedText = StringBuilder(initialText)
|
||||||
// The feedback on the composing state, as described above
|
// The feedback on the composing state, as described above
|
||||||
private val mStateFeedback = SpannableStringBuilder()
|
private val mStateFeedback = SpannableStringBuilder()
|
||||||
private val mCombiners = ArrayList<Combiner>()
|
private val mCombiners = ArrayList<Combiner>()
|
||||||
// Hangul combiner affects other scripts, e.g. period is seen as port of a word for latin,
|
|
||||||
// so we need to remove the combiner when not writing in hangul script.
|
|
||||||
// Maybe it would be better to always have the Hangul combiner, but make sure it doesn't affect
|
|
||||||
// events for other scripts, but how?
|
|
||||||
// todo: this really should be done properly, hangul combiner should do nothing when it's not needed
|
|
||||||
var isHangul = false
|
|
||||||
set(value) {
|
|
||||||
if (field == value) return
|
|
||||||
field = value
|
|
||||||
if (!value)
|
|
||||||
mCombiners.removeAll { it is HangulCombiner }
|
|
||||||
else if (mCombiners.none { it is HangulCombiner })
|
|
||||||
mCombiners.add(HangulCombiner())
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// The dead key combiner is always active, and always first
|
// The dead key combiner is always active, and always first
|
||||||
mCombiners.add(DeadKeyCombiner())
|
mCombiners.add(DeadKeyCombiner())
|
||||||
|
if (combiningSpec == "hangul")
|
||||||
|
mCombiners.add(HangulCombiner())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
|
|
|
@ -139,6 +139,16 @@ class Event private constructor(
|
||||||
null, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null)
|
null, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A helper method to split the code point and the key code.
|
||||||
|
// todo: Ultimately, they should not be squashed into the same variable, and this method should be removed.
|
||||||
|
@JvmStatic
|
||||||
|
fun createSoftwareKeypressEvent(keyCodeOrCodePoint: Int, metaState: Int, keyX: Int, keyY: Int, isKeyRepeat: Boolean) =
|
||||||
|
if (keyCodeOrCodePoint <= 0) {
|
||||||
|
createSoftwareKeypressEvent(NOT_A_CODE_POINT, keyCodeOrCodePoint, metaState, keyX, keyY, isKeyRepeat)
|
||||||
|
} else {
|
||||||
|
createSoftwareKeypressEvent(keyCodeOrCodePoint, NOT_A_KEY_CODE, metaState, keyX, keyY, isKeyRepeat)
|
||||||
|
}
|
||||||
|
|
||||||
fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, metaState: Int, next: Event?, isKeyRepeat: Boolean): Event {
|
fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, metaState: Int, next: Event?, isKeyRepeat: Boolean): Event {
|
||||||
return Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, metaState,
|
return Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, metaState,
|
||||||
Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
|
Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
|
||||||
|
@ -256,10 +266,8 @@ class Event private constructor(
|
||||||
source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING, source.mNextEvent)
|
source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING, source.mNextEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNotHandledEvent(): Event {
|
val notHandledEvent = Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0,
|
||||||
return Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0,
|
|
||||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null)
|
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is private - to create a new event, use one of the create* utility methods.
|
// This method is private - to create a new event, use one of the create* utility methods.
|
||||||
|
|
|
@ -16,6 +16,8 @@ class HangulCombiner : Combiner {
|
||||||
|
|
||||||
override fun processEvent(previousEvents: ArrayList<Event>?, event: Event): Event {
|
override fun processEvent(previousEvents: ArrayList<Event>?, event: Event): Event {
|
||||||
if (event.mKeyCode == KeyCode.SHIFT) return event
|
if (event.mKeyCode == KeyCode.SHIFT) return event
|
||||||
|
// previously we only used the combiner if codePoint > 0x1100 or codePoint == -1, but looks here it's not necessary
|
||||||
|
val event = HangulEventDecoder.decodeSoftwareKeyEvent(event)
|
||||||
if (Character.isWhitespace(event.mCodePoint)) {
|
if (Character.isWhitespace(event.mCodePoint)) {
|
||||||
val text = combiningStateFeedback
|
val text = combiningStateFeedback
|
||||||
reset()
|
reset()
|
||||||
|
|
|
@ -24,7 +24,8 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder {
|
||||||
// KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value
|
// KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value
|
||||||
// that includes both the unicode char in the lower 21 bits and flags in the upper bits,
|
// that includes both the unicode char in the lower 21 bits and flags in the upper bits,
|
||||||
// hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info.
|
// hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info.
|
||||||
val codePointAndFlags = keyEvent.unicodeChar
|
val codePointAndFlags = keyEvent.unicodeChar.takeIf { it != 0 }
|
||||||
|
?: Event.NOT_A_CODE_POINT // KeyEvent has 0 if no codePoint, but that's actually valid so we convert it to -1
|
||||||
// The keyCode is the abstraction used by the KeyEvent to represent different keys that
|
// The keyCode is the abstraction used by the KeyEvent to represent different keys that
|
||||||
// do not necessarily map to a unicode character. This represents a physical key, like
|
// do not necessarily map to a unicode character. This represents a physical key, like
|
||||||
// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
|
// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
|
||||||
|
@ -48,6 +49,21 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder {
|
||||||
} else Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat)
|
} else Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat)
|
||||||
// If not Enter, then this is just a regular keypress event for a normal character
|
// If not Enter, then this is just a regular keypress event for a normal character
|
||||||
// that can be committed right away, taking into account the current state.
|
// that can be committed right away, taking into account the current state.
|
||||||
} else Event.createNotHandledEvent()
|
} else if (isDpadDirection(keyCode)) {
|
||||||
|
Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat)
|
||||||
|
// } else if (KeyEvent.isModifierKey(keyCode)) {
|
||||||
|
// todo: we could synchronize meta state across HW and SW keyboard, but that's more work for little benefit (especially with shift & caps lock)
|
||||||
|
} else {
|
||||||
|
Event.notHandledEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun isDpadDirection(keyCode: Int) = when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN_LEFT, KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, KeyEvent.KEYCODE_DPAD_UP_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP_LEFT -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import helium314.keyboard.latin.common.StringUtils;
|
||||||
import helium314.keyboard.latin.utils.PopupKeysUtilsKt;
|
import helium314.keyboard.latin.utils.PopupKeysUtilsKt;
|
||||||
import helium314.keyboard.latin.utils.ToolbarKey;
|
import helium314.keyboard.latin.utils.ToolbarKey;
|
||||||
import helium314.keyboard.latin.utils.ToolbarUtilsKt;
|
import helium314.keyboard.latin.utils.ToolbarUtilsKt;
|
||||||
|
import kotlin.collections.ArraysKt;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -517,11 +518,7 @@ public class Key implements Comparable<Key> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isModifier() {
|
public final boolean isModifier() {
|
||||||
return switch (mCode) {
|
return KeyCode.INSTANCE.isModifier(mCode);
|
||||||
case KeyCode.SHIFT, KeyCode.SYMBOL_ALPHA, KeyCode.ALPHA, KeyCode.SYMBOL, KeyCode.NUMPAD, KeyCode.CTRL,
|
|
||||||
KeyCode.ALT, KeyCode.FN, KeyCode.META -> true;
|
|
||||||
default -> false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isRepeatable() {
|
public final boolean isRepeatable() {
|
||||||
|
@ -919,7 +916,7 @@ public class Key implements Comparable<Key> {
|
||||||
@NonNull final Drawable spacebarBackground,
|
@NonNull final Drawable spacebarBackground,
|
||||||
@NonNull final Drawable actionKeyBackground) {
|
@NonNull final Drawable actionKeyBackground) {
|
||||||
final Drawable background;
|
final Drawable background;
|
||||||
if (isAccentColored()) {
|
if (hasActionKeyBackground()) {
|
||||||
background = actionKeyBackground;
|
background = actionKeyBackground;
|
||||||
} else if (hasFunctionalBackground()) {
|
} else if (hasFunctionalBackground()) {
|
||||||
background = functionalKeyBackground;
|
background = functionalKeyBackground;
|
||||||
|
@ -933,17 +930,10 @@ public class Key implements Comparable<Key> {
|
||||||
return background;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isAccentColored() {
|
public final boolean hasActionKeyPopups() {
|
||||||
if (hasActionKeyBackground()) return true;
|
if (!hasActionKeyBackground()) return false;
|
||||||
final String iconName = getIconName();
|
// only use the special action key popups for action colored keys, and only for icon popups
|
||||||
if (iconName == null) return false;
|
return ArraysKt.none(getPopupKeys(), (key) -> key.mIconName == null);
|
||||||
// todo: other way of identifying the color?
|
|
||||||
// this should be done differently, as users can set any icon now
|
|
||||||
// how is the background drawable selected? can we use the same way?
|
|
||||||
return iconName.equals(KeyboardIconsSet.NAME_NEXT_KEY)
|
|
||||||
|| iconName.equals(KeyboardIconsSet.NAME_PREVIOUS_KEY)
|
|
||||||
|| iconName.equals("clipboard_action_key")
|
|
||||||
|| iconName.equals("emoji_action_key");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasFunctionalBackground() {
|
public boolean hasFunctionalBackground() {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
package helium314.keyboard.keyboard;
|
package helium314.keyboard.keyboard;
|
||||||
|
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
import helium314.keyboard.latin.common.Constants;
|
import helium314.keyboard.latin.common.Constants;
|
||||||
import helium314.keyboard.latin.common.InputPointers;
|
import helium314.keyboard.latin.common.InputPointers;
|
||||||
|
|
||||||
|
@ -31,6 +33,12 @@ public interface KeyboardActionListener {
|
||||||
*/
|
*/
|
||||||
void onReleaseKey(int primaryCode, boolean withSliding);
|
void onReleaseKey(int primaryCode, boolean withSliding);
|
||||||
|
|
||||||
|
/** For handling hardware key presses. Returns whether the event was handled. */
|
||||||
|
boolean onKeyDown(int keyCode, KeyEvent keyEvent);
|
||||||
|
|
||||||
|
/** For handling hardware key presses. Returns whether the event was handled. */
|
||||||
|
boolean onKeyUp(int keyCode, KeyEvent keyEvent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a key code to the listener.
|
* Send a key code to the listener.
|
||||||
*
|
*
|
||||||
|
@ -117,6 +125,10 @@ public interface KeyboardActionListener {
|
||||||
@Override
|
@Override
|
||||||
public void onReleaseKey(int primaryCode, boolean withSliding) {}
|
public void onReleaseKey(int primaryCode, boolean withSliding) {}
|
||||||
@Override
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { return false; }
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { return false; }
|
||||||
|
@Override
|
||||||
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
|
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
|
||||||
@Override
|
@Override
|
||||||
public void onTextInput(String text) {}
|
public void onTextInput(String text) {}
|
||||||
|
|
|
@ -1,21 +1,38 @@
|
||||||
package helium314.keyboard.keyboard
|
package helium314.keyboard.keyboard
|
||||||
|
|
||||||
|
import android.text.InputType
|
||||||
|
import android.util.SparseArray
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
import android.view.inputmethod.InputMethodSubtype
|
||||||
|
import helium314.keyboard.event.Event
|
||||||
|
import helium314.keyboard.event.HangulEventDecoder
|
||||||
|
import helium314.keyboard.event.HardwareEventDecoder
|
||||||
|
import helium314.keyboard.event.HardwareKeyboardEventDecoder
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
||||||
|
import helium314.keyboard.latin.EmojiAltPhysicalKeyDetector
|
||||||
import helium314.keyboard.latin.LatinIME
|
import helium314.keyboard.latin.LatinIME
|
||||||
import helium314.keyboard.latin.RichInputMethodManager
|
import helium314.keyboard.latin.RichInputMethodManager
|
||||||
import helium314.keyboard.latin.common.Constants
|
import helium314.keyboard.latin.common.Constants
|
||||||
import helium314.keyboard.latin.common.InputPointers
|
import helium314.keyboard.latin.common.InputPointers
|
||||||
import helium314.keyboard.latin.common.StringUtils
|
import helium314.keyboard.latin.common.StringUtils
|
||||||
|
import helium314.keyboard.latin.common.combiningRange
|
||||||
import helium314.keyboard.latin.common.loopOverCodePoints
|
import helium314.keyboard.latin.common.loopOverCodePoints
|
||||||
import helium314.keyboard.latin.common.loopOverCodePointsBackwards
|
import helium314.keyboard.latin.common.loopOverCodePointsBackwards
|
||||||
|
import helium314.keyboard.latin.define.ProductionFlags
|
||||||
import helium314.keyboard.latin.inputlogic.InputLogic
|
import helium314.keyboard.latin.inputlogic.InputLogic
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inputLogic: InputLogic) : KeyboardActionListener {
|
class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inputLogic: InputLogic) : KeyboardActionListener {
|
||||||
|
|
||||||
|
private val connection = inputLogic.mConnection
|
||||||
|
private val emojiAltPhysicalKeyDetector by lazy { EmojiAltPhysicalKeyDetector(latinIME.resources) }
|
||||||
|
|
||||||
|
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
|
||||||
|
// If it turns out we need several, it will get grown seamlessly.
|
||||||
|
private val hardwareEventDecoders: SparseArray<HardwareEventDecoder> = SparseArray(1)
|
||||||
|
|
||||||
private val keyboardSwitcher = KeyboardSwitcher.getInstance()
|
private val keyboardSwitcher = KeyboardSwitcher.getInstance()
|
||||||
private val settings = Settings.getInstance()
|
private val settings = Settings.getInstance()
|
||||||
private var metaState = 0 // is this enough, or are there threading issues with the different PointerTrackers?
|
private var metaState = 0 // is this enough, or are there threading issues with the different PointerTrackers?
|
||||||
|
@ -28,9 +45,15 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
private fun adjustMetaState(code: Int, remove: Boolean) {
|
private fun adjustMetaState(code: Int, remove: Boolean) {
|
||||||
val metaCode = when (code) {
|
val metaCode = when (code) {
|
||||||
KeyCode.CTRL -> KeyEvent.META_CTRL_ON
|
KeyCode.CTRL -> KeyEvent.META_CTRL_ON
|
||||||
|
KeyCode.CTRL_LEFT -> KeyEvent.META_CTRL_LEFT_ON
|
||||||
|
KeyCode.CTRL_RIGHT -> KeyEvent.META_CTRL_RIGHT_ON
|
||||||
KeyCode.ALT -> KeyEvent.META_ALT_ON
|
KeyCode.ALT -> KeyEvent.META_ALT_ON
|
||||||
|
KeyCode.ALT_LEFT -> KeyEvent.META_ALT_LEFT_ON
|
||||||
|
KeyCode.ALT_RIGHT -> KeyEvent.META_ALT_RIGHT_ON
|
||||||
KeyCode.FN -> KeyEvent.META_FUNCTION_ON
|
KeyCode.FN -> KeyEvent.META_FUNCTION_ON
|
||||||
KeyCode.META -> KeyEvent.META_META_ON
|
KeyCode.META -> KeyEvent.META_META_ON
|
||||||
|
KeyCode.META_LEFT -> KeyEvent.META_META_LEFT_ON
|
||||||
|
KeyCode.META_RIGHT -> KeyEvent.META_META_RIGHT_ON
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
metaState = if (remove) metaState and metaCode.inv()
|
metaState = if (remove) metaState and metaCode.inv()
|
||||||
|
@ -48,9 +71,62 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
keyboardSwitcher.onReleaseKey(primaryCode, withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
|
keyboardSwitcher.onReleaseKey(primaryCode, withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onKeyUp(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||||
|
emojiAltPhysicalKeyDetector.onKeyUp(keyEvent)
|
||||||
|
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val keyIdentifier = keyEvent.deviceId.toLong() shl 32 + keyEvent.keyCode
|
||||||
|
return inputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||||
|
emojiAltPhysicalKeyDetector.onKeyDown(keyEvent)
|
||||||
|
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val event: Event
|
||||||
|
if (settings.current.mLocale.language == "ko") { // todo: this does not appear to be the right place
|
||||||
|
val subtype = keyboardSwitcher.keyboard?.mId?.mSubtype ?: RichInputMethodManager.getInstance().currentSubtype
|
||||||
|
event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent) {
|
||||||
|
getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event = getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.isHandled) {
|
||||||
|
inputLogic.onCodeInput(
|
||||||
|
settings.current, event,
|
||||||
|
keyboardSwitcher.getKeyboardShiftMode(), // TODO: this is not necessarily correct for a hardware keyboard right now
|
||||||
|
keyboardSwitcher.getCurrentKeyboardScript(),
|
||||||
|
latinIME.mHandler
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) {
|
override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) {
|
||||||
|
when (primaryCode) {
|
||||||
|
KeyCode.TOGGLE_AUTOCORRECT -> return Settings.getInstance().toggleAutoCorrect()
|
||||||
|
KeyCode.TOGGLE_INCOGNITO_MODE -> return Settings.getInstance().toggleAlwaysIncognitoMode()
|
||||||
|
}
|
||||||
val mkv = keyboardSwitcher.mainKeyboardView
|
val mkv = keyboardSwitcher.mainKeyboardView
|
||||||
latinIME.onCodeInput(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
|
|
||||||
|
// checking if the character is a combining accent
|
||||||
|
val event = if (primaryCode in combiningRange) { // todo: should this be done later, maybe in inputLogic?
|
||||||
|
Event.createSoftwareDeadEvent(primaryCode, 0, metaState, mkv.getKeyX(x), mkv.getKeyY(y), null)
|
||||||
|
} else {
|
||||||
|
// todo:
|
||||||
|
// setting meta shift should only be done for arrow and similar cursor movement keys
|
||||||
|
// should only be enabled once it works more reliably (currently depends on app for some reason)
|
||||||
|
// if (mkv.keyboard?.mId?.isAlphabetShiftedManually == true)
|
||||||
|
// Event.createSoftwareKeypressEvent(primaryCode, metaState or KeyEvent.META_SHIFT_ON, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
|
||||||
|
// else Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
|
||||||
|
Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
|
||||||
|
}
|
||||||
|
latinIME.onEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTextInput(text: String?) = latinIME.onTextInput(text)
|
override fun onTextInput(text: String?) = latinIME.onTextInput(text)
|
||||||
|
@ -70,8 +146,9 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
keyboardSwitcher.onFinishSlidingInput(latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
|
keyboardSwitcher.onFinishSlidingInput(latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
|
||||||
|
|
||||||
override fun onCustomRequest(requestCode: Int): Boolean {
|
override fun onCustomRequest(requestCode: Int): Boolean {
|
||||||
if (requestCode == Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)
|
if (requestCode == Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER) {
|
||||||
return latinIME.showInputPickerDialog()
|
return latinIME.showInputPickerDialog()
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,30 +178,34 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
|
|
||||||
override fun onMoveDeletePointer(steps: Int) {
|
override fun onMoveDeletePointer(steps: Int) {
|
||||||
inputLogic.finishInput()
|
inputLogic.finishInput()
|
||||||
val end = inputLogic.mConnection.expectedSelectionEnd
|
val end = connection.expectedSelectionEnd
|
||||||
var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint
|
val actualSteps = actualSteps(steps)
|
||||||
|
val start = connection.expectedSelectionStart + actualSteps
|
||||||
|
if (start > end) return
|
||||||
|
connection.setSelection(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun actualSteps(steps: Int): Int {
|
||||||
|
var actualSteps = 0
|
||||||
|
// corrected steps to avoid splitting chars belonging to the same codepoint
|
||||||
if (steps > 0) {
|
if (steps > 0) {
|
||||||
val text = inputLogic.mConnection.getSelectedText(0)
|
val text = connection.getSelectedText(0) ?: return steps
|
||||||
if (text == null) actualSteps = steps
|
loopOverCodePoints(text) { cp, charCount ->
|
||||||
else loopOverCodePoints(text) {
|
actualSteps += charCount
|
||||||
actualSteps += Character.charCount(it)
|
|
||||||
actualSteps >= steps
|
actualSteps >= steps
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val text = inputLogic.mConnection.getTextBeforeCursor(-steps * 4, 0)
|
val text = connection.getTextBeforeCursor(-steps * 4, 0) ?: return steps
|
||||||
if (text == null) actualSteps = steps
|
loopOverCodePointsBackwards(text) { cp, charCount ->
|
||||||
else loopOverCodePointsBackwards(text) {
|
actualSteps -= charCount
|
||||||
actualSteps -= Character.charCount(it)
|
|
||||||
actualSteps <= steps
|
actualSteps <= steps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val start = inputLogic.mConnection.expectedSelectionStart + actualSteps
|
return actualSteps
|
||||||
if (start > end) return
|
|
||||||
inputLogic.mConnection.setSelection(start, end)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpWithDeletePointerActive() {
|
override fun onUpWithDeletePointerActive() {
|
||||||
if (!inputLogic.mConnection.hasSelection()) return
|
if (!connection.hasSelection()) return
|
||||||
inputLogic.finishInput()
|
inputLogic.finishInput()
|
||||||
onCodeInput(KeyCode.DELETE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
onCodeInput(KeyCode.DELETE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
||||||
}
|
}
|
||||||
|
@ -135,7 +216,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
|
|
||||||
private fun onLanguageSlide(steps: Int): Boolean {
|
private fun onLanguageSlide(steps: Int): Boolean {
|
||||||
if (abs(steps) < settings.current.mLanguageSwipeDistance) return false
|
if (abs(steps) < settings.current.mLanguageSwipeDistance) return false
|
||||||
val subtypes = RichInputMethodManager.getInstance().getMyEnabledInputMethodSubtypeList(false)
|
val subtypes = RichInputMethodManager.getInstance().getMyEnabledInputMethodSubtypes(true)
|
||||||
if (subtypes.size <= 1) { // only allow if we have more than one subtype
|
if (subtypes.size <= 1) { // only allow if we have more than one subtype
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -143,16 +224,17 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
val current = RichInputMethodManager.getInstance().currentSubtype.rawSubtype
|
val current = RichInputMethodManager.getInstance().currentSubtype.rawSubtype
|
||||||
var wantedIndex = subtypes.indexOf(current) + if (steps > 0) 1 else -1
|
var wantedIndex = subtypes.indexOf(current) + if (steps > 0) 1 else -1
|
||||||
wantedIndex %= subtypes.size
|
wantedIndex %= subtypes.size
|
||||||
if (wantedIndex < 0)
|
if (wantedIndex < 0) {
|
||||||
wantedIndex += subtypes.size
|
wantedIndex += subtypes.size
|
||||||
|
}
|
||||||
val newSubtype = subtypes[wantedIndex]
|
val newSubtype = subtypes[wantedIndex]
|
||||||
|
|
||||||
// do not switch if we would switch to the initial subtype after cycling all other subtypes
|
// do not switch if we would switch to the initial subtype after cycling all other subtypes
|
||||||
if (initialSubtype == null)
|
if (initialSubtype == null) initialSubtype = current
|
||||||
initialSubtype = current
|
|
||||||
if (initialSubtype == newSubtype) {
|
if (initialSubtype == newSubtype) {
|
||||||
if ((subtypeSwitchCount > 0 && steps > 0) || ((subtypeSwitchCount < 0 && steps < 0)))
|
if ((subtypeSwitchCount > 0 && steps > 0) || (subtypeSwitchCount < 0 && steps < 0)) {
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (steps > 0) subtypeSwitchCount++ else subtypeSwitchCount--
|
if (steps > 0) subtypeSwitchCount++ else subtypeSwitchCount--
|
||||||
|
|
||||||
|
@ -173,17 +255,8 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
val steps = if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) -rawSteps else rawSteps
|
val steps = if (RichInputMethodManager.getInstance().currentSubtype.isRtlSubtype) -rawSteps else rawSteps
|
||||||
val moveSteps: Int
|
val moveSteps: Int
|
||||||
if (steps < 0) {
|
if (steps < 0) {
|
||||||
var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint
|
val text = connection.getTextBeforeCursor(-steps * 4, 0) ?: return false
|
||||||
val text = inputLogic.mConnection.getTextBeforeCursor(-steps * 4, 0) ?: return false
|
moveSteps = negativeMoveSteps(text, steps)
|
||||||
loopOverCodePointsBackwards(text) {
|
|
||||||
if (StringUtils.mightBeEmoji(it)) {
|
|
||||||
actualSteps = 0
|
|
||||||
return@loopOverCodePointsBackwards true
|
|
||||||
}
|
|
||||||
actualSteps -= Character.charCount(it)
|
|
||||||
actualSteps <= steps
|
|
||||||
}
|
|
||||||
moveSteps = -text.length.coerceAtMost(abs(actualSteps))
|
|
||||||
if (moveSteps == 0) {
|
if (moveSteps == 0) {
|
||||||
// some apps don't return any text via input connection, and the cursor can't be moved
|
// some apps don't return any text via input connection, and the cursor can't be moved
|
||||||
// we fall back to virtually pressing the left/right key one or more times instead
|
// we fall back to virtually pressing the left/right key one or more times instead
|
||||||
|
@ -193,36 +266,70 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var actualSteps = 0 // corrected steps to avoid splitting chars belonging to the same codepoint
|
val text = connection.getTextAfterCursor(steps * 4, 0) ?: return false
|
||||||
val text = inputLogic.mConnection.getTextAfterCursor(steps * 4, 0) ?: return false
|
moveSteps = positiveMoveSteps(text, steps)
|
||||||
loopOverCodePoints(text) {
|
|
||||||
if (StringUtils.mightBeEmoji(it)) {
|
|
||||||
actualSteps = 0
|
|
||||||
return@loopOverCodePoints true
|
|
||||||
}
|
|
||||||
actualSteps += Character.charCount(it)
|
|
||||||
actualSteps >= steps
|
|
||||||
}
|
|
||||||
moveSteps = text.length.coerceAtMost(actualSteps)
|
|
||||||
if (moveSteps == 0) {
|
if (moveSteps == 0) {
|
||||||
|
// some apps don't return any text via input connection, and the cursor can't be moved
|
||||||
|
// we fall back to virtually pressing the left/right key one or more times instead
|
||||||
repeat(steps) {
|
repeat(steps) {
|
||||||
onCodeInput(KeyCode.ARROW_RIGHT, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
onCodeInput(KeyCode.ARROW_RIGHT, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (inputLogic.moveCursorByAndReturnIfInsideComposingWord(moveSteps)) {
|
|
||||||
|
// the shortcut below causes issues due to horrible handling of text fields by Firefox and forks
|
||||||
|
// issues:
|
||||||
|
// * setSelection "will cause the editor to call onUpdateSelection", see: https://developer.android.com/reference/android/view/inputmethod/InputConnection#setSelection(int,%20int)
|
||||||
|
// but Firefox is simply not doing this within the same word... WTF?
|
||||||
|
// https://github.com/Helium314/HeliBoard/issues/1139#issuecomment-2588169384
|
||||||
|
// * inputType is NOT if variant InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT (variant appears to always be 0)
|
||||||
|
// so we can't even only do it for browsers (identifying by app name will break for forks)
|
||||||
|
// best "solution" is not doing this for InputType variation 0 but this applies to the majority of text fields...
|
||||||
|
val variation = InputType.TYPE_MASK_VARIATION and Settings.getValues().mInputAttributes.mInputType
|
||||||
|
if (variation != 0 && inputLogic.moveCursorByAndReturnIfInsideComposingWord(moveSteps)) {
|
||||||
// no need to finish input and restart suggestions if we're still in the word
|
// no need to finish input and restart suggestions if we're still in the word
|
||||||
// this is a noticeable performance improvement
|
// this is a noticeable performance improvement when moving through long words
|
||||||
val newPosition = inputLogic.mConnection.expectedSelectionStart + moveSteps
|
val newPosition = connection.expectedSelectionStart + moveSteps
|
||||||
inputLogic.mConnection.setSelection(newPosition, newPosition)
|
connection.setSelection(newPosition, newPosition)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
inputLogic.finishInput()
|
inputLogic.finishInput()
|
||||||
val newPosition = inputLogic.mConnection.expectedSelectionStart + moveSteps
|
val newPosition = connection.expectedSelectionStart + moveSteps
|
||||||
inputLogic.mConnection.setSelection(newPosition, newPosition)
|
connection.setSelection(newPosition, newPosition)
|
||||||
inputLogic.restartSuggestionsOnWordTouchedByCursor(settings.current, keyboardSwitcher.currentKeyboardScript)
|
inputLogic.restartSuggestionsOnWordTouchedByCursor(settings.current, keyboardSwitcher.currentKeyboardScript)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun positiveMoveSteps(text: CharSequence, steps: Int): Int {
|
||||||
|
var actualSteps = 0
|
||||||
|
// corrected steps to avoid splitting chars belonging to the same codepoint
|
||||||
|
loopOverCodePoints(text) { cp, charCount ->
|
||||||
|
if (StringUtils.mightBeEmoji(cp)) return 0
|
||||||
|
actualSteps += charCount
|
||||||
|
actualSteps >= steps
|
||||||
|
}
|
||||||
|
return min(actualSteps, text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun negativeMoveSteps(text: CharSequence, steps: Int): Int {
|
||||||
|
var actualSteps = 0
|
||||||
|
// corrected steps to avoid splitting chars belonging to the same codepoint
|
||||||
|
loopOverCodePointsBackwards(text) { cp, charCount ->
|
||||||
|
if (StringUtils.mightBeEmoji(cp)) return 0
|
||||||
|
actualSteps -= charCount
|
||||||
|
actualSteps <= steps
|
||||||
|
}
|
||||||
|
return -min(-actualSteps, text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHardwareKeyEventDecoder(deviceId: Int): HardwareEventDecoder {
|
||||||
|
hardwareEventDecoders.get(deviceId)?.let { return it }
|
||||||
|
|
||||||
|
// TODO: create the decoder according to the specification
|
||||||
|
val newDecoder = HardwareKeyboardEventDecoder(deviceId)
|
||||||
|
hardwareEventDecoders.put(deviceId, newDecoder)
|
||||||
|
return newDecoder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,11 @@ public final class KeyboardId {
|
||||||
|| mElementId == ELEMENT_ALPHABET_AUTOMATIC_SHIFTED || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED;
|
|| mElementId == ELEMENT_ALPHABET_AUTOMATIC_SHIFTED || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAlphabetShiftedManually() {
|
||||||
|
return mElementId == ELEMENT_ALPHABET_SHIFT_LOCKED || mElementId == ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED
|
||||||
|
|| mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isNumberLayout() {
|
public boolean isNumberLayout() {
|
||||||
return mElementId == ELEMENT_NUMBER || mElementId == ELEMENT_NUMPAD
|
return mElementId == ELEMENT_NUMBER || mElementId == ELEMENT_NUMPAD
|
||||||
|| mElementId == ELEMENT_PHONE || mElementId == ELEMENT_PHONE_SYMBOLS;
|
|| mElementId == ELEMENT_PHONE || mElementId == ELEMENT_PHONE_SYMBOLS;
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
package helium314.keyboard.keyboard;
|
package helium314.keyboard.keyboard;
|
||||||
|
|
||||||
import android.app.KeyguardManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
||||||
|
import helium314.keyboard.compat.IsLockedCompatKt;
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardBuilder;
|
import helium314.keyboard.keyboard.internal.KeyboardBuilder;
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet;
|
import helium314.keyboard.keyboard.internal.KeyboardIconsSet;
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardParams;
|
import helium314.keyboard.keyboard.internal.KeyboardParams;
|
||||||
|
@ -96,7 +95,7 @@ public final class KeyboardLayoutSet {
|
||||||
public static void onSystemLocaleChanged() {
|
public static void onSystemLocaleChanged() {
|
||||||
clearKeyboardCache();
|
clearKeyboardCache();
|
||||||
LocaleKeyboardInfosKt.clearCache();
|
LocaleKeyboardInfosKt.clearCache();
|
||||||
SubtypeLocaleUtils.clearDisplayNameCache();
|
SubtypeLocaleUtils.clearSubtypeDisplayNameCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onKeyboardThemeChanged() {
|
public static void onKeyboardThemeChanged() {
|
||||||
|
@ -207,14 +206,8 @@ public final class KeyboardLayoutSet {
|
||||||
params.mEditorInfo = editorInfo;
|
params.mEditorInfo = editorInfo;
|
||||||
params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
|
params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
|
||||||
|
|
||||||
// When the device is still locked, features like showing the IME setting app need to
|
// When the device is still locked, features like showing the IME setting app need to be locked down.
|
||||||
// be locked down.
|
params.mDeviceLocked = IsLockedCompatKt.isDeviceLocked(context);
|
||||||
final KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
|
||||||
params.mDeviceLocked = km.isDeviceLocked();
|
|
||||||
} else {
|
|
||||||
params.mDeviceLocked = km.isKeyguardLocked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyboardLayoutSet buildEmojiClipBottomRow(final Context context, @Nullable final EditorInfo ei) {
|
public static KeyboardLayoutSet buildEmojiClipBottomRow(final Context context, @Nullable final EditorInfo ei) {
|
||||||
|
@ -252,7 +245,7 @@ public final class KeyboardLayoutSet {
|
||||||
mParams.mVoiceInputKeyEnabled = enabled;
|
mParams.mVoiceInputKeyEnabled = enabled;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setNumberRowEnabled(final boolean enabled) {
|
public Builder setNumberRowEnabled(final boolean enabled) {
|
||||||
mParams.mNumberRowEnabled = enabled;
|
mParams.mNumberRowEnabled = enabled;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.view.View;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodSubtype;
|
import android.view.inputmethod.InputMethodSubtype;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.HorizontalScrollView;
|
import android.widget.HorizontalScrollView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -49,8 +50,8 @@ import helium314.keyboard.latin.utils.Log;
|
||||||
import helium314.keyboard.latin.utils.RecapitalizeStatus;
|
import helium314.keyboard.latin.utils.RecapitalizeStatus;
|
||||||
import helium314.keyboard.latin.utils.ResourceUtils;
|
import helium314.keyboard.latin.utils.ResourceUtils;
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils;
|
import helium314.keyboard.latin.utils.ScriptUtils;
|
||||||
import helium314.keyboard.latin.utils.SubtypeLocaleUtils;
|
|
||||||
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional;
|
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional;
|
||||||
|
import helium314.keyboard.latin.utils.ToolbarMode;
|
||||||
|
|
||||||
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
|
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
|
||||||
|
@ -64,6 +65,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
private LinearLayout mClipboardStripView;
|
private LinearLayout mClipboardStripView;
|
||||||
private HorizontalScrollView mClipboardStripScrollView;
|
private HorizontalScrollView mClipboardStripScrollView;
|
||||||
private SuggestionStripView mSuggestionStripView;
|
private SuggestionStripView mSuggestionStripView;
|
||||||
|
private FrameLayout mStripContainer;
|
||||||
private ClipboardHistoryView mClipboardHistoryView;
|
private ClipboardHistoryView mClipboardHistoryView;
|
||||||
private TextView mFakeToastView;
|
private TextView mFakeToastView;
|
||||||
private LatinIME mLatinIME;
|
private LatinIME mLatinIME;
|
||||||
|
@ -136,7 +138,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
|
private void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
|
||||||
final int currentAutoCapsState, final int currentRecapitalizeState) {
|
final int currentAutoCapsState, final int currentRecapitalizeState) {
|
||||||
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
|
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
|
||||||
mThemeContext, editorInfo);
|
mThemeContext, editorInfo);
|
||||||
|
@ -157,10 +159,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
} catch (KeyboardLayoutSetException e) {
|
} catch (KeyboardLayoutSetException e) {
|
||||||
Log.e(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
|
Log.e(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
|
||||||
try {
|
try {
|
||||||
final InputMethodSubtype qwerty = SubtypeUtilsAdditional.INSTANCE
|
final InputMethodSubtype defaults = SubtypeUtilsAdditional.INSTANCE.createDefaultSubtype(mRichImm.getCurrentSubtypeLocale());
|
||||||
.createEmojiCapableAdditionalSubtype(mRichImm.getCurrentSubtypeLocale(), SubtypeLocaleUtils.QWERTY, true);
|
|
||||||
mKeyboardLayoutSet = builder.setKeyboardGeometry(keyboardWidth, keyboardHeight)
|
mKeyboardLayoutSet = builder.setKeyboardGeometry(keyboardWidth, keyboardHeight)
|
||||||
.setSubtype(RichInputMethodSubtype.Companion.get(qwerty))
|
.setSubtype(RichInputMethodSubtype.Companion.get(defaults))
|
||||||
.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey)
|
.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey)
|
||||||
.setNumberRowEnabled(settingsValues.mShowsNumberRow)
|
.setNumberRowEnabled(settingsValues.mShowsNumberRow)
|
||||||
.setLanguageSwitchKeyEnabled(settingsValues.isLanguageSwitchKeyEnabled())
|
.setLanguageSwitchKeyEnabled(settingsValues.isLanguageSwitchKeyEnabled())
|
||||||
|
@ -169,9 +170,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
.setOneHandedModeEnabled(oneHandedModeEnabled)
|
.setOneHandedModeEnabled(oneHandedModeEnabled)
|
||||||
.build();
|
.build();
|
||||||
mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState, oneHandedModeEnabled);
|
mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState, oneHandedModeEnabled);
|
||||||
showToast("error loading the keyboard, falling back to qwerty", false);
|
showToast("error loading the keyboard, falling back to defaults", false);
|
||||||
} catch (KeyboardLayoutSetException e2) {
|
} catch (KeyboardLayoutSetException e2) {
|
||||||
Log.e(TAG, "even fallback to qwerty failed: " + e2.mKeyboardId, e2.getCause());
|
Log.e(TAG, "even fallback to defaults failed: " + e2.mKeyboardId, e2.getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -308,6 +309,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
@NonNull final SettingsValues settingsValues,
|
@NonNull final SettingsValues settingsValues,
|
||||||
@NonNull final KeyboardSwitchState toggleState) {
|
@NonNull final KeyboardSwitchState toggleState) {
|
||||||
final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) ? View.GONE : View.VISIBLE;
|
final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) ? View.GONE : View.VISIBLE;
|
||||||
|
final int stripVisibility = settingsValues.mToolbarMode == ToolbarMode.HIDDEN ? View.GONE : View.VISIBLE;
|
||||||
|
mStripContainer.setVisibility(stripVisibility);
|
||||||
PointerTracker.switchTo(mKeyboardView);
|
PointerTracker.switchTo(mKeyboardView);
|
||||||
mKeyboardView.setVisibility(visibility);
|
mKeyboardView.setVisibility(visibility);
|
||||||
// The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
|
// The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
|
||||||
|
@ -318,7 +321,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
mEmojiPalettesView.stopEmojiPalettes();
|
mEmojiPalettesView.stopEmojiPalettes();
|
||||||
mEmojiTabStripView.setVisibility(View.GONE);
|
mEmojiTabStripView.setVisibility(View.GONE);
|
||||||
mClipboardStripScrollView.setVisibility(View.GONE);
|
mClipboardStripScrollView.setVisibility(View.GONE);
|
||||||
mSuggestionStripView.setVisibility(View.VISIBLE);
|
mSuggestionStripView.setVisibility(stripVisibility);
|
||||||
mClipboardHistoryView.setVisibility(View.GONE);
|
mClipboardHistoryView.setVisibility(View.GONE);
|
||||||
mClipboardHistoryView.stopClipboardHistory();
|
mClipboardHistoryView.stopClipboardHistory();
|
||||||
}
|
}
|
||||||
|
@ -335,6 +338,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
// @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
|
// @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
|
||||||
mKeyboardView.setVisibility(View.GONE);
|
mKeyboardView.setVisibility(View.GONE);
|
||||||
mSuggestionStripView.setVisibility(View.GONE);
|
mSuggestionStripView.setVisibility(View.GONE);
|
||||||
|
mStripContainer.setVisibility(getSecondaryStripVisibility());
|
||||||
mClipboardStripScrollView.setVisibility(View.GONE);
|
mClipboardStripScrollView.setVisibility(View.GONE);
|
||||||
mEmojiTabStripView.setVisibility(View.VISIBLE);
|
mEmojiTabStripView.setVisibility(View.VISIBLE);
|
||||||
mClipboardHistoryView.setVisibility(View.GONE);
|
mClipboardHistoryView.setVisibility(View.GONE);
|
||||||
|
@ -356,6 +360,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
mKeyboardView.setVisibility(View.GONE);
|
mKeyboardView.setVisibility(View.GONE);
|
||||||
mEmojiTabStripView.setVisibility(View.GONE);
|
mEmojiTabStripView.setVisibility(View.GONE);
|
||||||
mSuggestionStripView.setVisibility(View.GONE);
|
mSuggestionStripView.setVisibility(View.GONE);
|
||||||
|
mStripContainer.setVisibility(getSecondaryStripVisibility());
|
||||||
mClipboardStripScrollView.post(() -> mClipboardStripScrollView.fullScroll(HorizontalScrollView.FOCUS_RIGHT));
|
mClipboardStripScrollView.post(() -> mClipboardStripScrollView.fullScroll(HorizontalScrollView.FOCUS_RIGHT));
|
||||||
mClipboardStripScrollView.setVisibility(View.VISIBLE);
|
mClipboardStripScrollView.setVisibility(View.VISIBLE);
|
||||||
mEmojiPalettesView.setVisibility(View.GONE);
|
mEmojiPalettesView.setVisibility(View.GONE);
|
||||||
|
@ -477,10 +482,13 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
// Implements {@link KeyboardState.SwitchActions}.
|
// Implements {@link KeyboardState.SwitchActions}.
|
||||||
@Override
|
@Override
|
||||||
public void setOneHandedModeEnabled(boolean enabled) {
|
public void setOneHandedModeEnabled(boolean enabled) {
|
||||||
if (mKeyboardViewWrapper.getOneHandedModeEnabled() == enabled) {
|
setOneHandedModeEnabled(enabled, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOneHandedModeEnabled(boolean enabled, boolean force) {
|
||||||
|
if (!force && mKeyboardViewWrapper.getOneHandedModeEnabled() == enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mEmojiPalettesView.clearKeyboardCache();
|
|
||||||
final Settings settings = Settings.getInstance();
|
final Settings settings = Settings.getInstance();
|
||||||
mKeyboardViewWrapper.setOneHandedModeEnabled(enabled);
|
mKeyboardViewWrapper.setOneHandedModeEnabled(enabled);
|
||||||
mKeyboardViewWrapper.setOneHandedGravity(settings.getCurrent().mOneHandedModeGravity);
|
mKeyboardViewWrapper.setOneHandedGravity(settings.getCurrent().mOneHandedModeGravity);
|
||||||
|
@ -511,13 +519,20 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
!settings.getCurrent().mIsSplitKeyboardEnabled,
|
!settings.getCurrent().mIsSplitKeyboardEnabled,
|
||||||
mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE
|
mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
);
|
);
|
||||||
|
setOneHandedModeEnabled(settings.getCurrent().mOneHandedModeEnabled, true);
|
||||||
reloadKeyboard();
|
reloadKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadKeyboard() {
|
public void reloadKeyboard() {
|
||||||
if (mCurrentInputView != null)
|
if (mCurrentInputView == null)
|
||||||
loadKeyboard(mLatinIME.getCurrentInputEditorInfo(), Settings.getValues(),
|
return;
|
||||||
mLatinIME.getCurrentAutoCapsState(), mLatinIME.getCurrentRecapitalizeState());
|
mEmojiPalettesView.clearKeyboardCache();
|
||||||
|
reloadMainKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadMainKeyboard() {
|
||||||
|
loadKeyboard(mLatinIME.getCurrentInputEditorInfo(), Settings.getValues(),
|
||||||
|
mLatinIME.getCurrentAutoCapsState(), mLatinIME.getCurrentRecapitalizeState());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -539,6 +554,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getSecondaryStripVisibility() {
|
||||||
|
return Settings.getValues().mSecondaryStripVisible? View.VISIBLE : View.GONE;
|
||||||
|
}
|
||||||
|
|
||||||
// Displays a toast-like message with the provided text for a specified duration.
|
// Displays a toast-like message with the provided text for a specified duration.
|
||||||
private void showFakeToast(final String text, final int timeMillis) {
|
private void showFakeToast(final String text, final int timeMillis) {
|
||||||
if (mFakeToastView.getVisibility() == View.VISIBLE) return;
|
if (mFakeToastView.getVisibility() == View.VISIBLE) return;
|
||||||
|
@ -582,7 +601,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
if (mKeyboardView == null || !mKeyboardView.isShown()) {
|
if (mKeyboardView == null || !mKeyboardView.isShown()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId;
|
final Keyboard keyboard = mKeyboardView.getKeyboard();
|
||||||
|
if (keyboard == null) // may happen when using hardware keyboard
|
||||||
|
return false;
|
||||||
|
int activeKeyboardId = keyboard.mId.mElementId;
|
||||||
for (int keyboardId : keyboardIds) {
|
for (int keyboardId : keyboardIds) {
|
||||||
if (activeKeyboardId == keyboardId) {
|
if (activeKeyboardId == keyboardId) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -606,6 +628,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
return mKeyboardView.isShowingPopupKeysPanel();
|
return mKeyboardView.isShowingPopupKeysPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isShowingStripContainer() {
|
||||||
|
return mStripContainer.isShown();
|
||||||
|
}
|
||||||
|
|
||||||
public View getVisibleKeyboardView() {
|
public View getVisibleKeyboardView() {
|
||||||
if (isShowingEmojiPalettes()) {
|
if (isShowingEmojiPalettes()) {
|
||||||
return mEmojiPalettesView;
|
return mEmojiPalettesView;
|
||||||
|
@ -631,6 +657,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
return mKeyboardView;
|
return mKeyboardView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FrameLayout getStripContainer() { return mStripContainer; }
|
||||||
|
|
||||||
public void deallocateMemory() {
|
public void deallocateMemory() {
|
||||||
if (mKeyboardView != null) {
|
if (mKeyboardView != null) {
|
||||||
mKeyboardView.cancelAllOngoingEvents();
|
mKeyboardView.cancelAllOngoingEvents();
|
||||||
|
@ -644,6 +672,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void trimMemory() {
|
||||||
|
if (mEmojiPalettesView != null) {
|
||||||
|
mEmojiPalettesView.clearKeyboardCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
public View onCreateInputView(@NonNull Context displayContext, final boolean isHardwareAcceleratedDrawingEnabled) {
|
public View onCreateInputView(@NonNull Context displayContext, final boolean isHardwareAcceleratedDrawingEnabled) {
|
||||||
if (mKeyboardView != null) {
|
if (mKeyboardView != null) {
|
||||||
|
@ -678,6 +712,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
mClipboardStripView = mCurrentInputView.findViewById(R.id.clipboard_strip);
|
mClipboardStripView = mCurrentInputView.findViewById(R.id.clipboard_strip);
|
||||||
mClipboardStripScrollView = mCurrentInputView.findViewById(R.id.clipboard_strip_scroll_view);
|
mClipboardStripScrollView = mCurrentInputView.findViewById(R.id.clipboard_strip_scroll_view);
|
||||||
mSuggestionStripView = mCurrentInputView.findViewById(R.id.suggestion_strip_view);
|
mSuggestionStripView = mCurrentInputView.findViewById(R.id.suggestion_strip_view);
|
||||||
|
mStripContainer = mCurrentInputView.findViewById(R.id.strip_container);
|
||||||
|
|
||||||
prefs.registerOnSharedPreferenceChangeListener(mSuggestionStripView);
|
prefs.registerOnSharedPreferenceChangeListener(mSuggestionStripView);
|
||||||
prefs.registerOnSharedPreferenceChangeListener(mClipboardHistoryView);
|
prefs.registerOnSharedPreferenceChangeListener(mClipboardHistoryView);
|
||||||
|
|
|
@ -405,14 +405,7 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnusedThemeName(initialName: String, prefs: SharedPreferences): String {
|
fun getUnusedThemeName(initialName: String, prefs: SharedPreferences): String {
|
||||||
val existingNames = prefs.all.keys.mapNotNull {
|
val existingNames = getExistingThemeNames(prefs)
|
||||||
when {
|
|
||||||
it.startsWith(Settings.PREF_USER_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_COLORS_PREFIX)
|
|
||||||
it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_ALL_COLORS_PREFIX)
|
|
||||||
it.startsWith(Settings.PREF_USER_MORE_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_MORE_COLORS_PREFIX)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.toSortedSet()
|
|
||||||
if (initialName !in existingNames) return initialName
|
if (initialName !in existingNames) return initialName
|
||||||
var i = 1
|
var i = 1
|
||||||
while ("$initialName$i" in existingNames)
|
while ("$initialName$i" in existingNames)
|
||||||
|
@ -420,11 +413,8 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
|
||||||
return "$initialName$i"
|
return "$initialName$i"
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns false if not renamed due to invalid name or collision
|
private fun getExistingThemeNames(prefs: SharedPreferences) =
|
||||||
fun renameUserColors(from: String, to: String, prefs: SharedPreferences): Boolean {
|
prefs.all.keys.mapNotNull {
|
||||||
if (to.isBlank()) return false // don't want that
|
|
||||||
if (to == from) return true // nothing to do
|
|
||||||
val existingNames = prefs.all.keys.mapNotNull {
|
|
||||||
when {
|
when {
|
||||||
it.startsWith(Settings.PREF_USER_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_COLORS_PREFIX)
|
it.startsWith(Settings.PREF_USER_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_COLORS_PREFIX)
|
||||||
it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_ALL_COLORS_PREFIX)
|
it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_ALL_COLORS_PREFIX)
|
||||||
|
@ -432,6 +422,12 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}.toSortedSet()
|
}.toSortedSet()
|
||||||
|
|
||||||
|
// returns false if not renamed due to invalid name or collision
|
||||||
|
fun renameUserColors(from: String, to: String, prefs: SharedPreferences): Boolean {
|
||||||
|
if (to.isBlank()) return false // don't want that
|
||||||
|
if (to == from) return true // nothing to do
|
||||||
|
val existingNames = getExistingThemeNames(prefs)
|
||||||
if (to in existingNames) return false
|
if (to in existingNames) return false
|
||||||
// all good, now rename
|
// all good, now rename
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.view.View;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import helium314.keyboard.keyboard.emoji.EmojiPageKeyboardView;
|
||||||
import helium314.keyboard.keyboard.internal.KeyDrawParams;
|
import helium314.keyboard.keyboard.internal.KeyDrawParams;
|
||||||
import helium314.keyboard.keyboard.internal.KeyVisualAttributes;
|
import helium314.keyboard.keyboard.internal.KeyVisualAttributes;
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||||
|
@ -34,10 +35,10 @@ import helium314.keyboard.latin.R;
|
||||||
import helium314.keyboard.latin.common.ColorType;
|
import helium314.keyboard.latin.common.ColorType;
|
||||||
import helium314.keyboard.latin.common.Colors;
|
import helium314.keyboard.latin.common.Colors;
|
||||||
import helium314.keyboard.latin.common.Constants;
|
import helium314.keyboard.latin.common.Constants;
|
||||||
import helium314.keyboard.latin.common.StringUtils;
|
import helium314.keyboard.latin.common.StringUtilsKt;
|
||||||
import helium314.keyboard.latin.settings.Settings;
|
import helium314.keyboard.latin.settings.Settings;
|
||||||
import helium314.keyboard.latin.suggestions.MoreSuggestions;
|
import helium314.keyboard.latin.suggestions.MoreSuggestions;
|
||||||
import helium314.keyboard.latin.suggestions.PopupSuggestionsView;
|
import helium314.keyboard.latin.suggestions.MoreSuggestionsView;
|
||||||
import helium314.keyboard.latin.utils.TypefaceUtils;
|
import helium314.keyboard.latin.utils.TypefaceUtils;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -108,7 +109,7 @@ public class KeyboardView extends View {
|
||||||
|
|
||||||
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
|
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
|
||||||
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
||||||
if (this instanceof PopupSuggestionsView)
|
if (this instanceof MoreSuggestionsView)
|
||||||
mKeyBackground = mColors.selectAndColorDrawable(keyboardViewAttr, ColorType.MORE_SUGGESTIONS_WORD_BACKGROUND);
|
mKeyBackground = mColors.selectAndColorDrawable(keyboardViewAttr, ColorType.MORE_SUGGESTIONS_WORD_BACKGROUND);
|
||||||
else if (this instanceof PopupKeysKeyboardView)
|
else if (this instanceof PopupKeysKeyboardView)
|
||||||
mKeyBackground = mColors.selectAndColorDrawable(keyboardViewAttr, ColorType.POPUP_KEYS_BACKGROUND);
|
mKeyBackground = mColors.selectAndColorDrawable(keyboardViewAttr, ColorType.POPUP_KEYS_BACKGROUND);
|
||||||
|
@ -147,6 +148,7 @@ public class KeyboardView extends View {
|
||||||
|
|
||||||
mPaint.setAntiAlias(true);
|
mPaint.setAntiAlias(true);
|
||||||
mTypeface = Settings.getInstance().getCustomTypeface();
|
mTypeface = Settings.getInstance().getCustomTypeface();
|
||||||
|
setFitsSystemWindows(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -191,7 +193,8 @@ public class KeyboardView extends View {
|
||||||
invalidateAllKeys();
|
invalidateAllKeys();
|
||||||
requestLayout();
|
requestLayout();
|
||||||
mFontSizeMultiplier = mKeyboard.mId.isEmojiKeyboard()
|
mFontSizeMultiplier = mKeyboard.mId.isEmojiKeyboard()
|
||||||
? Settings.getValues().mFontSizeMultiplierEmoji
|
// In the case of EmojiKeyFit, the size of emojis is taken care of by the size of the keys
|
||||||
|
? (Settings.getValues().mEmojiKeyFit? 1 : Settings.getValues().mFontSizeMultiplierEmoji)
|
||||||
: Settings.getValues().mFontSizeMultiplier;
|
: Settings.getValues().mFontSizeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,10 +426,14 @@ public class KeyboardView extends View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.isEnabled()) {
|
if (key.isEnabled()) {
|
||||||
if (StringUtils.mightBeEmoji(label))
|
if (StringUtilsKt.isEmoji(label))
|
||||||
paint.setColor(key.selectTextColor(params) | 0xFF000000); // ignore alpha for emojis (though actually color isn't applied anyway and we could just set white)
|
paint.setColor(key.selectTextColor(params) | 0xFF000000); // ignore alpha for emojis (though actually color isn't applied anyway and we could just set white)
|
||||||
else if (key.hasActionKeyBackground())
|
else if (key.hasActionKeyBackground())
|
||||||
paint.setColor(mColors.get(ColorType.ACTION_KEY_ICON));
|
paint.setColor(mColors.get(ColorType.ACTION_KEY_ICON));
|
||||||
|
else if (this instanceof EmojiPageKeyboardView)
|
||||||
|
paint.setColor(mColors.get(ColorType.EMOJI_KEY_TEXT));
|
||||||
|
else if (this instanceof PopupKeysKeyboardView)
|
||||||
|
paint.setColor(mColors.get(ColorType.POPUP_KEY_TEXT));
|
||||||
else
|
else
|
||||||
paint.setColor(key.selectTextColor(params));
|
paint.setColor(key.selectTextColor(params));
|
||||||
// Set a drop shadow for the text if the shadow radius is positive value.
|
// Set a drop shadow for the text if the shadow radius is positive value.
|
||||||
|
@ -610,7 +617,7 @@ public class KeyboardView extends View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setKeyIconColor(Key key, Drawable icon, Keyboard keyboard) {
|
private void setKeyIconColor(Key key, Drawable icon, Keyboard keyboard) {
|
||||||
if (key.isAccentColored()) {
|
if (key.hasActionKeyBackground()) {
|
||||||
mColors.setColor(icon, ColorType.ACTION_KEY_ICON);
|
mColors.setColor(icon, ColorType.ACTION_KEY_ICON);
|
||||||
} else if (key.isShift() && keyboard != null) {
|
} else if (key.isShift() && keyboard != null) {
|
||||||
if (keyboard.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED
|
if (keyboard.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED
|
||||||
|
@ -624,8 +631,7 @@ public class KeyboardView extends View {
|
||||||
} else if (key.getBackgroundType() != Key.BACKGROUND_TYPE_NORMAL) {
|
} else if (key.getBackgroundType() != Key.BACKGROUND_TYPE_NORMAL) {
|
||||||
mColors.setColor(icon, ColorType.KEY_ICON);
|
mColors.setColor(icon, ColorType.KEY_ICON);
|
||||||
} else if (this instanceof PopupKeysKeyboardView) {
|
} else if (this instanceof PopupKeysKeyboardView) {
|
||||||
// set color filter for long press comma key, should not trigger anywhere else
|
mColors.setColor(icon, ColorType.POPUP_KEY_ICON);
|
||||||
mColors.setColor(icon, ColorType.KEY_ICON);
|
|
||||||
} else if (key.getCode() == Constants.CODE_SPACE || key.getCode() == KeyCode.ZWNJ) {
|
} else if (key.getCode() == Constants.CODE_SPACE || key.getCode() == KeyCode.ZWNJ) {
|
||||||
// set color of default number pad space bar icon for Holo style, or for zero-width non-joiner (zwnj) on some layouts like nepal
|
// set color of default number pad space bar icon for Holo style, or for zero-width non-joiner (zwnj) on some layouts like nepal
|
||||||
mColors.setColor(icon, ColorType.KEY_ICON);
|
mColors.setColor(icon, ColorType.KEY_ICON);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import android.graphics.Paint;
|
||||||
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Align;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -25,7 +26,6 @@ import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.view.ContextThemeWrapper;
|
|
||||||
|
|
||||||
import helium314.keyboard.accessibility.AccessibilityUtils;
|
import helium314.keyboard.accessibility.AccessibilityUtils;
|
||||||
import helium314.keyboard.accessibility.MainKeyboardAccessibilityDelegate;
|
import helium314.keyboard.accessibility.MainKeyboardAccessibilityDelegate;
|
||||||
|
@ -57,6 +57,7 @@ import helium314.keyboard.latin.settings.Settings;
|
||||||
import helium314.keyboard.latin.utils.KtxKt;
|
import helium314.keyboard.latin.utils.KtxKt;
|
||||||
import helium314.keyboard.latin.utils.LanguageOnSpacebarUtils;
|
import helium314.keyboard.latin.utils.LanguageOnSpacebarUtils;
|
||||||
import helium314.keyboard.latin.utils.Log;
|
import helium314.keyboard.latin.utils.Log;
|
||||||
|
import helium314.keyboard.latin.utils.SubtypeLocaleUtils;
|
||||||
import helium314.keyboard.latin.utils.TypefaceUtils;
|
import helium314.keyboard.latin.utils.TypefaceUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -359,25 +360,21 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
|
||||||
public void onKeyPressed(@NonNull final Key key, final boolean withPreview) {
|
public void onKeyPressed(@NonNull final Key key, final boolean withPreview) {
|
||||||
key.onPressed();
|
key.onPressed();
|
||||||
invalidateKey(key);
|
invalidateKey(key);
|
||||||
if (withPreview && !key.noKeyPreview()) {
|
|
||||||
|
final Keyboard keyboard = getKeyboard();
|
||||||
|
if (keyboard == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mKeyPreviewDrawParams.setVisibleOffset(-keyboard.mVerticalGap);
|
||||||
|
if (withPreview && !key.noKeyPreview() && mKeyPreviewDrawParams.isPopupEnabled()) {
|
||||||
showKeyPreview(key);
|
showKeyPreview(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showKeyPreview(@NonNull final Key key) {
|
private void showKeyPreview(@NonNull final Key key) {
|
||||||
final Keyboard keyboard = getKeyboard();
|
|
||||||
if (keyboard == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
|
|
||||||
if (!previewParams.isPopupEnabled()) {
|
|
||||||
previewParams.setVisibleOffset(-keyboard.mVerticalGap);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
locatePreviewPlacerView();
|
locatePreviewPlacerView();
|
||||||
getLocationInWindow(mOriginCoords);
|
getLocationInWindow(mOriginCoords);
|
||||||
mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(),
|
mKeyPreviewChoreographer.placeAndShowKeyPreview(key, getKeyboard().mIconsSet, getKeyDrawParams(),
|
||||||
KeyboardSwitcher.getInstance().getWrapperView().getWidth(), mOriginCoords, mDrawingPreviewPlacerView);
|
KeyboardSwitcher.getInstance().getWrapperView().getWidth(), mOriginCoords, mDrawingPreviewPlacerView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,7 +502,7 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
|
||||||
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
|
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
final View container = key.hasActionKeyBackground() ? mPopupKeysKeyboardForActionContainer
|
final View container = key.hasActionKeyPopups() ? mPopupKeysKeyboardForActionContainer
|
||||||
: mPopupKeysKeyboardContainer;
|
: mPopupKeysKeyboardContainer;
|
||||||
final PopupKeysKeyboardView popupKeysKeyboardView =
|
final PopupKeysKeyboardView popupKeysKeyboardView =
|
||||||
container.findViewById(R.id.popup_keys_keyboard_view);
|
container.findViewById(R.id.popup_keys_keyboard_view);
|
||||||
|
|
|
@ -328,12 +328,13 @@ public final class PopupKeysKeyboard extends Keyboard {
|
||||||
final PopupKeysKeyboardParams params = mParams;
|
final PopupKeysKeyboardParams params = mParams;
|
||||||
final int popupKeyFlags = mParentKey.getPopupKeyLabelFlags();
|
final int popupKeyFlags = mParentKey.getPopupKeyLabelFlags();
|
||||||
final PopupKeySpec[] popupKeys = mParentKey.getPopupKeys();
|
final PopupKeySpec[] popupKeys = mParentKey.getPopupKeys();
|
||||||
|
final int background = mParentKey.hasActionKeyPopups() ? Key.BACKGROUND_TYPE_ACTION : Key.BACKGROUND_TYPE_NORMAL;
|
||||||
for (int n = 0; n < popupKeys.length; n++) {
|
for (int n = 0; n < popupKeys.length; n++) {
|
||||||
final PopupKeySpec popupKeySpec = popupKeys[n];
|
final PopupKeySpec popupKeySpec = popupKeys[n];
|
||||||
final int row = n / params.mNumColumns;
|
final int row = n / params.mNumColumns;
|
||||||
final int x = params.getX(n, row);
|
final int x = params.getX(n, row);
|
||||||
final int y = params.getY(row);
|
final int y = params.getY(row);
|
||||||
final Key key = popupKeySpec.buildKey(x, y, popupKeyFlags, params);
|
final Key key = popupKeySpec.buildKey(x, y, popupKeyFlags, background, params);
|
||||||
params.markAsEdgeKey(key, row);
|
params.markAsEdgeKey(key, row);
|
||||||
params.onAddKey(key);
|
params.onAddKey(key);
|
||||||
|
|
||||||
|
|
|
@ -12,15 +12,15 @@ import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import helium314.keyboard.accessibility.AccessibilityUtils;
|
import helium314.keyboard.accessibility.AccessibilityUtils;
|
||||||
import helium314.keyboard.accessibility.PopupKeysKeyboardAccessibilityDelegate;
|
import helium314.keyboard.accessibility.PopupKeysKeyboardAccessibilityDelegate;
|
||||||
import helium314.keyboard.keyboard.emoji.OnKeyEventListener;
|
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
|
||||||
import helium314.keyboard.keyboard.internal.KeyDrawParams;
|
import helium314.keyboard.keyboard.internal.KeyDrawParams;
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||||
import helium314.keyboard.latin.R;
|
import helium314.keyboard.latin.R;
|
||||||
|
@ -39,7 +39,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
|
||||||
protected final KeyDetector mKeyDetector;
|
protected final KeyDetector mKeyDetector;
|
||||||
private Controller mController = EMPTY_CONTROLLER;
|
private Controller mController = EMPTY_CONTROLLER;
|
||||||
protected KeyboardActionListener mListener;
|
protected KeyboardActionListener mListener;
|
||||||
protected OnKeyEventListener mKeyEventListener;
|
protected EmojiViewCallback mEmojiViewCallback;
|
||||||
private int mOriginX;
|
private int mOriginX;
|
||||||
private int mOriginY;
|
private int mOriginY;
|
||||||
private Key mCurrentKey;
|
private Key mCurrentKey;
|
||||||
|
@ -122,7 +122,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
|
||||||
public void showPopupKeysPanel(final View parentView, final Controller controller,
|
public void showPopupKeysPanel(final View parentView, final Controller controller,
|
||||||
final int pointX, final int pointY, final KeyboardActionListener listener) {
|
final int pointX, final int pointY, final KeyboardActionListener listener) {
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
mKeyEventListener = null;
|
mEmojiViewCallback = null;
|
||||||
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
|
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,9 +131,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void showPopupKeysPanel(final View parentView, final Controller controller,
|
public void showPopupKeysPanel(final View parentView, final Controller controller,
|
||||||
final int pointX, final int pointY, final OnKeyEventListener listener) {
|
final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) {
|
||||||
mListener = null;
|
mListener = null;
|
||||||
mKeyEventListener = listener;
|
mEmojiViewCallback = emojiViewCallback;
|
||||||
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
|
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +157,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
|
||||||
|
|
||||||
mOriginX = x + container.getPaddingLeft();
|
mOriginX = x + container.getPaddingLeft();
|
||||||
mOriginY = y + container.getPaddingTop();
|
mOriginY = y + container.getPaddingTop();
|
||||||
|
var center = panelX + getMeasuredWidth() / 2;
|
||||||
|
// This is needed for cases where there's also a long text popup above this keyboard
|
||||||
|
controller.setLayoutGravity(center < pointX? Gravity.RIGHT : center > pointX? Gravity.LEFT : Gravity.CENTER_HORIZONTAL);
|
||||||
controller.onShowPopupKeysPanel(this);
|
controller.onShowPopupKeysPanel(this);
|
||||||
final PopupKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
|
final PopupKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
|
||||||
if (accessibilityDelegate != null
|
if (accessibilityDelegate != null
|
||||||
|
@ -222,8 +225,8 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
|
||||||
false /* isKeyRepeat */);
|
false /* isKeyRepeat */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (mKeyEventListener != null) {
|
} else if (mEmojiViewCallback != null) {
|
||||||
mKeyEventListener.onReleaseKey(key);
|
mEmojiViewCallback.onReleaseKey(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,28 +317,4 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
|
||||||
}
|
}
|
||||||
return super.onHoverEvent(event);
|
return super.onHoverEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private View getContainerView() {
|
|
||||||
return (View)getParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showInParent(final ViewGroup parentView) {
|
|
||||||
removeFromParent();
|
|
||||||
parentView.addView(getContainerView());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeFromParent() {
|
|
||||||
final View containerView = getContainerView();
|
|
||||||
final ViewGroup currentParent = (ViewGroup)containerView.getParent();
|
|
||||||
if (currentParent != null) {
|
|
||||||
currentParent.removeView(containerView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isShowingInParent() {
|
|
||||||
return (getContainerView().getParent() != null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,17 @@ package helium314.keyboard.keyboard;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import helium314.keyboard.keyboard.emoji.OnKeyEventListener;
|
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
|
||||||
|
|
||||||
public interface PopupKeysPanel {
|
public interface PopupKeysPanel {
|
||||||
interface Controller {
|
interface Controller {
|
||||||
|
/**
|
||||||
|
* Set the layout gravity.
|
||||||
|
* @param layoutGravity requested by the popup
|
||||||
|
*/
|
||||||
|
default void setLayoutGravity(int layoutGravity) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the {@link PopupKeysPanel} to the target view.
|
* Add the {@link PopupKeysPanel} to the target view.
|
||||||
* @param panel the panel to be shown.
|
* @param panel the panel to be shown.
|
||||||
|
@ -59,19 +66,18 @@ public interface PopupKeysPanel {
|
||||||
* Initializes the layout and event handling of this {@link PopupKeysPanel} and calls the
|
* Initializes the layout and event handling of this {@link PopupKeysPanel} and calls the
|
||||||
* controller's onShowPopupKeysPanel to add the panel's container view.
|
* controller's onShowPopupKeysPanel to add the panel's container view.
|
||||||
* Same as {@link PopupKeysPanel#showPopupKeysPanel(View, Controller, int, int, KeyboardActionListener)},
|
* Same as {@link PopupKeysPanel#showPopupKeysPanel(View, Controller, int, int, KeyboardActionListener)},
|
||||||
* but with a {@link OnKeyEventListener}.
|
* but with a {@link EmojiViewCallback}.
|
||||||
*
|
*
|
||||||
* @param parentView the parent view of this {@link PopupKeysPanel}
|
* @param parentView the parent view of this {@link PopupKeysPanel}
|
||||||
* @param controller the controller that can dismiss this {@link PopupKeysPanel}
|
* @param controller the controller that can dismiss this {@link PopupKeysPanel}
|
||||||
* @param pointX x coordinate of this {@link PopupKeysPanel}
|
* @param pointX x coordinate of this {@link PopupKeysPanel}
|
||||||
* @param pointY y coordinate of this {@link PopupKeysPanel}
|
* @param pointY y coordinate of this {@link PopupKeysPanel}
|
||||||
* @param listener the listener that will receive keyboard action from this
|
* @param emojiViewCallback to receive keyboard actions from this {@link PopupKeysPanel}.
|
||||||
* {@link PopupKeysPanel}.
|
|
||||||
*/
|
*/
|
||||||
// TODO: Currently the PopupKeysPanel is inside a container view that is added to the parent.
|
// TODO: Currently the PopupKeysPanel is inside a container view that is added to the parent.
|
||||||
// Consider the simpler approach of placing the PopupKeysPanel itself into the parent view.
|
// Consider the simpler approach of placing the PopupKeysPanel itself into the parent view.
|
||||||
void showPopupKeysPanel(View parentView, Controller controller, int pointX,
|
void showPopupKeysPanel(View parentView, Controller controller, int pointX,
|
||||||
int pointY, OnKeyEventListener listener);
|
int pointY, EmojiViewCallback emojiViewCallback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dismisses the popup keys panel and calls the controller's onDismissPopupKeysPanel to remove
|
* Dismisses the popup keys panel and calls the controller's onDismissPopupKeysPanel to remove
|
||||||
|
@ -127,20 +133,35 @@ public interface PopupKeysPanel {
|
||||||
*/
|
*/
|
||||||
int translateY(int y);
|
int translateY(int y);
|
||||||
|
|
||||||
|
default View getContainerView() {
|
||||||
|
return (View) ((View) this).getParent();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show this {@link PopupKeysPanel} in the parent view.
|
* Show this {@link PopupKeysPanel} in the parent view.
|
||||||
*
|
*
|
||||||
* @param parentView the {@link ViewGroup} that hosts this {@link PopupKeysPanel}.
|
* @param parentView the {@link ViewGroup} that hosts this {@link PopupKeysPanel}.
|
||||||
*/
|
*/
|
||||||
void showInParent(ViewGroup parentView);
|
default void showInParent(ViewGroup parentView) {
|
||||||
|
removeFromParent();
|
||||||
|
parentView.addView(getContainerView());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove this {@link PopupKeysPanel} from the parent view.
|
* Remove this {@link PopupKeysPanel} from the parent view.
|
||||||
*/
|
*/
|
||||||
void removeFromParent();
|
default void removeFromParent() {
|
||||||
|
final View containerView = getContainerView();
|
||||||
|
final ViewGroup currentParent = (ViewGroup)containerView.getParent();
|
||||||
|
if (currentParent != null) {
|
||||||
|
currentParent.removeView(containerView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the panel is currently being shown.
|
* Return whether the panel is currently being shown.
|
||||||
*/
|
*/
|
||||||
boolean isShowingInParent();
|
default boolean isShowingInParent() {
|
||||||
|
return getContainerView().getParent() != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
121
app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java
Normal file
121
app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
* modified
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package helium314.keyboard.keyboard;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
|
||||||
|
import helium314.keyboard.keyboard.internal.KeyDrawParams;
|
||||||
|
import helium314.keyboard.latin.R;
|
||||||
|
import helium314.keyboard.latin.common.ColorType;
|
||||||
|
import helium314.keyboard.latin.common.CoordinateUtils;
|
||||||
|
import helium314.keyboard.latin.settings.Settings;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A view that displays popup text.
|
||||||
|
*/
|
||||||
|
public class PopupTextView extends TextView implements PopupKeysPanel {
|
||||||
|
private final int[] mCoordinates = CoordinateUtils.newInstance();
|
||||||
|
private final Typeface mTypeface;
|
||||||
|
private Controller mController = EMPTY_CONTROLLER;
|
||||||
|
private int mOriginX;
|
||||||
|
private int mOriginY;
|
||||||
|
private Key mKey;
|
||||||
|
private EmojiViewCallback mEmojiViewCallback;
|
||||||
|
|
||||||
|
public PopupTextView(final Context context, final AttributeSet attrs) {
|
||||||
|
this(context, attrs, R.attr.popupKeysKeyboardViewStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupTextView(final Context context, final AttributeSet attrs,
|
||||||
|
final int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
mTypeface = Settings.getInstance().getCustomTypeface();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyDrawParams(Key key, KeyDrawParams drawParams) {
|
||||||
|
mKey = key;
|
||||||
|
Settings.getValues().mColors.setBackground(this, ColorType.KEY_PREVIEW_BACKGROUND);
|
||||||
|
setTextColor(drawParams.mPreviewTextColor);
|
||||||
|
setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectHintTextSize(drawParams) << 1);
|
||||||
|
setTypeface(mTypeface == null ? key.selectTypeface(drawParams) : mTypeface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showPopupKeysPanel(final View parentView, final Controller controller,
|
||||||
|
final int pointX, final int pointY, final KeyboardActionListener listener) {
|
||||||
|
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showPopupKeysPanel(final View parentView, final Controller controller,
|
||||||
|
final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) {
|
||||||
|
mEmojiViewCallback = emojiViewCallback;
|
||||||
|
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPopupKeysPanelInternal(final View parentView, final Controller controller,
|
||||||
|
final int pointX, final int pointY) {
|
||||||
|
mController = controller;
|
||||||
|
final View container = getContainerView();
|
||||||
|
// The coordinates of panel's left-top corner in parentView's coordinate system.
|
||||||
|
// We need to consider background drawable paddings.
|
||||||
|
final int x = pointX - getMeasuredWidth() / 2 - container.getPaddingLeft() - getPaddingLeft();
|
||||||
|
final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
|
||||||
|
+ getPaddingBottom();
|
||||||
|
|
||||||
|
parentView.getLocationInWindow(mCoordinates);
|
||||||
|
// Ensure the horizontal position of the panel does not extend past the parentView edges.
|
||||||
|
final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
|
||||||
|
final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
|
||||||
|
final int panelY = y + CoordinateUtils.y(mCoordinates);
|
||||||
|
container.setX(panelX);
|
||||||
|
container.setY(panelY);
|
||||||
|
|
||||||
|
mOriginX = x + container.getPaddingLeft();
|
||||||
|
mOriginY = y + container.getPaddingTop();
|
||||||
|
controller.setLayoutGravity(Gravity.NO_GRAVITY);
|
||||||
|
controller.onShowPopupKeysPanel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
|
||||||
|
mEmojiViewCallback.onReleaseKey(mKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dismissPopupKeysPanel() {
|
||||||
|
if (!isShowingInParent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mController.onDismissPopupKeysPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int translateX(final int x) {
|
||||||
|
return x - mOriginX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int translateY(final int y) {
|
||||||
|
return y - mOriginY;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,6 @@ import helium314.keyboard.keyboard.MainKeyboardView
|
||||||
import helium314.keyboard.keyboard.PointerTracker
|
import helium314.keyboard.keyboard.PointerTracker
|
||||||
import helium314.keyboard.keyboard.internal.KeyDrawParams
|
import helium314.keyboard.keyboard.internal.KeyDrawParams
|
||||||
import helium314.keyboard.keyboard.internal.KeyVisualAttributes
|
import helium314.keyboard.keyboard.internal.KeyVisualAttributes
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
||||||
import helium314.keyboard.latin.ClipboardHistoryManager
|
import helium314.keyboard.latin.ClipboardHistoryManager
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
|
@ -49,15 +48,14 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
private val clipboardLayoutParams = ClipboardLayoutParams(context)
|
private val clipboardLayoutParams = ClipboardLayoutParams(context)
|
||||||
private val pinIconId: Int
|
private val pinIconId: Int
|
||||||
private val keyBackgroundId: Int
|
private val keyBackgroundId: Int
|
||||||
private var initialized = false
|
|
||||||
|
|
||||||
private lateinit var clipboardRecyclerView: ClipboardHistoryRecyclerView
|
private lateinit var clipboardRecyclerView: ClipboardHistoryRecyclerView
|
||||||
private lateinit var placeholderView: TextView
|
private lateinit var placeholderView: TextView
|
||||||
private val toolbarKeys = mutableListOf<ImageButton>()
|
private val toolbarKeys = mutableListOf<ImageButton>()
|
||||||
private lateinit var clipboardAdapter: ClipboardAdapter
|
private lateinit var clipboardAdapter: ClipboardAdapter
|
||||||
|
|
||||||
var keyboardActionListener: KeyboardActionListener? = null
|
lateinit var keyboardActionListener: KeyboardActionListener
|
||||||
var clipboardHistoryManager: ClipboardHistoryManager? = null
|
private var clipboardHistoryManager: ClipboardHistoryManager? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val clipboardViewAttr = context.obtainStyledAttributes(attrs,
|
val clipboardViewAttr = context.obtainStyledAttributes(attrs,
|
||||||
|
@ -67,10 +65,11 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
val keyboardViewAttr = context.obtainStyledAttributes(attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView)
|
val keyboardViewAttr = context.obtainStyledAttributes(attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView)
|
||||||
keyBackgroundId = keyboardViewAttr.getResourceId(R.styleable.KeyboardView_keyBackground, 0)
|
keyBackgroundId = keyboardViewAttr.getResourceId(R.styleable.KeyboardView_keyBackground, 0)
|
||||||
keyboardViewAttr.recycle()
|
keyboardViewAttr.recycle()
|
||||||
val keyboardAttr = context.obtainStyledAttributes(attrs, R.styleable.Keyboard, defStyle, R.style.SuggestionStripView)
|
if (Settings.getValues().mSecondaryStripVisible) {
|
||||||
getEnabledClipboardToolbarKeys(context.prefs())
|
getEnabledClipboardToolbarKeys(context.prefs())
|
||||||
.forEach { toolbarKeys.add(createToolbarKey(context, KeyboardIconsSet.instance, it)) }
|
.forEach { toolbarKeys.add(createToolbarKey(context, it)) }
|
||||||
keyboardAttr.recycle()
|
}
|
||||||
|
fitsSystemWindows = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
@ -78,13 +77,13 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
val res = context.resources
|
val res = context.resources
|
||||||
// The main keyboard expands to the entire this {@link KeyboardView}.
|
// The main keyboard expands to the entire this {@link KeyboardView}.
|
||||||
val width = ResourceUtils.getKeyboardWidth(context, Settings.getValues()) + paddingLeft + paddingRight
|
val width = ResourceUtils.getKeyboardWidth(context, Settings.getValues()) + paddingLeft + paddingRight
|
||||||
val height = ResourceUtils.getKeyboardHeight(res, Settings.getValues()) + paddingTop + paddingBottom
|
val height = ResourceUtils.getSecondaryKeyboardHeight(res, Settings.getValues()) + paddingTop + paddingBottom
|
||||||
setMeasuredDimension(width, height)
|
setMeasuredDimension(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun initialize() { // needs to be delayed for access to ClipboardStrip, which is not a child of this view
|
private fun initialize() { // needs to be delayed for access to ClipboardStrip, which is not a child of this view
|
||||||
if (initialized) return
|
if (this::clipboardAdapter.isInitialized) return
|
||||||
val colors = Settings.getValues().mColors
|
val colors = Settings.getValues().mColors
|
||||||
clipboardAdapter = ClipboardAdapter(clipboardLayoutParams, this).apply {
|
clipboardAdapter = ClipboardAdapter(clipboardLayoutParams, this).apply {
|
||||||
itemBackgroundId = keyBackgroundId
|
itemBackgroundId = keyBackgroundId
|
||||||
|
@ -107,7 +106,6 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
colors.setColor(it, ColorType.TOOL_BAR_KEY)
|
colors.setColor(it, ColorType.TOOL_BAR_KEY)
|
||||||
colors.setBackground(it, ColorType.STRIP_BACKGROUND)
|
colors.setBackground(it, ColorType.STRIP_BACKGROUND)
|
||||||
}
|
}
|
||||||
initialized = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupClipKey(params: KeyDrawParams) {
|
private fun setupClipKey(params: KeyDrawParams) {
|
||||||
|
@ -188,7 +186,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopClipboardHistory() {
|
fun stopClipboardHistory() {
|
||||||
if (!initialized) return
|
if (!this::clipboardAdapter.isInitialized) return
|
||||||
clipboardRecyclerView.adapter = null
|
clipboardRecyclerView.adapter = null
|
||||||
clipboardHistoryManager?.setHistoryChangeListener(null)
|
clipboardHistoryManager?.setHistoryChangeListener(null)
|
||||||
clipboardHistoryManager = null
|
clipboardHistoryManager = null
|
||||||
|
@ -200,7 +198,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
if (tag is ToolbarKey) {
|
if (tag is ToolbarKey) {
|
||||||
val code = getCodeForToolbarKey(tag)
|
val code = getCodeForToolbarKey(tag)
|
||||||
if (code != KeyCode.UNSPECIFIED) {
|
if (code != KeyCode.UNSPECIFIED) {
|
||||||
keyboardActionListener?.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
keyboardActionListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +209,7 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
if (tag is ToolbarKey) {
|
if (tag is ToolbarKey) {
|
||||||
val longClickCode = getCodeForToolbarKeyLongClick(tag)
|
val longClickCode = getCodeForToolbarKeyLongClick(tag)
|
||||||
if (longClickCode != KeyCode.UNSPECIFIED) {
|
if (longClickCode != KeyCode.UNSPECIFIED) {
|
||||||
keyboardActionListener?.onCodeInput(
|
keyboardActionListener.onCodeInput(
|
||||||
longClickCode,
|
longClickCode,
|
||||||
Constants.NOT_A_COORDINATE,
|
Constants.NOT_A_COORDINATE,
|
||||||
Constants.NOT_A_COORDINATE,
|
Constants.NOT_A_COORDINATE,
|
||||||
|
@ -224,15 +222,15 @@ class ClipboardHistoryView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(clipId: Long) {
|
override fun onKeyDown(clipId: Long) {
|
||||||
keyboardActionListener?.onPressKey(KeyCode.NOT_SPECIFIED, 0, true)
|
keyboardActionListener.onPressKey(KeyCode.NOT_SPECIFIED, 0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyUp(clipId: Long) {
|
override fun onKeyUp(clipId: Long) {
|
||||||
val clipContent = clipboardHistoryManager?.getHistoryEntryContent(clipId)
|
val clipContent = clipboardHistoryManager?.getHistoryEntryContent(clipId)
|
||||||
keyboardActionListener?.onTextInput(clipContent?.content.toString())
|
keyboardActionListener.onTextInput(clipContent?.content.toString())
|
||||||
keyboardActionListener?.onReleaseKey(KeyCode.NOT_SPECIFIED, false)
|
keyboardActionListener.onReleaseKey(KeyCode.NOT_SPECIFIED, false)
|
||||||
if (Settings.getValues().mAlphaAfterClipHistoryEntry)
|
if (Settings.getValues().mAlphaAfterClipHistoryEntry)
|
||||||
keyboardActionListener?.onCodeInput(KeyCode.ALPHA, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
keyboardActionListener.onCodeInput(KeyCode.ALPHA, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClipboardHistoryEntryAdded(at: Int) {
|
override fun onClipboardHistoryEntryAdded(at: Int) {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
package helium314.keyboard.keyboard.clipboard
|
package helium314.keyboard.keyboard.clipboard
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -22,7 +21,7 @@ class ClipboardLayoutParams(ctx: Context) {
|
||||||
init {
|
init {
|
||||||
val res = ctx.resources
|
val res = ctx.resources
|
||||||
val sv = Settings.getValues()
|
val sv = Settings.getValues()
|
||||||
val defaultKeyboardHeight = ResourceUtils.getKeyboardHeight(res, sv)
|
val defaultKeyboardHeight = ResourceUtils.getSecondaryKeyboardHeight(res, sv)
|
||||||
val defaultKeyboardWidth = ResourceUtils.getKeyboardWidth(ctx, sv)
|
val defaultKeyboardWidth = ResourceUtils.getKeyboardWidth(ctx, sv)
|
||||||
|
|
||||||
if (sv.mNarrowKeyGaps) {
|
if (sv.mNarrowKeyGaps) {
|
||||||
|
@ -64,4 +63,4 @@ class ClipboardLayoutParams(ctx: Context) {
|
||||||
view.layoutParams = this
|
view.layoutParams = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import static helium314.keyboard.keyboard.internal.keyboard_parser.EmojiParserKt
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import helium314.keyboard.latin.common.Constants;
|
||||||
import helium314.keyboard.latin.settings.Defaults;
|
import helium314.keyboard.latin.settings.Defaults;
|
||||||
import helium314.keyboard.latin.utils.Log;
|
import helium314.keyboard.latin.utils.Log;
|
||||||
|
|
||||||
|
@ -34,8 +35,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
final class DynamicGridKeyboard extends Keyboard {
|
final class DynamicGridKeyboard extends Keyboard {
|
||||||
private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
|
private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
|
||||||
private static final int TEMPLATE_KEY_CODE_0 = 0x30;
|
|
||||||
private static final int TEMPLATE_KEY_CODE_1 = 0x31;
|
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
private final SharedPreferences mPrefs;
|
private final SharedPreferences mPrefs;
|
||||||
|
@ -60,8 +59,8 @@ final class DynamicGridKeyboard extends Keyboard {
|
||||||
mBaseWidth = width - paddingWidth;
|
mBaseWidth = width - paddingWidth;
|
||||||
mOccupiedWidth = width;
|
mOccupiedWidth = width;
|
||||||
final float spacerWidth = Settings.getValues().mSplitKeyboardSpacerRelativeWidth * mBaseWidth;
|
final float spacerWidth = Settings.getValues().mSplitKeyboardSpacerRelativeWidth * mBaseWidth;
|
||||||
final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
|
final Key key0 = getTemplateKey(Constants.RECENTS_TEMPLATE_KEY_CODE_0);
|
||||||
final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
|
final Key key1 = getTemplateKey(Constants.RECENTS_TEMPLATE_KEY_CODE_1);
|
||||||
final int horizontalGap = Math.abs(key1.getX() - key0.getX()) - key0.getWidth();
|
final int horizontalGap = Math.abs(key1.getX() - key0.getX()) - key0.getWidth();
|
||||||
final float widthScale = determineWidthScale(key0.getWidth() + horizontalGap);
|
final float widthScale = determineWidthScale(key0.getWidth() + horizontalGap);
|
||||||
mHorizontalGap = (int) (horizontalGap * widthScale);
|
mHorizontalGap = (int) (horizontalGap * widthScale);
|
||||||
|
@ -113,12 +112,12 @@ final class DynamicGridKeyboard extends Keyboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDynamicOccupiedHeight() {
|
public int getDynamicOccupiedHeight() {
|
||||||
final int row = (mGridKeys.size() - 1) / mColumnsNum + 1;
|
final int row = (mGridKeys.size() - 1) / getOccupiedColumnCount() + 1;
|
||||||
return row * mVerticalStep;
|
return row * mVerticalStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColumnsCount() {
|
public int getOccupiedColumnCount() {
|
||||||
return mColumnsNum;
|
return mColumnsNum - mEmptyColumnIndices.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPendingKey(final Key usedKey) {
|
public void addPendingKey(final Key usedKey) {
|
||||||
|
@ -213,7 +212,7 @@ final class DynamicGridKeyboard extends Keyboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fall back to creating the key
|
// fall back to creating the key
|
||||||
return new Key(getTemplateKey(TEMPLATE_KEY_CODE_0), null, null, Key.BACKGROUND_TYPE_EMPTY, code, null);
|
return new Key(getTemplateKey(Constants.RECENTS_TEMPLATE_KEY_CODE_0), null, null, Key.BACKGROUND_TYPE_EMPTY, code, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards,
|
private Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards,
|
||||||
|
@ -227,7 +226,7 @@ final class DynamicGridKeyboard extends Keyboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fall back to creating the key
|
// fall back to creating the key
|
||||||
return new Key(getTemplateKey(TEMPLATE_KEY_CODE_0), null, null, Key.BACKGROUND_TYPE_EMPTY, 0, outputText);
|
return new Key(getTemplateKey(Constants.RECENTS_TEMPLATE_KEY_CODE_0), null, null, Key.BACKGROUND_TYPE_EMPTY, 0, outputText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) {
|
public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) {
|
||||||
|
|
|
@ -261,20 +261,6 @@ final class EmojiCategory {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the view pager's page position for the categoryId
|
|
||||||
public int getPagerPageIdFromCategoryAndPageId(final int categoryId, final int categoryPageId) {
|
|
||||||
int sum = 0;
|
|
||||||
for (int i = 0; i < mShownCategories.size(); ++i) {
|
|
||||||
final CategoryProperties props = mShownCategories.get(i);
|
|
||||||
if (props.mCategoryId == categoryId) {
|
|
||||||
return sum + categoryPageId;
|
|
||||||
}
|
|
||||||
sum += props.getPageCount();
|
|
||||||
}
|
|
||||||
Log.w(TAG, "categoryId not found: " + categoryId);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRecentTabId() {
|
public int getRecentTabId() {
|
||||||
return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS);
|
return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS);
|
||||||
}
|
}
|
||||||
|
@ -285,11 +271,11 @@ final class EmojiCategory {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a keyboard from the recycler view's adapter position.
|
// Returns a keyboard from the recycler view's adapter position.
|
||||||
public DynamicGridKeyboard getKeyboardFromAdapterPosition(final int position) {
|
public DynamicGridKeyboard getKeyboardFromAdapterPosition(int categoryId, final int position) {
|
||||||
if (position >= 0 && position < getCurrentCategoryPageCount()) {
|
if (position >= 0 && position < getCategoryPageCount(categoryId)) {
|
||||||
return getKeyboard(mCurrentCategoryId, position);
|
return getKeyboard(categoryId, position);
|
||||||
}
|
}
|
||||||
Log.w(TAG, "invalid position for categoryId : " + mCurrentCategoryId);
|
Log.w(TAG, "invalid position for categoryId : " + categoryId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +324,7 @@ final class EmojiCategory {
|
||||||
final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
|
final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
|
||||||
mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
|
mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
|
||||||
0, 0, ResourceUtils.getKeyboardWidth(mContext, Settings.getValues()));
|
0, 0, ResourceUtils.getKeyboardWidth(mContext, Settings.getValues()));
|
||||||
return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getColumnsCount();
|
return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getOccupiedColumnCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Comparator<Key> EMOJI_KEY_COMPARATOR = (lhs, rhs) -> {
|
private static final Comparator<Key> EMOJI_KEY_COMPARATOR = (lhs, rhs) -> {
|
||||||
|
|
|
@ -8,7 +8,7 @@ package helium314.keyboard.keyboard.emoji
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
@ -22,7 +22,7 @@ internal class EmojiLayoutParams(res: Resources) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val sv = Settings.getValues()
|
val sv = Settings.getValues()
|
||||||
val defaultKeyboardHeight = ResourceUtils.getKeyboardHeight(res, sv)
|
val defaultKeyboardHeight = ResourceUtils.getSecondaryKeyboardHeight(res, sv)
|
||||||
|
|
||||||
val keyVerticalGap = if (sv.mNarrowKeyGaps) {
|
val keyVerticalGap = if (sv.mNarrowKeyGaps) {
|
||||||
res.getFraction(R.fraction.config_key_vertical_gap_holo_narrow,
|
res.getFraction(R.fraction.config_key_vertical_gap_holo_narrow,
|
||||||
|
@ -47,7 +47,7 @@ internal class EmojiLayoutParams(res: Resources) {
|
||||||
emojiKeyboardHeight = emojiListHeight - emojiCategoryPageIdViewHeight - emojiListBottomMargin
|
emojiKeyboardHeight = emojiListHeight - emojiCategoryPageIdViewHeight - emojiListBottomMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setEmojiListProperties(vp: RecyclerView) {
|
fun setEmojiListProperties(vp: ViewPager2) {
|
||||||
val lp = vp.layoutParams as LinearLayout.LayoutParams
|
val lp = vp.layoutParams as LinearLayout.LayoutParams
|
||||||
lp.height = emojiKeyboardHeight
|
lp.height = emojiKeyboardHeight
|
||||||
lp.bottomMargin = emojiListBottomMargin
|
lp.bottomMargin = emojiListBottomMargin
|
||||||
|
|
|
@ -13,6 +13,9 @@ import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import helium314.keyboard.keyboard.PopupTextView;
|
||||||
import helium314.keyboard.latin.utils.Log;
|
import helium314.keyboard.latin.utils.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
@ -53,14 +56,18 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
private static final long KEY_PRESS_DELAY_TIME = 250; // msec
|
private static final long KEY_PRESS_DELAY_TIME = 250; // msec
|
||||||
private static final long KEY_RELEASE_DELAY_TIME = 30; // msec
|
private static final long KEY_RELEASE_DELAY_TIME = 30; // msec
|
||||||
|
|
||||||
private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() {
|
private static final EmojiViewCallback EMPTY_EMOJI_VIEW_CALLBACK = new EmojiViewCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onPressKey(final Key key) {}
|
public void onPressKey(final Key key) {}
|
||||||
@Override
|
@Override
|
||||||
public void onReleaseKey(final Key key) {}
|
public void onReleaseKey(final Key key) {}
|
||||||
|
@Override
|
||||||
|
public String getDescription(String emoji) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private OnKeyEventListener mListener = EMPTY_LISTENER;
|
private EmojiViewCallback mEmojiViewCallback = EMPTY_EMOJI_VIEW_CALLBACK;
|
||||||
private final KeyDetector mKeyDetector = new KeyDetector();
|
private final KeyDetector mKeyDetector = new KeyDetector();
|
||||||
private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
|
private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
|
||||||
|
|
||||||
|
@ -74,6 +81,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
|
|
||||||
// More keys keyboard
|
// More keys keyboard
|
||||||
private final View mPopupKeysKeyboardContainer;
|
private final View mPopupKeysKeyboardContainer;
|
||||||
|
private final PopupTextView mDescriptionView;
|
||||||
|
private final PopupKeysKeyboardView mPopupKeysKeyboardView;
|
||||||
private final WeakHashMap<Key, Keyboard> mPopupKeysKeyboardCache = new WeakHashMap<>();
|
private final WeakHashMap<Key, Keyboard> mPopupKeysKeyboardCache = new WeakHashMap<>();
|
||||||
private final boolean mConfigShowPopupKeysKeyboardAtTouchedPoint;
|
private final boolean mConfigShowPopupKeysKeyboardAtTouchedPoint;
|
||||||
private final ViewGroup mPopupKeysPlacerView;
|
private final ViewGroup mPopupKeysPlacerView;
|
||||||
|
@ -102,6 +111,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
mPopupKeysKeyboardContainer = inflater.inflate(popupKeysKeyboardLayoutId, null);
|
mPopupKeysKeyboardContainer = inflater.inflate(popupKeysKeyboardLayoutId, null);
|
||||||
|
mDescriptionView = mPopupKeysKeyboardContainer.findViewById(R.id.description_view);
|
||||||
|
mPopupKeysKeyboardView = mPopupKeysKeyboardContainer.findViewById(R.id.popup_keys_keyboard_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,8 +157,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnKeyEventListener(final OnKeyEventListener listener) {
|
public void setEmojiViewCallback(final EmojiViewCallback emojiViewCallback) {
|
||||||
mListener = listener;
|
mEmojiViewCallback = emojiViewCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,7 +180,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, final int lastX, final int lastY) {
|
private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key) {
|
||||||
|
mPopupKeysKeyboardView.setVisibility(GONE);
|
||||||
final PopupKeySpec[] popupKeys = key.getPopupKeys();
|
final PopupKeySpec[] popupKeys = key.getPopupKeys();
|
||||||
if (popupKeys == null) {
|
if (popupKeys == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -182,21 +194,9 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
|
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
final View container = mPopupKeysKeyboardContainer;
|
mPopupKeysKeyboardView.setKeyboard(popupKeysKeyboard);
|
||||||
final PopupKeysKeyboardView popupKeysKeyboardView = container.findViewById(R.id.popup_keys_keyboard_view);
|
mPopupKeysKeyboardView.setVisibility(VISIBLE);
|
||||||
popupKeysKeyboardView.setKeyboard(popupKeysKeyboard);
|
return mPopupKeysKeyboardView;
|
||||||
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
||||||
|
|
||||||
final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, lastX, lastY);
|
|
||||||
// The popup keys keyboard is usually horizontally aligned with the center of the parent key.
|
|
||||||
// If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
|
|
||||||
// keys keyboard is placed at the touch point of the parent key.
|
|
||||||
final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint
|
|
||||||
? CoordinateUtils.x(lastCoords)
|
|
||||||
: key.getX() + key.getWidth() / 2;
|
|
||||||
final int pointY = key.getY();
|
|
||||||
popupKeysKeyboardView.showPopupKeysPanel(this, this, pointX, pointY, mListener);
|
|
||||||
return popupKeysKeyboardView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dismissPopupKeysPanel() {
|
private void dismissPopupKeysPanel() {
|
||||||
|
@ -209,6 +209,17 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
return mPopupKeysPanel != null;
|
return mPopupKeysPanel != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLayoutGravity(int layoutGravity) {
|
||||||
|
var layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
layoutParams.gravity = mDescriptionView.getMeasuredWidth() > mPopupKeysKeyboardView.getMeasuredWidth()?
|
||||||
|
layoutGravity : Gravity.CENTER_HORIZONTAL;
|
||||||
|
mPopupKeysKeyboardContainer.setLayoutParams(layoutParams);
|
||||||
|
mDescriptionView.setLayoutParams(layoutParams);
|
||||||
|
mPopupKeysKeyboardView.setLayoutParams(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onShowPopupKeysPanel(final PopupKeysPanel panel) {
|
public void onShowPopupKeysPanel(final PopupKeysPanel panel) {
|
||||||
// install placer view only when needed instead of when this
|
// install placer view only when needed instead of when this
|
||||||
|
@ -290,9 +301,11 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var descriptionPanel = showDescription(key);
|
||||||
|
final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key);
|
||||||
|
|
||||||
final int x = mLastX;
|
final int x = mLastX;
|
||||||
final int y = mLastY;
|
final int y = mLastY;
|
||||||
final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key, x, y);
|
|
||||||
if (popupKeysPanel != null) {
|
if (popupKeysPanel != null) {
|
||||||
final int translatedX = popupKeysPanel.translateX(x);
|
final int translatedX = popupKeysPanel.translateX(x);
|
||||||
final int translatedY = popupKeysPanel.translateY(y);
|
final int translatedY = popupKeysPanel.translateY(y);
|
||||||
|
@ -301,6 +314,34 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
// want any scroll to append during this entire input.
|
// want any scroll to append during this entire input.
|
||||||
disallowParentInterceptTouchEvent(true);
|
disallowParentInterceptTouchEvent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (popupKeysPanel != null || descriptionPanel != null) {
|
||||||
|
mPopupKeysKeyboardContainer.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
|
||||||
|
final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, x, y);
|
||||||
|
// The popup keys keyboard is usually horizontally aligned with the center of the parent key.
|
||||||
|
// If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
|
||||||
|
// keys keyboard is placed at the touch point of the parent key.
|
||||||
|
final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint
|
||||||
|
? CoordinateUtils.x(lastCoords)
|
||||||
|
: key.getX() + key.getWidth() / 2;
|
||||||
|
final int pointY = key.getY() - getKeyboard().mVerticalGap;
|
||||||
|
(popupKeysPanel != null? popupKeysPanel : descriptionPanel)
|
||||||
|
.showPopupKeysPanel(this, this, pointX, pointY, mEmojiViewCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PopupKeysPanel showDescription(Key key) {
|
||||||
|
mDescriptionView.setVisibility(GONE);
|
||||||
|
var description = mEmojiViewCallback.getDescription(key.getLabel());
|
||||||
|
if (description == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDescriptionView.setText(description);
|
||||||
|
mDescriptionView.setKeyDrawParams(key, getKeyDrawParams());
|
||||||
|
mDescriptionView.setVisibility(VISIBLE);
|
||||||
|
return mDescriptionView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerPress(final Key key) {
|
private void registerPress(final Key key) {
|
||||||
|
@ -318,7 +359,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
releasedKey.onReleased();
|
releasedKey.onReleased();
|
||||||
invalidateKey(releasedKey);
|
invalidateKey(releasedKey);
|
||||||
if (withKeyRegistering) {
|
if (withKeyRegistering) {
|
||||||
mListener.onReleaseKey(releasedKey);
|
mEmojiViewCallback.onReleaseKey(releasedKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +367,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
|
||||||
mPendingKeyDown = null;
|
mPendingKeyDown = null;
|
||||||
pressedKey.onPressed();
|
pressedKey.onPressed();
|
||||||
invalidateKey(pressedKey);
|
invalidateKey(pressedKey);
|
||||||
mListener.onPressKey(pressedKey);
|
mEmojiViewCallback.onPressKey(pressedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseCurrentKey(final boolean withKeyRegistering) {
|
public void releaseCurrentKey(final boolean withKeyRegistering) {
|
||||||
|
|
|
@ -7,161 +7,37 @@
|
||||||
package helium314.keyboard.keyboard.emoji;
|
package helium314.keyboard.keyboard.emoji;
|
||||||
|
|
||||||
import helium314.keyboard.latin.utils.Log;
|
import helium314.keyboard.latin.utils.Log;
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import helium314.keyboard.keyboard.Key;
|
|
||||||
import helium314.keyboard.keyboard.Keyboard;
|
import helium314.keyboard.keyboard.Keyboard;
|
||||||
import helium314.keyboard.keyboard.KeyboardView;
|
|
||||||
import helium314.keyboard.latin.R;
|
import helium314.keyboard.latin.R;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import helium314.keyboard.latin.settings.Settings;
|
|
||||||
|
|
||||||
final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapter.ViewHolder>{
|
final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapter.ViewHolder>{
|
||||||
private static final String TAG = EmojiPalettesAdapter.class.getSimpleName();
|
private static final String TAG = EmojiPalettesAdapter.class.getSimpleName();
|
||||||
private static final boolean DEBUG_PAGER = false;
|
private static final boolean DEBUG_PAGER = false;
|
||||||
|
|
||||||
private final OnKeyEventListener mListener;
|
private final int mCategoryId;
|
||||||
private final DynamicGridKeyboard mRecentsKeyboard;
|
private final EmojiViewCallback mEmojiViewCallback;
|
||||||
private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews = new SparseArray<>();
|
|
||||||
private final EmojiCategory mEmojiCategory;
|
private final EmojiCategory mEmojiCategory;
|
||||||
private int mActivePosition = 0;
|
|
||||||
|
|
||||||
public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
|
public EmojiPalettesAdapter(final EmojiCategory emojiCategory, int categoryId, final EmojiViewCallback emojiViewCallback) {
|
||||||
final OnKeyEventListener listener) {
|
|
||||||
mEmojiCategory = emojiCategory;
|
mEmojiCategory = emojiCategory;
|
||||||
mListener = listener;
|
mCategoryId = categoryId;
|
||||||
mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.ID_RECENTS, 0);
|
mEmojiViewCallback = emojiViewCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flushPendingRecentKeys() {
|
|
||||||
mRecentsKeyboard.flushPendingRecentKeys();
|
|
||||||
final KeyboardView recentKeyboardView =
|
|
||||||
mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
|
|
||||||
if (recentKeyboardView != null) {
|
|
||||||
recentKeyboardView.invalidateAllKeys();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addRecentKey(final Key key) {
|
|
||||||
if (Settings.getValues().mIncognitoModeEnabled) {
|
|
||||||
// We do not want to log recent keys while being in incognito
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mEmojiCategory.isInRecentTab()) {
|
|
||||||
mRecentsKeyboard.addPendingKey(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mRecentsKeyboard.addKeyFirst(key);
|
|
||||||
final KeyboardView recentKeyboardView =
|
|
||||||
mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
|
|
||||||
if (recentKeyboardView != null) {
|
|
||||||
recentKeyboardView.invalidateAllKeys();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPageScrolled() {
|
|
||||||
releaseCurrentKey(false /* withKeyRegistering */);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void releaseCurrentKey(final boolean withKeyRegistering) {
|
|
||||||
// Make sure the delayed key-down event (highlight effect and haptic feedback) will be
|
|
||||||
// canceled.
|
|
||||||
final EmojiPageKeyboardView currentKeyboardView =
|
|
||||||
mActiveKeyboardViews.get(mActivePosition);
|
|
||||||
if (currentKeyboardView == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentKeyboardView.releaseCurrentKey(withKeyRegistering);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@Override
|
|
||||||
public Object instantiateItem(final ViewGroup container, final int position) {
|
|
||||||
if (DEBUG_PAGER) {
|
|
||||||
Log.d(TAG, "instantiate item: " + position);
|
|
||||||
}
|
|
||||||
final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
|
|
||||||
if (oldKeyboardView != null) {
|
|
||||||
oldKeyboardView.deallocateMemory();
|
|
||||||
// This may be redundant but wanted to be safer..
|
|
||||||
mActiveKeyboardViews.remove(position);
|
|
||||||
}
|
|
||||||
final Keyboard keyboard =
|
|
||||||
mEmojiCategory.getKeyboardFromPagePosition(position);
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(container.getContext());
|
|
||||||
final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView) inflater.inflate(
|
|
||||||
R.layout.emoji_keyboard_page, container, false);
|
|
||||||
keyboardView.setKeyboard(keyboard);
|
|
||||||
keyboardView.setOnKeyEventListener(mListener);
|
|
||||||
container.addView(keyboardView);
|
|
||||||
mActiveKeyboardViews.put(position, keyboardView);
|
|
||||||
return keyboardView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPrimaryItem(final ViewGroup container, final int position,
|
|
||||||
final Object object) {
|
|
||||||
if (mActivePosition == position) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
|
|
||||||
if (oldKeyboardView != null) {
|
|
||||||
oldKeyboardView.releaseCurrentKey(false);
|
|
||||||
oldKeyboardView.deallocateMemory();
|
|
||||||
}
|
|
||||||
mActivePosition = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isViewFromObject(final View view, final Object object) {
|
|
||||||
return view == object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroyItem(final ViewGroup container, final int position,
|
|
||||||
final Object object) {
|
|
||||||
if (DEBUG_PAGER) {
|
|
||||||
Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
|
|
||||||
if (keyboardView != null) {
|
|
||||||
keyboardView.deallocateMemory();
|
|
||||||
mActiveKeyboardViews.remove(position);
|
|
||||||
}
|
|
||||||
if (object instanceof View) {
|
|
||||||
container.removeView((View)object);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
/*if (DEBUG_PAGER) {
|
|
||||||
Log.d(TAG, "instantiate item: " + viewType);
|
|
||||||
}
|
|
||||||
final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(viewType);
|
|
||||||
if (oldKeyboardView != null) {
|
|
||||||
oldKeyboardView.deallocateMemory();
|
|
||||||
// This may be redundant but wanted to be safer..
|
|
||||||
mActiveKeyboardViews.remove(viewType);
|
|
||||||
}
|
|
||||||
final Keyboard keyboard =
|
|
||||||
mEmojiCategory.getKeyboardFromPagePosition(parent.getVerticalScrollbarPosition());*/
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
|
final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
|
||||||
R.layout.emoji_keyboard_page, parent, false);
|
R.layout.emoji_keyboard_page, parent, false);
|
||||||
/*keyboardView.setKeyboard(keyboard);
|
keyboardView.setEmojiViewCallback(mEmojiViewCallback);
|
||||||
keyboardView.setOnKeyEventListener(mListener);
|
|
||||||
parent.addView(keyboardView);
|
|
||||||
mActiveKeyboardViews.put(parent.getVerticalScrollbarPosition(), keyboardView);*/
|
|
||||||
return new ViewHolder(keyboardView);
|
return new ViewHolder(keyboardView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,33 +46,21 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
|
||||||
if (DEBUG_PAGER) {
|
if (DEBUG_PAGER) {
|
||||||
Log.d(TAG, "instantiate item: " + position);
|
Log.d(TAG, "instantiate item: " + position);
|
||||||
}
|
}
|
||||||
final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
|
|
||||||
if (oldKeyboardView != null) {
|
|
||||||
oldKeyboardView.deallocateMemory();
|
|
||||||
// This may be redundant but wanted to be safer..
|
|
||||||
mActiveKeyboardViews.remove(position);
|
|
||||||
}
|
|
||||||
final Keyboard keyboard =
|
|
||||||
mEmojiCategory.getKeyboardFromAdapterPosition(position);
|
|
||||||
holder.getKeyboardView().setKeyboard(keyboard);
|
|
||||||
holder.getKeyboardView().setOnKeyEventListener(mListener);
|
|
||||||
//parent.addView(keyboardView);
|
|
||||||
mActiveKeyboardViews.put(position, holder.getKeyboardView());
|
|
||||||
|
|
||||||
/*if (mActivePosition == position) {
|
final Keyboard keyboard =
|
||||||
return;
|
mEmojiCategory.getKeyboardFromAdapterPosition(mCategoryId, position);
|
||||||
}
|
holder.getKeyboardView().setKeyboard(keyboard);
|
||||||
final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
|
}
|
||||||
if (oldKeyboardView != null) {
|
|
||||||
oldKeyboardView.releaseCurrentKey(false);
|
@Override
|
||||||
oldKeyboardView.deallocateMemory();
|
public void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
|
||||||
}
|
holder.getKeyboardView().releaseCurrentKey(false);
|
||||||
mActivePosition = position;*/
|
holder.getKeyboardView().deallocateMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return mEmojiCategory.getCurrentCategoryPageCount();
|
return mEmojiCategory.getCategoryPageCount(mCategoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
@ -210,7 +74,6 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
|
||||||
public EmojiPageKeyboardView getKeyboardView() {
|
public EmojiPageKeyboardView getKeyboardView() {
|
||||||
return customView;
|
return customView;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,15 @@
|
||||||
|
|
||||||
package helium314.keyboard.keyboard.emoji;
|
package helium314.keyboard.keyboard.emoji;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
@ -20,6 +24,7 @@ import android.widget.LinearLayout;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
import helium314.keyboard.keyboard.Key;
|
import helium314.keyboard.keyboard.Key;
|
||||||
import helium314.keyboard.keyboard.Keyboard;
|
import helium314.keyboard.keyboard.Keyboard;
|
||||||
import helium314.keyboard.keyboard.KeyboardActionListener;
|
import helium314.keyboard.keyboard.KeyboardActionListener;
|
||||||
|
@ -33,16 +38,19 @@ import helium314.keyboard.keyboard.internal.KeyDrawParams;
|
||||||
import helium314.keyboard.keyboard.internal.KeyVisualAttributes;
|
import helium314.keyboard.keyboard.internal.KeyVisualAttributes;
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||||
import helium314.keyboard.latin.AudioAndHapticFeedbackManager;
|
import helium314.keyboard.latin.AudioAndHapticFeedbackManager;
|
||||||
|
import helium314.keyboard.latin.Dictionary;
|
||||||
|
import helium314.keyboard.latin.DictionaryFactory;
|
||||||
import helium314.keyboard.latin.R;
|
import helium314.keyboard.latin.R;
|
||||||
|
import helium314.keyboard.latin.RichInputMethodManager;
|
||||||
import helium314.keyboard.latin.RichInputMethodSubtype;
|
import helium314.keyboard.latin.RichInputMethodSubtype;
|
||||||
|
import helium314.keyboard.latin.SingleDictionaryFacilitator;
|
||||||
import helium314.keyboard.latin.common.ColorType;
|
import helium314.keyboard.latin.common.ColorType;
|
||||||
import helium314.keyboard.latin.common.Colors;
|
import helium314.keyboard.latin.common.Colors;
|
||||||
import helium314.keyboard.latin.settings.Settings;
|
import helium314.keyboard.latin.settings.Settings;
|
||||||
import helium314.keyboard.latin.settings.SettingsValues;
|
import helium314.keyboard.latin.settings.SettingsValues;
|
||||||
|
import helium314.keyboard.latin.utils.DictionaryInfoUtils;
|
||||||
import helium314.keyboard.latin.utils.ResourceUtils;
|
import helium314.keyboard.latin.utils.ResourceUtils;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import static helium314.keyboard.latin.common.Constants.NOT_A_COORDINATE;
|
import static helium314.keyboard.latin.common.Constants.NOT_A_COORDINATE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,27 +65,135 @@ import static helium314.keyboard.latin.common.Constants.NOT_A_COORDINATE;
|
||||||
* Because of the above reasons, this class doesn't extend {@link KeyboardView}.
|
* Because of the above reasons, this class doesn't extend {@link KeyboardView}.
|
||||||
*/
|
*/
|
||||||
public final class EmojiPalettesView extends LinearLayout
|
public final class EmojiPalettesView extends LinearLayout
|
||||||
implements View.OnClickListener, OnKeyEventListener {
|
implements View.OnClickListener, EmojiViewCallback {
|
||||||
|
private static final class PagerViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private long mCategoryId;
|
||||||
|
|
||||||
|
private PagerViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder> {
|
||||||
|
private boolean mInitialized;
|
||||||
|
private final Map<Integer, RecyclerView> mViews = new HashMap<>(mEmojiCategory.getShownCategories().size());
|
||||||
|
|
||||||
|
private PagerAdapter(ViewPager2 pager) {
|
||||||
|
setHasStableIds(true);
|
||||||
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
var categoryId = (int) getItemId(position);
|
||||||
|
setCurrentCategoryId(categoryId, false);
|
||||||
|
var recyclerView = mViews.get(position);
|
||||||
|
if (recyclerView != null) {
|
||||||
|
updateState(recyclerView, categoryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||||
|
recyclerView.setItemViewCacheSize(mEmojiCategory.getShownCategories().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
var view = LayoutInflater.from(parent.getContext()).inflate(R.layout.emoji_category_view, parent, false);
|
||||||
|
var viewHolder = new PagerViewHolder(view);
|
||||||
|
var emojiRecyclerView = getRecyclerView(view);
|
||||||
|
|
||||||
|
emojiRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
// Ignore this message. Only want the actual page selected.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
updateState(recyclerView, viewHolder.mCategoryId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emojiRecyclerView.setPersistentDrawingCache(PERSISTENT_NO_CACHE);
|
||||||
|
return viewHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(PagerViewHolder holder, int position) {
|
||||||
|
holder.mCategoryId = getItemId(position);
|
||||||
|
var recyclerView = getRecyclerView(holder.itemView);
|
||||||
|
mViews.put(position, recyclerView);
|
||||||
|
recyclerView.setAdapter(new EmojiPalettesAdapter(mEmojiCategory, (int) holder.mCategoryId,
|
||||||
|
EmojiPalettesView.this));
|
||||||
|
|
||||||
|
if (! mInitialized) {
|
||||||
|
recyclerView.scrollToPosition(mEmojiCategory.getCurrentCategoryPageId());
|
||||||
|
mInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mEmojiCategory.getShownCategories().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewDetachedFromWindow(PagerViewHolder holder) {
|
||||||
|
if (holder.mCategoryId == EmojiCategory.ID_RECENTS) {
|
||||||
|
// Needs to save pending updates for recent keys when we get out of the recents
|
||||||
|
// category because we don't want to move the recent emojis around while the user
|
||||||
|
// is in the recents category.
|
||||||
|
getRecentsKeyboard().flushPendingRecentKeys();
|
||||||
|
getRecyclerView(holder.itemView).getAdapter().notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return mEmojiCategory.getShownCategories().get(position).mCategoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RecyclerView getRecyclerView(View view) {
|
||||||
|
return view.findViewById(R.id.emoji_keyboard_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(@NonNull RecyclerView recyclerView, long categoryId) {
|
||||||
|
if (categoryId != mEmojiCategory.getCurrentCategoryId()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int offset = recyclerView.computeVerticalScrollOffset();
|
||||||
|
final int extent = recyclerView.computeVerticalScrollExtent();
|
||||||
|
final int range = recyclerView.computeVerticalScrollRange();
|
||||||
|
final float percentage = offset / (float) (range - extent);
|
||||||
|
|
||||||
|
final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageCount();
|
||||||
|
final int a = (int) (percentage * currentCategorySize);
|
||||||
|
final float b = percentage * currentCategorySize - a;
|
||||||
|
mEmojiCategoryPageIndicatorView.setCategoryPageId(currentCategorySize, a, b);
|
||||||
|
|
||||||
|
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
final int firstCompleteVisibleBoard = layoutManager.findFirstCompletelyVisibleItemPosition();
|
||||||
|
final int firstVisibleBoard = layoutManager.findFirstVisibleItemPosition();
|
||||||
|
mEmojiCategory.setCurrentCategoryPageId(
|
||||||
|
firstCompleteVisibleBoard > 0 ? firstCompleteVisibleBoard : firstVisibleBoard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SingleDictionaryFacilitator sDictionaryFacilitator;
|
||||||
|
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
// keep the indicator in case emoji view is changed to tabs / viewpager
|
|
||||||
private final boolean mCategoryIndicatorEnabled;
|
|
||||||
private final int mCategoryIndicatorDrawableResId;
|
|
||||||
private final int mCategoryIndicatorBackgroundResId;
|
|
||||||
private final int mCategoryPageIndicatorColor;
|
|
||||||
private final Colors mColors;
|
private final Colors mColors;
|
||||||
private EmojiPalettesAdapter mEmojiPalettesAdapter;
|
|
||||||
private final EmojiLayoutParams mEmojiLayoutParams;
|
private final EmojiLayoutParams mEmojiLayoutParams;
|
||||||
private final LinearLayoutManager mEmojiLayoutManager;
|
|
||||||
|
|
||||||
private LinearLayout mTabStrip;
|
private LinearLayout mTabStrip;
|
||||||
private RecyclerView mEmojiRecyclerView;
|
|
||||||
private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView;
|
private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView;
|
||||||
|
|
||||||
private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
|
private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
|
||||||
|
|
||||||
private final EmojiCategory mEmojiCategory;
|
private final EmojiCategory mEmojiCategory;
|
||||||
|
private ViewPager2 mPager;
|
||||||
private ImageView mCurrentTab = null;
|
|
||||||
|
|
||||||
public EmojiPalettesView(final Context context, final AttributeSet attrs) {
|
public EmojiPalettesView(final Context context, final AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.emojiPalettesViewStyle);
|
this(context, attrs, R.attr.emojiPalettesViewStyle);
|
||||||
|
@ -96,16 +212,8 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
|
final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
|
||||||
R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
|
R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
|
||||||
mEmojiCategory = new EmojiCategory(context, layoutSet, emojiPalettesViewAttr);
|
mEmojiCategory = new EmojiCategory(context, layoutSet, emojiPalettesViewAttr);
|
||||||
mCategoryIndicatorEnabled = emojiPalettesViewAttr.getBoolean(
|
|
||||||
R.styleable.EmojiPalettesView_categoryIndicatorEnabled, false);
|
|
||||||
mCategoryIndicatorDrawableResId = emojiPalettesViewAttr.getResourceId(
|
|
||||||
R.styleable.EmojiPalettesView_categoryIndicatorDrawable, 0);
|
|
||||||
mCategoryIndicatorBackgroundResId = emojiPalettesViewAttr.getResourceId(
|
|
||||||
R.styleable.EmojiPalettesView_categoryIndicatorBackground, 0);
|
|
||||||
mCategoryPageIndicatorColor = emojiPalettesViewAttr.getColor( // todo: remove this and related attr
|
|
||||||
R.styleable.EmojiPalettesView_categoryPageIndicatorColor, 0);
|
|
||||||
emojiPalettesViewAttr.recycle();
|
emojiPalettesViewAttr.recycle();
|
||||||
mEmojiLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
|
setFitsSystemWindows(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -115,19 +223,12 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
// The main keyboard expands to the entire this {@link KeyboardView}.
|
// The main keyboard expands to the entire this {@link KeyboardView}.
|
||||||
final int width = ResourceUtils.getKeyboardWidth(getContext(), Settings.getValues())
|
final int width = ResourceUtils.getKeyboardWidth(getContext(), Settings.getValues())
|
||||||
+ getPaddingLeft() + getPaddingRight();
|
+ getPaddingLeft() + getPaddingRight();
|
||||||
final int height = ResourceUtils.getKeyboardHeight(res, Settings.getValues())
|
final int height = ResourceUtils.getSecondaryKeyboardHeight(res, Settings.getValues())
|
||||||
+ getPaddingTop() + getPaddingBottom();
|
+ getPaddingTop() + getPaddingBottom();
|
||||||
mEmojiCategoryPageIndicatorView.mWidth = width;
|
mEmojiCategoryPageIndicatorView.mWidth = width;
|
||||||
setMeasuredDimension(width, height);
|
setMeasuredDimension(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo (maybe): bring back the holo indicator thing?
|
|
||||||
// just some 2 dp high strip
|
|
||||||
// would probably need a vertical linear layout
|
|
||||||
// better not, would complicate stuff again
|
|
||||||
// when decided to definitely not bring it back:
|
|
||||||
// remove mCategoryIndicatorEnabled, mCategoryIndicatorDrawableResId, mCategoryIndicatorBackgroundResId
|
|
||||||
// and the attrs categoryIndicatorDrawable, categoryIndicatorEnabled, categoryIndicatorBackground (and the connected drawables)
|
|
||||||
private void addTab(final LinearLayout host, final int categoryId) {
|
private void addTab(final LinearLayout host, final int categoryId) {
|
||||||
final ImageView iconView = new ImageView(getContext());
|
final ImageView iconView = new ImageView(getContext());
|
||||||
mColors.setBackground(iconView, ColorType.STRIP_BACKGROUND);
|
mColors.setBackground(iconView, ColorType.STRIP_BACKGROUND);
|
||||||
|
@ -146,63 +247,18 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
mEmojiCategory.initialize();
|
mEmojiCategory.initialize();
|
||||||
mTabStrip = (LinearLayout) KeyboardSwitcher.getInstance().getEmojiTabStrip();
|
mTabStrip = (LinearLayout) KeyboardSwitcher.getInstance().getEmojiTabStrip();
|
||||||
for (final EmojiCategory.CategoryProperties properties : mEmojiCategory.getShownCategories()) {
|
if (Settings.getValues().mSecondaryStripVisible) {
|
||||||
addTab(mTabStrip, properties.mCategoryId);
|
for (final EmojiCategory.CategoryProperties properties : mEmojiCategory.getShownCategories()) {
|
||||||
}
|
addTab(mTabStrip, properties.mCategoryId);
|
||||||
// mTabStrip.setOnTabChangedListener(this); // now onClickListener
|
|
||||||
/* final TabWidget tabWidget = mTabStrip.getTabWidget();
|
|
||||||
tabWidget.setStripEnabled(mCategoryIndicatorEnabled);
|
|
||||||
if (mCategoryIndicatorEnabled) {
|
|
||||||
// On TabWidget's strip, what looks like an indicator is actually a background.
|
|
||||||
// And what looks like a background are actually left and right drawables.
|
|
||||||
tabWidget.setBackgroundResource(mCategoryIndicatorDrawableResId);
|
|
||||||
tabWidget.setLeftStripDrawable(mCategoryIndicatorBackgroundResId);
|
|
||||||
tabWidget.setRightStripDrawable(mCategoryIndicatorBackgroundResId);
|
|
||||||
tabWidget.setBackgroundColor(mColors.get(ColorType.EMOJI_CATEGORY_SELECTED));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this);
|
|
||||||
|
|
||||||
mEmojiRecyclerView = findViewById(R.id.emoji_keyboard_list);
|
|
||||||
mEmojiRecyclerView.setLayoutManager(mEmojiLayoutManager);
|
|
||||||
mEmojiRecyclerView.setAdapter(mEmojiPalettesAdapter);
|
|
||||||
mEmojiRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
|
||||||
@Override
|
|
||||||
public void onScrollStateChanged(@NonNull @NotNull RecyclerView recyclerView, int newState) {
|
|
||||||
super.onScrollStateChanged(recyclerView, newState);
|
|
||||||
// Ignore this message. Only want the actual page selected.
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
mPager = findViewById(R.id.emoji_pager);
|
||||||
public void onScrolled(@NonNull @NotNull RecyclerView recyclerView, int dx, int dy) {
|
mPager.setAdapter(new PagerAdapter(mPager));
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
mEmojiLayoutParams.setEmojiListProperties(mPager);
|
||||||
mEmojiPalettesAdapter.onPageScrolled();
|
|
||||||
|
|
||||||
final int offset = recyclerView.computeVerticalScrollOffset();
|
|
||||||
final int extent = recyclerView.computeVerticalScrollExtent();
|
|
||||||
final int range = recyclerView.computeVerticalScrollRange();
|
|
||||||
final float percentage = offset / (float) (range - extent);
|
|
||||||
|
|
||||||
final int currentCategorySize = mEmojiCategory.getCurrentCategoryPageCount();
|
|
||||||
final int a = (int) (percentage * currentCategorySize);
|
|
||||||
final float b = percentage * currentCategorySize - a;
|
|
||||||
mEmojiCategoryPageIndicatorView.setCategoryPageId(currentCategorySize, a, b);
|
|
||||||
|
|
||||||
final int firstCompleteVisibleBoard = mEmojiLayoutManager.findFirstCompletelyVisibleItemPosition();
|
|
||||||
final int firstVisibleBoard = mEmojiLayoutManager.findFirstVisibleItemPosition();
|
|
||||||
mEmojiCategory.setCurrentCategoryPageId(
|
|
||||||
firstCompleteVisibleBoard > 0 ? firstCompleteVisibleBoard : firstVisibleBoard);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mEmojiRecyclerView.setPersistentDrawingCache(PERSISTENT_NO_CACHE);
|
|
||||||
mEmojiLayoutParams.setEmojiListProperties(mEmojiRecyclerView);
|
|
||||||
|
|
||||||
mEmojiCategoryPageIndicatorView = findViewById(R.id.emoji_category_page_id_view);
|
mEmojiCategoryPageIndicatorView = findViewById(R.id.emoji_category_page_id_view);
|
||||||
mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
|
mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
|
||||||
|
setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true);
|
||||||
setCurrentCategoryAndPageId(mEmojiCategory.getCurrentCategoryId(), mEmojiCategory.getCurrentCategoryPageId(), true);
|
|
||||||
|
|
||||||
mEmojiCategoryPageIndicatorView.setColors(mColors.get(ColorType.EMOJI_CATEGORY_SELECTED), mColors.get(ColorType.STRIP_BACKGROUND));
|
mEmojiCategoryPageIndicatorView.setColors(mColors.get(ColorType.EMOJI_CATEGORY_SELECTED), mColors.get(ColorType.STRIP_BACKGROUND));
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
@ -219,15 +275,14 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this);
|
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, this);
|
||||||
final int categoryId = ((Long) tag).intValue();
|
final int categoryId = ((Long) tag).intValue();
|
||||||
if (categoryId != mEmojiCategory.getCurrentCategoryId()) {
|
if (categoryId != mEmojiCategory.getCurrentCategoryId()) {
|
||||||
setCurrentCategoryAndPageId(categoryId, 0, false);
|
setCurrentCategoryId(categoryId, false);
|
||||||
updateEmojiCategoryPageIdView();
|
updateEmojiCategoryPageIdView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from {@link EmojiPageKeyboardView} through
|
* Called from {@link EmojiPageKeyboardView} through {@link EmojiViewCallback}
|
||||||
* {@link helium314.keyboard.keyboard.emoji.OnKeyEventListener}
|
|
||||||
* interface to handle touch events from non-View-based elements such as Emoji buttons.
|
* interface to handle touch events from non-View-based elements such as Emoji buttons.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -237,14 +292,13 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from {@link EmojiPageKeyboardView} through
|
* Called from {@link EmojiPageKeyboardView} through {@link EmojiViewCallback}
|
||||||
* {@link helium314.keyboard.keyboard.emoji.OnKeyEventListener}
|
|
||||||
* interface to handle touch events from non-View-based elements such as Emoji buttons.
|
* interface to handle touch events from non-View-based elements such as Emoji buttons.
|
||||||
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)}.
|
* This may be called without any prior call to {@link EmojiViewCallback#onPressKey(Key)}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onReleaseKey(final Key key) {
|
public void onReleaseKey(final Key key) {
|
||||||
mEmojiPalettesAdapter.addRecentKey(key);
|
addRecentKey(key);
|
||||||
final int code = key.getCode();
|
final int code = key.getCode();
|
||||||
if (code == KeyCode.MULTIPLE_CODE_POINTS) {
|
if (code == KeyCode.MULTIPLE_CODE_POINTS) {
|
||||||
mKeyboardActionListener.onTextInput(key.getOutputText());
|
mKeyboardActionListener.onTextInput(key.getOutputText());
|
||||||
|
@ -256,6 +310,20 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
mKeyboardActionListener.onCodeInput(KeyCode.ALPHA, NOT_A_COORDINATE, NOT_A_COORDINATE, false);
|
mKeyboardActionListener.onCodeInput(KeyCode.ALPHA, NOT_A_COORDINATE, NOT_A_COORDINATE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(String emoji) {
|
||||||
|
if (sDictionaryFacilitator == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wordProperty = sDictionaryFacilitator.getWordProperty(emoji);
|
||||||
|
if (wordProperty == null || ! wordProperty.mHasShortcuts) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wordProperty.mShortcutTargets.get(0).mWord;
|
||||||
|
}
|
||||||
|
|
||||||
public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
|
public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
// TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
|
// TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
|
||||||
|
@ -269,11 +337,21 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
setupBottomRowKeyboard(editorInfo, keyboardActionListener);
|
setupBottomRowKeyboard(editorInfo, keyboardActionListener);
|
||||||
final KeyDrawParams params = new KeyDrawParams();
|
final KeyDrawParams params = new KeyDrawParams();
|
||||||
params.updateParams(mEmojiLayoutParams.getBottomRowKeyboardHeight(), keyVisualAttr);
|
params.updateParams(mEmojiLayoutParams.getBottomRowKeyboardHeight(), keyVisualAttr);
|
||||||
if (mEmojiRecyclerView.getAdapter() == null) {
|
|
||||||
mEmojiRecyclerView.setAdapter(mEmojiPalettesAdapter);
|
|
||||||
setCurrentCategoryAndPageId(mEmojiCategory.getCurrentCategoryId(), mEmojiCategory.getCurrentCategoryPageId(), true);
|
|
||||||
}
|
|
||||||
setupSidePadding();
|
setupSidePadding();
|
||||||
|
initDictionaryFacilitator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRecentKey(final Key key) {
|
||||||
|
if (Settings.getValues().mIncognitoModeEnabled) {
|
||||||
|
// We do not want to log recent keys while being in incognito
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mEmojiCategory.isInRecentTab()) {
|
||||||
|
getRecentsKeyboard().addPendingKey(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getRecentsKeyboard().addKeyFirst(key);
|
||||||
|
mPager.getAdapter().notifyItemChanged(mEmojiCategory.getRecentTabId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupBottomRowKeyboard(final EditorInfo editorInfo, final KeyboardActionListener keyboardActionListener) {
|
private void setupBottomRowKeyboard(final EditorInfo editorInfo, final KeyboardActionListener keyboardActionListener) {
|
||||||
|
@ -295,11 +373,11 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
final float rightPadding = keyboardAttr.getFraction(R.styleable.Keyboard_keyboardRightPadding,
|
final float rightPadding = keyboardAttr.getFraction(R.styleable.Keyboard_keyboardRightPadding,
|
||||||
keyboardWidth, keyboardWidth, 0f) * sv.mSidePaddingScale;
|
keyboardWidth, keyboardWidth, 0f) * sv.mSidePaddingScale;
|
||||||
keyboardAttr.recycle();
|
keyboardAttr.recycle();
|
||||||
mEmojiRecyclerView.setPadding(
|
mPager.setPadding(
|
||||||
(int) leftPadding,
|
(int) leftPadding,
|
||||||
mEmojiRecyclerView.getPaddingTop(),
|
mPager.getPaddingTop(),
|
||||||
(int) rightPadding,
|
(int) rightPadding,
|
||||||
mEmojiRecyclerView.getPaddingBottom()
|
mPager.getPaddingBottom()
|
||||||
);
|
);
|
||||||
mEmojiCategoryPageIndicatorView.setPadding(
|
mEmojiCategoryPageIndicatorView.setPadding(
|
||||||
(int) leftPadding,
|
(int) leftPadding,
|
||||||
|
@ -312,9 +390,11 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
|
|
||||||
public void stopEmojiPalettes() {
|
public void stopEmojiPalettes() {
|
||||||
if (!initialized) return;
|
if (!initialized) return;
|
||||||
mEmojiPalettesAdapter.releaseCurrentKey(true);
|
getRecentsKeyboard().flushPendingRecentKeys();
|
||||||
mEmojiPalettesAdapter.flushPendingRecentKeys();
|
}
|
||||||
mEmojiRecyclerView.setAdapter(null);
|
|
||||||
|
private DynamicGridKeyboard getRecentsKeyboard() {
|
||||||
|
return mEmojiCategory.getKeyboard(EmojiCategory.ID_RECENTS, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyboardActionListener(final KeyboardActionListener listener) {
|
public void setKeyboardActionListener(final KeyboardActionListener listener) {
|
||||||
|
@ -330,34 +410,62 @@ public final class EmojiPalettesView extends LinearLayout
|
||||||
mEmojiCategory.getCurrentCategoryPageId(), 0.0f);
|
mEmojiCategory.getCurrentCategoryPageId(), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCurrentCategoryAndPageId(final int categoryId, final int categoryPageId, final boolean force) {
|
private void setCurrentCategoryId(final int categoryId, final boolean initial) {
|
||||||
final int oldCategoryId = mEmojiCategory.getCurrentCategoryId();
|
final int oldCategoryId = mEmojiCategory.getCurrentCategoryId();
|
||||||
final int oldCategoryPageId = mEmojiCategory.getCurrentCategoryPageId();
|
if (initial || oldCategoryId != categoryId) {
|
||||||
|
|
||||||
if (oldCategoryId == EmojiCategory.ID_RECENTS && categoryId != EmojiCategory.ID_RECENTS) {
|
|
||||||
// Needs to save pending updates for recent keys when we get out of the recents
|
|
||||||
// category because we don't want to move the recent emojis around while the user
|
|
||||||
// is in the recents category.
|
|
||||||
mEmojiPalettesAdapter.flushPendingRecentKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force || oldCategoryId != categoryId || oldCategoryPageId != categoryPageId) {
|
|
||||||
mEmojiCategory.setCurrentCategoryId(categoryId);
|
mEmojiCategory.setCurrentCategoryId(categoryId);
|
||||||
mEmojiCategory.setCurrentCategoryPageId(categoryPageId);
|
|
||||||
mEmojiPalettesAdapter.notifyDataSetChanged();
|
if (mPager.getScrollState() != ViewPager2.SCROLL_STATE_DRAGGING) {
|
||||||
mEmojiRecyclerView.scrollToPosition(categoryPageId);
|
// Not swiping
|
||||||
|
mPager.setCurrentItem(mEmojiCategory.getTabIdFromCategoryId(
|
||||||
|
mEmojiCategory.getCurrentCategoryId()), ! initial && ! isAnimationsDisabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.getValues().mSecondaryStripVisible) {
|
||||||
|
final View old = mTabStrip.findViewWithTag((long) oldCategoryId);
|
||||||
|
final View current = mTabStrip.findViewWithTag((long) categoryId);
|
||||||
|
|
||||||
|
if (old instanceof ImageView)
|
||||||
|
Settings.getValues().mColors.setColor((ImageView) old, ColorType.EMOJI_CATEGORY);
|
||||||
|
if (current instanceof ImageView)
|
||||||
|
Settings.getValues().mColors.setColor((ImageView) current, ColorType.EMOJI_CATEGORY_SELECTED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final View old = mTabStrip.findViewWithTag((long) oldCategoryId);
|
private boolean isAnimationsDisabled() {
|
||||||
final View current = mTabStrip.findViewWithTag((long) categoryId);
|
return android.provider.Settings.Global.getFloat(getContext().getContentResolver(),
|
||||||
|
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f;
|
||||||
if (old instanceof ImageView)
|
|
||||||
Settings.getValues().mColors.setColor((ImageView) old, ColorType.EMOJI_CATEGORY);
|
|
||||||
if (current instanceof ImageView)
|
|
||||||
Settings.getValues().mColors.setColor((ImageView) current, ColorType.EMOJI_CATEGORY_SELECTED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearKeyboardCache() {
|
public void clearKeyboardCache() {
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mEmojiCategory.clearKeyboardCache();
|
mEmojiCategory.clearKeyboardCache();
|
||||||
|
mPager.getAdapter().notifyDataSetChanged();
|
||||||
|
closeDictionaryFacilitator();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void initDictionaryFacilitator() {
|
||||||
|
if (Settings.getValues().mShowEmojiDescriptions) {
|
||||||
|
var locale = RichInputMethodManager.getInstance().getCurrentSubtype().getLocale();
|
||||||
|
if (sDictionaryFacilitator == null || ! sDictionaryFacilitator.isForLocale(locale)) {
|
||||||
|
closeDictionaryFacilitator();
|
||||||
|
var dictFile = DictionaryInfoUtils.getCachedDictForLocaleAndType(locale, Dictionary.TYPE_EMOJI, getContext());
|
||||||
|
var dictionary = dictFile != null? DictionaryFactory.getDictionary(dictFile, locale) : null;
|
||||||
|
sDictionaryFacilitator = dictionary != null? new SingleDictionaryFacilitator(dictionary) : null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closeDictionaryFacilitator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void closeDictionaryFacilitator() {
|
||||||
|
if (sDictionaryFacilitator != null) {
|
||||||
|
sDictionaryFacilitator.closeDictionaries();
|
||||||
|
sDictionaryFacilitator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ package helium314.keyboard.keyboard.emoji;
|
||||||
import helium314.keyboard.keyboard.Key;
|
import helium314.keyboard.keyboard.Key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to handle touch events from non-View-based elements
|
* Interface to handle callbacks from child elements
|
||||||
* such as Emoji buttons.
|
* such as Emoji buttons and keyboard views.
|
||||||
*/
|
*/
|
||||||
public interface OnKeyEventListener {
|
public interface EmojiViewCallback {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a key is pressed by the user
|
* Called when a key is pressed by the user
|
||||||
|
@ -17,8 +17,13 @@ public interface OnKeyEventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a key is released.
|
* Called when a key is released.
|
||||||
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)},
|
* This may be called without any prior call to {@link EmojiViewCallback#onPressKey(Key)},
|
||||||
* for example when a key from a popup keys keyboard is selected by releasing touch on it.
|
* for example when a key from a popup keys keyboard is selected by releasing touch on it.
|
||||||
*/
|
*/
|
||||||
void onReleaseKey(Key key);
|
void onReleaseKey(Key key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from keyboard view to get an emoji description
|
||||||
|
*/
|
||||||
|
String getDescription(String emoji);
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package helium314.keyboard.keyboard.emoji
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import helium314.keyboard.latin.settings.Defaults
|
||||||
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
import helium314.keyboard.latin.utils.Log
|
||||||
|
import helium314.keyboard.latin.utils.prefs
|
||||||
|
|
||||||
|
object SupportedEmojis {
|
||||||
|
private val unsupportedEmojis = hashSetOf<String>()
|
||||||
|
|
||||||
|
fun load(context: Context) {
|
||||||
|
val maxSdk = context.prefs().getInt(Settings.PREF_EMOJI_MAX_SDK, Defaults.PREF_EMOJI_MAX_SDK)
|
||||||
|
unsupportedEmojis.clear()
|
||||||
|
context.assets.open("emoji/minApi.txt").reader().readLines().forEach {
|
||||||
|
val s = it.split(" ")
|
||||||
|
val minApi = s.first().toInt()
|
||||||
|
if (minApi > maxSdk)
|
||||||
|
unsupportedEmojis.addAll(s.drop(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUnsupported(emoji: String) = emoji in unsupportedEmojis
|
||||||
|
}
|
|
@ -108,7 +108,7 @@ public final class KeyPreviewChoreographer {
|
||||||
final boolean hasPopupKeys = (key.getPopupKeys() != null);
|
final boolean hasPopupKeys = (key.getPopupKeys() != null);
|
||||||
keyPreviewView.setPreviewBackground(hasPopupKeys, keyPreviewPosition);
|
keyPreviewView.setPreviewBackground(hasPopupKeys, keyPreviewPosition);
|
||||||
final Colors colors = Settings.getValues().mColors;
|
final Colors colors = Settings.getValues().mColors;
|
||||||
colors.setBackground(keyPreviewView, ColorType.KEY_PREVIEW);
|
colors.setBackground(keyPreviewView, ColorType.KEY_PREVIEW_BACKGROUND);
|
||||||
|
|
||||||
// The key preview is placed vertically above the top edge of the parent key with an
|
// The key preview is placed vertically above the top edge of the parent key with an
|
||||||
// arbitrary offset.
|
// arbitrary offset.
|
||||||
|
|
|
@ -15,8 +15,7 @@ import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.widget.TextView;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
|
||||||
|
|
||||||
import helium314.keyboard.keyboard.Key;
|
import helium314.keyboard.keyboard.Key;
|
||||||
import helium314.keyboard.latin.R;
|
import helium314.keyboard.latin.R;
|
||||||
|
@ -25,10 +24,9 @@ import helium314.keyboard.latin.settings.Settings;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
/**
|
/** The pop up key preview view. */
|
||||||
* The pop up key preview view.
|
// Android Studio complains about TextView, but we're not using tint or auto-size that should be the relevant differences
|
||||||
*/
|
public class KeyPreviewView extends TextView {
|
||||||
public class KeyPreviewView extends AppCompatTextView {
|
|
||||||
public static final int POSITION_MIDDLE = 0;
|
public static final int POSITION_MIDDLE = 0;
|
||||||
public static final int POSITION_LEFT = 1;
|
public static final int POSITION_LEFT = 1;
|
||||||
public static final int POSITION_RIGHT = 2;
|
public static final int POSITION_RIGHT = 2;
|
||||||
|
|
|
@ -129,7 +129,7 @@ public final class KeyVisualAttributes {
|
||||||
// when? -> hasShiftedLetterHint and isShiftedLetterActivated -> both are label flags
|
// when? -> hasShiftedLetterHint and isShiftedLetterActivated -> both are label flags
|
||||||
mShiftedLetterHintActivatedColor = keyAttr.getColor(
|
mShiftedLetterHintActivatedColor = keyAttr.getColor(
|
||||||
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
|
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
|
||||||
mPreviewTextColor = colors.get(ColorType.KEY_TEXT);
|
mPreviewTextColor = colors.get(ColorType.KEY_PREVIEW_TEXT);
|
||||||
|
|
||||||
mHintLabelVerticalAdjustment = ResourceUtils.getFraction(keyAttr,
|
mHintLabelVerticalAdjustment = ResourceUtils.getFraction(keyAttr,
|
||||||
R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, 0.0f);
|
R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, 0.0f);
|
||||||
|
|
|
@ -48,7 +48,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
if (id.isEmojiKeyboard) {
|
if (id.isEmojiKeyboard) {
|
||||||
mParams.mAllowRedundantPopupKeys = true
|
mParams.mAllowRedundantPopupKeys = true
|
||||||
readAttributes(R.xml.kbd_emoji)
|
readAttributes(R.xml.kbd_emoji)
|
||||||
keysInRows = EmojiParser(mParams, mContext, Settings.getValues().mEmojiMaxSdk).parse()
|
keysInRows = EmojiParser(mParams, mContext).parse()
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
setupParams()
|
setupParams()
|
||||||
|
@ -176,7 +176,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
val relativeWidthSumNew = row.sumOf { it.mWidth }
|
val relativeWidthSumNew = row.sumOf { it.mWidth }
|
||||||
val widthFactor = relativeWidthSum / relativeWidthSumNew
|
val widthFactor = relativeWidthSum / relativeWidthSumNew
|
||||||
// re-calculate absolute sizes and positions
|
// re-calculate absolute sizes and positions
|
||||||
var currentX = 0f
|
var currentX = mParams.mLeftPadding.toFloat()
|
||||||
row.forEach {
|
row.forEach {
|
||||||
it.mWidth *= widthFactor
|
it.mWidth *= widthFactor
|
||||||
it.setAbsoluteDimensions(currentX, y)
|
it.setAbsoluteDimensions(currentX, y)
|
||||||
|
|
|
@ -80,6 +80,7 @@ public final class KeyboardState {
|
||||||
private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
|
private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
|
||||||
private static final int SWITCH_STATE_SYMBOL = 2;
|
private static final int SWITCH_STATE_SYMBOL = 2;
|
||||||
private static final int SWITCH_STATE_NUMPAD = 3;
|
private static final int SWITCH_STATE_NUMPAD = 3;
|
||||||
|
private static final int SWITCH_STATE_NUMPAD_BEGIN = 9;
|
||||||
private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 4;
|
private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 4;
|
||||||
private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 5;
|
private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 5;
|
||||||
private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 6;
|
private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 6;
|
||||||
|
@ -403,7 +404,7 @@ public final class KeyboardState {
|
||||||
mMode = MODE_NUMPAD;
|
mMode = MODE_NUMPAD;
|
||||||
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
|
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
|
||||||
mSwitchActions.setNumpadKeyboard();
|
mSwitchActions.setNumpadKeyboard();
|
||||||
mSwitchState = withSliding ? SWITCH_STATE_MOMENTARY_TO_NUMPAD : SWITCH_STATE_NUMPAD;
|
mSwitchState = withSliding ? SWITCH_STATE_MOMENTARY_TO_NUMPAD : SWITCH_STATE_NUMPAD_BEGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleNumpad(final boolean withSliding, final int autoCapsFlags, final int recapitalizeMode,
|
public void toggleNumpad(final boolean withSliding, final int autoCapsFlags, final int recapitalizeMode,
|
||||||
|
@ -789,6 +790,17 @@ public final class KeyboardState {
|
||||||
mPrevSymbolsKeyboardWasShifted = false;
|
mPrevSymbolsKeyboardWasShifted = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SWITCH_STATE_NUMPAD:
|
||||||
|
// Switch back to alpha keyboard mode if user types one or more non-space/enter
|
||||||
|
// characters followed by a space/enter.
|
||||||
|
if (isSpaceOrEnter(code) && Settings.getValues().mAlphaAfterNumpadAndSpace) {
|
||||||
|
toggleNumpad(false, autoCapsFlags, recapitalizeMode, true, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SWITCH_STATE_NUMPAD_BEGIN:
|
||||||
|
if (!isSpaceOrEnter(code))
|
||||||
|
mSwitchState = SWITCH_STATE_NUMPAD;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the code is a letter, update keyboard shift state.
|
// If the code is a letter, update keyboard shift state.
|
||||||
|
@ -833,6 +845,7 @@ public final class KeyboardState {
|
||||||
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE -> "MOMENTARY-SYMBOL-MORE";
|
case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE -> "MOMENTARY-SYMBOL-MORE";
|
||||||
case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT -> "MOMENTARY-ALPHA_SHIFT";
|
case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT -> "MOMENTARY-ALPHA_SHIFT";
|
||||||
case SWITCH_STATE_NUMPAD -> "NUMPAD";
|
case SWITCH_STATE_NUMPAD -> "NUMPAD";
|
||||||
|
case SWITCH_STATE_NUMPAD_BEGIN -> "NUMPAD-BEGIN";
|
||||||
case SWITCH_STATE_MOMENTARY_TO_NUMPAD -> "MOMENTARY-TO-NUMPAD";
|
case SWITCH_STATE_MOMENTARY_TO_NUMPAD -> "MOMENTARY-TO-NUMPAD";
|
||||||
case SWITCH_STATE_MOMENTARY_FROM_NUMPAD -> "MOMENTARY-FROM-NUMPAD";
|
case SWITCH_STATE_MOMENTARY_FROM_NUMPAD -> "MOMENTARY-FROM-NUMPAD";
|
||||||
default -> null;
|
default -> null;
|
||||||
|
|
|
@ -68,11 +68,9 @@ public final class PopupKeySpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Key buildKey(final int x, final int y, final int labelFlags,
|
public Key buildKey(final int x, final int y, final int labelFlags, final int background, @NonNull final KeyboardParams params) {
|
||||||
@NonNull final KeyboardParams params) {
|
return new Key(mLabel, mIconName, mCode, mOutputText, null, labelFlags, background, x, y,
|
||||||
return new Key(mLabel, mIconName, mCode, mOutputText, null /* hintLabel */, labelFlags,
|
params.mDefaultAbsoluteKeyWidth, params.mDefaultAbsoluteRowHeight, params.mHorizontalGap, params.mVerticalGap);
|
||||||
Key.BACKGROUND_TYPE_NORMAL, x, y, params.mDefaultAbsoluteKeyWidth, params.mDefaultAbsoluteRowHeight,
|
|
||||||
params.mHorizontalGap, params.mVerticalGap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,38 +5,63 @@ import android.content.Context
|
||||||
import helium314.keyboard.keyboard.Key
|
import helium314.keyboard.keyboard.Key
|
||||||
import helium314.keyboard.keyboard.Key.KeyParams
|
import helium314.keyboard.keyboard.Key.KeyParams
|
||||||
import helium314.keyboard.keyboard.KeyboardId
|
import helium314.keyboard.keyboard.KeyboardId
|
||||||
|
import helium314.keyboard.keyboard.emoji.SupportedEmojis
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.common.Constants
|
||||||
import helium314.keyboard.latin.common.StringUtils
|
import helium314.keyboard.latin.common.StringUtils
|
||||||
|
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||||
|
import helium314.keyboard.latin.settings.Defaults
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.utils.ResourceUtils
|
import helium314.keyboard.latin.utils.ResourceUtils
|
||||||
|
import helium314.keyboard.latin.utils.prefs
|
||||||
|
import java.util.Collections
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
class EmojiParser(private val params: KeyboardParams, private val context: Context, private val maxSdk: Int) {
|
class EmojiParser(private val params: KeyboardParams, private val context: Context) {
|
||||||
|
|
||||||
fun parse(): ArrayList<ArrayList<KeyParams>> {
|
fun parse(): ArrayList<ArrayList<KeyParams>> {
|
||||||
val emojiArrayId = when (params.mId.mElementId) {
|
val emojiFileName = when (params.mId.mElementId) {
|
||||||
KeyboardId.ELEMENT_EMOJI_RECENTS -> R.array.emoji_recents
|
KeyboardId.ELEMENT_EMOJI_CATEGORY1 -> "SMILEYS_AND_EMOTION.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY1 -> R.array.emoji_smileys_emotion
|
KeyboardId.ELEMENT_EMOJI_CATEGORY2 -> "PEOPLE_AND_BODY.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY2 -> R.array.emoji_people_body
|
KeyboardId.ELEMENT_EMOJI_CATEGORY3 -> "ANIMALS_AND_NATURE.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY3 -> R.array.emoji_animals_nature
|
KeyboardId.ELEMENT_EMOJI_CATEGORY4 -> "FOOD_AND_DRINK.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY4 -> R.array.emoji_food_drink
|
KeyboardId.ELEMENT_EMOJI_CATEGORY5 -> "TRAVEL_AND_PLACES.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY5 -> R.array.emoji_travel_places
|
KeyboardId.ELEMENT_EMOJI_CATEGORY6 -> "ACTIVITIES.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY6 -> R.array.emoji_activities
|
KeyboardId.ELEMENT_EMOJI_CATEGORY7 -> "OBJECTS.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY7 -> R.array.emoji_objects
|
KeyboardId.ELEMENT_EMOJI_CATEGORY8 -> "SYMBOLS.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY8 -> R.array.emoji_symbols
|
KeyboardId.ELEMENT_EMOJI_CATEGORY9 -> "FLAGS.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY9 -> R.array.emoji_flags
|
KeyboardId.ELEMENT_EMOJI_CATEGORY10 -> "EMOTICONS.txt"
|
||||||
KeyboardId.ELEMENT_EMOJI_CATEGORY10 -> R.array.emoji_emoticons
|
else -> null
|
||||||
else -> throw(IllegalStateException("can only parse emoji categories where an array exists"))
|
|
||||||
}
|
}
|
||||||
val emojiArray = context.resources.getStringArray(emojiArrayId)
|
val emojiLines = if (emojiFileName == null) {
|
||||||
val popupEmojisArray = if (params.mId.mElementId != KeyboardId.ELEMENT_EMOJI_CATEGORY2) null
|
listOf( // special template keys for recents category
|
||||||
else context.resources.getStringArray(R.array.emoji_people_body_more)
|
StringUtils.newSingleCodePointString(Constants.RECENTS_TEMPLATE_KEY_CODE_0),
|
||||||
if (popupEmojisArray != null && emojiArray.size != popupEmojisArray.size)
|
StringUtils.newSingleCodePointString(Constants.RECENTS_TEMPLATE_KEY_CODE_1),
|
||||||
throw(IllegalStateException("Inconsistent array size between codesArray and popupKeysArray"))
|
)
|
||||||
|
} else {
|
||||||
|
context.assets.open("emoji/$emojiFileName").reader().use { it.readLines() }
|
||||||
|
}
|
||||||
|
val defaultSkinTone = context.prefs().getString(Settings.PREF_EMOJI_SKIN_TONE, Defaults.PREF_EMOJI_SKIN_TONE)!!
|
||||||
|
if (params.mId.mElementId == KeyboardId.ELEMENT_EMOJI_CATEGORY2 && defaultSkinTone != "") {
|
||||||
|
// adjust PEOPLE_AND_BODY if we have a non-yellow default skin tone
|
||||||
|
val modifiedLines = emojiLines.map {
|
||||||
|
val split = it.splitOnWhitespace().toMutableList()
|
||||||
|
// find the line containing the skin tone, and swap with first
|
||||||
|
val foundIndex = split.indexOfFirst { it.contains(defaultSkinTone) }
|
||||||
|
if (foundIndex > 0) {
|
||||||
|
Collections.swap(split, 0, foundIndex)
|
||||||
|
}
|
||||||
|
split.joinToString(" ")
|
||||||
|
}
|
||||||
|
return parseLines(modifiedLines)
|
||||||
|
}
|
||||||
|
return parseLines(emojiLines)
|
||||||
|
}
|
||||||
|
|
||||||
val row = ArrayList<KeyParams>(emojiArray.size)
|
private fun parseLines(lines: List<String>): ArrayList<ArrayList<KeyParams>> {
|
||||||
|
val row = ArrayList<KeyParams>(lines.size)
|
||||||
var currentX = params.mLeftPadding.toFloat()
|
var currentX = params.mLeftPadding.toFloat()
|
||||||
val currentY = params.mTopPadding.toFloat() // no need to ever change, assignment to rows into rows is done in DynamicGridKeyboard
|
val currentY = params.mTopPadding.toFloat() // no need to ever change, assignment to rows into rows is done in DynamicGridKeyboard
|
||||||
|
|
||||||
|
@ -44,14 +69,20 @@ class EmojiParser(private val params: KeyboardParams, private val context: Conte
|
||||||
// this is a bit long, but ensures that emoji size stays the same, independent of these settings
|
// this is a bit long, but ensures that emoji size stays the same, independent of these settings
|
||||||
// we also ignore side padding for key width, and prefer fewer keys per row over narrower keys
|
// we also ignore side padding for key width, and prefer fewer keys per row over narrower keys
|
||||||
val defaultKeyWidth = ResourceUtils.getDefaultKeyboardWidth(context) * params.mDefaultKeyWidth
|
val defaultKeyWidth = ResourceUtils.getDefaultKeyboardWidth(context) * params.mDefaultKeyWidth
|
||||||
val keyWidth = defaultKeyWidth * sqrt(Settings.getValues().mKeyboardHeightScale)
|
var keyWidth = defaultKeyWidth * sqrt(Settings.getValues().mKeyboardHeightScale)
|
||||||
val defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(context.resources, false)
|
val defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(context.resources, false)
|
||||||
val defaultBottomPadding = context.resources.getFraction(R.fraction.config_keyboard_bottom_padding_holo, defaultKeyboardHeight, defaultKeyboardHeight)
|
val defaultBottomPadding = context.resources.getFraction(R.fraction.config_keyboard_bottom_padding_holo, defaultKeyboardHeight, defaultKeyboardHeight)
|
||||||
val emojiKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(context.resources, false) * 0.75f + params.mVerticalGap - defaultBottomPadding - context.resources.getDimensionPixelSize(R.dimen.config_emoji_category_page_id_height)
|
val emojiKeyboardHeight = defaultKeyboardHeight * 0.75f + params.mVerticalGap - defaultBottomPadding - context.resources.getDimensionPixelSize(R.dimen.config_emoji_category_page_id_height)
|
||||||
val keyHeight = emojiKeyboardHeight * params.mDefaultRowHeight * Settings.getValues().mKeyboardHeightScale // still apply height scale to key
|
var keyHeight = emojiKeyboardHeight * params.mDefaultRowHeight * Settings.getValues().mKeyboardHeightScale // still apply height scale to key
|
||||||
|
|
||||||
emojiArray.forEachIndexed { i, codeArraySpec ->
|
if (Settings.getValues().mEmojiKeyFit) {
|
||||||
val keyParams = parseEmojiKey(codeArraySpec, popupEmojisArray?.get(i)?.takeIf { it.isNotEmpty() }) ?: return@forEachIndexed
|
keyWidth *= Settings.getValues().mFontSizeMultiplierEmoji
|
||||||
|
keyHeight *= Settings.getValues().mFontSizeMultiplierEmoji
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lines.forEach { line ->
|
||||||
|
val keyParams = parseEmojiKeyNew(line) ?: return@forEach
|
||||||
keyParams.xPos = currentX
|
keyParams.xPos = currentX
|
||||||
keyParams.yPos = currentY
|
keyParams.yPos = currentY
|
||||||
keyParams.mAbsoluteWidth = keyWidth
|
keyParams.mAbsoluteWidth = keyWidth
|
||||||
|
@ -62,44 +93,30 @@ class EmojiParser(private val params: KeyboardParams, private val context: Conte
|
||||||
return arrayListOf(row)
|
return arrayListOf(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLabelAndCode(spec: String): Pair<String, Int>? {
|
private fun parseEmojiKeyNew(line: String): KeyParams? {
|
||||||
val specAndSdk = spec.split("||")
|
if (!line.contains(" ") || params.mId.mElementId == KeyboardId.ELEMENT_EMOJI_CATEGORY10) {
|
||||||
if (specAndSdk.getOrNull(1)?.toIntOrNull()?.let { it > maxSdk } == true) return null
|
// single emoji without popups, or emoticons (there is one that contains space...)
|
||||||
if ("," !in specAndSdk.first()) {
|
return if (SupportedEmojis.isUnsupported(line)) null
|
||||||
val code = specAndSdk.first().toIntOrNull(16) ?: return specAndSdk.first() to KeyCode.MULTIPLE_CODE_POINTS // text emojis
|
else KeyParams(line, line.getCode(), null, null, Key.LABEL_FLAGS_FONT_NORMAL, params)
|
||||||
val label = StringUtils.newSingleCodePointString(code)
|
|
||||||
return label to code
|
|
||||||
}
|
}
|
||||||
val labelBuilder = StringBuilder()
|
val split = line.split(" ")
|
||||||
for (codePointString in specAndSdk.first().split(",")) {
|
val label = split.first()
|
||||||
val cp = codePointString.toInt(16)
|
if (SupportedEmojis.isUnsupported(label)) return null
|
||||||
labelBuilder.appendCodePoint(cp)
|
val popupKeysSpec = split.drop(1).filterNot { SupportedEmojis.isUnsupported(it) }
|
||||||
}
|
.takeIf { it.isNotEmpty() }?.joinToString(",")
|
||||||
return labelBuilder.toString() to KeyCode.MULTIPLE_CODE_POINTS
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseEmojiKey(spec: String, popupKeysString: String? = null): KeyParams? {
|
|
||||||
val (label, code) = getLabelAndCode(spec) ?: return null
|
|
||||||
val sb = StringBuilder()
|
|
||||||
popupKeysString?.split(";")?.let { popupKeys ->
|
|
||||||
popupKeys.forEach {
|
|
||||||
val (mkLabel, _) = getLabelAndCode(it) ?: return@forEach
|
|
||||||
sb.append(mkLabel).append(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val popupKeysSpec = if (sb.isNotEmpty()) {
|
|
||||||
sb.deleteCharAt(sb.length - 1)
|
|
||||||
sb.toString()
|
|
||||||
} else null
|
|
||||||
return KeyParams(
|
return KeyParams(
|
||||||
label,
|
label,
|
||||||
code,
|
label.getCode(),
|
||||||
if (popupKeysSpec != null) EMOJI_HINT_LABEL else null,
|
if (popupKeysSpec != null) EMOJI_HINT_LABEL else null,
|
||||||
popupKeysSpec,
|
popupKeysSpec,
|
||||||
Key.LABEL_FLAGS_FONT_NORMAL,
|
Key.LABEL_FLAGS_FONT_NORMAL,
|
||||||
params
|
params
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.getCode(): Int =
|
||||||
|
if (StringUtils.codePointCount(this) != 1) KeyCode.MULTIPLE_CODE_POINTS
|
||||||
|
else Character.codePointAt(this, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const val EMOJI_HINT_LABEL = "◥"
|
const val EMOJI_HINT_LABEL = "◥"
|
||||||
|
|
|
@ -229,15 +229,17 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co
|
||||||
return
|
return
|
||||||
// replace comma / period if 2 keys in normal bottom row
|
// replace comma / period if 2 keys in normal bottom row
|
||||||
if (baseKeys.last().size == 2) {
|
if (baseKeys.last().size == 2) {
|
||||||
|
val newComma = baseKeys.last()[0]
|
||||||
functionalKeysBottom.replaceFirst(
|
functionalKeysBottom.replaceFirst(
|
||||||
{ it.label == KeyLabel.COMMA || it.groupId == KeyData.GROUP_COMMA},
|
{ it.label == KeyLabel.COMMA || it.groupId == KeyData.GROUP_COMMA},
|
||||||
{ baseKeys.last()[0].copy(newGroupId = 1, newType = baseKeys.last()[0].type ?: it.type) }
|
{ newComma.copy(newGroupId = 1, newType = newComma.type, newLabelFlags = it.labelFlags or newComma.labelFlags) }
|
||||||
)
|
)
|
||||||
|
val newPeriod = baseKeys.last()[1]
|
||||||
functionalKeysBottom.replaceFirst(
|
functionalKeysBottom.replaceFirst(
|
||||||
{ it.label == KeyLabel.PERIOD || it.groupId == KeyData.GROUP_PERIOD},
|
{ it.label == KeyLabel.PERIOD || it.groupId == KeyData.GROUP_PERIOD},
|
||||||
{ baseKeys.last()[1].copy(newGroupId = 2, newType = baseKeys.last()[1].type ?: it.type) }
|
{ newPeriod.copy(newGroupId = 2, newType = newPeriod.type, newLabelFlags = it.labelFlags or newPeriod.labelFlags) }
|
||||||
)
|
)
|
||||||
baseKeys.removeLast()
|
baseKeys.removeAt(baseKeys.lastIndex)
|
||||||
}
|
}
|
||||||
// add zwnj key next to space if necessary
|
// add zwnj key next to space if necessary
|
||||||
val spaceIndex = functionalKeysBottom.indexOfFirst { it.label == KeyLabel.SPACE && it.width <= 0 } // width could be 0 or -1
|
val spaceIndex = functionalKeysBottom.indexOfFirst { it.label == KeyLabel.SPACE && it.width <= 0 } // width could be 0 or -1
|
||||||
|
|
|
@ -59,8 +59,7 @@ object LayoutParser {
|
||||||
|
|
||||||
/** Parse simple layouts, defined only as rows of (normal) keys with popup keys. */
|
/** Parse simple layouts, defined only as rows of (normal) keys with popup keys. */
|
||||||
fun parseSimpleString(layoutText: String): List<List<KeyData>> {
|
fun parseSimpleString(layoutText: String): List<List<KeyData>> {
|
||||||
val rowStrings = layoutText.replace("\r\n", "\n").split("\\n\\s*\\n".toRegex()).filter { it.isNotBlank() }
|
return LayoutUtils.getSimpleRowStrings(layoutText).map { row ->
|
||||||
return rowStrings.map { row ->
|
|
||||||
row.split("\n").mapNotNull { parseKey(it) }
|
row.split("\n").mapNotNull { parseKey(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import helium314.keyboard.latin.R
|
||||||
import helium314.keyboard.latin.common.splitOnFirstSpacesOnly
|
import helium314.keyboard.latin.common.splitOnFirstSpacesOnly
|
||||||
import helium314.keyboard.latin.common.splitOnWhitespace
|
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
import helium314.keyboard.latin.utils.SpacedTokens
|
||||||
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
|
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -44,7 +45,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
|
||||||
"mns" -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
|
"mns" -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
val tlds = getLocaleTlds(locale) // todo: USE IT
|
val tlds = mutableListOf(Key.POPUP_KEYS_HAS_LABELS)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
readStream(dataStream, false, true)
|
readStream(dataStream, false, true)
|
||||||
|
@ -83,18 +84,12 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
|
||||||
READER_MODE_EXTRA_KEYS -> if (!onlyPopupKeys) addExtraKey(line.split(colonSpaceRegex, 2))
|
READER_MODE_EXTRA_KEYS -> if (!onlyPopupKeys) addExtraKey(line.split(colonSpaceRegex, 2))
|
||||||
READER_MODE_LABELS -> if (!onlyPopupKeys) addLabel(line.split(colonSpaceRegex, 2))
|
READER_MODE_LABELS -> if (!onlyPopupKeys) addLabel(line.split(colonSpaceRegex, 2))
|
||||||
READER_MODE_NUMBER_ROW -> localizedNumberKeys = line.splitOnWhitespace()
|
READER_MODE_NUMBER_ROW -> localizedNumberKeys = line.splitOnWhitespace()
|
||||||
READER_MODE_TLD -> line.splitOnWhitespace().forEach { tlds.add(".$it") }
|
READER_MODE_TLD -> tlds.addAll(SpacedTokens(line).map { ".$it" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addDefaultTlds(locale: Locale) {
|
|
||||||
if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country))
|
|
||||||
tlds.add(".eu")
|
|
||||||
tlds.addAll(defaultTlds.splitOnWhitespace())
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Pair(extraKeysLeft, extraKeysRight) */
|
/** Pair(extraKeysLeft, extraKeysRight) */
|
||||||
fun getTabletExtraKeys(elementId: Int): Pair<List<KeyData>, List<KeyData>> {
|
fun getTabletExtraKeys(elementId: Int): Pair<List<KeyData>, List<KeyData>> {
|
||||||
val flags = Key.LABEL_FLAGS_FONT_DEFAULT
|
val flags = Key.LABEL_FLAGS_FONT_DEFAULT
|
||||||
|
@ -161,6 +156,16 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addLocaleTlds(locale: Locale) {
|
||||||
|
tlds.add(0, comTld)
|
||||||
|
val ccLower = locale.country.lowercase()
|
||||||
|
if (ccLower.isNotEmpty() && locale.language != SubtypeLocaleUtils.NO_LANGUAGE) {
|
||||||
|
specialCountryTlds[ccLower]?.let { tlds.addAll(SpacedTokens(it)) } ?: tlds.add(".$ccLower")
|
||||||
|
}
|
||||||
|
if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country))
|
||||||
|
tlds.add(".eu")
|
||||||
|
tlds.addAll(SpacedTokens(otherDefaultTlds))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addFixedColumnOrder(popupKeys: MutableCollection<String>) {
|
private fun addFixedColumnOrder(popupKeys: MutableCollection<String>) {
|
||||||
|
@ -205,12 +210,12 @@ private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, popup
|
||||||
if (locale == params.mId.locale) return@forEach
|
if (locale == params.mId.locale) return@forEach
|
||||||
lkt.addFile(getStreamForLocale(locale, context), true)
|
lkt.addFile(getStreamForLocale(locale, context), true)
|
||||||
}
|
}
|
||||||
lkt.addDefaultTlds(params.mId.locale)
|
|
||||||
when (popupKeysSetting) {
|
when (popupKeysSetting) {
|
||||||
POPUP_KEYS_MAIN -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_main.txt"), false)
|
POPUP_KEYS_MAIN -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_main.txt"), false)
|
||||||
POPUP_KEYS_MORE -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt"), false)
|
POPUP_KEYS_MORE -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt"), false)
|
||||||
POPUP_KEYS_ALL -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt"), false)
|
POPUP_KEYS_ALL -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt"), false)
|
||||||
}
|
}
|
||||||
|
lkt.addLocaleTlds(params.mId.locale)
|
||||||
return lkt
|
return lkt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,26 +225,12 @@ private fun getStreamForLocale(locale: Locale, context: Context) =
|
||||||
else context.assets.open("$LOCALE_TEXTS_FOLDER/${locale.toLanguageTag()}.txt")
|
else context.assets.open("$LOCALE_TEXTS_FOLDER/${locale.toLanguageTag()}.txt")
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
try {
|
try {
|
||||||
context.assets.open("$LOCALE_TEXTS_FOLDER/${locale.language}.txt")
|
context.assets.open("$LOCALE_TEXTS_FOLDER/${if (locale.language == "he") "iw" else locale.language}.txt")
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLocaleTlds(locale: Locale): LinkedHashSet<String> {
|
|
||||||
val ccLower = locale.country.lowercase()
|
|
||||||
val tlds = LinkedHashSet<String>()
|
|
||||||
if (ccLower.isEmpty() || locale.language == SubtypeLocaleUtils.NO_LANGUAGE)
|
|
||||||
return tlds
|
|
||||||
specialCountryTlds.forEach {
|
|
||||||
if (ccLower != it.first) return@forEach
|
|
||||||
tlds.addAll(it.second.splitOnWhitespace())
|
|
||||||
return tlds
|
|
||||||
}
|
|
||||||
tlds.add(".$ccLower")
|
|
||||||
return tlds
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearCache() = localeKeyboardInfosCache.clear()
|
fun clearCache() = localeKeyboardInfosCache.clear()
|
||||||
|
|
||||||
// cache the texts, so they don't need to be read over and over
|
// cache the texts, so they don't need to be read over and over
|
||||||
|
@ -263,9 +254,9 @@ private fun getCurrencyKey(locale: Locale): Pair<String, List<String>> {
|
||||||
return euro
|
return euro
|
||||||
if (locale.toString().matches(euroLocales))
|
if (locale.toString().matches(euroLocales))
|
||||||
return euro
|
return euro
|
||||||
if (locale.language.matches("ca|eu|lb|mt".toRegex()))
|
if (locale.language.matches("ca|eu|lb|mt|pms".toRegex()))
|
||||||
return euro
|
return euro
|
||||||
if (locale.language.matches("fa|iw|ko|lo|mn|ne|si|th|uk|vi|km".toRegex()))
|
if (locale.language.matches("ak|dag|ee|fa|gaa|ha|he|ig|iw|lo|ko|km|mn|ne|si|th|uk|vi|yo".toRegex()))
|
||||||
return genericCurrencyKey(getCurrency(locale))
|
return genericCurrencyKey(getCurrency(locale))
|
||||||
if (locale.language == "hy")
|
if (locale.language == "hy")
|
||||||
return dram
|
return dram
|
||||||
|
@ -291,17 +282,24 @@ private fun getCurrency(locale: Locale): String {
|
||||||
if (locale.country == "BD") return "৳"
|
if (locale.country == "BD") return "৳"
|
||||||
if (locale.country == "LK") return "රු"
|
if (locale.country == "LK") return "රු"
|
||||||
return when (locale.language) {
|
return when (locale.language) {
|
||||||
|
"ak" -> "¢"
|
||||||
|
"dag" -> "¢"
|
||||||
|
"ee" -> "¢"
|
||||||
"fa" -> "﷼"
|
"fa" -> "﷼"
|
||||||
"iw" -> "₪"
|
"gaa" -> "¢"
|
||||||
"ko" -> "₩"
|
"ha" -> "₦"
|
||||||
|
"ig" -> "₦"
|
||||||
|
"iw", "he" -> "₪"
|
||||||
"lo" -> "₭"
|
"lo" -> "₭"
|
||||||
|
"km" -> "៛"
|
||||||
|
"ko" -> "₩"
|
||||||
"mn" -> "₮"
|
"mn" -> "₮"
|
||||||
"ne" -> "रु."
|
"ne" -> "रु."
|
||||||
"si" -> "රු"
|
"si" -> "රු"
|
||||||
"th" -> "฿"
|
"th" -> "฿"
|
||||||
"uk" -> "₴"
|
"uk" -> "₴"
|
||||||
"vi" -> "₫"
|
"vi" -> "₫"
|
||||||
"km" -> "៛"
|
"yo" -> "₦"
|
||||||
else -> "$"
|
else -> "$"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +330,7 @@ const val POPUP_KEYS_NORMAL = "normal"
|
||||||
private const val LOCALE_TEXTS_FOLDER = "locale_key_texts"
|
private const val LOCALE_TEXTS_FOLDER = "locale_key_texts"
|
||||||
|
|
||||||
// either tld is not simply lowercase ISO 3166-1 code, or there are multiple according to some list
|
// either tld is not simply lowercase ISO 3166-1 code, or there are multiple according to some list
|
||||||
private val specialCountryTlds = listOf(
|
private val specialCountryTlds = hashMapOf<String, String>(
|
||||||
"bd" to ".bd .com.bd",
|
"bd" to ".bd .com.bd",
|
||||||
"bq" to ".bq .an .nl",
|
"bq" to ".bq .an .nl",
|
||||||
"bl" to ".bl .gp .fr",
|
"bl" to ".bl .gp .fr",
|
||||||
|
@ -342,4 +340,5 @@ private val specialCountryTlds = listOf(
|
||||||
"mf" to ".mf .gp .fr",
|
"mf" to ".mf .gp .fr",
|
||||||
"tl" to ".tl .tp",
|
"tl" to ".tl .tp",
|
||||||
)
|
)
|
||||||
private const val defaultTlds = ".com .gov .edu .org .net"
|
private const val comTld = ".com"
|
||||||
|
private const val otherDefaultTlds = ".gov .edu .org .net"
|
||||||
|
|
|
@ -24,15 +24,15 @@ object KeyCode {
|
||||||
const val UNSPECIFIED = 0
|
const val UNSPECIFIED = 0
|
||||||
|
|
||||||
const val CTRL = -1
|
const val CTRL = -1
|
||||||
const val CTRL_LOCK = -2
|
//const val CTRL_LOCK = -2
|
||||||
const val ALT = -3
|
const val ALT = -3
|
||||||
const val ALT_LOCK = -4
|
//const val ALT_LOCK = -4
|
||||||
const val FN = -5
|
const val FN = -5
|
||||||
const val FN_LOCK = -6
|
//const val FN_LOCK = -6
|
||||||
const val DELETE = -7
|
const val DELETE = -7
|
||||||
const val DELETE_WORD = -8
|
//const val DELETE_WORD = -8
|
||||||
const val FORWARD_DELETE = -9
|
//const val FORWARD_DELETE = -9
|
||||||
const val FORWARD_DELETE_WORD = -10
|
//const val FORWARD_DELETE_WORD = -10
|
||||||
const val SHIFT = -11
|
const val SHIFT = -11
|
||||||
const val CAPS_LOCK = -13
|
const val CAPS_LOCK = -13
|
||||||
|
|
||||||
|
@ -51,21 +51,21 @@ object KeyCode {
|
||||||
const val CLIPBOARD_SELECT_WORD = -34 // CLIPBOARD_SELECT
|
const val CLIPBOARD_SELECT_WORD = -34 // CLIPBOARD_SELECT
|
||||||
const val CLIPBOARD_SELECT_ALL = -35
|
const val CLIPBOARD_SELECT_ALL = -35
|
||||||
const val CLIPBOARD_CLEAR_HISTORY = -36
|
const val CLIPBOARD_CLEAR_HISTORY = -36
|
||||||
const val CLIPBOARD_CLEAR_FULL_HISTORY = -37
|
//const val CLIPBOARD_CLEAR_FULL_HISTORY = -37
|
||||||
const val CLIPBOARD_CLEAR_PRIMARY_CLIP = -38
|
//const val CLIPBOARD_CLEAR_PRIMARY_CLIP = -38
|
||||||
|
|
||||||
const val COMPACT_LAYOUT_TO_LEFT = -111
|
//const val COMPACT_LAYOUT_TO_LEFT = -111
|
||||||
const val COMPACT_LAYOUT_TO_RIGHT = -112
|
//const val COMPACT_LAYOUT_TO_RIGHT = -112
|
||||||
const val SPLIT_LAYOUT = -113
|
const val SPLIT_LAYOUT = -113
|
||||||
const val MERGE_LAYOUT = -114
|
//const val MERGE_LAYOUT = -114
|
||||||
|
|
||||||
const val UNDO = -131
|
const val UNDO = -131
|
||||||
const val REDO = -132
|
const val REDO = -132
|
||||||
|
|
||||||
const val ALPHA = -201 // VIEW_CHARACTERS
|
const val ALPHA = -201 // VIEW_CHARACTERS
|
||||||
const val SYMBOL = -202 // VIEW_SYMBOLS
|
const val SYMBOL = -202 // VIEW_SYMBOLS
|
||||||
const val VIEW_SYMBOLS2 = -203
|
//const val VIEW_SYMBOLS2 = -203
|
||||||
const val VIEW_NUMERIC = -204
|
//const val VIEW_NUMERIC = -204
|
||||||
const val NUMPAD = -205 // VIEW_NUMERIC_ADVANCED
|
const val NUMPAD = -205 // VIEW_NUMERIC_ADVANCED
|
||||||
const val VIEW_PHONE = -206
|
const val VIEW_PHONE = -206
|
||||||
const val VIEW_PHONE2 = -207
|
const val VIEW_PHONE2 = -207
|
||||||
|
@ -74,21 +74,21 @@ object KeyCode {
|
||||||
const val EMOJI = -212 // IME_UI_MODE_MEDIA
|
const val EMOJI = -212 // IME_UI_MODE_MEDIA
|
||||||
const val CLIPBOARD = -213 // IME_UI_MODE_CLIPBOARD
|
const val CLIPBOARD = -213 // IME_UI_MODE_CLIPBOARD
|
||||||
|
|
||||||
const val SYSTEM_INPUT_METHOD_PICKER = -221
|
//const val SYSTEM_INPUT_METHOD_PICKER = -221
|
||||||
const val SYSTEM_PREV_INPUT_METHOD = -222
|
//const val SYSTEM_PREV_INPUT_METHOD = -222
|
||||||
const val SYSTEM_NEXT_INPUT_METHOD = -223
|
//const val SYSTEM_NEXT_INPUT_METHOD = -223
|
||||||
const val IME_SUBTYPE_PICKER = -224
|
//const val IME_SUBTYPE_PICKER = -224
|
||||||
const val IME_PREV_SUBTYPE = -225
|
//const val IME_PREV_SUBTYPE = -225
|
||||||
const val IME_NEXT_SUBTYPE = -226
|
//const val IME_NEXT_SUBTYPE = -226
|
||||||
const val LANGUAGE_SWITCH = -227
|
const val LANGUAGE_SWITCH = -227
|
||||||
|
|
||||||
const val IME_SHOW_UI = -231
|
//const val IME_SHOW_UI = -231
|
||||||
const val IME_HIDE_UI = -232
|
const val IME_HIDE_UI = -232
|
||||||
const val VOICE_INPUT = -233
|
const val VOICE_INPUT = -233
|
||||||
|
|
||||||
const val TOGGLE_SMARTBAR_VISIBILITY = -241
|
//const val TOGGLE_SMARTBAR_VISIBILITY = -241
|
||||||
const val TOGGLE_ACTIONS_OVERFLOW = -242
|
//const val TOGGLE_ACTIONS_OVERFLOW = -242
|
||||||
const val TOGGLE_ACTIONS_EDITOR = -243
|
//const val TOGGLE_ACTIONS_EDITOR = -243
|
||||||
const val TOGGLE_INCOGNITO_MODE = -244
|
const val TOGGLE_INCOGNITO_MODE = -244
|
||||||
const val TOGGLE_AUTOCORRECT = -245
|
const val TOGGLE_AUTOCORRECT = -245
|
||||||
|
|
||||||
|
@ -104,18 +104,18 @@ object KeyCode {
|
||||||
const val CURRENCY_SLOT_6 = -806
|
const val CURRENCY_SLOT_6 = -806
|
||||||
|
|
||||||
const val MULTIPLE_CODE_POINTS = -902
|
const val MULTIPLE_CODE_POINTS = -902
|
||||||
const val DRAG_MARKER = -991
|
//const val DRAG_MARKER = -991
|
||||||
const val NOOP = -999
|
//const val NOOP = -999
|
||||||
|
|
||||||
const val CHAR_WIDTH_SWITCHER = -9701
|
//const val CHAR_WIDTH_SWITCHER = -9701
|
||||||
const val CHAR_WIDTH_FULL = -9702
|
//const val CHAR_WIDTH_FULL = -9702
|
||||||
const val CHAR_WIDTH_HALF = -9703
|
//const val CHAR_WIDTH_HALF = -9703
|
||||||
|
|
||||||
const val KANA_SMALL = 12307
|
//const val KANA_SMALL = 12307
|
||||||
const val KANA_SWITCHER = -9710
|
//const val KANA_SWITCHER = -9710
|
||||||
const val KANA_HIRA = -9711
|
//const val KANA_HIRA = -9711
|
||||||
const val KANA_KATA = -9712
|
//const val KANA_KATA = -9712
|
||||||
const val KANA_HALF_KATA = -9713
|
//const val KANA_HALF_KATA = -9713
|
||||||
|
|
||||||
const val KESHIDA = 1600
|
const val KESHIDA = 1600
|
||||||
const val ZWNJ = 8204 // 0x200C, named HALF_SPACE in FlorisBoard
|
const val ZWNJ = 8204 // 0x200C, named HALF_SPACE in FlorisBoard
|
||||||
|
@ -137,7 +137,7 @@ object KeyCode {
|
||||||
const val PAGE_UP = -10010
|
const val PAGE_UP = -10010
|
||||||
const val PAGE_DOWN = -10011
|
const val PAGE_DOWN = -10011
|
||||||
const val META = -10012
|
const val META = -10012
|
||||||
const val META_LOCK = -10013 // to be consistent with the CTRL/ALT/FN LOCK codes, not sure whether this will be used
|
//const val META_LOCK = -10013 // to be consistent with the CTRL/ALT/FN LOCK codes, not sure whether this will be used
|
||||||
const val TAB = -10014
|
const val TAB = -10014
|
||||||
const val WORD_LEFT = -10015
|
const val WORD_LEFT = -10015
|
||||||
const val WORD_RIGHT = -10016
|
const val WORD_RIGHT = -10016
|
||||||
|
@ -165,8 +165,21 @@ object KeyCode {
|
||||||
const val F11 = -10038
|
const val F11 = -10038
|
||||||
const val F12 = -10039
|
const val F12 = -10039
|
||||||
const val BACK = -10040
|
const val BACK = -10040
|
||||||
const val SELECT_LEFT = -10041
|
//const val SELECT_LEFT = -10041
|
||||||
const val SELECT_RIGHT = -10042
|
//const val SELECT_RIGHT = -10042
|
||||||
|
const val TIMESTAMP = -10043
|
||||||
|
const val CTRL_LEFT = -10044
|
||||||
|
const val CTRL_RIGHT = -10045
|
||||||
|
const val ALT_LEFT = -10046
|
||||||
|
const val ALT_RIGHT = -10047
|
||||||
|
const val META_LEFT = -10048
|
||||||
|
const val META_RIGHT = -10049
|
||||||
|
|
||||||
|
|
||||||
|
// Intents
|
||||||
|
const val SEND_INTENT_ONE = -20000
|
||||||
|
const val SEND_INTENT_TWO = -20001
|
||||||
|
const val SEND_INTENT_THREE = -20002
|
||||||
|
|
||||||
/** to make sure a FlorisBoard code works when reading a JSON layout */
|
/** to make sure a FlorisBoard code works when reading a JSON layout */
|
||||||
fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) {
|
fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) {
|
||||||
|
@ -176,13 +189,14 @@ object KeyCode {
|
||||||
REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_PASTE, CLIPBOARD_SELECT_ALL,
|
REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_PASTE, CLIPBOARD_SELECT_ALL,
|
||||||
CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE,
|
CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE,
|
||||||
MOVE_START_OF_PAGE, MOVE_END_OF_PAGE, SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED, CTRL, ALT,
|
MOVE_START_OF_PAGE, MOVE_END_OF_PAGE, SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED, CTRL, ALT,
|
||||||
FN, CLIPBOARD_CLEAR_HISTORY, NUMPAD,
|
FN, CLIPBOARD_CLEAR_HISTORY, NUMPAD, IME_HIDE_UI,
|
||||||
|
|
||||||
// heliboard only
|
// heliboard only
|
||||||
SYMBOL_ALPHA, TOGGLE_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SPLIT_LAYOUT, SHIFT_ENTER,
|
SYMBOL_ALPHA, TOGGLE_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SPLIT_LAYOUT, SHIFT_ENTER,
|
||||||
ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, WORD_LEFT, WORD_RIGHT, PAGE_UP,
|
ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, WORD_LEFT, WORD_RIGHT, PAGE_UP,
|
||||||
PAGE_DOWN, META, TAB, ESCAPE, INSERT, SLEEP, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_PLAY_PAUSE, MEDIA_NEXT,
|
PAGE_DOWN, META, TAB, ESCAPE, INSERT, SLEEP, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_PLAY_PAUSE, MEDIA_NEXT,
|
||||||
MEDIA_PREVIOUS, VOL_UP, VOL_DOWN, MUTE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BACK
|
MEDIA_PREVIOUS, VOL_UP, VOL_DOWN, MUTE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BACK,
|
||||||
|
TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT, SEND_INTENT_ONE, SEND_INTENT_TWO, SEND_INTENT_THREE,
|
||||||
-> this
|
-> this
|
||||||
|
|
||||||
// conversion
|
// conversion
|
||||||
|
@ -194,64 +208,19 @@ object KeyCode {
|
||||||
else -> throw IllegalStateException("key code $this not yet supported")
|
else -> throw IllegalStateException("key code $this not yet supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: three are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0
|
fun Int.isModifier() = when (this) {
|
||||||
/** convert a keyCode / codePoint to a KeyEvent.KEYCODE_<xxx>, fallback to KeyEvent.KEYCODE_UNKNOWN */
|
SHIFT, SYMBOL_ALPHA, ALPHA, SYMBOL, NUMPAD, FN, CTRL, CTRL_LEFT, CTRL_RIGHT, ALT, ALT_LEFT, ALT_RIGHT,
|
||||||
fun Int.toKeyEventCode(): Int = if (this > 0)
|
META, META_LEFT, META_RIGHT -> true
|
||||||
when (this.toChar().uppercaseChar()) {
|
else -> false
|
||||||
'/' -> KeyEvent.KEYCODE_SLASH
|
}
|
||||||
'\\' -> KeyEvent.KEYCODE_BACKSLASH
|
|
||||||
';' -> KeyEvent.KEYCODE_SEMICOLON
|
// todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0
|
||||||
',' -> KeyEvent.KEYCODE_COMMA
|
/**
|
||||||
'.' -> KeyEvent.KEYCODE_PERIOD
|
* Convert an internal keyCode to a KeyEvent.KEYCODE_<xxx>.
|
||||||
'\'' -> KeyEvent.KEYCODE_APOSTROPHE
|
* Positive codes are passed through, unknown negative codes result in KeyEvent.KEYCODE_UNKNOWN.
|
||||||
'`' -> KeyEvent.KEYCODE_GRAVE
|
* To be uses for fake hardware key press.
|
||||||
'*' -> KeyEvent.KEYCODE_STAR
|
*/
|
||||||
']' -> KeyEvent.KEYCODE_RIGHT_BRACKET
|
@JvmStatic fun keyCodeToKeyEventCode(keyCode: Int) = when (keyCode) {
|
||||||
'[' -> KeyEvent.KEYCODE_LEFT_BRACKET
|
|
||||||
'+' -> KeyEvent.KEYCODE_PLUS
|
|
||||||
'-' -> KeyEvent.KEYCODE_MINUS
|
|
||||||
'=' -> KeyEvent.KEYCODE_EQUALS
|
|
||||||
'\n' -> KeyEvent.KEYCODE_ENTER
|
|
||||||
'\t' -> KeyEvent.KEYCODE_TAB
|
|
||||||
'0' -> KeyEvent.KEYCODE_0
|
|
||||||
'1' -> KeyEvent.KEYCODE_1
|
|
||||||
'2' -> KeyEvent.KEYCODE_2
|
|
||||||
'3' -> KeyEvent.KEYCODE_3
|
|
||||||
'4' -> KeyEvent.KEYCODE_4
|
|
||||||
'5' -> KeyEvent.KEYCODE_5
|
|
||||||
'6' -> KeyEvent.KEYCODE_6
|
|
||||||
'7' -> KeyEvent.KEYCODE_7
|
|
||||||
'8' -> KeyEvent.KEYCODE_8
|
|
||||||
'9' -> KeyEvent.KEYCODE_9
|
|
||||||
'A' -> KeyEvent.KEYCODE_A
|
|
||||||
'B' -> KeyEvent.KEYCODE_B
|
|
||||||
'C' -> KeyEvent.KEYCODE_C
|
|
||||||
'D' -> KeyEvent.KEYCODE_D
|
|
||||||
'E' -> KeyEvent.KEYCODE_E
|
|
||||||
'F' -> KeyEvent.KEYCODE_F
|
|
||||||
'G' -> KeyEvent.KEYCODE_G
|
|
||||||
'H' -> KeyEvent.KEYCODE_H
|
|
||||||
'I' -> KeyEvent.KEYCODE_I
|
|
||||||
'J' -> KeyEvent.KEYCODE_J
|
|
||||||
'K' -> KeyEvent.KEYCODE_K
|
|
||||||
'L' -> KeyEvent.KEYCODE_L
|
|
||||||
'M' -> KeyEvent.KEYCODE_M
|
|
||||||
'N' -> KeyEvent.KEYCODE_N
|
|
||||||
'O' -> KeyEvent.KEYCODE_O
|
|
||||||
'P' -> KeyEvent.KEYCODE_P
|
|
||||||
'Q' -> KeyEvent.KEYCODE_Q
|
|
||||||
'R' -> KeyEvent.KEYCODE_R
|
|
||||||
'S' -> KeyEvent.KEYCODE_S
|
|
||||||
'T' -> KeyEvent.KEYCODE_T
|
|
||||||
'U' -> KeyEvent.KEYCODE_U
|
|
||||||
'V' -> KeyEvent.KEYCODE_V
|
|
||||||
'W' -> KeyEvent.KEYCODE_W
|
|
||||||
'X' -> KeyEvent.KEYCODE_X
|
|
||||||
'Y' -> KeyEvent.KEYCODE_Y
|
|
||||||
'Z' -> KeyEvent.KEYCODE_Z
|
|
||||||
else -> KeyEvent.KEYCODE_UNKNOWN
|
|
||||||
}
|
|
||||||
else when (this) {
|
|
||||||
ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP
|
ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP
|
||||||
ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT
|
ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT
|
||||||
ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN
|
ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN
|
||||||
|
@ -285,6 +254,67 @@ object KeyCode {
|
||||||
F10 -> KeyEvent.KEYCODE_F10
|
F10 -> KeyEvent.KEYCODE_F10
|
||||||
F11 -> KeyEvent.KEYCODE_F11
|
F11 -> KeyEvent.KEYCODE_F11
|
||||||
F12 -> KeyEvent.KEYCODE_F12
|
F12 -> KeyEvent.KEYCODE_F12
|
||||||
|
else -> if (keyCode < 0) KeyEvent.KEYCODE_UNKNOWN else keyCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0
|
||||||
|
/**
|
||||||
|
* Convert a codePoint to a KeyEvent.KEYCODE_<xxx>.
|
||||||
|
* Fallback to KeyEvent.KEYCODE_UNKNOWN.
|
||||||
|
* To be uses for fake hardware key press.
|
||||||
|
*/
|
||||||
|
@JvmStatic fun codePointToKeyEventCode(codePoint: Int): Int = when (codePoint.toChar().uppercaseChar()) {
|
||||||
|
'/' -> KeyEvent.KEYCODE_SLASH
|
||||||
|
'\\' -> KeyEvent.KEYCODE_BACKSLASH
|
||||||
|
';' -> KeyEvent.KEYCODE_SEMICOLON
|
||||||
|
',' -> KeyEvent.KEYCODE_COMMA
|
||||||
|
'.' -> KeyEvent.KEYCODE_PERIOD
|
||||||
|
'\'' -> KeyEvent.KEYCODE_APOSTROPHE
|
||||||
|
'`' -> KeyEvent.KEYCODE_GRAVE
|
||||||
|
'*' -> KeyEvent.KEYCODE_STAR
|
||||||
|
']' -> KeyEvent.KEYCODE_RIGHT_BRACKET
|
||||||
|
'[' -> KeyEvent.KEYCODE_LEFT_BRACKET
|
||||||
|
'+' -> KeyEvent.KEYCODE_PLUS
|
||||||
|
'-' -> KeyEvent.KEYCODE_MINUS
|
||||||
|
'=' -> KeyEvent.KEYCODE_EQUALS
|
||||||
|
'\n' -> KeyEvent.KEYCODE_ENTER
|
||||||
|
'\t' -> KeyEvent.KEYCODE_TAB
|
||||||
|
'0' -> KeyEvent.KEYCODE_0
|
||||||
|
'1' -> KeyEvent.KEYCODE_1
|
||||||
|
'2' -> KeyEvent.KEYCODE_2
|
||||||
|
'3' -> KeyEvent.KEYCODE_3
|
||||||
|
'4' -> KeyEvent.KEYCODE_4
|
||||||
|
'5' -> KeyEvent.KEYCODE_5
|
||||||
|
'6' -> KeyEvent.KEYCODE_6
|
||||||
|
'7' -> KeyEvent.KEYCODE_7
|
||||||
|
'8' -> KeyEvent.KEYCODE_8
|
||||||
|
'9' -> KeyEvent.KEYCODE_9
|
||||||
|
'A' -> KeyEvent.KEYCODE_A
|
||||||
|
'B' -> KeyEvent.KEYCODE_B
|
||||||
|
'C' -> KeyEvent.KEYCODE_C
|
||||||
|
'D' -> KeyEvent.KEYCODE_D
|
||||||
|
'E' -> KeyEvent.KEYCODE_E
|
||||||
|
'F' -> KeyEvent.KEYCODE_F
|
||||||
|
'G' -> KeyEvent.KEYCODE_G
|
||||||
|
'H' -> KeyEvent.KEYCODE_H
|
||||||
|
'I' -> KeyEvent.KEYCODE_I
|
||||||
|
'J' -> KeyEvent.KEYCODE_J
|
||||||
|
'K' -> KeyEvent.KEYCODE_K
|
||||||
|
'L' -> KeyEvent.KEYCODE_L
|
||||||
|
'M' -> KeyEvent.KEYCODE_M
|
||||||
|
'N' -> KeyEvent.KEYCODE_N
|
||||||
|
'O' -> KeyEvent.KEYCODE_O
|
||||||
|
'P' -> KeyEvent.KEYCODE_P
|
||||||
|
'Q' -> KeyEvent.KEYCODE_Q
|
||||||
|
'R' -> KeyEvent.KEYCODE_R
|
||||||
|
'S' -> KeyEvent.KEYCODE_S
|
||||||
|
'T' -> KeyEvent.KEYCODE_T
|
||||||
|
'U' -> KeyEvent.KEYCODE_U
|
||||||
|
'V' -> KeyEvent.KEYCODE_V
|
||||||
|
'W' -> KeyEvent.KEYCODE_W
|
||||||
|
'X' -> KeyEvent.KEYCODE_X
|
||||||
|
'Y' -> KeyEvent.KEYCODE_Y
|
||||||
|
'Z' -> KeyEvent.KEYCODE_Z
|
||||||
else -> KeyEvent.KEYCODE_UNKNOWN
|
else -> KeyEvent.KEYCODE_UNKNOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
package helium314.keyboard.keyboard.internal.keyboard_parser.floris
|
package helium314.keyboard.keyboard.internal.keyboard_parser.floris
|
||||||
|
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import helium314.keyboard.keyboard.KeyboardId
|
||||||
|
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||||
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyData.Companion.replaceIconWithLabelIfNoDrawable
|
||||||
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
import helium314.keyboard.latin.utils.InputTypeUtils
|
||||||
import helium314.keyboard.latin.utils.ToolbarKey
|
import helium314.keyboard.latin.utils.ToolbarKey
|
||||||
import helium314.keyboard.latin.utils.toolbarKeyStrings
|
import helium314.keyboard.latin.utils.toolbarKeyStrings
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/** labels for functional / special keys */
|
/** labels for functional / special keys */
|
||||||
object KeyLabel {
|
object KeyLabel {
|
||||||
|
@ -31,6 +38,7 @@ object KeyLabel {
|
||||||
const val META = "meta"
|
const val META = "meta"
|
||||||
const val TAB = "tab"
|
const val TAB = "tab"
|
||||||
const val ESCAPE = "esc"
|
const val ESCAPE = "esc"
|
||||||
|
const val TIMESTAMP = "timestamp"
|
||||||
|
|
||||||
/** to make sure a FlorisBoard label works when reading a JSON layout */
|
/** to make sure a FlorisBoard label works when reading a JSON layout */
|
||||||
// resulting special labels should be names of FunctionalKey enum, case insensitive
|
// resulting special labels should be names of FunctionalKey enum, case insensitive
|
||||||
|
@ -77,4 +85,79 @@ object KeyLabel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun keyLabelToActualLabel(label: String, params: KeyboardParams) = when (label) {
|
||||||
|
SYMBOL_ALPHA -> if (params.mId.isAlphabetKeyboard) params.mLocaleKeyboardInfos.labelSymbol else params.mLocaleKeyboardInfos.labelAlphabet
|
||||||
|
SYMBOL -> params.mLocaleKeyboardInfos.labelSymbol
|
||||||
|
ALPHA -> params.mLocaleKeyboardInfos.labelAlphabet
|
||||||
|
COMMA -> params.mLocaleKeyboardInfos.labelComma
|
||||||
|
PERIOD -> getPeriodLabel(params)
|
||||||
|
SPACE -> getSpaceLabel(params)
|
||||||
|
ACTION -> "${getActionKeyLabel(params)}|${getActionKeyCode(params)}"
|
||||||
|
DELETE -> "!icon/delete_key|!code/key_delete"
|
||||||
|
SHIFT -> "${getShiftLabel(params)}|!code/key_shift"
|
||||||
|
COM -> params.mLocaleKeyboardInfos.tlds.first()
|
||||||
|
LANGUAGE_SWITCH -> "!icon/language_switch_key|!code/key_language_switch"
|
||||||
|
ZWNJ -> "!icon/zwnj_key|\u200C"
|
||||||
|
CURRENCY -> params.mLocaleKeyboardInfos.currencyKey.first
|
||||||
|
CURRENCY1 -> params.mLocaleKeyboardInfos.currencyKey.second[0]
|
||||||
|
CURRENCY2 -> params.mLocaleKeyboardInfos.currencyKey.second[1]
|
||||||
|
CURRENCY3 -> params.mLocaleKeyboardInfos.currencyKey.second[2]
|
||||||
|
CURRENCY4 -> params.mLocaleKeyboardInfos.currencyKey.second[3]
|
||||||
|
CURRENCY5 -> params.mLocaleKeyboardInfos.currencyKey.second[4]
|
||||||
|
CTRL, ALT, FN, META, ESCAPE -> label.uppercase(Locale.US)
|
||||||
|
TAB -> "!icon/tab_key|!code/${KeyCode.TAB}"
|
||||||
|
TIMESTAMP -> "⌚|!code/${KeyCode.TIMESTAMP}"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getShiftLabel(params: KeyboardParams) = when (params.mId.mElementId) {
|
||||||
|
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> params.mLocaleKeyboardInfos.labelSymbol
|
||||||
|
KeyboardId.ELEMENT_SYMBOLS -> params.mLocaleKeyboardInfos.getShiftSymbolLabel(
|
||||||
|
Settings.getInstance().isTablet)
|
||||||
|
KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY_SHIFTED}"
|
||||||
|
KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY_LOCKED}"
|
||||||
|
|
||||||
|
else -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo (later): try avoiding this weirdness
|
||||||
|
// maybe just remove it and if users want it they can use custom functional layouts?
|
||||||
|
// but it has been like this "forever" and actually seems to make sense
|
||||||
|
private fun getPeriodLabel(params: KeyboardParams): String {
|
||||||
|
if (params.mId.isNumberLayout) return "."
|
||||||
|
if (params.mId.isAlphabetKeyboard || params.mId.locale.language in listOf("ar", "fa"))
|
||||||
|
return params.mLocaleKeyboardInfos.labelPeriod
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSpaceLabel(params: KeyboardParams): String =
|
||||||
|
if (params.mId.isAlphaOrSymbolKeyboard || params.mId.isEmojiClipBottomRow)
|
||||||
|
"!icon/space_key|!code/key_space"
|
||||||
|
else "!icon/space_key_for_number_layout|!code/key_space"
|
||||||
|
|
||||||
|
// todo (later): should this be handled with metaState? but metaState shift would require LOTS of changes...
|
||||||
|
private fun getActionKeyCode(params: KeyboardParams) =
|
||||||
|
if (params.mId.isMultiLine && (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED))
|
||||||
|
"!code/key_shift_enter"
|
||||||
|
else "!code/key_enter"
|
||||||
|
|
||||||
|
private fun getActionKeyLabel(params: KeyboardParams): String {
|
||||||
|
if (params.mId.isMultiLine && (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED))
|
||||||
|
return "!icon/enter_key"
|
||||||
|
val iconName = when (params.mId.imeAction()) {
|
||||||
|
EditorInfo.IME_ACTION_GO -> KeyboardIconsSet.NAME_GO_KEY
|
||||||
|
EditorInfo.IME_ACTION_SEARCH -> KeyboardIconsSet.NAME_SEARCH_KEY
|
||||||
|
EditorInfo.IME_ACTION_SEND -> KeyboardIconsSet.NAME_SEND_KEY
|
||||||
|
EditorInfo.IME_ACTION_NEXT -> KeyboardIconsSet.NAME_NEXT_KEY
|
||||||
|
EditorInfo.IME_ACTION_DONE -> KeyboardIconsSet.NAME_DONE_KEY
|
||||||
|
EditorInfo.IME_ACTION_PREVIOUS -> KeyboardIconsSet.NAME_PREVIOUS_KEY
|
||||||
|
InputTypeUtils.IME_ACTION_CUSTOM_LABEL -> return params.mId.mCustomActionLabel
|
||||||
|
else -> return "!icon/enter_key"
|
||||||
|
}
|
||||||
|
val replacement = iconName.replaceIconWithLabelIfNoDrawable(params)
|
||||||
|
return if (iconName == replacement) // i.e. icon exists
|
||||||
|
"!icon/$iconName"
|
||||||
|
else
|
||||||
|
replacement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,6 @@ open class PopupSet<T : AbstractKeyData>(
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimplePopups(val popupKeys: Collection<String>?) : PopupSet<AbstractKeyData>() {
|
class SimplePopups(val popupKeys: Collection<String>?) : PopupSet<AbstractKeyData>() {
|
||||||
override fun getPopupKeyLabels(params: KeyboardParams) = popupKeys
|
override fun getPopupKeyLabels(params: KeyboardParams) = popupKeys?.map { KeyData.processLabel(it, params) }
|
||||||
override fun isEmpty(): Boolean = popupKeys.isNullOrEmpty()
|
override fun isEmpty(): Boolean = popupKeys.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import kotlinx.serialization.Transient
|
||||||
import helium314.keyboard.keyboard.Key
|
import helium314.keyboard.keyboard.Key
|
||||||
import helium314.keyboard.keyboard.KeyboardId
|
import helium314.keyboard.keyboard.KeyboardId
|
||||||
import helium314.keyboard.keyboard.KeyboardTheme
|
import helium314.keyboard.keyboard.KeyboardTheme
|
||||||
|
import helium314.keyboard.keyboard.internal.KeyboardCodesSet
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
||||||
|
@ -23,7 +24,7 @@ import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||||
import helium314.keyboard.latin.common.StringUtils
|
import helium314.keyboard.latin.common.StringUtils
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.spellcheck.AndroidSpellCheckerService
|
import helium314.keyboard.latin.spellcheck.AndroidSpellCheckerService
|
||||||
import helium314.keyboard.latin.utils.InputTypeUtils
|
import helium314.keyboard.latin.utils.LayoutType
|
||||||
import helium314.keyboard.latin.utils.Log
|
import helium314.keyboard.latin.utils.Log
|
||||||
import helium314.keyboard.latin.utils.ToolbarKey
|
import helium314.keyboard.latin.utils.ToolbarKey
|
||||||
import helium314.keyboard.latin.utils.getCodeForToolbarKey
|
import helium314.keyboard.latin.utils.getCodeForToolbarKey
|
||||||
|
@ -92,30 +93,6 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
*/
|
*/
|
||||||
const val GROUP_KANA: Int = 97
|
const val GROUP_KANA: Int = 97
|
||||||
|
|
||||||
private fun getShiftLabel(params: KeyboardParams) = when (params.mId.mElementId) {
|
|
||||||
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> params.mLocaleKeyboardInfos.labelSymbol
|
|
||||||
KeyboardId.ELEMENT_SYMBOLS -> params.mLocaleKeyboardInfos.getShiftSymbolLabel(Settings.getInstance().isTablet)
|
|
||||||
KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY_SHIFTED}"
|
|
||||||
KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY_LOCKED}"
|
|
||||||
|
|
||||||
else -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo (later): try avoiding this weirdness
|
|
||||||
// maybe just remove it and if users want it they can use custom functional layouts?
|
|
||||||
// but it has been like this "forever" and actually seems to make sense
|
|
||||||
private fun getPeriodLabel(params: KeyboardParams): String {
|
|
||||||
if (params.mId.isNumberLayout) return "."
|
|
||||||
if (params.mId.isAlphabetKeyboard || params.mId.locale.language in listOf("ar", "fa"))
|
|
||||||
return params.mLocaleKeyboardInfos.labelPeriod
|
|
||||||
return "."
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSpaceLabel(params: KeyboardParams): String =
|
|
||||||
if (params.mId.isAlphaOrSymbolKeyboard || params.mId.isEmojiClipBottomRow)
|
|
||||||
"!icon/space_key|!code/key_space"
|
|
||||||
else "!icon/space_key_for_number_layout|!code/key_space"
|
|
||||||
|
|
||||||
// todo: emoji and language switch popups should actually disappear depending on current layout (including functional keys)
|
// todo: emoji and language switch popups should actually disappear depending on current layout (including functional keys)
|
||||||
// keys could be replaced with toolbar keys, but parsing needs to be adjusted (should happen anyway...)
|
// keys could be replaced with toolbar keys, but parsing needs to be adjusted (should happen anyway...)
|
||||||
private fun getCommaPopupKeys(params: KeyboardParams): List<String> {
|
private fun getCommaPopupKeys(params: KeyboardParams): List<String> {
|
||||||
|
@ -130,6 +107,9 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
keys.add("!icon/start_onehanded_mode_key|!code/key_toggle_onehanded")
|
keys.add("!icon/start_onehanded_mode_key|!code/key_toggle_onehanded")
|
||||||
if (!params.mId.mDeviceLocked)
|
if (!params.mId.mDeviceLocked)
|
||||||
keys.add("!icon/settings_key|!code/key_settings")
|
keys.add("!icon/settings_key|!code/key_settings")
|
||||||
|
if (shouldShowTldPopups(params)) {
|
||||||
|
keys.add(",")
|
||||||
|
}
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,34 +149,6 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
return Settings.getInstance().getInLocale(id, locale)
|
return Settings.getInstance().getInLocale(id, locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// action key stuff below
|
|
||||||
|
|
||||||
// todo (later): should this be handled with metaState? but metaState shift would require LOTS of changes...
|
|
||||||
private fun getActionKeyCode(params: KeyboardParams) =
|
|
||||||
if (params.mId.isMultiLine && (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED))
|
|
||||||
"!code/key_shift_enter"
|
|
||||||
else "!code/key_enter"
|
|
||||||
|
|
||||||
private fun getActionKeyLabel(params: KeyboardParams): String {
|
|
||||||
if (params.mId.isMultiLine && (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED))
|
|
||||||
return "!icon/enter_key"
|
|
||||||
val iconName = when (params.mId.imeAction()) {
|
|
||||||
EditorInfo.IME_ACTION_GO -> KeyboardIconsSet.NAME_GO_KEY
|
|
||||||
EditorInfo.IME_ACTION_SEARCH -> KeyboardIconsSet.NAME_SEARCH_KEY
|
|
||||||
EditorInfo.IME_ACTION_SEND -> KeyboardIconsSet.NAME_SEND_KEY
|
|
||||||
EditorInfo.IME_ACTION_NEXT -> KeyboardIconsSet.NAME_NEXT_KEY
|
|
||||||
EditorInfo.IME_ACTION_DONE -> KeyboardIconsSet.NAME_DONE_KEY
|
|
||||||
EditorInfo.IME_ACTION_PREVIOUS -> KeyboardIconsSet.NAME_PREVIOUS_KEY
|
|
||||||
InputTypeUtils.IME_ACTION_CUSTOM_LABEL -> return params.mId.mCustomActionLabel
|
|
||||||
else -> return "!icon/enter_key"
|
|
||||||
}
|
|
||||||
val replacement = iconName.replaceIconWithLabelIfNoDrawable(params)
|
|
||||||
return if (iconName == replacement) // i.e. icon exists
|
|
||||||
"!icon/$iconName"
|
|
||||||
else
|
|
||||||
replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getActionKeyPopupKeys(params: KeyboardParams): SimplePopups? =
|
private fun getActionKeyPopupKeys(params: KeyboardParams): SimplePopups? =
|
||||||
getActionKeyPopupKeyString(params.mId)?.let { createActionPopupKeys(it, params) }
|
getActionKeyPopupKeyString(params.mId)?.let { createActionPopupKeys(it, params) }
|
||||||
|
|
||||||
|
@ -206,34 +158,34 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
val navigateNext = keyboardId.navigateNext()
|
val navigateNext = keyboardId.navigateNext()
|
||||||
return when {
|
return when {
|
||||||
keyboardId.passwordInput() -> when {
|
keyboardId.passwordInput() -> when {
|
||||||
navigatePrev && action == EditorInfo.IME_ACTION_NEXT -> POPUP_EYS_NAVIGATE_PREVIOUS
|
navigatePrev && action == EditorInfo.IME_ACTION_NEXT -> POPUP_KEYS_NAVIGATE_PREVIOUS
|
||||||
action == EditorInfo.IME_ACTION_NEXT -> null
|
action == EditorInfo.IME_ACTION_NEXT -> null
|
||||||
navigateNext && action == EditorInfo.IME_ACTION_PREVIOUS -> POPUP_EYS_NAVIGATE_NEXT
|
navigateNext && action == EditorInfo.IME_ACTION_PREVIOUS -> POPUP_KEYS_NAVIGATE_NEXT
|
||||||
action == EditorInfo.IME_ACTION_PREVIOUS -> null
|
action == EditorInfo.IME_ACTION_PREVIOUS -> null
|
||||||
navigateNext && navigatePrev -> POPUP_EYS_NAVIGATE_PREVIOUS_NEXT
|
navigateNext && navigatePrev -> POPUP_KEYS_NAVIGATE_PREVIOUS_NEXT
|
||||||
navigateNext -> POPUP_EYS_NAVIGATE_NEXT
|
navigateNext -> POPUP_KEYS_NAVIGATE_NEXT
|
||||||
navigatePrev -> POPUP_EYS_NAVIGATE_PREVIOUS
|
navigatePrev -> POPUP_KEYS_NAVIGATE_PREVIOUS
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
// could change definition of numbers to query a range, or have a pre-defined list, but not that crucial
|
// could change definition of numbers to query a range, or have a pre-defined list, but not that crucial
|
||||||
keyboardId.isNumberLayout || keyboardId.mMode in listOf(KeyboardId.MODE_EMAIL, KeyboardId.MODE_DATE, KeyboardId.MODE_TIME, KeyboardId.MODE_DATETIME) -> when {
|
keyboardId.isNumberLayout || keyboardId.mMode in listOf(KeyboardId.MODE_EMAIL, KeyboardId.MODE_DATE, KeyboardId.MODE_TIME, KeyboardId.MODE_DATETIME) -> when {
|
||||||
action == EditorInfo.IME_ACTION_NEXT && navigatePrev -> POPUP_EYS_NAVIGATE_PREVIOUS
|
action == EditorInfo.IME_ACTION_NEXT && navigatePrev -> POPUP_KEYS_NAVIGATE_PREVIOUS
|
||||||
action == EditorInfo.IME_ACTION_NEXT -> null
|
action == EditorInfo.IME_ACTION_NEXT -> null
|
||||||
action == EditorInfo.IME_ACTION_PREVIOUS && navigateNext -> POPUP_EYS_NAVIGATE_NEXT
|
action == EditorInfo.IME_ACTION_PREVIOUS && navigateNext -> POPUP_KEYS_NAVIGATE_NEXT
|
||||||
action == EditorInfo.IME_ACTION_PREVIOUS -> null
|
action == EditorInfo.IME_ACTION_PREVIOUS -> null
|
||||||
navigateNext && navigatePrev -> POPUP_EYS_NAVIGATE_PREVIOUS_NEXT
|
navigateNext && navigatePrev -> POPUP_KEYS_NAVIGATE_PREVIOUS_NEXT
|
||||||
navigateNext -> POPUP_EYS_NAVIGATE_NEXT
|
navigateNext -> POPUP_KEYS_NAVIGATE_NEXT
|
||||||
navigatePrev -> POPUP_EYS_NAVIGATE_PREVIOUS
|
navigatePrev -> POPUP_KEYS_NAVIGATE_PREVIOUS
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
action == EditorInfo.IME_ACTION_NEXT && navigatePrev -> POPUP_EYS_NAVIGATE_EMOJI_PREVIOUS
|
action == EditorInfo.IME_ACTION_NEXT && navigatePrev -> POPUP_KEYS_NAVIGATE_EMOJI_PREVIOUS
|
||||||
action == EditorInfo.IME_ACTION_NEXT -> POPUP_EYS_NAVIGATE_EMOJI
|
action == EditorInfo.IME_ACTION_NEXT -> POPUP_KEYS_NAVIGATE_EMOJI
|
||||||
action == EditorInfo.IME_ACTION_PREVIOUS && navigateNext -> POPUP_EYS_NAVIGATE_EMOJI_NEXT
|
action == EditorInfo.IME_ACTION_PREVIOUS && navigateNext -> POPUP_KEYS_NAVIGATE_EMOJI_NEXT
|
||||||
action == EditorInfo.IME_ACTION_PREVIOUS -> POPUP_EYS_NAVIGATE_EMOJI
|
action == EditorInfo.IME_ACTION_PREVIOUS -> POPUP_KEYS_NAVIGATE_EMOJI
|
||||||
navigateNext && navigatePrev -> POPUP_EYS_NAVIGATE_EMOJI_PREVIOUS_NEXT
|
navigateNext && navigatePrev -> POPUP_KEYS_NAVIGATE_EMOJI_PREVIOUS_NEXT
|
||||||
navigateNext -> POPUP_EYS_NAVIGATE_EMOJI_NEXT
|
navigateNext -> POPUP_KEYS_NAVIGATE_EMOJI_NEXT
|
||||||
navigatePrev -> POPUP_EYS_NAVIGATE_EMOJI_PREVIOUS
|
navigatePrev -> POPUP_KEYS_NAVIGATE_EMOJI_PREVIOUS
|
||||||
else -> POPUP_EYS_NAVIGATE_EMOJI
|
else -> POPUP_KEYS_NAVIGATE_EMOJI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +219,7 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
return SimplePopups(popupKeys)
|
return SimplePopups(popupKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.replaceIconWithLabelIfNoDrawable(params: KeyboardParams): String {
|
fun String.replaceIconWithLabelIfNoDrawable(params: KeyboardParams): String {
|
||||||
if (params.mIconsSet.getIconDrawable(this) != null) return this
|
if (params.mIconsSet.getIconDrawable(this) != null) return this
|
||||||
if (params.mId.mWidth == AndroidSpellCheckerService.SPELLCHECKER_DUMMY_KEYBOARD_WIDTH
|
if (params.mId.mWidth == AndroidSpellCheckerService.SPELLCHECKER_DUMMY_KEYBOARD_WIDTH
|
||||||
&& params.mId.mHeight == AndroidSpellCheckerService.SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT
|
&& params.mId.mHeight == AndroidSpellCheckerService.SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT
|
||||||
|
@ -284,14 +236,25 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
return getStringInLocale(id, params)
|
return getStringInLocale(id, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun processLabel(label: String, params: KeyboardParams): String =
|
||||||
|
KeyLabel.keyLabelToActualLabel(label, params)
|
||||||
|
?: if (label in toolbarKeyStrings.values)
|
||||||
|
"!icon/$label|!code/${getCodeForToolbarKey(ToolbarKey.valueOf(label.uppercase(Locale.US)))}"
|
||||||
|
else label
|
||||||
|
|
||||||
|
private fun shouldShowTldPopups(params: KeyboardParams): Boolean =
|
||||||
|
(Settings.getInstance().current.mShowTldPopupKeys
|
||||||
|
&& params.mId.mSubtype.layouts[LayoutType.FUNCTIONAL] != "functional_keys_tablet"
|
||||||
|
&& params.mId.mMode in setOf(KeyboardId.MODE_URL, KeyboardId.MODE_EMAIL))
|
||||||
|
|
||||||
// could make arrays right away, but they need to be copied anyway as popupKeys arrays are modified when creating KeyParams
|
// could make arrays right away, but they need to be copied anyway as popupKeys arrays are modified when creating KeyParams
|
||||||
private const val POPUP_EYS_NAVIGATE_PREVIOUS = "!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard"
|
private const val POPUP_KEYS_NAVIGATE_PREVIOUS = "!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard"
|
||||||
private const val POPUP_EYS_NAVIGATE_NEXT = "!icon/clipboard_action_key|!code/key_clipboard,!icon/next_key|!code/key_action_next"
|
private const val POPUP_KEYS_NAVIGATE_NEXT = "!icon/clipboard_action_key|!code/key_clipboard,!icon/next_key|!code/key_action_next"
|
||||||
private const val POPUP_EYS_NAVIGATE_PREVIOUS_NEXT = "!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/next_key|!code/key_action_next"
|
private const val POPUP_KEYS_NAVIGATE_PREVIOUS_NEXT = "!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/next_key|!code/key_action_next"
|
||||||
private const val POPUP_EYS_NAVIGATE_EMOJI_PREVIOUS = "!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji"
|
private const val POPUP_KEYS_NAVIGATE_EMOJI_PREVIOUS = "!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji"
|
||||||
private const val POPUP_EYS_NAVIGATE_EMOJI = "!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji"
|
private const val POPUP_KEYS_NAVIGATE_EMOJI = "!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji"
|
||||||
private const val POPUP_EYS_NAVIGATE_EMOJI_NEXT = "!fixedColumnOrder!3,!needsDividers!,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next"
|
private const val POPUP_KEYS_NAVIGATE_EMOJI_NEXT = "!fixedColumnOrder!3,!needsDividers!,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next"
|
||||||
private const val POPUP_EYS_NAVIGATE_EMOJI_PREVIOUS_NEXT = "!fixedColumnOrder!4,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next"
|
private const val POPUP_KEYS_NAVIGATE_EMOJI_PREVIOUS_NEXT = "!fixedColumnOrder!4,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get the label, but also considers code, which can't be set separately for popup keys and thus goes into the label */
|
/** get the label, but also considers code, which can't be set separately for popup keys and thus goes into the label */
|
||||||
|
@ -300,12 +263,13 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
// so better only do it in case the popup stuff needs more improvements
|
// so better only do it in case the popup stuff needs more improvements
|
||||||
// idea: directly create PopupKeySpec, but need to deal with needsToUpcase and popupKeysColumnAndFlags
|
// idea: directly create PopupKeySpec, but need to deal with needsToUpcase and popupKeysColumnAndFlags
|
||||||
fun getPopupLabel(params: KeyboardParams): String {
|
fun getPopupLabel(params: KeyboardParams): String {
|
||||||
val newLabel = processLabel(params)
|
val newLabel = processLabel(label, params)
|
||||||
if (code == KeyCode.UNSPECIFIED) {
|
if (code == KeyCode.UNSPECIFIED) {
|
||||||
if (newLabel == label) return label
|
if (newLabel == label || newLabel.contains(KeyboardCodesSet.PREFIX_CODE))
|
||||||
|
return newLabel
|
||||||
val newCode = processCode()
|
val newCode = processCode()
|
||||||
if (newLabel.endsWith("|")) return "${newLabel}!code/$newCode" // for toolbar keys
|
if (newLabel.endsWith("|")) return "${newLabel}${KeyboardCodesSet.PREFIX_CODE}$newCode" // maybe not used any more
|
||||||
return if (newCode == code) newLabel else "${newLabel}|!code/$newCode"
|
return if (newCode == code) newLabel else "${newLabel}|${KeyboardCodesSet.PREFIX_CODE}$newCode"
|
||||||
}
|
}
|
||||||
if (code >= 32) {
|
if (code >= 32) {
|
||||||
if (newLabel.startsWith(KeyboardIconsSet.PREFIX_ICON)) {
|
if (newLabel.startsWith(KeyboardIconsSet.PREFIX_ICON)) {
|
||||||
|
@ -323,12 +287,12 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
val outputText = String(codePoints, 0, codePoints.size)
|
val outputText = String(codePoints, 0, codePoints.size)
|
||||||
return "${newLabel}|$outputText"
|
return "${newLabel}|$outputText"
|
||||||
}
|
}
|
||||||
return if (newLabel.endsWith("|")) "$newLabel!code/${processCode()}" // for toolbar keys
|
return if (newLabel.endsWith("|")) "$newLabel${KeyboardCodesSet.PREFIX_CODE}${processCode()}" // for toolbar keys
|
||||||
else "$newLabel|!code/${processCode()}"
|
else "$newLabel|${KeyboardCodesSet.PREFIX_CODE}${processCode()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrencyLabel(params: KeyboardParams): String {
|
fun getCurrencyLabel(params: KeyboardParams): String {
|
||||||
val newLabel = processLabel(params)
|
val newLabel = processLabel(label, params)
|
||||||
return when (code) {
|
return when (code) {
|
||||||
// consider currency codes for label
|
// consider currency codes for label
|
||||||
KeyCode.CURRENCY_SLOT_1 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.first}"
|
KeyCode.CURRENCY_SLOT_1 -> "$newLabel|${params.mLocaleKeyboardInfos.currencyKey.first}"
|
||||||
|
@ -387,9 +351,9 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
newLabel = getCurrencyLabel(params)
|
newLabel = getCurrencyLabel(params)
|
||||||
} else {
|
} else {
|
||||||
newCode = processCode()
|
newCode = processCode()
|
||||||
newLabel = processLabel(params)
|
newLabel = processLabel(label, params)
|
||||||
}
|
}
|
||||||
val newLabelFlags = labelFlags or additionalLabelFlags or getAdditionalLabelFlags(params)
|
var newLabelFlags = labelFlags or additionalLabelFlags or getAdditionalLabelFlags(params)
|
||||||
val newPopupKeys = popup.merge(getAdditionalPopupKeys(params))
|
val newPopupKeys = popup.merge(getAdditionalPopupKeys(params))
|
||||||
|
|
||||||
val background = when (type) {
|
val background = when (type) {
|
||||||
|
@ -401,6 +365,9 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
KeyType.LOCK -> getShiftBackground(params)
|
KeyType.LOCK -> getShiftBackground(params)
|
||||||
null -> getDefaultBackground(params)
|
null -> getDefaultBackground(params)
|
||||||
}
|
}
|
||||||
|
if (background == Key.BACKGROUND_TYPE_FUNCTIONAL
|
||||||
|
|| background == Key.BACKGROUND_TYPE_STICKY_ON || background == Key.BACKGROUND_TYPE_STICKY_OFF)
|
||||||
|
newLabelFlags = newLabelFlags or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR
|
||||||
|
|
||||||
return if (newCode == KeyCode.UNSPECIFIED || newCode == KeyCode.MULTIPLE_CODE_POINTS) {
|
return if (newCode == KeyCode.UNSPECIFIED || newCode == KeyCode.MULTIPLE_CODE_POINTS) {
|
||||||
// code will be determined from label if possible (i.e. label is single code point)
|
// code will be determined from label if possible (i.e. label is single code point)
|
||||||
|
@ -470,37 +437,6 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
else params.mDefaultKeyWidth
|
else params.mDefaultKeyWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo (later): encoding the code in the label should be avoided, because we know it already
|
|
||||||
private fun processLabel(params: KeyboardParams): String = when (label) {
|
|
||||||
KeyLabel.SYMBOL_ALPHA -> if (params.mId.isAlphabetKeyboard) params.mLocaleKeyboardInfos.labelSymbol else params.mLocaleKeyboardInfos.labelAlphabet
|
|
||||||
KeyLabel.SYMBOL -> params.mLocaleKeyboardInfos.labelSymbol
|
|
||||||
KeyLabel.ALPHA -> params.mLocaleKeyboardInfos.labelAlphabet
|
|
||||||
KeyLabel.COMMA -> params.mLocaleKeyboardInfos.labelComma
|
|
||||||
KeyLabel.PERIOD -> getPeriodLabel(params)
|
|
||||||
KeyLabel.SPACE -> getSpaceLabel(params)
|
|
||||||
KeyLabel.ACTION -> "${getActionKeyLabel(params)}|${getActionKeyCode(params)}"
|
|
||||||
KeyLabel.DELETE -> "!icon/delete_key|!code/key_delete"
|
|
||||||
KeyLabel.SHIFT -> "${getShiftLabel(params)}|!code/key_shift"
|
|
||||||
// KeyLabel.EMOJI -> "!icon/emoji_normal_key|!code/key_emoji"
|
|
||||||
// todo (later): label and popupKeys for .com should be in localeKeyTexts, handled similar to currency key
|
|
||||||
KeyLabel.COM -> ".com"
|
|
||||||
KeyLabel.LANGUAGE_SWITCH -> "!icon/language_switch_key|!code/key_language_switch"
|
|
||||||
KeyLabel.ZWNJ -> "!icon/zwnj_key|\u200C"
|
|
||||||
KeyLabel.CURRENCY -> params.mLocaleKeyboardInfos.currencyKey.first
|
|
||||||
KeyLabel.CURRENCY1 -> params.mLocaleKeyboardInfos.currencyKey.second[0]
|
|
||||||
KeyLabel.CURRENCY2 -> params.mLocaleKeyboardInfos.currencyKey.second[1]
|
|
||||||
KeyLabel.CURRENCY3 -> params.mLocaleKeyboardInfos.currencyKey.second[2]
|
|
||||||
KeyLabel.CURRENCY4 -> params.mLocaleKeyboardInfos.currencyKey.second[3]
|
|
||||||
KeyLabel.CURRENCY5 -> params.mLocaleKeyboardInfos.currencyKey.second[4]
|
|
||||||
KeyLabel.CTRL, KeyLabel.ALT, KeyLabel.FN, KeyLabel.META , KeyLabel.ESCAPE -> label.uppercase(Locale.US)
|
|
||||||
KeyLabel.TAB -> "!icon/tab_key|"
|
|
||||||
else -> {
|
|
||||||
if (label in toolbarKeyStrings.values) {
|
|
||||||
"!icon/$label|"
|
|
||||||
} else label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processCode(): Int {
|
private fun processCode(): Int {
|
||||||
if (code != KeyCode.UNSPECIFIED) return code
|
if (code != KeyCode.UNSPECIFIED) return code
|
||||||
return when (label) {
|
return when (label) {
|
||||||
|
@ -513,6 +449,7 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
KeyLabel.META -> KeyCode.META
|
KeyLabel.META -> KeyCode.META
|
||||||
KeyLabel.TAB -> KeyCode.TAB
|
KeyLabel.TAB -> KeyCode.TAB
|
||||||
KeyLabel.ESCAPE -> KeyCode.ESCAPE
|
KeyLabel.ESCAPE -> KeyCode.ESCAPE
|
||||||
|
KeyLabel.TIMESTAMP -> KeyCode.TIMESTAMP
|
||||||
else -> {
|
else -> {
|
||||||
if (label in toolbarKeyStrings.values) {
|
if (label in toolbarKeyStrings.values) {
|
||||||
getCodeForToolbarKey(ToolbarKey.valueOf(label.uppercase(Locale.US)))
|
getCodeForToolbarKey(ToolbarKey.valueOf(label.uppercase(Locale.US)))
|
||||||
|
@ -524,17 +461,20 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
// todo (later): add explanations / reasoning, often this is just taken from conversion from OpenBoard / AOSP layouts
|
// todo (later): add explanations / reasoning, often this is just taken from conversion from OpenBoard / AOSP layouts
|
||||||
private fun getAdditionalLabelFlags(params: KeyboardParams): Int {
|
private fun getAdditionalLabelFlags(params: KeyboardParams): Int {
|
||||||
return when (label) {
|
return when (label) {
|
||||||
KeyLabel.ALPHA, KeyLabel.SYMBOL_ALPHA, KeyLabel.SYMBOL -> Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR
|
KeyLabel.ALPHA, KeyLabel.SYMBOL_ALPHA, KeyLabel.SYMBOL -> Key.LABEL_FLAGS_PRESERVE_CASE
|
||||||
KeyLabel.COMMA -> Key.LABEL_FLAGS_HAS_POPUP_HINT
|
KeyLabel.COMMA -> Key.LABEL_FLAGS_HAS_POPUP_HINT
|
||||||
// essentially this only changes the appearance of the armenian period key in holo theme
|
// essentially the first term only changes the appearance of the armenian period key in holo theme
|
||||||
KeyLabel.PERIOD -> Key.LABEL_FLAGS_HAS_POPUP_HINT and if (params.mId.isAlphabetKeyboard) params.mLocaleKeyboardInfos.labelFlags else 0
|
KeyLabel.PERIOD -> (Key.LABEL_FLAGS_HAS_POPUP_HINT and
|
||||||
|
if (params.mId.isAlphabetKeyboard) params.mLocaleKeyboardInfos.labelFlags else 0) or
|
||||||
|
Key.LABEL_FLAGS_PRESERVE_CASE or
|
||||||
|
// in functional_keys.json the label flag is already defined, let's not override it in case it's removed by the user
|
||||||
|
if (!params.mId.isAlphaOrSymbolKeyboard && shouldShowTldPopups(params)) Key.LABEL_FLAGS_DISABLE_HINT_LABEL else 0
|
||||||
KeyLabel.ACTION -> {
|
KeyLabel.ACTION -> {
|
||||||
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_AUTO_X_SCALE or
|
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO or
|
||||||
Key.LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR or
|
|
||||||
Key.LABEL_FLAGS_HAS_POPUP_HINT or KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId)
|
Key.LABEL_FLAGS_HAS_POPUP_HINT or KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId)
|
||||||
}
|
}
|
||||||
KeyLabel.SPACE -> if (params.mId.isNumberLayout) Key.LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM else 0
|
KeyLabel.SPACE -> if (params.mId.isNumberLayout) Key.LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM else 0
|
||||||
KeyLabel.SHIFT -> Key.LABEL_FLAGS_PRESERVE_CASE or if (!params.mId.isAlphabetKeyboard) Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR else 0
|
KeyLabel.SHIFT -> Key.LABEL_FLAGS_PRESERVE_CASE
|
||||||
toolbarKeyStrings[ToolbarKey.EMOJI] -> KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId)
|
toolbarKeyStrings[ToolbarKey.EMOJI] -> KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId)
|
||||||
KeyLabel.COM -> Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FONT_NORMAL or Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_PRESERVE_CASE
|
KeyLabel.COM -> Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FONT_NORMAL or Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_PRESERVE_CASE
|
||||||
KeyLabel.ZWNJ -> Key.LABEL_FLAGS_HAS_POPUP_HINT
|
KeyLabel.ZWNJ -> Key.LABEL_FLAGS_HAS_POPUP_HINT
|
||||||
|
@ -546,12 +486,12 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
|
|
||||||
private fun getAdditionalPopupKeys(params: KeyboardParams): PopupSet<AbstractKeyData>? {
|
private fun getAdditionalPopupKeys(params: KeyboardParams): PopupSet<AbstractKeyData>? {
|
||||||
if (groupId == GROUP_COMMA) return SimplePopups(getCommaPopupKeys(params))
|
if (groupId == GROUP_COMMA) return SimplePopups(getCommaPopupKeys(params))
|
||||||
if (groupId == GROUP_PERIOD) return SimplePopups(getPunctuationPopupKeys(params))
|
if (groupId == GROUP_PERIOD) return getPeriodPopups(params)
|
||||||
if (groupId == GROUP_ENTER) return getActionKeyPopupKeys(params)
|
if (groupId == GROUP_ENTER) return getActionKeyPopupKeys(params)
|
||||||
if (groupId == GROUP_NO_DEFAULT_POPUP) return null
|
if (groupId == GROUP_NO_DEFAULT_POPUP) return null
|
||||||
return when (label) {
|
return when (label) {
|
||||||
KeyLabel.COMMA -> SimplePopups(getCommaPopupKeys(params))
|
KeyLabel.COMMA -> SimplePopups(getCommaPopupKeys(params))
|
||||||
KeyLabel.PERIOD -> SimplePopups(getPunctuationPopupKeys(params))
|
KeyLabel.PERIOD -> getPeriodPopups(params)
|
||||||
KeyLabel.ACTION -> getActionKeyPopupKeys(params)
|
KeyLabel.ACTION -> getActionKeyPopupKeys(params)
|
||||||
KeyLabel.SHIFT -> {
|
KeyLabel.SHIFT -> {
|
||||||
if (params.mId.isAlphabetKeyboard) SimplePopups(
|
if (params.mId.isAlphabetKeyboard) SimplePopups(
|
||||||
|
@ -561,13 +501,22 @@ sealed interface KeyData : AbstractKeyData {
|
||||||
)
|
)
|
||||||
) else null // why the alphabet popup keys actually?
|
) else null // why the alphabet popup keys actually?
|
||||||
}
|
}
|
||||||
KeyLabel.COM -> SimplePopups(listOf(Key.POPUP_KEYS_HAS_LABELS, ".net", ".org", ".gov", ".edu"))
|
KeyLabel.COM -> SimplePopups(
|
||||||
|
listOf(Key.POPUP_KEYS_HAS_LABELS).plus(params.mLocaleKeyboardInfos.tlds.drop(1))
|
||||||
|
)
|
||||||
|
|
||||||
KeyLabel.ZWNJ -> SimplePopups(listOf("!icon/zwj_key|\u200D"))
|
KeyLabel.ZWNJ -> SimplePopups(listOf("!icon/zwj_key|\u200D"))
|
||||||
// only add currency popups if there are none defined on the key
|
// only add currency popups if there are none defined on the key
|
||||||
KeyLabel.CURRENCY -> if (popup.isEmpty()) SimplePopups(params.mLocaleKeyboardInfos.currencyKey.second) else null
|
KeyLabel.CURRENCY -> if (popup.isEmpty()) SimplePopups(params.mLocaleKeyboardInfos.currencyKey.second) else null
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPeriodPopups(params: KeyboardParams): SimplePopups =
|
||||||
|
SimplePopups(
|
||||||
|
if (shouldShowTldPopups(params)) params.mLocaleKeyboardInfos.tlds
|
||||||
|
else getPunctuationPopupKeys(params)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import helium314.keyboard.keyboard.ColorSetting
|
import helium314.keyboard.keyboard.ColorSetting
|
||||||
import helium314.keyboard.keyboard.KeyboardTheme
|
import helium314.keyboard.keyboard.KeyboardTheme
|
||||||
|
import helium314.keyboard.keyboard.emoji.SupportedEmojis
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
||||||
import helium314.keyboard.latin.common.ColorType
|
import helium314.keyboard.latin.common.ColorType
|
||||||
import helium314.keyboard.latin.common.Constants.Separators
|
import helium314.keyboard.latin.common.Constants.Separators
|
||||||
|
@ -17,12 +18,14 @@ import helium314.keyboard.latin.settings.Defaults
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.settings.SettingsSubtype
|
import helium314.keyboard.latin.settings.SettingsSubtype
|
||||||
import helium314.keyboard.latin.settings.SettingsSubtype.Companion.toSettingsSubtype
|
import helium314.keyboard.latin.settings.SettingsSubtype.Companion.toSettingsSubtype
|
||||||
|
import helium314.keyboard.latin.settings.createPrefKeyForBooleanSettings
|
||||||
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
import helium314.keyboard.latin.utils.DeviceProtectedUtils
|
||||||
import helium314.keyboard.latin.utils.DictionaryInfoUtils
|
import helium314.keyboard.latin.utils.DictionaryInfoUtils
|
||||||
import helium314.keyboard.latin.utils.DictionaryInfoUtils.USER_DICTIONARY_SUFFIX
|
import helium314.keyboard.latin.utils.DictionaryInfoUtils.USER_DICTIONARY_SUFFIX
|
||||||
import helium314.keyboard.latin.utils.LayoutType
|
import helium314.keyboard.latin.utils.LayoutType
|
||||||
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
||||||
import helium314.keyboard.latin.utils.LayoutUtilsCustom
|
import helium314.keyboard.latin.utils.LayoutUtilsCustom
|
||||||
|
import helium314.keyboard.latin.utils.Log
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils.SCRIPT_LATIN
|
import helium314.keyboard.latin.utils.ScriptUtils.SCRIPT_LATIN
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||||
import helium314.keyboard.latin.utils.SubtypeSettings
|
import helium314.keyboard.latin.utils.SubtypeSettings
|
||||||
|
@ -31,9 +34,8 @@ import helium314.keyboard.latin.utils.ToolbarKey
|
||||||
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
|
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
|
||||||
import helium314.keyboard.latin.utils.getResourceSubtypes
|
import helium314.keyboard.latin.utils.getResourceSubtypes
|
||||||
import helium314.keyboard.latin.utils.locale
|
import helium314.keyboard.latin.utils.locale
|
||||||
import helium314.keyboard.latin.utils.mainLayoutName
|
import helium314.keyboard.latin.utils.mainLayoutNameOrQwerty
|
||||||
import helium314.keyboard.latin.utils.prefs
|
import helium314.keyboard.latin.utils.prefs
|
||||||
import helium314.keyboard.latin.utils.protectedPrefs
|
|
||||||
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
|
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
|
||||||
import helium314.keyboard.latin.utils.writeCustomKeyCodes
|
import helium314.keyboard.latin.utils.writeCustomKeyCodes
|
||||||
import helium314.keyboard.settings.screens.colorPrefsAndResIds
|
import helium314.keyboard.settings.screens.colorPrefsAndResIds
|
||||||
|
@ -43,14 +45,24 @@ import java.util.EnumMap
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Settings.init(this)
|
|
||||||
DebugFlags.init(this)
|
DebugFlags.init(this)
|
||||||
|
Settings.init(this)
|
||||||
SubtypeSettings.init(this)
|
SubtypeSettings.init(this)
|
||||||
RichInputMethodManager.init(this)
|
RichInputMethodManager.init(this)
|
||||||
|
|
||||||
checkVersionUpgrade(this)
|
checkVersionUpgrade(this)
|
||||||
app = this
|
app = this
|
||||||
Defaults.initDynamicDefaults(this)
|
Defaults.initDynamicDefaults(this)
|
||||||
|
LayoutUtilsCustom.removeMissingLayouts(this) // only after version upgrade
|
||||||
|
SupportedEmojis.load(this)
|
||||||
|
|
||||||
|
val packageInfo = packageManager.getPackageInfo(packageName, 0)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
Log.i(
|
||||||
|
"startup", "Starting ${applicationInfo.processName} version ${packageInfo.versionName} (${
|
||||||
|
packageInfo.versionCode
|
||||||
|
}) on Android ${android.os.Build.VERSION.RELEASE} (SDK ${android.os.Build.VERSION.SDK_INT})"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -74,16 +86,13 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
if (oldVersion == BuildConfig.VERSION_CODE)
|
if (oldVersion == BuildConfig.VERSION_CODE)
|
||||||
return
|
return
|
||||||
// clear extracted dictionaries, in case updated version contains newer ones
|
// clear extracted dictionaries, in case updated version contains newer ones
|
||||||
DictionaryInfoUtils.getCachedDirectoryList(context)?.forEach {
|
DictionaryInfoUtils.getCacheDirectories(context).forEach {
|
||||||
if (!it.isDirectory) return@forEach
|
|
||||||
val files = it.listFiles() ?: return@forEach
|
val files = it.listFiles() ?: return@forEach
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
if (!file.name.endsWith(USER_DICTIONARY_SUFFIX))
|
if (!file.name.endsWith(USER_DICTIONARY_SUFFIX))
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion == 0) // new install or restoring settings from old app name
|
|
||||||
upgradesWhenComingFromOldAppName(context)
|
|
||||||
if (oldVersion <= 1000) { // upgrade old custom layouts name
|
if (oldVersion <= 1000) { // upgrade old custom layouts name
|
||||||
val oldShiftSymbolsFile = getCustomLayoutFile("custom.shift_symbols", context)
|
val oldShiftSymbolsFile = getCustomLayoutFile("custom.shift_symbols", context)
|
||||||
if (oldShiftSymbolsFile.exists()) {
|
if (oldShiftSymbolsFile.exists()) {
|
||||||
|
@ -161,7 +170,7 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
split[1] = newName
|
split[1] = newName
|
||||||
split.joinToString(":")
|
split.joinToString(":")
|
||||||
}
|
}
|
||||||
Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";"))
|
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, newSubtypeStrings.joinToString(";")).apply()
|
||||||
}
|
}
|
||||||
// rename other custom layouts
|
// rename other custom layouts
|
||||||
LayoutUtilsCustom.onLayoutFileChanged()
|
LayoutUtilsCustom.onLayoutFileChanged()
|
||||||
|
@ -232,7 +241,7 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
KeyboardTheme.writeUserMoreColors(prefs, themeNameNight, moreColorsNight)
|
KeyboardTheme.writeUserMoreColors(prefs, themeNameNight, moreColorsNight)
|
||||||
}
|
}
|
||||||
if (prefs.contains("theme_dark_color_all_colors")) {
|
if (prefs.contains("theme_dark_color_all_colors")) {
|
||||||
val allColorsNight = readAllColorsMap(false)
|
val allColorsNight = readAllColorsMap(true)
|
||||||
prefs.edit().remove("theme_dark_color_all_colors").apply()
|
prefs.edit().remove("theme_dark_color_all_colors").apply()
|
||||||
KeyboardTheme.writeUserAllColors(prefs, themeNameNight, allColorsNight)
|
KeyboardTheme.writeUserAllColors(prefs, themeNameNight, allColorsNight)
|
||||||
}
|
}
|
||||||
|
@ -437,19 +446,19 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
val mainLayoutName = oldSplit[1]
|
val mainLayoutName = oldSplit[1]
|
||||||
// we now need more information than just locale and main layout name, get it from existing subtypes
|
// we now need more information than just locale and main layout name, get it from existing subtypes
|
||||||
val filtered = additionalSubtypes.filter {
|
val filtered = additionalSubtypes.filter {
|
||||||
it.locale().toLanguageTag() == languageTag && (it.mainLayoutName() ?: "qwerty") == mainLayoutName
|
it.locale().toLanguageTag() == languageTag && (it.mainLayoutNameOrQwerty()) == mainLayoutName
|
||||||
}
|
}
|
||||||
if (filtered.isNotEmpty())
|
if (filtered.isNotEmpty())
|
||||||
return@joinToString filtered.first().toSettingsSubtype().toPref()
|
return@joinToString filtered.first().toSettingsSubtype().toPref()
|
||||||
// find best matching resource subtype
|
// find best matching resource subtype
|
||||||
val goodMatch = resourceSubtypes.filter {
|
val goodMatch = resourceSubtypes.filter {
|
||||||
it.locale().toLanguageTag() == languageTag && (it.mainLayoutName() ?: "qwerty") == mainLayoutName
|
it.locale().toLanguageTag() == languageTag && (it.mainLayoutNameOrQwerty()) == mainLayoutName
|
||||||
}
|
}
|
||||||
if (goodMatch.isNotEmpty())
|
if (goodMatch.isNotEmpty())
|
||||||
return@joinToString goodMatch.first().toSettingsSubtype().toPref()
|
return@joinToString goodMatch.first().toSettingsSubtype().toPref()
|
||||||
// not sure how we can get here, but better deal with it
|
// not sure how we can get here, but better deal with it
|
||||||
val okMatch = resourceSubtypes.filter {
|
val okMatch = resourceSubtypes.filter {
|
||||||
it.locale().language == languageTag.constructLocale().language && (it.mainLayoutName() ?: "qwerty") == mainLayoutName
|
it.locale().language == languageTag.constructLocale().language && (it.mainLayoutNameOrQwerty()) == mainLayoutName
|
||||||
}
|
}
|
||||||
if (okMatch.isNotEmpty())
|
if (okMatch.isNotEmpty())
|
||||||
okMatch.first().toSettingsSubtype().toPref()
|
okMatch.first().toSettingsSubtype().toPref()
|
||||||
|
@ -530,115 +539,81 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
prefs.edit().remove("auto_correction_confidence").putFloat(Settings.PREF_AUTO_CORRECT_THRESHOLD, value).apply()
|
prefs.edit().remove("auto_correction_confidence").putFloat(Settings.PREF_AUTO_CORRECT_THRESHOLD, value).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion <= 2310) {
|
if (oldVersion <= 2310) {
|
||||||
listOf(
|
listOf(
|
||||||
Settings.PREF_ENABLED_SUBTYPES,
|
Settings.PREF_ENABLED_SUBTYPES,
|
||||||
Settings.PREF_SELECTED_SUBTYPE,
|
Settings.PREF_SELECTED_SUBTYPE,
|
||||||
Settings.PREF_ADDITIONAL_SUBTYPES
|
Settings.PREF_ADDITIONAL_SUBTYPES
|
||||||
).forEach { key ->
|
).forEach { key ->
|
||||||
val value = prefs.getString(key, "")!!
|
val value = prefs.getString(key, "")!!
|
||||||
if ("bengali," in value) {
|
if ("bengali," in value) {
|
||||||
prefs.edit().putString(key, value.replace("bengali,", "bengali_inscript,")).apply()
|
prefs.edit().putString(key, value.replace("bengali,", "bengali_inscript,")).apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldVersion <= 3001 && prefs.getInt(Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME, Defaults.PREF_CLIPBOARD_HISTORY_RETENTION_TIME) <= 0) {
|
||||||
|
prefs.edit().putInt(Settings.PREF_CLIPBOARD_HISTORY_RETENTION_TIME, 121).apply()
|
||||||
|
}
|
||||||
|
if (oldVersion <= 3002) {
|
||||||
|
prefs.all.filterKeys { it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) }.forEach {
|
||||||
|
val oldValue = prefs.getString(it.key, "")!!
|
||||||
|
if ("KEY_PREVIEW" !in oldValue) return@forEach
|
||||||
|
val newValue = oldValue.replace("KEY_PREVIEW", "KEY_PREVIEW_BACKGROUND")
|
||||||
|
prefs.edit().putString(it.key, newValue).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (oldVersion <= 3101) {
|
||||||
|
val e = prefs.edit()
|
||||||
|
prefs.all.toMap().forEach { (key, value) ->
|
||||||
|
if (key == "side_padding_scale") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_SIDE_PADDING_SCALE_PREFIX, 0, 2), value as Float)
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_SIDE_PADDING_SCALE_PREFIX, 2, 2), value)
|
||||||
|
} else if (key == "side_padding_scale_landscape") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_SIDE_PADDING_SCALE_PREFIX, 1, 2), value as Float)
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_SIDE_PADDING_SCALE_PREFIX, 3, 2), value)
|
||||||
|
} else if (key == "bottom_padding_scale") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_BOTTOM_PADDING_SCALE_PREFIX, 0, 1), value as Float)
|
||||||
|
} else if (key == "bottom_padding_scale_landscape") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_BOTTOM_PADDING_SCALE_PREFIX, 1, 1), value as Float)
|
||||||
|
} else if (key == "split_spacer_scale") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_SPLIT_SPACER_SCALE_PREFIX, 0, 1), value as Float)
|
||||||
|
} else if (key == "split_spacer_scale_landscape") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_SPLIT_SPACER_SCALE_PREFIX, 1, 1), value as Float)
|
||||||
|
} else if (key == "one_handed_mode_enabled_p_true") {
|
||||||
|
e.putBoolean(createPrefKeyForBooleanSettings(Settings.PREF_ONE_HANDED_MODE_PREFIX, 0, 2), value as Boolean)
|
||||||
|
} else if (key == "one_handed_mode_enabled_p_false") {
|
||||||
|
e.putBoolean(createPrefKeyForBooleanSettings(Settings.PREF_ONE_HANDED_MODE_PREFIX, 1, 2), value as Boolean)
|
||||||
|
} else if (key == "one_handed_mode_scale_p_true") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_ONE_HANDED_SCALE_PREFIX, 0, 2), value as Float)
|
||||||
|
} else if (key == "one_handed_mode_scale_p_false") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_ONE_HANDED_SCALE_PREFIX, 1, 2), value as Float)
|
||||||
|
} else if (key == "one_handed_mode_gravity_p_true") {
|
||||||
|
e.putInt(createPrefKeyForBooleanSettings(Settings.PREF_ONE_HANDED_GRAVITY_PREFIX, 0, 2), value as Int)
|
||||||
|
} else if (key == "one_handed_mode_gravity_p_false") {
|
||||||
|
e.putInt(createPrefKeyForBooleanSettings(Settings.PREF_ONE_HANDED_GRAVITY_PREFIX, 1, 2), value as Int)
|
||||||
|
} else if (key == "keyboard_height_scale") {
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_KEYBOARD_HEIGHT_SCALE_PREFIX, 1, 1), value as Float)
|
||||||
|
e.putFloat(createPrefKeyForBooleanSettings(Settings.PREF_KEYBOARD_HEIGHT_SCALE_PREFIX, 1, 1), value)
|
||||||
|
} else {
|
||||||
|
if (key == Settings.PREF_ADDITIONAL_SUBTYPES || key == Settings.PREF_ENABLED_SUBTYPES) {
|
||||||
|
val subtypes = prefs.getString(key, "")!!.split(Separators.SETS).filter { it.isNotEmpty() }.map {
|
||||||
|
val st = it.toSettingsSubtype()
|
||||||
|
if (st.locale.language == "ko") st.with(ExtraValue.COMBINING_RULES, "hangul")
|
||||||
|
else st
|
||||||
|
}
|
||||||
|
e.putString(key, subtypes.joinToString(Separators.SETS) { it.toPref() })
|
||||||
|
} else if (key == Settings.PREF_SELECTED_SUBTYPE) {
|
||||||
|
val subtype = prefs.getString(key, "")!!.toSettingsSubtype()
|
||||||
|
if (subtype.locale.language == "ko")
|
||||||
|
e.putString(key, subtype.with(ExtraValue.COMBINING_RULES, "hangul").toPref())
|
||||||
|
}
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
e.remove(key)
|
||||||
|
}
|
||||||
|
e.apply()
|
||||||
|
}
|
||||||
upgradeToolbarPrefs(prefs)
|
upgradeToolbarPrefs(prefs)
|
||||||
LayoutUtilsCustom.onLayoutFileChanged() // just to be sure
|
LayoutUtilsCustom.onLayoutFileChanged() // just to be sure
|
||||||
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo (later): remove it when most users probably have upgraded
|
|
||||||
private fun upgradesWhenComingFromOldAppName(context: Context) {
|
|
||||||
// move layout files
|
|
||||||
try {
|
|
||||||
File(context.filesDir, "layouts").listFiles()?.forEach {
|
|
||||||
it.copyTo(getCustomLayoutFile(it.name, context), true)
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {}
|
|
||||||
// move background images
|
|
||||||
try {
|
|
||||||
val bgDay = File(context.filesDir, "custom_background_image")
|
|
||||||
if (bgDay.isFile) {
|
|
||||||
bgDay.copyTo(Settings.getCustomBackgroundFile(context, false, false), true)
|
|
||||||
bgDay.delete()
|
|
||||||
}
|
|
||||||
val bgNight = File(context.filesDir, "custom_background_image_night")
|
|
||||||
if (bgNight.isFile) {
|
|
||||||
bgNight.copyTo(Settings.getCustomBackgroundFile(context, true, false), true)
|
|
||||||
bgNight.delete()
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {}
|
|
||||||
// upgrade prefs
|
|
||||||
val prefs = context.prefs()
|
|
||||||
if (prefs.all.containsKey("theme_variant")) {
|
|
||||||
prefs.edit().putString(Settings.PREF_THEME_COLORS, prefs.getString("theme_variant", "")).apply()
|
|
||||||
prefs.edit().remove("theme_variant").apply()
|
|
||||||
}
|
|
||||||
if (prefs.all.containsKey("theme_variant_night")) {
|
|
||||||
prefs.edit().putString(Settings.PREF_THEME_COLORS_NIGHT, prefs.getString("theme_variant_night", "")).apply()
|
|
||||||
prefs.edit().remove("theme_variant_night").apply()
|
|
||||||
}
|
|
||||||
prefs.all.toMap().forEach {
|
|
||||||
if (it.key.startsWith("pref_key_") && it.key != "pref_key_longpress_timeout") {
|
|
||||||
var remove = true
|
|
||||||
when (val value = it.value) {
|
|
||||||
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_key_"), value).apply()
|
|
||||||
is Int -> prefs.edit().putInt(it.key.substringAfter("pref_key_"), value).apply()
|
|
||||||
is Long -> prefs.edit().putLong(it.key.substringAfter("pref_key_"), value).apply()
|
|
||||||
is String -> prefs.edit().putString(it.key.substringAfter("pref_key_"), value).apply()
|
|
||||||
is Float -> prefs.edit().putFloat(it.key.substringAfter("pref_key_"), value).apply()
|
|
||||||
else -> remove = false
|
|
||||||
}
|
|
||||||
if (remove)
|
|
||||||
prefs.edit().remove(it.key).apply()
|
|
||||||
} else if (it.key.startsWith("pref_")) {
|
|
||||||
var remove = true
|
|
||||||
when (val value = it.value) {
|
|
||||||
is Boolean -> prefs.edit().putBoolean(it.key.substringAfter("pref_"), value).apply()
|
|
||||||
is Int -> prefs.edit().putInt(it.key.substringAfter("pref_"), value).apply()
|
|
||||||
is Long -> prefs.edit().putLong(it.key.substringAfter("pref_"), value).apply()
|
|
||||||
is String -> prefs.edit().putString(it.key.substringAfter("pref_"), value).apply()
|
|
||||||
is Float -> prefs.edit().putFloat(it.key.substringAfter("pref_"), value).apply()
|
|
||||||
else -> remove = false
|
|
||||||
}
|
|
||||||
if (remove)
|
|
||||||
prefs.edit().remove(it.key).apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// change more_keys to popup_keys
|
|
||||||
if (prefs.contains("more_keys_order")) {
|
|
||||||
prefs.edit().putString(Settings.PREF_POPUP_KEYS_ORDER, prefs.getString("more_keys_order", "")?.replace("more_", "popup_")).apply()
|
|
||||||
prefs.edit().remove("more_keys_order").apply()
|
|
||||||
}
|
|
||||||
if (prefs.contains("more_keys_labels_order")) {
|
|
||||||
prefs.edit().putString(Settings.PREF_POPUP_KEYS_LABELS_ORDER, prefs.getString("more_keys_labels_order", "")?.replace("more_", "popup_")).apply()
|
|
||||||
prefs.edit().remove("more_keys_labels_order").apply()
|
|
||||||
}
|
|
||||||
if (prefs.contains("more_more_keys")) {
|
|
||||||
prefs.edit().putString(Settings.PREF_MORE_POPUP_KEYS, prefs.getString("more_more_keys", "")).apply()
|
|
||||||
prefs.edit().remove("more_more_keys").apply()
|
|
||||||
}
|
|
||||||
if (prefs.contains("spellcheck_use_contacts")) {
|
|
||||||
prefs.edit().putBoolean(Settings.PREF_USE_CONTACTS, prefs.getBoolean("spellcheck_use_contacts", false)).apply()
|
|
||||||
prefs.edit().remove("spellcheck_use_contacts").apply()
|
|
||||||
}
|
|
||||||
// upgrade additional subtype locale strings
|
|
||||||
if (prefs.contains(Settings.PREF_ADDITIONAL_SUBTYPES)) {
|
|
||||||
val additionalSubtypes = mutableListOf<String>()
|
|
||||||
prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, "")!!.split(";").forEach {
|
|
||||||
val localeString = it.substringBefore(":")
|
|
||||||
additionalSubtypes.add(it.replace(localeString, localeString.constructLocale().toLanguageTag()))
|
|
||||||
}
|
|
||||||
Settings.writePrefAdditionalSubtypes(prefs, additionalSubtypes.joinToString(";"))
|
|
||||||
}
|
|
||||||
// move pinned clips to credential protected storage if device is not locked (should never happen)
|
|
||||||
if (!prefs.contains(Settings.PREF_PINNED_CLIPS)) return
|
|
||||||
try {
|
|
||||||
val defaultProtectedPrefs = context.protectedPrefs()
|
|
||||||
defaultProtectedPrefs.edit { putString(Settings.PREF_PINNED_CLIPS, prefs.getString(Settings.PREF_PINNED_CLIPS, "")) }
|
|
||||||
prefs.edit { remove(Settings.PREF_PINNED_CLIPS) }
|
|
||||||
} catch (_: IllegalStateException) {
|
|
||||||
// SharedPreferences in credential encrypted storage are not available until after user is unlocked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
package helium314.keyboard.latin;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.BinaryDictionary;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import helium314.keyboard.latin.common.StringUtils;
|
||||||
|
import helium314.keyboard.latin.utils.Log;
|
||||||
|
import helium314.keyboard.latin.utils.SpacedTokens;
|
||||||
|
|
||||||
|
public class AppsBinaryDictionary extends ExpandableBinaryDictionary {
|
||||||
|
private static final String TAG = AppsBinaryDictionary.class.getSimpleName();
|
||||||
|
private static final String NAME = "apps";
|
||||||
|
|
||||||
|
private static final int FREQUENCY_FOR_APPS = 100;
|
||||||
|
private static final int FREQUENCY_FOR_APPS_BIGRAM = 200;
|
||||||
|
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
private static final boolean DEBUG_DUMP = false;
|
||||||
|
|
||||||
|
private final AppsManager mAppsManager;
|
||||||
|
|
||||||
|
protected AppsBinaryDictionary(final Context ctx, final Locale locale,
|
||||||
|
final File dictFile, final String name) {
|
||||||
|
super(ctx, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_APPS, dictFile);
|
||||||
|
mAppsManager = new AppsManager(ctx);
|
||||||
|
reloadDictionaryIfRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppsBinaryDictionary getDictionary(final Context context, final Locale locale,
|
||||||
|
final File dictFile, final String dictNamePrefix) {
|
||||||
|
return new AppsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typically called whenever the dictionary is created for the first time or recreated when we
|
||||||
|
* think that there are updates to the dictionary. This is called asynchronously.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadInitialContentsLocked() {
|
||||||
|
loadDictionaryLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads app names to the dictionary.
|
||||||
|
*/
|
||||||
|
private void loadDictionaryLocked() {
|
||||||
|
for (final String name : mAppsManager.getNames()) {
|
||||||
|
addNameLocked(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the words in an app label to the binary dictionary along with their n-grams.
|
||||||
|
*/
|
||||||
|
private void addNameLocked(final String appLabel) {
|
||||||
|
NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext(
|
||||||
|
BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM);
|
||||||
|
// TODO: Better tokenization for non-Latin writing systems
|
||||||
|
for (final String word : new SpacedTokens(appLabel)) {
|
||||||
|
if (DEBUG_DUMP) {
|
||||||
|
Log.d(TAG, "addName word = " + word);
|
||||||
|
}
|
||||||
|
final int wordLen = StringUtils.codePointCount(word);
|
||||||
|
// Don't add single letter words, possibly confuses capitalization of i.
|
||||||
|
if (1 < wordLen && wordLen <= MAX_WORD_LENGTH) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "addName " + appLabel + ", " + word + ", " + ngramContext);
|
||||||
|
}
|
||||||
|
runGCIfRequiredLocked(true /* mindsBlockByGC */);
|
||||||
|
addUnigramLocked(word, FREQUENCY_FOR_APPS,
|
||||||
|
null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
|
||||||
|
false /* isPossiblyOffensive */,
|
||||||
|
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
|
||||||
|
if (ngramContext.isValid()) {
|
||||||
|
runGCIfRequiredLocked(true /* mindsBlockByGC */);
|
||||||
|
addNgramEntryLocked(ngramContext,
|
||||||
|
word,
|
||||||
|
FREQUENCY_FOR_APPS_BIGRAM,
|
||||||
|
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
|
||||||
|
}
|
||||||
|
ngramContext = ngramContext.getNextNgramContext(
|
||||||
|
new NgramContext.WordInfo(word));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
app/src/main/java/helium314/keyboard/latin/AppsManager.kt
Normal file
30
app/src/main/java/helium314/keyboard/latin/AppsManager.kt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
package helium314.keyboard.latin
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.ResolveInfo
|
||||||
|
import java.util.HashSet
|
||||||
|
|
||||||
|
class AppsManager(context: Context) {
|
||||||
|
private val mPackageManager: PackageManager = context.packageManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all app labels associated with a launcher icon, sorted arbitrarily.
|
||||||
|
*/
|
||||||
|
fun getNames(): HashSet<String> {
|
||||||
|
val filter = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
// activities with an entry/icon for the launcher
|
||||||
|
val launcherApps: List<ResolveInfo> = mPackageManager.queryIntentActivities(filter, 0)
|
||||||
|
|
||||||
|
val names = HashSet<String>(launcherApps.size)
|
||||||
|
for (info in launcherApps) {
|
||||||
|
val name = info.activityInfo.loadLabel(mPackageManager).toString()
|
||||||
|
names.add(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,18 +2,12 @@
|
||||||
|
|
||||||
package helium314.keyboard.latin
|
package helium314.keyboard.latin
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ClipboardHistoryEntry (
|
data class ClipboardHistoryEntry (
|
||||||
var timeStamp: Long,
|
var timeStamp: Long,
|
||||||
@Serializable(with = CharSequenceStringSerializer::class)
|
val content: String,
|
||||||
val content: CharSequence,
|
|
||||||
var isPinned: Boolean = false
|
var isPinned: Boolean = false
|
||||||
) : Comparable<ClipboardHistoryEntry> {
|
) : Comparable<ClipboardHistoryEntry> {
|
||||||
override fun compareTo(other: ClipboardHistoryEntry): Int {
|
override fun compareTo(other: ClipboardHistoryEntry): Int {
|
||||||
|
@ -21,13 +15,3 @@ data class ClipboardHistoryEntry (
|
||||||
return if (result != 0) result else other.timeStamp.compareTo(timeStamp)
|
return if (result != 0) result else other.timeStamp.compareTo(timeStamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharSequenceStringSerializer : KSerializer<CharSequence> {
|
|
||||||
override val descriptor = PrimitiveSerialDescriptor("CharSequence", PrimitiveKind.STRING)
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: CharSequence) {
|
|
||||||
encoder.encodeString(value.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder) = decoder.decodeString()
|
|
||||||
}
|
|
|
@ -61,7 +61,7 @@ class ClipboardHistoryManager(
|
||||||
val content = clipItem.coerceToText(latinIME)
|
val content = clipItem.coerceToText(latinIME)
|
||||||
if (TextUtils.isEmpty(content)) return
|
if (TextUtils.isEmpty(content)) return
|
||||||
|
|
||||||
val duplicateEntryIndex = historyEntries.indexOfFirst { it.content.toString() == content.toString() }
|
val duplicateEntryIndex = historyEntries.indexOfFirst { it.content == content.toString() }
|
||||||
if (duplicateEntryIndex != -1) {
|
if (duplicateEntryIndex != -1) {
|
||||||
val existingEntry = historyEntries[duplicateEntryIndex]
|
val existingEntry = historyEntries[duplicateEntryIndex]
|
||||||
if (existingEntry.timeStamp == timeStamp) return // nothing to change (may occur frequently starting with API 30)
|
if (existingEntry.timeStamp == timeStamp) return // nothing to change (may occur frequently starting with API 30)
|
||||||
|
@ -74,9 +74,9 @@ class ClipboardHistoryManager(
|
||||||
onHistoryChangeListener?.onClipboardHistoryEntryMoved(duplicateEntryIndex, newIndex)
|
onHistoryChangeListener?.onClipboardHistoryEntryMoved(duplicateEntryIndex, newIndex)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (historyEntries.any { it.content.toString() == content.toString() }) return
|
if (historyEntries.any { it.content == content.toString() }) return
|
||||||
|
|
||||||
val entry = ClipboardHistoryEntry(timeStamp, content)
|
val entry = ClipboardHistoryEntry(timeStamp, content.toString())
|
||||||
historyEntries.add(entry)
|
historyEntries.add(entry)
|
||||||
sortHistoryEntries()
|
sortHistoryEntries()
|
||||||
val at = historyEntries.indexOf(entry)
|
val at = historyEntries.indexOf(entry)
|
||||||
|
@ -120,7 +120,7 @@ class ClipboardHistoryManager(
|
||||||
|
|
||||||
private fun checkClipRetentionElapsed() {
|
private fun checkClipRetentionElapsed() {
|
||||||
val mins = latinIME.mSettings.current.mClipboardHistoryRetentionTime
|
val mins = latinIME.mSettings.current.mClipboardHistoryRetentionTime
|
||||||
if (mins <= 0) return // No retention limit
|
if (mins > 120) return // No retention limit, changed from <= 0 because we want it to be larger than all other choices
|
||||||
val maxClipRetentionTime = mins * 60 * 1000L
|
val maxClipRetentionTime = mins * 60 * 1000L
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
historyEntries.removeAll { !it.isPinned && (now - it.timeStamp) > maxClipRetentionTime }
|
historyEntries.removeAll { !it.isPinned && (now - it.timeStamp) > maxClipRetentionTime }
|
||||||
|
|
|
@ -14,7 +14,6 @@ import android.provider.ContactsContract.Contacts;
|
||||||
import helium314.keyboard.latin.utils.Log;
|
import helium314.keyboard.latin.utils.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.inputmethod.latin.BinaryDictionary;
|
import com.android.inputmethod.latin.BinaryDictionary;
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ContactsBinaryDictionary getDictionary(final Context context, @NonNull final Locale locale,
|
public static ContactsBinaryDictionary getDictionary(final Context context, @NonNull final Locale locale,
|
||||||
final File dictFile, final String dictNamePrefix, @Nullable final String account) {
|
final File dictFile, final String dictNamePrefix) {
|
||||||
return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
|
return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ public class ContactsDictionaryConstants {
|
||||||
/**
|
/**
|
||||||
* Frequency for contacts information into the dictionary
|
* Frequency for contacts information into the dictionary
|
||||||
*/
|
*/
|
||||||
public static final int FREQUENCY_FOR_CONTACTS = 40;
|
public static final int FREQUENCY_FOR_CONTACTS = 100; // much increased from original frequency because contacts were barely suggested
|
||||||
public static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
|
public static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 200; // todo: seems broken, how to actually get bigrams?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not attempt to query contacts if there are more than this many entries.
|
* Do not attempt to query contacts if there are more than this many entries.
|
||||||
|
|
|
@ -6,15 +6,14 @@
|
||||||
|
|
||||||
package helium314.keyboard.latin;
|
package helium314.keyboard.latin;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
|
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
import helium314.keyboard.latin.common.ComposedData;
|
import helium314.keyboard.latin.common.ComposedData;
|
||||||
|
import helium314.keyboard.latin.makedict.WordProperty;
|
||||||
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
|
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
|
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
|
||||||
* strokes.
|
* strokes.
|
||||||
|
@ -48,24 +47,16 @@ public abstract class Dictionary {
|
||||||
// phony dictionary instances for them.
|
// phony dictionary instances for them.
|
||||||
public static final String TYPE_MAIN = "main";
|
public static final String TYPE_MAIN = "main";
|
||||||
public static final String TYPE_CONTACTS = "contacts";
|
public static final String TYPE_CONTACTS = "contacts";
|
||||||
|
public static final String TYPE_APPS = "apps";
|
||||||
// User dictionary, the system-managed one.
|
// User dictionary, the system-managed one.
|
||||||
public static final String TYPE_USER = "user";
|
public static final String TYPE_USER = "user";
|
||||||
// User history dictionary internal to LatinIME.
|
// User history dictionary internal to LatinIME.
|
||||||
public static final String TYPE_USER_HISTORY = "history";
|
public static final String TYPE_USER_HISTORY = "history";
|
||||||
|
public static final String TYPE_EMOJI = "emoji";
|
||||||
public final String mDictType;
|
public final String mDictType;
|
||||||
// The locale for this dictionary. May be null if unknown (phony dictionary for example).
|
// The locale for this dictionary. May be null if unknown (phony dictionary for example).
|
||||||
public final Locale mLocale;
|
public final Locale mLocale;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set out of the dictionary types listed above that are based on data specific to the user,
|
|
||||||
* e.g., the user's contacts.
|
|
||||||
*/
|
|
||||||
private static final HashSet<String> sUserSpecificDictionaryTypes = new HashSet<>(Arrays.asList(
|
|
||||||
TYPE_USER_TYPED,
|
|
||||||
TYPE_USER,
|
|
||||||
TYPE_CONTACTS,
|
|
||||||
TYPE_USER_HISTORY));
|
|
||||||
|
|
||||||
public Dictionary(final String dictType, final Locale locale) {
|
public Dictionary(final String dictType, final Locale locale) {
|
||||||
mDictType = dictType;
|
mDictType = dictType;
|
||||||
mLocale = locale;
|
mLocale = locale;
|
||||||
|
@ -178,14 +169,25 @@ public abstract class Dictionary {
|
||||||
* @return Whether this dictionary is specific to the user.
|
* @return Whether this dictionary is specific to the user.
|
||||||
*/
|
*/
|
||||||
public boolean isUserSpecific() {
|
public boolean isUserSpecific() {
|
||||||
return sUserSpecificDictionaryTypes.contains(mDictType);
|
return switch (mDictType) {
|
||||||
|
case TYPE_USER_TYPED,
|
||||||
|
TYPE_USER,
|
||||||
|
TYPE_CONTACTS,
|
||||||
|
TYPE_APPS,
|
||||||
|
TYPE_USER_HISTORY -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public WordProperty getWordProperty(final String word, final boolean isBeginningOfSentence) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
|
* Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
|
||||||
* real dictionary.
|
* real dictionary.
|
||||||
*/
|
*/
|
||||||
static class PhonyDictionary extends Dictionary {
|
public static class PhonyDictionary extends Dictionary {
|
||||||
PhonyDictionary(final String type) {
|
PhonyDictionary(final String type) {
|
||||||
super(type, null);
|
super(type, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,30 +6,35 @@
|
||||||
|
|
||||||
package helium314.keyboard.latin;
|
package helium314.keyboard.latin;
|
||||||
|
|
||||||
import helium314.keyboard.latin.utils.Log;
|
|
||||||
|
|
||||||
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
|
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
import helium314.keyboard.latin.common.ComposedData;
|
import helium314.keyboard.latin.common.ComposedData;
|
||||||
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
|
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
|
||||||
|
import helium314.keyboard.latin.utils.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for a collection of dictionaries that behave like one dictionary.
|
* Class for a collection of dictionaries that behave like one dictionary.
|
||||||
*/
|
*/
|
||||||
public final class DictionaryCollection extends Dictionary {
|
public final class DictionaryCollection extends Dictionary {
|
||||||
private final String TAG = DictionaryCollection.class.getSimpleName();
|
private final String TAG = DictionaryCollection.class.getSimpleName();
|
||||||
private final CopyOnWriteArrayList<Dictionary> mDictionaries;
|
private final ArrayList<Dictionary> mDictionaries;
|
||||||
|
private final float[] mWeights;
|
||||||
|
|
||||||
public DictionaryCollection(final String dictType, final Locale locale,
|
public DictionaryCollection(final String dictType, final Locale locale,
|
||||||
final Collection<Dictionary> dictionaries) {
|
final Collection<Dictionary> dictionaries, final float[] weights) {
|
||||||
super(dictType, locale);
|
super(dictType, locale);
|
||||||
mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
|
mDictionaries = new ArrayList<>(dictionaries);
|
||||||
mDictionaries.removeAll(Collections.singleton(null));
|
mDictionaries.removeAll(Collections.singleton(null));
|
||||||
|
if (mDictionaries.size() > weights.length) {
|
||||||
|
mWeights = new float[mDictionaries.size()];
|
||||||
|
Arrays.fill(mWeights, 1f);
|
||||||
|
Log.w(TAG, "got weights array of length " + weights.length + ", expected "+mDictionaries.size());
|
||||||
|
} else mWeights = weights;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,19 +43,19 @@ public final class DictionaryCollection extends Dictionary {
|
||||||
final SettingsValuesForSuggestion settingsValuesForSuggestion,
|
final SettingsValuesForSuggestion settingsValuesForSuggestion,
|
||||||
final int sessionId, final float weightForLocale,
|
final int sessionId, final float weightForLocale,
|
||||||
final float[] inOutWeightOfLangModelVsSpatialModel) {
|
final float[] inOutWeightOfLangModelVsSpatialModel) {
|
||||||
final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
|
final ArrayList<Dictionary> dictionaries = mDictionaries;
|
||||||
if (dictionaries.isEmpty()) return null;
|
if (dictionaries.isEmpty()) return null;
|
||||||
// To avoid creating unnecessary objects, we get the list out of the first
|
// To avoid creating unnecessary objects, we get the list out of the first
|
||||||
// dictionary and add the rest to it if not null, hence the get(0)
|
// dictionary and add the rest to it if not null, hence the get(0)
|
||||||
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composedData,
|
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composedData,
|
||||||
ngramContext, proximityInfoHandle, settingsValuesForSuggestion, sessionId,
|
ngramContext, proximityInfoHandle, settingsValuesForSuggestion, sessionId,
|
||||||
weightForLocale, inOutWeightOfLangModelVsSpatialModel);
|
weightForLocale * mWeights[0], inOutWeightOfLangModelVsSpatialModel);
|
||||||
if (null == suggestions) suggestions = new ArrayList<>();
|
if (null == suggestions) suggestions = new ArrayList<>();
|
||||||
final int length = dictionaries.size();
|
final int length = dictionaries.size();
|
||||||
for (int i = 1; i < length; ++ i) {
|
for (int i = 1; i < length; ++ i) {
|
||||||
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(
|
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(
|
||||||
composedData, ngramContext, proximityInfoHandle, settingsValuesForSuggestion,
|
composedData, ngramContext, proximityInfoHandle, settingsValuesForSuggestion,
|
||||||
sessionId, weightForLocale, inOutWeightOfLangModelVsSpatialModel);
|
sessionId, weightForLocale * mWeights[i], inOutWeightOfLangModelVsSpatialModel);
|
||||||
if (null != sugg) suggestions.addAll(sugg);
|
if (null != sugg) suggestions.addAll(sugg);
|
||||||
}
|
}
|
||||||
return suggestions;
|
return suggestions;
|
||||||
|
@ -93,22 +98,4 @@ public final class DictionaryCollection extends Dictionary {
|
||||||
for (final Dictionary dict : mDictionaries)
|
for (final Dictionary dict : mDictionaries)
|
||||||
dict.close();
|
dict.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: this is not thread-safe. Take necessary precaution when calling.
|
|
||||||
public void addDictionary(final Dictionary newDict) {
|
|
||||||
if (null == newDict) return;
|
|
||||||
if (mDictionaries.contains(newDict)) {
|
|
||||||
Log.w(TAG, "This collection already contains this dictionary: " + newDict);
|
|
||||||
}
|
|
||||||
mDictionaries.add(newDict);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning: this is not thread-safe. Take necessary precaution when calling.
|
|
||||||
public void removeDictionary(final Dictionary dict) {
|
|
||||||
if (mDictionaries.contains(dict)) {
|
|
||||||
mDictionaries.remove(dict);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "This collection does not contain this dictionary: " + dict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that facilitates interaction with different kinds of dictionaries. Provides APIs to
|
* Interface that facilitates interaction with different kinds of dictionaries. Provides APIs to
|
||||||
* instantiate and select the correct dictionaries (based on language or account), update entries
|
* instantiate and select the correct dictionaries (based on language and settings), update entries
|
||||||
* and fetch suggestions. Currently AndroidSpellCheckerService and LatinIME both use
|
* and fetch suggestions. Currently AndroidSpellCheckerService and LatinIME both use
|
||||||
* DictionaryFacilitator as a client for interacting with dictionaries.
|
* DictionaryFacilitator as a client for interacting with dictionaries.
|
||||||
*/
|
*/
|
||||||
|
@ -32,22 +32,20 @@ public interface DictionaryFacilitator {
|
||||||
String[] ALL_DICTIONARY_TYPES = new String[] {
|
String[] ALL_DICTIONARY_TYPES = new String[] {
|
||||||
Dictionary.TYPE_MAIN,
|
Dictionary.TYPE_MAIN,
|
||||||
Dictionary.TYPE_CONTACTS,
|
Dictionary.TYPE_CONTACTS,
|
||||||
|
Dictionary.TYPE_APPS,
|
||||||
Dictionary.TYPE_USER_HISTORY,
|
Dictionary.TYPE_USER_HISTORY,
|
||||||
Dictionary.TYPE_USER};
|
Dictionary.TYPE_USER};
|
||||||
|
|
||||||
String[] DYNAMIC_DICTIONARY_TYPES = new String[] {
|
String[] DYNAMIC_DICTIONARY_TYPES = new String[] {
|
||||||
Dictionary.TYPE_CONTACTS,
|
Dictionary.TYPE_CONTACTS,
|
||||||
|
Dictionary.TYPE_APPS,
|
||||||
Dictionary.TYPE_USER_HISTORY,
|
Dictionary.TYPE_USER_HISTORY,
|
||||||
Dictionary.TYPE_USER};
|
Dictionary.TYPE_USER};
|
||||||
|
|
||||||
/**
|
/** The facilitator will put words into the cache whenever it decodes them. */
|
||||||
* The facilitator will put words into the cache whenever it decodes them.
|
|
||||||
*/
|
|
||||||
void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache);
|
void setValidSpellingWordReadCache(final LruCache<String, Boolean> cache);
|
||||||
|
|
||||||
/**
|
/** The facilitator will get words from the cache whenever it needs to check their spelling. */
|
||||||
* The facilitator will get words from the cache whenever it needs to check their spelling.
|
|
||||||
*/
|
|
||||||
void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache);
|
void setValidSpellingWordWriteCache(final LruCache<String, Boolean> cache);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,56 +75,64 @@ public interface DictionaryFacilitator {
|
||||||
*/
|
*/
|
||||||
void onFinishInput(Context context);
|
void onFinishInput(Context context);
|
||||||
|
|
||||||
|
/** whether a dictionary is set */
|
||||||
boolean isActive();
|
boolean isActive();
|
||||||
|
|
||||||
|
/** the locale provided in resetDictionaries */
|
||||||
@NonNull Locale getMainLocale();
|
@NonNull Locale getMainLocale();
|
||||||
|
|
||||||
// useful for multilingual typing
|
/** the most "trusted" locale, differs from getMainLocale only if multilingual typing is used */
|
||||||
Locale getCurrentLocale();
|
@NonNull Locale getCurrentLocale();
|
||||||
|
|
||||||
boolean usesSameSettings(
|
boolean usesSameSettings(
|
||||||
@NonNull final List<Locale> locales,
|
@NonNull final List<Locale> locales,
|
||||||
final boolean contacts,
|
final boolean contacts,
|
||||||
final boolean personalization,
|
final boolean apps,
|
||||||
@Nullable final String account
|
final boolean personalization
|
||||||
);
|
);
|
||||||
|
|
||||||
String getAccount();
|
/** switches to newLocale, gets secondary locales from current settings, and sets secondary dictionaries */
|
||||||
|
|
||||||
void resetDictionaries(
|
void resetDictionaries(
|
||||||
final Context context,
|
final Context context,
|
||||||
final Locale newLocale,
|
final Locale newLocale,
|
||||||
final boolean useContactsDict,
|
final boolean useContactsDict,
|
||||||
|
final boolean useAppsDict,
|
||||||
final boolean usePersonalizedDicts,
|
final boolean usePersonalizedDicts,
|
||||||
final boolean forceReloadMainDictionary,
|
final boolean forceReloadMainDictionary,
|
||||||
@Nullable final String account,
|
|
||||||
final String dictNamePrefix,
|
final String dictNamePrefix,
|
||||||
@Nullable final DictionaryInitializationListener listener);
|
@Nullable final DictionaryInitializationListener listener);
|
||||||
|
|
||||||
|
/** removes the word from all editable dictionaries, and adds it to a blacklist in case it's in a read-only dictionary */
|
||||||
void removeWord(String word);
|
void removeWord(String word);
|
||||||
|
|
||||||
void closeDictionaries();
|
void closeDictionaries();
|
||||||
|
|
||||||
// The main dictionaries are loaded asynchronously. Don't cache the return value
|
/** main dictionaries are loaded asynchronously after resetDictionaries */
|
||||||
// of these methods.
|
|
||||||
boolean hasAtLeastOneInitializedMainDictionary();
|
boolean hasAtLeastOneInitializedMainDictionary();
|
||||||
|
|
||||||
|
/** main dictionaries are loaded asynchronously after resetDictionaries */
|
||||||
boolean hasAtLeastOneUninitializedMainDictionary();
|
boolean hasAtLeastOneUninitializedMainDictionary();
|
||||||
|
|
||||||
|
/** main dictionaries are loaded asynchronously after resetDictionaries */
|
||||||
void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
|
void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
|
||||||
throws InterruptedException;
|
throws InterruptedException;
|
||||||
|
|
||||||
|
/** adds the word to user history dictionary, calls adjustConfindences, and might add it to personal dictionary if the setting is enabled */
|
||||||
void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
|
void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
|
||||||
@NonNull final NgramContext ngramContext, final long timeStampInSeconds,
|
@NonNull final NgramContext ngramContext, final long timeStampInSeconds,
|
||||||
final boolean blockPotentiallyOffensive);
|
final boolean blockPotentiallyOffensive);
|
||||||
|
|
||||||
|
/** adjust confidences for multilingual typing */
|
||||||
void adjustConfidences(final String word, final boolean wasAutoCapitalized);
|
void adjustConfidences(final String word, final boolean wasAutoCapitalized);
|
||||||
|
|
||||||
|
/** a string with all used locales and their current confidences, null if multilingual typing is not used */
|
||||||
|
@Nullable String localesAndConfidences();
|
||||||
|
|
||||||
|
/** completely removes the word from user history (currently not if event is a backspace event) */
|
||||||
void unlearnFromUserHistory(final String word,
|
void unlearnFromUserHistory(final String word,
|
||||||
@NonNull final NgramContext ngramContext, final long timeStampInSeconds,
|
@NonNull final NgramContext ngramContext, final long timeStampInSeconds,
|
||||||
final int eventType);
|
final int eventType);
|
||||||
|
|
||||||
// TODO: Revise the way to fusion suggestion results.
|
|
||||||
@NonNull SuggestionResults getSuggestionResults(final ComposedData composedData,
|
@NonNull SuggestionResults getSuggestionResults(final ComposedData composedData,
|
||||||
final NgramContext ngramContext, @NonNull final Keyboard keyboard,
|
final NgramContext ngramContext, @NonNull final Keyboard keyboard,
|
||||||
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId,
|
final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId,
|
||||||
|
@ -136,12 +142,10 @@ public interface DictionaryFacilitator {
|
||||||
|
|
||||||
boolean isValidSuggestionWord(final String word);
|
boolean isValidSuggestionWord(final String word);
|
||||||
|
|
||||||
boolean clearUserHistoryDictionary(final Context context);
|
void clearUserHistoryDictionary(final Context context);
|
||||||
|
|
||||||
String dump(final Context context);
|
String dump(final Context context);
|
||||||
|
|
||||||
String localesAndConfidences();
|
|
||||||
|
|
||||||
void dumpDictionaryForDebug(final String dictName);
|
void dumpDictionaryForDebug(final String dictName);
|
||||||
|
|
||||||
@NonNull List<DictionaryStats> getDictionaryStats(final Context context);
|
@NonNull List<DictionaryStats> getDictionaryStats(final Context context);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,826 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Android Open Source Project
|
||||||
|
* modified
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||||
|
*/
|
||||||
|
package helium314.keyboard.latin
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.UserDictionary
|
||||||
|
import android.util.LruCache
|
||||||
|
import helium314.keyboard.keyboard.Keyboard
|
||||||
|
import helium314.keyboard.keyboard.emoji.SupportedEmojis
|
||||||
|
import helium314.keyboard.latin.DictionaryFacilitator.DictionaryInitializationListener
|
||||||
|
import helium314.keyboard.latin.NgramContext.WordInfo
|
||||||
|
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo
|
||||||
|
import helium314.keyboard.latin.common.ComposedData
|
||||||
|
import helium314.keyboard.latin.common.Constants
|
||||||
|
import helium314.keyboard.latin.common.StringUtils
|
||||||
|
import helium314.keyboard.latin.common.decapitalize
|
||||||
|
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||||
|
import helium314.keyboard.latin.permissions.PermissionsUtil
|
||||||
|
import helium314.keyboard.latin.personalization.UserHistoryDictionary
|
||||||
|
import helium314.keyboard.latin.settings.Settings
|
||||||
|
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion
|
||||||
|
import helium314.keyboard.latin.utils.Log
|
||||||
|
import helium314.keyboard.latin.utils.SubtypeSettings
|
||||||
|
import helium314.keyboard.latin.utils.SuggestionResults
|
||||||
|
import helium314.keyboard.latin.utils.getSecondaryLocales
|
||||||
|
import helium314.keyboard.latin.utils.locale
|
||||||
|
import helium314.keyboard.latin.utils.prefs
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facilitates interaction with different kinds of dictionaries. Provides APIs
|
||||||
|
* to instantiate and select the correct dictionaries (based on language and settings),
|
||||||
|
* update entries and fetch suggestions.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Currently AndroidSpellCheckerService and LatinIME both use DictionaryFacilitator as
|
||||||
|
* a client for interacting with dictionaries.
|
||||||
|
*/
|
||||||
|
class DictionaryFacilitatorImpl : DictionaryFacilitator {
|
||||||
|
private var dictionaryGroups = listOf(DictionaryGroup())
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var mLatchForWaitingLoadingMainDictionaries = CountDownLatch(0)
|
||||||
|
|
||||||
|
// The library does not deal well with ngram history for auto-capitalized words, so we adjust
|
||||||
|
// the ngram context to store next word suggestions for such cases.
|
||||||
|
// todo: this is awful, find a better solution / workaround
|
||||||
|
// or remove completely? not sure if it's actually an improvement
|
||||||
|
// should be fixed in the library, but that's not feasible with current user-provides-library approach
|
||||||
|
// added in 12cbd43bda7d0f0cd73925e9cf836de751c32ed0 / https://github.com/Helium314/HeliBoard/issues/135
|
||||||
|
private var tryChangingWords = false
|
||||||
|
private var changeFrom = ""
|
||||||
|
private var changeTo = ""
|
||||||
|
|
||||||
|
// todo: write cache never set, and never read (only written)
|
||||||
|
// tried to use read cache for a while, but small performance improvements are not worth the work,
|
||||||
|
// see https://github.com/Helium314/HeliBoard/issues/307
|
||||||
|
private var mValidSpellingWordReadCache: LruCache<String, Boolean>? = null
|
||||||
|
private var mValidSpellingWordWriteCache: LruCache<String, Boolean>? = null
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
|
override fun setValidSpellingWordReadCache(cache: LruCache<String, Boolean>) {
|
||||||
|
mValidSpellingWordReadCache = cache
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValidSpellingWordWriteCache(cache: LruCache<String, Boolean>) {
|
||||||
|
mValidSpellingWordWriteCache = cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// judging by usage before adding multilingual typing, this should check primary group locale only
|
||||||
|
override fun isForLocale(locale: Locale?): Boolean {
|
||||||
|
return locale != null && locale == dictionaryGroups[0].locale
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartInput() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinishInput(context: Context) {
|
||||||
|
for (dictGroup in dictionaryGroups) {
|
||||||
|
DictionaryFacilitator.ALL_DICTIONARY_TYPES.forEach { dictGroup.getDict(it)?.onFinishInput() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isActive(): Boolean {
|
||||||
|
return dictionaryGroups[0].locale.language.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMainLocale(): Locale {
|
||||||
|
return dictionaryGroups[0].locale
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentLocale(): Locale {
|
||||||
|
return currentlyPreferredDictionaryGroup.locale
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun usesSameSettings(locales: List<Locale>, contacts: Boolean, apps: Boolean, personalization: Boolean): Boolean {
|
||||||
|
val dictGroup = dictionaryGroups[0] // settings are the same for all groups
|
||||||
|
return contacts == dictGroup.hasDict(Dictionary.TYPE_CONTACTS)
|
||||||
|
&& apps == dictGroup.hasDict(Dictionary.TYPE_APPS)
|
||||||
|
&& personalization == dictGroup.hasDict(Dictionary.TYPE_USER_HISTORY)
|
||||||
|
&& locales.size == dictionaryGroups.size
|
||||||
|
&& locales.none { findDictionaryGroupWithLocale(dictionaryGroups, it) == null }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- managing (loading & closing) dictionaries ------------
|
||||||
|
|
||||||
|
override fun resetDictionaries(
|
||||||
|
context: Context,
|
||||||
|
newLocale: Locale,
|
||||||
|
useContactsDict: Boolean,
|
||||||
|
useAppsDict: Boolean,
|
||||||
|
usePersonalizedDicts: Boolean,
|
||||||
|
forceReloadMainDictionary: Boolean,
|
||||||
|
dictNamePrefix: String,
|
||||||
|
listener: DictionaryInitializationListener?
|
||||||
|
) {
|
||||||
|
Log.i(TAG, "resetDictionaries, force reloading main dictionary: $forceReloadMainDictionary")
|
||||||
|
|
||||||
|
val locales = getUsedLocales(newLocale, context)
|
||||||
|
|
||||||
|
val subDictTypesToUse = listOfNotNull(
|
||||||
|
Dictionary.TYPE_USER,
|
||||||
|
if (useAppsDict) Dictionary.TYPE_APPS else null,
|
||||||
|
if (usePersonalizedDicts) Dictionary.TYPE_USER_HISTORY else null,
|
||||||
|
if (useContactsDict && PermissionsUtil.checkAllPermissionsGranted(context, Manifest.permission.READ_CONTACTS))
|
||||||
|
Dictionary.TYPE_CONTACTS else null
|
||||||
|
)
|
||||||
|
|
||||||
|
val (newDictionaryGroups, existingDictsToCleanup) =
|
||||||
|
getNewDictGroupsAndDictsToCleanup(locales, subDictTypesToUse, forceReloadMainDictionary, dictNamePrefix, context)
|
||||||
|
|
||||||
|
// Replace Dictionaries.
|
||||||
|
val oldDictionaryGroups: List<DictionaryGroup>
|
||||||
|
synchronized(this) {
|
||||||
|
oldDictionaryGroups = dictionaryGroups
|
||||||
|
dictionaryGroups = newDictionaryGroups
|
||||||
|
if (hasAtLeastOneUninitializedMainDictionary()) {
|
||||||
|
asyncReloadUninitializedMainDictionaries(context, locales, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary())
|
||||||
|
|
||||||
|
// Clean up old dictionaries.
|
||||||
|
existingDictsToCleanup.forEach { (locale, dictTypes) ->
|
||||||
|
val dictGroupToCleanup = findDictionaryGroupWithLocale(oldDictionaryGroups, locale) ?: return@forEach
|
||||||
|
for (dictType in dictTypes) {
|
||||||
|
dictGroupToCleanup.closeDict(dictType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mValidSpellingWordWriteCache?.evictAll()
|
||||||
|
mValidSpellingWordReadCache?.evictAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** creates dictionaryGroups for [newLocales] with given [newSubDictTypes], trying to re-use existing dictionaries.
|
||||||
|
* returns the new dictionaryGroups and unused dictionary types by locale */
|
||||||
|
private fun getNewDictGroupsAndDictsToCleanup(
|
||||||
|
newLocales: Collection<Locale>,
|
||||||
|
newSubDictTypes: Collection<String>,
|
||||||
|
forceReload: Boolean,
|
||||||
|
dictNamePrefix: String,
|
||||||
|
context: Context
|
||||||
|
): Pair<List<DictionaryGroup>, Map<Locale, List<String>>> {
|
||||||
|
// Gather all dictionaries by locale. We may remove some from the list later.
|
||||||
|
val existingDictsToCleanup = HashMap<Locale, MutableList<String>>()
|
||||||
|
for (dictGroup in dictionaryGroups) {
|
||||||
|
existingDictsToCleanup[dictGroup.locale] = DictionaryFacilitator.ALL_DICTIONARY_TYPES
|
||||||
|
.filterTo(mutableListOf()) { dictGroup.hasDict(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new dictionary groups and remove dictionaries to re-use from existingDictsToCleanup
|
||||||
|
val newDictionaryGroups = mutableListOf<DictionaryGroup>()
|
||||||
|
for (locale in newLocales) {
|
||||||
|
// get existing dictionary group for new locale
|
||||||
|
val oldDictGroupForLocale = findDictionaryGroupWithLocale(dictionaryGroups, locale)
|
||||||
|
val dictTypesToCleanupForLocale = existingDictsToCleanup[locale]
|
||||||
|
|
||||||
|
// create new or re-use already loaded main dict
|
||||||
|
val mainDict: Dictionary?
|
||||||
|
if (forceReload || oldDictGroupForLocale == null
|
||||||
|
|| !oldDictGroupForLocale.hasDict(Dictionary.TYPE_MAIN)
|
||||||
|
) {
|
||||||
|
mainDict = null // null main dicts will be loaded later in asyncReloadUninitializedMainDictionaries
|
||||||
|
} else {
|
||||||
|
mainDict = oldDictGroupForLocale.getDict(Dictionary.TYPE_MAIN)
|
||||||
|
dictTypesToCleanupForLocale?.remove(Dictionary.TYPE_MAIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new or re-use already loaded sub-dicts
|
||||||
|
val subDicts: MutableMap<String, ExpandableBinaryDictionary> = HashMap()
|
||||||
|
for (subDictType in newSubDictTypes) {
|
||||||
|
val subDict: ExpandableBinaryDictionary
|
||||||
|
if (forceReload || oldDictGroupForLocale == null
|
||||||
|
|| !oldDictGroupForLocale.hasDict(subDictType)
|
||||||
|
) {
|
||||||
|
// Create a new dictionary.
|
||||||
|
subDict = createSubDict(subDictType, context, locale, null, dictNamePrefix) ?: continue
|
||||||
|
} else {
|
||||||
|
// Reuse the existing dictionary.
|
||||||
|
subDict = oldDictGroupForLocale.getSubDict(subDictType) ?: continue
|
||||||
|
dictTypesToCleanupForLocale?.remove(subDictType)
|
||||||
|
}
|
||||||
|
subDicts[subDictType] = subDict
|
||||||
|
}
|
||||||
|
val newDictGroup = DictionaryGroup(locale, mainDict, subDicts, context)
|
||||||
|
newDictionaryGroups.add(newDictGroup)
|
||||||
|
}
|
||||||
|
return newDictionaryGroups to existingDictsToCleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun asyncReloadUninitializedMainDictionaries(
|
||||||
|
context: Context, locales: Collection<Locale>, listener: DictionaryInitializationListener?
|
||||||
|
) {
|
||||||
|
val latchForWaitingLoadingMainDictionary = CountDownLatch(1)
|
||||||
|
mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val useEmojiDict = Settings.getValues().mSuggestEmojis
|
||||||
|
val dictGroupsWithNewMainDict = locales.mapNotNull {
|
||||||
|
val dictionaryGroup = findDictionaryGroupWithLocale(dictionaryGroups, it)
|
||||||
|
if (dictionaryGroup == null) {
|
||||||
|
Log.w(TAG, "Expected a dictionary group for $it but none found")
|
||||||
|
return@mapNotNull null // This should never happen
|
||||||
|
}
|
||||||
|
if (dictionaryGroup.getDict(Dictionary.TYPE_MAIN)?.isInitialized == true) null
|
||||||
|
else dictionaryGroup to DictionaryFactory.createMainDictionaryCollection(context, it, useEmojiDict)
|
||||||
|
}
|
||||||
|
synchronized(this) {
|
||||||
|
dictGroupsWithNewMainDict.forEach { (dictGroup, mainDict) ->
|
||||||
|
dictGroup.setMainDict(mainDict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary())
|
||||||
|
latchForWaitingLoadingMainDictionary.countDown()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "could not initialize main dictionaries for $locales", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeDictionaries() {
|
||||||
|
val dictionaryGroupsToClose: List<DictionaryGroup>
|
||||||
|
synchronized(this) {
|
||||||
|
dictionaryGroupsToClose = dictionaryGroups
|
||||||
|
dictionaryGroups = listOf(DictionaryGroup())
|
||||||
|
}
|
||||||
|
for (dictionaryGroup in dictionaryGroupsToClose) {
|
||||||
|
for (dictType in DictionaryFacilitator.ALL_DICTIONARY_TYPES) {
|
||||||
|
dictionaryGroup.closeDict(dictType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main dictionaries are loaded asynchronously. Don't cache the return value of these methods.
|
||||||
|
override fun hasAtLeastOneInitializedMainDictionary(): Boolean =
|
||||||
|
dictionaryGroups.any { it.getDict(Dictionary.TYPE_MAIN)?.isInitialized == true }
|
||||||
|
|
||||||
|
override fun hasAtLeastOneUninitializedMainDictionary(): Boolean =
|
||||||
|
dictionaryGroups.any { it.getDict(Dictionary.TYPE_MAIN)?.isInitialized != true }
|
||||||
|
|
||||||
|
@Throws(InterruptedException::class)
|
||||||
|
override fun waitForLoadingMainDictionaries(timeout: Long, unit: TimeUnit) {
|
||||||
|
mLatchForWaitingLoadingMainDictionaries.await(timeout, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- actual dictionary stuff like getting suggestions ------------
|
||||||
|
|
||||||
|
override fun addToUserHistory(
|
||||||
|
suggestion: String, wasAutoCapitalized: Boolean, ngramContext: NgramContext,
|
||||||
|
timeStampInSeconds: Long, blockPotentiallyOffensive: Boolean
|
||||||
|
) {
|
||||||
|
// Update the spelling cache before learning. Words that are not yet added to user history
|
||||||
|
// and appear in no other language model are not considered valid.
|
||||||
|
putWordIntoValidSpellingWordCache("addToUserHistory", suggestion)
|
||||||
|
|
||||||
|
val words = suggestion.splitOnWhitespace().dropLastWhile { it.isEmpty() }
|
||||||
|
|
||||||
|
// increase / decrease confidence
|
||||||
|
if (words.size == 1) // ignore if more than a single word, which only happens with (badly working) spaceAwareGesture
|
||||||
|
adjustConfidences(suggestion, wasAutoCapitalized)
|
||||||
|
|
||||||
|
// Add word to user dictionary if it is in no other dictionary except user history dictionary (i.e. typed again).
|
||||||
|
val sv = Settings.getValues()
|
||||||
|
if (sv.mAddToPersonalDictionary // require the opt-in
|
||||||
|
&& sv.mAutoCorrectEnabled == sv.mAutoCorrectionEnabledPerUserSettings // don't add if user wants autocorrect but input field does not, see https://github.com/Helium314/HeliBoard/issues/427#issuecomment-1905438000
|
||||||
|
&& dictionaryGroups[0].hasDict(Dictionary.TYPE_USER_HISTORY) // require personalized suggestions
|
||||||
|
&& !wasAutoCapitalized // we can't be 100% sure about what the user intended to type, so better don't add it
|
||||||
|
&& words.size == 1 // only single words
|
||||||
|
) {
|
||||||
|
addToPersonalDictionaryIfInvalidButInHistory(suggestion)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ngramContextForCurrentWord = ngramContext
|
||||||
|
val preferredGroup = currentlyPreferredDictionaryGroup
|
||||||
|
for (i in words.indices) {
|
||||||
|
val currentWord = words[i]
|
||||||
|
val wasCurrentWordAutoCapitalized = (i == 0) && wasAutoCapitalized
|
||||||
|
// add to history for preferred dictionary group, to avoid mixing languages in history
|
||||||
|
addWordToUserHistory(
|
||||||
|
preferredGroup, ngramContextForCurrentWord, currentWord,
|
||||||
|
wasCurrentWordAutoCapitalized, timeStampInSeconds.toInt(), blockPotentiallyOffensive
|
||||||
|
)
|
||||||
|
ngramContextForCurrentWord = ngramContextForCurrentWord.getNextNgramContext(WordInfo(currentWord))
|
||||||
|
|
||||||
|
// remove manually entered blacklisted words from blacklist for likely matching languages
|
||||||
|
dictionaryGroups.filter { it.confidence == preferredGroup.confidence }.forEach {
|
||||||
|
it.removeFromBlacklist(currentWord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addWordToUserHistory(
|
||||||
|
dictionaryGroup: DictionaryGroup, ngramContext: NgramContext, word: String, wasAutoCapitalized: Boolean,
|
||||||
|
timeStampInSeconds: Int, blockPotentiallyOffensive: Boolean
|
||||||
|
) {
|
||||||
|
val userHistoryDictionary = dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY) ?: return
|
||||||
|
|
||||||
|
val mainFreq = dictionaryGroup.getDict(Dictionary.TYPE_MAIN)?.getFrequency(word) ?: Dictionary.NOT_A_PROBABILITY
|
||||||
|
if (mainFreq == 0 && blockPotentiallyOffensive)
|
||||||
|
return
|
||||||
|
if (tryChangingWords)
|
||||||
|
tryChangingWords = ngramContext.changeWordIfAfterBeginningOfSentence(changeFrom, changeTo)
|
||||||
|
|
||||||
|
val wordToUse: String
|
||||||
|
// Check for isBeginningOfSentenceContext too, because not all text fields auto-capitalize in this case.
|
||||||
|
// Even if the user capitalizes manually, they most likely don't want the capitalized form suggested.
|
||||||
|
if (wasAutoCapitalized || ngramContext.isBeginningOfSentenceContext) {
|
||||||
|
val decapitalizedWord = word.decapitalize(dictionaryGroup.locale) // try undoing auto-capitalization
|
||||||
|
if (isValidWord(word, DictionaryFacilitator.ALL_DICTIONARY_TYPES, dictionaryGroup)
|
||||||
|
&& !isValidWord(decapitalizedWord, DictionaryFacilitator.ALL_DICTIONARY_TYPES, dictionaryGroup)
|
||||||
|
) {
|
||||||
|
// If the word was auto-capitalized and exists only as a capitalized word in the
|
||||||
|
// dictionary, then we must not downcase it before registering it. For example,
|
||||||
|
// the name of the contacts in start-of-sentence position would come here with the
|
||||||
|
// wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
|
||||||
|
// of that contact's name which would end up popping in suggestions.
|
||||||
|
wordToUse = word
|
||||||
|
} else {
|
||||||
|
// If however the word is not in the dictionary, or exists as a de-capitalized word
|
||||||
|
// only, then we consider that was a lower-case word that had been auto-capitalized.
|
||||||
|
wordToUse = decapitalizedWord
|
||||||
|
tryChangingWords = true
|
||||||
|
changeFrom = word
|
||||||
|
changeTo = wordToUse
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// HACK: We'd like to avoid adding the capitalized form of common words to the User
|
||||||
|
// History dictionary in order to avoid suggesting them until the dictionary
|
||||||
|
// consolidation is done.
|
||||||
|
// TODO: Remove this hack when ready.
|
||||||
|
val lowerCasedWord = word.lowercase(dictionaryGroup.locale)
|
||||||
|
val lowerCaseFreqInMainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN)?.getFrequency(lowerCasedWord)
|
||||||
|
?: Dictionary.NOT_A_PROBABILITY
|
||||||
|
wordToUse = if (mainFreq < lowerCaseFreqInMainDict
|
||||||
|
&& lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT
|
||||||
|
) {
|
||||||
|
// Use lower cased word as the word can be a distracter of the popular word.
|
||||||
|
lowerCasedWord
|
||||||
|
} else {
|
||||||
|
word
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We demote unrecognized words (frequency <= 0) by specifying them as "invalid".
|
||||||
|
// We don't add words with 0-frequency (assuming they would be profanity etc.).
|
||||||
|
val isValid = mainFreq > 0
|
||||||
|
UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, wordToUse, isValid, timeStampInSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addToPersonalDictionaryIfInvalidButInHistory(word: String) {
|
||||||
|
if (word.length <= 1) return
|
||||||
|
val dictionaryGroup = clearlyPreferredDictionaryGroup ?: return
|
||||||
|
val userDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER) ?: return
|
||||||
|
val userHistoryDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY) ?: return
|
||||||
|
if (isValidWord(word, DictionaryFacilitator.ALL_DICTIONARY_TYPES, dictionaryGroup))
|
||||||
|
return // valid word, no reason to auto-add it to personal dict
|
||||||
|
if (userDict.isInDictionary(word))
|
||||||
|
return // should never happen, but better be safe
|
||||||
|
|
||||||
|
// User history always reports words as invalid, so we check the frequency instead.
|
||||||
|
// Testing shows that after 2 times adding, the frequency is 111, and then rises slowly with usage (values vary slightly).
|
||||||
|
// 120 is after 3 uses of the word, so we simply require more than that. todo: Could be made configurable.
|
||||||
|
// Words added to dictionaries (user and history) seem to be found only after some delay.
|
||||||
|
// This is not too bad, but it delays adding in case a user wants to fill a dictionary using this functionality
|
||||||
|
if (userHistoryDict.getFrequency(word) > 120) {
|
||||||
|
scope.launch {
|
||||||
|
// adding can throw IllegalArgumentException: Unknown URL content://user_dictionary/words
|
||||||
|
// https://stackoverflow.com/q/41474623 https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/490
|
||||||
|
// apparently some devices don't have a dictionary? or it's just sporadic hiccups?
|
||||||
|
runCatching { UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun putWordIntoValidSpellingWordCache(caller: String, originalWord: String) {
|
||||||
|
if (mValidSpellingWordWriteCache == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
val lowerCaseWord = originalWord.lowercase(currentLocale)
|
||||||
|
val lowerCaseValid = isValidSpellingWord(lowerCaseWord)
|
||||||
|
mValidSpellingWordWriteCache?.put(lowerCaseWord, lowerCaseValid)
|
||||||
|
|
||||||
|
val capitalWord = StringUtils.capitalizeFirstAndDowncaseRest(originalWord, currentLocale)
|
||||||
|
val capitalValid = if (lowerCaseValid) {
|
||||||
|
true // The lower case form of the word is valid, so the upper case must be valid.
|
||||||
|
} else {
|
||||||
|
isValidSpellingWord(capitalWord)
|
||||||
|
}
|
||||||
|
mValidSpellingWordWriteCache?.put(capitalWord, capitalValid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun adjustConfidences(word: String, wasAutoCapitalized: Boolean) {
|
||||||
|
if (dictionaryGroups.size == 1 || word.contains(Constants.WORD_SEPARATOR))
|
||||||
|
return
|
||||||
|
|
||||||
|
// if suggestion was auto-capitalized, check against both the suggestion and the de-capitalized suggestion
|
||||||
|
val decapitalizedSuggestion = if (wasAutoCapitalized) word.decapitalize(currentLocale) else word
|
||||||
|
dictionaryGroups.forEach {
|
||||||
|
if (isValidWord(word, DictionaryFacilitator.ALL_DICTIONARY_TYPES, it)) {
|
||||||
|
it.increaseConfidence()
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
// also increase confidence if suggestion was auto-capitalized and the lowercase variant it valid
|
||||||
|
if (wasAutoCapitalized && isValidWord(decapitalizedSuggestion, DictionaryFacilitator.ALL_DICTIONARY_TYPES, it))
|
||||||
|
it.increaseConfidence()
|
||||||
|
else it.decreaseConfidence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** the dictionaryGroup with most confidence, first group when tied */
|
||||||
|
private val currentlyPreferredDictionaryGroup: DictionaryGroup get() = dictionaryGroups.maxBy { it.confidence }
|
||||||
|
|
||||||
|
/** the only dictionary group, or the dictionaryGroup confidence >= DictionaryGroup.MAX_CONFIDENCE if all others have 0 */
|
||||||
|
private val clearlyPreferredDictionaryGroup: DictionaryGroup? get() {
|
||||||
|
if (dictionaryGroups.size == 1) return dictionaryGroups.first() // confidence not used if we only have a single group
|
||||||
|
|
||||||
|
val preferred = currentlyPreferredDictionaryGroup
|
||||||
|
if (preferred.confidence < DictionaryGroup.MAX_CONFIDENCE) return null
|
||||||
|
if (dictionaryGroups.any { it.confidence > 0 && it !== preferred })
|
||||||
|
return null
|
||||||
|
return preferred
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unlearnFromUserHistory(word: String, ngramContext: NgramContext, timeStampInSeconds: Long, eventType: Int) {
|
||||||
|
// TODO: Decide whether or not to remove the word on EVENT_BACKSPACE.
|
||||||
|
if (eventType != Constants.EVENT_BACKSPACE) {
|
||||||
|
currentlyPreferredDictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY)?.removeUnigramEntryDynamically(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the spelling cache after unlearning. Words that are removed from user history
|
||||||
|
// and appear in no other language model are not considered valid.
|
||||||
|
putWordIntoValidSpellingWordCache("unlearnFromUserHistory", word.lowercase(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Revise the way to fusion suggestion results.
|
||||||
|
override fun getSuggestionResults(
|
||||||
|
composedData: ComposedData, ngramContext: NgramContext, keyboard: Keyboard,
|
||||||
|
settingsValuesForSuggestion: SettingsValuesForSuggestion, sessionId: Int, inputStyle: Int
|
||||||
|
): SuggestionResults {
|
||||||
|
val proximityInfoHandle = keyboard.proximityInfo.nativeProximityInfo
|
||||||
|
val weightOfLangModelVsSpatialModel = floatArrayOf(Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL)
|
||||||
|
|
||||||
|
val waitForOtherDicts = if (dictionaryGroups.size == 1) null else CountDownLatch(dictionaryGroups.size - 1)
|
||||||
|
val suggestionsArray = Array<List<SuggestedWordInfo>?>(dictionaryGroups.size) { null }
|
||||||
|
for (i in 1..dictionaryGroups.lastIndex) {
|
||||||
|
scope.launch {
|
||||||
|
suggestionsArray[i] = getSuggestions(composedData, ngramContext, settingsValuesForSuggestion, sessionId,
|
||||||
|
proximityInfoHandle, weightOfLangModelVsSpatialModel, dictionaryGroups[i])
|
||||||
|
waitForOtherDicts?.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suggestionsArray[0] = getSuggestions(composedData, ngramContext, settingsValuesForSuggestion, sessionId,
|
||||||
|
proximityInfoHandle, weightOfLangModelVsSpatialModel, dictionaryGroups[0])
|
||||||
|
val suggestionResults = SuggestionResults(
|
||||||
|
SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext, false
|
||||||
|
)
|
||||||
|
waitForOtherDicts?.await()
|
||||||
|
|
||||||
|
suggestionsArray.forEach {
|
||||||
|
if (it == null) return@forEach
|
||||||
|
suggestionResults.addAll(it)
|
||||||
|
suggestionResults.mRawSuggestions?.addAll(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestionResults
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSuggestions(
|
||||||
|
composedData: ComposedData, ngramContext: NgramContext,
|
||||||
|
settingsValuesForSuggestion: SettingsValuesForSuggestion, sessionId: Int,
|
||||||
|
proximityInfoHandle: Long, weightOfLangModelVsSpatialModel: FloatArray, dictGroup: DictionaryGroup
|
||||||
|
): List<SuggestedWordInfo> {
|
||||||
|
val suggestions = ArrayList<SuggestedWordInfo>()
|
||||||
|
val weightForLocale = dictGroup.getWeightForLocale(dictionaryGroups, composedData.mIsBatchMode)
|
||||||
|
for (dictType in DictionaryFacilitator.ALL_DICTIONARY_TYPES) {
|
||||||
|
val dictionary = dictGroup.getDict(dictType) ?: continue
|
||||||
|
val dictionarySuggestions = dictionary.getSuggestions(composedData, ngramContext, proximityInfoHandle,
|
||||||
|
settingsValuesForSuggestion, sessionId, weightForLocale, weightOfLangModelVsSpatialModel
|
||||||
|
) ?: continue
|
||||||
|
|
||||||
|
// For some reason "garbage" words are produced when glide typing. For user history
|
||||||
|
// and main dictionaries we can filter them out by checking whether the dictionary
|
||||||
|
// actually contains the word. But personal and addon dictionaries may contain shortcuts,
|
||||||
|
// which do not pass an isInDictionary check (e.g. emojis).
|
||||||
|
// (if the main dict contains shortcuts to non-words, this will break!)
|
||||||
|
val checkForGarbage = composedData.mIsBatchMode && (dictType == Dictionary.TYPE_USER_HISTORY || dictType == Dictionary.TYPE_MAIN)
|
||||||
|
|
||||||
|
for (info in dictionarySuggestions) {
|
||||||
|
val word = info.word
|
||||||
|
if (isBlacklisted(word) || SupportedEmojis.isUnsupported(word)) // don't add blacklisted words and unsupported emojis
|
||||||
|
continue
|
||||||
|
if (checkForGarbage
|
||||||
|
// consider the user might use custom main dictionary containing shortcuts
|
||||||
|
// assume this is unlikely to happen, and take care about common shortcuts that are not actual words (emoji, symbols)
|
||||||
|
&& word.length > 2 // should exclude most symbol shortcuts
|
||||||
|
&& info.mSourceDict.mDictType == dictType // dictType is always main, but info.mSourceDict.mDictType contains the actual dict (main dict is a dictionary group)
|
||||||
|
&& !StringUtils.mightBeEmoji(word) // simplified check for performance reasons
|
||||||
|
&& !dictionary.isInDictionary(word)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (word.length == 1 && info.mSourceDict.mDictType == Dictionary.TYPE_EMOJI && !StringUtils.mightBeEmoji(word[0].code))
|
||||||
|
continue
|
||||||
|
|
||||||
|
suggestions.add(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return suggestions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spell checker is using this, and has its own instance of DictionaryFacilitatorImpl,
|
||||||
|
// meaning that it always has default mConfidence. So we cannot choose to only check preferred
|
||||||
|
// locale, and instead simply return true if word is in any of the available dictionaries
|
||||||
|
override fun isValidSpellingWord(word: String): Boolean {
|
||||||
|
mValidSpellingWordReadCache?.get(word)?.let { return it }
|
||||||
|
val result = dictionaryGroups.any { isValidWord(word, DictionaryFacilitator.ALL_DICTIONARY_TYPES, it) }
|
||||||
|
mValidSpellingWordReadCache?.put(word, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is unused, so leave it for now (redirecting to isValidWord seems to defeat the purpose...)
|
||||||
|
override fun isValidSuggestionWord(word: String): Boolean {
|
||||||
|
return isValidWord(word, DictionaryFacilitator.ALL_DICTIONARY_TYPES, dictionaryGroups[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: move into dictionaryGroup?
|
||||||
|
private fun isValidWord(word: String, dictionariesToCheck: Array<String>, dictionaryGroup: DictionaryGroup): Boolean {
|
||||||
|
if (word.isEmpty() || dictionaryGroup.isBlacklisted(word)) return false
|
||||||
|
return dictionariesToCheck.any { dictionaryGroup.getDict(it)?.isValidWord(word) == true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBlacklisted(word: String): Boolean = dictionaryGroups.any { it.isBlacklisted(word) }
|
||||||
|
|
||||||
|
override fun removeWord(word: String) {
|
||||||
|
for (dictionaryGroup in dictionaryGroups) {
|
||||||
|
dictionaryGroup.removeWord(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserHistoryDictionary(context: Context) {
|
||||||
|
for (dictionaryGroup in dictionaryGroups) {
|
||||||
|
dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY)?.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun localesAndConfidences(): String? {
|
||||||
|
if (dictionaryGroups.size < 2) return null
|
||||||
|
return dictionaryGroups.joinToString(", ") { "${it.locale} ${it.confidence}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dumpDictionaryForDebug(dictName: String) {
|
||||||
|
val dictToDump = dictionaryGroups[0].getSubDict(dictName)
|
||||||
|
if (dictToDump == null) {
|
||||||
|
Log.e(TAG, ("Cannot dump $dictName. The dictionary is not being used for suggestion or cannot be dumped."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dictToDump.dumpAllWordsForDebug()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDictionaryStats(context: Context): List<DictionaryStats> =
|
||||||
|
DictionaryFacilitator.DYNAMIC_DICTIONARY_TYPES.flatMap { dictType ->
|
||||||
|
dictionaryGroups.mapNotNull { it.getSubDict(dictType)?.dictionaryStats }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dump(context: Context) = getDictionaryStats(context).joinToString("\n")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = DictionaryFacilitatorImpl::class.java.simpleName
|
||||||
|
|
||||||
|
// HACK: This threshold is being used when adding a capitalized entry in the User History dictionary.
|
||||||
|
private const val CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140
|
||||||
|
|
||||||
|
private fun createSubDict(
|
||||||
|
dictType: String, context: Context, locale: Locale, dictFile: File?, dictNamePrefix: String
|
||||||
|
): ExpandableBinaryDictionary? {
|
||||||
|
try {
|
||||||
|
return when (dictType) {
|
||||||
|
Dictionary.TYPE_USER_HISTORY -> UserHistoryDictionary.getDictionary(context, locale, dictFile, dictNamePrefix)
|
||||||
|
Dictionary.TYPE_USER -> UserBinaryDictionary.getDictionary(context, locale, dictFile, dictNamePrefix)
|
||||||
|
Dictionary.TYPE_CONTACTS -> ContactsBinaryDictionary.getDictionary(context, locale, dictFile, dictNamePrefix)
|
||||||
|
Dictionary.TYPE_APPS -> AppsBinaryDictionary.getDictionary(context, locale, dictFile, dictNamePrefix)
|
||||||
|
else -> throw IllegalArgumentException("unknown dictionary type $dictType")
|
||||||
|
}
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.e(TAG, "Cannot create dictionary: $dictType", e)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Log.e(TAG, "Cannot create dictionary: $dictType", e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findDictionaryGroupWithLocale(dictGroups: List<DictionaryGroup>?, locale: Locale): DictionaryGroup? {
|
||||||
|
return dictGroups?.firstOrNull { it.locale == locale }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUsedLocales(mainLocale: Locale, context: Context): Collection<Locale> {
|
||||||
|
val locales = hashSetOf(mainLocale)
|
||||||
|
// adding secondary locales is a bit tricky since they depend on the subtype
|
||||||
|
// but usually this is called with the selected subtype locale
|
||||||
|
val selectedSubtype = SubtypeSettings.getSelectedSubtype(context.prefs())
|
||||||
|
if (selectedSubtype.locale() == mainLocale) {
|
||||||
|
locales.addAll(getSecondaryLocales(selectedSubtype.extraValue))
|
||||||
|
} else {
|
||||||
|
// probably we're called from the spell checker when using a different app as keyboard
|
||||||
|
// so best bet is adding all secondary locales for matching main locale
|
||||||
|
SubtypeSettings.getEnabledSubtypes(false).forEach {
|
||||||
|
if (it.locale() == mainLocale)
|
||||||
|
locales.addAll(getSecondaryLocales(it.extraValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return locales
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A group of dictionaries that work together for a single language. */
|
||||||
|
private class DictionaryGroup(
|
||||||
|
val locale: Locale = Locale(""),
|
||||||
|
private var mainDict: Dictionary? = null,
|
||||||
|
subDicts: Map<String, ExpandableBinaryDictionary> = emptyMap(),
|
||||||
|
context: Context? = null
|
||||||
|
) {
|
||||||
|
private val subDicts: ConcurrentHashMap<String, ExpandableBinaryDictionary> = ConcurrentHashMap(subDicts)
|
||||||
|
|
||||||
|
/** Removes a word from all dictionaries in this group. If the word is in a read-only dictionary, it is blacklisted. */
|
||||||
|
fun removeWord(word: String) {
|
||||||
|
// remove from user history
|
||||||
|
getSubDict(Dictionary.TYPE_USER_HISTORY)?.removeUnigramEntryDynamically(word)
|
||||||
|
|
||||||
|
// and from personal dictionary
|
||||||
|
getSubDict(Dictionary.TYPE_USER)?.removeUnigramEntryDynamically(word)
|
||||||
|
|
||||||
|
val contactsDict = getSubDict(Dictionary.TYPE_CONTACTS)
|
||||||
|
if (contactsDict != null && contactsDict.isInDictionary(word)) {
|
||||||
|
contactsDict.removeUnigramEntryDynamically(word) // will be gone until next reload of dict
|
||||||
|
addToBlacklist(word)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val appsDict = getSubDict(Dictionary.TYPE_APPS)
|
||||||
|
if (appsDict != null && appsDict.isInDictionary(word)) {
|
||||||
|
appsDict.removeUnigramEntryDynamically(word) // will be gone until next reload of dict
|
||||||
|
addToBlacklist(word)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val mainDict = mainDict ?: return
|
||||||
|
if (mainDict.isValidWord(word)) {
|
||||||
|
addToBlacklist(word)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val lowercase = word.lowercase(locale)
|
||||||
|
if (getDict(Dictionary.TYPE_MAIN)!!.isValidWord(lowercase)) {
|
||||||
|
addToBlacklist(lowercase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------- Confidence for multilingual typing -------------------
|
||||||
|
|
||||||
|
// Confidence that the most probable language is actually the language the user is
|
||||||
|
// typing in. For now, this is simply the number of times a word from this language
|
||||||
|
// has been committed in a row, with an exception when typing a single word not contained
|
||||||
|
// in this language.
|
||||||
|
var confidence = 1
|
||||||
|
|
||||||
|
// allow to go above max confidence, for better determination of currently preferred language
|
||||||
|
// when decreasing confidence or getting weight factor, limit to maximum
|
||||||
|
fun increaseConfidence() {
|
||||||
|
confidence += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// If confidence is above max, drop to max confidence. This does not change weights and
|
||||||
|
// allows conveniently typing single words from the other language without affecting suggestions
|
||||||
|
fun decreaseConfidence() {
|
||||||
|
if (confidence > MAX_CONFIDENCE) confidence = MAX_CONFIDENCE
|
||||||
|
else if (confidence > 0) {
|
||||||
|
confidence -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWeightForLocale(groups: List<DictionaryGroup>, isGesturing: Boolean) =
|
||||||
|
getWeightForLocale(groups, if (isGesturing) 0.05f else 0.15f)
|
||||||
|
|
||||||
|
// might need some more tuning
|
||||||
|
fun getWeightForLocale(groups: List<DictionaryGroup>, step: Float): Float {
|
||||||
|
if (groups.size == 1) return 1f
|
||||||
|
if (confidence < 2) return 1f - step * (MAX_CONFIDENCE - confidence)
|
||||||
|
for (group in groups) {
|
||||||
|
if (group !== this && group.confidence >= confidence) return 1f - step / 2f
|
||||||
|
}
|
||||||
|
return 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------- Blacklist -------------------
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
// words cannot be (permanently) removed from some dictionaries, so we use a blacklist for "removing" words
|
||||||
|
private val blacklistFile = if (context?.filesDir == null) null
|
||||||
|
else {
|
||||||
|
val file = File(context.filesDir.absolutePath + File.separator + "blacklists" + File.separator + locale.toLanguageTag() + ".txt")
|
||||||
|
if (file.isDirectory) file.delete() // this apparently was an issue in some versions
|
||||||
|
if (file.parentFile?.exists() == true || file.parentFile?.mkdirs() == true) file
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private val blacklist = hashSetOf<String>().apply {
|
||||||
|
if (blacklistFile?.isFile != true) return@apply
|
||||||
|
scope.launch {
|
||||||
|
synchronized(this) {
|
||||||
|
try {
|
||||||
|
addAll(blacklistFile.readLines())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Exception while trying to read blacklist from ${blacklistFile.name}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isBlacklisted(word: String) = blacklist.contains(word)
|
||||||
|
|
||||||
|
fun addToBlacklist(word: String) {
|
||||||
|
if (!blacklist.add(word) || blacklistFile == null) return
|
||||||
|
scope.launch {
|
||||||
|
synchronized(this) {
|
||||||
|
try {
|
||||||
|
if (blacklistFile.isDirectory) blacklistFile.delete()
|
||||||
|
blacklistFile.appendText("$word\n")
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Exception while trying to add word \"$word\" to blacklist ${blacklistFile.name}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFromBlacklist(word: String) {
|
||||||
|
if (!blacklist.remove(word) || blacklistFile == null) return
|
||||||
|
scope.launch {
|
||||||
|
synchronized(this) {
|
||||||
|
try {
|
||||||
|
val newLines = blacklistFile.readLines().filterNot { it == word }
|
||||||
|
blacklistFile.writeText(newLines.joinToString("\n"))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Exception while trying to remove word \"$word\" to blacklist ${blacklistFile.name}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------- Dictionary handling -------------------
|
||||||
|
|
||||||
|
fun setMainDict(newMainDict: Dictionary?) {
|
||||||
|
// Close old dictionary if exists. Main dictionary can be assigned multiple times.
|
||||||
|
val oldDict = mainDict
|
||||||
|
mainDict = newMainDict
|
||||||
|
if (oldDict != null && newMainDict !== oldDict)
|
||||||
|
oldDict.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDict(dictType: String): Dictionary? {
|
||||||
|
if (dictType == Dictionary.TYPE_MAIN) {
|
||||||
|
return mainDict
|
||||||
|
}
|
||||||
|
return getSubDict(dictType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSubDict(dictType: String): ExpandableBinaryDictionary? {
|
||||||
|
return subDicts[dictType]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasDict(dictType: String): Boolean {
|
||||||
|
if (dictType == Dictionary.TYPE_MAIN) {
|
||||||
|
return mainDict != null
|
||||||
|
}
|
||||||
|
return subDicts.containsKey(dictType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeDict(dictType: String) {
|
||||||
|
val dict = if (Dictionary.TYPE_MAIN == dictType) {
|
||||||
|
mainDict
|
||||||
|
} else {
|
||||||
|
subDicts.remove(dictType)
|
||||||
|
}
|
||||||
|
dict?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = DictionaryGroup::class.java.simpleName
|
||||||
|
const val MAX_CONFIDENCE = 2
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ public class DictionaryFacilitatorLruCache {
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
private final DictionaryFacilitator mDictionaryFacilitator;
|
private final DictionaryFacilitator mDictionaryFacilitator;
|
||||||
private boolean mUseContactsDictionary;
|
private boolean mUseContactsDictionary;
|
||||||
|
private boolean mUseAppsDictionary;
|
||||||
private Locale mLocale;
|
private Locale mLocale;
|
||||||
|
|
||||||
public DictionaryFacilitatorLruCache(final Context context, final String dictionaryNamePrefix) {
|
public DictionaryFacilitatorLruCache(final Context context, final String dictionaryNamePrefix) {
|
||||||
|
@ -58,10 +59,8 @@ public class DictionaryFacilitatorLruCache {
|
||||||
// Nothing to do if the locale is null. This would be the case before any get() calls.
|
// Nothing to do if the locale is null. This would be the case before any get() calls.
|
||||||
if (mLocale != null) {
|
if (mLocale != null) {
|
||||||
// Note: Given that personalized dictionaries are not used here; we can pass null account.
|
// Note: Given that personalized dictionaries are not used here; we can pass null account.
|
||||||
mDictionaryFacilitator.resetDictionaries(mContext, mLocale,
|
mDictionaryFacilitator.resetDictionaries(mContext, mLocale, mUseContactsDictionary,
|
||||||
mUseContactsDictionary, false /* usePersonalizedDicts */,
|
mUseAppsDictionary, false, false, mDictionaryNamePrefix, null);
|
||||||
false /* forceReloadMainDictionary */, null /* account */,
|
|
||||||
mDictionaryNamePrefix, null /* listener */);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +76,18 @@ public class DictionaryFacilitatorLruCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUseAppsDictionary(final boolean useAppsDictionary) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mUseAppsDictionary == useAppsDictionary) {
|
||||||
|
// The value has not been changed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mUseAppsDictionary = useAppsDictionary;
|
||||||
|
resetDictionariesForLocaleLocked();
|
||||||
|
waitForLoadingMainDictionary(mDictionaryFacilitator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DictionaryFacilitator get(final Locale locale) {
|
public DictionaryFacilitator get(final Locale locale) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (!mDictionaryFacilitator.isForLocale(locale)) {
|
if (!mDictionaryFacilitator.isForLocale(locale)) {
|
||||||
|
|
|
@ -6,88 +6,103 @@
|
||||||
package helium314.keyboard.latin
|
package helium314.keyboard.latin
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import helium314.keyboard.latin.common.FileUtils
|
|
||||||
import helium314.keyboard.latin.common.LocaleUtils
|
import helium314.keyboard.latin.common.LocaleUtils
|
||||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
|
||||||
import helium314.keyboard.latin.utils.DictionaryInfoUtils
|
import helium314.keyboard.latin.utils.DictionaryInfoUtils
|
||||||
import helium314.keyboard.latin.utils.Log
|
import helium314.keyboard.latin.utils.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
object DictionaryFactory {
|
||||||
* Initializes a main dictionary collection from a dictionary pack, with explicit flags.
|
/**
|
||||||
*
|
* Initializes a main dictionary collection for a locale.
|
||||||
*
|
* Uses all dictionaries in cache folder for locale, and adds built-in
|
||||||
* This searches for a content provider providing a dictionary pack for the specified
|
* dictionaries of matching locales if type is not already in cache folder.
|
||||||
* locale. If none is found, it falls back to the built-in dictionary - if any.
|
*
|
||||||
* @param context application context for reading resources
|
* @return an initialized instance of DictionaryCollection
|
||||||
* @param locale the locale for which to create the dictionary
|
*/
|
||||||
* @return an initialized instance of DictionaryCollection
|
// todo:
|
||||||
*/
|
// expose the weight so users can adjust dictionary "importance" (useful for addons like emoji dict)
|
||||||
fun createMainDictionary(context: Context, locale: Locale): DictionaryCollection {
|
// allow users to block certain dictionaries (not sure how this should work exactly)
|
||||||
val cacheDir = DictionaryInfoUtils.getAndCreateCacheDirectoryForLocale(locale, context)
|
fun createMainDictionaryCollection(context: Context, locale: Locale, useEmojiDict: Boolean): DictionaryCollection {
|
||||||
val dictList = LinkedList<Dictionary>()
|
val dictList = LinkedList<Dictionary>()
|
||||||
// get cached dict files
|
val (extracted, nonExtracted) = getAvailableDictsForLocale(locale, context, useEmojiDict)
|
||||||
val (userDicts, extractedDicts) = DictionaryInfoUtils.getCachedDictsForLocale(locale, context)
|
extracted.sortedBy { !it.name.endsWith(DictionaryInfoUtils.USER_DICTIONARY_SUFFIX) }.forEach {
|
||||||
.partition { it.name.endsWith(DictionaryInfoUtils.USER_DICTIONARY_SUFFIX) }
|
// we sort to have user dicts first, so they have priority over internal dicts of the same type
|
||||||
// add user dicts to list
|
checkAndAddDictionaryToListIfNewType(it, dictList, locale)
|
||||||
userDicts.forEach { checkAndAddDictionaryToListIfNotExisting(it, dictList, locale) }
|
}
|
||||||
// add extracted dicts to list (after userDicts, to skip extracted dicts of same type)
|
nonExtracted.forEach { filename ->
|
||||||
extractedDicts.forEach { checkAndAddDictionaryToListIfNotExisting(it, dictList, locale) }
|
val type = filename.substringBefore("_")
|
||||||
if (dictList.any { it.mDictType == Dictionary.TYPE_MAIN })
|
if (dictList.any { it.mDictType == type }) return@forEach
|
||||||
return DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList)
|
val extractedFile = DictionaryInfoUtils.extractAssetsDictionary(filename, locale, context) ?: return@forEach
|
||||||
|
checkAndAddDictionaryToListIfNewType(extractedFile, dictList, locale)
|
||||||
// no main dict found -> check assets
|
}
|
||||||
val assetsDicts = DictionaryInfoUtils.getAssetsDictionaryList(context)
|
return DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList, FloatArray(dictList.size) { 1f })
|
||||||
// file name is <type>_<language tag>.dict
|
}
|
||||||
val dictsByType = assetsDicts?.groupBy { it.substringBefore("_") }
|
|
||||||
// for each type find the best match
|
fun getAvailableDictsForLocale(locale: Locale, context: Context, useEmojiDict: Boolean): Pair<Array<out File>, List<String>> {
|
||||||
dictsByType?.forEach { (dictType, dicts) ->
|
var cachedDicts = DictionaryInfoUtils.getCachedDictsForLocale(locale, context)
|
||||||
val bestMatch = LocaleUtils.getBestMatch(locale, dicts) { it.substringAfter("_")
|
if (!useEmojiDict) cachedDicts = cachedDicts.filter { it.name.substringBefore("_") != Dictionary.TYPE_EMOJI }.toTypedArray()
|
||||||
.substringBefore(".").constructLocale() } ?: return@forEach
|
|
||||||
// extract dict and add extracted file
|
val nonExtractedDicts = mutableListOf<String>()
|
||||||
val targetFile = File(cacheDir, "$dictType.dict")
|
DictionaryInfoUtils.getAssetsDictionaryList(context)
|
||||||
FileUtils.copyStreamToNewFile(
|
// file name is <type>_<language tag>.dict
|
||||||
context.assets.open(DictionaryInfoUtils.ASSETS_DICTIONARY_FOLDER + File.separator + bestMatch),
|
?.groupBy { it.substringBefore("_") }
|
||||||
targetFile
|
?.forEach { (dictType, dicts) ->
|
||||||
)
|
if (cachedDicts.any { it.name == "$dictType.dict" })
|
||||||
checkAndAddDictionaryToListIfNotExisting(targetFile, dictList, locale)
|
return@forEach // dictionary is already extracted (can't be old because of cleanup on upgrade)
|
||||||
}
|
val bestMatch = LocaleUtils.getBestMatch(locale, dicts) {
|
||||||
// If the list is empty, that means we should not use any dictionary (for example, the user
|
DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(it)
|
||||||
// explicitly disabled the main dictionary), so the following is okay. dictList is never
|
} ?: return@forEach
|
||||||
// null, but if for some reason it is, DictionaryCollection handles it gracefully.
|
nonExtractedDicts.add(bestMatch)
|
||||||
return DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList)
|
}
|
||||||
}
|
return cachedDicts to nonExtractedDicts
|
||||||
|
}
|
||||||
/**
|
|
||||||
* add dictionary created from [file] to [dicts]
|
/**
|
||||||
* if [file] cannot be loaded it is deleted
|
* add dictionary created from [file] to [dicts]
|
||||||
* if the dictionary type already exists in [dicts], the [file] is skipped
|
* if [file] cannot be loaded it is deleted
|
||||||
*/
|
* if the dictionary type already exists in [dicts], the [file] is skipped
|
||||||
private fun checkAndAddDictionaryToListIfNotExisting(file: File, dicts: MutableList<Dictionary>, locale: Locale) {
|
*/
|
||||||
if (!file.isFile) return
|
private fun checkAndAddDictionaryToListIfNewType(file: File, dicts: MutableList<Dictionary>, locale: Locale) {
|
||||||
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) ?: return killDictionary(file)
|
val dictionary = getDictionary(file, locale) ?: return
|
||||||
val dictType = header.mIdString.split(":").first()
|
if (dicts.any { it.mDictType == dictionary.mDictType }) {
|
||||||
if (dicts.any { it.mDictType == dictType }) return
|
dictionary.close()
|
||||||
val readOnlyBinaryDictionary = ReadOnlyBinaryDictionary(
|
return
|
||||||
file.absolutePath, 0, file.length(), false, locale, dictType
|
}
|
||||||
)
|
dicts.add(dictionary)
|
||||||
|
}
|
||||||
if (readOnlyBinaryDictionary.isValidDictionary) {
|
|
||||||
if (locale.language == "ko") {
|
@JvmStatic
|
||||||
// Use KoreanDictionary for Korean locale
|
fun getDictionary(
|
||||||
dicts.add(KoreanDictionary(readOnlyBinaryDictionary))
|
file: File,
|
||||||
} else {
|
locale: Locale
|
||||||
dicts.add(readOnlyBinaryDictionary)
|
): Dictionary? {
|
||||||
|
if (!file.isFile) return null
|
||||||
|
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file)
|
||||||
|
if (header == null) {
|
||||||
|
killDictionary(file)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val dictType = header.mIdString.split(":").first()
|
||||||
|
val readOnlyBinaryDictionary = ReadOnlyBinaryDictionary(
|
||||||
|
file.absolutePath, 0, file.length(), false, locale, dictType
|
||||||
|
)
|
||||||
|
|
||||||
|
if (readOnlyBinaryDictionary.isValidDictionary) {
|
||||||
|
if (locale.language == "ko") {
|
||||||
|
// Use KoreanDictionary for Korean locale
|
||||||
|
return KoreanDictionary(readOnlyBinaryDictionary)
|
||||||
|
}
|
||||||
|
return readOnlyBinaryDictionary
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
readOnlyBinaryDictionary.close()
|
readOnlyBinaryDictionary.close()
|
||||||
killDictionary(file)
|
killDictionary(file)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun killDictionary(file: File) {
|
||||||
|
Log.e("DictionaryFactory", "could not load dictionary ${file.parentFile?.name}/${file.name}, deleting")
|
||||||
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun killDictionary(file: File) {
|
|
||||||
Log.e("DictionaryFactory", "could not load dictionary ${file.parentFile?.name}/${file.name}, deleting")
|
|
||||||
file.delete()
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A class for detecting Emoji-Alt physical key.
|
* A class for detecting Emoji-Alt physical key.
|
||||||
*/
|
*/
|
||||||
final class EmojiAltPhysicalKeyDetector {
|
public final class EmojiAltPhysicalKeyDetector {
|
||||||
private static final String TAG = "EmojiAltPhysKeyDetector";
|
private static final String TAG = "EmojiAltPhysKeyDetector";
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue