Compare commits
190 Commits
v0.3.16-be
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26650d2a00 | ||
|
|
4395eac500 | ||
|
|
90e60a5e03 | ||
|
|
e647e0d248 | ||
|
|
31c046720a | ||
|
|
03b70b43a6 | ||
|
|
de2b3b9433 | ||
|
|
a51f671c3c | ||
|
|
314cdf79bf | ||
|
|
d7137b41fe | ||
|
|
acad9f66a6 | ||
|
|
8d0565854c | ||
|
|
6932fecbbd | ||
|
|
3e6ed3d7b0 | ||
|
|
85e76892b7 | ||
|
|
65cbc4bea3 | ||
|
|
79d177144a | ||
|
|
8cb2b0bfa7 | ||
|
|
201de6a6db | ||
|
|
f65b11bc6d | ||
|
|
86031bb428 | ||
|
|
58f62e1bd5 | ||
|
|
6212e35382 | ||
|
|
c8d0c6269f | ||
|
|
e6f40932ed | ||
|
|
f8af02c400 | ||
|
|
932a7c3126 | ||
|
|
f0c2ac566f | ||
|
|
19224e5f18 | ||
|
|
6c325af80e | ||
|
|
bb82b78cb7 | ||
|
|
eb30eed735 | ||
|
|
3198977143 | ||
|
|
f3b3c21aaa | ||
|
|
0606afbb64 | ||
|
|
5362df02a5 | ||
|
|
3d15bd7f46 | ||
|
|
15e94ecf2c | ||
|
|
ebb3873fe4 | ||
|
|
c1231cd964 | ||
|
|
53ab0a3fa0 | ||
|
|
aeeff67d2e | ||
|
|
4343703eb3 | ||
|
|
5f09bdbce2 | ||
|
|
3a3e3625f2 | ||
|
|
bdf14c1997 | ||
|
|
d6e064ae00 | ||
|
|
1012094568 | ||
|
|
4117537ff2 | ||
|
|
2a72cb70d6 | ||
|
|
b1cd9d9389 | ||
|
|
568dfc973d | ||
|
|
2e74cec0db | ||
|
|
db378159d6 | ||
|
|
50ff2d8f1b | ||
|
|
061495fb27 | ||
|
|
40cb59ddfd | ||
|
|
38affddc9e | ||
|
|
17dcb90473 | ||
|
|
772402b46f | ||
|
|
29bd8a289c | ||
|
|
79d9e73608 | ||
|
|
b04f8d75f3 | ||
|
|
32b1d123d2 | ||
|
|
b576cafaa4 | ||
|
|
312ef93ffc | ||
|
|
a1dda0c247 | ||
|
|
0c36b96922 | ||
|
|
07e92f052b | ||
|
|
244c834de9 | ||
|
|
61b5c2cffd | ||
|
|
1441bd63cb | ||
|
|
e7d0db0fc0 | ||
|
|
a87d340b25 | ||
|
|
3f0d90cb7c | ||
|
|
b9e9f9b122 | ||
|
|
f2d1cf3baf | ||
|
|
cf1112327a | ||
|
|
c2cb28668d | ||
|
|
75f4fcb91a | ||
|
|
3d92bd0584 | ||
|
|
52ca98a14d | ||
|
|
629a73a5cf | ||
|
|
077ec43855 | ||
|
|
3ecd3618cb | ||
|
|
38bc34913b | ||
|
|
c733e5ceea | ||
|
|
e2536ceb92 | ||
|
|
c17b6f073d | ||
|
|
936b177776 | ||
|
|
7d8036fe69 | ||
|
|
6d08d1a265 | ||
|
|
48aba1c055 | ||
|
|
0b3d3317bf | ||
|
|
d1fbdc581b | ||
|
|
044170eb4b | ||
|
|
a7c16b3ceb | ||
|
|
dd12be2275 | ||
|
|
1049bc543a | ||
|
|
5c5ad3cd32 | ||
|
|
6fce521122 | ||
|
|
2af9941ea6 | ||
|
|
9b24f742d1 | ||
|
|
b36bcf7733 | ||
|
|
9559dbdcd6 | ||
|
|
668dd4b5bf | ||
|
|
b98feab9c4 | ||
|
|
5f4711ac3e | ||
|
|
4a931cbdc0 | ||
|
|
90b2ddbdf2 | ||
|
|
3d6cacc753 | ||
|
|
3aed315dfc | ||
|
|
06233cca8c | ||
|
|
3da0ab260a | ||
|
|
5d47159151 | ||
|
|
7e92a7382e | ||
|
|
e69dcd87df | ||
|
|
fb9589f642 | ||
|
|
b034075437 | ||
|
|
cd10906811 | ||
|
|
a1e0bd9a0e | ||
|
|
2dd70db4c2 | ||
|
|
fc835cd1ab | ||
|
|
5d88058354 | ||
|
|
cda111f623 | ||
|
|
fc5ea6204d | ||
|
|
70ba72bf8b | ||
|
|
c9f84a5a32 | ||
|
|
10f53d1bd9 | ||
|
|
7624aac935 | ||
|
|
06ffb89198 | ||
|
|
d48ce5ad94 | ||
|
|
5c1dd57802 | ||
|
|
1f4133676a | ||
|
|
f83d40d137 | ||
|
|
0999cb7b86 | ||
|
|
c58d6311e1 | ||
|
|
8c9016d7e3 | ||
|
|
072d768ef6 | ||
|
|
7eabe77358 | ||
|
|
e4df74bbae | ||
|
|
80f7d50770 | ||
|
|
31d901ba1c | ||
|
|
4e2a43a6cd | ||
|
|
0408d437e1 | ||
|
|
5a6c36d421 | ||
|
|
5fc07f9ae3 | ||
|
|
91cbe6d8ec | ||
|
|
c2aa87beab | ||
|
|
0094699a88 | ||
|
|
4adaf9a315 | ||
|
|
01eee827df | ||
|
|
fd87241887 | ||
|
|
d63792cb15 | ||
|
|
e2c4992b51 | ||
|
|
481d929f2b | ||
|
|
3d1f1d1e12 | ||
|
|
9f4ef7e1ad | ||
|
|
7d24f0c5ae | ||
|
|
67408130b1 | ||
|
|
cdb9504e5f | ||
|
|
977b32de6e | ||
|
|
4ae4eb00e9 | ||
|
|
063fca6dd1 | ||
|
|
a650bfe94c | ||
|
|
517448da06 | ||
|
|
8ba9abdace | ||
|
|
193d03e0eb | ||
|
|
363cfe1443 | ||
|
|
d9cd36966e | ||
|
|
02bfae55b9 | ||
|
|
6edbf64104 | ||
|
|
1c29319036 | ||
|
|
8c8664cafa | ||
|
|
652c7f4e4b | ||
|
|
753fbc30df | ||
|
|
01de9a4ae1 | ||
|
|
aa52784174 | ||
|
|
dc72e2162e | ||
|
|
8ef37cedfb | ||
|
|
08a77ce0eb | ||
|
|
afc6f21a6a | ||
|
|
bdc2a9a5d3 | ||
|
|
c27365bccd | ||
|
|
83d4fc727d | ||
|
|
5bf4819d83 | ||
|
|
60221743f6 | ||
|
|
9d78661ecb | ||
|
|
addedb6f9b | ||
|
|
e1701b2ba7 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
||||
github: [patrickgold]
|
||||
liberapay: patrickgold
|
||||
custom: ["https://paypal.me/devpatrickgold"]
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,6 +43,3 @@ crowdin.properties
|
||||
|
||||
# C++
|
||||
.cxx/
|
||||
|
||||
# AndroidX Room schema JSONs
|
||||
/app/schemas/
|
||||
|
||||
@@ -54,7 +54,8 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme and enter the public beta on Google Play.
|
||||
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme.
|
||||
Beginning with v0.5.0 FlorisBoard will enter the public beta on Google Play.
|
||||
|
||||
## Highlighted features
|
||||
- Integrated clipboard manager / history
|
||||
@@ -84,6 +85,8 @@ to get more information on this topic.
|
||||
[Android Jetpack](https://github.com/androidx)
|
||||
* [Accompanist Compose UI libraries](https://github.com/google/accompanist/) by
|
||||
[Google](https://github.com/google)
|
||||
* [AboutLibraries](https://github.com/mikepenz/AboutLibraries) by
|
||||
[mikepenz](https://github.com/mikepenz)
|
||||
* [Google Material icons](https://github.com/google/material-design-icons) by
|
||||
[Google](https://github.com/google)
|
||||
* [JetPref preference library](https://github.com/patrickgold/jetpref) by
|
||||
|
||||
101
ROADMAP.md
101
ROADMAP.md
@@ -1,4 +1,3 @@
|
||||
|
||||
# FlorisBoard's feature roadmap & milestones
|
||||
|
||||
This feature roadmap intents to provide transparency to what I want to add to FlorisBoard in the foreseeable future.
|
||||
@@ -9,35 +8,83 @@ out a bit on the stable track. If you are interested in following the developmen
|
||||
along the beta track releases! These are generally more unstable but you get new stuff faster and can provide early
|
||||
feedback, which helps a lot!
|
||||
|
||||
## 0.3.x
|
||||
## 0.4
|
||||
|
||||
Releases in this section still follow the old versioning scheme, meaning the patch number is a feature upgrade. As this
|
||||
naming convention is more confusing than useful, beginning with v0.4.0 development a new release/development cycle will
|
||||
be introduced.
|
||||
Major release which mainly focuses on adding proper word suggestions and inline autocorrect (for Latin-based languages
|
||||
only at first). This is a big effort which will take some time to be fully completed. Additionally general small bug
|
||||
fixes and improvements will be made alongside the development of the main objective.
|
||||
|
||||
### 0.3.15 & 0.3.16 (currently 0.3.15 done, 0.3.16 in work)
|
||||
With this release the versioning scheme changes to `0.x.y`, where `x` specifies the major changes, and `y` are just
|
||||
small bug fixes and improvements for the former major stable release `x`. This is different to `0.3.x`, where the
|
||||
version scheme just did not make any sense anymore, especially with the latest `0.3.x` releases. As for the beta track,
|
||||
major developments (`0.x`) will have alpha, beta and release candidate releases on the beta track before it goes live on
|
||||
the stable track. Small follow-up bug fixes (`0.x.y`) will be published on both the stable and beta track without
|
||||
release candidates.
|
||||
|
||||
- Hotfix releases for possible bugs in the preference rework (in work)
|
||||
- Lots and lots of bug fixing in general (in work)
|
||||
- Preparation work for 0.4.0, fixing text state logic and use break iterator (done)
|
||||
- Reducing or getting rid of input lag some devices experience (done)
|
||||
- Clean up of project structure for better future development (done)
|
||||
### Word suggestions / Autocorrect
|
||||
|
||||
## 0.4.0
|
||||
The development effort of this feature is quite big, thus it is split into multiple phases:
|
||||
|
||||
- Re-adding word suggestions (at least for Latin-based languages at first)
|
||||
- Importing the dictionaries as well as management relies on the Flex extension core and UI in Kotlin
|
||||
- Actually parsing and generating suggestions happens in C++ to avoid another OOM catastrophe like in 0.3.9/10
|
||||
- The actual format of the dictionary and word list source is not decided yet
|
||||
- Community repository on GitHub for theme sharing across users (may be 0.5.0)
|
||||
**Phase 1: Preparations of suggestions UI & interfacing API (first alpha release(s))**
|
||||
|
||||
With this release the versioning scheme changes: the second number now indicates new features, changes in the third "
|
||||
patch" number now indicates bug fixes and minor feature additions for the stable track. The development cycle for each
|
||||
0.x release will have `-alphaXX` (optional and only for large releases), `-betaXX` and `-rcXX` (release candidate)
|
||||
releases on the beta track for interested people to follow along the development. The first release to follow the new
|
||||
scheme will be `0.4.0-alpha01` on the beta track.
|
||||
- Rework Smartbar suggestions UI
|
||||
- Allow for primary and optionally secondary label (in a smaller font) to be shown per suggestion
|
||||
- Better integrate clipboard suggestions into word suggestion flow
|
||||
- Add long-press suggestion action for user to prevent from showing again
|
||||
- Generally fix and polish suggestions UI design (3-column mode and scrollable mode)
|
||||
- Add a `SuggestionProvider` interface API to allow for any specialized implementation to be written
|
||||
- A provider's main task is to receive updates on the current state of the editor (except for raw inputs) and
|
||||
provide both current word autocorrect/suggestions or next word suggestions if there is no current word
|
||||
- The provider can utilize the basic APK asset and file APIs for reading dictionary files, however there is no
|
||||
standardization in parsing as different languages may require different dictionary structures and thus have
|
||||
different requirements
|
||||
- Document API and add dummy implementation to test API
|
||||
- Try to add toggle for not underlining the current word (composing region) while not loosing the caching benefits
|
||||
- In parallel: Do local research and preps for phase 2
|
||||
|
||||
## 0.5.0
|
||||
_[Anomaly](https://www.anomaly.ltd/), an Australian software company, will sponsor this project with 1000€ so this phase
|
||||
gets implemented first, as they want to use FlorisBoard as a base for
|
||||
their [WCC Language Program project (Gurray)](https://www.anomaly.ltd/portfolio/wcc/gurray/). As this fits in perfectly
|
||||
with the current dev cycle and this had to be done anyways (some parts like documenting and UI polishing just later in
|
||||
the 0.4 milestone), I have accepted this. However in general this does not mean this project accepts sponsoring for any
|
||||
feature to be prioritized, as the project's main goals and planned feature timeline must always come first and human dev
|
||||
resources are limited._
|
||||
|
||||
**Phase 2: Add native (C++) Latin word suggestion core (alpha releases)**
|
||||
|
||||
- Research and experiment with different approaches/data sources for Latin-based language prediction and autocorrect
|
||||
- Research will mainly be done first locally on Linux to decide what to use
|
||||
- Implementation will be in C++ using STL libraries and if needed other open-source libraries, with compatibility
|
||||
and CPU/memory restrictions on Android devices in mind
|
||||
- Once an experiment runs well locally it will be included in the main project and tested out within the keyboard UI
|
||||
in different alpha releases
|
||||
- Especially at the beginning an idea may be scrapped and replaced by something else if found that another approach
|
||||
is better
|
||||
- (Based on research) Introduce new dictionary/language model format
|
||||
- Importing the dictionaries/models as well as management relies on the Flex extension core and UI in Kotlin
|
||||
- Actually parsing and generating suggestions happens in C++
|
||||
- The actual format of the dictionary/model source is not decided yet
|
||||
- Add system in preprocessing stage to properly mark slightly offensive words and prevent extremely offensive words
|
||||
from being included at all
|
||||
- Add system in preprocessing stage to filter out email addresses and phone numbers that may be included in the
|
||||
large datasets which are used for building the models
|
||||
|
||||
**Phase 3: Add support for more languages & Allow glide typing to utilize new word prediction system (beta releases)**
|
||||
|
||||
- Glide typing: Utilize new prediction system and get rid of current English (US) json dictionary
|
||||
- Add support for more languages (Latin-based), may need to utilize datasets like Opensubtitles or Wikimedia, although
|
||||
those need extensive cleaning and are not as reliable
|
||||
- Focus on improving performance and stabilizing the Latin suggestion core
|
||||
- Possibly address some language-specific issues and ensure suggested word capitalization is correct
|
||||
- Finalize Settings and keyboard UI regarding word suggestions.
|
||||
|
||||
### Other planned features for 0.4
|
||||
|
||||
- General small fixes and improvements
|
||||
- Community repository on GitHub for extension sharing across users (may be 0.5.0 though)
|
||||
- Localized emoji suggestions (may be 0.5.0 though)
|
||||
|
||||
## 0.5
|
||||
|
||||
- Complete rework of the Emoji panel
|
||||
- Recently used / Emoji history (already implemented with 0.3.14)
|
||||
@@ -51,18 +98,16 @@ scheme will be `0.4.0-alpha01` on the beta track.
|
||||
- Rework branding images and texts of FlorisBoard for the app stores
|
||||
- Focus on stability and experience improvements of the app and keyboard
|
||||
|
||||
## 0.6.0
|
||||
## Backlog
|
||||
|
||||
**Features that MAY be added (even in versions mentioned above) or dismissed altogether**
|
||||
|
||||
- Full on-board layout editor which allows users to create their own layouts without writing a JSON file
|
||||
- Import/Export of custom layout files packed in Flex extensions
|
||||
|
||||
## Backlog / Features that MAY be added, even in versions not mentioned above if the feature implementation fits perfectly with another feature
|
||||
|
||||
- Theme rework part II
|
||||
- Adaptive themes v2
|
||||
- Voice-to-text with Mozilla's open-source voice service
|
||||
- Text translation
|
||||
- Glide typing better word detection
|
||||
- Proximity-based key typo detection
|
||||
- Floating keyboard
|
||||
- Tablet mode / Optimizations for landscape input
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Suppress needed until https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
|
||||
@file:Suppress("DSL_SCOPE_VIOLATION")
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.google.android.gms.oss-licenses-plugin")
|
||||
id("de.mannodermaus.android-junit5")
|
||||
alias(libs.plugins.agp.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.mannodermaus.android.junit5)
|
||||
alias(libs.plugins.mikepenz.aboutlibraries)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 31
|
||||
namespace = "dev.patrickgold.florisboard"
|
||||
compileSdk = 32
|
||||
buildToolsVersion = "31.0.0"
|
||||
ndkVersion = "22.1.7171670"
|
||||
|
||||
@@ -18,12 +41,11 @@ android {
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xallow-result-return-type",
|
||||
"-Xopt-in=kotlin.RequiresOptIn",
|
||||
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
|
||||
"-Xjvm-default=compatibility",
|
||||
"-opt-in=kotlin.contracts.ExperimentalContracts",
|
||||
"-Xjvm-default=all-compatibility",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,19 +53,17 @@ android {
|
||||
applicationId = "dev.patrickgold.florisboard"
|
||||
minSdk = 24
|
||||
targetSdk = 31
|
||||
versionCode = 81
|
||||
versionName = "0.3.16"
|
||||
versionCode = 89
|
||||
versionName = "0.4.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += mapOf(
|
||||
Pair("room.schemaLocation", "$projectDir/schemas"),
|
||||
Pair("room.incremental", "true"),
|
||||
Pair("room.expandProjection", "true")
|
||||
)
|
||||
}
|
||||
buildConfigField("String", "BUILD_COMMIT_HASH", "\"${getGitCommitHash()}\"")
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
arg("room.incremental", "true")
|
||||
arg("room.expandProjection", "true")
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
@@ -87,7 +107,7 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.1.1"
|
||||
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
@@ -97,9 +117,9 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("debug").configure {
|
||||
named("debug") {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
versionNameSuffix = "-debug-${getGitCommitHash(short = true)}"
|
||||
|
||||
isDebuggable = true
|
||||
isJniDebuggable = false
|
||||
@@ -115,11 +135,13 @@ android {
|
||||
resValue("string", "floris_app_name", "FlorisBoard Debug")
|
||||
}
|
||||
|
||||
create("beta") // Needed because by default the "beta" BuildType does not exist
|
||||
named("beta").configure {
|
||||
create("beta") {
|
||||
applicationIdSuffix = ".beta"
|
||||
versionNameSuffix = "-beta01"
|
||||
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
versionNameSuffix = "-alpha03"
|
||||
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
|
||||
resValue("mipmap", "floris_app_icon_round", "@mipmap/ic_app_icon_beta_round")
|
||||
@@ -127,14 +149,31 @@ android {
|
||||
resValue("string", "floris_app_name", "FlorisBoard Beta")
|
||||
}
|
||||
|
||||
named("release").configure {
|
||||
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
named("release") {
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_stable")
|
||||
resValue("mipmap", "floris_app_icon_round", "@mipmap/ic_app_icon_stable_round")
|
||||
resValue("drawable", "floris_app_icon_foreground", "@drawable/ic_app_icon_stable_foreground")
|
||||
resValue("string", "floris_app_name", "@string/app_name")
|
||||
}
|
||||
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks += listOf("release")
|
||||
|
||||
ndk {
|
||||
// For running FlorisBoard on the emulator
|
||||
abiFilters += listOf("x86", "x86_64")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aboutLibraries {
|
||||
configPath = "app/src/main/config"
|
||||
}
|
||||
|
||||
testOptions {
|
||||
@@ -152,37 +191,57 @@ tasks.withType<Test> {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.activity:activity-compose:1.4.0")
|
||||
implementation("androidx.activity:activity-ktx:1.4.0")
|
||||
implementation("androidx.autofill:autofill:1.1.0")
|
||||
implementation("androidx.collection:collection-ktx:1.2.0")
|
||||
implementation("androidx.compose.material:material:1.1.1")
|
||||
implementation("androidx.compose.runtime:runtime-livedata:1.1.1")
|
||||
implementation("androidx.compose.ui:ui:1.1.1")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview:1.1.1")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.0-beta02")
|
||||
implementation("androidx.emoji2:emoji2:1.1.0")
|
||||
implementation("androidx.emoji2:emoji2-views:1.1.0")
|
||||
implementation("androidx.navigation:navigation-compose:2.4.2")
|
||||
implementation("com.google.accompanist:accompanist-flowlayout:0.23.0")
|
||||
implementation("com.google.accompanist:accompanist-insets:0.23.0")
|
||||
implementation("com.google.accompanist:accompanist-systemuicontroller:0.23.0")
|
||||
implementation("dev.patrickgold.jetpref:jetpref-datastore-model:0.1.0-beta08")
|
||||
implementation("dev.patrickgold.jetpref:jetpref-datastore-ui:0.1.0-beta08")
|
||||
implementation("dev.patrickgold.jetpref:jetpref-material-ui:0.1.0-beta08")
|
||||
implementation("io.github.reactivecircus.cache4k:cache4k:0.5.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
|
||||
implementation("androidx.room:room-runtime:2.4.2")
|
||||
kapt("androidx.room:room-compiler:2.4.2")
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
implementation(libs.accompanist.systemuicontroller)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.activity.ktx)
|
||||
implementation(libs.androidx.autofill)
|
||||
implementation(libs.androidx.collection.ktx)
|
||||
implementation(libs.androidx.compose.material)
|
||||
implementation(libs.androidx.compose.runtime.livedata)
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(libs.androidx.emoji2)
|
||||
implementation(libs.androidx.emoji2.views)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.cache4k)
|
||||
implementation(libs.jetpref.datastore.model)
|
||||
implementation(libs.jetpref.datastore.ui)
|
||||
implementation(libs.jetpref.material.ui)
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.mikepenz.aboutlibraries.core)
|
||||
implementation(libs.mikepenz.aboutlibraries.compose)
|
||||
implementation(libs.patrickgold.compose.tooltip)
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5:5.2.3")
|
||||
testImplementation("io.kotest:kotest-assertions-core:5.2.3")
|
||||
testImplementation("io.kotest:kotest-property:5.2.3")
|
||||
testImplementation("io.kotest.extensions:kotest-extensions-robolectric:0.5.0")
|
||||
testImplementation("nl.jqno.equalsverifier:equalsverifier:3.10")
|
||||
testImplementation(libs.equalsverifier)
|
||||
testImplementation(libs.kotest.assertions.core)
|
||||
testImplementation(libs.kotest.extensions.roboelectric)
|
||||
testImplementation(libs.kotest.property)
|
||||
testImplementation(libs.kotest.runner.junit5)
|
||||
|
||||
androidTestImplementation("androidx.test.ext", "junit", "1.1.2")
|
||||
androidTestImplementation("androidx.test.espresso", "espresso-core", "3.3.0")
|
||||
androidTestImplementation(libs.androidx.test.ext)
|
||||
androidTestImplementation(libs.androidx.test.espresso.core)
|
||||
}
|
||||
|
||||
fun getGitCommitHash(short: Boolean = false): String {
|
||||
if (!File(".git").exists()) {
|
||||
return "null"
|
||||
}
|
||||
|
||||
val stdout = ByteArrayOutputStream()
|
||||
exec {
|
||||
if (short) {
|
||||
commandLine("git", "rev-parse", "--short", "HEAD")
|
||||
} else {
|
||||
commandLine("git", "rev-parse", "HEAD")
|
||||
}
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().trim()
|
||||
}
|
||||
|
||||
29
app/proguard-rules.pro
vendored
Normal file
29
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Disable obfuscation (we use Proguard exclusively for optimization)
|
||||
-dontobfuscate
|
||||
|
||||
# Keep `Companion` object fields of serializable classes.
|
||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||
-if @kotlinx.serialization.Serializable class **
|
||||
-keepclassmembers class <1> {
|
||||
static <1>$Companion Companion;
|
||||
}
|
||||
|
||||
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
static **$* *;
|
||||
}
|
||||
-keepclassmembers class <2>$<3> {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Keep `INSTANCE.serializer()` of serializable objects.
|
||||
-if @kotlinx.serialization.Serializable class ** {
|
||||
public static ** INSTANCE;
|
||||
}
|
||||
-keepclassmembers class <1> {
|
||||
public static <1> INSTANCE;
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "d573e2ae2cbe7026957bc8d8fda291f3",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "clipboard_files",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, `_display_name` TEXT NOT NULL, `_size` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, PRIMARY KEY(`_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "_display_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "_size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mimeTypes",
|
||||
"columnName": "mimeTypes",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_clipboard_files__id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_files__id` ON `${TABLE_NAME}` (`_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd573e2ae2cbe7026957bc8d8fda291f3')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "6fbec9d7ea017f8aefac4fb84dbd5189",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "clipboard_history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uri",
|
||||
"columnName": "uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "creationTimestampMs",
|
||||
"columnName": "creationTimestampMs",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPinned",
|
||||
"columnName": "isPinned",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mimeTypes",
|
||||
"columnName": "mimeTypes",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_clipboard_history__id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6fbec9d7ea017f8aefac4fb84dbd5189')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "787af4a2df15bf9d2c0597519d3fb273",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "words",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `word` TEXT NOT NULL, `frequency` INTEGER NOT NULL, `locale` TEXT, `shortcut` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "word",
|
||||
"columnName": "word",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "freq",
|
||||
"columnName": "frequency",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "locale",
|
||||
"columnName": "locale",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "shortcut",
|
||||
"columnName": "shortcut",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_words__id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_words__id` ON `${TABLE_NAME}` (`_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '787af4a2df15bf9d2c0597519d3fb273')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,8 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="dev.patrickgold.florisboard">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Permission needed to vibrate if the user has key press vibration enabled -->
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
@@ -46,14 +44,16 @@
|
||||
android:theme="@style/FlorisAppTheme"
|
||||
tools:targetApi="s">
|
||||
|
||||
<!-- Allow app to be profiled for benchmarking and baseline profile generation -->
|
||||
<profileable android:shell="true"/>
|
||||
|
||||
<!-- IME service -->
|
||||
<service
|
||||
android:name="dev.patrickgold.florisboard.FlorisImeService"
|
||||
android:label="@string/floris_app_name"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD"
|
||||
android:directBootAware="true"
|
||||
android:exported="true"
|
||||
tools:targetApi="n">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod"/>
|
||||
</intent-filter>
|
||||
@@ -80,7 +80,7 @@
|
||||
android:launchMode="singleTask"
|
||||
android:roundIcon="@mipmap/floris_app_icon_round"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/FlorisAppTheme"
|
||||
android:theme="@style/FlorisAppTheme.Splash"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<data android:scheme="florisboard" android:host="app-ui"/>
|
||||
@@ -95,7 +95,7 @@
|
||||
android:launchMode="singleTask"
|
||||
android:roundIcon="@mipmap/floris_app_icon_round"
|
||||
android:targetActivity="dev.patrickgold.florisboard.app.FlorisAppActivity"
|
||||
android:theme="@style/FlorisAppTheme"
|
||||
android:theme="@style/FlorisAppTheme.Splash"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
@@ -147,6 +147,17 @@
|
||||
android:resource="@xml/file_paths"/>
|
||||
</provider>
|
||||
|
||||
<!-- Disable default EmojiCompat initializer -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
||||
tools:node="remove"/>
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
Binary file not shown.
@@ -2,10 +2,10 @@
|
||||
"$": "ime.extension.keyboard",
|
||||
"meta": {
|
||||
"id": "org.florisboard.composers",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"title": "Default composers",
|
||||
"description": "Default composers which are always available.",
|
||||
"maintainers": [ "patrickgold <patrick@patrickgold.dev>" ],
|
||||
"maintainers": [ "patrickgold <patrick@patrickgold.dev>", "thanhhocse96 <thanh.hoquang@pm.me>" ],
|
||||
"license": "apache-2.0"
|
||||
},
|
||||
"composers": [
|
||||
@@ -13,10 +13,10 @@
|
||||
{ "$": "hangul-unicode" },
|
||||
{ "$": "kana-unicode" },
|
||||
{ "$": "with-rules",
|
||||
"id": "basic-telex",
|
||||
"label": "Basic Telex",
|
||||
"id": "telex",
|
||||
"label": "Telex",
|
||||
"rules": {
|
||||
"aw": "ă", "aa": "â", "dd": "đ", "ee": "ê", "oo": "ô", "ow": "ơ", "uw": "ư", "w": "ư",
|
||||
"aw": "ă", "aa": "â", "dd": "đ", "ee": "ê", "oo": "ô", "ow": "ơ", "uw": "ư", "w": "ư",
|
||||
"uow": "ươ",
|
||||
"af": "à", "ar": "ả", "ax": "ã", "as": "á", "aj": "ạ",
|
||||
"ăf": "ằ", "ăr": "ẳ", "ăx": "ẵ", "ăs": "ắ", "ăj": "ặ",
|
||||
@@ -30,7 +30,7 @@
|
||||
"uf": "ù", "ur": "ủ", "ux": "ũ", "us": "ú", "uj": "ụ",
|
||||
"ưf": "ừ", "ưr": "ử", "ưx": "ữ", "ưs": "ứ", "ưj": "ự",
|
||||
"yf": "ỳ", "yr": "ỷ", "yx": "ỹ", "ys": "ý", "yj": "ỵ",
|
||||
"ăw": "aw", "âa": "aa", "đd": "dd", "êe": "ee", "ôo": "oo", "ơw": "ow", "ưw": "w",
|
||||
"ăw": "aw", "âa": "aa", "đd": "dd", "êe": "ee", "ôo": "oo", "ơw": "ow", "ưw": "w", "iêe": "iee",
|
||||
"ươw": "uow",
|
||||
"àf": "af", "ảr": "ar", "ãx": "ax", "ás": "as", "ạj": "aj",
|
||||
"ằf": "ăf", "ẳr": "ăr", "ẵx": "ăx", "ắs": "ăs", "ặj": "ăj",
|
||||
@@ -55,7 +55,623 @@
|
||||
"ờz": "ơ", "ởz": "ơ", "ỡz": "ơ", "ớz": "ơ", "ợz": "ơ",
|
||||
"ùz": "u", "ủz": "u", "ũz": "u", "úz": "u", "ụz": "u",
|
||||
"ừz": "ư", "ửz": "ư", "ữz": "ư", "ứz": "ư", "ựz": "ư",
|
||||
"ỳz": "y", "ỷz": "y", "ỹz": "y", "ýz": "y", "ỵz": "y"
|
||||
"ỳz": "y", "ỷz": "y", "ỹz": "y", "ýz": "y", "ỵz": "y",
|
||||
|
||||
"acw": "ăc", "amw": "ăm", "anw": "ăn", "apw": "ăp", "atw": "ăt", "angw": "ăng",
|
||||
"aca": "âc", "ama": "âm", "ana": "ân", "apa": "âp", "ata": "ât", "aua": "âu", "aya": "ây", "anga": "âng",
|
||||
"eme": "êm", "ene": "ên", "epe": "êp", "ete": "êt", "enhe": "ênh",
|
||||
"oio": "ôi","omo": "ôm", "ono": "ôn", "opo": "ôp", "oto": "ôt", "ongo": "ông",
|
||||
"oiw": "ơi", "omw": "ơm", "onw": "ơn", "opw": "ơp", "otw": "ơt",
|
||||
"uaw": "ưa", "uiw": "ưi", "umw": "ưm", "unw": "ưn", "utw": "ưt", "uuw": "ưu", "ungw": "ưng",
|
||||
"ieme": "iêm", "iene": "iên", "iepe": "iêp", "iete": "iêt", "ieue": "iêu", "ienge": "iêng",
|
||||
"uocw": "ươc", "uoiw": "ươi", "uomw": "ươm", "uonw": "ươn", "uotw": "ươt", "uongw": "ương",
|
||||
"uoco": "uôc", "uoio": "uôi", "uomo": "uôm", "uono": "uôn", "uoto": "uôt", "uongo": "uông",
|
||||
"uyene": "uyên", "uyete": "uyêt",
|
||||
"yeme": "yêm", "yene": "yên", "yete": "yêt", "yeue": "yêu", "yenge": "yêng",
|
||||
|
||||
"ăca": "âc", "ăma": "âm", "ăna": "ân", "ăpa": "âp", "ăta": "ât", "ănga": "âng",
|
||||
"âcw": "ăc", "âmw": "ăm", "ânw": "ăn", "âpw": "ăp", "âtw": "ăt", "ângw": "ăng",
|
||||
"ôiw": "ơi", "ômw": "ơm", "ônw": "ơn", "ôpw": "ơp", "ôtw": "ơt",
|
||||
"ơio": "ôi", "ơmo": "ôm", "ơno": "ôn", "ơpo": "ôp", "ơto": "ôt",
|
||||
|
||||
"ăcw": "acw", "ămw": "amw", "ănw": "anw", "ăpw": "apw", "ătw": "atw", "ăngw": "angw",
|
||||
"âca": "aca", "âma": "ama", "âna": "ana", "âpa": "apa", "âta": "ata", "âua": "aua", "âya": "aya", "ânga": "anga",
|
||||
"ême": "eme", "êne": "ene", "êpe": "epe", "ête": "ete",
|
||||
"ôio": "oio", "ômo": "omo", "ôno": "ono", "ôpo": "opo", "ôto": "oto", "ôngo": "ongo",
|
||||
"ơmw": "omw", "ơnw": "onw", "ơpw": "opw", "ơtw": "otw",
|
||||
"ưaw": "uaw", "ưiw": "uiw", "ưmw": "umw", "ưnw": "unw" , "ưtw": "utw", "ưuw": "uuw", "ưngw": "ungw",
|
||||
"iême": "ieme", "iêne": "iene", "iêpe": "iepe", "iête": "iete", "iêue": "ieue", "iênge": "ienge",
|
||||
"ươcw": "uocw", "ươiw": "uoiw", "ươmw": "uomw", "ươnw": "uonw", "ươtw": "uotw", "ươngw": "uongw",
|
||||
"uyêne": "uyene", "uyêt": "uyete",
|
||||
"yême": "yeme", "yêne": "yene", "yête": "yete", "yêue": "yeue", "yênge": "yenge",
|
||||
|
||||
"acs": "ác", "acj": "ạc",
|
||||
"achs": "ách", "achj": "ạch",
|
||||
"ais": "ái", "aif": "ài", "air": "ải", "aix": "ãi", "aij": "ại",
|
||||
"ams": "ám", "amf": "àm", "amr": "ảm", "amx": "ãm", "amj": "ạm",
|
||||
"ans": "án" , "anf": "àn", "anr": "ản", "anx": "ãn", "anj": "ạn",
|
||||
"aos": "áo", "aof": "ào", "aor": "ảo", "aox": "ão", "aoj": "ạo",
|
||||
"aps":"áp", "apj": "ạp",
|
||||
"ats":"át", "atj": "ạt",
|
||||
"aus":"áu", "auf": "àu", "aur": "ảu", "aux": "ãu", "auj": "ạu",
|
||||
"ays": "áy", "ayf": "ày", "ayr": "ảy", "ayx": "ãy", "ayj": "ạy",
|
||||
"angs": "áng", "angf": "àng", "angr": "ảng", "angx": "ãng", "angj": "ạng",
|
||||
"anhs": "ánh", "anhf": "ành", "anhr": "ảnh", "anhx": "ãnh", "anhj": "ạnh",
|
||||
|
||||
"ács": "acs", "ạcj": "acj",
|
||||
"áchs": "achs", "ạchj": "achj",
|
||||
"áis": "ais", "àif": "aif", "ảir": "air", "ãix": "aix", "ạij": "aij",
|
||||
"áms": "ams", "àmf": "amf", "ảmr": "amr", "ãmx": "amx", "ạmj": "amj",
|
||||
"áns": "ans", "ànf": "anf", "ảnr": "anr", "ãnx": "anx", "ạnj": "anj",
|
||||
"áos": "aos", "àof": "aof", "ảor": "aor", "ãox": "aox", "ạoj":"aoj",
|
||||
"áps": "aps", "ạpj": "apj",
|
||||
"áts": "ats", "ạtj": "atj",
|
||||
"áus": "aus", "àuf": "auf", "ảur": "aur", "ãux": "aux", "ạuj": "auj",
|
||||
"áys": "ays", "àyf": "ayf", "ảyr": "ayr", "ãyx": "ayx", "ạyj": "ayj",
|
||||
"ángs": "angs", "àngf": "angf", "ảngr": "angr", "ãngx": "angx", "ạngj": "angj",
|
||||
"ánhs": "anhs", "ànhf": "anhf", "ảnhr": "anhr", "ãnhx": "anhx", "ạnhj": "anhj",
|
||||
|
||||
"ecs": "éc", "ecj": "ẹc",
|
||||
"ems": "ém", "emf": "èm", "emr": "ẻm", "emx": "ẽm", "emj": "ẹm",
|
||||
"ens": "én", "enf": "èn", "enr": "ẻn", "enx": "ẽn", "enj": "ẹn",
|
||||
"eps": "ép", "epj": "ẹp",
|
||||
"ets": "ét", "etj": "ẹt",
|
||||
"engs": "éng", "engf": "èng", "engr": "ẻng", "engx": "ẽng", "engj": "ẹng",
|
||||
"enhs": "énh", "enhf": "ènh", "enhr": "ẻnh", "enhx": "ẽnh", "enhj": "ẹnh",
|
||||
|
||||
"écs": "ecs", "ẹcj": "ecj",
|
||||
"éms": "ems", "èmf": "emf", "ẻmr": "emr", "ẽmx": "emx", "ẹmj": "emj",
|
||||
"éns": "ens", "ènf": "enf","ẻnr": "enr", "ẽnx": "enx", "ẹnj": "enj",
|
||||
"éps": "eps", "ẹpj": "epj",
|
||||
"éts": "ets", "ẹtj": "etj",
|
||||
"éngs": "engs", "èngf": "engf", "ẻngr": "engr", "ẽngx":"engx", "ẹngj": "engj",
|
||||
"énhs": "enhs", "ènhf": "enhf", "ẻnhr": "enhr", "ẽnh": "enhx", "ẹnh": "enhj",
|
||||
|
||||
"ias": "ía", "iaf": "ìa", "iar": "ỉa", "iax": "ĩa", "iaj": "ịa",
|
||||
"ics": "íc", "icj": "ịc",
|
||||
"ims": "ím", "imf": "ìm", "imr": "ỉm", "imx": "ĩm", "imj": "ịm",
|
||||
"ins": "ín", "inf": "ìn", "inr": "ỉn", "inx": "ĩn", "inj": "ịn",
|
||||
"ips": "íp", "ipj": "ịp",
|
||||
"its": "ít", "itj": "ịt",
|
||||
"ius": "íu", "iuf": "ìu", "iur": "ỉu", "iux": "ĩu", "iuj": "ịu",
|
||||
"ichs": "ích", "ichj": "ịch",
|
||||
"inhs": "ính", "inhf": "ình", "inhr": "ỉnh", "inhx": "ĩnh", "inhj": "ịnh",
|
||||
|
||||
"ías": "ias", "ìaf": "iaf", "ỉar": "iar", "ĩax": "iax", "ịaj": "iaj",
|
||||
"ícs": "ics", "ịcj": "icj",
|
||||
"íms": "ims", "ìmf": "imf", "ỉmr": "imr", "ĩmx": "imx", "ịmj": "imj",
|
||||
"íns": "ins", "ìnf": "inf", "ỉnr": "inr", "ĩnx": "inx", "ịnj": "inj",
|
||||
"íps": "ips", "ịpj": "ipj",
|
||||
"íts": "its", "ịtj": "itj",
|
||||
"íus": "ius", "ìuf": "iuf", "ỉur": "iur", "ĩux": "iux", "ịuj": "iuj",
|
||||
"íchs": "ichs", "ịchj": "ichj",
|
||||
"ínhs": "inhs", "ìnhf": "inhf", "ỉnhr": "inhr", "ĩnhx": "inhx", "ịnhj": "inhj",
|
||||
|
||||
"oas": "óa", "oaf": "òa", "oar": "ỏa", "oax": "õa", "oaj": "ọa",
|
||||
"ocs": "óc", "ocj": "ọc",
|
||||
"ois": "ói", "oif": "òi", "oir": "ỏi", "oix": "õi", "oij": "ọi",
|
||||
"oms": "óm", "omf": "òm", "omr": "ỏm", "omx": "õm", "omj": "ọm",
|
||||
"ons": "ón", "onf": "òn", "onr": "ỏn", "onx": "õn", "onj": "ọn",
|
||||
"ops": "óp", "opj": "ọp",
|
||||
"ots": "ót", "otj": "ọt",
|
||||
"ongs": "óng", "ongf": "òng", "ongr": "ỏng", "ongx": "õng", "ongj": "ọng",
|
||||
|
||||
"óas": "oas", "òaf": "oaf", "ỏar": "oar", "õax": "oax", "ọaj": "oaj",
|
||||
"ócs": "ocs", "ọcj": "ocj",
|
||||
"óis": "ois", "òif": "oif", "ỏir": "oir", "õix": "oix", "ọij": "oij",
|
||||
"óms": "oms", "òmf": "omf", "ỏmr": "omr", "õmx": "omx", "ọmj": "omj",
|
||||
"óns": "ons", "ònf": "onf", "ỏnr": "onr", "õnx": "onx", "ọnj": "onj",
|
||||
"óps": "ops", "ọpj": "opj",
|
||||
"óts": "ots", "ọtj": "otj",
|
||||
"óngs": "ongs", "òngf": "ongf", "ỏngr": "ongr", "õngx": "ongx", "ọngj": "ongj",
|
||||
|
||||
"uas": "úa", "uaf": "ùa", "uar": "ủa", "uax": "ũa", "uaj": "ụa",
|
||||
"ucs": "úc", "ucj": "ục",
|
||||
"uis": "úi", "uif": "ùi", "uir": "ủi", "uix": "ũi", "uij": "ụi",
|
||||
"ums": "úm", "umf": "ùm", "umr": "ủm", "umx": "ũm", "umj": "ụm",
|
||||
"uns": "ún", "unf": "ùn", "unr": "ủn", "unx": "ũn", "unj": "ụn",
|
||||
"ups": "úp", "upj": "ụp",
|
||||
"uts": "út", "utj": "ụt",
|
||||
"uus": "úu", "uuf": "ùu", "uur": "ủu", "uux": "ũu", "uuj": "ụu",
|
||||
"uys": "úy", "uyf": "ùy", "uyr": "ủy", "uyx": "ũy", "uyj": "ụy",
|
||||
"ungs": "úng", "ungf": "ùng", "ungr": "ủng", "ungx": "ũng", "ungj": "ụng",
|
||||
|
||||
"úas": "uas", "ùaf": "uaf", "ủar": "uar", "ũax": "uax", "ụaj": "uaj",
|
||||
"úcs": "ucs", "ụcj": "ucj",
|
||||
"úis": "uis", "ùif": "uif", "ủir": "uir", "ũix": "uix", "ụij": "uij",
|
||||
"úms": "ums", "ùmf": "umf", "ủmr": "umr", "ũmx": "umx", "ụmj": "umj",
|
||||
"úns": "uns", "ùnf": "unf", "ủnr": "unr", "ũnx": "unx", "ụnj": "unj",
|
||||
"úps": "ups", "ụpj": "upj",
|
||||
"úts": "uts", "ụtj": "utj",
|
||||
"úus": "uus", "ùuf": "uuf", "ủur": "uur", "ũux": "uux", "ụuj": "uuj",
|
||||
"úu ": "uus", "ùu ": "uuf", "ủu ": "uur", "ũu ": "uux", "ụu ": "uuj",
|
||||
"úys": "uys", "ùyf": "uyf", "ủyr": "uyr", "ũyx": "uyx", "ụyj": "uyj",
|
||||
"úngs": "ungs", "ùngf": "ungf", "ủngr": "ungr", "ũngx": "ungx", "ụngj": "ungj",
|
||||
|
||||
"ăcs": "ắc", "ăcj": "ặc",
|
||||
"ácw": "ắc", "ạcw": "ặc",
|
||||
"ăms": "ắm", "ămf": "ằm", "ămr": "ẳm", "ămx": "ẵm", "ămj": "ặm",
|
||||
"ámw": "ắm", "àmw": "ằm", "ảmw": "ẳm", "ãmw": "ẵm", "ạmw": "ặm",
|
||||
"ăns": "ắn", "ănf": "ằn", "ănr": "ẳn", "ănx": "ẵn", "ănj": "ặn",
|
||||
"ánw": "ắn", "ànw": "ằn", "ảnw": "ẳn", "ãnw": "ẵn", "ạnw": "ặn",
|
||||
"ăps": "ắp", "ăpj": "ặp",
|
||||
"ápw": "ắp", "ạpw": "ặp",
|
||||
"ăts": "ắt", "ătj": "ặt",
|
||||
"átw": "ắt", "ạtw": "ặt",
|
||||
"ăngs": "ắng", "ăngf": "ằng", "ăngr": "ẳng", "ăngx": "ẵng", "ăngj": "ặng",
|
||||
"ángw": "ắng", "àngw": "ằng", "ảngw": "ẳng", "ãngw":"ẵng", "ạngw": "ặng",
|
||||
|
||||
"ắcw": "ácw", "ắcs": "ăcs", "ăcsw": "acsw", "ácws": "acws",
|
||||
"ặcw": "ạcw", "ặcj": "ăcj", "ăcjw": "acjw", "ạcwj": "acwj",
|
||||
"ắms": "ăms", "ắmw": "ámw", "ămsw": "amsw", "ámws": "amws",
|
||||
"ằmf": "ămf", "ằmw": "àmw", "ămfw": "amfw", "àmwf": "amwf",
|
||||
"ẳmr": "ămr", "ẳmw": "ảmw", "ămrw": "amrw", "ảmwr": "amwr",
|
||||
"ẵmx": "ămx", "ẵmw": "ãmw", "ămxw": "amxw", "ãmwx": "amwx",
|
||||
"ặmj": "ămj", "ặmw": "ạmw", "ămjw": "amjw", "ạmwj": "amwj",
|
||||
"ắns": "ăns", "ắnw": "ánw", "ănsw": "answ", "ánws": "anws",
|
||||
"ằnf": "ănf", "ằnw": "ànw", "ănfw": "anfw", "ànwf": "anwf",
|
||||
"ẳnr": "ănr", "ẳnw": "ảnw", "ănrw": "anrw", "ảnwr": "anwr",
|
||||
"ẵnx": "ănx", "ẵnw": "ãnw", "ănxw": "anxw", "ãnwx": "anwx",
|
||||
"ặnj": "ănj", "ặnw": "ạnw", "ănjw": "anjw", "ạnwj": "anwj",
|
||||
"ắps": "ăps", "ắpw": "ápw", "ăpsw": "apsw", "ápws": "apws",
|
||||
"ặpj": "ăpj", "ặpw": "ạpw", "ăpjw": "apjw", "ạpwj": "apwj",
|
||||
"ắts": "ăts", "ắtw": "átw", "ătsw": "atws", "átws": "atws",
|
||||
"ặtj": "ătj", "ặtw": "ạtw", "ătjw": "atjw", "ạtwj": "atwj",
|
||||
"ắngs": "ăngs", "ắngw": "ángw", "ăngsw": "angsw", "ángws": "angws",
|
||||
"ằngf": "ăngf", "ằngw": "àngw", "ăngfw": "angfw", "àngwf": "angwf",
|
||||
"ẳngr": "ăngr", "ẳngw": "ảngw", "ăngrw": "angrw", "ảngwr": "angwr",
|
||||
"ẵngx": "ăngx", "ẵngw": "ãngw", "ăngxw": "angxw", "ãngwx": "angwx",
|
||||
"ặngj": "ăngj", "ặngw": "ạngw", "ăngjw": "angjw", "ạngwj": "angwj",
|
||||
|
||||
"âcs": "ấc", "âcj": "ậc",
|
||||
"áca": "ấc", "ạca": "ậc",
|
||||
"âms": "ấm", "âmf": "ầm", "âmr": "ẩm", "âmx": "ẫm", "âmj": "ậm",
|
||||
"áma": "ấm", "àma": "ầm", "ảma": "ẩm", "ãma": "ẫm", "ạma": "ậm",
|
||||
"âns": "ấn", "ânf": "ần", "ânr": "ẩn", "ânx": "ẫn", "ânj": "ận",
|
||||
"ána": "ấn", "àna": "ần", "ảna": "ẩn", "ãna": "ẫn", "ạna": "ận",
|
||||
"âps": "ấp", "âpj": "ập",
|
||||
"ápa": "ấp", "ạpa": "ập",
|
||||
"âts": "ất", "âtj": "ật",
|
||||
"áta": "ất", "ạta": "ật",
|
||||
"âus": "ấu", "âuf": "ầu", "âur": "ẩu", "âux": "ẫu", "âuj": "ậu",
|
||||
"áua": "ấu", "àua": "ầu", "ảua": "ẩu", "ãua": "ẫu", "ạua": "ậu",
|
||||
"âys": "ấy", "âyf": "ầy", "âyr": "ẩy", "âyx": "ẫy", "âyj": "ậy",
|
||||
"áya": "ấy", "àya": "ầy", "ảya": "ẩy", "ãya": "ẫy", "ạya": "ậy",
|
||||
"ângs": "ấng", "ângf": "ầng", "ângr": "ẩng", "ângx": "ẫng", "ângj": "ậng",
|
||||
"ánga": "ấng", "ànga": "ầng", "ảnga": "ẩng", "ãnga": "ẫng", "ạnga": "ậng",
|
||||
|
||||
"ấcs": "âcs", "ấca": "áca", "âcsa": "acsa", "ácas": "acas",
|
||||
"ậcj": "âcj", "ậca": "ạca", "âcja": "acja", "ạcaj": "acaj",
|
||||
"ấms": "âms", "ấma": "áma", "âmsa": "amsa", "ámas": "amas",
|
||||
"ầmf": "âmf", "ầma": "àma", "âmfa": "amfa", "àmaf": "amaf",
|
||||
"ẩmr": "âmr", "ẩma": "ảma", "âmra": "amra", "ảmar": "amar",
|
||||
"ẫmx": "âmx", "ẫma": "ãma", "âmxa": "amxa", "ãmax": "amax",
|
||||
"ậmj": "âmj", "ậma": "ạma", "âmja": "amja", "ạmaj": "amaj",
|
||||
"ấns": "âns", "ấna": "ána", "ânsa": "ansa", "ánas": "anas",
|
||||
"ầnf": "ânf", "ầna": "àna", "ânfa": "anfa", "ànaf": "anaf",
|
||||
"ẩnr": "ânr", "ẩna": "ảna", "ânra": "anra", "ảnar": "anar",
|
||||
"ẫnx": "ânx", "ẫna": "ãna", "ânxa": "anxa", "ãnax": "anax",
|
||||
"ậnj": "ânj", "ậna": "ạna", "ânja": "anja", "ạnaj": "anaj",
|
||||
"ấps": "âps", "ấpa": "ápa", "âpsa": "apsa", "ápas": "apas",
|
||||
"ậpj": "âpj", "ậpa": "ạpa", "âpja": "apja", "ạpaj": "apaj",
|
||||
"ấts": "âts", "ấta": "áta", "âtsa": "atas", "átas": "atas",
|
||||
"ậtj": "âtj", "ậta": "ạta", "âtja": "atja", "ạtaj": "ataj",
|
||||
"ấus": "âus", "ấua": "áua", "âusa": "ausa", "áuas": "auas",
|
||||
"ầuf": "âuf", "ầua": "àua", "âufa": "aufa", "àuaf": "auaf",
|
||||
"ẩur": "âur", "ẩua": "ảua", "âura": "aura", "ảuar": "auar",
|
||||
"ẫux": "âux", "ẫua": "ãua", "âuxa": "auxa", "ãuax": "auax",
|
||||
"ậuj": "âuj", "ậua": "ạua", "âuja": "auja", "ạuaj": "auaj",
|
||||
"ấys": "âys", "ấya": "áya", "âysa": "aysa", "áyas": "ayas",
|
||||
"ầyf": "âyf", "ầya": "àya", "âyfa": "ayfa", "àyaf": "ayaf",
|
||||
"ẩyr": "âyr", "ẩya": "ảya", "âyra": "ayra", "ảyar": "ayar",
|
||||
"ẫyx": "âyx", "ẫya": "ãya", "âyxa": "ayxa", "ãyax": "ayax",
|
||||
"ậyj": "âyj", "ậya": "ạya", "âyja": "ayja", "ạyaj": "ayaj",
|
||||
"ấngs": "ângs", "ấnga": "ánga", "ângsa": "angsa", "ángas": "angas",
|
||||
"ầngf": "ângf", "ầnga": "ànga", "ângfa": "angfa", "àngaf": "angaf",
|
||||
"ẩngr": "ângr", "ẩnga": "ảnga", "ângra": "angra", "ảngar": "angar",
|
||||
"ẫngx": "ângx", "ẫnga": "ãnga", "ângxa": "angxa", "ãngax": "angax",
|
||||
"ậngj": "ângj", "ậnga": "ạnga", "ângja": "angja", "ạngaj": "angaj",
|
||||
|
||||
"êcs": "ếc", "êcj": "ệc",
|
||||
"éce": "ếc", "ẹce": "ệc",
|
||||
"êms": "ếm", "êmf": "ềm", "êmr": "ểm", "êmx": "ễm", "êmj": "ệm",
|
||||
"éme": "ếm", "ème": "ềm", "ẻme": "ểm", "ẽme": "ễm", "ẹme": "ệm",
|
||||
"êns": "ến", "ênf": "ền", "ênr": "ển", "ênx": "ễn", "ênj": "ện",
|
||||
"éne": "ến", "ène": "ền", "ẻne": "ển", "ẽne": "ễn", "ẹne": "ện",
|
||||
"êps": "ếp", "êpj": "ệp",
|
||||
"épe": "ếp", "ẹpe": "ệp",
|
||||
"êts": "ết", "êtj": "ệt",
|
||||
"éte": "ết", "ẹte": "ệt",
|
||||
"êchs": "ếch", "êchj": "ệch",
|
||||
"echs": "éch", "echj": "ẹch",
|
||||
"éche": "ếch", "ẹche": "ệch",
|
||||
"ênhs": "ếnh", "ênhf": "ềnh", "ênhr": "ểnh", "ênhx": "ễnh", "ênhj": "ệnh",
|
||||
"énhe": "ếnh", "ènhe": "ềnh", "ẻnhe": "ểnh", "ẽnhe": "ễnh", "ẹnhe": "ệnh",
|
||||
|
||||
"ếms": "êms", "ếme": "éme", "êmse": "emse", "émes": "emes",
|
||||
"ềmf": "êmf", "ềme": "ème", "êmfe": "emfe", "èmef": "emef",
|
||||
"ểmr": "êmr", "ểme": "ẻme", "êmre": "emre", "ẻmer": "emer",
|
||||
"ễmx": "êmx", "ễme": "ẽme", "êmxe": "emxe", "ẽmex": "emex",
|
||||
"ệmj": "êmj", "ệme": "ẹme", "êmje": "emje", "ẹmej": "emej",
|
||||
"ếns": "êns", "ếne": "éne", "ênse": "ense", "énes": "enes",
|
||||
"ềnf": "ênf", "ềne": "ène", "ênfe": "enfe", "ènef": "enef",
|
||||
"ểnr": "ênr", "ểne": "ẻne", "ênre": "enre", "ẻner": "ener",
|
||||
"ễnx": "ênx", "ễne": "ẽne", "ênxe": "enxe", "ẽnex": "enex",
|
||||
"ệnj": "ênj", "ệne": "ẹne", "ênje": "enje", "ẹnej": "enej",
|
||||
"ếps": "êps", "ếpe": "épe", "êpse": "epse", "épes": "epes",
|
||||
"ệpj": "êpj", "ệpe": "ẹpe", "êpje": "epje", "ẹpej": "epej",
|
||||
"ếts": "êts", "ếte": "éte", "êtse": "etes", "étes": "etes",
|
||||
"ệtj": "êtj", "ệte": "ẹte", "êtje": "etje", "ẹtej": "etej",
|
||||
"ếchs": "êchs", "ếche": "éche", "êchse": "eches", "éches": "eches",
|
||||
"ệchj": "êchj", "ệche": "ẹche", "êchje": "echje", "ẹchej": "echej",
|
||||
"ếnhs": "ênhs", "ếnhe": "énhe", "ênhse": "enhse", "énhes": "enhes",
|
||||
"ềnhf": "ênhf", "ềnhe": "ènhe", "ênhfe": "enhfe", "ènhef": "enhef",
|
||||
"ểnhr": "ênhr", "ểnhe": "ẻnhe", "ênhre": "enhre", "ẻnher": "enher",
|
||||
"ễnhx": "ênhx", "ễnhe": "ẽnhe", "ênhxe": "enhxe", "ẽnhex": "enhex",
|
||||
"ệnhj": "ênhj", "ệnhe": "ẹnhe", "ênhje": "enhje", "ẹnhej": "enhej",
|
||||
|
||||
"ôcs": "ốc", "ôcj": "ộc",
|
||||
"óco": "ốc", "ọco": "ộc",
|
||||
"ôis": "ối", "ôif": "ồi", "ôir": "ổi", "ôix": "ỗi", "ôij": "ội",
|
||||
"óio": "ối", "òio": "ồi", "ỏio": "ổi", "õio": "ỗi", "ọio": "ội",
|
||||
"ôms": "ốm", "ômf": "ồm", "ômr": "ổm", "ômx": "ỗm", "ômj": "ộm",
|
||||
"ómo": "ốm", "òmo": "ồm", "ỏmo": "ổm", "õmo": "ỗm", "ọmo": "ộm",
|
||||
"ôns": "ốn", "ônf": "ồn", "ônr": "ổn", "ônx": "ỗn", "ônj": "ộn",
|
||||
"óno": "ốn", "òno": "ồn", "ỏno": "ổn", "õno": "ỗn", "ọno": "ộn",
|
||||
"ôps": "ốp", "ôpj": "ộp",
|
||||
"ópo": "ốp", "ọpo": "ộp",
|
||||
"ôts": "ốt", "ôtj": "ột",
|
||||
"óto": "ốt", "ọto": "ột",
|
||||
"ôngs": "ống", "ôngf": "ồng", "ôngr": "ổng", "ôngx": "ỗng", "ôngj": "ộng",
|
||||
"óngo": "ống", "òngo": "ồng", "ỏngo": "ổng", "õngo": "ỗng", "ọngo": "ộng",
|
||||
|
||||
"ốcs": "ôcs", "ốco": "óco", "ôcso": "ocso", "ócos": "ocos",
|
||||
"ộcj": "ôcj", "ộco": "ọco", "ôcjo": "ocjo", "ọcoj": "ocoj",
|
||||
"ốis": "ôis", "ốio": "óio", "ôiso": "oiso", "óios": "oios",
|
||||
"ồif": "ôif", "ồio": "òio", "ôifo": "oifo", "òiof": "oiof",
|
||||
"ổir": "ôir", "ổio": "ỏio", "ôiro": "oiro", "ỏior": "oior",
|
||||
"ỗix": "ôix", "ỗio": "õio", "ôixo": "oixo", "õiox": "oiox",
|
||||
"ộij": "ôij", "ộio": "ọio", "ôijo": "oijo", "ọioj": "oioj",
|
||||
"ốms": "ôms", "ốmo": "ómo", "ômso": "omso", "ómos": "omos",
|
||||
"ồmf": "ômf", "ồmo": "òmo", "ômfo": "omfo", "òmof": "omof",
|
||||
"ổmr": "ômr", "ổmo": "ỏmo", "ômro": "omro", "ỏmor": "omor",
|
||||
"ỗmx": "ômx", "ỗmo": "õmo", "ômxo": "omxo", "õmox": "omox",
|
||||
"ộmj": "ômj", "ộmo": "ọmo", "ômjo": "omjo", "ọmoj": "omoj",
|
||||
"ốns": "ôns", "ốno": "óno", "ônso": "onso", "ónos": "onos",
|
||||
"ồnf": "ônf", "ồno": "òno", "ônfo": "onfo", "ònof": "onof",
|
||||
"ổnr": "ônr", "ổno": "ỏno", "ônro": "onro", "ỏnor": "onor",
|
||||
"ỗnx": "ônx", "ỗno": "õno", "ônxo": "onxo", "õnox": "onox",
|
||||
"ộnj": "ônj", "ộno": "ọno", "ônjo": "onjo", "ọnoj": "onoj",
|
||||
"ốps": "ôps", "ốpo": "ópo", "ôpso": "opso", "ópos": "opos",
|
||||
"ộpj": "ôpj", "ộpo": "ọpo", "ôpjo": "opjo", "ọpoj": "opoj",
|
||||
"ốts": "ôts", "ốto": "óto", "ôtso": "otso", "ótos": "otos",
|
||||
"ộtj": "ôtj", "ộto": "ọto", "ôtjo": "otjo", "ọtoj": "otoj",
|
||||
"ốngs": "ôngs", "ốngo": "óngo", "ôngso": "ongso", "óngos": "ongos",
|
||||
"ồngf": "ôngf", "ồngo": "òngo", "ôngfo": "ongfo", "òngof": "ongof",
|
||||
"ổngr": "ôngr", "ổngo": "ỏngo", "ôngro": "ongro", "ỏngor": "ongor",
|
||||
"ỗngx": "ôngx", "ỗngo": "õngo", "ôngxo": "ongxo", "õngox": "ongox",
|
||||
"ộngj": "ôngj", "ộngo": "ọngo", "ôngjo": "ongjo", "ọngoj": "ongoj",
|
||||
|
||||
"ơis": "ới", "ơif": "ời", "ơir": "ởi", "ơix": "ỡi", "ơij": "ợi",
|
||||
"óiw": "ới", "òiw": "ời", "ỏiw": "ởi", "õiw": "ỡi", "ọiw": "ợi",
|
||||
"ơms": "ớm", "ơmf": "ờm", "ơmr": "ởm", "ơmx": "ỡm", "ơmj": "ợm",
|
||||
"ómw": "ớm", "òmw": "ờm", "ỏmw": "ởm", "õmw": "ỡm", "ọmw": "ợm",
|
||||
"ơns": "ớn", "ơnf": "ờn", "ơnr": "ởn", "ơnx": "ỡn", "ơnj": "ợn",
|
||||
"ónw": "ớn", "ònw": "ờn", "ỏnw": "ởn", "õnw": "ỡn", "ọnw": "ợn",
|
||||
"ơps": "ớp", "ơpj": "ợp",
|
||||
"ópw": "ớp", "ọpw": "ợp",
|
||||
"ơts": "ớt", "ơtj": "ợt",
|
||||
"ótw": "ớt", "ọtw": "ợt",
|
||||
|
||||
"ớis": "ơis", "ớiw": "óiw", "ơisw": "oisw", "óiws": "oiws",
|
||||
"ờif": "ơif", "ờiw": "òiw", "ơifw": "oifw", "òiwf": "oiwf",
|
||||
"ởir": "ơir", "ởiw": "ỏiw", "ơirw": "oirw", "ỏiwr": "oiwr",
|
||||
"ỡix": "ơix", "ỡiw": "õiw", "ơixw": "oixw", "õiwx": "oiwx",
|
||||
"ợij": "ơij", "ợiw": "ọiw", "ơijw": "oijw", "ọiwj": "oiwj",
|
||||
"ớms": "ơms", "ớmw": "ómw", "ơmsw": "omsw", "ómws": "omws",
|
||||
"ờmf": "ơmf", "ờmw": "òmw", "ơmfw": "omfw", "òmwf": "omwf",
|
||||
"ởmr": "ơmr", "ởmw": "ỏmw", "ơmrw": "omrw", "ỏmwr": "omwr",
|
||||
"ỡmx": "ơmx", "ỡmw": "õmw", "ơmxw": "omxw", "õmwx": "omwx",
|
||||
"ợmj": "ơmj", "ợmw": "ọmw", "ơmjw": "omjw", "ọmwj": "omwj",
|
||||
"ớns": "ơns", "ớnw": "ónw", "ơnsw": "onsw", "ónws": "onws",
|
||||
"ờnf": "ơnf", "ờnw": "ònw", "ơnfw": "onfw", "ònwf": "onwf",
|
||||
"ởnr": "ơnr", "ởnw": "ỏnw", "ơnrw": "onrw", "ỏnwr": "onwr",
|
||||
"ỡnx": "ơnx", "ỡnw": "õnw", "ơnxw": "onxw", "õnwx": "onwx",
|
||||
"ợnj": "ơnj", "ợnw": "ọnw", "ơnjw": "onjw", "ọnwj": "onwj",
|
||||
"ớps": "ơps", "ớpw": "ópw", "ơpsw": "opsw", "ópws": "opws",
|
||||
"ợpj": "ơpj", "ợpw": "ọpw", "ơpjw": "opjw", "ọpwj": "opwj",
|
||||
"ớts": "ơts", "ớtw": "ótw", "ơtsw": "otsw", "ótws": "otws",
|
||||
"ợtj": "ơtj", "ợtw": "ọtw", "ơtjw": "otjw", "ọtwj": "otwj",
|
||||
|
||||
"ưas": "ứa", "ưaf": "ừa", "ưar": "ửa", "ưax": "ữa", "ưaj": "ựa",
|
||||
"úaw": "ứa", "ùaw": "ừa", "ủaw": "ửa", "ũaw": "ữa", "ụaw": "ựa",
|
||||
"ưis": "ứi", "ưif": "ừi", "ưir": "ửi", "ưix": "ữi", "ưij": "ựi",
|
||||
"úiw": "ứi", "ùiw": "ừi", "ủiw": "ửi", "ũiw": "ữi", "ụiw": "ựi",
|
||||
"ưms": "ứm", "ưmf": "ừm", "ưmr": "ửm", "ưmx": "ữm", "ưmj": "ựm",
|
||||
"úmw": "ứm", "ùmw": "ừm", "ủmw": "ửm", "ũmw": "ữm", "ụmw": "ựm",
|
||||
"ưns": "ứn", "ưnf": "ừn", "ưnr": "ửn", "ưnx": "ữn", "ưnj": "ựn",
|
||||
"únw": "ứn", "ùnw": "ừn", "ủnw": "ửn", "ũnw": "ữn", "ụnw": "ựn",
|
||||
"ưts": "ứt", "ưtj": "ựt",
|
||||
"útw": "ứt", "ụtw": "ựt",
|
||||
"ưus": "ứu", "ưuf": "ừu", "ưur": "ửu", "ưux": "ữu", "ưuj": "ựu",
|
||||
"úuw": "ứu", "ùuw": "ừu", "ủuw": "ửu", "ũuw": "ữu", "ụuw": "ựu",
|
||||
"ưngs": "ứng", "ưngf": "ừng", "ưngr": "ửng", "ưngx": "ững", "ưngj": "ựng",
|
||||
"úngw": "ứng", "ùngw": "ừng", "ủngw": "ửng", "ũngw": "ững", "ụngw": "ựng",
|
||||
|
||||
"ứas": "ưas", "ứaw": "úaw", "ưasw": "uasw", "úaws": "uaws",
|
||||
"ừaf": "ưaf", "ừaw": "ùaw", "ưafw": "uafw", "ùawf": "uawf",
|
||||
"ửar": "ưar", "ửaw": "ủaw", "ưarw": "uarw", "ủawr": "uawr",
|
||||
"ữax": "ưax", "ữaw": "ũaw", "ưaxw": "uaxw", "ũawx": "uawx",
|
||||
"ựaj": "ưaj", "ựaw": "ụaw", "ưajw": "uajw", "ụawj": "uawj",
|
||||
"ứis": "ưis", "ứiw": "úiw", "ưisw": "uisw", "úiws": "uiws",
|
||||
"ừif": "ưif", "ừiw": "ùiw", "ưifw": "uifw", "ùiwf": "uiwf",
|
||||
"ửir": "ưir", "ửiw": "ủiw", "ưirw": "uirw", "ủiwr": "uiwr",
|
||||
"ữix": "ưix", "ữiw": "ũiw", "ưixw": "uixw", "ũiwx": "uiwx",
|
||||
"ựij": "ưij", "ựiw": "ụiw", "ưijw": "uijw", "ụiwj": "uiwj",
|
||||
"ứms": "ưms", "ứmw": "úmw", "ưmsw": "umsw", "úmws": "umws",
|
||||
"ừmf": "ưmf", "ừmw": "ùmw", "ưmfw": "umfw", "ùmwf": "umwf",
|
||||
"ửmr": "ưmr", "ửmw": "ủmw", "ưmrw": "umrw", "ủmwr": "umwr",
|
||||
"ữmx": "ưmx", "ữmw": "ũmw", "ưmxw": "umxw", "ũmwx": "umwx",
|
||||
"ựmj": "ưmj", "ựmw": "ụmw", "ưmjw": "umjw", "ụmwj": "umwj",
|
||||
"ứns": "ưns", "ứnw": "únw", "ưnsw": "unsw", "únws": "unws",
|
||||
"ừnf": "ưnf", "ừnw": "ùnw", "ưnfw": "unfw", "ùnwf": "unwf",
|
||||
"ửnr": "ưnr", "ửnw": "ủnw", "ưnrw": "unrw", "ủnwr": "unwr",
|
||||
"ữnx": "ưnx", "ữnw": "ũnw", "ưnxw": "unxw", "ũnwx": "unwx",
|
||||
"ựnj": "ưnj", "ựnw": "ụnw", "ưnjw": "unjw", "ụnwj": "unwj",
|
||||
"ứts": "ưts", "ứtw": "útw", "ưtsw": "utsw", "útws": "utws",
|
||||
"ựtj": "ưtj", "ựtw": "ụtw", "ưtjw": "utjw", "ụtwj": "utwj",
|
||||
"ứus": "ưus", "ứuw": "úuw", "ưusw": "uusw", "úuws": "uuws",
|
||||
"ừuf": "ưuf", "ừuw": "ùuw", "ưufw": "uufw", "ùuwf": "uuwf",
|
||||
"ửur": "ưur", "ửuw": "ủuw", "ưurw": "uurw", "ủuwr": "uuwr",
|
||||
"ữux": "ưux", "ữuw": "ũuw", "ưuxw": "uuxw", "ũuwx": "uuwx",
|
||||
"ựuj": "ưuj", "ựuw": "ụuw", "ưujw": "uujw", "ụuwj": "uuwj",
|
||||
"ứngs": "ưngs", "ứngw": "úngw", "ưngsw": "ungsw", "úngws": "ungws",
|
||||
"ừngf": "ưngf", "ừngw": "ùngw", "ưngfw": "ungfw", "ùngwf": "ungwf",
|
||||
"ửngr": "ưngr", "ửngw": "ủngw", "ưngrw": "ungrw", "ủngwr": "ungwr",
|
||||
"ữngx": "ưngx", "ữngw": "ũngw", "ưngxw": "ungxw", "ũngwx": "ungwx",
|
||||
"ựngj": "ưngj", "ựngw": "ụngw", "ưngjw": "ungjw", "ụngwj": "ungwj",
|
||||
|
||||
"iems": "iém", "iemf": "ièm", "iemr": "iẻm", "iemx": "iẽm", "iemj": "iẹm",
|
||||
"iéme": "iếm", "ième": "iềm", "iẻme": "iểm", "iẽme": "iễm", "iẹme": "iệm",
|
||||
"iêms": "iếm", "iêmf": "iềm", "iêmr": "iểm", "iêmx": "iễm", "iêmj": "iệm",
|
||||
"iens": "ién", "ienf": "ièn", "ienr": "iẻn", "ienx": "iẽn", "ienj": "iẹn",
|
||||
"iéne": "iến", "iène": "iền", "iẻne": "iển", "iẽne": "iễn", "iẹne": "iện",
|
||||
"iêns": "iến", "iênf": "iền", "iênr": "iển", "iênx": "iễn", "iênj": "iện",
|
||||
"ieps": "iép", "iepj": "iẹp",
|
||||
"iépe": "iếp", "iẹpe": "iệp",
|
||||
"iêps": "iếp", "iêpj": "iệp",
|
||||
"iets": "iét", "ietj": "iẹt",
|
||||
"iéte": "iết", "iẹte": "iệt",
|
||||
"iêts": "iết", "iêtj": "iệt",
|
||||
"ieus": "iéu", "ieuf": "ièu", "ieur": "iẻu", "ieux": "iẽu", "ieuj": "iẹu",
|
||||
"iéue": "iếu", "ièue": "iều", "iẻue": "iểu", "iẽue": "iễu", "iẹue": "iệu",
|
||||
"iêus": "iếu", "iêuf": "iều", "iêur": "iểu", "iêux": "iễu", "iêuj": "iệu",
|
||||
"iengs": "iéng", "iengf": "ièng", "iengr": "iẻng", "iengx": "iẽng", "iengj": "iẹng",
|
||||
"iénge": "iếng", "iènge": "iềng", "iẻnge": "iểng", "iẽnge": "iễng", "iẹnge": "iệng",
|
||||
"iêngs": "iếng", "iêngf": "iềng", "iêngr": "iểng", "iêngx": "iễng", "iêngj": "iệng",
|
||||
|
||||
"iếms": "iêms", "iếme": "iéme", "iêmse": "iemse", "iémes": "iemes",
|
||||
"iềmf": "iêmf", "iềme": "ième", "iêmfe": "iemfe", "ièmef": "iemef",
|
||||
"iểmr": "iêmr", "iểme": "iẻme", "iêmre": "iemre", "iẻmer": "iemer",
|
||||
"iễmx": "iêmx", "iễme": "iẽme", "iêmxe": "iemxe", "iẽmex": "iemex",
|
||||
"iệmj": "iêmj", "iệme": "iẹme", "iêmje": "iemje", "iẹmej": "iemej",
|
||||
"iếns": "iêns", "iếne": "iéne", "iênse": "iense", "iénes": "ienes",
|
||||
"iềnf": "iênf", "iềne": "iène", "iênfe": "ienfe", "iènef": "ienef",
|
||||
"iểnr": "iênr", "iểne": "iẻne", "iênre": "ienre", "iẻner": "iener",
|
||||
"iễnx": "iênx", "iễne": "iẽne", "iênxe": "ienxe", "iẽnex": "ienex",
|
||||
"iệnj": "iênj", "iệne": "iẹne", "iênje": "ienje", "iẹnej": "ienej",
|
||||
"iếps": "iêps", "iếpe": "iépe", "iêpse": "iepse", "iépes": "iepes",
|
||||
"iệpj": "iêpj", "iệpe": "iẹpe", "iêpje": "iepje", "iẹpej": "iepej",
|
||||
"iếts": "iêts", "iếte": "iéte", "iêtse": "ietse", "iétes": "ietes",
|
||||
"iệtj": "iêtj", "iệte": "iẹte", "iêtje": "ietje", "iẹtej": "ietej",
|
||||
"iếus": "iêus", "iếue": "iéue", "iêuse": "ieuse", "iéues": "ieues",
|
||||
"iềuf": "iêuf", "iềue": "ièue", "iêufe": "ieufe", "ièuef": "ieuef",
|
||||
"iểur": "iêur", "iểue": "iẻue", "iêure": "ieure", "iẻuer": "ieuer",
|
||||
"iễux": "iêux", "iễue": "iẽue", "iêuxe": "ieuxe", "iẽuex": "ieuex",
|
||||
"iệuj": "iêuj", "iệue": "iẹue", "iêuje": "ieuje", "iẹuej": "ieuej",
|
||||
"iếngs": "iêngs", "iếnge": "iénge", "iêngse": "iengse", "iénges": "ienges",
|
||||
"iềngf": "iêngf", "iềnge": "iènge", "iêngfe": "iengfe", "ièngef": "iengef",
|
||||
"iểngr": "iêngr", "iểnge": "iẻnge", "iêngre": "iengre", "iẻnger": "ienger",
|
||||
"iễngx": "iêngx", "iễnge": "iẽnge", "iêngxe": "iengxe", "iẽngex": "iengex",
|
||||
"iệngj": "iêngj", "iệnge": "iẹnge", "iêngje": "iengje", "iẹngej": "iengej",
|
||||
|
||||
"uyens": "uyén", "uyenf": "uyèn", "uyenr": "uyẻn", "uyenx": "uyẽn", "uyenj": "uyẹn",
|
||||
"uyéne": "uyến", "uyène": "uyền", "uyẻne": "uyển", "uyẽne": "uyễn", "uyẹne": "uyện",
|
||||
"uyêns": "uyến", "uyênf": "uyền", "uyênr": "uyển", "uyênx": "uyễn", "uyênj": "uyện",
|
||||
"uyets": "uyét", "uyetf": "uyèt", "uyetr": "uyẻt", "uyetx": "uyẽt", "uyetj": "uyẹt",
|
||||
"uyéte": "uyết", "uyète": "uyềt", "uyẻte": "uyểt", "uyẽte": "uyễt", "uyẹte": "uyệt",
|
||||
"uyêts": "uyết", "uyêtf": "uyềt", "uyêtr": "uyểt", "uyêtx": "uyễt", "uyêtj": "uyệt",
|
||||
|
||||
"uyếns": "uyêns", "uyếne": "uyéne", "uyênse": "uyense", "uyénes": "uyenes",
|
||||
"uyềnf": "uyênf", "uyềne": "uyène", "uyênfe": "uyenfe", "uyènef": "uyenef",
|
||||
"uyểnr": "uyênr", "uyểne": "uyẻne", "uyênre": "uyenre", "uyẻner": "uyener",
|
||||
"uyễnx": "uyênx", "uyễne": "uyẽne", "uyênxe": "uyenxe", "uyẽnex": "uyenex",
|
||||
"uyệnj": "uyênj", "uyệne": "uyẹne", "uyênje": "uyenje", "uyẹnej": "uyenej",
|
||||
"uyếts": "uyêts", "uyếte": "uyéte", "uyêtse": "uyetse", "uyétes": "uyetes",
|
||||
"uyềtf": "uyêtf", "uyềte": "uyète", "uyêtfe": "uyetfe", "uyètef": "uyetef",
|
||||
"uyểtr": "uyêtr", "uyểte": "uyẻte", "uyêtre": "uyetre", "uyẻter": "uyeter",
|
||||
"uyễtx": "uyêtx", "uyễte": "uyẽte", "uyêtxe": "uyetxe", "uyẽtex": "uyetex",
|
||||
"uyệtj": "uyêtj", "uyệte": "uyẹte", "uyêtje": "uyetje", "uyẹtej": "uyetej",
|
||||
|
||||
"uyts": "uýt", "uytj": "uỵt",
|
||||
"uynhs": "uýnh", "uynhf": "uỳnh", "uynhr": "uỷnh", "uynhx": "uỹnh", "uynhj": "uỵnh",
|
||||
|
||||
"uýts": "uyts", "uỵtj": "uytj",
|
||||
"uýnhs": "uynhs", "uỳnhf": "uynhf", "uỷnhr": "uynhr", "uỹnh": "uynhx", "uỵnhj": "uynhj",
|
||||
|
||||
"uits": "uít", "uitj": "uịt",
|
||||
|
||||
"uíts": "uits", "uịtj": "uitj",
|
||||
|
||||
"uos": "úo", "uof": "ùo", "uor": "ủo", "uox": "ũo", "uoj": "ụo",
|
||||
"úoc": "uóc", "ụoc": "uọc",
|
||||
"uocs": "uóc", "uocj": "uọc",
|
||||
"uócw": "ước", "uọcw": "ược",
|
||||
"ươcs": "ước", "ươcj": "ược",
|
||||
"úoi": "uói", "ùoi" : "uòi", "ủoi": "uỏi", "ũoi": "uõi", "ụoi": "uọi",
|
||||
"uois": "uói", "uoif": "uòi", "uoir": "uỏi", "uoix": "uõi", "uoij": "uọi",
|
||||
"uóiw": "ưới", "uòiw": "ười", "uỏiw": "ưởi", "uõiw": "ưỡi", "uọiw": "ượi",
|
||||
"ươis": "ưới", "ươif": "ười", "ươir": "ưởi", "ươix": "ưỡi", "ươij": "ượi",
|
||||
"úom": "uóm", "ùom" : "uòm", "ủom": "uỏm", "ũom": "uõm", "ụom": "uọm",
|
||||
"uoms": "uóm", "uomf": "uòm", "uomr": "uỏm", "uomx": "uõm", "uomj": "uọm",
|
||||
"uómw": "ướm", "uòmw": "ườm", "uỏmw": "ưởm", "uõmw": "ưỡm", "uọmw": "ượm",
|
||||
"ươms": "ướm", "ươmf": "ườm", "ươmr": "ưởm", "ươmx": "ưỡm", "ươmj": "ượm",
|
||||
"úon": "uón", "ùon" : "uòn", "ủon": "uỏn", "ũon": "uõn", "ụon": "uọn",
|
||||
"uons": "uón", "uonf": "uòn", "uonr": "uỏn", "uonx": "uõn", "uonj": "uọn",
|
||||
"uónw": "ướn", "uònw": "ườn", "uỏnw": "ưởn", "uõnw": "ưỡn", "uọnw": "ượn",
|
||||
"ươns": "ướn", "ươnf": "ườn", "ươnr": "ưởn", "ươnx": "ưỡn", "ươnj": "ượn",
|
||||
"úop": "uóp", "ụop": "uọp",
|
||||
"uops": "uóp", "uopj": "uọp",
|
||||
"uópw": "ướp", "uọpw": "ượp",
|
||||
"ươps": "ướp", "ươpj": "ượp",
|
||||
"úot": "uót", "ụot": "uọt",
|
||||
"uots": "uót", "uotj": "uọt",
|
||||
"uótw": "ướt", "uọtw": "ượt",
|
||||
"ươts": "ướt", "ươtj": "ượt",
|
||||
"úou": "uóu", "ùou" : "uòu", "ủou": "uỏu", "ũou": "uõu", "ụou": "uọu",
|
||||
"uous": "uóu", "uouf": "uòu", "uour": "uỏu", "uoux": "uõu", "uouj": "uọu",
|
||||
"uóuw": "ướu", "uòuw": "ườu", "uỏuw": "ưởu", "uõuw": "ưỡu", "uọuw": "ượu",
|
||||
"ươus": "ướu", "ươuf": "ườu", "ươur": "ưởu", "ươux": "ưỡu", "ươuj": "ượu",
|
||||
"úong": "uóng", "ùong" : "uòng", "ủong": "uỏng", "ũong": "uõng", "ụong": "uọng",
|
||||
"uongs": "uóng", "uongf": "uòng", "uongr": "uỏng", "uongx": "uõng", "uongj": "uọng",
|
||||
"uóngw": "ướng", "uòngw": "ường", "uỏngw": "ưởng", "uõngw": "ưỡng", "uọngw": "ượng",
|
||||
"ươngs": "ướng", "ươngf": "ường", "ươngr": "ưởng", "ươngx": "ưỡng", "ươngj": "ượng",
|
||||
|
||||
"úos": "uos", "ùof": "uof", "ủor": "uor", "ũo": "uox", "ụoj": "uoj",
|
||||
"uócs": "uocs", "uọcj": "uocj",
|
||||
"ướcs": "ươcs", "ướcw": "uócw", "ươcsw": "uocsw", "uócws": "uocws",
|
||||
"ượcj": "ươcj", "ượcw": "uọcw", "ươcjw": "uocjw", "uọcwj": "uocwj",
|
||||
"uóis": "uois", "uòif": "uoif", "uỏir": "uoir", "uõix": "uoix", "uọij": "uoij",
|
||||
"ướis": "ươis", "ướiw": "uóiw", "ươisw": "uoisw", "uóiws": "uoiws",
|
||||
"ườif": "ươif", "ườiw": "uòiw", "ươifw": "uoifw", "uòiwf": "uoiwf",
|
||||
"ưởir": "ươir", "ưởiw": "uỏiw", "ươirw": "uoirw", "uỏiwr": "uoiwr",
|
||||
"ưỡix": "ươix", "ưỡiw": "uõiw", "ươixw": "uoixw", "uõiwx": "uoiwx",
|
||||
"ượij": "ươij", "ượiw": "uóiw", "ươijw": "uoijw", "uọiwj": "uoiwj",
|
||||
"uóms": "uoms", "uòmf": "uomf", "uỏmr": "uomr", "uõmx": "uomx", "uọmj": "uomj",
|
||||
"ướms": "ươms", "ướmw": "uómw", "ươmsw": "uomsw", "uómws": "uomws",
|
||||
"ườmf": "ươmf", "ườmw": "uòmw", "ươmfw": "uomfw", "uòmwf": "uomwf",
|
||||
"ưởmr": "ươmr", "ưởmw": "uỏmw", "ươmrw": "uomrw", "uỏmwr": "uomwr",
|
||||
"ưỡmx": "ươmx", "ưỡmw": "uõmw", "ươmxw": "uomxw", "uõmwx": "uomwx",
|
||||
"ượmj": "ươmj", "ượmw": "uómw", "ươmjw": "uomjw", "uọmwj": "uomwj",
|
||||
"uóns": "uons", "uònf": "uonf", "uỏnr": "uonr", "uõnx": "uonx", "uọnj": "uonj",
|
||||
"ướns": "ươns", "ướnw": "uónw", "ươnsw": "uonsw", "uónws": "uonws",
|
||||
"ườnf": "ươnf", "ườnw": "uònw", "ươnfw": "uonfw", "uònwf": "uonwf",
|
||||
"ưởnr": "ươnr", "ưởnw": "uỏnw", "ươnrw": "uonrw", "uỏnwr": "uonwr",
|
||||
"ưỡnx": "ươnx", "ưỡnw": "uõnw", "ươnxw": "uonxw", "uõnwx": "uonwx",
|
||||
"ượnj": "ươnj", "ượnw": "uónw", "ươnjw": "uonjw", "uọnwj": "uonwj",
|
||||
"uóps": "uops", "uọpj": "uopj",
|
||||
"ướps": "ươps", "ướpw": "uópw", "ươpsw": "uopsw", "uópws": "uopws",
|
||||
"ượpj": "ươpj", "ượpw": "uọpw", "ươpjw": "uopjw", "uọpwj": "uopwj",
|
||||
"uóts": "uots", "uọtj": "uotj",
|
||||
"ướts": "ươts", "ướtw": "uótw", "ươtsw": "uotsw", "uótws": "uotws",
|
||||
"ượtj": "ươtj", "ượtw": "uọtw", "ươtjw": "uotjw", "uọtwj": "uotwj",
|
||||
"uóus": "uous", "uòuf": "uouf", "uỏur": "uour", "uõux": "uoux", "uọuj": "uouj",
|
||||
"ướus": "ươus", "ướuw": "uóuw", "ươusw": "uousw", "uóuws": "uouws",
|
||||
"ườuf": "ươuf", "ườuw": "uòuw", "ươufw": "uoufw", "uòuwf": "uouwf",
|
||||
"ưởur": "ươur", "ưởuw": "uỏuw", "ươurw": "uourw", "uỏuwr": "uouwr",
|
||||
"ưỡux": "ươux", "ưỡuw": "uõuw", "ươuxw": "uouxw", "uõuwx": "uouwx",
|
||||
"ượuj": "ươuj", "ượuw": "uóuw", "ươujw": "uoujw", "uọuwj": "uouwj",
|
||||
"uóngs": "uongs", "uòngf": "uongf", "uỏngr": "uongr", "uõngx": "uongx", "uọngj": "uongj",
|
||||
"ướngs": "ươngs", "ướngw": "uóngw", "ươngsw": "uongsw", "uóngws": "uongws",
|
||||
"ườngf": "ươngf", "ườngw": "uòngw", "ươngfw": "uongfw", "uòngwf": "uongwf",
|
||||
"ưởngr": "ươngr", "ưởngw": "uỏngw", "ươngrw": "uongrw", "uỏngwr": "uongwr",
|
||||
"ưỡngx": "ươngx", "ưỡngw": "uõngw", "ươngxw": "uongxw", "uõngwx": "uongwx",
|
||||
"ượngj": "ươngj", "ượngw": "uóngw", "ươngjw": "uongjw", "uọngwj": "uongwj",
|
||||
|
||||
"uóco": "uốc", "uọco": "uộc",
|
||||
"uôcs": "uốc", "uôcj": "uộc",
|
||||
"uóio": "uối", "uòio": "uồi", "uỏio": "uổi", "uõio": "uỗi", "uọio": "uội",
|
||||
"uôis": "uối", "uôif": "uồi", "uôir": "uổi", "uôix": "uỗi", "uôij": "uội",
|
||||
"uómo": "uốm", "uòmo": "uồm", "uỏmo": "uổm", "uõmo": "uỗm", "uọmo": "uộm",
|
||||
"uôms": "uốm", "uômf": "uồm", "uômr": "uổm", "uômx": "uỗm", "uômj": "uộm",
|
||||
"uóno": "uốn", "uòno": "uồn", "uỏno": "uổn", "uõno": "uỗn", "uọno": "uộn",
|
||||
"uôns": "uốn", "uônf": "uồn", "uônr": "uổn", "uônx": "uỗn", "uônj": "uộn",
|
||||
"uópo": "uốp", "uọpo": "uộp",
|
||||
"uôps": "uốp", "uôpj": "uộp",
|
||||
"uóto": "uốt", "uọto": "uột",
|
||||
"uôts": "uốt", "uôtj": "uột",
|
||||
"uóuo": "uốu", "uòuo": "uồu", "uỏuo": "uổu", "uõuo": "uỗu", "uọuo": "uộu",
|
||||
"uôus": "uốu", "uôuf": "uồu", "uôur": "uổu", "uôux": "uỗu", "uôuj": "uộu",
|
||||
"uóngo": "uống", "uòngo": "uồng", "uỏngo": "uổng", "uõngo": "uỗng", "uọngo": "uộng",
|
||||
"uôngs": "uống", "uôngf": "uồng", "uôngr": "uổng", "uôngx": "uỗng", "uôngj": "uộng",
|
||||
|
||||
"uốcs": "uôcs", "uốco": "uóco", "uôcso": "uocso", "uócos": "uocos",
|
||||
"uộcj": "uôcj", "uộco": "uọco", "uôcjo": "uocjo", "uọcoj": "uocoj",
|
||||
"uốis": "uôis", "uốio": "uóio", "uôiso": "uoiso", "uóios": "uoios",
|
||||
"uồif": "uôif", "uồio": "uòio", "uôifo": "uoifo", "uòiof": "uoiof",
|
||||
"uổir": "uôir", "uổio": "uỏio", "uôiro": "uoiro", "uỏior": "uoior",
|
||||
"uỗix": "uôix", "uỗio": "uõio", "uôixo": "uoixo", "uõiox": "uoiox",
|
||||
"uộij": "uôij", "uộio": "uóio", "uôijo": "uoijo", "uọioj": "uoioj",
|
||||
"uốms": "uôms", "uốmo": "uómo", "uômso": "uomso", "uómos": "uomos",
|
||||
"uồmf": "uômf", "uồmo": "uòmo", "uômfo": "uomfo", "uòmof": "uomof",
|
||||
"uổmr": "uômr", "uổmo": "uỏmo", "uômro": "uomro", "uỏmor": "uomor",
|
||||
"uỗmx": "uômx", "uỗmo": "uõmo", "uômxo": "uomxo", "uõmox": "uomox",
|
||||
"uộmj": "uômj", "uộmo": "uómo", "uômjo": "uomjo", "uọmoj": "uomoj",
|
||||
"uốns": "uôns", "uốno": "uóno", "uônso": "uonso", "uónos": "uonos",
|
||||
"uồnf": "uônf", "uồno": "uòno", "uônfo": "uonfo", "uònof": "uonof",
|
||||
"uổnr": "uônr", "uổno": "uỏno", "uônro": "uonro", "uỏnor": "uonor",
|
||||
"uỗnx": "uônx", "uỗno": "uõno", "uônxo": "uonxo", "uõnox": "uonox",
|
||||
"uộnj": "uônj", "uộno": "uóno", "uônjo": "uonjo", "uọnoj": "uonoj",
|
||||
"uốps": "uôps", "uốpo": "uópo", "uôpso": "uopso", "uópos": "uopos",
|
||||
"uộpj": "uôpj", "uộpo": "uọpo", "uôpjo": "uopjo", "uọpoj": "uopoj",
|
||||
"uốts": "uôts", "uốto": "uóto", "uôtso": "uotso", "uótos": "uotos",
|
||||
"uộtj": "uôtj", "uộto": "uọto", "uôtjo": "uotjo", "uọtoj": "uotoj",
|
||||
"uốus": "uôus", "uốuo": "uóuo", "uôuso": "uouso", "uóuos": "uouos",
|
||||
"uồuf": "uôuf", "uồuo": "uòuo", "uôufo": "uoufo", "uòuof": "uouof",
|
||||
"uổur": "uôur", "uổuo": "uỏuo", "uôuro": "uouro", "uỏuor": "uouor",
|
||||
"uỗux": "uôux", "uỗuo": "uõuo", "uôuxo": "uouxo", "uõuox": "uouox",
|
||||
"uộuj": "uôuj", "uộuo": "uóuo", "uôujo": "uoujo", "uọuoj": "uouoj",
|
||||
"uốngs": "uôngs", "uốngo": "uóngo", "uôngso": "uongso", "uóngos": "uongos",
|
||||
"uồngf": "uôngf", "uồngo": "uòngo", "uôngfo": "uongfo", "uòngof": "uongof",
|
||||
"uổngr": "uôngr", "uổngo": "uỏngo", "uôngro": "uongro", "uỏngor": "uongor",
|
||||
"uỗngx": "uôngx", "uỗngo": "uõngo", "uôngxo": "uongxo", "uõngox": "uongox",
|
||||
"uộngj": "uôngj", "uộngo": "uóngo", "uôngjo": "uongjo", "uọngoj": "uongoj",
|
||||
|
||||
"yes": "ýe", "yef": "ỳe", "yer": "ỷe", "yex": "ỹe", "yej": "ỵe",
|
||||
"ýem": "yém", "ỳem": "yèm", "ỷem": "yẻm", "ỹem": "yẽm", "ỵem": "yẹm",
|
||||
"yems": "yém", "yemf": "yèm", "yemr": "yẻm", "yemx": "yẽm", "yemj": "yẹm",
|
||||
"yêms": "yếm", "yêmf": "yềm", "yêmr": "yểm", "yêmx": "yễm", "yêmj": "yệm",
|
||||
"yéme": "yếm", "yème": "yềm", "yẻme": "yểm", "yẽme": "yễm", "yẹme": "yệm",
|
||||
"ýen": "yén", "ỳen": "yèn", "ỷen": "yẻn", "ỹen": "yẽn", "ỵen": "yẹn",
|
||||
"yens": "yén", "yenf": "yèn", "yenr": "yẻn", "yenx": "yẽn", "yenj": "yẹn",
|
||||
"yêns": "yến", "yênf": "yền", "yênr": "yển", "yênx": "yễn", "yênj": "yện",
|
||||
"yéne": "yến", "yène": "yền", "yẻne": "yển", "yẽne": "yễn", "yẹne": "yện",
|
||||
"ýet": "yét", "ỵet": "yẹt",
|
||||
"yets": "yét", "yetj": "yẹt",
|
||||
"yêts": "yết", "yêtj": "yệt",
|
||||
"yéte": "yết", "yẹte": "yệt",
|
||||
"ýeu": "yéu", "ỳeu": "yèu", "ỷeu": "yẻu", "ỹeu": "yẽu", "ỵeu": "yẹu",
|
||||
"yeus": "yéu", "yeuf": "yèu", "yeur": "yẻu", "yeux": "yẽu", "yeuj": "yẹu",
|
||||
"yêus": "yếu", "yêuf": "yều", "yêur": "yểu", "yêux": "yễu", "yêuj": "yệu",
|
||||
"yéue": "yếu", "yèue": "yều", "yẻue": "yểu", "yẽue": "yễu", "yẹue": "yệu",
|
||||
"ýeng": "yéng", "ỳeng": "yèng", "ỷeng": "yẻng", "ỹeng": "yẽng", "ỵeng": "yẹng",
|
||||
"yengs": "yéng", "yengf": "yèng", "yengr": "yẻng", "yengx": "yẽng", "yengj": "yẹng",
|
||||
"yêngs": "yếng", "yêngf": "yềng", "yêngr": "yểng", "yêngx": "yễng", "yêngj": "yệng",
|
||||
"yénge": "yếng", "yènge": "yềng", "yẻnge": "yểng", "yẽnge": "yễng", "yẹnge": "yệng",
|
||||
|
||||
"ýes": "yes", "ỳef": "yef", "ỷer": "yer", "ỹex": "yex", "ỵej": "yej",
|
||||
"yếms": "yêms", "yếme": "yéme", "yêmse": "yemse", "yémes": "yemes",
|
||||
"yềmf": "yêmf", "yềme": "yème", "yêmfe": "yemfe", "yèmef": "yemef",
|
||||
"yểmr": "yêmr", "yểme": "yẻme", "yêmre": "yemre", "yẻmer": "yemer",
|
||||
"yễmx": "yêmx", "yễme": "yẽme", "yêmxe": "yemxe", "yẽmex": "yemex",
|
||||
"yệmj": "yêmj", "yệme": "yẹme", "yêmje": "yemje", "yẹmej": "yemej",
|
||||
"yếns": "yêns", "yếne": "yéne", "yênse": "yense", "yénes": "yenes",
|
||||
"yềnf": "yênf", "yềne": "yène", "yênfe": "yenfe", "yènef": "yenef",
|
||||
"yểnr": "yênr", "yểne": "yẻne", "yênre": "yenre", "yẻner": "yener",
|
||||
"yễnx": "yênx", "yễne": "yẽne", "yênxe": "yenxe", "yẽnex": "yenex",
|
||||
"yệnj": "yênj", "yệne": "yẹne", "yênje": "yenje", "yẹnej": "yenej",
|
||||
"yếts": "yêts", "yếte": "yéte", "yêtse": "yetse", "yétes": "yetes",
|
||||
"yệtj": "yêtj", "yệte": "yẹte", "yêtje": "yetje", "yẹtej": "yetej",
|
||||
"yếus": "yêus", "yếue": "yéue", "yêuse": "yeuse", "yéues": "yeues",
|
||||
"yềuf": "yêuf", "yềue": "yèue", "yêufe": "yeufe", "yèuef": "yeuef",
|
||||
"yểur": "yêur", "yểue": "yẻue", "yêure": "yeure", "yẻuer": "yeuer",
|
||||
"yễux": "yêux", "yễue": "yẽue", "yêuxe": "yeuxe", "yẽuex": "yeuex",
|
||||
"yệuj": "yêuj", "yệue": "yẹue", "yêuje": "yeuje", "yẹuej": "yeuej",
|
||||
"yếngs": "yêngs", "yếnge": "yénge", "yêngse": "yengse", "yénges": "yenges",
|
||||
"yềngf": "yêngf", "yềnge": "yènge", "yêngfe": "yengfe", "yèngef": "yengef",
|
||||
"yểngr": "yêngr", "yểnge": "yẻnge", "yêngre": "yengre", "yẻnger": "yenger",
|
||||
"yễngx": "yêngx", "yễnge": "yẽnge", "yêngxe": "yengxe", "yẽngex": "yengex",
|
||||
"yệngj": "yêngj", "yệnge": "yẹnge", "yêngje": "yengje", "yẹngej": "yengej"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -45,6 +45,18 @@
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "costa_rican_colon",
|
||||
"label": "Costa Rican colón (₡)",
|
||||
"slots": [
|
||||
{ "code": 8353, "label": "₡" },
|
||||
{ "code": 36, "label": "$" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dollar",
|
||||
"label": "Dollar ($)",
|
||||
|
||||
@@ -111,6 +111,13 @@
|
||||
"direction": "ltr",
|
||||
"modifier": "org.florisboard.layouts:dvorak"
|
||||
},
|
||||
{
|
||||
"id": "dvorak_es",
|
||||
"label": "Dvorak (ÑÇ)",
|
||||
"authors": [ "tsiflimagas" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "org.florisboard.layouts:dvorak"
|
||||
},
|
||||
{
|
||||
"id": "esperanto",
|
||||
"label": "Esperanto",
|
||||
@@ -178,6 +185,12 @@
|
||||
"authors": [ "nd500" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "indonesian",
|
||||
"label": "Indonesian (QWERTY)",
|
||||
"authors": [ "Linerly" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "ipa",
|
||||
"label": "International Phonetic Alphabet",
|
||||
@@ -186,13 +199,13 @@
|
||||
},
|
||||
{
|
||||
"id": "jcuken_russian",
|
||||
"label": "Russian (JCUKEN)",
|
||||
"label": "Russian (ЙЦУКЕН)",
|
||||
"authors": [ "williamtheaker" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "jcuken_ukrainian",
|
||||
"label": "Ukrainian (JCUKEN)",
|
||||
"label": "Ukrainian (ЙЦУКЕН)",
|
||||
"authors": [ "williamtheaker", "33kk" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
@@ -262,6 +275,13 @@
|
||||
"direction": "rtl",
|
||||
"modifier": "org.florisboard.layouts:persian2"
|
||||
},
|
||||
{
|
||||
"id": "persian3",
|
||||
"label": "Persian3",
|
||||
"authors": [ "SaeID-Rz" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "org.florisboard.layouts:persian3"
|
||||
},
|
||||
{
|
||||
"id": "qwerty",
|
||||
"label": "QWERTY",
|
||||
@@ -438,6 +458,12 @@
|
||||
"label": "Persian2",
|
||||
"authors": [ "M-Koushan" ],
|
||||
"direction": "rtl"
|
||||
},
|
||||
{
|
||||
"id": "persian3",
|
||||
"label": "Persian3",
|
||||
"authors": [ "SaeID-Rz" ],
|
||||
"direction": "rtl"
|
||||
}
|
||||
],
|
||||
"extension": [
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
[
|
||||
[
|
||||
{ "$": "variation_selector",
|
||||
"default": { "$": "case_selector",
|
||||
"lower": { "code": 39, "label": "'", "popup": {
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 34, "label": "\"", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 39, "label": "'"}
|
||||
]
|
||||
} },
|
||||
"default": { "$": "variation_selector",
|
||||
"email": { "code": 64, "label": "@" },
|
||||
"uri": { "code": 47, "label": "/" },
|
||||
"default": { "code": 39, "label": "'", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 34, "label": "\"" }
|
||||
]
|
||||
} },
|
||||
"upper": { "code": 34, "label": "\"", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 39, "label": "'"}
|
||||
]
|
||||
} }
|
||||
},
|
||||
"email": { "code": 64, "label": "@" },
|
||||
"uri": { "code": 47, "label": "/" }
|
||||
}
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 44, "label": ",", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
} },
|
||||
"upper": { "code": 60, "label": "<", "popup": {
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 60, "label": "<", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
} },
|
||||
"default": { "code": 44, "label": ",", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 46, "label": ".", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 62, "label": ">" }
|
||||
]
|
||||
} },
|
||||
"upper": { "code": 62, "label": ">", "popup": {
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 62, "label": ">", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 46, "label": "." }
|
||||
]
|
||||
} },
|
||||
"default": { "code": 46, "label": ".", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 62, "label": ">" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 112, "label": "p" },
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
[
|
||||
[
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 60, "label": "<", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
} },
|
||||
"default": { "code": 44, "label": ",", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 62, "label": ">", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 46, "label": "." }
|
||||
]
|
||||
} },
|
||||
"default": { "code": 46, "label": ".", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 62, "label": ">" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 241, "label": "ñ" },
|
||||
{ "$": "auto_text_key", "code": 112, "label": "p" },
|
||||
{ "$": "auto_text_key", "code": 121, "label": "y" },
|
||||
{ "$": "auto_text_key", "code": 102, "label": "f" },
|
||||
{ "$": "auto_text_key", "code": 103, "label": "g" },
|
||||
{ "$": "auto_text_key", "code": 99, "label": "c" },
|
||||
{ "$": "auto_text_key", "code": 104, "label": "h" },
|
||||
{ "$": "auto_text_key", "code": 108, "label": "l" }
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 97, "label": "a" },
|
||||
{ "$": "auto_text_key", "code": 111, "label": "o" },
|
||||
{ "$": "auto_text_key", "code": 101, "label": "e" },
|
||||
{ "$": "auto_text_key", "code": 117, "label": "u" },
|
||||
{ "$": "auto_text_key", "code": 105, "label": "i" },
|
||||
{ "$": "auto_text_key", "code": 100, "label": "d" },
|
||||
{ "$": "auto_text_key", "code": 114, "label": "r" },
|
||||
{ "$": "auto_text_key", "code": 116, "label": "t" },
|
||||
{ "$": "auto_text_key", "code": 110, "label": "n" },
|
||||
{ "$": "auto_text_key", "code": 115, "label": "s" },
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 34, "label": "\"", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 39, "label": "'"}
|
||||
]
|
||||
} },
|
||||
"default": { "$": "variation_selector",
|
||||
"email": { "code": 64, "label": "@" },
|
||||
"uri": { "code": 47, "label": "/" },
|
||||
"default": { "code": 39, "label": "'", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 34, "label": "\"" }
|
||||
]
|
||||
} }
|
||||
}
|
||||
},
|
||||
{ "$": "auto_text_key", "code": 231, "label": "ç" }
|
||||
],
|
||||
[
|
||||
{ "$": "auto_text_key", "code": 106, "label": "j" },
|
||||
{ "$": "auto_text_key", "code": 107, "label": "k" },
|
||||
{ "$": "auto_text_key", "code": 120, "label": "x" },
|
||||
{ "$": "auto_text_key", "code": 98, "label": "b" },
|
||||
{ "$": "auto_text_key", "code": 109, "label": "m" },
|
||||
{ "$": "auto_text_key", "code": 119, "label": "w" },
|
||||
{ "$": "auto_text_key", "code": 118, "label": "v" }
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
[
|
||||
[
|
||||
{ "code": 1590, "label": "ض" },
|
||||
{ "code": 1589, "label": "ص" },
|
||||
{ "code": 1579, "label": "ث" },
|
||||
{ "code": 1602, "label": "ق" },
|
||||
{ "code": 1601, "label": "ف" },
|
||||
{ "code": 1594, "label": "غ" },
|
||||
{ "code": 1593, "label": "ع" },
|
||||
{ "code": 1607, "label": "ه" },
|
||||
{ "code": 1582, "label": "خ" },
|
||||
{ "code": 1581, "label": "ح" },
|
||||
{ "code": 1580, "label": "ج" }
|
||||
],
|
||||
[
|
||||
{ "code": 1588, "label": "ش" },
|
||||
{ "code": 1587, "label": "س" },
|
||||
{ "code": 1740, "label": "ی" },
|
||||
{ "code": 1576, "label": "ب" },
|
||||
{ "code": 1604, "label": "ل" },
|
||||
{ "code": 1575, "label": "ا" },
|
||||
{ "code": 1578, "label": "ت" },
|
||||
{ "code": 1606, "label": "ن" },
|
||||
{ "code": 1605, "label": "م" },
|
||||
{ "code": 1705, "label": "ک" },
|
||||
{ "code": 1711, "label": "گ" }
|
||||
],
|
||||
[
|
||||
{ "code": 1592, "label": "ظ" },
|
||||
{ "code": 1591, "label": "ط" },
|
||||
{ "code": 1586, "label": "ز" },
|
||||
{ "code": 1585, "label": "ر" },
|
||||
{ "code": 1584, "label": "ذ" },
|
||||
{ "code": 1583, "label": "د" },
|
||||
{ "code": 1662, "label": "پ" },
|
||||
{ "code": 1608, "label": "و" },
|
||||
{ "code": 1670, "label": "چ" }
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
[
|
||||
[
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -7, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "$": "variation_selector",
|
||||
"default": { "code": 1548, "label": "،", "groupId": 1 },
|
||||
"email": { "code": 64, "label": "@", "groupId": 1 },
|
||||
"uri": { "code": 47, "label": "/", "groupId": 1 }
|
||||
},
|
||||
{ "code": -227, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -212, "label": "ime_ui_mode_media", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 46, "label": ".", "groupId": 2 },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
@@ -1,12 +0,0 @@
|
||||
[
|
||||
[
|
||||
{ "code": -35, "label": "clipboard_select_all", "type": "enter_editing" },
|
||||
{ "code": -31, "label": "clipboard_copy", "type": "enter_editing" },
|
||||
{ "code": -32, "label": "clipboard_cut", "type": "enter_editing" },
|
||||
{ "code": -21, "label": "arrow_left", "type": "navigation" },
|
||||
{ "code": -22, "label": "arrow_right", "type": "navigation" },
|
||||
{ "code": -33, "label": "clipboard_paste", "type": "enter_editing" },
|
||||
{ "code": -38, "label": "clipboard_clear_primary_clip", "type": "system_gui"},
|
||||
{ "code": -213, "label": "ime_ui_mode_clipboard", "type": "system_gui"}
|
||||
]
|
||||
]
|
||||
@@ -1,68 +1,200 @@
|
||||
[
|
||||
[
|
||||
{ "code": 49, "label": "1", "type": "numeric", "popup": {
|
||||
"main": { "code": 185, "label": "¹" },
|
||||
"relevant": [
|
||||
{ "code": 8537, "label": "⅙" },
|
||||
{ "code": 8528, "label": "⅐" },
|
||||
{ "code": 8539, "label": "⅛" },
|
||||
{ "code": 8529, "label": "⅑" },
|
||||
{ "code": 8530, "label": "⅒" },
|
||||
{ "code": 189, "label": "½" },
|
||||
{ "code": 8531, "label": "⅓" },
|
||||
{ "code": 188, "label": "¼" },
|
||||
{ "code": 8533, "label": "⅕" }
|
||||
]
|
||||
} },
|
||||
{ "code": 50, "label": "2", "type": "numeric", "popup": {
|
||||
"main": { "code": 178, "label": "²" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 51, "label": "3", "type": "numeric", "popup": {
|
||||
"main": { "code": 179, "label": "³" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 52, "label": "4", "type": "numeric", "popup": {
|
||||
"main": { "code": 8308, "label": "⁴" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" }
|
||||
]
|
||||
} },
|
||||
{ "code": 53, "label": "5", "type": "numeric", "popup": {
|
||||
"main": { "code": 8309, "label": "⁵" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 54, "label": "6", "type": "numeric", "popup": {
|
||||
"main": { "code": 8310, "label": "⁶" }
|
||||
} },
|
||||
{ "code": 55, "label": "7", "type": "numeric", "popup": {
|
||||
"main": { "code": 8311, "label": "⁷" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" }
|
||||
]
|
||||
} },
|
||||
{ "code": 56, "label": "8", "type": "numeric", "popup": {
|
||||
"main": { "code": 8312, "label": "⁸" }
|
||||
} },
|
||||
{ "code": 57, "label": "9", "type": "numeric", "popup": {
|
||||
"main": { "code": 8313, "label": "⁹" }
|
||||
} },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": {
|
||||
"main": { "code": 8304, "label": "⁰" },
|
||||
"relevant": [
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8319, "label": "ⁿ" }
|
||||
]
|
||||
} }
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 33, "label": "!", "type": "numeric", "popup": {
|
||||
"main": { "code": 161, "label": "¡" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 49, "label": "1", "type": "numeric", "popup": {
|
||||
"main": { "code": 185, "label": "¹" },
|
||||
"relevant": [
|
||||
{ "code": 8537, "label": "⅙" },
|
||||
{ "code": 8528, "label": "⅐" },
|
||||
{ "code": 8539, "label": "⅛" },
|
||||
{ "code": 8529, "label": "⅑" },
|
||||
{ "code": 8530, "label": "⅒" },
|
||||
{ "code": 189, "label": "½" },
|
||||
{ "code": 8531, "label": "⅓" },
|
||||
{ "code": 188, "label": "¼" },
|
||||
{ "code": 8533, "label": "⅕" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 64, "label": "@", "type": "numeric" },
|
||||
"default": {
|
||||
"code": 50, "label": "2", "type": "numeric", "popup": {
|
||||
"main": { "code": 178, "label": "²" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 35, "label": "#", "type": "numeric", "popup": {
|
||||
"main": { "code": 8470, "label": "№" }
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 51, "label": "3", "type": "numeric", "popup": {
|
||||
"main": { "code": 179, "label": "³" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": -801, "label": "currency_slot_1", "type": "numeric", "popup": {
|
||||
"main": { "code": -802, "label": "currency_slot_2" },
|
||||
"relevant": [
|
||||
{ "code": -806, "label": "currency_slot_6" },
|
||||
{ "code": -803, "label": "currency_slot_3" },
|
||||
{ "code": -804, "label": "currency_slot_4" },
|
||||
{ "code": -805, "label": "currency_slot_5" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 52, "label": "4", "type": "numeric", "popup": {
|
||||
"main": { "code": 8308, "label": "⁴" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 37, "label": "%", "type": "numeric", "popup": {
|
||||
"main": { "code": 8240, "label": "‰" },
|
||||
"relevant": [
|
||||
{ "code": 8453, "label": "℅" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 53, "label": "5", "type": "numeric", "popup": {
|
||||
"main": { "code": 8309, "label": "⁵" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 94, "label": "^", "type": "numeric", "popup": {
|
||||
"main": { "code": 8593, "label": "↑" },
|
||||
"relevant": [
|
||||
{ "code": 8592, "label": "←" },
|
||||
{ "code": 8595, "label": "↓" },
|
||||
{ "code": 8594, "label": "→" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 54, "label": "6", "type": "numeric", "popup": {
|
||||
"main": { "code": 8310, "label": "⁶" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": { "code": 38, "label": "&", "type": "numeric" },
|
||||
"default": {
|
||||
"code": 55, "label": "7", "type": "numeric", "popup": {
|
||||
"main": { "code": 8311, "label": "⁷" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"code": 42, "label": "*", "type": "numeric", "popup": {
|
||||
"main": { "code": 8224, "label": "†" },
|
||||
"relevant": [
|
||||
{ "code": 9733, "label": "★" },
|
||||
{ "code": 8225, "label": "‡" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 56, "label": "8", "type": "numeric", "popup": {
|
||||
"main": { "code": 8312, "label": "⁸" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"$": "layout_direction_selector",
|
||||
"ltr": {
|
||||
"code": 40, "label": "(", "type": "numeric", "popup": {
|
||||
"main": { "code": 60, "label": "<" },
|
||||
"relevant": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 123, "label": "{" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"rtl": {
|
||||
"code": 41, "label": "(", "type": "numeric", "popup": {
|
||||
"main": { "code": 62, "label": "<" },
|
||||
"relevant": [
|
||||
{ "code": 93, "label": "[" },
|
||||
{ "code": 125, "label": "{" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 57, "label": "9", "type": "numeric", "popup": {
|
||||
"main": { "code": 8313, "label": "⁹" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "$": "shift_state_selector",
|
||||
"shiftedManual": {
|
||||
"$": "layout_direction_selector",
|
||||
"ltr": {
|
||||
"code": 41, "label": ")", "type": "numeric", "popup": {
|
||||
"main": { "code": 62, "label": ">" },
|
||||
"relevant": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 125, "label": "}" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"rtl": {
|
||||
"code": 40, "label": ")", "type": "numeric", "popup": {
|
||||
"main": { "code": 60, "label": ">" },
|
||||
"relevant": [
|
||||
{ "code": 91, "label": "]" },
|
||||
{ "code": 123, "label": "}" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"code": 48, "label": "0", "type": "numeric", "popup": {
|
||||
"main": { "code": 8304, "label": "⁰" },
|
||||
"relevant": [
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8319, "label": "ⁿ" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"$": "ime.extension.keyboard",
|
||||
"meta": {
|
||||
"id": "org.florisboard.localization",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"title": "Default subtype presets / popup mappings",
|
||||
"description": "Default subptye presets and popup mappings which are always available.",
|
||||
"description": "Default subtype presets and popup mappings which are always available.",
|
||||
"maintainers": [ "patrickgold <patrick@patrickgold.dev>" ],
|
||||
"license": "apache-2.0"
|
||||
},
|
||||
@@ -13,188 +13,66 @@
|
||||
"org.florisboard.currencysets",
|
||||
"org.florisboard.layouts"
|
||||
],
|
||||
"popupMappings": [
|
||||
"punctuationRules": [
|
||||
{
|
||||
"id": "default",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "ar",
|
||||
"authors": [ "HeiWiper" ]
|
||||
},
|
||||
{
|
||||
"id": "bg",
|
||||
"authors": [ "iorvethe" ]
|
||||
},
|
||||
{
|
||||
"id": "ca",
|
||||
"authors": [ "mikelloc" ]
|
||||
},
|
||||
{
|
||||
"id": "ckb",
|
||||
"authors": [ "GoRaN" ]
|
||||
},
|
||||
{
|
||||
"id": "cs",
|
||||
"authors": [ "stefan-misik" ]
|
||||
},
|
||||
{
|
||||
"id": "da",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "de",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "de-DE-neobone",
|
||||
"authors": [ "ostrya" ]
|
||||
},
|
||||
{
|
||||
"id": "el",
|
||||
"authors": [ "tsiflimagas" ]
|
||||
},
|
||||
{
|
||||
"id": "en",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "eo",
|
||||
"authors": [ "jeremiah-miller" ]
|
||||
},
|
||||
{
|
||||
"id": "es",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "fa",
|
||||
"authors": [ "PHELAT" ]
|
||||
},
|
||||
{
|
||||
"id": "fa2",
|
||||
"authors": [ "M-Koushan" ]
|
||||
},
|
||||
{
|
||||
"id": "fi",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "fo",
|
||||
"authors": [ "BinFlush" ]
|
||||
},
|
||||
{
|
||||
"id": "fr",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "hr",
|
||||
"authors": [ "hedidnothingwrong" ]
|
||||
},
|
||||
{
|
||||
"id": "hu",
|
||||
"authors": [ "zoli111, gabik65" ]
|
||||
},
|
||||
{
|
||||
"id": "hy",
|
||||
"authors": [ "PJTSearch" ]
|
||||
},
|
||||
{
|
||||
"id": "is",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "it",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "iw",
|
||||
"authors": [ "Antony" ]
|
||||
},
|
||||
{
|
||||
"id": "ja-JP-jis",
|
||||
"authors": [ "waelwindows" ]
|
||||
},
|
||||
{
|
||||
"id": "ko",
|
||||
"authors": [ "patrickgold", "Hayleia" ]
|
||||
},
|
||||
{
|
||||
"id": "ku",
|
||||
"authors": [ "GoRaN" ]
|
||||
},
|
||||
{
|
||||
"id": "lt",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "lv",
|
||||
"authors": [ "patrickgold", "eandersons" ]
|
||||
},
|
||||
{
|
||||
"id": "nb",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "nn",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "pl",
|
||||
"authors": [ "Mikołaj Biel" ]
|
||||
},
|
||||
{
|
||||
"id": "pt",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "pt-BR",
|
||||
"authors": [ "rickym7" ]
|
||||
},
|
||||
{
|
||||
"id": "ro",
|
||||
"authors": [ "bertin0" ]
|
||||
},
|
||||
{
|
||||
"id": "ru",
|
||||
"authors": [ "williamtheaker", "33kk" ]
|
||||
},
|
||||
{
|
||||
"id": "rue",
|
||||
"authors": [ "svvvst" ]
|
||||
},
|
||||
{
|
||||
"id": "sk",
|
||||
"authors": [ "stefan-misik", "majso" ]
|
||||
},
|
||||
{
|
||||
"id": "sr",
|
||||
"authors": [ "hedidnothingwrong", "GrbavaCigla" ]
|
||||
},
|
||||
{
|
||||
"id": "sv",
|
||||
"authors": [ "patrickgold" ]
|
||||
},
|
||||
{
|
||||
"id": "tr",
|
||||
"authors": [ "kisekinopureya", "patrickgold", "dvrnynr" ]
|
||||
},
|
||||
{
|
||||
"id": "uk",
|
||||
"authors": [ "williamtheaker", "33kk", "honsiorovskyi" ]
|
||||
},
|
||||
{
|
||||
"id": "uk-cyr-ext",
|
||||
"authors": [ "williamtheaker", "33kk", "honsiorovskyi" ]
|
||||
},
|
||||
{
|
||||
"id": "ur-PK",
|
||||
"authors": [ "mubashir-rehman", "mirfatif" ]
|
||||
},
|
||||
{
|
||||
"id": "vi-VN",
|
||||
"authors": [ "patrickgold", "Hayleia" ]
|
||||
"label": "Default",
|
||||
"symbolsPrecedingAutoSpace": ".,?‽!\"&%)]}»",
|
||||
"symbolsFollowingAutoSpace": "",
|
||||
"symbolsPrecedingPhantomSpace": ".,;:?‽!&%)]}»©®™",
|
||||
"symbolsFollowingPhantomSpace": "¿⸘¡([{",
|
||||
"symbolsTerminatingSentence": ".?‽!"
|
||||
}
|
||||
],
|
||||
"popupMappings": [
|
||||
{ "id": "default", "authors": [ "patrickgold" ] },
|
||||
{ "id": "ar", "authors": [ "HeiWiper" ] },
|
||||
{ "id": "bg", "authors": [ "iorvethe" ] },
|
||||
{ "id": "ca", "authors": [ "mikelloc" ] },
|
||||
{ "id": "ckb", "authors": [ "GoRaN" ] },
|
||||
{ "id": "cs", "authors": [ "stefan-misik" ] },
|
||||
{ "id": "da", "authors": [ "patrickgold" ] },
|
||||
{ "id": "de", "authors": [ "patrickgold" ] },
|
||||
{ "id": "de-DE-neobone", "authors": [ "ostrya" ] },
|
||||
{ "id": "el", "authors": [ "tsiflimagas" ] },
|
||||
{ "id": "en", "authors": [ "patrickgold" ] },
|
||||
{ "id": "eo", "authors": [ "jeremiah-miller" ] },
|
||||
{ "id": "es", "authors": [ "patrickgold" ] },
|
||||
{ "id": "fa", "authors": [ "PHELAT" ] },
|
||||
{ "id": "fa2", "authors": [ "M-Koushan" ] },
|
||||
{ "id": "fa3", "authors": [ "SaeID-Rz" ] },
|
||||
{ "id": "fi", "authors": [ "patrickgold" ] },
|
||||
{ "id": "fo", "authors": [ "BinFlush" ] },
|
||||
{ "id": "fr", "authors": [ "patrickgold" ] },
|
||||
{ "id": "hr", "authors": [ "hedidnothingwrong" ] },
|
||||
{ "id": "hu", "authors": [ "zoli111, gabik65" ] },
|
||||
{ "id": "hy", "authors": [ "PJTSearch" ] },
|
||||
{ "id": "id", "authors": [ "Linerly" ] },
|
||||
{ "id": "is", "authors": [ "patrickgold" ] },
|
||||
{ "id": "it", "authors": [ "patrickgold" ] },
|
||||
{ "id": "iw", "authors": [ "Antony" ] },
|
||||
{ "id": "ja-JP-jis", "authors": [ "waelwindows" ] },
|
||||
{ "id": "ko", "authors": [ "patrickgold", "Hayleia" ] },
|
||||
{ "id": "ku", "authors": [ "GoRaN" ] },
|
||||
{ "id": "lt", "authors": [ "patrickgold" ] },
|
||||
{ "id": "lv", "authors": [ "patrickgold", "eandersons" ] },
|
||||
{ "id": "nb", "authors": [ "patrickgold" ] },
|
||||
{ "id": "nn", "authors": [ "patrickgold" ] },
|
||||
{ "id": "pl", "authors": [ "Mikołaj Biel" ] },
|
||||
{ "id": "pt", "authors": [ "patrickgold" ] },
|
||||
{ "id": "pt-BR", "authors": [ "rickym7" ] },
|
||||
{ "id": "ro", "authors": [ "bertin0" ] },
|
||||
{ "id": "ru", "authors": [ "williamtheaker", "33kk" ] },
|
||||
{ "id": "rue", "authors": [ "svvvst" ] },
|
||||
{ "id": "sk", "authors": [ "stefan-misik", "majso" ] },
|
||||
{ "id": "sr", "authors": [ "hedidnothingwrong", "GrbavaCigla" ] },
|
||||
{ "id": "sv", "authors": [ "patrickgold" ] },
|
||||
{ "id": "tr", "authors": [ "kisekinopureya", "patrickgold", "dvrnynr" ] },
|
||||
{ "id": "uk", "authors": [ "williamtheaker", "33kk", "honsiorovskyi" ] },
|
||||
{ "id": "uk-cyr-ext", "authors": [ "williamtheaker", "33kk", "honsiorovskyi" ] },
|
||||
{ "id": "ur-PK", "authors": [ "mubashir-rehman", "mirfatif" ] },
|
||||
{ "id": "vi-VN", "authors": [ "patrickgold", "Hayleia" ] }
|
||||
],
|
||||
"subtypePresets": [
|
||||
{
|
||||
"languageTag": "en-US",
|
||||
@@ -405,6 +283,15 @@
|
||||
"characters": "org.florisboard.layouts:danish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "id",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
"currencySet": "org.florisboard.currencysets:dollar",
|
||||
"popupMapping": "org.florisboard.localization:id",
|
||||
"preferred": {
|
||||
"characters": "org.florisboard.layouts:qwerty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "is-IS",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
@@ -447,6 +334,18 @@
|
||||
"numericRow": "org.florisboard.layouts:persian"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "fa-FA",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
"currencySet": "org.florisboard.currencysets:rial",
|
||||
"popupMapping": "org.florisboard.localization:fa3",
|
||||
"preferred": {
|
||||
"characters": "org.florisboard.layouts:persian3",
|
||||
"symbols": "org.florisboard.layouts:persian",
|
||||
"symbols2": "org.florisboard.layouts:persian",
|
||||
"numericRow": "org.florisboard.layouts:western_arabic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languageTag": "ar",
|
||||
"composer": "org.florisboard.composers:appender",
|
||||
@@ -713,7 +612,7 @@
|
||||
},
|
||||
{
|
||||
"languageTag": "vi-VN",
|
||||
"composer": "org.florisboard.composers:basic-telex",
|
||||
"composer": "org.florisboard.composers:telex",
|
||||
"currencySet": "org.florisboard.currencysets:vietnamese_dong",
|
||||
"popupMapping": "org.florisboard.localization:vi-VN",
|
||||
"preferred": {
|
||||
|
||||
@@ -2,41 +2,74 @@
|
||||
"all": {
|
||||
"α": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 940, "label": "ά" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 940, "label": "ά" },
|
||||
"upper": { "code": 902, "label": "Ά" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"ε": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 941, "label": "έ" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 941, "label": "έ" },
|
||||
"upper": { "code": 904, "label": "Έ" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"η": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 942, "label": "ή" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 942, "label": "ή" },
|
||||
"upper": { "code": 905, "label": "Ή" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"ι": {
|
||||
"main": { "$": "auto_text_key", "code": 943, "label": "ί" },
|
||||
"main": { "$": "case_selector",
|
||||
"lower": { "code": 943, "label": "ί" },
|
||||
"upper": { "code": 906, "label": "Ί" }
|
||||
},
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 912, "label": "ΐ" },
|
||||
{ "$": "auto_text_key", "code": 970, "label": "ϊ" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 912, "label": "ΐ" },
|
||||
"upper": { "$": "multi_text_key", "codePoints": [921, 776, 769], "label": "Ϊ́" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 970, "label": "ϊ" },
|
||||
"upper": { "code": 938, "label": "Ϊ" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"ο": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 972, "label": "ό" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 972, "label": "ό" },
|
||||
"upper": { "code": 908, "label": "Ό" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"υ": {
|
||||
"main": { "$": "auto_text_key", "code": 973, "label": "ύ" },
|
||||
"main": { "$": "case_selector",
|
||||
"lower": { "code": 973, "label": "ύ" },
|
||||
"upper": { "code": 910, "label": "Ύ" }
|
||||
},
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 944, "label": "ΰ" },
|
||||
{ "$": "auto_text_key", "code": 971, "label": "ϋ" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 944, "label": "ΰ" },
|
||||
"upper": { "$": "multi_text_key", "codePoints": [933, 776, 769], "label": "Ϋ́" }
|
||||
},
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 971, "label": "ϋ" },
|
||||
"upper": { "code": 939, "label": "Ϋ" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"ω": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 974, "label": "ώ" }
|
||||
{ "$": "case_selector",
|
||||
"lower": { "code": 974, "label": "ώ" },
|
||||
"upper": { "code": 911, "label": "Ώ" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"all": {
|
||||
"ی": {
|
||||
"main": { "code": 1574, "label": "ئ" },
|
||||
"relevant": [
|
||||
{ "code": 1610, "label": "ي" },
|
||||
{ "code": 1746, "label": "ے" }
|
||||
]
|
||||
},
|
||||
"ا": {
|
||||
"main": { "code": 1570, "label": "آ" },
|
||||
"relevant": [
|
||||
{ "code": 1649, "label": "ٱ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 1571, "label": "أ" },
|
||||
{ "code": 1573, "label": "إ" }
|
||||
]
|
||||
},
|
||||
"ه": {
|
||||
"relevant": [
|
||||
{ "code": 1729, "label": "ہ" },
|
||||
{ "code": 1728, "label": "ۀ" },
|
||||
{ "code": 1726, "label": "ھ" }
|
||||
]
|
||||
},
|
||||
"ت": {
|
||||
"relevant": [
|
||||
{ "code": 1577, "label": "ة" }
|
||||
]
|
||||
},
|
||||
"ک": {
|
||||
"relevant": [
|
||||
{ "code": 1706, "label": "ڪ"}
|
||||
]
|
||||
},
|
||||
"ز": {
|
||||
"relevant": [
|
||||
{ "code": 1688, "label": "ژ" }
|
||||
]
|
||||
},
|
||||
"و": {
|
||||
"relevant": [
|
||||
{ "code": 1572, "label": "ؤ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 1611, "label": "ً" },
|
||||
"relevant": [
|
||||
{ "code": 1615, "label": "ُ" },
|
||||
{ "code": 1616, "label": "ِ" },
|
||||
{ "code": 1614, "label": "َ" },
|
||||
{ "code": 1617, "label": "ّ" },
|
||||
{ "code": 1620, "label": "ٔ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".ir"},
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@
|
||||
{ "$": "auto_text_key", "code": 337, "label": "ő" }
|
||||
]
|
||||
},
|
||||
"ö": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 337, "label": "ő" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 250, "label": "ú" },
|
||||
@@ -29,6 +34,11 @@
|
||||
{ "$": "auto_text_key", "code": 369, "label": "ű" }
|
||||
]
|
||||
},
|
||||
"ü": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 369, "label": "ű" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
@@ -58,13 +68,13 @@
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"main": { "code": -255, "label": ".hu" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".hu" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
{ "code": -255, "label": ".eu" },
|
||||
{ "code": -255, "label": ".gov.hu" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"all": {
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 234, "label": "ê" },
|
||||
{ "$": "auto_text_key", "code": 233, "label": "é" },
|
||||
{ "$": "auto_text_key", "code": 232, "label": "è" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "$": "layout_direction_selector",
|
||||
"ltr": { "code": 40, "label": "(" },
|
||||
"rtl": { "code": 41, "label": "(" }
|
||||
},
|
||||
{ "$": "layout_direction_selector",
|
||||
"ltr": { "code": 41, "label": ")" },
|
||||
"rtl": { "code": 40, "label": ")" }
|
||||
},
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".id" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" },
|
||||
{ "code": -255, "label": ".co.id"},
|
||||
{ "code": -255, "label": ".go.id"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 378, "label": "ź" },
|
||||
{ "$": "auto_text_key", "code": 380, "label": "ż" }
|
||||
{ "$": "auto_text_key", "code": 380, "label": "ż" },
|
||||
{ "$": "auto_text_key", "code": 378, "label": "ź" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"all": {
|
||||
"a": {
|
||||
"main": { "$": "auto_text_key", "code": 226, "label": "â" }
|
||||
},
|
||||
"c": {
|
||||
"main": { "$": "auto_text_key", "code": 231, "label": "ç" }
|
||||
},
|
||||
@@ -10,13 +13,19 @@
|
||||
"main": { "$": "case_selector",
|
||||
"lower": { "code": 305, "label": "ı" },
|
||||
"upper": { "code": 73, "label": "I" }
|
||||
}
|
||||
},
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 238, "label": "î" }
|
||||
]
|
||||
},
|
||||
"ı": {
|
||||
"main": { "$": "case_selector",
|
||||
"lower": { "code": 105, "label": "i" },
|
||||
"upper": { "code": 304, "label": "İ" }
|
||||
}
|
||||
},
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 238, "label": "î" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"main": { "$": "auto_text_key", "code": 246, "label": "ö" }
|
||||
@@ -25,7 +34,10 @@
|
||||
"main": { "$": "auto_text_key", "code": 351, "label": "ş" }
|
||||
},
|
||||
"u": {
|
||||
"main": { "$": "auto_text_key", "code": 252, "label": "ü" }
|
||||
"main": { "$": "auto_text_key", "code": 252, "label": "ü" },
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 251, "label": "û" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
[
|
||||
")",
|
||||
"}",
|
||||
"]",
|
||||
">",
|
||||
|
||||
"&",
|
||||
|
||||
".",
|
||||
",",
|
||||
"?",
|
||||
"!",
|
||||
";",
|
||||
":"
|
||||
]
|
||||
@@ -39,7 +39,7 @@
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][mode={m:capslock}]": {
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
@@ -64,36 +64,65 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"smartbar-primary-actions-toggle": {
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-secondary-actions-toggle": {
|
||||
"smartbar-extended-actions-toggle": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-quick-action": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-key": {
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-key:pressed": {
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-key:disabled": {
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
@@ -183,6 +212,10 @@
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#00000011"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#e8f5e9",
|
||||
"foreground": "#424242"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][mode={m:capslock}]": {
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
@@ -63,35 +63,65 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"smartbar-primary-actions-toggle": {
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "circle()"
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-secondary-actions-toggle": {
|
||||
"smartbar-extended-actions-toggle": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-quick-action": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-key": {
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-key:pressed": {
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-key:disabled": {
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#12121248"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
@@ -181,6 +211,10 @@
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#00000011"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#e8f5e9",
|
||||
"foreground": "#424242"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][mode={m:capslock}]": {
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
@@ -64,36 +64,65 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"smartbar-primary-actions-toggle": {
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-secondary-actions-toggle": {
|
||||
"smartbar-extended-actions-toggle": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-quick-action": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-key": {
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-key:pressed": {
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-key:disabled": {
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
@@ -183,6 +212,10 @@
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#1b5e20",
|
||||
"foreground": "#eeeeee"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][mode={m:capslock}]": {
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
@@ -63,35 +63,65 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"smartbar-primary-actions-toggle": {
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "circle()"
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-secondary-actions-toggle": {
|
||||
"smartbar-extended-actions-toggle": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-quick-action": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-key": {
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-key:pressed": {
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-key:disabled": {
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
@@ -181,6 +211,10 @@
|
||||
"foreground": "var(--primary)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#1b5e20",
|
||||
"foreground": "#eeeeee"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][mode={m:capslock}]": {
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
@@ -64,36 +64,65 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"smartbar-primary-actions-toggle": {
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-secondary-actions-toggle": {
|
||||
"smartbar-extended-actions-toggle": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-quick-action": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-key": {
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-key:pressed": {
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-key:disabled": {
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
@@ -183,6 +212,10 @@
|
||||
"foreground": "var(--primary-variant)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#000000",
|
||||
"foreground": "#eeeeee"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"background": "var(--primary-variant)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"key[code={c:shift}][mode={m:capslock}]": {
|
||||
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
|
||||
"foreground": "var(--secondary)"
|
||||
},
|
||||
"key[code={c:space}]": {
|
||||
@@ -63,36 +63,65 @@
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
|
||||
"smartbar-primary-actions-toggle": {
|
||||
"smartbar-shared-actions-toggle": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-secondary-actions-toggle": {
|
||||
"smartbar-extended-actions-toggle": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-surface-variant)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-quick-action": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"shape": "circle()"
|
||||
},
|
||||
"smartbar-key": {
|
||||
"smartbar-action-key": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-key:pressed": {
|
||||
"smartbar-action-key:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-key:disabled": {
|
||||
"smartbar-action-key:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-action-tile": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "18sp",
|
||||
"shape": "var(--shape)"
|
||||
},
|
||||
"smartbar-action-tile:pressed": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)"
|
||||
},
|
||||
"smartbar-action-tile:disabled": {
|
||||
"background": "transparent",
|
||||
"foreground": "#dcdcdc48"
|
||||
},
|
||||
"smartbar-actions-overflow-customize-button": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "14sp",
|
||||
"shape": "circle()",
|
||||
"shadow-elevation": "2dp"
|
||||
},
|
||||
"smartbar-actions-editor": {
|
||||
"background": "var(--background)",
|
||||
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
|
||||
},
|
||||
"smartbar-actions-editor-header": {
|
||||
"background": "var(--surface)",
|
||||
"foreground": "var(--on-surface)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-actions-editor-subheader": {
|
||||
"foreground": "var(--on-background)",
|
||||
"font-size": "16sp"
|
||||
},
|
||||
"smartbar-candidate-word": {
|
||||
"background": "transparent",
|
||||
"foreground": "var(--on-background)",
|
||||
@@ -182,6 +211,10 @@
|
||||
"foreground": "var(--primary-variant)"
|
||||
},
|
||||
|
||||
"incognito-mode-indicator": {
|
||||
"foreground": "#ffffff11"
|
||||
},
|
||||
|
||||
"one-handed-panel": {
|
||||
"background": "#000000",
|
||||
"foreground": "#eeeeee"
|
||||
|
||||
8108
app/src/main/baseline-prof.txt
Normal file
8108
app/src/main/baseline-prof.txt
Normal file
File diff suppressed because it is too large
Load Diff
14
app/src/main/config/libraries/google-material-icons.json
Normal file
14
app/src/main/config/libraries/google-material-icons.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"uniqueId": "google-material-icons",
|
||||
"name": "Google Material Icons",
|
||||
"developers": [
|
||||
{
|
||||
"name": "Google Inc.",
|
||||
"organisationUrl": "https://github.com/google"
|
||||
}
|
||||
],
|
||||
"website": "https://github.com/google/material-design-icons",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
]
|
||||
}
|
||||
14
app/src/main/config/libraries/icu4c.json
Normal file
14
app/src/main/config/libraries/icu4c.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"uniqueId": "icu4c",
|
||||
"name": "ICU4C Native C library",
|
||||
"developers": [
|
||||
{
|
||||
"name": "The Unicode Consortium",
|
||||
"organisationUrl": "https://github.com/unicode-org"
|
||||
}
|
||||
],
|
||||
"website": "https://github.com/unicode-org/icu",
|
||||
"licenses": [
|
||||
"icu4c"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"uniqueId": "pictogrammers-material-icons",
|
||||
"name": "Pictogrammers Material Icons",
|
||||
"developers": [
|
||||
{
|
||||
"name": "Pictogrammers Icon Group",
|
||||
"organisationUrl": "https://pictogrammers.com"
|
||||
}
|
||||
],
|
||||
"website": "https://github.com/Templarian/MaterialDesign",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
]
|
||||
}
|
||||
6
app/src/main/config/licenses/icu4c.json
Normal file
6
app/src/main/config/licenses/icu4c.json
Normal file
File diff suppressed because one or more lines are too long
@@ -18,17 +18,12 @@ add_library(ICU::uc STATIC IMPORTED)
|
||||
set_property(TARGET ICU::uc PROPERTY IMPORTED_LOCATION "${JNI_LIBS}/libicuuc.a")
|
||||
|
||||
### FlorisBoard ###
|
||||
add_subdirectory(nuspell)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(ime/nlp)
|
||||
add_subdirectory(ime/spelling)
|
||||
|
||||
add_library(
|
||||
florisboard-native
|
||||
SHARED
|
||||
dev_patrickgold_florisboard_FlorisApplication.cpp
|
||||
dev_patrickgold_florisboard_ime_nlp_SuggestionList.cpp
|
||||
dev_patrickgold_florisboard_ime_spelling_SpellingDict.cpp
|
||||
)
|
||||
|
||||
target_compile_options(florisboard-native PRIVATE -ffunction-sections -fdata-sections -fexceptions)
|
||||
@@ -41,8 +36,5 @@ target_link_libraries(
|
||||
log
|
||||
ICU::uc
|
||||
ICU::data
|
||||
Nuspell::nuspell
|
||||
utils
|
||||
ime-nlp
|
||||
ime-spelling
|
||||
)
|
||||
|
||||
@@ -25,9 +25,8 @@
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_dev_patrickgold_florisboard_FlorisApplication_00024Companion_nativeInitICUData(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jobject path) {
|
||||
JNIEnv *env, jobject thiz, jobject path)
|
||||
{
|
||||
auto path_str = utils::j2std_string(env, path);
|
||||
std::ifstream in_file(path_str, std::ios::in | std::ios::binary);
|
||||
if (!in_file) {
|
||||
@@ -42,6 +41,7 @@ Java_dev_patrickgold_florisboard_FlorisApplication_00024Companion_nativeInitICUD
|
||||
char *icu_data = new char[size + 1];
|
||||
in_file.read(icu_data, size);
|
||||
if (!in_file) {
|
||||
delete[] icu_data;
|
||||
in_file.close();
|
||||
return U_FILE_ACCESS_ERROR;
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include "ime/nlp/suggestion_list.h"
|
||||
|
||||
#pragma ide diagnostic ignored "UnusedLocalVariable"
|
||||
|
||||
using namespace ime::nlp;
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeInitialize(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jint max_size) {
|
||||
auto *suggestionList = new SuggestionList(max_size);
|
||||
return reinterpret_cast<jlong>(suggestionList);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeDispose(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
suggestionList->clear();
|
||||
delete suggestionList;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeAdd(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jstring word,
|
||||
jint freq) {
|
||||
const char *cWord = env->GetStringUTFChars(word, nullptr);
|
||||
word_t stdWord = word_t(cWord);
|
||||
env->ReleaseStringUTFChars(word, cWord);
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->add(std::move(stdWord), freq);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeClear(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
suggestionList->clear();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeContains(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jstring element) {
|
||||
const char *cWord = env->GetStringUTFChars(element, nullptr);
|
||||
const word_t stdWord = word_t(cWord);
|
||||
env->ReleaseStringUTFChars(element, cWord);
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->containsWord(stdWord);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeGetOrNull(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jint index) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
auto weightedToken = suggestionList->get(index);
|
||||
if (weightedToken == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return env->NewStringUTF(weightedToken->data.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeSize(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->size();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeGetIsPrimaryTokenAutoInsert(
|
||||
JNIEnv *env, jobject thiz, jlong native_ptr) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
return suggestionList->isPrimaryTokenAutoInsert;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_nlp_SuggestionList_00024Companion_nativeSetIsPrimaryTokenAutoInsert(
|
||||
JNIEnv *env, jobject thiz, jlong native_ptr, jboolean v) {
|
||||
auto *suggestionList = reinterpret_cast<SuggestionList *>(native_ptr);
|
||||
suggestionList->isPrimaryTokenAutoInsert = v;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <algorithm>
|
||||
#include "ime/spelling/spellingdict.h"
|
||||
#include "utils/jni_utils.h"
|
||||
|
||||
#pragma ide diagnostic ignored "UnusedLocalVariable"
|
||||
|
||||
using namespace ime::spellcheck;
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeInitialize(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jobject base_path) {
|
||||
auto strBasePath = utils::j2std_string(env, base_path);
|
||||
|
||||
auto *spellingDict = SpellingDict::load(strBasePath);
|
||||
|
||||
if (spellingDict == nullptr) {
|
||||
return 0L;
|
||||
} else {
|
||||
return reinterpret_cast<jlong>(spellingDict);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeDispose(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr) {
|
||||
auto spellingDict = reinterpret_cast<SpellingDict *>(native_ptr);
|
||||
|
||||
delete spellingDict;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeSpell(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jobject word) {
|
||||
auto strWord = utils::j2std_string(env, word);
|
||||
|
||||
auto spellingDict = reinterpret_cast<SpellingDict *>(native_ptr);
|
||||
auto result = spellingDict->spell(strWord);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_dev_patrickgold_florisboard_ime_spelling_SpellingDict_00024Companion_nativeSuggest(
|
||||
JNIEnv *env,
|
||||
jobject thiz,
|
||||
jlong native_ptr,
|
||||
jobject word,
|
||||
jint limit) {
|
||||
auto strWord = utils::j2std_string(env, word);
|
||||
|
||||
auto spellingDict = reinterpret_cast<SpellingDict *>(native_ptr);
|
||||
auto result = spellingDict->suggest(strWord);
|
||||
auto retSize = std::min(result.size(), (size_t)std::max(0, limit));
|
||||
|
||||
jclass jByteArrayClass = env->FindClass("java/nio/ByteBuffer");
|
||||
jobjectArray jSuggestions = env->NewObjectArray(retSize, jByteArrayClass, nullptr);
|
||||
for (int n = 0; n < retSize; n++) {
|
||||
env->SetObjectArrayElement(jSuggestions, n, utils::std2j_string(env, result[n]));
|
||||
}
|
||||
|
||||
return jSuggestions;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
add_library(
|
||||
# Name
|
||||
ime-nlp
|
||||
|
||||
# Headers
|
||||
nlp.h
|
||||
token.h
|
||||
suggestion_list.h
|
||||
|
||||
# Sources
|
||||
token.cpp
|
||||
suggestion_list.cpp
|
||||
)
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_NLP_H
|
||||
#define FLORISBOARD_NLP_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
typedef std::string word_t;
|
||||
typedef uint16_t freq_t;
|
||||
|
||||
static const freq_t FREQ_VALUE_MASK = 0xFF;
|
||||
static const freq_t FREQ_POSSIBLY_OFFENSIVE = 0x01;
|
||||
|
||||
} // namespace ime::nlp
|
||||
|
||||
#endif // FLORISBOARD_NLP_H
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "suggestion_list.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace ime::nlp;
|
||||
|
||||
SuggestionList::SuggestionList(size_t _maxSize) :
|
||||
maxSize(_maxSize), internalSize(0), tokens(_maxSize), isPrimaryTokenAutoInsert(false)
|
||||
{ }
|
||||
|
||||
SuggestionList::~SuggestionList() = default;
|
||||
|
||||
bool SuggestionList::add(word_t &&word, freq_t &&freq) {
|
||||
auto entryIndex = indexOfWord(word);
|
||||
if (entryIndex.has_value()) {
|
||||
// Word exists already
|
||||
auto entry = tokens[entryIndex.value()];
|
||||
if (entry.freq < freq) {
|
||||
// Need to update freq
|
||||
entry.freq = freq;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (internalSize < maxSize) {
|
||||
tokens[internalSize++] = WeightedToken(std::move(word), freq);
|
||||
} else {
|
||||
auto last = tokens[internalSize - 1];
|
||||
if (last.freq < freq) {
|
||||
tokens[internalSize - 1] = WeightedToken(std::move(word), freq);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(tokens.begin(), tokens.begin() + internalSize, std::greater<>());
|
||||
return true;
|
||||
}
|
||||
|
||||
void SuggestionList::clear() {
|
||||
internalSize = 0;
|
||||
isPrimaryTokenAutoInsert = false;
|
||||
}
|
||||
|
||||
bool SuggestionList::contains(const WeightedToken &element) const {
|
||||
return indexOf(element).has_value();
|
||||
}
|
||||
|
||||
bool SuggestionList::containsWord(const word_t &word) const {
|
||||
return indexOfWord(word).has_value();
|
||||
}
|
||||
|
||||
const WeightedToken *SuggestionList::get(size_t index) const {
|
||||
if (index < 0 || index >= internalSize) return nullptr;
|
||||
return &tokens[index];
|
||||
}
|
||||
|
||||
std::optional<size_t> SuggestionList::indexOf(const WeightedToken &element) const {
|
||||
for (size_t n = 0; n < internalSize; n++) {
|
||||
if (element == tokens[n]) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<size_t> SuggestionList::indexOfWord(const word_t &word) const {
|
||||
for (size_t n = 0; n < internalSize; n++) {
|
||||
if (word == tokens[n].data) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SuggestionList::isEmpty() const {
|
||||
return internalSize == 0;
|
||||
}
|
||||
|
||||
size_t SuggestionList::size() const {
|
||||
return internalSize;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_SUGGESTION_LIST_H
|
||||
#define FLORISBOARD_SUGGESTION_LIST_H
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "token.h"
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
class SuggestionList {
|
||||
public:
|
||||
SuggestionList(size_t _maxSize);
|
||||
~SuggestionList();
|
||||
|
||||
bool add(word_t &&word, freq_t &&freq);
|
||||
void clear();
|
||||
bool contains(const WeightedToken &element) const;
|
||||
bool containsWord(const word_t &word) const;
|
||||
const WeightedToken *get(size_t index) const;
|
||||
std::optional<size_t> indexOf(const WeightedToken &element) const;
|
||||
std::optional<size_t> indexOfWord(const word_t &word) const;
|
||||
bool isEmpty() const;
|
||||
size_t size() const;
|
||||
|
||||
bool isPrimaryTokenAutoInsert;
|
||||
|
||||
private:
|
||||
std::vector<WeightedToken> tokens;
|
||||
size_t internalSize;
|
||||
size_t maxSize;
|
||||
};
|
||||
|
||||
} // namespace ime::nlp
|
||||
|
||||
#endif // FLORISBOARD_SUGGESTION_LIST_H
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "token.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
Token::Token() : data() {}
|
||||
Token::Token(word_t &&_data) : data(std::move(_data)) {}
|
||||
|
||||
bool operator==(const Token &t1, const Token &t2) {
|
||||
return t1.data == t2.data;
|
||||
}
|
||||
|
||||
bool operator!=(const Token &t1, const Token &t2) {
|
||||
return !(t1 == t2);
|
||||
}
|
||||
|
||||
WeightedToken::WeightedToken() : Token(), freq(0) {}
|
||||
WeightedToken::WeightedToken(word_t &&_data, freq_t _freq) : Token(std::move(_data)), freq(_freq) {}
|
||||
|
||||
bool operator==(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.data == t2.data && t1.freq == t2.freq;
|
||||
}
|
||||
|
||||
bool operator!=(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return !(t1 == t2);
|
||||
}
|
||||
|
||||
bool operator<(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq < t2.freq;
|
||||
}
|
||||
|
||||
bool operator<=(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq <= t2.freq;
|
||||
}
|
||||
|
||||
bool operator>(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq > t2.freq;
|
||||
}
|
||||
|
||||
bool operator>=(const WeightedToken &t1, const WeightedToken &t2) {
|
||||
return t1.freq >= t2.freq;
|
||||
}
|
||||
|
||||
} // namespace ime::nlp
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_TOKEN_H
|
||||
#define FLORISBOARD_TOKEN_H
|
||||
|
||||
#include "nlp.h"
|
||||
#include <string>
|
||||
|
||||
namespace ime::nlp {
|
||||
|
||||
class Token {
|
||||
public:
|
||||
word_t data;
|
||||
Token();
|
||||
Token(word_t &&_data);
|
||||
|
||||
friend bool operator==(const Token &t1, const Token &t2);
|
||||
friend bool operator!=(const Token &t1, const Token &t2);
|
||||
};
|
||||
|
||||
class WeightedToken : public Token {
|
||||
public:
|
||||
freq_t freq;
|
||||
WeightedToken();
|
||||
WeightedToken(word_t &&_data, freq_t _freq);
|
||||
|
||||
friend bool operator==(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator!=(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator<(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator<=(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator>(const WeightedToken &t1, const WeightedToken &t2);
|
||||
friend bool operator>=(const WeightedToken &t1, const WeightedToken &t2);
|
||||
};
|
||||
|
||||
} // namespace ime::nlp
|
||||
|
||||
#endif // FLORISBOARD_TOKEN_H
|
||||
@@ -1,10 +0,0 @@
|
||||
add_library(
|
||||
# Name
|
||||
ime-spelling
|
||||
|
||||
# Headers
|
||||
spellingdict.h
|
||||
|
||||
# Sources
|
||||
spellingdict.cpp
|
||||
)
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "spellingdict.h"
|
||||
#include "utils/log.h"
|
||||
|
||||
using namespace ime::spellcheck;
|
||||
|
||||
SpellingDict::SpellingDict(const nuspell::Dictionary& dict) : dictionary(std::make_unique<nuspell::Dictionary>(dict))
|
||||
{ }
|
||||
|
||||
SpellingDict::~SpellingDict() = default;
|
||||
|
||||
SpellingDict* SpellingDict::load(const std::string &basePath) {
|
||||
utils::start_stdout_stderr_logger("spell-floris");
|
||||
try {
|
||||
auto temp = nuspell::Dictionary::load_from_path(basePath);
|
||||
auto spellingDict = new SpellingDict(temp);
|
||||
return spellingDict;
|
||||
} catch (const nuspell::Dictionary_Loading_Error& e) {
|
||||
utils::log_error("SpellingDict.load()", e.what());
|
||||
return nullptr;
|
||||
} catch (...) {
|
||||
utils::log_error("SpellingDict.load()", "An unknown error occurred!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpellingDict::spell(const std::string& word) {
|
||||
bool result = dictionary->spell(word);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> SpellingDict::suggest(const std::string &word) {
|
||||
auto result = std::vector<std::string>();
|
||||
dictionary->suggest(word, result);
|
||||
return result;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FLORISBOARD_SPELLINGDICT_H
|
||||
#define FLORISBOARD_SPELLINGDICT_H
|
||||
|
||||
#include "nuspell/dictionary.hxx"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ime::spellcheck {
|
||||
|
||||
class SpellingDict {
|
||||
public:
|
||||
SpellingDict(const nuspell::Dictionary& dict);
|
||||
~SpellingDict();
|
||||
|
||||
static SpellingDict* load(const std::string& basePath);
|
||||
|
||||
bool spell(const std::string& word);
|
||||
std::vector<std::string> suggest(const std::string& word);
|
||||
|
||||
private:
|
||||
std::unique_ptr<nuspell::Dictionary> dictionary;
|
||||
};
|
||||
|
||||
} // namespace ime::spellcheck
|
||||
|
||||
#endif // FLORISBOARD_SPELLINGDICT_H
|
||||
@@ -1,10 +0,0 @@
|
||||
add_library(nuspell
|
||||
aff_data.cxx aff_data.hxx
|
||||
checker.cxx checker.hxx
|
||||
suggester.cxx suggester.hxx
|
||||
dictionary.cxx dictionary.hxx
|
||||
unicode.hxx
|
||||
utils.cxx utils.hxx
|
||||
structures.hxx)
|
||||
|
||||
add_library(Nuspell::nuspell ALIAS nuspell)
|
||||
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,173 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NUSPELL_AFF_DATA_HXX
|
||||
#define NUSPELL_AFF_DATA_HXX
|
||||
|
||||
#include "nuspell_export.h"
|
||||
#include "structures.hxx"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <unicode/locid.h>
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
class Encoding {
|
||||
std::string name;
|
||||
|
||||
NUSPELL_EXPORT auto normalize_name() -> void;
|
||||
|
||||
public:
|
||||
enum Enc_Type { SINGLEBYTE = false, UTF8 = true };
|
||||
|
||||
Encoding() = default;
|
||||
explicit Encoding(const std::string& e) : name(e) { normalize_name(); }
|
||||
explicit Encoding(std::string&& e) : name(move(e)) { normalize_name(); }
|
||||
explicit Encoding(const char* e) : name(e) { normalize_name(); }
|
||||
auto& operator=(const std::string& e)
|
||||
{
|
||||
name = e;
|
||||
normalize_name();
|
||||
return *this;
|
||||
}
|
||||
auto& operator=(std::string&& e)
|
||||
{
|
||||
name = move(e);
|
||||
normalize_name();
|
||||
return *this;
|
||||
}
|
||||
auto& operator=(const char* e)
|
||||
{
|
||||
name = e;
|
||||
normalize_name();
|
||||
return *this;
|
||||
}
|
||||
auto empty() const { return name.empty(); }
|
||||
auto& value() const { return name; }
|
||||
auto is_utf8() const { return name == "UTF-8"; }
|
||||
auto value_or_default() const -> std::string
|
||||
{
|
||||
if (name.empty())
|
||||
return "ISO8859-1";
|
||||
else
|
||||
return name;
|
||||
}
|
||||
operator Enc_Type() const { return is_utf8() ? UTF8 : SINGLEBYTE; }
|
||||
};
|
||||
|
||||
enum class Flag_Type { SINGLE_CHAR, DOUBLE_CHAR, NUMBER, UTF8 };
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Map between words and word_flags.
|
||||
*
|
||||
* Flags are stored as part of the container. Maybe for the future flags should
|
||||
* be stored elsewhere (flag aliases) and this should store pointers.
|
||||
*
|
||||
* Does not store morphological data as is low priority feature and is out of
|
||||
* scope.
|
||||
*/
|
||||
using Word_List = Hash_Multimap<std::string, Flag_Set>;
|
||||
|
||||
struct Aff_Data {
|
||||
static constexpr auto HIDDEN_HOMONYM_FLAG = char16_t(-1);
|
||||
static constexpr auto MAX_SUGGESTIONS = size_t(16);
|
||||
|
||||
// spell checking options
|
||||
Word_List words;
|
||||
Prefix_Table prefixes;
|
||||
Suffix_Table suffixes;
|
||||
|
||||
bool complex_prefixes;
|
||||
bool fullstrip;
|
||||
bool checksharps;
|
||||
bool forbid_warn;
|
||||
char16_t compound_onlyin_flag;
|
||||
char16_t circumfix_flag;
|
||||
char16_t forbiddenword_flag;
|
||||
char16_t keepcase_flag;
|
||||
char16_t need_affix_flag;
|
||||
char16_t warn_flag;
|
||||
|
||||
// compounding options
|
||||
char16_t compound_flag;
|
||||
char16_t compound_begin_flag;
|
||||
char16_t compound_last_flag;
|
||||
char16_t compound_middle_flag;
|
||||
Compound_Rule_Table compound_rules;
|
||||
|
||||
// spell checking options
|
||||
Break_Table break_table;
|
||||
Substr_Replacer input_substr_replacer;
|
||||
std::string ignored_chars;
|
||||
icu::Locale icu_locale;
|
||||
Substr_Replacer output_substr_replacer;
|
||||
|
||||
// suggestion options
|
||||
Replacement_Table replacements;
|
||||
std::vector<Similarity_Group> similarities;
|
||||
std::string keyboard_closeness;
|
||||
std::string try_chars;
|
||||
// Phonetic_Table phonetic_table;
|
||||
|
||||
char16_t nosuggest_flag;
|
||||
char16_t substandard_flag;
|
||||
unsigned short max_compound_suggestions;
|
||||
unsigned short max_ngram_suggestions;
|
||||
unsigned short max_diff_factor;
|
||||
bool only_max_diff;
|
||||
bool no_split_suggestions;
|
||||
bool suggest_with_dots;
|
||||
|
||||
// compounding options
|
||||
unsigned short compound_min_length;
|
||||
unsigned short compound_max_word_count;
|
||||
char16_t compound_permit_flag;
|
||||
char16_t compound_forbid_flag;
|
||||
char16_t compound_root_flag;
|
||||
char16_t compound_force_uppercase;
|
||||
bool compound_more_suffixes;
|
||||
bool compound_check_duplicate;
|
||||
bool compound_check_rep;
|
||||
bool compound_check_case;
|
||||
bool compound_check_triple;
|
||||
bool compound_simplified_triple;
|
||||
bool compound_syllable_num;
|
||||
unsigned short compound_syllable_max;
|
||||
std::string compound_syllable_vowels;
|
||||
std::vector<Compound_Pattern> compound_patterns;
|
||||
|
||||
// data members used only while parsing
|
||||
Flag_Type flag_type;
|
||||
Encoding encoding;
|
||||
std::vector<Flag_Set> flag_aliases;
|
||||
std::string wordchars; // deprecated?
|
||||
|
||||
auto parse_aff(std::istream& in) -> bool;
|
||||
auto parse_dic(std::istream& in) -> bool;
|
||||
auto parse_aff_dic(std::istream& aff, std::istream& dic)
|
||||
{
|
||||
if (parse_aff(aff))
|
||||
return parse_dic(dic);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
#endif // NUSPELL_AFF_DATA_HXX
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,352 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NUSPELL_CHECKER_HXX
|
||||
#define NUSPELL_CHECKER_HXX
|
||||
|
||||
#include "aff_data.hxx"
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
enum Affixing_Mode {
|
||||
FULL_WORD,
|
||||
AT_COMPOUND_BEGIN,
|
||||
AT_COMPOUND_END,
|
||||
AT_COMPOUND_MIDDLE
|
||||
};
|
||||
|
||||
struct Affixing_Result_Base {
|
||||
Word_List::const_pointer root_word = {};
|
||||
|
||||
operator Word_List::const_pointer() const { return root_word; }
|
||||
auto& operator*() const { return *root_word; }
|
||||
auto operator->() const { return root_word; }
|
||||
};
|
||||
|
||||
template <class T1 = void, class T2 = void>
|
||||
struct Affixing_Result : Affixing_Result_Base {
|
||||
const T1* a = {};
|
||||
const T2* b = {};
|
||||
|
||||
Affixing_Result() = default;
|
||||
Affixing_Result(Word_List::const_reference r, const T1& a, const T2& b)
|
||||
: Affixing_Result_Base{&r}, a{&a}, b{&b}
|
||||
{
|
||||
}
|
||||
};
|
||||
template <class T1>
|
||||
struct Affixing_Result<T1, void> : Affixing_Result_Base {
|
||||
const T1* a = {};
|
||||
|
||||
Affixing_Result() = default;
|
||||
Affixing_Result(Word_List::const_reference r, const T1& a)
|
||||
: Affixing_Result_Base{&r}, a{&a}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Affixing_Result<void, void> : Affixing_Result_Base {
|
||||
Affixing_Result() = default;
|
||||
Affixing_Result(Word_List::const_reference r) : Affixing_Result_Base{&r}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct Compounding_Result {
|
||||
Word_List::const_pointer word_entry = {};
|
||||
unsigned char num_words_modifier = {};
|
||||
signed char num_syllable_modifier = {};
|
||||
bool affixed_and_modified = {}; /**< non-zero affix */
|
||||
operator Word_List::const_pointer() const { return word_entry; }
|
||||
auto& operator*() const { return *word_entry; }
|
||||
auto operator->() const { return word_entry; }
|
||||
};
|
||||
|
||||
struct Checker : public Aff_Data {
|
||||
enum Forceucase : bool {
|
||||
FORBID_BAD_FORCEUCASE = false,
|
||||
ALLOW_BAD_FORCEUCASE = true
|
||||
};
|
||||
|
||||
enum Hidden_Homonym : bool {
|
||||
ACCEPT_HIDDEN_HOMONYM = false,
|
||||
SKIP_HIDDEN_HOMONYM = true
|
||||
};
|
||||
Checker()
|
||||
: Aff_Data() // we explicity do value init so content is zeroed
|
||||
{
|
||||
}
|
||||
auto spell_priv(std::string& s) const -> bool;
|
||||
auto spell_break(std::string& s, size_t depth = 0) const -> bool;
|
||||
auto spell_casing(std::string& s) const -> const Flag_Set*;
|
||||
auto spell_casing_upper(std::string& s) const -> const Flag_Set*;
|
||||
auto spell_casing_title(std::string& s) const -> const Flag_Set*;
|
||||
auto spell_sharps(std::string& base, size_t n_pos = 0, size_t n = 0,
|
||||
size_t rep = 0) const -> const Flag_Set*;
|
||||
|
||||
auto check_word(std::string& s, Forceucase allow_bad_forceucase = {},
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> const Flag_Set*;
|
||||
auto check_simple_word(std::string& word,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> const Flag_Set*;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto affix_NOT_valid(const Prefix& a) const;
|
||||
template <Affixing_Mode m>
|
||||
auto affix_NOT_valid(const Suffix& a) const;
|
||||
template <Affixing_Mode m, class AffixT>
|
||||
auto outer_affix_NOT_valid(const AffixT& a) const;
|
||||
template <class AffixT>
|
||||
auto is_circumfix(const AffixT& a) const;
|
||||
template <Affixing_Mode m>
|
||||
auto is_valid_inside_compound(const Flag_Set& flags) const;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_prefix_only(std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Prefix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_suffix_only(std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Suffix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto
|
||||
strip_prefix_then_suffix(std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Suffix, Prefix>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_pfx_then_sfx_2(const Prefix& pe, std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<Suffix, Prefix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto
|
||||
strip_suffix_then_prefix(std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Prefix, Suffix>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_sfx_then_pfx_2(const Suffix& se, std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<Prefix, Suffix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_prefix_then_suffix_commutative(
|
||||
std::string& word, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Suffix, Prefix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_pfx_then_sfx_comm_2(const Prefix& pe, std::string& word,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<Suffix, Prefix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto
|
||||
strip_suffix_then_suffix(std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Suffix, Suffix>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_sfx_then_sfx_2(const Suffix& se1, std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<Suffix, Suffix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto
|
||||
strip_prefix_then_prefix(std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<Prefix, Prefix>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_pfx_then_pfx_2(const Prefix& pe1, std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<Prefix, Prefix>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_prefix_then_2_suffixes(
|
||||
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_pfx_2_sfx_3(const Prefix& pe1, const Suffix& se1,
|
||||
std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_suffix_prefix_suffix(
|
||||
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_s_p_s_3(const Suffix& se1, const Prefix& pe1,
|
||||
std::string& word,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_2_suffixes_then_prefix(
|
||||
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_2_sfx_pfx_3(const Suffix& se1, const Suffix& se2,
|
||||
std::string& word,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_suffix_then_2_prefixes(
|
||||
std::string& s, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_sfx_2_pfx_3(const Suffix& se1, const Prefix& pe1,
|
||||
std::string& s,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_prefix_suffix_prefix(
|
||||
std::string& word, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_p_s_p_3(const Prefix& pe1, const Suffix& se1,
|
||||
std::string& word,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m = FULL_WORD>
|
||||
auto strip_2_prefixes_then_suffix(
|
||||
std::string& word, Hidden_Homonym skip_hidden_homonym = {}) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto strip_2_pfx_sfx_3(const Prefix& pe1, const Prefix& pe2,
|
||||
std::string& word,
|
||||
Hidden_Homonym skip_hidden_homonym) const
|
||||
-> Affixing_Result<>;
|
||||
|
||||
auto check_compound(std::string& word,
|
||||
Forceucase allow_bad_forceucase) const
|
||||
-> Compounding_Result;
|
||||
|
||||
template <Affixing_Mode m = AT_COMPOUND_BEGIN>
|
||||
auto check_compound(std::string& word, size_t start_pos,
|
||||
size_t num_part, std::string& part,
|
||||
Forceucase allow_bad_forceucase) const
|
||||
-> Compounding_Result;
|
||||
|
||||
template <Affixing_Mode m = AT_COMPOUND_BEGIN>
|
||||
auto check_compound_classic(std::string& word, size_t start_pos,
|
||||
size_t i, size_t num_part,
|
||||
std::string& part,
|
||||
Forceucase allow_bad_forceucase) const
|
||||
-> Compounding_Result;
|
||||
|
||||
template <Affixing_Mode m = AT_COMPOUND_BEGIN>
|
||||
auto check_compound_with_pattern_replacements(
|
||||
std::string& word, size_t start_pos, size_t i, size_t num_part,
|
||||
std::string& part, Forceucase allow_bad_forceucase) const
|
||||
-> Compounding_Result;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto check_word_in_compound(std::string& s) const -> Compounding_Result;
|
||||
|
||||
auto calc_num_words_modifier(const Prefix& pfx) const -> unsigned char;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto calc_syllable_modifier(Word_List::const_reference we) const
|
||||
-> signed char;
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto calc_syllable_modifier(Word_List::const_reference we,
|
||||
const Suffix& sfx) const -> signed char;
|
||||
|
||||
auto count_syllables(std::string_view word) const -> size_t;
|
||||
|
||||
auto check_compound_with_rules(std::string& word,
|
||||
std::vector<const Flag_Set*>& words_data,
|
||||
size_t start_pos, std::string& part,
|
||||
Forceucase allow_bad_forceucase) const
|
||||
|
||||
-> Compounding_Result;
|
||||
auto is_rep_similar(std::string& word) const -> bool;
|
||||
};
|
||||
|
||||
template <Affixing_Mode m>
|
||||
auto Checker::affix_NOT_valid(const Prefix& e) const
|
||||
{
|
||||
if (m == FULL_WORD && e.cont_flags.contains(compound_onlyin_flag))
|
||||
return true;
|
||||
if (m == AT_COMPOUND_END &&
|
||||
!e.cont_flags.contains(compound_permit_flag))
|
||||
return true;
|
||||
if (m != FULL_WORD && e.cont_flags.contains(compound_forbid_flag))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
template <Affixing_Mode m>
|
||||
auto Checker::affix_NOT_valid(const Suffix& e) const
|
||||
{
|
||||
if (m == FULL_WORD && e.cont_flags.contains(compound_onlyin_flag))
|
||||
return true;
|
||||
if (m == AT_COMPOUND_BEGIN &&
|
||||
!e.cont_flags.contains(compound_permit_flag))
|
||||
return true;
|
||||
if (m != FULL_WORD && e.cont_flags.contains(compound_forbid_flag))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
template <Affixing_Mode m, class AffixT>
|
||||
auto Checker::outer_affix_NOT_valid(const AffixT& e) const
|
||||
{
|
||||
if (affix_NOT_valid<m>(e))
|
||||
return true;
|
||||
if (e.cont_flags.contains(need_affix_flag))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
template <class AffixT>
|
||||
auto Checker::is_circumfix(const AffixT& a) const
|
||||
{
|
||||
return a.cont_flags.contains(circumfix_flag);
|
||||
}
|
||||
|
||||
template <class AffixInner, class AffixOuter>
|
||||
auto cross_valid_inner_outer(const AffixInner& inner, const AffixOuter& outer)
|
||||
{
|
||||
return inner.cont_flags.contains(outer.flag);
|
||||
}
|
||||
|
||||
template <class Affix>
|
||||
auto cross_valid_inner_outer(const Flag_Set& word_flags, const Affix& afx)
|
||||
{
|
||||
return word_flags.contains(afx.flag);
|
||||
}
|
||||
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
#endif // NUSPELL_CHECKER_HXX
|
||||
@@ -1 +0,0 @@
|
||||
clang-format -style=file -i *.[ch]xx
|
||||
@@ -1,115 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "dictionary.hxx"
|
||||
#include "utils.hxx"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
Dictionary::Dictionary(std::istream& aff, std::istream& dic)
|
||||
{
|
||||
if (!parse_aff_dic(aff, dic))
|
||||
throw Dictionary_Loading_Error("error parsing");
|
||||
}
|
||||
|
||||
Dictionary::Dictionary() = default;
|
||||
|
||||
/**
|
||||
* @brief Create a dictionary from opened files as iostreams
|
||||
*
|
||||
* Prefer using load_from_path(). Use this if you have a specific use case,
|
||||
* like when .aff and .dic are in-memory buffers istringstream.
|
||||
*
|
||||
* @param aff The iostream of the .aff file
|
||||
* @param dic The iostream of the .dic file
|
||||
* @return Dictionary object
|
||||
* @throws Dictionary_Loading_Error on error
|
||||
*/
|
||||
auto Dictionary::load_from_aff_dic(std::istream& aff, std::istream& dic)
|
||||
-> Dictionary
|
||||
{
|
||||
return Dictionary(aff, dic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a dictionary from files
|
||||
* @param file_path_without_extension path *without* extensions (without .dic or
|
||||
* .aff)
|
||||
* @return Dictionary object
|
||||
* @throws Dictionary_Loading_Error on error
|
||||
*/
|
||||
auto Dictionary::load_from_path(const std::string& file_path_without_extension)
|
||||
-> Dictionary
|
||||
{
|
||||
auto path = file_path_without_extension;
|
||||
path += ".aff";
|
||||
std::ifstream aff_file(path);
|
||||
if (aff_file.fail()) {
|
||||
auto err = "Aff file " + path + " not found";
|
||||
throw Dictionary_Loading_Error(err);
|
||||
}
|
||||
path.replace(path.size() - 3, 3, "dic");
|
||||
std::ifstream dic_file(path);
|
||||
if (dic_file.fail()) {
|
||||
auto err = "Dic file " + path + " not found";
|
||||
throw Dictionary_Loading_Error(err);
|
||||
}
|
||||
return load_from_aff_dic(aff_file, dic_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a given word is correct
|
||||
* @param word any word
|
||||
* @return true if correct, false otherwise
|
||||
*/
|
||||
auto Dictionary::spell(std::string_view word) const -> bool
|
||||
{
|
||||
auto ok_enc = validate_utf8(word);
|
||||
if (unlikely(word.size() > 360))
|
||||
return false;
|
||||
if (unlikely(!ok_enc))
|
||||
return false;
|
||||
auto word_buf = string(word);
|
||||
return spell_priv(word_buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Suggests correct words for a given incorrect word
|
||||
* @param[in] word incorrect word
|
||||
* @param[out] out this object will be populated with the suggestions
|
||||
*/
|
||||
auto Dictionary::suggest(std::string_view word,
|
||||
std::vector<std::string>& out) const -> void
|
||||
{
|
||||
out.clear();
|
||||
auto ok_enc = validate_utf8(word);
|
||||
if (unlikely(word.size() > 360))
|
||||
return;
|
||||
if (unlikely(!ok_enc))
|
||||
return;
|
||||
suggest_priv(word, out);
|
||||
}
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
@@ -1,59 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Dictionary spelling.
|
||||
*/
|
||||
|
||||
#ifndef NUSPELL_DICTIONARY_HXX
|
||||
#define NUSPELL_DICTIONARY_HXX
|
||||
|
||||
#include "suggester.hxx"
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
/**
|
||||
* @brief The only important public exception
|
||||
*/
|
||||
class Dictionary_Loading_Error : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The only important public class
|
||||
*/
|
||||
class NUSPELL_EXPORT Dictionary : private Suggester {
|
||||
Dictionary(std::istream& aff, std::istream& dic);
|
||||
|
||||
public:
|
||||
Dictionary();
|
||||
auto static load_from_aff_dic(std::istream& aff, std::istream& dic)
|
||||
-> Dictionary;
|
||||
auto static load_from_path(
|
||||
const std::string& file_path_without_extension) -> Dictionary;
|
||||
auto spell(std::string_view word) const -> bool;
|
||||
auto suggest(std::string_view word, std::vector<std::string>& out) const
|
||||
-> void;
|
||||
};
|
||||
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
#endif // NUSPELL_DICTIONARY_HXX
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef NUSPELL_EXPORT_H
|
||||
#define NUSPELL_EXPORT_H
|
||||
|
||||
#ifdef NUSPELL_STATIC_DEFINE
|
||||
# define NUSPELL_EXPORT
|
||||
#elif defined(_WIN32) || defined(__CYGWIN__)
|
||||
# ifdef nuspell_EXPORTS // Define this only when building Nuspell as DLL on Windows, not when using the DLL.
|
||||
# define NUSPELL_EXPORT __declspec(dllexport)
|
||||
# else
|
||||
# define NUSPELL_EXPORT __declspec(dllimport)
|
||||
# endif
|
||||
#elif __GNUC__ >= 4
|
||||
# define NUSPELL_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
# define NUSPELL_EXPORT
|
||||
#endif
|
||||
|
||||
#endif /* NUSPELL_EXPORT_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NUSPELL_SUGGESTER_HXX
|
||||
#define NUSPELL_SUGGESTER_HXX
|
||||
|
||||
#include "checker.hxx"
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
struct NUSPELL_EXPORT Suggester : public Checker {
|
||||
|
||||
enum High_Quality_Sugs : bool {
|
||||
ALL_LOW_QUALITY_SUGS = false,
|
||||
HAS_HIGH_QUALITY_SUGS = true
|
||||
};
|
||||
|
||||
auto suggest_priv(std::string_view input_word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto suggest_low(std::string& word, List_Strings& out) const
|
||||
-> High_Quality_Sugs;
|
||||
|
||||
auto add_sug_if_correct(std::string& word, List_Strings& out) const
|
||||
-> bool;
|
||||
|
||||
auto uppercase_suggest(const std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto rep_suggest(std::string& word, List_Strings& out) const -> void;
|
||||
|
||||
auto try_rep_suggestion(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto max_attempts_for_long_alogs(std::string_view word) const -> size_t;
|
||||
|
||||
auto map_suggest(std::string& word, List_Strings& out) const -> void;
|
||||
|
||||
auto map_suggest(std::string& word, List_Strings& out, size_t i,
|
||||
size_t& remaining_attempts) const -> void;
|
||||
|
||||
auto adjacent_swap_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto distant_swap_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto keyboard_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto extra_char_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto forgotten_char_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto move_char_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto bad_char_suggest(std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto doubled_two_chars_suggest(std::string& word,
|
||||
List_Strings& out) const -> void;
|
||||
|
||||
auto two_words_suggest(const std::string& word, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto ngram_suggest(const std::string& word_u8, List_Strings& out) const
|
||||
-> void;
|
||||
|
||||
auto expand_root_word_for_ngram(Word_List::const_reference root,
|
||||
std::string_view wrong,
|
||||
List_Strings& expanded_list,
|
||||
std::vector<bool>& cross_affix) const
|
||||
-> void;
|
||||
};
|
||||
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
#endif // NUSPELL_SUGGESTER_HXX
|
||||
@@ -1,383 +0,0 @@
|
||||
/* Copyright 2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef NUSPELL_UNICODE_HXX
|
||||
#define NUSPELL_UNICODE_HXX
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unicode/utf16.h>
|
||||
#include <unicode/utf8.h>
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
// UTF-8, work on malformed
|
||||
|
||||
inline constexpr auto u8_max_cp_length = U8_MAX_LENGTH;
|
||||
|
||||
auto inline u8_is_cp_error(int32_t cp) -> bool { return cp < 0; }
|
||||
|
||||
template <class Range>
|
||||
auto u8_advance_cp(const Range& str, size_t& i, int32_t& cp) -> void
|
||||
{
|
||||
using std::size, std::data;
|
||||
#if U_ICU_VERSION_MAJOR_NUM <= 60
|
||||
auto s_ptr = data(str);
|
||||
int32_t idx = i;
|
||||
int32_t len = size(str);
|
||||
U8_NEXT(s_ptr, idx, len, cp);
|
||||
i = idx;
|
||||
#else
|
||||
auto len = size(str);
|
||||
U8_NEXT(str, i, len, cp);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u8_advance_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
using std::size;
|
||||
auto len = size(str);
|
||||
U8_FWD_1(str, i, len);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u8_reverse_cp(const Range& str, size_t& i, int32_t& cp) -> void
|
||||
{
|
||||
using std::size, std::data;
|
||||
auto ptr = data(str);
|
||||
int32_t idx = i;
|
||||
U8_PREV(ptr, 0, idx, cp);
|
||||
i = idx;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u8_reverse_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
using std::size, std::data;
|
||||
auto ptr = data(str);
|
||||
int32_t idx = i;
|
||||
U8_BACK_1(ptr, 0, idx);
|
||||
i = idx;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u8_write_cp_and_advance(Range& buf, size_t& i, int32_t cp, bool& error)
|
||||
-> void
|
||||
{
|
||||
using std::size, std::data;
|
||||
#if U_ICU_VERSION_MAJOR_NUM <= 60
|
||||
auto ptr = data(buf);
|
||||
int32_t idx = i;
|
||||
int32_t len = size(buf);
|
||||
U8_APPEND(buf, idx, len, cp, error);
|
||||
i = idx;
|
||||
#else
|
||||
auto len = size(buf);
|
||||
U8_APPEND(buf, i, len, cp, error);
|
||||
#endif
|
||||
}
|
||||
|
||||
// UTF-8, valid
|
||||
|
||||
template <class Range>
|
||||
auto valid_u8_advance_cp(const Range& str, size_t& i, char32_t& cp) -> void
|
||||
{
|
||||
U8_NEXT_UNSAFE(str, i, cp);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u8_advance_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
U8_FWD_1_UNSAFE(str, i);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u8_reverse_cp(const Range& str, size_t& i, char32_t& cp) -> void
|
||||
{
|
||||
U8_PREV_UNSAFE(str, i, cp);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u8_reverse_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
U8_BACK_1_UNSAFE(str, i);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u8_write_cp_and_advance(Range& buf, size_t& i, char32_t cp) -> void
|
||||
{
|
||||
U8_APPEND_UNSAFE(buf, i, cp);
|
||||
}
|
||||
|
||||
// UTF-16, work on malformed
|
||||
|
||||
inline constexpr auto u16_max_cp_length = U16_MAX_LENGTH;
|
||||
|
||||
auto inline u16_is_cp_error(int32_t cp) -> bool { return U_IS_SURROGATE(cp); }
|
||||
|
||||
template <class Range>
|
||||
auto u16_advance_cp(const Range& str, size_t& i, int32_t& cp) -> void
|
||||
{
|
||||
using std::size;
|
||||
auto len = size(str);
|
||||
U16_NEXT(str, i, len, cp);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u16_advance_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
using std::size;
|
||||
auto len = size(str);
|
||||
U16_FWD_1(str, i, len);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u16_reverse_cp(const Range& str, size_t& i, int32_t& cp) -> void
|
||||
{
|
||||
U16_PREV(str, 0, i, cp);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u16_reverse_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
U16_BACK_1(str, 0, i);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto u16_write_cp_and_advance(Range& buf, size_t& i, int32_t cp, bool& error)
|
||||
-> void
|
||||
{
|
||||
using std::size;
|
||||
auto len = size(buf);
|
||||
U16_APPEND(buf, i, len, cp, error);
|
||||
}
|
||||
|
||||
// UTF-16, valid
|
||||
|
||||
template <class Range>
|
||||
auto valid_u16_advance_cp(const Range& str, size_t& i, char32_t& cp) -> void
|
||||
{
|
||||
U16_NEXT_UNSAFE(str, i, cp);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u16_advance_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
U16_FWD_1_UNSAFE(str, i);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u16_reverse_cp(const Range& str, size_t& i, char32_t& cp) -> void
|
||||
{
|
||||
U16_PREV_UNSAFE(str, i, cp);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u16_reverse_index(const Range& str, size_t& i) -> void
|
||||
{
|
||||
U16_BACK_1_UNSAFE(str, i);
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
auto valid_u16_write_cp_and_advance(Range& buf, size_t& i, char32_t cp) -> void
|
||||
{
|
||||
U16_APPEND_UNSAFE(buf, i, cp);
|
||||
}
|
||||
|
||||
// higer level funcs
|
||||
|
||||
struct U8_CP_Pos {
|
||||
size_t begin_i = 0;
|
||||
size_t end_i = begin_i;
|
||||
};
|
||||
|
||||
class U8_Encoded_CP {
|
||||
char d[u8_max_cp_length];
|
||||
int sz;
|
||||
|
||||
public:
|
||||
explicit U8_Encoded_CP(std::string_view str, U8_CP_Pos pos)
|
||||
: sz(pos.end_i - pos.begin_i)
|
||||
{
|
||||
auto i = sz;
|
||||
auto j = pos.end_i;
|
||||
auto max_len = 4;
|
||||
do {
|
||||
d[--i] = str[--j];
|
||||
} while (i && --max_len);
|
||||
}
|
||||
U8_Encoded_CP(char32_t cp)
|
||||
{
|
||||
size_t z = 0;
|
||||
valid_u8_write_cp_and_advance(d, z, cp);
|
||||
sz = z;
|
||||
}
|
||||
auto size() const noexcept -> size_t { return sz; }
|
||||
auto data() const noexcept -> const char* { return d; }
|
||||
operator std::string_view() const noexcept
|
||||
{
|
||||
return std::string_view(data(), size());
|
||||
}
|
||||
auto copy_to(std::string& str, size_t j) const
|
||||
{
|
||||
auto i = sz;
|
||||
j += sz;
|
||||
auto max_len = 4;
|
||||
do {
|
||||
str[--j] = d[--i];
|
||||
} while (i && --max_len);
|
||||
}
|
||||
};
|
||||
|
||||
auto inline u8_swap_adjacent_cp(std::string& str, size_t i1, size_t i2,
|
||||
size_t i3) -> size_t
|
||||
{
|
||||
auto cp1 = U8_Encoded_CP(str, {i1, i2});
|
||||
auto cp2 = U8_Encoded_CP(str, {i2, i3});
|
||||
auto new_i2 = i1 + std::size(cp2);
|
||||
cp1.copy_to(str, new_i2);
|
||||
cp2.copy_to(str, i1);
|
||||
return new_i2;
|
||||
}
|
||||
|
||||
auto inline u8_swap_cp(std::string& str, U8_CP_Pos pos1, U8_CP_Pos pos2)
|
||||
-> std::pair<size_t, size_t>
|
||||
{
|
||||
using std::size;
|
||||
auto cp1 = U8_Encoded_CP(str, pos1);
|
||||
auto cp2 = U8_Encoded_CP(str, pos2);
|
||||
auto new_p1_end_i = pos1.begin_i + size(cp2);
|
||||
auto new_p2_begin_i = pos2.end_i - size(cp1);
|
||||
std::char_traits<char>::move(&str[new_p1_end_i], &str[pos1.end_i],
|
||||
pos2.begin_i - pos1.end_i);
|
||||
cp2.copy_to(str, pos1.begin_i);
|
||||
cp1.copy_to(str, new_p2_begin_i);
|
||||
return {new_p1_end_i, new_p2_begin_i};
|
||||
}
|
||||
|
||||
// bellow go func without out-parametars
|
||||
|
||||
// UTF-8, can be malformed, no out-parametars
|
||||
|
||||
struct Idx_And_Next_CP {
|
||||
size_t end_i;
|
||||
int32_t cp;
|
||||
};
|
||||
|
||||
struct Idx_And_Prev_CP {
|
||||
size_t begin_i;
|
||||
int32_t cp;
|
||||
};
|
||||
|
||||
struct Write_CP_Idx_and_Error {
|
||||
size_t end_i;
|
||||
bool error;
|
||||
};
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto u8_next_cp(const Range& str, size_t i) -> Idx_And_Next_CP
|
||||
{
|
||||
int32_t cp;
|
||||
u8_advance_cp(str, i, cp);
|
||||
return {i, cp};
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto u8_next_index(const Range& str, size_t i) -> size_t
|
||||
{
|
||||
u8_advance_index(str, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto u8_prev_cp(const Range& str, size_t i) -> Idx_And_Prev_CP
|
||||
{
|
||||
int32_t cp;
|
||||
u8_reverse_cp(str, i, cp);
|
||||
return {i, cp};
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto u8_prev_index(const Range& str, size_t i) -> size_t
|
||||
{
|
||||
u8_reverse_index(str, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto u8_write_cp(Range& buf, size_t i, int32_t cp)
|
||||
-> Write_CP_Idx_and_Error
|
||||
{
|
||||
bool err;
|
||||
u8_write_cp_and_advance(buf, i, cp, err);
|
||||
return {i, err};
|
||||
}
|
||||
|
||||
// UTF-8, valid, no out-parametars
|
||||
|
||||
struct Idx_And_Next_CP_Valid {
|
||||
size_t end_i;
|
||||
char32_t cp;
|
||||
};
|
||||
|
||||
struct Idx_And_Prev_CP_Valid {
|
||||
size_t begin_i;
|
||||
char32_t cp;
|
||||
};
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto valid_u8_next_cp(const Range& str, size_t i)
|
||||
-> Idx_And_Next_CP_Valid
|
||||
{
|
||||
char32_t cp;
|
||||
valid_u8_advance_cp(str, i, cp);
|
||||
return {i, cp};
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto valid_u8_next_index(const Range& str, size_t i) -> size_t
|
||||
{
|
||||
valid_u8_advance_index(str, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto valid_u8_prev_cp(const Range& str, size_t i)
|
||||
-> Idx_And_Prev_CP_Valid
|
||||
{
|
||||
char32_t cp;
|
||||
valid_u8_reverse_cp(str, i, cp);
|
||||
return {i, cp};
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto valid_u8_prev_index(const Range& str, size_t i) -> size_t
|
||||
{
|
||||
valid_u8_reverse_index(str, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
[[nodiscard]] auto valid_u8_write_cp(Range& buf, size_t i, int32_t cp) -> size_t
|
||||
{
|
||||
valid_u8_write_cp_and_advance(buf, i, cp);
|
||||
return i;
|
||||
}
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
#endif // NUSPELL_UNICODE_HXX
|
||||
@@ -1,465 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils.hxx"
|
||||
#include "unicode.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/ucnv.h>
|
||||
#include <unicode/unistr.h>
|
||||
#include <unicode/ustring.h>
|
||||
|
||||
#if ' ' != 32 || '.' != 46 || 'A' != 65 || 'Z' != 90 || 'a' != 97 || 'z' != 122
|
||||
#error "Basic execution character set is not ASCII"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
template <class SepT>
|
||||
static auto& split_on_any_of_low(std::string_view s, const SepT& sep,
|
||||
std::vector<std::string>& out)
|
||||
{
|
||||
size_t i1 = 0;
|
||||
size_t i2;
|
||||
do {
|
||||
i2 = s.find_first_of(sep, i1);
|
||||
out.emplace_back(s.substr(i1, i2 - i1));
|
||||
i1 = i2 + 1; // we can only add +1 if separator is single char.
|
||||
|
||||
// i2 gets s.npos after the last separator.
|
||||
// Length of i2-i1 will always go past the end. That is defined.
|
||||
} while (i2 != s.npos);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Splits string on single char seperator.
|
||||
*
|
||||
* Consecutive separators are treated as separate and will emit empty strings.
|
||||
*
|
||||
* @param s string to split.
|
||||
* @param sep char that acts as separator to split on.
|
||||
* @param out vector where separated strings are appended.
|
||||
* @return @p out.
|
||||
*/
|
||||
auto split(std::string_view s, char sep, std::vector<std::string>& out)
|
||||
-> std::vector<std::string>&
|
||||
{
|
||||
return split_on_any_of_low(s, sep, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Splits string on set of single char seperators.
|
||||
*
|
||||
* Consecutive separators are treated as separate and will emit empty strings.
|
||||
*
|
||||
* @param s string to split.
|
||||
* @param sep seperator(s) to split on.
|
||||
* @param out vector where separated strings are appended.
|
||||
* @return @p out.
|
||||
*/
|
||||
auto split_on_any_of(std::string_view s, const char* sep,
|
||||
std::vector<std::string>& out) -> std::vector<std::string>&
|
||||
{
|
||||
return split_on_any_of_low(s, sep, out);
|
||||
}
|
||||
|
||||
auto utf32_to_utf8(std::u32string_view in, std::string& out) -> void
|
||||
{
|
||||
out.clear();
|
||||
for (size_t i = 0; i != size(in); ++i) {
|
||||
auto cp = in[i];
|
||||
auto enc_cp = U8_Encoded_CP(cp);
|
||||
out += enc_cp;
|
||||
}
|
||||
}
|
||||
auto utf32_to_utf8(std::u32string_view in) -> std::string
|
||||
{
|
||||
auto out = string();
|
||||
utf32_to_utf8(in, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
auto valid_utf8_to_32(std::string_view in, std::u32string& out) -> void
|
||||
{
|
||||
out.clear();
|
||||
for (size_t i = 0; i != size(in);) {
|
||||
char32_t cp;
|
||||
valid_u8_advance_cp(in, i, cp);
|
||||
out.push_back(cp);
|
||||
}
|
||||
}
|
||||
auto valid_utf8_to_32(std::string_view in) -> std::u32string
|
||||
{
|
||||
auto out = u32string();
|
||||
valid_utf8_to_32(in, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
auto utf8_to_16(std::string_view in) -> std::u16string
|
||||
{
|
||||
auto out = u16string();
|
||||
utf8_to_16(in, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool utf8_to_16(std::string_view in, std::u16string& out)
|
||||
{
|
||||
int32_t len;
|
||||
auto err = U_ZERO_ERROR;
|
||||
u_strFromUTF8(data(out), size(out), &len, data(in), size(in), &err);
|
||||
out.resize(len);
|
||||
if (err == U_BUFFER_OVERFLOW_ERROR) {
|
||||
err = U_ZERO_ERROR;
|
||||
u_strFromUTF8(data(out), size(out), &len, data(in), size(in),
|
||||
&err);
|
||||
}
|
||||
if (U_SUCCESS(err))
|
||||
return true;
|
||||
out.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool validate_utf8(string_view s)
|
||||
{
|
||||
auto err = U_ZERO_ERROR;
|
||||
u_strFromUTF8(nullptr, 0, nullptr, data(s), size(s), &err);
|
||||
if (err == U_INVALID_CHAR_FOUND)
|
||||
return false;
|
||||
return err == U_BUFFER_OVERFLOW_ERROR || U_SUCCESS(err);
|
||||
}
|
||||
|
||||
auto static is_ascii(char c) -> bool
|
||||
{
|
||||
return static_cast<unsigned char>(c) <= 127;
|
||||
}
|
||||
|
||||
auto is_all_ascii(std::string_view s) -> bool
|
||||
{
|
||||
return all_of(begin(s), end(s), is_ascii);
|
||||
}
|
||||
|
||||
auto static widen_latin1(char c) -> char16_t
|
||||
{
|
||||
return static_cast<unsigned char>(c);
|
||||
}
|
||||
|
||||
auto latin1_to_ucs2(std::string_view s) -> std::u16string
|
||||
{
|
||||
u16string ret;
|
||||
latin1_to_ucs2(s, ret);
|
||||
return ret;
|
||||
}
|
||||
auto latin1_to_ucs2(std::string_view s, std::u16string& out) -> void
|
||||
{
|
||||
out.resize(s.size());
|
||||
transform(begin(s), end(s), begin(out), widen_latin1);
|
||||
}
|
||||
|
||||
auto static is_surrogate_pair(char16_t c) -> bool
|
||||
{
|
||||
return 0xD800 <= c && c <= 0xDFFF;
|
||||
}
|
||||
auto is_all_bmp(std::u16string_view s) -> bool
|
||||
{
|
||||
return none_of(begin(s), end(s), is_surrogate_pair);
|
||||
}
|
||||
|
||||
auto to_upper_ascii(std::string& s) -> void
|
||||
{
|
||||
auto& char_type = use_facet<ctype<char>>(locale::classic());
|
||||
char_type.toupper(begin_ptr(s), end_ptr(s));
|
||||
}
|
||||
|
||||
auto static utf32_to_icu(u32string_view in) -> icu::UnicodeString
|
||||
{
|
||||
static_assert(sizeof(UChar32) == sizeof(char32_t));
|
||||
return icu::UnicodeString::fromUTF32(
|
||||
reinterpret_cast<const UChar32*>(in.data()), in.size());
|
||||
}
|
||||
auto static icu_to_utf32(const icu::UnicodeString& in, std::u32string& out)
|
||||
-> bool
|
||||
{
|
||||
out.resize(in.length());
|
||||
auto err = U_ZERO_ERROR;
|
||||
auto len =
|
||||
in.toUTF32(reinterpret_cast<UChar32*>(out.data()), out.size(), err);
|
||||
if (U_SUCCESS(err)) {
|
||||
out.erase(len);
|
||||
return true;
|
||||
}
|
||||
out.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto to_upper(std::string_view in, const icu::Locale& loc) -> std::string
|
||||
{
|
||||
auto out = std::string();
|
||||
to_upper(in, loc, out);
|
||||
return out;
|
||||
}
|
||||
auto to_title(std::string_view in, const icu::Locale& loc) -> std::string
|
||||
{
|
||||
auto out = std::string();
|
||||
to_title(in, loc, out);
|
||||
return out;
|
||||
}
|
||||
auto to_lower(std::string_view in, const icu::Locale& loc) -> std::string
|
||||
{
|
||||
auto out = std::string();
|
||||
to_lower(in, loc, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
auto to_upper(string_view in, const icu::Locale& loc, string& out) -> void
|
||||
{
|
||||
auto sp = icu::StringPiece(data(in), size(in));
|
||||
auto us = icu::UnicodeString::fromUTF8(sp);
|
||||
us.toUpper(loc);
|
||||
out.clear();
|
||||
us.toUTF8String(out);
|
||||
}
|
||||
auto to_title(string_view in, const icu::Locale& loc, string& out) -> void
|
||||
{
|
||||
auto sp = icu::StringPiece(data(in), size(in));
|
||||
auto us = icu::UnicodeString::fromUTF8(sp);
|
||||
us.toTitle(nullptr, loc);
|
||||
out.clear();
|
||||
us.toUTF8String(out);
|
||||
}
|
||||
auto to_lower(u32string_view in, const icu::Locale& loc, u32string& out) -> void
|
||||
{
|
||||
auto us = utf32_to_icu(in);
|
||||
us.toLower(loc);
|
||||
icu_to_utf32(us, out);
|
||||
}
|
||||
auto to_lower(string_view in, const icu::Locale& loc, string& out) -> void
|
||||
{
|
||||
auto sp = icu::StringPiece(data(in), size(in));
|
||||
auto us = icu::UnicodeString::fromUTF8(sp);
|
||||
us.toLower(loc);
|
||||
out.clear();
|
||||
us.toUTF8String(out);
|
||||
}
|
||||
|
||||
auto to_lower_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void
|
||||
{
|
||||
auto cp = valid_u8_next_cp(s, i);
|
||||
auto us = icu::UnicodeString(UChar32(cp.cp));
|
||||
us.toLower(loc);
|
||||
auto u8_low = string();
|
||||
us.toUTF8String(u8_low);
|
||||
s.replace(i, cp.end_i - i, u8_low);
|
||||
}
|
||||
auto to_title_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void
|
||||
{
|
||||
auto cp = valid_u8_next_cp(s, i);
|
||||
auto us = icu::UnicodeString(UChar32(cp.cp));
|
||||
us.toTitle(nullptr, loc);
|
||||
auto u8_title = string();
|
||||
us.toUTF8String(u8_title);
|
||||
s.replace(i, cp.end_i - i, u8_title);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Determines casing (capitalization) type for a word.
|
||||
*
|
||||
* Casing is sometimes referred to as capitalization.
|
||||
*
|
||||
* @param s word.
|
||||
* @return The casing type.
|
||||
*/
|
||||
auto classify_casing(string_view s) -> Casing
|
||||
{
|
||||
size_t upper = 0;
|
||||
size_t lower = 0;
|
||||
for (size_t i = 0; i != size(s);) {
|
||||
char32_t c;
|
||||
valid_u8_advance_cp(s, i, c);
|
||||
if (u_isupper(c))
|
||||
upper++;
|
||||
else if (u_islower(c))
|
||||
lower++;
|
||||
// else neutral
|
||||
}
|
||||
if (upper == 0) // all lowercase, maybe with some neutral
|
||||
return Casing::SMALL; // most common case
|
||||
|
||||
auto first_cp = valid_u8_next_cp(s, 0);
|
||||
auto first_capital = u_isupper(first_cp.cp);
|
||||
if (first_capital && upper == 1)
|
||||
return Casing::INIT_CAPITAL; // second most common
|
||||
|
||||
if (lower == 0)
|
||||
return Casing::ALL_CAPITAL;
|
||||
|
||||
if (first_capital)
|
||||
return Casing::PASCAL;
|
||||
else
|
||||
return Casing::CAMEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Check if word[i] or word[i-1] are uppercase
|
||||
*
|
||||
* Check if the two chars are alphabetic and at least one of them is in
|
||||
* uppercase.
|
||||
*
|
||||
* @return true if at least one is uppercase, false otherwise.
|
||||
*/
|
||||
auto has_uppercase_at_compound_word_boundary(string_view word, size_t i) -> bool
|
||||
{
|
||||
auto cp = valid_u8_next_cp(word, i);
|
||||
auto cp_prev = valid_u8_prev_cp(word, i);
|
||||
if (u_isupper(cp.cp)) {
|
||||
if (u_isalpha(cp_prev.cp))
|
||||
return true;
|
||||
}
|
||||
else if (u_isupper(cp_prev.cp) && u_isalpha(cp.cp))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Encoding_Converter::Encoding_Converter(const char* enc)
|
||||
{
|
||||
auto err = UErrorCode();
|
||||
cnv = ucnv_open(enc, &err);
|
||||
}
|
||||
|
||||
Encoding_Converter::~Encoding_Converter()
|
||||
{
|
||||
if (cnv)
|
||||
ucnv_close(cnv);
|
||||
}
|
||||
|
||||
Encoding_Converter::Encoding_Converter(const Encoding_Converter& other)
|
||||
{
|
||||
auto err = UErrorCode();
|
||||
cnv = ucnv_safeClone(other.cnv, nullptr, nullptr, &err);
|
||||
}
|
||||
|
||||
auto Encoding_Converter::operator=(const Encoding_Converter& other)
|
||||
-> Encoding_Converter&
|
||||
{
|
||||
this->~Encoding_Converter();
|
||||
auto err = UErrorCode();
|
||||
cnv = ucnv_safeClone(other.cnv, nullptr, nullptr, &err);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto Encoding_Converter::to_utf8(string_view in, string& out) -> bool
|
||||
{
|
||||
if (ucnv_getType(cnv) == UCNV_UTF8) {
|
||||
if (validate_utf8(in)) {
|
||||
out = in;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
out.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto err = U_ZERO_ERROR;
|
||||
auto len = ucnv_toAlgorithmic(UCNV_UTF8, cnv, out.data(), out.size(),
|
||||
in.data(), in.size(), &err);
|
||||
out.resize(len);
|
||||
if (err == U_BUFFER_OVERFLOW_ERROR) {
|
||||
err = U_ZERO_ERROR;
|
||||
ucnv_toAlgorithmic(UCNV_UTF8, cnv, out.data(), out.size(),
|
||||
in.data(), in.size(), &err);
|
||||
}
|
||||
return U_SUCCESS(err);
|
||||
}
|
||||
|
||||
auto replace_ascii_char(string& s, char from, char to) -> void
|
||||
{
|
||||
for (auto i = s.find(from); i != s.npos; i = s.find(from, i + 1)) {
|
||||
s[i] = to;
|
||||
}
|
||||
}
|
||||
|
||||
auto erase_chars(string& s, string_view erase_chars) -> void
|
||||
{
|
||||
if (erase_chars.empty())
|
||||
return;
|
||||
for (size_t i = 0, next_i = 0; i != size(s); i = next_i) {
|
||||
valid_u8_advance_index(s, next_i);
|
||||
auto enc_cp = string_view(&s[i], next_i - i);
|
||||
if (erase_chars.find(enc_cp) != erase_chars.npos) {
|
||||
s.erase(i, next_i - i);
|
||||
next_i = i;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Tests if word is a number.
|
||||
*
|
||||
* Allow numbers with dot ".", dash "-" or comma "," inbetween the digits, but
|
||||
* forbids double separators such as "..", "--" and ".,".
|
||||
*/
|
||||
auto is_number(string_view s) -> bool
|
||||
{
|
||||
if (s.empty())
|
||||
return false;
|
||||
|
||||
auto it = begin(s);
|
||||
if (s[0] == '-')
|
||||
++it;
|
||||
while (it != end(s)) {
|
||||
auto next = std::find_if(
|
||||
it, end(s), [](auto c) { return c < '0' || c > '9'; });
|
||||
if (next == it)
|
||||
return false;
|
||||
if (next == end(s))
|
||||
return true;
|
||||
it = next;
|
||||
auto c = *it;
|
||||
if (c == '.' || c == ',' || c == '-')
|
||||
++it;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto count_appereances_of(string_view haystack, string_view needles) -> size_t
|
||||
{
|
||||
auto ret = size_t(0);
|
||||
for (size_t i = 0, next_i = 0; i != size(haystack); i = next_i) {
|
||||
valid_u8_advance_index(haystack, next_i);
|
||||
auto enc_cp = string_view(&haystack[i], next_i - i);
|
||||
ret += needles.find(enc_cp) != needles.npos;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
@@ -1,228 +0,0 @@
|
||||
/* Copyright 2016-2021 Dimitrij Mijoski
|
||||
*
|
||||
* This file is part of Nuspell.
|
||||
*
|
||||
* Nuspell is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Nuspell is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Nuspell. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NUSPELL_UTILS_HXX
|
||||
#define NUSPELL_UTILS_HXX
|
||||
|
||||
#include "nuspell_export.h"
|
||||
|
||||
#include <clocale>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#if !defined(_WIN32) && \
|
||||
(defined(__unix__) || defined(__unix) || \
|
||||
(defined(__APPLE__) && defined(__MACH__)) || defined(__HAIKU__))
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <unicode/locid.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define likely(expr) __builtin_expect(!!(expr), 1)
|
||||
#define unlikely(expr) __builtin_expect(!!(expr), 0)
|
||||
#else
|
||||
#define likely(expr) (expr)
|
||||
#define unlikely(expr) (expr)
|
||||
#endif
|
||||
|
||||
struct UConverter; // unicode/ucnv.h
|
||||
|
||||
namespace nuspell {
|
||||
inline namespace v5 {
|
||||
|
||||
auto split(std::string_view s, char sep, std::vector<std::string>& out)
|
||||
-> std::vector<std::string>&;
|
||||
NUSPELL_EXPORT auto split_on_any_of(std::string_view s, const char* sep,
|
||||
std::vector<std::string>& out)
|
||||
-> std::vector<std::string>&;
|
||||
|
||||
NUSPELL_EXPORT auto utf32_to_utf8(std::u32string_view in, std::string& out)
|
||||
-> void;
|
||||
NUSPELL_EXPORT auto utf32_to_utf8(std::u32string_view in) -> std::string;
|
||||
|
||||
auto valid_utf8_to_32(std::string_view in, std::u32string& out) -> void;
|
||||
auto valid_utf8_to_32(std::string_view in) -> std::u32string;
|
||||
|
||||
auto utf8_to_16(std::string_view in) -> std::u16string;
|
||||
auto utf8_to_16(std::string_view in, std::u16string& out) -> bool;
|
||||
|
||||
auto validate_utf8(std::string_view s) -> bool;
|
||||
|
||||
NUSPELL_EXPORT auto is_all_ascii(std::string_view s) -> bool;
|
||||
|
||||
NUSPELL_EXPORT auto latin1_to_ucs2(std::string_view s) -> std::u16string;
|
||||
auto latin1_to_ucs2(std::string_view s, std::u16string& out) -> void;
|
||||
|
||||
NUSPELL_EXPORT auto is_all_bmp(std::u16string_view s) -> bool;
|
||||
|
||||
auto to_upper_ascii(std::string& s) -> void;
|
||||
|
||||
[[nodiscard]] NUSPELL_EXPORT auto to_upper(std::string_view in,
|
||||
const icu::Locale& loc)
|
||||
-> std::string;
|
||||
[[nodiscard]] NUSPELL_EXPORT auto to_title(std::string_view in,
|
||||
const icu::Locale& loc)
|
||||
-> std::string;
|
||||
[[nodiscard]] NUSPELL_EXPORT auto to_lower(std::string_view in,
|
||||
const icu::Locale& loc)
|
||||
-> std::string;
|
||||
|
||||
auto to_upper(std::string_view in, const icu::Locale& loc, std::string& out)
|
||||
-> void;
|
||||
auto to_title(std::string_view in, const icu::Locale& loc, std::string& out)
|
||||
-> void;
|
||||
auto to_lower(std::u32string_view in, const icu::Locale& loc,
|
||||
std::u32string& out) -> void;
|
||||
auto to_lower(std::string_view in, const icu::Locale& loc, std::string& out)
|
||||
-> void;
|
||||
auto to_lower_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void;
|
||||
auto to_title_char_at(std::string& s, size_t i, const icu::Locale& loc) -> void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @brief Enum that identifies the casing type of a word.
|
||||
*
|
||||
* Neutral characters like numbers are ignored, so "abc" and "abc123abc" are
|
||||
* both classified as small.
|
||||
*/
|
||||
enum class Casing : char {
|
||||
SMALL,
|
||||
INIT_CAPITAL,
|
||||
ALL_CAPITAL,
|
||||
CAMEL /**< @internal camelCase i.e. mixed case with first small */,
|
||||
PASCAL /**< @internal PascalCase i.e. mixed case with first capital */
|
||||
};
|
||||
|
||||
NUSPELL_EXPORT auto classify_casing(std::string_view s) -> Casing;
|
||||
|
||||
auto has_uppercase_at_compound_word_boundary(std::string_view word, size_t i)
|
||||
-> bool;
|
||||
|
||||
class Encoding_Converter {
|
||||
UConverter* cnv = nullptr;
|
||||
|
||||
public:
|
||||
Encoding_Converter() = default;
|
||||
explicit Encoding_Converter(const char* enc);
|
||||
explicit Encoding_Converter(const std::string& enc)
|
||||
: Encoding_Converter(enc.c_str())
|
||||
{
|
||||
}
|
||||
~Encoding_Converter();
|
||||
Encoding_Converter(const Encoding_Converter& other);
|
||||
Encoding_Converter(Encoding_Converter&& other) noexcept
|
||||
{
|
||||
cnv = other.cnv;
|
||||
cnv = nullptr;
|
||||
}
|
||||
auto operator=(const Encoding_Converter& other) -> Encoding_Converter&;
|
||||
auto operator=(Encoding_Converter&& other) noexcept
|
||||
-> Encoding_Converter&
|
||||
{
|
||||
std::swap(cnv, other.cnv);
|
||||
return *this;
|
||||
}
|
||||
auto to_utf8(std::string_view in, std::string& out) -> bool;
|
||||
auto valid() -> bool { return cnv != nullptr; }
|
||||
};
|
||||
|
||||
//#if _POSIX_VERSION >= 200809L
|
||||
#if defined(_POSIX_VERSION) && !defined(__NetBSD__) && !defined(__HAIKU__)
|
||||
class Setlocale_To_C_In_Scope {
|
||||
locale_t old_loc = nullptr;
|
||||
|
||||
public:
|
||||
Setlocale_To_C_In_Scope()
|
||||
: old_loc{uselocale(newlocale(0, "C", nullptr))}
|
||||
{
|
||||
}
|
||||
~Setlocale_To_C_In_Scope()
|
||||
{
|
||||
auto new_loc = uselocale(old_loc);
|
||||
if (new_loc != old_loc)
|
||||
freelocale(new_loc);
|
||||
}
|
||||
Setlocale_To_C_In_Scope(const Setlocale_To_C_In_Scope&) = delete;
|
||||
};
|
||||
#else
|
||||
class Setlocale_To_C_In_Scope {
|
||||
std::string old_name;
|
||||
#ifdef _WIN32
|
||||
int old_per_thread;
|
||||
#endif
|
||||
public:
|
||||
Setlocale_To_C_In_Scope() : old_name(setlocale(LC_ALL, nullptr))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
old_per_thread = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||
#endif
|
||||
auto x = setlocale(LC_ALL, "C");
|
||||
if (!x)
|
||||
old_name.clear();
|
||||
}
|
||||
~Setlocale_To_C_In_Scope()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_configthreadlocale(old_per_thread);
|
||||
if (old_per_thread == _ENABLE_PER_THREAD_LOCALE)
|
||||
#endif
|
||||
{
|
||||
if (!old_name.empty())
|
||||
setlocale(LC_ALL, old_name.c_str());
|
||||
}
|
||||
}
|
||||
Setlocale_To_C_In_Scope(const Setlocale_To_C_In_Scope&) = delete;
|
||||
};
|
||||
#endif
|
||||
|
||||
auto replace_ascii_char(std::string& s, char from, char to) -> void;
|
||||
auto erase_chars(std::string& s, std::string_view erase_chars) -> void;
|
||||
NUSPELL_EXPORT auto is_number(std::string_view s) -> bool;
|
||||
auto count_appereances_of(std::string_view haystack, std::string_view needles)
|
||||
-> size_t;
|
||||
|
||||
auto inline begins_with(std::string_view haystack, std::string_view needle)
|
||||
-> bool
|
||||
{
|
||||
return haystack.compare(0, needle.size(), needle) == 0;
|
||||
}
|
||||
|
||||
auto inline ends_with(std::string_view haystack, std::string_view needle)
|
||||
-> bool
|
||||
{
|
||||
return haystack.size() >= needle.size() &&
|
||||
haystack.compare(haystack.size() - needle.size(), needle.size(),
|
||||
needle) == 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
auto begin_ptr(T& x)
|
||||
{
|
||||
return x.data();
|
||||
}
|
||||
template <class T>
|
||||
auto end_ptr(T& x)
|
||||
{
|
||||
return x.data() + x.size();
|
||||
}
|
||||
} // namespace v5
|
||||
} // namespace nuspell
|
||||
#endif // NUSPELL_UTILS_HXX
|
||||
@@ -21,12 +21,12 @@ std::string utils::j2std_string(JNIEnv *env, jobject jStr) {
|
||||
auto cStr = reinterpret_cast<const char *>(env->GetDirectBufferAddress(jStr));
|
||||
auto size = env->GetDirectBufferCapacity(jStr);
|
||||
std::string stdStr(cStr, size);
|
||||
log_debug("spell j2s", stdStr);
|
||||
utils::log(ANDROID_LOG_DEBUG, "spell j2s", stdStr);
|
||||
return stdStr;
|
||||
}
|
||||
|
||||
jobject utils::std2j_string(JNIEnv *env, const std::string& stdStr) {
|
||||
log_debug("spell s2j", stdStr);
|
||||
utils::log(ANDROID_LOG_DEBUG, "spell s2j", stdStr);
|
||||
size_t byteCount = stdStr.length();
|
||||
auto cStr = stdStr.c_str();
|
||||
auto buffer = env->NewDirectByteBuffer((void *) cStr, byteCount);
|
||||
|
||||
@@ -15,67 +15,61 @@
|
||||
*/
|
||||
|
||||
#include <android/log.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
void utils::log_debug(const std::string &tag, const std::string &msg) {
|
||||
__android_log_print(ANDROID_LOG_DEBUG, tag.c_str(), "%s", msg.c_str());
|
||||
}
|
||||
|
||||
void utils::log_info(const std::string &tag, const std::string &msg) {
|
||||
__android_log_print(ANDROID_LOG_INFO, tag.c_str(), "%s", msg.c_str());
|
||||
}
|
||||
|
||||
void utils::log_warning(const std::string &tag, const std::string &msg) {
|
||||
__android_log_print(ANDROID_LOG_WARN, tag.c_str(), "%s", msg.c_str());
|
||||
}
|
||||
|
||||
void utils::log_error(const std::string &tag, const std::string &msg) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, tag.c_str(), "%s", msg.c_str());
|
||||
}
|
||||
|
||||
void utils::log_wtf(const std::string &tag, const std::string &msg) {
|
||||
__android_log_print(ANDROID_LOG_FATAL, tag.c_str(), "%s", msg.c_str());
|
||||
void utils::log(int log_priority, const std::string &tag, const std::string &msg) {
|
||||
__android_log_print(log_priority, tag.c_str(), "%s", msg.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Code below taken from here:
|
||||
* Code below based on:
|
||||
* https://codelab.wordpress.com/2014/11/03/how-to-use-standard-output-streams-for-logging-in-android-apps/
|
||||
*/
|
||||
static int pfd[2];
|
||||
static pthread_t thr;
|
||||
static const char *tag = "myapp";
|
||||
static bool already_started = false;
|
||||
int utils::start_stdout_stderr_logger(const std::string &app_name) {
|
||||
static bool already_started = false;
|
||||
if (already_started)
|
||||
return 0;
|
||||
|
||||
static void *thread_func(void*) {
|
||||
ssize_t rdsz;
|
||||
char buf[2048];
|
||||
while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
|
||||
if (buf[rdsz - 1] == '\n') --rdsz;
|
||||
buf[rdsz] = 0; /* add null-terminator */
|
||||
__android_log_write(ANDROID_LOG_DEBUG, tag, buf);
|
||||
int piperw[2];
|
||||
if (pipe(piperw) < 0) {
|
||||
std::string msg = "pipe(): ";
|
||||
msg += strerror(errno);
|
||||
utils::log(ANDROID_LOG_ERROR, "stdout/stderr logger", std::ref(msg));
|
||||
return 1;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int utils::start_stdout_stderr_logger(const char *app_name) {
|
||||
if (already_started) return 0;
|
||||
already_started = true;
|
||||
tag = app_name;
|
||||
|
||||
/* make stdout line-buffered and stderr unbuffered */
|
||||
setvbuf(stdout, nullptr, _IOLBF, 0);
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
|
||||
/* create the pipe and redirect stdout and stderr */
|
||||
pipe(pfd);
|
||||
dup2(pfd[1], 1);
|
||||
dup2(pfd[1], 2);
|
||||
dup2(piperw[0], STDIN_FILENO);
|
||||
dup2(piperw[1], STDOUT_FILENO);
|
||||
dup2(piperw[1], STDERR_FILENO);
|
||||
close(piperw[0]);
|
||||
close(piperw[1]);
|
||||
|
||||
auto f = [](const std::string &tag) {
|
||||
std::string buf;
|
||||
while (std::getline(std::cin, buf)) {
|
||||
char &back = buf.back();
|
||||
if (back == '\n')
|
||||
back = '\0';
|
||||
utils::log(ANDROID_LOG_DEBUG, tag, std::ref(buf));
|
||||
}
|
||||
};
|
||||
|
||||
/* spawn the logging thread */
|
||||
if (pthread_create(&thr, nullptr, thread_func, nullptr) != 0) {
|
||||
return -1;
|
||||
}
|
||||
pthread_detach(thr);
|
||||
std::thread thr(f, app_name);
|
||||
thr.detach();
|
||||
|
||||
already_started = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,14 @@
|
||||
#ifndef FLORISBOARD_LOG_H
|
||||
#define FLORISBOARD_LOG_H
|
||||
|
||||
#include <android/log.h>
|
||||
#include <string>
|
||||
|
||||
namespace utils {
|
||||
|
||||
void log_debug(const std::string& tag, const std::string& msg);
|
||||
void log_info(const std::string& tag, const std::string& msg);
|
||||
void log_warning(const std::string& tag, const std::string& msg);
|
||||
void log_error(const std::string& tag, const std::string& msg);
|
||||
void log_wtf(const std::string& tag, const std::string& msg);
|
||||
void log(int log_priority, const std::string &tag, const std::string &msg);
|
||||
|
||||
int start_stdout_stderr_logger(const char *app_name);
|
||||
int start_stdout_stderr_logger(const std::string &app_name);
|
||||
|
||||
} // namespace utils
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ package dev.patrickgold.florisboard
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Handler
|
||||
import androidx.core.os.UserManagerCompat
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
|
||||
@@ -28,9 +30,8 @@ import dev.patrickgold.florisboard.ime.core.SubtypeManager
|
||||
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorInstance
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardManager
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.FlorisEmojiCompat
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpManager
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingManager
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingService
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.lib.NativeStr
|
||||
@@ -43,9 +44,17 @@ import dev.patrickgold.florisboard.lib.devtools.flogInfo
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
|
||||
import dev.patrickgold.florisboard.lib.io.AssetManager
|
||||
import dev.patrickgold.florisboard.lib.io.deleteContentsRecursively
|
||||
import dev.patrickgold.florisboard.lib.io.subFile
|
||||
import dev.patrickgold.florisboard.lib.kotlin.tryOrNull
|
||||
import dev.patrickgold.florisboard.lib.toNativeStr
|
||||
import dev.patrickgold.jetpref.datastore.JetPref
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Global weak reference for the [FlorisApplication] class. This is needed as in certain scenarios an application
|
||||
* reference is needed, but the Android framework hasn't finished setting up
|
||||
*/
|
||||
private var FlorisApplicationReference = WeakReference<FlorisApplication?>(null)
|
||||
|
||||
@Suppress("unused")
|
||||
class FlorisApplication : Application() {
|
||||
@@ -63,6 +72,7 @@ class FlorisApplication : Application() {
|
||||
}
|
||||
|
||||
private val prefs by florisPreferenceModel()
|
||||
private val mainHandler by lazy { Handler(mainLooper) }
|
||||
|
||||
val assetManager = lazy { AssetManager(this) }
|
||||
val cacheManager = lazy { CacheManager(this) }
|
||||
@@ -72,13 +82,12 @@ class FlorisApplication : Application() {
|
||||
val glideTypingManager = lazy { GlideTypingManager(this) }
|
||||
val keyboardManager = lazy { KeyboardManager(this) }
|
||||
val nlpManager = lazy { NlpManager(this) }
|
||||
val spellingManager = lazy { SpellingManager(this) }
|
||||
val spellingService = lazy { SpellingService(this) }
|
||||
val subtypeManager = lazy { SubtypeManager(this) }
|
||||
val themeManager = lazy { ThemeManager(this) }
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
FlorisApplicationReference = WeakReference(this)
|
||||
try {
|
||||
JetPref.configure(saveIntervalMs = 500)
|
||||
Flog.install(
|
||||
@@ -89,30 +98,35 @@ class FlorisApplication : Application() {
|
||||
flogOutputs = Flog.OUTPUT_CONSOLE,
|
||||
)
|
||||
CrashUtility.install(this)
|
||||
FlorisEmojiCompat.init(this)
|
||||
|
||||
if (!UserManagerCompat.isUserUnlocked(this)) {
|
||||
val context = createDeviceProtectedStorageContext()
|
||||
initICU(context)
|
||||
prefs.initializeBlocking(context)
|
||||
registerReceiver(BootComplete(), IntentFilter(Intent.ACTION_USER_UNLOCKED))
|
||||
} else {
|
||||
initICU(this)
|
||||
cacheDir?.deleteContentsRecursively()
|
||||
prefs.initializeBlocking(this)
|
||||
clipboardManager.value.initializeForContext(this)
|
||||
extensionManager.value.init()
|
||||
registerReceiver(BootComplete(), IntentFilter(Intent.ACTION_USER_UNLOCKED))
|
||||
return
|
||||
}
|
||||
|
||||
DictionaryManager.init(this)
|
||||
init()
|
||||
} catch (e: Exception) {
|
||||
CrashUtility.stageException(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun init() {
|
||||
initICU(this)
|
||||
cacheDir?.deleteContentsRecursively()
|
||||
prefs.initializeBlocking(this)
|
||||
extensionManager.value.init()
|
||||
clipboardManager.value.initializeForContext(this)
|
||||
DictionaryManager.init(this)
|
||||
}
|
||||
|
||||
fun initICU(context: Context): Boolean {
|
||||
try {
|
||||
val androidAssetManager = context.assets ?: return false
|
||||
val icuTmpDataFile = File(context.cacheDir, "icudt.dat")
|
||||
val icuTmpDataFile = context.cacheDir.subFile("icudt.dat")
|
||||
icuTmpDataFile.outputStream().use { os ->
|
||||
androidAssetManager.open(ICU_DATA_ASSET_PATH).use { it.copyTo(os) }
|
||||
}
|
||||
@@ -140,43 +154,41 @@ class FlorisApplication : Application() {
|
||||
} catch (e: Exception) {
|
||||
flogError { e.toString() }
|
||||
}
|
||||
cacheDir?.deleteContentsRecursively()
|
||||
prefs.initializeBlocking(this@FlorisApplication)
|
||||
clipboardManager.value.initializeForContext(this@FlorisApplication)
|
||||
mainHandler.post { init() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.florisApplication(): FlorisApplication {
|
||||
private tailrec fun Context.florisApplication(): FlorisApplication {
|
||||
return when (this) {
|
||||
is FlorisApplication -> this
|
||||
else -> this.applicationContext as FlorisApplication
|
||||
is ContextWrapper -> when {
|
||||
this.baseContext != null -> this.baseContext.florisApplication()
|
||||
else -> FlorisApplicationReference.get()!!
|
||||
}
|
||||
else -> tryOrNull { this.applicationContext as FlorisApplication } ?: FlorisApplicationReference.get()!!
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.appContext() = lazy { this.florisApplication() }
|
||||
fun Context.appContext() = lazyOf(this.florisApplication())
|
||||
|
||||
fun Context.assetManager() = lazy { this.florisApplication().assetManager.value }
|
||||
fun Context.assetManager() = this.florisApplication().assetManager
|
||||
|
||||
fun Context.cacheManager() = lazy { this.florisApplication().cacheManager.value }
|
||||
fun Context.cacheManager() = this.florisApplication().cacheManager
|
||||
|
||||
fun Context.clipboardManager() = lazy { this.florisApplication().clipboardManager.value }
|
||||
fun Context.clipboardManager() = this.florisApplication().clipboardManager
|
||||
|
||||
fun Context.editorInstance() = lazy { this.florisApplication().editorInstance.value }
|
||||
fun Context.editorInstance() = this.florisApplication().editorInstance
|
||||
|
||||
fun Context.extensionManager() = lazy { this.florisApplication().extensionManager.value }
|
||||
fun Context.extensionManager() = this.florisApplication().extensionManager
|
||||
|
||||
fun Context.glideTypingManager() = lazy { this.florisApplication().glideTypingManager.value }
|
||||
fun Context.glideTypingManager() = this.florisApplication().glideTypingManager
|
||||
|
||||
fun Context.keyboardManager() = lazy { this.florisApplication().keyboardManager.value }
|
||||
fun Context.keyboardManager() = this.florisApplication().keyboardManager
|
||||
|
||||
fun Context.nlpManager() = lazy { this.florisApplication().nlpManager.value }
|
||||
fun Context.nlpManager() = this.florisApplication().nlpManager
|
||||
|
||||
fun Context.spellingManager() = lazy { this.florisApplication().spellingManager.value }
|
||||
fun Context.subtypeManager() = this.florisApplication().subtypeManager
|
||||
|
||||
fun Context.spellingService() = lazy { this.florisApplication().spellingService.value }
|
||||
|
||||
fun Context.subtypeManager() = lazy { this.florisApplication().subtypeManager.value }
|
||||
|
||||
fun Context.themeManager() = lazy { this.florisApplication().themeManager.value }
|
||||
fun Context.themeManager() = this.florisApplication().themeManager
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InlineSuggestionsRequest
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.view.inputmethod.InputMethodInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
@@ -69,24 +70,28 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dev.patrickgold.florisboard.app.FlorisAppActivity
|
||||
import dev.patrickgold.florisboard.app.devtools.DevtoolsOverlay
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.ImeUiMode
|
||||
import dev.patrickgold.florisboard.ime.clipboard.ClipboardInputLayout
|
||||
import dev.patrickgold.florisboard.ime.sheet.BottomSheetHostUi
|
||||
import dev.patrickgold.florisboard.ime.sheet.isBottomSheetShowing
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorRange
|
||||
import dev.patrickgold.florisboard.ime.editor.FlorisEditorInfo
|
||||
import dev.patrickgold.florisboard.ime.input.InputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.keyboard.InputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.LocalInputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.keyboard.ProvideKeyboardRowBaseHeight
|
||||
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
|
||||
import dev.patrickgold.florisboard.ime.lifecycle.LifecycleInputMethodService
|
||||
import dev.patrickgold.florisboard.ime.media.MediaInputLayout
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedPanel
|
||||
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputLayout
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.SecondaryRowPlacement
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.lib.android.AndroidInternalR
|
||||
@@ -95,6 +100,7 @@ import dev.patrickgold.florisboard.lib.android.isOrientationLandscape
|
||||
import dev.patrickgold.florisboard.lib.android.isOrientationPortrait
|
||||
import dev.patrickgold.florisboard.lib.android.launchActivity
|
||||
import dev.patrickgold.florisboard.lib.android.setLocale
|
||||
import dev.patrickgold.florisboard.lib.android.showShortToast
|
||||
import dev.patrickgold.florisboard.lib.android.systemServiceOrNull
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButton
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
@@ -103,6 +109,7 @@ import dev.patrickgold.florisboard.lib.devtools.LogTopic
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogInfo
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogWarning
|
||||
import dev.patrickgold.florisboard.lib.kotlin.collectLatestIn
|
||||
import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
import dev.patrickgold.florisboard.lib.snygg.ui.SnyggSurface
|
||||
import dev.patrickgold.florisboard.lib.snygg.ui.shape
|
||||
@@ -215,6 +222,29 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun switchToVoiceInputMethod(): Boolean {
|
||||
val ims = FlorisImeServiceReference.get() ?: return false
|
||||
val imm = ims.systemServiceOrNull(InputMethodManager::class) ?: return false
|
||||
val list: List<InputMethodInfo> = imm.enabledInputMethodList
|
||||
for (el in list) {
|
||||
for (i in 0 until el.subtypeCount){
|
||||
if (el.getSubtypeAt(i).mode != "voice") continue
|
||||
if (AndroidVersion.ATLEAST_API28_P) {
|
||||
ims.switchInputMethod(el.id)
|
||||
return true
|
||||
} else {
|
||||
ims.window.window?.let { window ->
|
||||
@Suppress("DEPRECATION")
|
||||
imm.setInputMethod(window.attributes.token, el.id)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ims.showShortToast("Failed to find voice IME, do you have one installed?")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private val prefs by florisPreferenceModel()
|
||||
@@ -240,7 +270,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
FlorisImeServiceReference = WeakReference(this)
|
||||
subtypeManager.activeSubtype.observe(this) { subtype ->
|
||||
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
|
||||
val config = Configuration(resources.configuration)
|
||||
config.setLocale(subtype.primaryLocale)
|
||||
resourcesContext = createConfigurationContext(config)
|
||||
@@ -299,8 +329,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
activeState.batchEdit {
|
||||
activeState.imeUiMode = ImeUiMode.TEXT
|
||||
activeState.isSelectionMode = editorInfo.initialSelection.isSelectionMode
|
||||
editorInstance.handleStartInputView(editorInfo)
|
||||
keyboardManager.updateCapsState()
|
||||
editorInstance.handleStartInputView(editorInfo, isRestart = restarting)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +350,6 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
newSelection = EditorRange.normalized(newSelStart, newSelEnd),
|
||||
composing = EditorRange.normalized(candidatesStart, candidatesEnd),
|
||||
)
|
||||
keyboardManager.updateCapsState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,18 +366,6 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
nlpManager.clearInlineSuggestions()
|
||||
}
|
||||
|
||||
//override fun onWordHistoryChanged(
|
||||
// currentWord: EditorInstance.Region?,
|
||||
// wordsBeforeCurrent: List<EditorInstance.Region>,
|
||||
// wordsAfterCurrent: List<EditorInstance.Region>,
|
||||
//) {
|
||||
// if (currentWord == null || !currentWord.isValid || !activeState.isComposingEnabled) {
|
||||
// nlpManager.clearSuggestions()
|
||||
// return
|
||||
// }
|
||||
// nlpManager.suggest(currentWord.text, listOf())
|
||||
//}
|
||||
|
||||
override fun onWindowShown() {
|
||||
super.onWindowShown()
|
||||
if (isWindowShown) {
|
||||
@@ -360,6 +376,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
isWindowShown = true
|
||||
themeManager.updateActiveTheme()
|
||||
inputFeedbackController.updateSystemPrefsState()
|
||||
}
|
||||
|
||||
override fun onWindowHidden() {
|
||||
@@ -371,6 +388,10 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
flogInfo(LogTopic.IMS_EVENTS)
|
||||
}
|
||||
isWindowShown = false
|
||||
activeState.batchEdit {
|
||||
activeState.isActionsOverflowVisible = false
|
||||
activeState.isActionsEditorVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEvaluateFullscreenMode(): Boolean {
|
||||
@@ -389,7 +410,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
override fun onUpdateExtractingVisibility(info: EditorInfo?) {
|
||||
if (info != null) {
|
||||
editorInstance.handleStartInputView(FlorisEditorInfo.wrap(info))
|
||||
editorInstance.handleStartInputView(FlorisEditorInfo.wrap(info), isRestart = true)
|
||||
}
|
||||
when (prefs.keyboard.landscapeInputUiMode.get()) {
|
||||
LandscapeInputUiMode.DYNAMICALLY_SHOW -> super.onUpdateExtractingVisibility(info)
|
||||
@@ -450,16 +471,18 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
val visibleTopY = inputWindowView.height - inputViewSize.height
|
||||
val needAdditionalOverlay =
|
||||
prefs.smartbar.enabled.get() &&
|
||||
prefs.smartbar.secondaryActionsEnabled.get() &&
|
||||
prefs.smartbar.secondaryActionsExpanded.get() &&
|
||||
prefs.smartbar.secondaryActionsPlacement.get() == SecondaryRowPlacement.OVERLAY_APP_UI &&
|
||||
prefs.smartbar.layout.get() == SmartbarLayout.SUGGESTIONS_ACTIONS_EXTENDED &&
|
||||
prefs.smartbar.extendedActionsExpanded.get() &&
|
||||
prefs.smartbar.extendedActionsPlacement.get() == ExtendedActionsPlacement.OVERLAY_APP_UI &&
|
||||
keyboardManager.activeState.imeUiMode == ImeUiMode.TEXT
|
||||
|
||||
outInsets.contentTopInsets = visibleTopY
|
||||
outInsets.visibleTopInsets = visibleTopY
|
||||
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION
|
||||
val left = 0
|
||||
val top = visibleTopY - if (needAdditionalOverlay) FlorisImeSizing.Static.smartbarHeightPx else 0
|
||||
val top = if (keyboardManager.activeState.isBottomSheetShowing()) { 0 } else {
|
||||
visibleTopY - if (needAdditionalOverlay) FlorisImeSizing.Static.smartbarHeightPx else 0
|
||||
}
|
||||
val right = inputViewSize.width
|
||||
val bottom = inputWindowView.height
|
||||
outInsets.touchableRegion.set(left, top, right, bottom)
|
||||
@@ -519,6 +542,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
ImeUi()
|
||||
}
|
||||
BottomSheetHostUi()
|
||||
SystemUiIme()
|
||||
}
|
||||
}
|
||||
@@ -532,11 +556,13 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
val activeState by keyboardManager.observeActiveState()
|
||||
val keyboardStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.Keyboard,
|
||||
mode = activeState.inputMode.value,
|
||||
mode = activeState.inputShiftState.value,
|
||||
)
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
SideEffect {
|
||||
keyboardManager.activeState.layoutDirection = layoutDirection
|
||||
if (keyboardManager.activeState.layoutDirection != layoutDirection) {
|
||||
keyboardManager.activeState.layoutDirection = layoutDirection
|
||||
}
|
||||
}
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
SnyggSurface(
|
||||
|
||||
@@ -21,19 +21,21 @@ import android.view.textservice.SentenceSuggestionsInfo
|
||||
import android.view.textservice.SuggestionsInfo
|
||||
import android.view.textservice.TextInfo
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingService
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingResult
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.devtools.LogTopic
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogInfo
|
||||
import dev.patrickgold.florisboard.lib.kotlin.map
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
private val prefs by florisPreferenceModel()
|
||||
private val dictionaryManager get() = DictionaryManager.default()
|
||||
private val spellingManager by spellingManager()
|
||||
private val spellingService by spellingService()
|
||||
private val nlpManager by nlpManager()
|
||||
private val subtypeManager by subtypeManager()
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -56,7 +58,7 @@ class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
}
|
||||
|
||||
private inner class FlorisSpellCheckerSession : Session() {
|
||||
private var cachedSpellingLocale: FlorisLocale? = null
|
||||
private var cachedSpellingSubtype: Subtype? = null
|
||||
|
||||
override fun onCreate() {
|
||||
flogInfo(LogTopic.SPELL_EVENTS) { "Session requested locale: $locale" }
|
||||
@@ -65,33 +67,36 @@ class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
}
|
||||
|
||||
private fun setupSpellingIfNecessary() {
|
||||
val evaluatedLocale = when (prefs.spelling.languageMode.get()) {
|
||||
val evaluatedSubtype = when (prefs.spelling.languageMode.get()) {
|
||||
SpellingLanguageMode.USE_KEYBOARD_SUBTYPES -> {
|
||||
subtypeManager.activeSubtype().primaryLocale
|
||||
subtypeManager.activeSubtype
|
||||
}
|
||||
else -> {
|
||||
FlorisLocale.default()
|
||||
Subtype.DEFAULT.copy(primaryLocale = FlorisLocale.default())
|
||||
}
|
||||
}
|
||||
|
||||
if (evaluatedLocale != cachedSpellingLocale) {
|
||||
cachedSpellingLocale = evaluatedLocale
|
||||
if (evaluatedSubtype != cachedSpellingSubtype) {
|
||||
cachedSpellingSubtype = evaluatedSubtype
|
||||
nlpManager.preload(evaluatedSubtype)
|
||||
}
|
||||
flogInfo(LogTopic.SPELL_EVENTS) {
|
||||
"Session actual locale: ${cachedSpellingSubtype?.primaryLocale?.languageTag()}"
|
||||
}
|
||||
flogInfo(LogTopic.SPELL_EVENTS) { "Session actual locale: ${cachedSpellingLocale?.languageTag()}" }
|
||||
}
|
||||
|
||||
private fun spellMultiple(
|
||||
spellingLocale: FlorisLocale,
|
||||
spellingSubtype: Subtype,
|
||||
textInfos: Array<out TextInfo>,
|
||||
suggestionsLimit: Int,
|
||||
): Array<SuggestionsInfo> = runBlocking {
|
||||
): Array<SpellingResult> = runBlocking {
|
||||
val retInfos = Array(textInfos.size) { n ->
|
||||
val word = textInfos[n].text ?: ""
|
||||
spellingService.spellAsync(spellingLocale, word, suggestionsLimit)
|
||||
async { nlpManager.spell(spellingSubtype, word, emptyList(), emptyList(), suggestionsLimit) }
|
||||
}
|
||||
Array(textInfos.size) { n ->
|
||||
retInfos[n].await().apply {
|
||||
setCookieAndSequence(textInfos[n].cookie, textInfos[n].sequence)
|
||||
suggestionsInfo.setCookieAndSequence(textInfos[n].cookie, textInfos[n].sequence)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,13 +104,16 @@ class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
override fun onGetSuggestions(textInfo: TextInfo?, suggestionsLimit: Int): SuggestionsInfo {
|
||||
flogInfo(LogTopic.SPELL_EVENTS) { "text=${textInfo?.text}, limit=$suggestionsLimit" }
|
||||
|
||||
textInfo?.text ?: return SpellingService.emptySuggestionsInfo()
|
||||
textInfo?.text ?: return SpellingResult.unspecified().suggestionsInfo
|
||||
setupSpellingIfNecessary()
|
||||
val spellingLocale = cachedSpellingLocale ?: return SpellingService.emptySuggestionsInfo()
|
||||
val spellingSubtype = cachedSpellingSubtype ?: return SpellingResult.unspecified().suggestionsInfo
|
||||
|
||||
return spellingService
|
||||
.spell(spellingLocale, textInfo.text, suggestionsLimit)
|
||||
.sendToDebugOverlayIfEnabled(textInfo)
|
||||
return runBlocking {
|
||||
nlpManager
|
||||
.spell(spellingSubtype, textInfo.text, emptyList(), emptyList(), suggestionsLimit)
|
||||
.sendToDebugOverlayIfEnabled(textInfo)
|
||||
.suggestionsInfo
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetSuggestionsMultiple(
|
||||
@@ -117,9 +125,11 @@ class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
|
||||
textInfos ?: return emptyArray()
|
||||
setupSpellingIfNecessary()
|
||||
val spellingLocale = cachedSpellingLocale ?: return emptyArray()
|
||||
val spellingSubtype = cachedSpellingSubtype ?: return emptyArray()
|
||||
|
||||
return spellMultiple(spellingLocale, textInfos, suggestionsLimit).sendToDebugOverlayIfEnabled(textInfos)
|
||||
return spellMultiple(spellingSubtype, textInfos, suggestionsLimit)
|
||||
.sendToDebugOverlayIfEnabled(textInfos)
|
||||
.map { it.suggestionsInfo }
|
||||
}
|
||||
|
||||
override fun onGetSentenceSuggestionsMultiple(
|
||||
@@ -137,7 +147,7 @@ class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
|
||||
super.onCancel()
|
||||
if (prefs.devtools.showSpellingOverlay.get()) {
|
||||
spellingManager.clearDebugOverlay()
|
||||
nlpManager.clearDebugOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,25 +156,25 @@ class FlorisSpellCheckerService : SpellCheckerService() {
|
||||
|
||||
super.onClose()
|
||||
if (prefs.devtools.showSpellingOverlay.get()) {
|
||||
spellingManager.clearDebugOverlay()
|
||||
nlpManager.clearDebugOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
fun SuggestionsInfo.sendToDebugOverlayIfEnabled(
|
||||
fun SpellingResult.sendToDebugOverlayIfEnabled(
|
||||
textInfo: TextInfo,
|
||||
): SuggestionsInfo {
|
||||
): SpellingResult {
|
||||
if (prefs.devtools.showSpellingOverlay.get()) {
|
||||
spellingManager.addToDebugOverlay(textInfo.text, this)
|
||||
nlpManager.addToDebugOverlay(textInfo.text, this)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun Array<SuggestionsInfo>.sendToDebugOverlayIfEnabled(
|
||||
fun Array<SpellingResult>.sendToDebugOverlayIfEnabled(
|
||||
textInfos: Array<out TextInfo>,
|
||||
): Array<SuggestionsInfo> {
|
||||
): Array<SpellingResult> {
|
||||
if (prefs.devtools.showSpellingOverlay.get()) {
|
||||
for ((n, info) in this.withIndex()) {
|
||||
spellingManager.addToDebugOverlay(textInfos[n].text, info)
|
||||
nlpManager.addToDebugOverlay(textInfos[n].text, info)
|
||||
}
|
||||
}
|
||||
return this
|
||||
|
||||
@@ -23,19 +23,23 @@ import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
|
||||
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
|
||||
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
|
||||
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHairStyle
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiRecentlyUsedHelper
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.CandidatesDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.SecondaryRowPlacement
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.SmartbarRowType
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
|
||||
import dev.patrickgold.florisboard.lib.android.isOrientationPortrait
|
||||
@@ -44,7 +48,9 @@ import dev.patrickgold.florisboard.lib.observeAsTransformingState
|
||||
import dev.patrickgold.florisboard.lib.snygg.SnyggLevel
|
||||
import dev.patrickgold.florisboard.lib.util.VersionName
|
||||
import dev.patrickgold.jetpref.datastore.JetPref
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceType
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
|
||||
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
|
||||
@@ -64,8 +70,13 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "advanced__show_app_icon",
|
||||
default = true,
|
||||
)
|
||||
val forcePrivateMode = boolean(
|
||||
key = "advanced__force_private_mode",
|
||||
val incognitoMode = enum(
|
||||
key = "advanced__incognito_mode",
|
||||
default = IncognitoMode.DYNAMIC_ON_OFF,
|
||||
)
|
||||
// Internal pref
|
||||
val forceIncognitoModeFromDynamic = boolean(
|
||||
key = "advanced__force_incognito_mode_from_dynamic",
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
@@ -116,6 +127,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "correction__auto_capitalization",
|
||||
default = true,
|
||||
)
|
||||
val autoSpacePunctuation = boolean(
|
||||
key = "correction__auto_space_punctuation",
|
||||
default = false,
|
||||
)
|
||||
val doubleSpacePeriod = boolean(
|
||||
key = "correction__double_space_period",
|
||||
default = true,
|
||||
@@ -152,6 +167,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "devtools__show_touch_boundaries",
|
||||
default = false,
|
||||
)
|
||||
val showDragAndDropHelpers = boolean(
|
||||
key = "devtools__show_drag_and_drop_helpers",
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
||||
val dictionary = Dictionary()
|
||||
@@ -252,9 +271,9 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "input_feedback__audio_enabled",
|
||||
default = true,
|
||||
)
|
||||
val audioIgnoreSystemSettings = boolean(
|
||||
key = "input_feedback__audio_ignore_system_settings",
|
||||
default = false,
|
||||
val audioActivationMode = enum(
|
||||
key = "input_feedback__audio_activation_mode",
|
||||
default = InputFeedbackActivationMode.RESPECT_SYSTEM_SETTINGS,
|
||||
)
|
||||
val audioVolume = int(
|
||||
key = "input_feedback__audio_volume",
|
||||
@@ -285,13 +304,13 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "input_feedback__haptic_enabled",
|
||||
default = true,
|
||||
)
|
||||
val hapticIgnoreSystemSettings = boolean(
|
||||
key = "input_feedback__haptic_ignore_system_settings",
|
||||
default = false,
|
||||
val hapticActivationMode = enum(
|
||||
key = "input_feedback__haptic_activation_mode",
|
||||
default = InputFeedbackActivationMode.RESPECT_SYSTEM_SETTINGS,
|
||||
)
|
||||
val hapticUseVibrator = boolean(
|
||||
key = "input_feedback__haptic_use_vibrator",
|
||||
default = true,
|
||||
val hapticVibrationMode = enum(
|
||||
key = "input_feedback__haptic_vibration_mode",
|
||||
default = HapticVibrationMode.USE_VIBRATOR_DIRECTLY,
|
||||
)
|
||||
val hapticVibrationDuration = int(
|
||||
key = "input_feedback__haptic_vibration_duration",
|
||||
@@ -326,7 +345,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
val internal = Internal()
|
||||
inner class Internal {
|
||||
val homeIsBetaToolboxCollapsed = boolean(
|
||||
key = "internal__home_is_beta_toolbox_collapsed_0316beta01",
|
||||
key = "internal__home_is_beta_toolbox_collapsed_040a01",
|
||||
default = false,
|
||||
)
|
||||
val isImeSetUp = boolean(
|
||||
@@ -518,45 +537,38 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "smartbar__enabled",
|
||||
default = true,
|
||||
)
|
||||
val layout = enum(
|
||||
key = "smartbar__layout",
|
||||
default = SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED,
|
||||
)
|
||||
val actionArrangement = custom(
|
||||
key = "smartbar__action_arrangement",
|
||||
default = QuickActionArrangement.Default,
|
||||
serializer = QuickActionArrangement.Serializer,
|
||||
)
|
||||
val flipToggles = boolean(
|
||||
key = "smartbar__flip_toggles",
|
||||
default = false,
|
||||
)
|
||||
val primaryActionsExpanded = boolean(
|
||||
key = "smartbar__primary_actions_expanded",
|
||||
val sharedActionsExpanded = boolean(
|
||||
key = "smartbar__shared_actions_expanded",
|
||||
default = false,
|
||||
)
|
||||
val primaryActionsRowType = enum(
|
||||
key = "smartbar__primary_actions_row_type",
|
||||
default = SmartbarRowType.QUICK_ACTIONS,
|
||||
)
|
||||
val primaryActionsAutoExpandCollapse = boolean(
|
||||
key = "smartbar__primary_actions_auto_expand_collapse",
|
||||
val sharedActionsAutoExpandCollapse = boolean(
|
||||
key = "smartbar__shared_actions_auto_expand_collapse",
|
||||
default = true,
|
||||
)
|
||||
val primaryActionsExpandWithAnimation = boolean(
|
||||
key = "smartbar__primary_actions_expand_with_animation",
|
||||
val sharedActionsExpandWithAnimation = boolean(
|
||||
key = "smartbar__shared_actions_expand_with_animation",
|
||||
default = true,
|
||||
)
|
||||
val secondaryActionsEnabled = boolean(
|
||||
key = "smartbar__secondary_actions_enabled",
|
||||
default = true,
|
||||
)
|
||||
val secondaryActionsExpanded = boolean(
|
||||
key = "smartbar__secondary_actions_expanded",
|
||||
val extendedActionsExpanded = boolean(
|
||||
key = "smartbar__extended_actions_expanded",
|
||||
default = false,
|
||||
)
|
||||
val secondaryActionsPlacement = enum(
|
||||
key = "smartbar__secondary_actions_placement",
|
||||
default = SecondaryRowPlacement.ABOVE_PRIMARY,
|
||||
)
|
||||
val secondaryActionsRowType = enum(
|
||||
key = "smartbar__secondary_actions_row_type",
|
||||
default = SmartbarRowType.CLIPBOARD_CURSOR_TOOLS,
|
||||
)
|
||||
val quickActions = string(
|
||||
key = "smartbar__quick_actions",
|
||||
default = "[]",
|
||||
val extendedActionsPlacement = enum(
|
||||
key = "smartbar__extended_actions_placement",
|
||||
default = ExtendedActionsPlacement.ABOVE_CANDIDATES,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -590,10 +602,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "suggestion__display_mode",
|
||||
default = CandidatesDisplayMode.DYNAMIC_SCROLLABLE,
|
||||
)
|
||||
val usePrevWords = boolean(
|
||||
key = "suggestion__use_prev_words",
|
||||
default = true,
|
||||
)
|
||||
val blockPossiblyOffensive = boolean(
|
||||
key = "suggestion__block_possibly_offensive",
|
||||
default = true,
|
||||
@@ -604,7 +612,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
)
|
||||
val clipboardContentTimeout = int(
|
||||
key = "suggestion__clipboard_content_timeout",
|
||||
default = 30,
|
||||
default = 60,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -653,4 +661,41 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
default = SnyggLevel.ADVANCED,
|
||||
)
|
||||
}
|
||||
|
||||
override fun migrate(entry: PreferenceMigrationEntry): PreferenceMigrationEntry {
|
||||
return when (entry.key) {
|
||||
// Migrate enums from their lowercase to uppercase representation
|
||||
// Keep migration rule until: 0.5 dev cycle
|
||||
"advanced__settings_theme", "gestures__swipe_up", "gestures__swipe_down", "gestures__swipe_left",
|
||||
"gestures__swipe_right", "gestures__space_bar_swipe_up", "gestures__space_bar_swipe_left",
|
||||
"gestures__space_bar_swipe_right", "gestures__space_bar_long_press", "gestures__delete_key_swipe_left",
|
||||
"gestures__delete_key_long_press", "keyboard__hinted_number_row_mode", "keyboard__hinted_symbols_mode",
|
||||
"keyboard__utility_key_action", "keyboard__one_handed_mode", "keyboard__landscape_input_ui_mode",
|
||||
"localization__display_language_names_in", "media__emoji_preferred_skin_tone",
|
||||
"media__emoji_preferred_hair_style", "smartbar__primary_actions_row_type",
|
||||
"smartbar__secondary_actions_placement", "smartbar__secondary_actions_row_type", "spelling__language_mode",
|
||||
"suggestion__display_mode", "theme__mode", "theme__editor_display_colors_as",
|
||||
"theme__editor_display_kbd_after_dialogs", "theme__editor_level",
|
||||
-> {
|
||||
entry.transform(rawValue = entry.rawValue.uppercase())
|
||||
}
|
||||
|
||||
// Migrate old private mode force flag as this is a sensitive preference
|
||||
// Keep migration rule until: 0.5 dev cycle
|
||||
"advanced__force_private_mode" -> {
|
||||
if (entry.rawValue.toBoolean()) {
|
||||
entry.transform(
|
||||
type = PreferenceType.string(),
|
||||
key = "advanced__incognito_mode",
|
||||
rawValue = IncognitoMode.FORCE_ON.toString(),
|
||||
)
|
||||
} else {
|
||||
entry.reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Default: keep entry
|
||||
else -> entry.keepAsIs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -37,9 +39,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.accompanist.insets.ProvideWindowInsets
|
||||
import com.google.accompanist.insets.navigationBarsWithImePadding
|
||||
import com.google.accompanist.insets.statusBarsPadding
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
@@ -54,6 +53,7 @@ import dev.patrickgold.florisboard.lib.compose.SystemUiApp
|
||||
import dev.patrickgold.florisboard.lib.compose.rememberPreviewFieldController
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.AppVersionUtils
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ProvideDefaultDialogPrefStrings
|
||||
|
||||
enum class AppTheme(val id: String) {
|
||||
@@ -70,18 +70,18 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
|
||||
|
||||
class FlorisAppActivity : ComponentActivity() {
|
||||
private val prefs by florisPreferenceModel()
|
||||
private var isDatastoreReady by mutableStateOf(false)
|
||||
private var appTheme by mutableStateOf(AppTheme.AUTO)
|
||||
private var showAppIcon = true
|
||||
private var resourcesContext by mutableStateOf(this as Context)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
installSplashScreen()
|
||||
|
||||
prefs.datastoreReadyStatus.observe(this) {
|
||||
isDatastoreReady = it
|
||||
// Splash screen should be installed before calling super.onCreate()
|
||||
installSplashScreen().apply {
|
||||
setKeepOnScreenCondition { !prefs.datastoreReadyStatus.get() }
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
prefs.advanced.settingsTheme.observe(this) {
|
||||
appTheme = it
|
||||
}
|
||||
@@ -96,13 +96,13 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
setContent {
|
||||
ProvideLocalizedResources(resourcesContext) {
|
||||
FlorisAppTheme(theme = appTheme) {
|
||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = false) {
|
||||
// We defer the setContent call until the datastore model is loaded, until then the splash screen stays drawn
|
||||
prefs.datastoreReadyStatus.observe(this) { isModelLoaded ->
|
||||
if (!isModelLoaded) return@observe
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
setContent {
|
||||
ProvideLocalizedResources(resourcesContext) {
|
||||
FlorisAppTheme(theme = appTheme) {
|
||||
Surface(color = MaterialTheme.colors.background) {
|
||||
SystemUiApp()
|
||||
AppContent()
|
||||
@@ -124,17 +124,16 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
} else {
|
||||
this.hideAppIcon()
|
||||
}
|
||||
} else {
|
||||
this.showAppIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun AppContent() {
|
||||
val navController = rememberNavController()
|
||||
val previewFieldController = rememberPreviewFieldController()
|
||||
|
||||
val isImeSetUp by prefs.internal.isImeSetUp.observeAsState()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalNavController provides navController,
|
||||
LocalPreviewFieldController provides previewFieldController,
|
||||
@@ -147,12 +146,13 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.navigationBarsWithImePadding(),
|
||||
.navigationBarsPadding()
|
||||
.imePadding(),
|
||||
) {
|
||||
Routes.AppNavHost(
|
||||
modifier = Modifier.weight(1.0f),
|
||||
navController = navController,
|
||||
startDestination = Routes.Splash.Screen,
|
||||
startDestination = if (isImeSetUp) Routes.Settings.Home else Routes.Setup.Screen,
|
||||
)
|
||||
PreviewKeyboardField(previewFieldController)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.navigation.compose.composable
|
||||
import dev.patrickgold.florisboard.app.devtools.AndroidLocalesScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.AndroidSettingsScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.DevtoolsScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.ExportDebugLogScreen
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionEditScreen
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionExportScreen
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreen
|
||||
@@ -48,24 +49,15 @@ import dev.patrickgold.florisboard.app.settings.localization.SelectLocaleScreen
|
||||
import dev.patrickgold.florisboard.app.settings.localization.SubtypeEditorScreen
|
||||
import dev.patrickgold.florisboard.app.settings.media.MediaScreen
|
||||
import dev.patrickgold.florisboard.app.settings.smartbar.SmartbarScreen
|
||||
import dev.patrickgold.florisboard.app.settings.spelling.ImportSpellingArchiveScreen
|
||||
import dev.patrickgold.florisboard.app.settings.spelling.ManageSpellingDictsScreen
|
||||
import dev.patrickgold.florisboard.app.settings.spelling.SpellingInfoScreen
|
||||
import dev.patrickgold.florisboard.app.settings.spelling.SpellingScreen
|
||||
import dev.patrickgold.florisboard.app.settings.theme.ThemeManagerScreen
|
||||
import dev.patrickgold.florisboard.app.settings.theme.ThemeManagerScreenAction
|
||||
import dev.patrickgold.florisboard.app.settings.theme.ThemeScreen
|
||||
import dev.patrickgold.florisboard.app.settings.typing.TypingScreen
|
||||
import dev.patrickgold.florisboard.app.setup.SetupScreen
|
||||
import dev.patrickgold.florisboard.app.splash.SplashScreen
|
||||
import dev.patrickgold.florisboard.lib.kotlin.curlyFormat
|
||||
|
||||
@Suppress("FunctionName")
|
||||
object Routes {
|
||||
object Splash {
|
||||
const val Screen = "splash"
|
||||
}
|
||||
|
||||
object Setup {
|
||||
const val Screen = "setup"
|
||||
}
|
||||
@@ -90,12 +82,6 @@ object Routes {
|
||||
|
||||
const val Typing = "settings/typing"
|
||||
|
||||
const val Spelling = "settings/spelling"
|
||||
const val SpellingInfo = "settings/spelling/info"
|
||||
const val ManageSpellingDicts = "settings/spelling/manage-dicts"
|
||||
const val ImportSpellingArchive = "settings/spelling/import-archive"
|
||||
const val ImportSpellingAffDic = "settings/spelling/import-aff-dic"
|
||||
|
||||
const val Dictionary = "settings/dictionary"
|
||||
const val UserDictionary = "settings/dictionary/user-dictionary/{type}"
|
||||
fun UserDictionary(type: UserDictionaryType) = UserDictionary.curlyFormat("type" to type.id)
|
||||
@@ -121,6 +107,8 @@ object Routes {
|
||||
const val AndroidLocales = "devtools/android/locales"
|
||||
const val AndroidSettings = "devtools/android/settings/{name}"
|
||||
fun AndroidSettings(name: String) = AndroidSettings.curlyFormat("name" to name)
|
||||
|
||||
const val ExportDebugLog = "export-debug-log"
|
||||
}
|
||||
|
||||
object Ext {
|
||||
@@ -153,8 +141,6 @@ object Routes {
|
||||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
) {
|
||||
composable(Splash.Screen) { SplashScreen() }
|
||||
|
||||
composable(Setup.Screen) { SetupScreen() }
|
||||
|
||||
composable(Settings.Home) { HomeScreen() }
|
||||
@@ -182,11 +168,6 @@ object Routes {
|
||||
|
||||
composable(Settings.Typing) { TypingScreen() }
|
||||
|
||||
composable(Settings.Spelling) { SpellingScreen() }
|
||||
composable(Settings.SpellingInfo) { SpellingInfoScreen() }
|
||||
composable(Settings.ManageSpellingDicts) { ManageSpellingDictsScreen() }
|
||||
composable(Settings.ImportSpellingArchive) { ImportSpellingArchiveScreen() }
|
||||
|
||||
composable(Settings.Dictionary) { DictionaryScreen() }
|
||||
composable(Settings.UserDictionary) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
@@ -215,6 +196,7 @@ object Routes {
|
||||
val name = navBackStack.arguments?.getString("name")
|
||||
AndroidSettingsScreen(name)
|
||||
}
|
||||
composable(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
|
||||
|
||||
composable(Ext.Edit) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
|
||||
@@ -26,11 +26,17 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.io.subDir
|
||||
import dev.patrickgold.florisboard.lib.io.subFile
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import java.util.*
|
||||
|
||||
@@ -39,8 +45,38 @@ fun AndroidLocalesScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.devtools__android_locales__title)
|
||||
scrollable = false
|
||||
|
||||
val context = LocalContext.current
|
||||
val availableLocales = remember { Locale.getAvailableLocales().sortedBy { it.toLanguageTag() } }
|
||||
|
||||
actions {
|
||||
FlorisIconButton(
|
||||
onClick = {
|
||||
try {
|
||||
val devtoolsDir = context.noBackupFilesDir.subDir("devtools")
|
||||
devtoolsDir.mkdirs()
|
||||
val txtFile = devtoolsDir.subFile("system_locales.tsv")
|
||||
txtFile.bufferedWriter().use { out ->
|
||||
for (locale in availableLocales) {
|
||||
out.append(locale.toLanguageTag())
|
||||
out.append('\t')
|
||||
out.append(locale.getDisplayName(Locale.ENGLISH))
|
||||
out.append('\t')
|
||||
out.append(locale.getDisplayName(locale))
|
||||
out.appendLine()
|
||||
}
|
||||
}
|
||||
context.showLongToast("Exported available system locales to \"${txtFile.path}\"")
|
||||
} catch (e: Exception) {
|
||||
context.showLongToast(
|
||||
R.string.error__snackbar_message_template,
|
||||
"error_message" to e.message.toString(),
|
||||
)
|
||||
}
|
||||
},
|
||||
icon = painterResource(R.drawable.ic_save),
|
||||
)
|
||||
}
|
||||
|
||||
content {
|
||||
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.devtools
|
||||
|
||||
import android.view.textservice.SuggestionsInfo
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@@ -28,7 +27,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -44,7 +42,7 @@ import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.editorInstance
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.spellingManager
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -84,7 +82,7 @@ private fun DevtoolsClipboardOverlay() {
|
||||
val clipboardManager by context.clipboardManager()
|
||||
|
||||
DevtoolsOverlayBox(title = "Clipboard overlay") {
|
||||
val primaryClip by clipboardManager.primaryClip.observeAsState()
|
||||
val primaryClip by clipboardManager.primaryClipFlow.collectAsState()
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp),
|
||||
text = primaryClip.toString(),
|
||||
@@ -98,20 +96,22 @@ private fun DevtoolsInputStateOverlay() {
|
||||
val context = LocalContext.current
|
||||
val editorInstance by context.editorInstance()
|
||||
|
||||
val activeEditorInfo by editorInstance.activeInfoFlow.collectAsState()
|
||||
val activeEditorContent by editorInstance.activeContentFlow.collectAsState()
|
||||
val selection = activeEditorContent.selection
|
||||
val info by editorInstance.activeInfoFlow.collectAsState()
|
||||
val content by editorInstance.activeContentFlow.collectAsState()
|
||||
val selection = content.selection
|
||||
|
||||
DevtoolsOverlayBox(title = "Input state overlay") {
|
||||
DevtoolsSubGroup(title = "EditorInfo") {
|
||||
DevtoolsText(text = "Type=${activeEditorInfo.inputAttributes.type} Variation=${activeEditorInfo.inputAttributes.variation} IsRich=${activeEditorInfo.isRichInputEditor}")
|
||||
DevtoolsText(text = "Selection { start=${selection.start}, end=${selection.end} }")
|
||||
DevtoolsText(text = "Type=${info.inputAttributes.type} Variation=${info.inputAttributes.variation} IsRich=${info.isRichInputEditor}")
|
||||
DevtoolsText(text = "InitialSelection: ${info.initialSelection}")
|
||||
}
|
||||
DevtoolsSubGroup(title = "EditorContent") {
|
||||
DevtoolsText(text = "Before: \"${activeEditorContent.textBeforeSelection}\"")
|
||||
DevtoolsText(text = "Selected: \"${activeEditorContent.selectedText}\"")
|
||||
DevtoolsText(text = "After: \"${activeEditorContent.textAfterSelection}\"")
|
||||
DevtoolsText(text = "ComposingWord: ${activeEditorContent.composing}")
|
||||
DevtoolsText(text = "Selection: { start=${selection.start}, end=${selection.end} }")
|
||||
DevtoolsText(text = "Before: \"${content.textBeforeSelection}\"")
|
||||
DevtoolsText(text = "Selected: \"${content.selectedText}\"")
|
||||
DevtoolsText(text = "After: \"${content.textAfterSelection}\"")
|
||||
DevtoolsText(text = "Composing: ${content.composing}")
|
||||
DevtoolsText(text = "CurrentWord: ${content.currentWord}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,17 +120,16 @@ private fun DevtoolsInputStateOverlay() {
|
||||
@Composable
|
||||
private fun DevtoolsSpellingOverlay() {
|
||||
val context = LocalContext.current
|
||||
val spellingManager by context.spellingManager()
|
||||
val nlpManager by context.nlpManager()
|
||||
|
||||
val debugOverlayVersion by spellingManager.debugOverlayVersion.observeAsNonNullState()
|
||||
val suggestionsInfos = remember(debugOverlayVersion) { spellingManager.debugOverlaySuggestionsInfos.snapshot() }
|
||||
val debugOverlayVersion by nlpManager.debugOverlayVersion.observeAsNonNullState()
|
||||
val suggestionsInfos = remember(debugOverlayVersion) { nlpManager.debugOverlaySuggestionsInfos.snapshot() }
|
||||
|
||||
val sortedEntries = suggestionsInfos.entries.sortedByDescending { it.key }
|
||||
DevtoolsOverlayBox(title = "Spelling overlay (${sortedEntries.size})") {
|
||||
for ((timestamp, wordInfoPair) in sortedEntries) {
|
||||
val (word, info) = wordInfoPair
|
||||
val isTypo = (info.suggestionsAttributes and SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0
|
||||
val suggestions = Array(info.suggestionsCount) { n -> info.getSuggestionAt(n) }
|
||||
val suggestions = info.suggestions()
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||
val date = DateFormat.format(Date(timestamp))
|
||||
Text(
|
||||
@@ -140,8 +139,8 @@ private fun DevtoolsSpellingOverlay() {
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
val details = buildString {
|
||||
appendLine("isTypo: $isTypo")
|
||||
if (isTypo) {
|
||||
appendLine("isTypo: ${info.isTypo} | isGrammarError: ${info.isGrammarError}")
|
||||
if (info.isTypo || info.isGrammarError) {
|
||||
appendLine("providing corrections list of size n=${suggestions.size}")
|
||||
for ((n, suggestion) in suggestions.withIndex()) {
|
||||
append(" [$n] = string[${suggestion.length}] { \"")
|
||||
|
||||
@@ -20,12 +20,15 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
|
||||
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
|
||||
import dev.patrickgold.florisboard.lib.android.AndroidSettings
|
||||
import dev.patrickgold.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
@@ -43,7 +46,10 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.devtools__title)
|
||||
previewFieldVisible = true
|
||||
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val extensionManager by context.extensionManager()
|
||||
|
||||
val (showDialog, setShowDialog) = remember { mutableStateOf(false) }
|
||||
|
||||
content {
|
||||
@@ -84,6 +90,12 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
summary = stringRes(R.string.devtools__show_key_touch_boundaries__summary),
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.devtools.showDragAndDropHelpers,
|
||||
title = stringRes(R.string.devtools__show_drag_and_drop_helpers__label),
|
||||
summary = stringRes(R.string.devtools__show_drag_and_drop_helpers__summary),
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
Preference(
|
||||
title = stringRes(R.string.devtools__clear_udm_internal_database__label),
|
||||
summary = stringRes(R.string.devtools__clear_udm_internal_database__summary),
|
||||
@@ -93,14 +105,7 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
Preference(
|
||||
title = stringRes(R.string.devtools__reset_flag__label, "flag_name" to "isImeSetUp"),
|
||||
summary = stringRes(R.string.devtools__reset_flag_is_ime_set_up__summary),
|
||||
onClick = {
|
||||
prefs.internal.isImeSetUp.set(false)
|
||||
navController.navigate(Routes.Setup.Screen) {
|
||||
popUpTo(Routes.Settings.Home) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = { prefs.internal.isImeSetUp.set(false) },
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
Preference(
|
||||
@@ -109,6 +114,12 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
onClick = { throw DebugOnPurposeCrashException() },
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
Preference(
|
||||
title = "Debug log",
|
||||
summary = "View and export the debug log",
|
||||
onClick = { navController.navigate(Routes.Devtools.ExportDebugLog) },
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.devtools__group_android__title)) {
|
||||
@@ -164,6 +175,23 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = "ExtensionManager index paths") {
|
||||
Preference(
|
||||
title = "keyboardExtensions",
|
||||
summary = extensionManager.keyboardExtensions.internalModuleDir.absolutePath,
|
||||
onClick = {
|
||||
context.showLongToast(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
|
||||
},
|
||||
)
|
||||
Preference(
|
||||
title = "themes",
|
||||
summary = extensionManager.themes.internalModuleDir.absolutePath,
|
||||
onClick = {
|
||||
context.showLongToast(extensionManager.themes.internalModuleDir.absolutePath)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
FlorisConfirmDeleteDialog(
|
||||
onConfirm = {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.app.devtools
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.lib.android.showShortToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.devtools.Devtools
|
||||
|
||||
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI and in localization
|
||||
@Composable
|
||||
fun ExportDebugLogScreen() = FlorisScreen {
|
||||
title = "Debug log"
|
||||
scrollable = false
|
||||
|
||||
val prefs by florisPreferenceModel()
|
||||
val context = LocalContext.current
|
||||
val clipboardManager by context.clipboardManager()
|
||||
|
||||
var debugLog by remember { mutableStateOf<List<String>?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
debugLog = Devtools.generateDebugLog(context, prefs, includeLogcat = true).lines()
|
||||
}
|
||||
|
||||
bottomBar {
|
||||
FlorisButton(
|
||||
onClick = {
|
||||
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
|
||||
context.showShortToast("Copied debug log to clipboard")
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Export (copy to clipboard)",
|
||||
enabled = debugLog != null,
|
||||
)
|
||||
}
|
||||
|
||||
content {
|
||||
// Forcing LTR because text displayed is a debug log
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisScrollbar(lazyListState, isVertical = true)
|
||||
.florisHorizontalScroll(),
|
||||
state = lazyListState,
|
||||
) {
|
||||
val log = debugLog
|
||||
if (log == null) {
|
||||
item {
|
||||
Text("Loading...")
|
||||
}
|
||||
} else {
|
||||
items(log) { logLine ->
|
||||
Text(
|
||||
text = logLine,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 10.sp,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
@@ -51,7 +50,6 @@ import dev.patrickgold.florisboard.app.settings.theme.ThemeEditorScreen
|
||||
import dev.patrickgold.florisboard.cacheManager
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardExtension
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingExtension
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
|
||||
@@ -218,7 +216,6 @@ private fun ExtensionEditScreenSheetSwitcher(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun EditScreen(
|
||||
workspace: CacheManager.ExtEditorWorkspace<*>,
|
||||
@@ -227,14 +224,12 @@ private fun EditScreen(
|
||||
title = stringRes(if (isCreateExt) {
|
||||
when (workspace.ext) {
|
||||
is KeyboardExtension -> R.string.ext__editor__title_create_keyboard
|
||||
is SpellingExtension -> R.string.ext__editor__title_create_spelling
|
||||
is ThemeExtension -> R.string.ext__editor__title_create_theme
|
||||
else -> R.string.ext__editor__title_create_any
|
||||
}
|
||||
} else {
|
||||
when (workspace.ext) {
|
||||
is KeyboardExtension -> R.string.ext__editor__title_edit_keyboard
|
||||
is SpellingExtension -> R.string.ext__editor__title_edit_spelling
|
||||
is ThemeExtension -> R.string.ext__editor__title_edit_theme
|
||||
else -> R.string.ext__editor__title_edit_any
|
||||
}
|
||||
|
||||
@@ -48,9 +48,8 @@ import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.cacheManager
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardExtension
|
||||
import dev.patrickgold.florisboard.ime.nlp.NATIVE_NULLPTR
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingExtension
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
|
||||
import dev.patrickgold.florisboard.lib.NATIVE_NULLPTR
|
||||
import dev.patrickgold.florisboard.lib.android.showLongToast
|
||||
import dev.patrickgold.florisboard.lib.cache.CacheManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisBulletSpacer
|
||||
@@ -79,11 +78,6 @@ enum class ExtensionImportScreenType(
|
||||
titleResId = R.string.ext__import__ext_keyboard,
|
||||
supportedFiles = listOf(FileRegistry.FlexExtension),
|
||||
),
|
||||
EXT_SPELLING(
|
||||
id = "ext-spelling",
|
||||
titleResId = R.string.ext__import__ext_spelling,
|
||||
supportedFiles = listOf(FileRegistry.FlexExtension),
|
||||
),
|
||||
EXT_THEME(
|
||||
id = "ext-theme",
|
||||
titleResId = R.string.ext__import__ext_theme,
|
||||
@@ -116,14 +110,14 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
|
||||
if (extensionManager.getExtensionById(ext.meta.id)?.sourceRef?.isAssets == true) {
|
||||
R.string.ext__import__file_skip_ext_core
|
||||
} else {
|
||||
NATIVE_NULLPTR
|
||||
NATIVE_NULLPTR.toInt()
|
||||
}
|
||||
}
|
||||
fileInfo.mediaType == FileRegistry.FlexExtension.mediaType -> {
|
||||
R.string.ext__import__file_skip_ext_corrupted
|
||||
}
|
||||
else -> {
|
||||
NATIVE_NULLPTR
|
||||
NATIVE_NULLPTR.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +150,7 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
|
||||
}
|
||||
val enabled = remember(importResult) {
|
||||
importResult?.getOrNull()?.takeIf { workspace ->
|
||||
workspace.inputFileInfos.any { it.skipReason == NATIVE_NULLPTR }
|
||||
workspace.inputFileInfos.any { it.skipReason == NATIVE_NULLPTR.toInt() }
|
||||
} != null
|
||||
}
|
||||
ButtonBarButton(
|
||||
@@ -166,7 +160,7 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
|
||||
val workspace = importResult!!.getOrThrow()
|
||||
runCatching {
|
||||
for (fileInfo in workspace.inputFileInfos) {
|
||||
if (fileInfo.skipReason != NATIVE_NULLPTR) {
|
||||
if (fileInfo.skipReason != NATIVE_NULLPTR.toInt()) {
|
||||
continue
|
||||
}
|
||||
val ext = fileInfo.ext
|
||||
@@ -177,9 +171,6 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
|
||||
ExtensionImportScreenType.EXT_KEYBOARD -> {
|
||||
ext.takeIf { it is KeyboardExtension }?.let { extensionManager.import(it) }
|
||||
}
|
||||
ExtensionImportScreenType.EXT_SPELLING -> {
|
||||
ext.takeIf { it is SpellingExtension }?.let { extensionManager.import(it) }
|
||||
}
|
||||
ExtensionImportScreenType.EXT_THEME -> {
|
||||
ext.takeIf { it is ThemeExtension }?.let { extensionManager.import(it) }
|
||||
}
|
||||
@@ -313,7 +304,7 @@ private fun FileInfoView(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (fileInfo.skipReason != NATIVE_NULLPTR) {
|
||||
if (fileInfo.skipReason != NATIVE_NULLPTR.toInt()) {
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(19.dp)
|
||||
|
||||
@@ -39,8 +39,6 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.lib.android.launchUrl
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisErrorCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisWarningCard
|
||||
@@ -83,7 +81,7 @@ fun HomeScreen() = FlorisScreen {
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "Welcome to the 0.3.16 beta series!",
|
||||
text = "Welcome to the 0.4 alpha series!",
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
@@ -100,9 +98,8 @@ fun HomeScreen() = FlorisScreen {
|
||||
}
|
||||
}
|
||||
if (!isCollapsed) {
|
||||
Text("The 0.3.16 beta series focuses on preparing the keyboard for word suggestions in 0.4.0, getting rid of the input lag and improve input connection handling. Most work is already done, now I am focusing on fixing introduced bugs and generally fixing a lot of bugs to improve the stability of this keyboard.\n")
|
||||
Text("If you have general feedback on this rework or want to report a newly broken/buggy input for a specific app (or all apps), please make sure to post your feedback in this thread:\n")
|
||||
FlorisButton(onClick = { context.launchUrl("https://github.com/florisboard/florisboard/discussions/1827") }, text = "Open feedback thread")
|
||||
Text("0.4 will be quite a big release and finally work on adding support for word suggestion and inline autocorrect within the keyboard UI, at first for Latin-based languages. Additionally general improvements and bug fixes will also be made.\n")
|
||||
Text("Currently the alpha releases are preparations for the suggestions implementation and general improvements and bug fixes.\n")
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text("Note that this release does not contain support for word suggestions (will show the current word plus numbers as a placeholder).", color = Color.Red)
|
||||
Text("Please DO NOT file an issue for this. It is already more than known and a major goal for implementation in 0.4.0. Thank you!\n")
|
||||
@@ -131,15 +128,10 @@ fun HomeScreen() = FlorisScreen {
|
||||
onClick = { navController.navigate(Routes.Settings.Smartbar) },
|
||||
)
|
||||
Preference(
|
||||
iconId = R.drawable.ic_settings_suggest,
|
||||
iconId = R.drawable.ic_spellcheck,
|
||||
title = stringRes(R.string.settings__typing__title),
|
||||
onClick = { navController.navigate(Routes.Settings.Typing) },
|
||||
)
|
||||
Preference(
|
||||
iconId = R.drawable.ic_spellcheck,
|
||||
title = stringRes(R.string.settings__spelling__title),
|
||||
onClick = { navController.navigate(Routes.Settings.Spelling) },
|
||||
)
|
||||
Preference(
|
||||
iconId = R.drawable.ic_library_books,
|
||||
title = stringRes(R.string.settings__dictionary__title),
|
||||
|
||||
@@ -16,29 +16,15 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.about
|
||||
|
||||
import android.webkit.URLUtil
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.lib.android.launchUrl
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
|
||||
data class Library(val name: String, val licenseText: String)
|
||||
|
||||
@Composable
|
||||
fun ThirdPartyLicensesScreen() = FlorisScreen {
|
||||
@@ -46,67 +32,14 @@ fun ThirdPartyLicensesScreen() = FlorisScreen {
|
||||
scrollable = false
|
||||
iconSpaceReserved = false
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
var dialogLibraryToShow by rememberSaveable {
|
||||
mutableStateOf<Library?>(null)
|
||||
}
|
||||
|
||||
val libraries = remember {
|
||||
val list = mutableListOf<Library>()
|
||||
val licensesData = context.resources
|
||||
.openRawResource(R.raw.third_party_licenses)
|
||||
.readBytes()
|
||||
val licensesMetaDataReader = context.resources
|
||||
.openRawResource(R.raw.third_party_license_metadata)
|
||||
.bufferedReader()
|
||||
licensesMetaDataReader.use { it.readLines() }.map { line ->
|
||||
val (section, name) = line.split(" ", limit = 2)
|
||||
val (startOffset, length) = section.split(":", limit = 2).map { it.toInt() }
|
||||
val licenseData = licensesData.sliceArray(startOffset until startOffset + length)
|
||||
val licenseText = licenseData.toString(Charsets.UTF_8)
|
||||
Library(name, licenseText)
|
||||
}.all { list.add(it) }
|
||||
list.add(
|
||||
Library("ICU4C Native C library", "https://github.com/unicode-org/icu/blob/main/icu4c/LICENSE")
|
||||
)
|
||||
list.add(
|
||||
Library("Google Material Icons", "https://www.apache.org/licenses/LICENSE-2.0.txt")
|
||||
)
|
||||
list.sortedBy { it.name.lowercase() }.toList()
|
||||
}
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
content {
|
||||
val state = rememberLazyListState()
|
||||
LazyColumn(
|
||||
LibrariesContainer(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.florisScrollbar(state = state, isVertical = true),
|
||||
state = state,
|
||||
) {
|
||||
items(libraries) { library ->
|
||||
val isUrl = URLUtil.isValidUrl(library.licenseText)
|
||||
Preference(
|
||||
title = library.name,
|
||||
onClick = {
|
||||
if (isUrl) {
|
||||
context.launchUrl(library.licenseText)
|
||||
} else {
|
||||
dialogLibraryToShow = library
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (dialogLibraryToShow != null) {
|
||||
JetPrefAlertDialog(
|
||||
title = dialogLibraryToShow?.name ?: "",
|
||||
dismissLabel = stringRes(android.R.string.ok),
|
||||
onDismiss = { dialogLibraryToShow = null },
|
||||
) {
|
||||
Text(dialogLibraryToShow?.licenseText ?: "")
|
||||
}
|
||||
}
|
||||
.florisScrollbar(lazyListState, isVertical = true),
|
||||
lazyListState = lazyListState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import dev.patrickgold.florisboard.app.AppTheme
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.android.AndroidVersion
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
@@ -143,11 +144,11 @@ fun AdvancedScreen() = FlorisScreen {
|
||||
},
|
||||
enabledIf = { AndroidVersion.ATMOST_API28_P },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.advanced.forcePrivateMode,
|
||||
iconId = R.drawable.ic_security,
|
||||
title = stringRes(R.string.pref__advanced__force_private_mode__label),
|
||||
summary = stringRes(R.string.pref__advanced__force_private_mode__summary),
|
||||
ListPreference(
|
||||
prefs.advanced.incognitoMode,
|
||||
iconId = R.drawable.ic_incognito,
|
||||
title = stringRes(R.string.pref__advanced__incognito_mode__label),
|
||||
entries = IncognitoMode.listEntries(),
|
||||
)
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.backup_and_restore__title)) {
|
||||
|
||||
@@ -43,6 +43,7 @@ import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.rippleClickable
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
|
||||
import dev.patrickgold.florisboard.lib.io.FileRegistry
|
||||
import dev.patrickgold.florisboard.lib.io.ZipUtils
|
||||
@@ -70,11 +71,10 @@ object Backup {
|
||||
class FilesSelector {
|
||||
var jetprefDatastore by mutableStateOf(true)
|
||||
var imeKeyboard by mutableStateOf(true)
|
||||
var imeSpelling by mutableStateOf(true)
|
||||
var imeTheme by mutableStateOf(true)
|
||||
|
||||
fun atLeastOneSelected(): Boolean {
|
||||
return jetprefDatastore || imeKeyboard || imeSpelling || imeTheme
|
||||
return jetprefDatastore || imeKeyboard || imeTheme
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,8 @@ fun BackupScreen() = FlorisScreen {
|
||||
context.showLongToast(R.string.backup_and_restore__back_up__success)
|
||||
navController.popBackStack()
|
||||
}.onFailure { error ->
|
||||
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.localizedMessage)
|
||||
flogError { error.stackTraceToString() }
|
||||
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
|
||||
backupWorkspace = null
|
||||
}
|
||||
},
|
||||
@@ -137,11 +138,6 @@ fun BackupScreen() = FlorisScreen {
|
||||
dir.copyRecursively(workspaceFilesDir.subDir(ExtensionManager.IME_KEYBOARD_PATH))
|
||||
}
|
||||
}
|
||||
if (backupFilesSelector.imeSpelling) {
|
||||
context.filesDir.subDir(ExtensionManager.IME_SPELLING_PATH).let { dir ->
|
||||
dir.copyRecursively(workspaceFilesDir.subDir(ExtensionManager.IME_SPELLING_PATH))
|
||||
}
|
||||
}
|
||||
if (backupFilesSelector.imeTheme) {
|
||||
context.filesDir.subDir(ExtensionManager.IME_THEME_PATH).let { dir ->
|
||||
dir.copyRecursively(workspaceFilesDir.subDir(ExtensionManager.IME_THEME_PATH))
|
||||
@@ -179,7 +175,8 @@ fun BackupScreen() = FlorisScreen {
|
||||
}
|
||||
}
|
||||
}.onFailure { error ->
|
||||
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.localizedMessage)
|
||||
flogError { error.stackTraceToString() }
|
||||
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
|
||||
backupWorkspace = null
|
||||
}
|
||||
}
|
||||
@@ -251,11 +248,6 @@ internal fun BackupFilesSelector(
|
||||
checked = filesSelector.imeKeyboard,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_ime_keyboard),
|
||||
)
|
||||
CheckboxListItem(
|
||||
onClick = { filesSelector.imeSpelling = !filesSelector.imeSpelling },
|
||||
checked = filesSelector.imeSpelling,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_ime_spelling),
|
||||
)
|
||||
CheckboxListItem(
|
||||
onClick = { filesSelector.imeTheme = !filesSelector.imeTheme },
|
||||
checked = filesSelector.imeTheme,
|
||||
|
||||
@@ -159,16 +159,6 @@ fun RestoreScreen() = FlorisScreen {
|
||||
srcDir.copyRecursively(dstDir, overwrite = true)
|
||||
}
|
||||
}
|
||||
if (restoreFilesSelector.imeSpelling) {
|
||||
val srcDir = workspaceFilesDir.subDir(ExtensionManager.IME_SPELLING_PATH)
|
||||
val dstDir = context.filesDir.subDir(ExtensionManager.IME_SPELLING_PATH)
|
||||
if (shouldReset) {
|
||||
dstDir.deleteContentsRecursively()
|
||||
}
|
||||
if (srcDir.exists()) {
|
||||
srcDir.copyRecursively(dstDir, overwrite = true)
|
||||
}
|
||||
}
|
||||
if (restoreFilesSelector.imeTheme) {
|
||||
val srcDir = workspaceFilesDir.subDir(ExtensionManager.IME_THEME_PATH)
|
||||
val dstDir = context.filesDir.subDir(ExtensionManager.IME_THEME_PATH)
|
||||
|
||||
@@ -65,7 +65,7 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.cleanUpAfter,
|
||||
title = stringRes(R.string.pref__clipboard__clean_up_after__label),
|
||||
unit = pluralsRes(R.plurals.unit__minutes__written, prefs.clipboard.cleanUpAfter.get()),
|
||||
valueLabel = { pluralsRes(R.plurals.unit__minutes__written, it, "v" to it) },
|
||||
min = 0,
|
||||
max = 120,
|
||||
stepIncrement = 5,
|
||||
@@ -79,7 +79,7 @@ fun ClipboardScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.clipboard.maxHistorySize,
|
||||
title = stringRes(R.string.pref__clipboard__max_history_size__label),
|
||||
unit = pluralsRes(R.plurals.unit__items__written, prefs.clipboard.maxHistorySize.get()),
|
||||
valueLabel = { pluralsRes(R.plurals.unit__items__written, it, "v" to it) },
|
||||
min = 5,
|
||||
max = 100,
|
||||
stepIncrement = 5,
|
||||
|
||||
@@ -60,7 +60,7 @@ fun GesturesScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.glide.trailDuration,
|
||||
title = stringRes(R.string.pref__glide_trail_fade_duration),
|
||||
unit = stringRes(R.string.unit__milliseconds__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__milliseconds__symbol, "v" to it) },
|
||||
min = 0,
|
||||
max = 500,
|
||||
stepIncrement = 10,
|
||||
@@ -75,7 +75,7 @@ fun GesturesScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.glide.previewRefreshDelay,
|
||||
title = stringRes(R.string.pref__glide_preview_refresh_delay),
|
||||
unit = stringRes(R.string.unit__milliseconds__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__milliseconds__symbol, "v" to it) },
|
||||
min = 50,
|
||||
max = 500,
|
||||
stepIncrement = 25,
|
||||
@@ -153,7 +153,7 @@ fun GesturesScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.gestures.swipeVelocityThreshold,
|
||||
title = stringRes(R.string.pref__gestures__swipe_velocity_threshold__label),
|
||||
unit = stringRes(R.string.unit__display_pixel_per_seconds__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__display_pixel_per_seconds__symbol, "v" to it) },
|
||||
min = 400,
|
||||
max = 4000,
|
||||
stepIncrement = 100,
|
||||
@@ -161,7 +161,7 @@ fun GesturesScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.gestures.swipeDistanceThreshold,
|
||||
title = stringRes(R.string.pref__gestures__swipe_distance_threshold__label),
|
||||
unit = stringRes(R.string.unit__display_pixel__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__display_pixel__symbol, "v" to it) },
|
||||
min = 12,
|
||||
max = 72,
|
||||
stepIncrement = 1,
|
||||
|
||||
@@ -17,12 +17,18 @@
|
||||
package dev.patrickgold.florisboard.app.settings.keyboard
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.keyboard.InputFeedbackController
|
||||
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
|
||||
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
|
||||
import dev.patrickgold.florisboard.lib.android.AndroidVersion
|
||||
import dev.patrickgold.florisboard.lib.android.systemVibratorOrNull
|
||||
import dev.patrickgold.florisboard.lib.android.vibrate
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
|
||||
@@ -31,24 +37,24 @@ import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
fun InputFeedbackScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__input_feedback__title)
|
||||
previewFieldVisible = true
|
||||
iconSpaceReserved = false
|
||||
|
||||
val context = LocalContext.current
|
||||
val vibrator = context.systemVibratorOrNull()
|
||||
|
||||
content {
|
||||
PreferenceGroup(title = stringRes(R.string.pref__input_feedback__group_audio__label)) {
|
||||
SwitchPreference(
|
||||
prefs.inputFeedback.audioEnabled,
|
||||
ListPreference(
|
||||
listPref = prefs.inputFeedback.audioActivationMode,
|
||||
switchPref = prefs.inputFeedback.audioEnabled,
|
||||
title = stringRes(R.string.pref__input_feedback__audio_enabled__label),
|
||||
summary = stringRes(R.string.pref__input_feedback__audio_enabled__summary),
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.inputFeedback.audioIgnoreSystemSettings,
|
||||
title = stringRes(R.string.pref__input_feedback__audio_ignore_system_settings__label),
|
||||
summary = stringRes(R.string.pref__input_feedback__audio_ignore_system_settings__summary),
|
||||
enabledIf = { prefs.inputFeedback.audioEnabled isEqualTo true },
|
||||
summarySwitchDisabled = stringRes(R.string.pref__input_feedback__audio_enabled__summary_disabled),
|
||||
entries = InputFeedbackActivationMode.audioListEntries(),
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.inputFeedback.audioVolume,
|
||||
title = stringRes(R.string.pref__input_feedback__audio_volume__label),
|
||||
unit = stringRes(R.string.unit__percent__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__percent__symbol, "v" to it) },
|
||||
min = 1,
|
||||
max = 100,
|
||||
stepIncrement = 1,
|
||||
@@ -87,42 +93,71 @@ fun InputFeedbackScreen() = FlorisScreen {
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__input_feedback__group_haptic__label)) {
|
||||
SwitchPreference(
|
||||
prefs.inputFeedback.hapticEnabled,
|
||||
ListPreference(
|
||||
listPref = prefs.inputFeedback.hapticActivationMode,
|
||||
switchPref = prefs.inputFeedback.hapticEnabled,
|
||||
title = stringRes(R.string.pref__input_feedback__haptic_enabled__label),
|
||||
summary = stringRes(R.string.pref__input_feedback__haptic_enabled__summary),
|
||||
summarySwitchDisabled = stringRes(R.string.pref__input_feedback__haptic_enabled__summary_disabled),
|
||||
entries = InputFeedbackActivationMode.hapticListEntries(),
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.inputFeedback.hapticIgnoreSystemSettings,
|
||||
title = stringRes(R.string.pref__input_feedback__haptic_ignore_system_settings__label),
|
||||
summary = stringRes(R.string.pref__input_feedback__haptic_ignore_system_settings__summary),
|
||||
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.inputFeedback.hapticUseVibrator,
|
||||
title = stringRes(R.string.pref__input_feedback__haptic_use_vibrator__label),
|
||||
summary = stringRes(R.string.pref__input_feedback__haptic_use_vibrator__summary),
|
||||
ListPreference(
|
||||
prefs.inputFeedback.hapticVibrationMode,
|
||||
title = stringRes(R.string.pref__input_feedback__haptic_vibration_mode__label),
|
||||
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true },
|
||||
entries = HapticVibrationMode.listEntries(),
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.inputFeedback.hapticVibrationDuration,
|
||||
title = stringRes(R.string.pref__input_feedback__haptic_vibration_duration__label),
|
||||
unit = stringRes(R.string.unit__milliseconds__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__milliseconds__symbol, "v" to it) },
|
||||
summary = {
|
||||
if (vibrator == null || !vibrator.hasVibrator()) {
|
||||
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_no_vibrator)
|
||||
} else {
|
||||
stringRes(R.string.unit__milliseconds__symbol, "v" to it)
|
||||
}
|
||||
},
|
||||
min = 1,
|
||||
max = 100,
|
||||
stepIncrement = 1,
|
||||
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true && prefs.inputFeedback.hapticUseVibrator isEqualTo true },
|
||||
onPreviewSelectedValue = { duration ->
|
||||
val strength = prefs.inputFeedback.hapticVibrationStrength.get()
|
||||
vibrator?.vibrate(duration, strength)
|
||||
},
|
||||
enabledIf = {
|
||||
prefs.inputFeedback.hapticEnabled isEqualTo true &&
|
||||
prefs.inputFeedback.hapticVibrationMode isEqualTo HapticVibrationMode.USE_VIBRATOR_DIRECTLY &&
|
||||
vibrator != null && vibrator.hasVibrator()
|
||||
},
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.inputFeedback.hapticVibrationStrength,
|
||||
title = stringRes(R.string.pref__input_feedback__haptic_vibration_strength__label),
|
||||
summary = InputFeedbackController.generateVibrationStrengthErrorSummary() ?:
|
||||
stringRes(R.string.unit__percent__symbol),
|
||||
unit = stringRes(R.string.unit__percent__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__percent__symbol, "v" to it) },
|
||||
summary = { strength ->
|
||||
if (vibrator == null || !vibrator.hasVibrator()) {
|
||||
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_no_vibrator)
|
||||
} else if (AndroidVersion.ATMOST_API25_N_MR1) {
|
||||
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_unsupported_android_version)
|
||||
} else if (!vibrator.hasAmplitudeControl()) {
|
||||
stringRes(R.string.pref__input_feedback__haptic_vibration_strength__summary_no_amplitude_ctrl)
|
||||
} else {
|
||||
stringRes(R.string.unit__percent__symbol, "v" to strength)
|
||||
}
|
||||
},
|
||||
min = 1,
|
||||
max = 100,
|
||||
stepIncrement = 1,
|
||||
enabledIf = { prefs.inputFeedback.hapticEnabled isEqualTo true && prefs.inputFeedback.hapticUseVibrator isEqualTo true && InputFeedbackController.hasAmplitudeControl() },
|
||||
onPreviewSelectedValue = { strength ->
|
||||
val duration = prefs.inputFeedback.hapticVibrationDuration.get()
|
||||
vibrator?.vibrate(duration, strength)
|
||||
},
|
||||
enabledIf = {
|
||||
prefs.inputFeedback.hapticEnabled isEqualTo true &&
|
||||
prefs.inputFeedback.hapticVibrationMode isEqualTo HapticVibrationMode.USE_VIBRATOR_DIRECTLY &&
|
||||
vibrator != null && vibrator.hasVibrator() &&
|
||||
AndroidVersion.ATLEAST_API26_O && vibrator.hasAmplitudeControl()
|
||||
},
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.inputFeedback.hapticFeatKeyPress,
|
||||
|
||||
@@ -83,7 +83,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__keyboard__font_size_multiplier__label),
|
||||
primaryLabel = stringRes(R.string.screen_orientation__portrait),
|
||||
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
|
||||
unit = stringRes(R.string.unit__percent__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__percent__symbol, "v" to it) },
|
||||
min = 50,
|
||||
max = 150,
|
||||
stepIncrement = 5,
|
||||
@@ -98,7 +98,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.keyboard.oneHandedModeScaleFactor,
|
||||
title = stringRes(R.string.pref__keyboard__one_handed_mode_scale_factor__label),
|
||||
unit = stringRes(R.string.unit__percent__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__percent__symbol, "v" to it) },
|
||||
min = 70,
|
||||
max = 90,
|
||||
stepIncrement = 1,
|
||||
@@ -115,7 +115,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__keyboard__height_factor__label),
|
||||
primaryLabel = stringRes(R.string.screen_orientation__portrait),
|
||||
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
|
||||
unit = stringRes(R.string.unit__percent__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__percent__symbol, "v" to it) },
|
||||
min = 50,
|
||||
max = 150,
|
||||
stepIncrement = 5,
|
||||
@@ -126,7 +126,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__keyboard__key_spacing__label),
|
||||
primaryLabel = stringRes(R.string.screen_orientation__vertical),
|
||||
secondaryLabel = stringRes(R.string.screen_orientation__horizontal),
|
||||
unit = stringRes(R.string.unit__display_pixel__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__display_pixel__symbol, "v" to it) },
|
||||
min = 0.0f,
|
||||
max = 10.0f,
|
||||
stepIncrement = 0.5f,
|
||||
@@ -137,7 +137,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__keyboard__bottom_offset__label),
|
||||
primaryLabel = stringRes(R.string.screen_orientation__portrait),
|
||||
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
|
||||
unit = stringRes(R.string.unit__display_pixel__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__display_pixel__symbol, "v" to it) },
|
||||
min = 0,
|
||||
max = 60,
|
||||
stepIncrement = 1,
|
||||
@@ -162,7 +162,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
DialogSliderPreference(
|
||||
prefs.keyboard.longPressDelay,
|
||||
title = stringRes(R.string.pref__keyboard__long_press_delay__label),
|
||||
unit = stringRes(R.string.unit__milliseconds__symbol),
|
||||
valueLabel = { stringRes(R.string.unit__milliseconds__symbol, "v" to it) },
|
||||
min = 100,
|
||||
max = 700,
|
||||
stepIncrement = 10,
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.material.ExtendedFloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -73,8 +74,8 @@ fun LocalizationScreen() = FlorisScreen {
|
||||
entries = DisplayLanguageNamesIn.listEntries(),
|
||||
)
|
||||
PreferenceGroup(title = stringRes(R.string.settings__localization__group_subtypes__label)) {
|
||||
val subtypes by subtypeManager.subtypes.observeAsNonNullState()
|
||||
if (subtypes.isNullOrEmpty()) {
|
||||
val subtypes by subtypeManager.subtypesFlow.collectAsState()
|
||||
if (subtypes.isEmpty()) {
|
||||
FlorisWarningCard(
|
||||
modifier = Modifier.padding(all = 8.dp),
|
||||
text = stringRes(R.string.settings__localization__subtype_no_subtypes_configured_warning),
|
||||
|
||||
@@ -58,6 +58,7 @@ import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.core.SubtypeJsonConfig
|
||||
import dev.patrickgold.florisboard.ime.core.SubtypeLayoutMap
|
||||
import dev.patrickgold.florisboard.ime.core.SubtypeNlpProviderMap
|
||||
import dev.patrickgold.florisboard.ime.core.SubtypePreset
|
||||
import dev.patrickgold.florisboard.ime.keyboard.LayoutArrangementComponent
|
||||
import dev.patrickgold.florisboard.ime.keyboard.LayoutType
|
||||
@@ -79,6 +80,10 @@ import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
private val SelectComponentName = ExtensionComponentName("00", "00")
|
||||
private val SelectNlpProviderId = SelectComponentName.toString()
|
||||
private val SelectNlpProviders = SubtypeNlpProviderMap(
|
||||
spelling = SelectNlpProviderId,
|
||||
)
|
||||
private val SelectLayoutMap = SubtypeLayoutMap(
|
||||
characters = SelectComponentName,
|
||||
symbols = SelectComponentName,
|
||||
@@ -100,8 +105,10 @@ private class SubtypeEditorState(init: Subtype?) {
|
||||
id = editor.id.value,
|
||||
primaryLocale = editor.primaryLocale.value,
|
||||
secondaryLocales = editor.secondaryLocales.value,
|
||||
nlpProviders = editor.nlpProviders.value,
|
||||
composer = editor.composer.value,
|
||||
currencySet = editor.currencySet.value,
|
||||
punctuationRule = editor.punctuationRule.value,
|
||||
popupMapping = editor.popupMapping.value,
|
||||
layoutMap = editor.layoutMap.value,
|
||||
)
|
||||
@@ -117,8 +124,10 @@ private class SubtypeEditorState(init: Subtype?) {
|
||||
val id: MutableState<Long> = mutableStateOf(init?.id ?: -1)
|
||||
val primaryLocale: MutableState<FlorisLocale> = mutableStateOf(init?.primaryLocale ?: SelectLocale)
|
||||
val secondaryLocales: MutableState<List<FlorisLocale>> = mutableStateOf(init?.secondaryLocales ?: listOf())
|
||||
val nlpProviders: MutableState<SubtypeNlpProviderMap> = mutableStateOf(init?.nlpProviders ?: Subtype.DEFAULT.nlpProviders)
|
||||
val composer: MutableState<ExtensionComponentName> = mutableStateOf(init?.composer ?: SelectComponentName)
|
||||
val currencySet: MutableState<ExtensionComponentName> = mutableStateOf(init?.currencySet ?: SelectComponentName)
|
||||
val punctuationRule: MutableState<ExtensionComponentName> = mutableStateOf(init?.punctuationRule ?: Subtype.DEFAULT.punctuationRule)
|
||||
val popupMapping: MutableState<ExtensionComponentName> = mutableStateOf(init?.popupMapping ?: SelectComponentName)
|
||||
val layoutMap: MutableState<SubtypeLayoutMap> = mutableStateOf(init?.layoutMap ?: SelectLayoutMap)
|
||||
|
||||
@@ -128,14 +137,18 @@ private class SubtypeEditorState(init: Subtype?) {
|
||||
secondaryLocales.value = subtype.secondaryLocales
|
||||
composer.value = subtype.composer
|
||||
currencySet.value = subtype.currencySet
|
||||
punctuationRule.value = subtype.punctuationRule
|
||||
popupMapping.value = subtype.popupMapping
|
||||
layoutMap.value = subtype.layoutMap
|
||||
}
|
||||
|
||||
fun toSubtype() = runCatching<Subtype> {
|
||||
check(primaryLocale.value != SelectLocale)
|
||||
check(nlpProviders.value.spelling != SelectNlpProviderId)
|
||||
check(nlpProviders.value.suggestion != SelectNlpProviderId)
|
||||
check(composer.value != SelectComponentName)
|
||||
check(currencySet.value != SelectComponentName)
|
||||
check(punctuationRule.value != SelectComponentName)
|
||||
check(popupMapping.value != SelectComponentName)
|
||||
check(layoutMap.value.characters != SelectComponentName)
|
||||
check(layoutMap.value.symbols != SelectComponentName)
|
||||
@@ -146,8 +159,8 @@ private class SubtypeEditorState(init: Subtype?) {
|
||||
check(layoutMap.value.phone != SelectComponentName)
|
||||
check(layoutMap.value.phone2 != SelectComponentName)
|
||||
Subtype(
|
||||
id.value, primaryLocale.value, secondaryLocales.value, composer.value,
|
||||
currencySet.value, popupMapping.value, layoutMap.value,
|
||||
id.value, primaryLocale.value, secondaryLocales.value, nlpProviders.value, composer.value,
|
||||
currencySet.value, punctuationRule.value, popupMapping.value, layoutMap.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,11 @@
|
||||
package dev.patrickgold.florisboard.app.settings.media
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.pluralsRes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
@@ -41,14 +39,15 @@ fun MediaScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.prefs__media__emoji_preferred_skin_tone),
|
||||
entries = EmojiSkinTone.listEntries(),
|
||||
)
|
||||
val maxSize by prefs.media.emojiRecentlyUsedMaxSize.observeAsState()
|
||||
DialogSliderPreference(
|
||||
prefs.media.emojiRecentlyUsedMaxSize,
|
||||
title = stringRes(R.string.prefs__media__emoji_recently_used_max_size),
|
||||
summary = if (maxSize == 0) {
|
||||
stringRes(R.string.general__unlimited)
|
||||
} else {
|
||||
pluralsRes(R.plurals.unit__items__written, maxSize)
|
||||
valueLabel = { maxSize ->
|
||||
if (maxSize == 0) {
|
||||
stringRes(R.string.general__unlimited)
|
||||
} else {
|
||||
pluralsRes(R.plurals.unit__items__written, maxSize, "v" to maxSize)
|
||||
}
|
||||
},
|
||||
min = 0,
|
||||
max = 120,
|
||||
|
||||
@@ -18,8 +18,9 @@ package dev.patrickgold.florisboard.app.settings.smartbar
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.SecondaryRowPlacement
|
||||
import dev.patrickgold.florisboard.ime.text.smartbar.SmartbarRowType
|
||||
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
|
||||
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
@@ -37,41 +38,44 @@ fun SmartbarScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__smartbar__enabled__label),
|
||||
summary = stringRes(R.string.pref__smartbar__enabled__summary),
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.smartbar.flipToggles,
|
||||
title = stringRes(R.string.pref__smartbar__flip_toggles__label),
|
||||
summary = stringRes(R.string.pref__smartbar__flip_toggles__summary),
|
||||
ListPreference(
|
||||
listPref = prefs.smartbar.layout,
|
||||
title = stringRes(R.string.pref__smartbar__layout__label),
|
||||
entries = SmartbarLayout.listEntries(),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
)
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_primary_actions__label)) {
|
||||
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_layout_specific__label)) {
|
||||
ListPreference(
|
||||
prefs.suggestion.displayMode,
|
||||
title = stringRes(R.string.pref__suggestion__display_mode__label),
|
||||
entries = CandidatesDisplayMode.listEntries(),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
visibleIf = { prefs.smartbar.layout isNotEqualTo SmartbarLayout.ACTIONS_ONLY },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.smartbar.primaryActionsAutoExpandCollapse,
|
||||
title = stringRes(R.string.pref__smartbar__primary_actions_auto_expand_collapse__label),
|
||||
summary = stringRes(R.string.pref__smartbar__primary_actions_auto_expand_collapse__summary),
|
||||
prefs.smartbar.flipToggles,
|
||||
title = stringRes(R.string.pref__smartbar__flip_toggles__label),
|
||||
summary = stringRes(R.string.pref__smartbar__flip_toggles__summary),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
visibleIf = {
|
||||
prefs.smartbar.layout isEqualTo SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED ||
|
||||
prefs.smartbar.layout isEqualTo SmartbarLayout.SUGGESTIONS_ACTIONS_EXTENDED
|
||||
},
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.smartbar.sharedActionsAutoExpandCollapse,
|
||||
title = stringRes(R.string.pref__smartbar__shared_actions_auto_expand_collapse__label),
|
||||
summary = stringRes(R.string.pref__smartbar__shared_actions_auto_expand_collapse__summary),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
visibleIf = { prefs.smartbar.layout isEqualTo SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED },
|
||||
)
|
||||
ListPreference(
|
||||
prefs.smartbar.primaryActionsRowType,
|
||||
title = stringRes(R.string.pref__smartbar__any_row_type__label),
|
||||
entries = SmartbarRowType.listEntries(),
|
||||
listPref = prefs.smartbar.extendedActionsPlacement,
|
||||
title = stringRes(R.string.pref__smartbar__extended_actions_placement__label),
|
||||
entries = ExtendedActionsPlacement.listEntries(),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__smartbar__group_secondary_actions__label)) {
|
||||
ListPreference(
|
||||
listPref = prefs.smartbar.secondaryActionsPlacement,
|
||||
switchPref = prefs.smartbar.secondaryActionsEnabled,
|
||||
title = stringRes(R.string.pref__smartbar__secondary_actions_enabled__label),
|
||||
entries = SecondaryRowPlacement.listEntries(),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
)
|
||||
ListPreference(
|
||||
prefs.smartbar.secondaryActionsRowType,
|
||||
title = stringRes(R.string.pref__smartbar__any_row_type__label),
|
||||
entries = SmartbarRowType.listEntries(),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true && prefs.smartbar.secondaryActionsEnabled isEqualTo true },
|
||||
visibleIf = { prefs.smartbar.layout isEqualTo SmartbarLayout.SUGGESTIONS_ACTIONS_EXTENDED },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.spelling
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingExtensionEditor
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisDropdownMenu
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisStep
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisStepLayout
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisStepState
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.spellingManager
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
|
||||
private object Step {
|
||||
const val SelectSource: Int = 1
|
||||
const val ImportArchive: Int = 2
|
||||
const val VerifyImport: Int = 3
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ImportSpellingArchiveScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__spelling__import__title)
|
||||
scrollable = false
|
||||
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
val extensionManager by context.extensionManager()
|
||||
val spellingManager by context.spellingManager()
|
||||
|
||||
val sources = remember { listOf("-") + SpellingManager.Config.importSources.map { it.label } }
|
||||
var sourceExpanded by remember { mutableStateOf(false) }
|
||||
var sourceSelectedIndex by rememberSaveable { mutableStateOf(0) }
|
||||
|
||||
var importArchiveUri by remember { mutableStateOf<Uri?>(null) }
|
||||
var importArchiveEditor by remember { mutableStateOf<SpellingExtensionEditor?>(null) }
|
||||
var importArchiveError by remember { mutableStateOf<Throwable?>(null) }
|
||||
var writeExtError by remember { mutableStateOf<Throwable?>(null) }
|
||||
var errorDialogVisible by remember { mutableStateOf(false) }
|
||||
val importArchiveLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent(),
|
||||
onResult = { uri ->
|
||||
// If uri is null it indicates that the selection activity
|
||||
// was cancelled (mostly by pressing the back button), so
|
||||
// we don't display an error message here.
|
||||
if (uri == null) return@rememberLauncherForActivityResult
|
||||
val importSource = SpellingManager.Config.importSources[sourceSelectedIndex - 1]
|
||||
spellingManager.prepareImport(importSource.id, uri).fold(
|
||||
onSuccess = {
|
||||
importArchiveUri = uri
|
||||
importArchiveEditor = it
|
||||
importArchiveError = null
|
||||
writeExtError = null
|
||||
},
|
||||
onFailure = {
|
||||
importArchiveUri = null
|
||||
importArchiveEditor = null
|
||||
importArchiveError = it
|
||||
writeExtError = null
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val stepState = rememberSaveable(saver = FlorisStepState.Saver) {
|
||||
FlorisStepState.new(init = Step.SelectSource)
|
||||
}
|
||||
|
||||
content {
|
||||
LaunchedEffect(sourceSelectedIndex, importArchiveEditor) {
|
||||
stepState.setCurrentAuto(when {
|
||||
sourceSelectedIndex <= 0 -> Step.SelectSource
|
||||
importArchiveEditor == null -> Step.ImportArchive
|
||||
else -> Step.VerifyImport
|
||||
})
|
||||
}
|
||||
|
||||
FlorisStepLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
stepState = stepState,
|
||||
steps = listOf(
|
||||
FlorisStep(
|
||||
id = Step.SelectSource,
|
||||
title = if (stepState.getCurrent().value > Step.SelectSource) {
|
||||
sources.getOrElse(sourceSelectedIndex) { "undefined" }
|
||||
} else {
|
||||
stringRes(R.string.settings__spelling__import_archive_s1__title)
|
||||
},
|
||||
) {
|
||||
StepText(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__import_archive_s1__p1),
|
||||
)
|
||||
FlorisDropdownMenu(
|
||||
items = sources,
|
||||
expanded = sourceExpanded,
|
||||
selectedIndex = sourceSelectedIndex,
|
||||
onSelectItem = { sourceSelectedIndex = it },
|
||||
onExpandRequest = { sourceExpanded = true },
|
||||
onDismissRequest = { sourceExpanded = false },
|
||||
)
|
||||
},
|
||||
FlorisStep(
|
||||
id = Step.ImportArchive,
|
||||
title = if (stepState.getCurrent().value > Step.ImportArchive && importArchiveEditor != null) {
|
||||
importArchiveEditor?.meta?.title ?: "undefined"
|
||||
} else {
|
||||
stringRes(R.string.settings__spelling__import_archive_s2__title)
|
||||
},
|
||||
) {
|
||||
StepText(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__import_archive_s2__p1),
|
||||
)
|
||||
StepText(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
text = importArchiveUri?.toString() ?: "No file selected.",
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
if (importArchiveError != null) {
|
||||
ErrorCard(
|
||||
onActionClick = { errorDialogVisible = true }
|
||||
)
|
||||
}
|
||||
StepButton(
|
||||
onClick = { importArchiveLauncher.launch("*/*") },
|
||||
label = stringRes(R.string.action__select_file),
|
||||
)
|
||||
},
|
||||
FlorisStep(
|
||||
id = Step.VerifyImport,
|
||||
title = stringRes(R.string.settings__spelling__import_any_s3__title),
|
||||
) {
|
||||
StepText(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__import_any_s3__p1),
|
||||
)
|
||||
StepText(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
// TODO: add verify view
|
||||
text = "TODO: add verify view",
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
if (writeExtError != null) {
|
||||
ErrorCard(
|
||||
onActionClick = { errorDialogVisible = true }
|
||||
)
|
||||
}
|
||||
StepButton(
|
||||
onClick = {
|
||||
runCatching {
|
||||
extensionManager.import(importArchiveEditor!!.build())
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
onFailure = {
|
||||
writeExtError = it
|
||||
},
|
||||
)
|
||||
},
|
||||
label = stringRes(R.string.action__import),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
if (errorDialogVisible) {
|
||||
JetPrefAlertDialog(
|
||||
title = "Detailed crash log",
|
||||
onDismiss = { errorDialogVisible = false },
|
||||
) {
|
||||
if (importArchiveError != null) {
|
||||
Text(
|
||||
text = importArchiveError.toString(),
|
||||
style = MaterialTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
if (writeExtError != null) {
|
||||
Text(
|
||||
text = writeExtError.toString(),
|
||||
style = MaterialTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ErrorCard(
|
||||
onActionClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(modifier = modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1.0f)
|
||||
.padding(end = 8.dp),
|
||||
text = "Something went wrong!",
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
TextButton(onClick = { onActionClick() }) {
|
||||
Text(text = "Details")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.spelling
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionList
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisInfoCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun ManageSpellingDictsScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__spelling__manage_dicts__title)
|
||||
previewFieldVisible = true
|
||||
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
val extensionManager by context.extensionManager()
|
||||
|
||||
floatingActionButton {
|
||||
FloatingActionButton(
|
||||
onClick = { navController.navigate(Routes.Settings.ImportSpellingArchive) },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_add),
|
||||
contentDescription = "Add dictionary",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
content {
|
||||
FlorisInfoCard(
|
||||
modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__dict_sources_info__title),
|
||||
onClick = { navController.navigate(Routes.Settings.SpellingInfo) },
|
||||
)
|
||||
|
||||
val spellingDicts by extensionManager.spellingDicts.observeAsState()
|
||||
if (spellingDicts != null && spellingDicts!!.isNotEmpty()) {
|
||||
ExtensionList(
|
||||
extList = spellingDicts!!,
|
||||
summaryProvider = { ext ->
|
||||
"${ext.spelling.locale.languageTag()} | ${ext.meta.version} | ${ext.spelling.originalSourceId}"
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__manage_dicts__no_dicts_installed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.spelling
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingManager
|
||||
import dev.patrickgold.florisboard.lib.android.launchUrl
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
|
||||
@Composable
|
||||
fun SpellingInfoScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__spelling__dict_sources_info__title)
|
||||
iconSpaceReserved = false
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
content {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__dict_sources_info__intro),
|
||||
)
|
||||
for (source in SpellingManager.Config.importSources) {
|
||||
Preference(
|
||||
title = source.label,
|
||||
summary = source.url,
|
||||
onClick = {
|
||||
source.url?.let { context.launchUrl(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = stringRes(R.string.settings__spelling__dict_sources_info__other),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.spelling
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.ime.spelling.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.lib.android.AndroidSettings
|
||||
import dev.patrickgold.florisboard.lib.android.launchActivity
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisCanvasIcon
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisErrorCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisSimpleCard
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisWarningCard
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun SpellingScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__spelling__title)
|
||||
previewFieldVisible = true
|
||||
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
val extensionManager by context.extensionManager()
|
||||
|
||||
val systemSpellCheckerId by AndroidSettings.Secure.observeAsState(
|
||||
key = "selected_spell_checker",
|
||||
foregroundOnly = true,
|
||||
)
|
||||
val systemSpellCheckerEnabled by AndroidSettings.Secure.observeAsState(
|
||||
key = "spell_checker_enabled",
|
||||
foregroundOnly = true,
|
||||
)
|
||||
val systemSpellCheckerSubtypeIndex by AndroidSettings.Secure.observeAsState(
|
||||
key = "selected_spell_checker_subtype",
|
||||
foregroundOnly = true,
|
||||
)
|
||||
val systemSpellCheckerPkgName = remember(systemSpellCheckerId) {
|
||||
runCatching {
|
||||
ComponentName.unflattenFromString(systemSpellCheckerId!!)!!.packageName
|
||||
}.getOrDefault("null")
|
||||
}
|
||||
val openSystemSpellCheckerSettings = {
|
||||
val componentToLaunch = ComponentName(
|
||||
"com.android.settings",
|
||||
"com.android.settings.Settings\$SpellCheckersSettingsActivity",
|
||||
)
|
||||
context.launchActivity {
|
||||
it.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
it.component = componentToLaunch
|
||||
}
|
||||
}
|
||||
val florisSpellCheckerEnabled =
|
||||
systemSpellCheckerEnabled == "1" &&
|
||||
systemSpellCheckerPkgName == context.packageName &&
|
||||
systemSpellCheckerSubtypeIndex != "0"
|
||||
|
||||
content {
|
||||
PreferenceGroup(title = stringRes(R.string.pref__spelling__active_spellchecker__label)) {
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||
if (systemSpellCheckerEnabled == "1") {
|
||||
if (systemSpellCheckerId == null) {
|
||||
FlorisWarningCard(
|
||||
text = stringRes(R.string.pref__spelling__active_spellchecker__summary_none),
|
||||
onClick = openSystemSpellCheckerSettings,
|
||||
)
|
||||
} else {
|
||||
var spellCheckerIcon: Drawable?
|
||||
var spellCheckerLabel = "Unknown"
|
||||
try {
|
||||
val pm = context.packageManager
|
||||
val remoteAppInfo = pm.getApplicationInfo(systemSpellCheckerPkgName, 0)
|
||||
spellCheckerIcon = pm.getApplicationIcon(remoteAppInfo)
|
||||
spellCheckerLabel = pm.getApplicationLabel(remoteAppInfo).toString()
|
||||
} catch (e: Exception) {
|
||||
spellCheckerIcon = null
|
||||
}
|
||||
FlorisSimpleCard(
|
||||
icon = {
|
||||
if (spellCheckerIcon != null) {
|
||||
FlorisCanvasIcon(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.requiredSize(32.dp),
|
||||
drawable = spellCheckerIcon,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.requiredSize(32.dp),
|
||||
painter = painterResource(R.drawable.ic_help_outline),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
text = spellCheckerLabel,
|
||||
secondaryText = systemSpellCheckerPkgName,
|
||||
contentPadding = PaddingValues(all = 8.dp),
|
||||
onClick = openSystemSpellCheckerSettings,
|
||||
)
|
||||
if (systemSpellCheckerPkgName == context.packageName && systemSpellCheckerSubtypeIndex == "0") {
|
||||
FlorisWarningCard(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
text = stringRes(
|
||||
R.string.pref__spelling__active_spellchecker__summary_use_sys_lang_set,
|
||||
"use_floris_config" to stringRes(R.string.settings__spelling__use_floris_config),
|
||||
),
|
||||
onClick = openSystemSpellCheckerSettings,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FlorisErrorCard(
|
||||
text = stringRes(R.string.pref__spelling__active_spellchecker__summary_disabled),
|
||||
onClick = openSystemSpellCheckerSettings,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val spellingDicts by extensionManager.spellingDicts.observeAsState()
|
||||
Preference(
|
||||
iconId = R.drawable.ic_library_books,
|
||||
title = stringRes(R.string.settings__spelling__manage_dicts__title),
|
||||
summary = stringRes(
|
||||
R.string.settings__spelling__manage_dicts__n_installed,
|
||||
"n" to (spellingDicts?.size ?: 0).toString(),
|
||||
),
|
||||
onClick = { navController.navigate(Routes.Settings.ManageSpellingDicts) },
|
||||
enabledIf = { florisSpellCheckerEnabled },
|
||||
)
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.pref__spelling__group_spellchecker_config__title)) {
|
||||
ListPreference(
|
||||
prefs.spelling.languageMode,
|
||||
iconId = R.drawable.ic_language,
|
||||
title = stringRes(R.string.pref__spelling__language_mode__label),
|
||||
entries = SpellingLanguageMode.listEntries(),
|
||||
enabledIf = { florisSpellCheckerEnabled },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.spelling.useContacts,
|
||||
iconId = R.drawable.ic_contacts,
|
||||
title = stringRes(R.string.pref__spelling__use_contacts__label),
|
||||
summary = stringRes(R.string.pref__spelling__use_contacts__summary),
|
||||
enabledIf = { florisSpellCheckerEnabled },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.spelling.useUdmEntries,
|
||||
iconId = R.drawable.ic_library_books,
|
||||
title = stringRes(R.string.pref__spelling__use_udm_entries__label),
|
||||
summary = stringRes(R.string.pref__spelling__use_udm_entries__summary),
|
||||
enabledIf = { florisSpellCheckerEnabled },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user