Compare commits

...

48 Commits

Author SHA1 Message Date
Patrick Goldinger
64040f0407 Release v0.3.12 2021-05-10 15:47:05 +02:00
Patrick Goldinger
0c1abdd507 Merge pull request #850 from X-yl/master
Stop glide suggestions disappearing and remove the redundant first suggestion
2021-05-10 15:28:21 +02:00
Patrick Goldinger
53594e3343 Fix glide logic not triggering when shift/caps is active (#847) 2021-05-10 15:22:45 +02:00
X-yl
c6c06b87c5 Stop glide suggestions disappearing and remove redundant first option 2021-05-10 16:52:50 +04:00
Patrick Goldinger
ae6eb5d72d Release v0.3.11 2021-05-10 00:06:07 +02:00
Patrick Goldinger
bbce53fdf4 Update README and open-source licenses 2021-05-09 20:45:33 +02:00
Patrick Goldinger
fdd7e60c1d Release v0.3.11-beta06 2021-05-09 16:49:10 +02:00
Patrick Goldinger
3b9a489d5c Update translations from Crowdin 2021-05-09 16:43:35 +02:00
Patrick Goldinger
de40ccb759 Fix KeyboardView null issue and rename Preferences (#785) 2021-05-09 16:30:52 +02:00
Patrick Goldinger
a04d584402 Remove unused dimension updating in glide typing manager 2021-05-09 15:09:51 +02:00
Patrick Goldinger
a14a6a798b Merge pull request #843 from X-yl/main-thread-clip
Set up clipboard history view on main thread
2021-05-09 14:57:42 +02:00
Patrick Goldinger
636d329dba Merge pull request #842 from X-yl/quick-glide-fix
Make normalizeBoxSide have a non-zero minimum longestSide
2021-05-09 14:56:16 +02:00
X-yl
d1e97dac57 Setup clipboard history on main thread.
No idea why it was on a different thread before? Think I just copied it
like that from MediaInputManager. Oops.
2021-05-09 16:36:40 +04:00
X-yl
41fbca8f65 Make normalizeBoxSide have a minimum longestSide
If it is zero, as sometimes happens because the dictionary contains
"words" like "yyy" and "ggg", it causes NaN issues.
2021-05-09 16:22:51 +04:00
Patrick Goldinger
535b48e5b4 Re-implement glide typing for new keyboard view 2021-05-09 13:53:27 +02:00
Patrick Goldinger
d3e8d35e5d Release v0.3.11-beta05 2021-05-08 15:40:58 +02:00
Patrick Goldinger
da8073141e Fix dynamic text size infinite loop bug (#825) 2021-05-08 15:35:16 +02:00
Patrick Goldinger
030665732a Merge pull request #817 from florisboard/user-dictionary
System / internal user dictionary
2021-05-08 11:22:32 +02:00
Patrick Goldinger
cc042dd77c Add input validation for user dictionary add/edit dialogs 2021-05-07 20:52:30 +02:00
Patrick Goldinger
773624769d Add shortcut support for user dictionary 2021-05-07 19:21:15 +02:00
Patrick Goldinger
0b1d0c74fe Fix query syntax issues for the system user dictionary 2021-05-07 04:22:17 +02:00
Patrick Goldinger
760d307478 Improve user dictionary UI 2021-05-07 04:01:47 +02:00
Patrick Goldinger
084c2abfc2 Add user dictionary manager UI for system and internal 2021-05-07 03:51:40 +02:00
Patrick Goldinger
df6b08024f Fix SQL user input causing crash 2021-05-06 19:04:36 +02:00
Patrick Goldinger
25498695ef Add basic UI wrapper for managing user dictionaries 2021-05-06 18:16:38 +02:00
Patrick Goldinger
5c81179d60 Add import/export backend logic for user dictionaries 2021-05-06 18:16:01 +02:00
Patrick Goldinger
58d150bb03 Update Kotlin to 1.5.0 2021-05-06 01:05:45 +02:00
Patrick Goldinger
2b1951ea5f Add internal and system user dictionary 2021-05-05 21:07:59 +02:00
Patrick Goldinger
5a5089c413 Fix AppCompat theme crash for Huawei devices (#799, #809) 2021-05-05 20:34:16 +02:00
Patrick Goldinger
dcd20e4b73 Add user dictionary preferences 2021-05-05 18:32:20 +02:00
Patrick Goldinger
dfec1f3804 Release v0.3.11-beta04 2021-05-04 20:37:39 +02:00
Patrick Goldinger
1fffe7f6e5 Fix ؤ Arabic Letter Waw with Hamza Above not written correctly (#438) 2021-05-04 19:48:54 +02:00
Patrick Goldinger
862a6cc82a Fix font size multiplier and also scale drawables (#540) 2021-05-04 18:56:21 +02:00
Patrick Goldinger
068caaf09b Add schwa symbol (ə) in Italian extended popups (#693) 2021-05-04 18:04:20 +02:00
Patrick Goldinger
93fb6d6016 Fix English (US) store description being cut off in F-Droid (#709) 2021-05-04 17:55:25 +02:00
Patrick Goldinger
28f0657bd7 Improve and fix KeyboardIconSet (#778) 2021-05-04 17:46:06 +02:00
Patrick Goldinger
8c53c2a057 Fix Hungarian layout not containing special keys 2021-05-04 17:34:32 +02:00
Patrick Goldinger
6251fb2ef6 Fix bottom row keys not shifted in Dvorak layout (#805) 2021-05-04 17:07:50 +02:00
Patrick Goldinger
cba2b873b8 Add devtool overlay for heap memory usage 2021-05-04 16:55:14 +02:00
Patrick Goldinger
d7ee61f316 Merge pull request #718 from X-yl/emoji-key-mem
Recycle EmojiKeyViews to reduce memory usage
2021-05-04 15:17:00 +02:00
X-yl
cf309f43a4 Recycle EmojiKeyViews for 15%-20% memory savings
Instead of creating an EmojiKeyView for every emoji, you can use a
RecyclerView to only create the ones which are visible on screen, and
then reuse them later.
2021-05-04 09:48:31 +04:00
Patrick Goldinger
93acee778e Release v0.3.11-beta03 2021-05-03 20:52:30 +02:00
Patrick Goldinger
c7f2f31c99 Fix gestures (except space+shift) 2021-05-03 20:11:08 +02:00
Patrick Goldinger
ebb8837d8a Fix Double NaN crashes (#774, #790) 2021-05-03 15:47:55 +02:00
Patrick Goldinger
f04f185034 Fix adaptive theme memory management (#763) 2021-05-02 12:17:35 +02:00
Patrick Goldinger
20de007d3b Add "Copied to system clipboard" toast to crash dialog (#724) 2021-05-01 12:20:56 +02:00
Patrick Goldinger
df01f6fe57 Fix theme manager buttons not wrapping (#777) 2021-05-01 11:51:15 +02:00
Patrick Goldinger
f9e6d7b09c Fix keyboard preview visual bugs (#776) 2021-05-01 11:51:15 +02:00
119 changed files with 3230 additions and 1025 deletions

5
.gitignore vendored
View File

@@ -39,4 +39,7 @@ captures/
# Keystore files
*.jks
crowdin.properties
crowdin.properties
# AndroidX Room schema JSONs
/app/schemas/

View File

@@ -74,8 +74,8 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
### Layouts
* [x] Latin character layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish, Norwegian, Swedish/Finnish, Icelandic, Danish,
Hungarian, Croatian, Polish, Romanian, Colemak, Dvorak, Turkish-Q, Turkish-F, ...)
* [x] Non-latin character layouts (Arabic, Persian, Kurdish, Greek, Russian (JCUKEN))
Hungarian, Croatian, Polish, Romanian, Colemak, Dvorak, Turkish-Q, Turkish-F, and more...)
* [x] Non-latin character layouts (Arabic, Persian, Kurdish, Greek, Russian (JCUKEN), and more...)
* [x] Adapt to situation in app (password, url, text, etc. )
* [x] Special character layout(s)
* [x] Numeric layout
@@ -93,6 +93,7 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
* [x] Subtype selection (language/layout)
* [x] Keyboard behaviour preferences
* [x] Gesture preferences
* [x] User dictionary manager (system and internal)
### Other useful features
* [x] One-handed mode
@@ -100,6 +101,8 @@ milestones, please refer to the [Feature roadmap](#feature-roadmap).
* [x] Clipboard manager/history
* [x] Integrated number row / symbols in character layouts
* [x] Gesture support
* [x] Full support for the system user dictionary (shared dictionary
between all keyboards) and a private, internal user dictionary
* [x] Full integration in IME service list of Android (xml/method)
(integration is internal-only, because Android's default subtype
implementation not really allows for dynamic language/layout
@@ -131,13 +134,14 @@ close as possible.
- Next-word suggestions by training language models. Data collected here is stored locally and never leaves
the user's device.
- Module C: Extension packs (base implementation with [#162])
- Module C: Extension packs (Implemented with [#162], reworked several times and still not stable)
- Ability to load dictionaries (and later potentially other cool
features too) only if needed to keep the core APK size small
- Currently unclear how exactly this will work, but this is definitely
a must-have feature
- A full implementation may come only in v0.5.0
- Module D: Glide typing
- Module D: Glide typing (Implemented with [#544])
- Swiping over the characters will automatically convert this to a word
- Possibly also add improvements based on the Flow keyboard
@@ -151,9 +155,11 @@ close as possible.
- Theme import/export
### [v0.5.0](https://github.com/florisboard/florisboard/milestone/5)
There's no exact roadmap yet but it is planned that the media part of
FlorisBoard (emojis, emoticons, kaomoji) gets a rework. Also as an extension
(requires v0.4.0/Module C) GIF support is planned.
There's no exact roadmap yet, but these are the most important points:
- Full layout customization in runtime
- Extensive rework and customization of the media input (emojis, emoticons, kaomoji)
- Better Smartbar customization
- As an extension GIF support
### > v0.5.0
This is completely open as of now and will gather planned features as time
@@ -166,6 +172,7 @@ Backlog (currently not assigned to any milestone):
[#91]: https://github.com/florisboard/florisboard/pull/91
[#162]: https://github.com/florisboard/florisboard/pull/162
[#329]: https://github.com/florisboard/florisboard/pull/329
[#544]: https://github.com/florisboard/florisboard/pull/544
## Contributing
Wanna contribute to FlorisBoard? That's great to hear! There are lots of
@@ -183,8 +190,8 @@ to get more information on this topic.
by [google](https://github.com/google)
* [Google Material icons](https://github.com/google/material-design-icons) by
[google](https://github.com/google)
* [Moshi JSON library](https://github.com/square/moshi) by
[square](https://github.com/square)
* [KotlinX serialization library](https://github.com/Kotlin/kotlinx.serialization) by
[Kotlin](https://github.com/Kotlin)
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
[Jared Rummler](https://github.com/jaredrummler)
* [Timber](https://github.com/JakeWharton/timber) by
@@ -194,7 +201,7 @@ to get more information on this topic.
## Usage notes for included binary dictionary files
All binary dictionaries included within this project in
(this)[app/src/main/assets/ime/dict) asset folder are built from various
(this)[app/src/main/assets/ime/dict] asset folder are built from various
sources, as stated below.
### Source 1: [wordfreq library by LuminosoInsight](https://github.com/LuminosoInsight/wordfreq):

View File

@@ -1,9 +1,9 @@
plugins {
id("com.android.application") version "4.1.3"
kotlin("android") version "1.5.0-RC"
kotlin("kapt") version "1.5.0-RC"
kotlin("plugin.serialization") version "1.5.0-RC"
id("com.android.application") version "4.2.0"
kotlin("android") version "1.5.0"
kotlin("kapt") version "1.5.0"
kotlin("plugin.serialization") version "1.5.0"
}
android {
@@ -24,10 +24,20 @@ android {
applicationId = "dev.patrickgold.florisboard"
minSdkVersion(23)
targetSdkVersion(30)
versionCode(37)
versionName("0.3.11")
versionCode(43)
versionName("0.3.12")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments += mapOf(
Pair("room.schemaLocation", "$projectDir/schemas"),
Pair("room.incremental", "true"),
Pair("room.expandProjection", "true")
)
}
}
}
buildFeatures {
@@ -47,7 +57,7 @@ android {
create("beta") // Needed because by default the "beta" BuildType does not exist
named("beta").configure {
applicationIdSuffix = ".beta"
versionNameSuffix = "-beta02"
versionNameSuffix = "-beta06"
proguardFiles.add(getDefaultProguardFile("proguard-android-optimize.txt"))
resValue("mipmap", "floris_app_icon", "@mipmap/ic_app_icon_beta")
@@ -84,7 +94,7 @@ dependencies {
implementation("androidx.preference", "preference-ktx", "1.1.1")
implementation("androidx.constraintlayout", "constraintlayout", "2.0.4")
implementation("androidx.lifecycle", "lifecycle-service", "2.2.0")
implementation("com.google.android", "flexbox", "2.0.1") // requires jcenter as of version 2.0.1
implementation("com.google.android", "flexbox", "2.0.1")
implementation("com.google.android.material", "material", "1.3.0")
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-android", "1.4.2")
implementation("org.jetbrains.kotlinx", "kotlinx-serialization-json", "1.1.0")

View File

@@ -66,6 +66,13 @@
</intent-filter>
</activity-alias>
<!-- User Dictionary Manager Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.UdmActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/settings__title"
android:theme="@style/SettingsTheme"/>
<!-- Theme Selector Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.ThemeManagerActivity"

View File

@@ -35,7 +35,7 @@
[
{ "code": 1584, "label": "ذ" },
{ "code": 1569, "label": "ء" },
{ "code": 65157, "label": "" },
{ "code": 1572, "label": "ؤ" },
{ "code": 1585, "label": "ر" },
{ "code": 1609, "label": "ى" },
{ "code": 1577, "label": "ة" },

View File

@@ -20,9 +20,10 @@
"e": {
"main": { "$": "auto_text_key", "code": 232, "label": "è" },
"relevant": [
{ "$": "auto_text_key", "code": 275, "label": "ē" },
{ "$": "auto_text_key", "code": 281, "label": "ę" },
{ "$": "auto_text_key", "code": 279, "label": "ė" },
{ "$": "auto_text_key", "code": 601, "label": "ə" },
{ "$": "auto_text_key", "code": 281, "label": "ę" },
{ "$": "auto_text_key", "code": 275, "label": "ē" },
{ "$": "auto_text_key", "code": 234, "label": "ê" },
{ "$": "auto_text_key", "code": 233, "label": "é" },
{ "$": "auto_text_key", "code": 235, "label": "ë" }

View File

@@ -1,8 +1,8 @@
{
"type": "characters",
"name": "hungarian",
"label": "Hungarian (QWERTZ)",
"authors": [ "zoli111, gabik65" ],
"label": "Hungarian",
"authors": [ "zoli111, gabik65", "patrickgold" ],
"direction": "ltr",
"arrangement": [
[
@@ -15,7 +15,8 @@
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 112, "label": "p" }
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 246, "label": "ö" }
],
[
{ "$": "auto_text_key", "code": 97, "label": "a" },
@@ -26,7 +27,9 @@
{ "$": "auto_text_key", "code": 104, "label": "h" },
{ "$": "auto_text_key", "code": 106, "label": "j" },
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 108, "label": "l" }
{ "$": "auto_text_key", "code": 108, "label": "l" },
{ "$": "auto_text_key", "code": 233, "label": "é" },
{ "$": "auto_text_key", "code": 225, "label": "á" }
],
[
{ "$": "auto_text_key", "code": 121, "label": "y" },
@@ -35,7 +38,8 @@
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 110, "label": "n" },
{ "$": "auto_text_key", "code": 109, "label": "m" }
{ "$": "auto_text_key", "code": 109, "label": "m" },
{ "$": "auto_text_key", "code": 252, "label": "ü" }
]
]
}

View File

@@ -12,11 +12,11 @@
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 113, "label": "q", "groupId": 1 },
{ "$": "auto_text_key", "code": 113, "label": "q", "groupId": 1 },
{ "code": -210, "label": "language_switch", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "code": 122, "label": "z", "groupId": 2 },
{ "$": "auto_text_key", "code": 122, "label": "z", "groupId": 2 },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -503,24 +503,6 @@ SOFTWARE.
<hr>
<h3>kotlin-result</h3>
<span>Copyright (c) 2017-2020 Michael Bull (https://www.michael-bull.com)</span>
<pre>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</pre>
<hr>
<h3>Material Icons</h3>
<span>Copyright 2018 Google LLC</span>
<pre>
@@ -729,24 +711,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
<hr>
<h3>Moshi</h3>
<span>Copyright 2015 Square, Inc.</span>
<pre>
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.
</pre>
<hr>
<h3>Timber</h3>
<span>Copyright 2013 Jake Wharton</span>
<pre>

View File

@@ -20,19 +20,19 @@ import android.content.*
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.CrashDialogBinding
import dev.patrickgold.florisboard.debug.*
import dev.patrickgold.florisboard.ime.core.PrefHelper
import java.util.*
import dev.patrickgold.florisboard.ime.core.Preferences
class CrashDialogActivity : AppCompatActivity() {
private lateinit var binding: CrashDialogBinding
private var stacktraces: List<CrashUtility.Stacktrace> = listOf()
private var errorReport: StringBuilder = StringBuilder()
private var prefs: PrefHelper? = null
private var prefs: Preferences? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -42,7 +42,7 @@ class CrashDialogActivity : AppCompatActivity() {
// We secure the PrefHelper usage here because the PrefHelper could potentially be the
// source of the crash, thus making the crash dialog unusable if not wrapped.
try {
prefs = PrefHelper.getDefaultInstance(this)
prefs = Preferences.default()
} catch (_: Exception) {
}
@@ -93,9 +93,13 @@ class CrashDialogActivity : AppCompatActivity() {
binding.copyToClipboard.setOnClickListener {
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE)
if (clipboardManager != null && clipboardManager is ClipboardManager) {
val toastMessage: String = if (clipboardManager != null && clipboardManager is ClipboardManager) {
clipboardManager.setPrimaryClip(ClipData.newPlainText(errorReport, errorReport))
resources.getString(R.string.crash_dialog__copy_to_clipboard_success)
} else {
resources.getString(R.string.crash_dialog__copy_to_clipboard_failure)
}
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
}
binding.openBugReportForm.setOnClickListener {
@@ -131,10 +135,10 @@ class CrashDialogActivity : AppCompatActivity() {
private fun getDeviceName(): String {
val manufacturer = Build.MANUFACTURER
val model = Build.MODEL
return if (model.toLowerCase(Locale.ENGLISH).startsWith(manufacturer.toLowerCase(Locale.ENGLISH))) {
model.capitalize(Locale.ENGLISH)
return if (model.lowercase().startsWith(manufacturer.lowercase())) {
model.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
} else {
"${manufacturer.capitalize(Locale.ENGLISH)} $model"
"${manufacturer.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }} $model"
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalContracts::class, ExperimentalUnsignedTypes::class)
@file:OptIn(ExperimentalContracts::class)
package dev.patrickgold.florisboard.debug

View File

@@ -14,8 +14,6 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalUnsignedTypes::class)
package dev.patrickgold.florisboard.debug
/**
@@ -37,6 +35,7 @@ object LogTopic {
const val SUBTYPE_MANAGER: FlogTopic = 4u
const val LAYOUT_MANAGER: FlogTopic = 8u
const val TEXT_KEYBOARD_VIEW: FlogTopic = 16u
const val GESTURES: FlogTopic = 32u
const val GLIDE: FlogTopic = 512u
const val CLIPBOARD: FlogTopic = 1024u

View File

@@ -49,23 +49,21 @@ class ClipboardInputManager private constructor() : CoroutineScope by MainScope(
*/
@SuppressLint("ClickableViewAccessibility")
override fun onRegisterInputView(inputView: InputView) {
launch(Dispatchers.Default) {
inputView.findViewById<ImageButton>(R.id.back_to_keyboard_button)
.setOnTouchListener { view, event -> onButtonPressEvent(view, event) }
inputView.findViewById<ImageButton>(R.id.back_to_keyboard_button)
.setOnTouchListener { view, event -> onButtonPressEvent(view, event) }
inputView.findViewById<ImageButton>(R.id.clear_clipboard_history)
.setOnTouchListener { view, event -> onButtonPressEvent(view, event) }
inputView.findViewById<ImageButton>(R.id.clear_clipboard_history)
.setOnTouchListener { view, event -> onButtonPressEvent(view, event) }
recyclerView = inputView.findViewById(R.id.clipboard_history_items)
recyclerView = inputView.findViewById(R.id.clipboard_history_items)
if (BuildConfig.DEBUG && adapter == null) {
error("initClipboard() not called")
}
recyclerView!!.adapter = adapter
val manager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recyclerView!!.layoutManager = manager
if (BuildConfig.DEBUG && adapter == null) {
error("initClipboard() not called")
}
recyclerView!!.adapter = adapter
val manager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recyclerView!!.layoutManager = manager
}
/**
@@ -79,7 +77,7 @@ class ClipboardInputManager private constructor() : CoroutineScope by MainScope(
/**
* Returns a reference to the [ClipboardHistoryView]
*/
fun getClipboardHistoryView(): ClipboardHistoryView{
fun getClipboardHistoryView(): ClipboardHistoryView {
return FlorisBoard.getInstance().inputView?.mainViewFlipper?.getChildAt(2) as ClipboardHistoryView
}

View File

@@ -7,7 +7,7 @@ import android.os.Handler
import android.os.Looper
import dev.patrickgold.florisboard.ime.clip.provider.*
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.util.cancelAll
import dev.patrickgold.florisboard.util.postAtScheduledRate
import timber.log.Timber
@@ -49,7 +49,7 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
private var onPrimaryClipChangedListeners: ArrayList<OnPrimaryClipChangedListener> = arrayListOf()
private lateinit var systemClipboardManager: ClipboardManager
private lateinit var handler: Handler
private lateinit var prefHelper: PrefHelper
private val prefs get() = Preferences.default()
data class TimedClipData(val data: ClipboardItem, val timeUTC: Long)
@@ -105,7 +105,7 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
* Adds a new item to the clipboard history (if enabled).
*/
fun updateHistory(newData: ClipboardItem) {
val clipboardPrefs = prefHelper.clipboard
val clipboardPrefs = prefs.clipboard
if (clipboardPrefs.enableHistory) {
if (clipboardPrefs.limitHistorySize) {
@@ -133,14 +133,14 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
* Changes current clipboard item. WITHOUT updating the history.
*/
fun changeCurrent(newData: ClipboardItem, closePrevious: Boolean) {
if (prefHelper.clipboard.enableInternal) {
if (prefs.clipboard.enableInternal) {
if (closePrevious) current?.close()
current = newData
val isEqual = when (newData.type) {
ItemType.TEXT -> newData.text == systemClipboardManager.primaryClip?.getItemAt(0)?.text
ItemType.IMAGE -> newData.uri == systemClipboardManager.primaryClip?.getItemAt(0)?.uri
}
if (prefHelper.clipboard.syncToSystem && !isEqual)
if (prefs.clipboard.syncToSystem && !isEqual)
systemClipboardManager.setPrimaryClip(newData.toClipData())
} else {
shouldUpdateHistory = false
@@ -156,7 +156,7 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
fun addNewClip(newData: ClipboardItem) {
updateHistory(newData)
// If history is disabled, this new item will replace the old one and hence should be closed.
changeCurrent(newData, !prefHelper.clipboard.enableHistory)
changeCurrent(newData, !prefs.clipboard.enableHistory)
}
/**
@@ -168,7 +168,7 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
}
val primaryClip: ClipboardItem?
get() = if (prefHelper.clipboard.enableInternal) {
get() = if (prefs.clipboard.enableInternal) {
current
} else {
systemClipboardManager.primaryClip?.let { ClipboardItem.fromClipData(it, false) }
@@ -204,13 +204,13 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
ItemType.IMAGE -> internalPrimaryClip.uri == systemPrimaryClip.getItemAt(0)?.uri
else -> false
}
if (prefHelper.clipboard.enableInternal) {
if (prefs.clipboard.enableInternal) {
// In the event that the internal clipboard is enabled, sync to internal clipboard is enabled
// and the item is not already in internal clipboard, add it.
if (prefHelper.clipboard.syncToFloris && !isEqual) {
if (prefs.clipboard.syncToFloris && !isEqual) {
addNewClip(ClipboardItem.fromClipData(systemPrimaryClip, true))
}
} else if (prefHelper.clipboard.enableHistory) {
} else if (prefs.clipboard.enableHistory) {
// in the event history is enabled, and it should be updated it is updated
if (shouldUpdateHistory) {
updateHistory(ClipboardItem.fromClipData(systemPrimaryClip, false))
@@ -247,16 +247,14 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
systemClipboardManager = (context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager)
systemClipboardManager.addPrimaryClipChangedListener(this)
prefHelper = PrefHelper.getDefaultInstance(context)
val cleanUpClipboard = Runnable {
if (!prefHelper.clipboard.cleanUpOld) {
if (!prefs.clipboard.cleanUpOld) {
return@Runnable
}
val currentTime = System.currentTimeMillis()
var numToPop = 0
val expiryTime = prefHelper.clipboard.cleanUpAfter * 60 * 1000
val expiryTime = prefs.clipboard.cleanUpAfter * 60 * 1000
for (item in history.asReversed()) {
if (item.timeUTC + expiryTime < currentTime) {
numToPop += 1
@@ -271,7 +269,7 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
}
FlorisBoard.getInstance().clipInputManager.initClipboard(this.history, this.pins)
handler = Handler(Looper.getMainLooper())
prefHelper
prefs
handler.postAtScheduledRate(0, INTERVAL, cleanUpClipboard)
executor = FlorisBoard.getInstance().asyncExecutor
executor.execute {
@@ -333,7 +331,7 @@ class FlorisClipboardManager private constructor() : ClipboardManager.OnPrimaryC
val clipInputManager = FlorisBoard.getInstance().clipInputManager
val item = pins.removeAt(adapterPos)
val clipboardPrefs = prefHelper.clipboard
val clipboardPrefs = prefs.clipboard
if (clipboardPrefs.limitHistorySize) {
var numRemoved = 0
while (history.size >= clipboardPrefs.maxHistorySize) {

View File

@@ -34,10 +34,7 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineS
private var _binding: V? = null
protected val binding: V
get() = _binding!!
private var _prefs: PrefHelper? = null
protected val prefs: PrefHelper
get() = _prefs!!
protected val prefs: Preferences get() = Preferences.default()
private var errorDialog: AlertDialog? = null
private var errorSnackbar: Snackbar? = null
@@ -48,7 +45,6 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineS
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_prefs = PrefHelper.getDefaultInstance(applicationContext)
onCreateBinding().let {
_binding = it
setContentView(it.root)
@@ -59,7 +55,6 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineS
super.onDestroy()
cancel()
_binding = null
_prefs = null
errorDialog?.dismiss()
errorDialog = null
errorSnackbar?.dismiss()
@@ -102,7 +97,7 @@ abstract class FlorisActivity<V : ViewBinding> : AppCompatActivity(), CoroutineS
errorDialog?.dismiss()
errorDialog = AlertDialog.Builder(this@FlorisActivity).run {
setTitle(R.string.assets__error__details)
setMessage(errorThrowable.toString())
setMessage(errorThrowable?.stackTraceToString())
setPositiveButton(android.R.string.ok, null)
setNeutralButton(R.string.crash_dialog__copy_to_clipboard) { _, _ ->
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE)

View File

@@ -43,11 +43,11 @@ class FlorisApplication : Application(), CoroutineScope by MainScope() {
flogOutputs = Flog.OUTPUT_CONSOLE
)
CrashUtility.install(this)
val prefHelper = PrefHelper.getDefaultInstance(this)
val prefs = Preferences.initDefault(this)
val assetManager = AssetManager.init(this)
SubtypeManager.init(this, prefHelper)
SubtypeManager.init(this)
DictionaryManager.init(this)
ThemeManager.init(this, assetManager, prefHelper)
prefHelper.initDefaultPreferences()
ThemeManager.init(this, assetManager)
prefs.initDefaultPreferences()
}
}

View File

@@ -27,7 +27,6 @@ import android.media.AudioManager
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.provider.Settings
import android.view.*
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
@@ -38,7 +37,6 @@ import androidx.annotation.StyleRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.lifecycle.*
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.debug.*
import dev.patrickgold.florisboard.ime.clip.ClipboardInputManager
@@ -57,7 +55,7 @@ import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.setup.SetupActivity
import dev.patrickgold.florisboard.util.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@@ -84,17 +82,17 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
private val serviceLifecycleDispatcher: ServiceLifecycleDispatcher = ServiceLifecycleDispatcher(this)
private val uiScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
private var devtoolsOverlaySyncJob: Job? = null
/**
* The theme context for the UI. Must only be used for inflating/creating Views for the keyboard UI, else the IME
* service class should be used directly.
*/
private var _themeContext: Context? = null
private val themeContext: Context
val themeContext: Context
get() = _themeContext ?: this
lateinit var prefs: PrefHelper
private set
private val prefs: Preferences get() = Preferences.default()
private var extractEditLayout: WeakReference<ViewGroup?> = WeakReference(null)
var inputView: InputView? = null
@@ -147,7 +145,6 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
lateinit var asyncExecutor: ExecutorService
companion object {
@Synchronized
fun getInstance(): FlorisBoard {
return florisboardInstance!!
@@ -199,8 +196,6 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
audioManager = getSystemService(Context.AUDIO_SERVICE) as? AudioManager
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
prefs = PrefHelper.getDefaultInstance(this)
prefs.initDefaultPreferences()
prefs.sync()
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
@@ -393,6 +388,17 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
onSubtypeChanged(activeSubtype)
setActiveInput(R.id.text_input)
if (prefs.devtools.enabled && prefs.devtools.showHeapMemoryStats) {
devtoolsOverlaySyncJob?.cancel()
devtoolsOverlaySyncJob = uiScope.launch(Dispatchers.Default) {
while (true) {
if (!isActive) break
withContext(Dispatchers.Main) { inputView?.invalidate() }
delay(1000)
}
}
}
eventListeners.toList().forEach { it?.onWindowShown() }
}
@@ -406,6 +412,9 @@ class FlorisBoard : InputMethodService(), LifecycleOwner, FlorisClipboardManager
}
isWindowShown = false
devtoolsOverlaySyncJob?.cancel()
devtoolsOverlaySyncJob = null
eventListeners.toList().forEach { it?.onWindowHidden() }
}

View File

@@ -18,6 +18,11 @@ package dev.patrickgold.florisboard.ime.core
import android.content.Context
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.ViewGroup
@@ -35,8 +40,8 @@ import kotlin.math.roundToInt
* Root view of the keyboard. Notifies [FlorisBoard] when it has been attached to a window.
*/
class InputView : LinearLayout {
private var florisboard: FlorisBoard = FlorisBoard.getInstance()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val florisboard get() = FlorisBoard.getInstance()
private val prefs get() = Preferences.default()
var desiredInputViewHeight: Float = resources.getDimension(R.dimen.inputView_baseHeight)
private set
@@ -58,6 +63,13 @@ class InputView : LinearLayout {
var oneHandedCtrlPanelEnd: ViewGroup? = null
private set
private val overlayTextPaint: TextPaint = TextPaint().apply {
color = Color.GREEN
textAlign = Paint.Align.RIGHT
textSize = resources.getDimension(R.dimen.devtools_memory_overlay_textSize)
typeface = Typeface.MONOSPACE
}
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
@@ -124,9 +136,9 @@ class InputView : LinearLayout {
// adding a value to the height now will result in a bottom padding (aka offset).
baseHeight += ViewLayoutUtils.convertDpToPixel(
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
florisboard.prefs.keyboard.bottomOffsetLandscape.toFloat()
prefs.keyboard.bottomOffsetLandscape.toFloat()
} else {
florisboard.prefs.keyboard.bottomOffsetPortrait.toFloat()
prefs.keyboard.bottomOffsetPortrait.toFloat()
},
context
)
@@ -161,4 +173,34 @@ class InputView : LinearLayout {
resources.getDimension(R.dimen.inputView_baseHeight)
)
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
canvas ?: return
if (prefs.devtools.enabled && prefs.devtools.showHeapMemoryStats) {
try {
// Note: the below code only gets the heap size in MB, the actual RAM usage (native or others) can be
// a lot higher
// Source: https://stackoverflow.com/a/19267315/6801193
val runtime = Runtime.getRuntime()
val usedMemInMB = (runtime.totalMemory() - runtime.freeMemory()) / 1048576L
val maxHeapSizeInMB = runtime.maxMemory() / 1048576L
val availHeapSizeInMB = maxHeapSizeInMB - usedMemInMB
val output = listOf(
"heap mem:",
String.format("used=%4dMB", usedMemInMB),
String.format("max=%4dMB", maxHeapSizeInMB),
String.format("avail=%4dMB", availHeapSizeInMB),
)
val x = measuredWidth.toFloat()
var y = overlayTextPaint.descent() - overlayTextPaint.ascent()
for (line in output) {
canvas.drawText(line, x, y, overlayTextPaint)
y += overlayTextPaint.descent() - overlayTextPaint.ascent()
}
} catch (_: Throwable) {
}
}
}
}

View File

@@ -37,7 +37,7 @@ import java.lang.ref.WeakReference
/**
* Helper class for an organized access to the shared preferences.
*/
class PrefHelper(
class Preferences(
context: Context,
val shared: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
) {
@@ -48,7 +48,10 @@ class PrefHelper(
private val cacheString: HashMap<String, String> = hashMapOf()
val advanced = Advanced(this)
val clipboard = Clipboard(this)
val correction = Correction(this)
val devtools = Devtools(this)
val dictionary = Dictionary(this)
val gestures = Gestures(this)
val glide = Glide(this)
val internal = Internal(this)
@@ -57,7 +60,6 @@ class PrefHelper(
val smartbar = Smartbar(this)
val suggestion = Suggestion(this)
val theme = Theme(this)
val clipboard = Clipboard(this)
/**
* Checks the cache if an entry for [key] exists, else calls [getPrefInternal] to retrieve the
@@ -128,14 +130,21 @@ class PrefHelper(
companion object {
private val OLD_SUBTYPES_REGEX = """^([\-0-9]+/[\-a-zA-Z0-9]+/[a-zA-Z_]+[;]*)+${'$'}""".toRegex()
private var defaultInstance: PrefHelper? = null
private var defaultInstance: Preferences? = null
@Synchronized
fun getDefaultInstance(context: Context): PrefHelper {
if (defaultInstance == null) {
defaultInstance = PrefHelper(context)
}
return defaultInstance!!
fun initDefault(context: Context): Preferences {
val instance = Preferences(context.applicationContext)
defaultInstance = instance
return instance
}
fun default(): Preferences {
return defaultInstance
?: throw UninitializedPropertyAccessException("""
Default preferences not initialized! Make sure to call initDefault()
before accessing the default preferences.
""".trimIndent())
}
}
@@ -183,7 +192,7 @@ class PrefHelper(
/**
* Wrapper class for advanced preferences.
*/
class Advanced(private val prefHelper: PrefHelper) {
class Advanced(private val prefs: Preferences) {
companion object {
const val SETTINGS_THEME = "advanced__settings_theme"
const val SHOW_APP_ICON = "advanced__show_app_icon"
@@ -191,20 +200,20 @@ class PrefHelper(
}
var settingsTheme: String = ""
get() = prefHelper.getPref(SETTINGS_THEME, "auto")
get() = prefs.getPref(SETTINGS_THEME, "auto")
private set
var showAppIcon: Boolean = false
get() = prefHelper.getPref(SHOW_APP_ICON, true)
get() = prefs.getPref(SHOW_APP_ICON, true)
private set
var forcePrivateMode: Boolean
get() = prefHelper.getPref(FORCE_PRIVATE_MODE, false)
set(v) = prefHelper.setPref(FORCE_PRIVATE_MODE, v)
get() = prefs.getPref(FORCE_PRIVATE_MODE, false)
set(v) = prefs.setPref(FORCE_PRIVATE_MODE, v)
}
/**
* Wrapper class for correction preferences.
*/
class Correction(private val prefHelper: PrefHelper) {
class Correction(private val prefs: Preferences) {
companion object {
const val AUTO_CAPITALIZATION = "correction__auto_capitalization"
const val DOUBLE_SPACE_PERIOD = "correction__double_space_period"
@@ -212,20 +221,57 @@ class PrefHelper(
}
var autoCapitalization: Boolean
get() = prefHelper.getPref(AUTO_CAPITALIZATION, true)
set(v) = prefHelper.setPref(AUTO_CAPITALIZATION, v)
get() = prefs.getPref(AUTO_CAPITALIZATION, true)
set(v) = prefs.setPref(AUTO_CAPITALIZATION, v)
var doubleSpacePeriod: Boolean
get() = prefHelper.getPref(DOUBLE_SPACE_PERIOD, true)
set(v) = prefHelper.setPref(DOUBLE_SPACE_PERIOD, v)
get() = prefs.getPref(DOUBLE_SPACE_PERIOD, true)
set(v) = prefs.setPref(DOUBLE_SPACE_PERIOD, v)
var rememberCapsLockState: Boolean
get() = prefHelper.getPref(REMEMBER_CAPS_LOCK_STATE, false)
set(v) = prefHelper.setPref(REMEMBER_CAPS_LOCK_STATE, v)
get() = prefs.getPref(REMEMBER_CAPS_LOCK_STATE, false)
set(v) = prefs.setPref(REMEMBER_CAPS_LOCK_STATE, v)
}
/**
* Wrapper class for devtools preferences.
*/
class Devtools(private val prefs: Preferences) {
companion object {
const val ENABLED = "devtools__enabled"
const val SHOW_HEAP_MEMORY_STATS = "devtools__show_heap_memory_stats"
const val CLEAR_UDM_INTERNAL_DATABASE = "devtools__clear_udm_internal_database"
}
var enabled: Boolean
get() = prefs.getPref(ENABLED, false)
set(v) = prefs.setPref(ENABLED, v)
var showHeapMemoryStats: Boolean
get() = prefs.getPref(SHOW_HEAP_MEMORY_STATS, false)
set(v) = prefs.setPref(SHOW_HEAP_MEMORY_STATS, v)
}
/**
* Wrapper class for dictionary preferences.
*/
class Dictionary(private val prefs: Preferences) {
companion object {
const val ENABLE_SYSTEM_USER_DICTIONARY = "suggestion__enable_system_user_dictionary"
const val MANAGE_SYSTEM_USER_DICTIONARY = "suggestion__manage_system_user_dictionary"
const val ENABLE_FLORIS_USER_DICTIONARY = "suggestion__enable_floris_user_dictionary"
const val MANAGE_FLORIS_USER_DICTIONARY = "suggestion__manage_floris_user_dictionary"
}
var enableSystemUserDictionary: Boolean
get() = prefs.getPref(ENABLE_SYSTEM_USER_DICTIONARY, true)
set(v) = prefs.setPref(ENABLE_SYSTEM_USER_DICTIONARY, v)
var enableFlorisUserDictionary: Boolean
get() = prefs.getPref(ENABLE_FLORIS_USER_DICTIONARY, true)
set(v) = prefs.setPref(ENABLE_FLORIS_USER_DICTIONARY, v)
}
/**
* Wrapper class for gestures preferences.
*/
class Gestures(private val prefHelper: PrefHelper) {
class Gestures(private val prefs: Preferences) {
companion object {
const val SWIPE_UP = "gestures__swipe_up"
const val SWIPE_DOWN = "gestures__swipe_down"
@@ -241,44 +287,44 @@ class PrefHelper(
}
var swipeUp: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_UP, "no_action"))
set(v) = prefHelper.setPref(SWIPE_UP, v)
get() = SwipeAction.fromString(prefs.getPref(SWIPE_UP, "no_action"))
set(v) = prefs.setPref(SWIPE_UP, v)
var swipeDown: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_DOWN, "no_action"))
set(v) = prefHelper.setPref(SWIPE_DOWN, v)
get() = SwipeAction.fromString(prefs.getPref(SWIPE_DOWN, "no_action"))
set(v) = prefs.setPref(SWIPE_DOWN, v)
var swipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(SWIPE_LEFT, v)
get() = SwipeAction.fromString(prefs.getPref(SWIPE_LEFT, "no_action"))
set(v) = prefs.setPref(SWIPE_LEFT, v)
var swipeRight: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_RIGHT, "no_action"))
set(v) = prefHelper.setPref(SWIPE_RIGHT, v)
get() = SwipeAction.fromString(prefs.getPref(SWIPE_RIGHT, "no_action"))
set(v) = prefs.setPref(SWIPE_RIGHT, v)
var spaceBarLongPress: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_LONG_PRESS, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_LONG_PRESS, v)
get() = SwipeAction.fromString(prefs.getPref(SPACE_BAR_LONG_PRESS, "no_action"))
set(v) = prefs.setPref(SPACE_BAR_LONG_PRESS, v)
var spaceBarSwipeUp: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_UP, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_UP, v)
get() = SwipeAction.fromString(prefs.getPref(SPACE_BAR_SWIPE_UP, "no_action"))
set(v) = prefs.setPref(SPACE_BAR_SWIPE_UP, v)
var spaceBarSwipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_LEFT, v)
get() = SwipeAction.fromString(prefs.getPref(SPACE_BAR_SWIPE_LEFT, "no_action"))
set(v) = prefs.setPref(SPACE_BAR_SWIPE_LEFT, v)
var spaceBarSwipeRight: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_RIGHT, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_RIGHT, v)
get() = SwipeAction.fromString(prefs.getPref(SPACE_BAR_SWIPE_RIGHT, "no_action"))
set(v) = prefs.setPref(SPACE_BAR_SWIPE_RIGHT, v)
var deleteKeySwipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(DELETE_KEY_SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(DELETE_KEY_SWIPE_LEFT, v)
get() = SwipeAction.fromString(prefs.getPref(DELETE_KEY_SWIPE_LEFT, "no_action"))
set(v) = prefs.setPref(DELETE_KEY_SWIPE_LEFT, v)
var swipeVelocityThreshold: VelocityThreshold
get() = VelocityThreshold.fromString(prefHelper.getPref(SWIPE_VELOCITY_THRESHOLD, "normal"))
set(v) = prefHelper.setPref(SWIPE_VELOCITY_THRESHOLD, v)
get() = VelocityThreshold.fromString(prefs.getPref(SWIPE_VELOCITY_THRESHOLD, "normal"))
set(v) = prefs.setPref(SWIPE_VELOCITY_THRESHOLD, v)
var swipeDistanceThreshold: DistanceThreshold
get() = DistanceThreshold.fromString(prefHelper.getPref(SWIPE_DISTANCE_THRESHOLD, "normal"))
set(v) = prefHelper.setPref(SWIPE_DISTANCE_THRESHOLD, v)
get() = DistanceThreshold.fromString(prefs.getPref(SWIPE_DISTANCE_THRESHOLD, "normal"))
set(v) = prefs.setPref(SWIPE_DISTANCE_THRESHOLD, v)
}
/**
* Wrapper class for glide preferences.
*/
class Glide(private val prefHelper: PrefHelper) {
class Glide(private val prefs: Preferences) {
companion object {
const val ENABLED = "glide__enabled"
const val SHOW_TRAIL = "glide__show_trail"
@@ -289,30 +335,30 @@ class PrefHelper(
}
var enabled: Boolean
get() = prefHelper.getPref(ENABLED, false)
set(v) = prefHelper.setPref(ENABLED, v)
get() = prefs.getPref(ENABLED, false)
set(v) = prefs.setPref(ENABLED, v)
var showTrail: Boolean
get() = prefHelper.getPref(SHOW_TRAIL, false)
set(v) = prefHelper.setPref(SHOW_TRAIL, v)
get() = prefs.getPref(SHOW_TRAIL, false)
set(v) = prefs.setPref(SHOW_TRAIL, v)
var trailDuration: Int
get() = prefHelper.getPref(TRAIL_DURATION, 200)
set(v) = prefHelper.setPref(TRAIL_DURATION, v)
get() = prefs.getPref(TRAIL_DURATION, 200)
set(v) = prefs.setPref(TRAIL_DURATION, v)
var showPreview: Boolean
get() = prefHelper.getPref(SHOW_PREVIEW, true)
set(v) = prefHelper.setPref(SHOW_PREVIEW, v)
get() = prefs.getPref(SHOW_PREVIEW, true)
set(v) = prefs.setPref(SHOW_PREVIEW, v)
var previewRefreshDelay: Int
get() = prefHelper.getPref(PREVIEW_REFRESH_DELAY, 150)
set(v) = prefHelper.setPref(PREVIEW_REFRESH_DELAY, v)
get() = prefs.getPref(PREVIEW_REFRESH_DELAY, 150)
set(v) = prefs.setPref(PREVIEW_REFRESH_DELAY, v)
var trailMaxLength: Int
get() = prefHelper.getPref(MAX_TRAIL_LENGTH, 150)
set(v) = prefHelper.setPref(MAX_TRAIL_LENGTH, v)
get() = prefs.getPref(MAX_TRAIL_LENGTH, 150)
set(v) = prefs.setPref(MAX_TRAIL_LENGTH, v)
}
/**
* Wrapper class for internal preferences. A preference qualifies as an internal pref if the
* user has no ability to control this preference's value directly (via a UI pref view).
*/
class Internal(private val prefHelper: PrefHelper) {
class Internal(private val prefs: Preferences) {
companion object {
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val VERSION_ON_INSTALL = "internal__version_on_install"
@@ -321,23 +367,23 @@ class PrefHelper(
}
var isImeSetUp: Boolean
get() = prefHelper.getPref(IS_IME_SET_UP, false)
set(v) = prefHelper.setPref(IS_IME_SET_UP, v)
get() = prefs.getPref(IS_IME_SET_UP, false)
set(v) = prefs.setPref(IS_IME_SET_UP, v)
var versionOnInstall: String
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_ON_INSTALL, v)
get() = prefs.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
set(v) = prefs.setPref(VERSION_ON_INSTALL, v)
var versionLastUse: String
get() = prefHelper.getPref(VERSION_LAST_USE, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_LAST_USE, v)
get() = prefs.getPref(VERSION_LAST_USE, VersionName.DEFAULT_RAW)
set(v) = prefs.setPref(VERSION_LAST_USE, v)
var versionLastChangelog: String
get() = prefHelper.getPref(VERSION_LAST_CHANGELOG, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_LAST_CHANGELOG, v)
get() = prefs.getPref(VERSION_LAST_CHANGELOG, VersionName.DEFAULT_RAW)
set(v) = prefs.setPref(VERSION_LAST_CHANGELOG, v)
}
/**
* Wrapper class for keyboard preferences.
*/
class Keyboard(private val prefHelper: PrefHelper) {
class Keyboard(private val prefs: Preferences) {
companion object {
const val BOTTOM_OFFSET_PORTRAIT = "keyboard__bottom_offset_portrait"
const val BOTTOM_OFFSET_LANDSCAPE = "keyboard__bottom_offset_landscape"
@@ -366,115 +412,115 @@ class PrefHelper(
}
var bottomOffsetPortrait: Int = 0
get() = prefHelper.getPref(BOTTOM_OFFSET_PORTRAIT, 0)
get() = prefs.getPref(BOTTOM_OFFSET_PORTRAIT, 0)
private set
var bottomOffsetLandscape: Int = 0
get() = prefHelper.getPref(BOTTOM_OFFSET_LANDSCAPE, 0)
get() = prefs.getPref(BOTTOM_OFFSET_LANDSCAPE, 0)
private set
var fontSizeMultiplierPortrait: Int
get() = prefHelper.getPref(FONT_SIZE_MULTIPLIER_PORTRAIT, 100)
set(v) = prefHelper.setPref(FONT_SIZE_MULTIPLIER_PORTRAIT, v)
get() = prefs.getPref(FONT_SIZE_MULTIPLIER_PORTRAIT, 100)
set(v) = prefs.setPref(FONT_SIZE_MULTIPLIER_PORTRAIT, v)
var fontSizeMultiplierLandscape: Int
get() = prefHelper.getPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, 100)
set(v) = prefHelper.setPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, v)
get() = prefs.getPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, 100)
set(v) = prefs.setPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, v)
var heightFactor: String = ""
get() = prefHelper.getPref(HEIGHT_FACTOR, "normal")
get() = prefs.getPref(HEIGHT_FACTOR, "normal")
private set
var heightFactorCustom: Int
get() = prefHelper.getPref(HEIGHT_FACTOR_CUSTOM, 100)
set(v) = prefHelper.setPref(HEIGHT_FACTOR_CUSTOM, v)
get() = prefs.getPref(HEIGHT_FACTOR_CUSTOM, 100)
set(v) = prefs.setPref(HEIGHT_FACTOR_CUSTOM, v)
var hintedNumberRowMode: KeyHintMode
get() = KeyHintMode.fromString(prefHelper.getPref(HINTED_NUMBER_ROW_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
set(v) = prefHelper.setPref(HINTED_NUMBER_ROW_MODE, v)
get() = KeyHintMode.fromString(prefs.getPref(HINTED_NUMBER_ROW_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
set(v) = prefs.setPref(HINTED_NUMBER_ROW_MODE, v)
var hintedSymbolsMode: KeyHintMode
get() = KeyHintMode.fromString(prefHelper.getPref(HINTED_SYMBOLS_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
set(v) = prefHelper.setPref(HINTED_SYMBOLS_MODE, v)
get() = KeyHintMode.fromString(prefs.getPref(HINTED_SYMBOLS_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
set(v) = prefs.setPref(HINTED_SYMBOLS_MODE, v)
var keySpacingHorizontal: Float = 2f
get() = prefHelper.getPref(KEY_SPACING_HORIZONTAL, 4) / 2f
get() = prefs.getPref(KEY_SPACING_HORIZONTAL, 4) / 2f
private set
var keySpacingVertical: Float = 5f
get() = prefHelper.getPref(KEY_SPACING_VERTICAL, 10) / 2f
get() = prefs.getPref(KEY_SPACING_VERTICAL, 10) / 2f
private set
var landscapeInputUiMode: LandscapeInputUiMode
get() = LandscapeInputUiMode.fromString(prefHelper.getPref(LANDSCAPE_INPUT_UI_MODE, LandscapeInputUiMode.DYNAMICALLY_SHOW.toString()))
set(v) = prefHelper.setPref(LANDSCAPE_INPUT_UI_MODE, v)
get() = LandscapeInputUiMode.fromString(prefs.getPref(LANDSCAPE_INPUT_UI_MODE, LandscapeInputUiMode.DYNAMICALLY_SHOW.toString()))
set(v) = prefs.setPref(LANDSCAPE_INPUT_UI_MODE, v)
var longPressDelay: Int = 0
get() = prefHelper.getPref(LONG_PRESS_DELAY, 300)
get() = prefs.getPref(LONG_PRESS_DELAY, 300)
private set
var numberRow: Boolean
get() = prefHelper.getPref(NUMBER_ROW, false)
set(v) = prefHelper.setPref(NUMBER_ROW, v)
get() = prefs.getPref(NUMBER_ROW, false)
set(v) = prefs.setPref(NUMBER_ROW, v)
var oneHandedMode: String
get() = prefHelper.getPref(ONE_HANDED_MODE, OneHandedMode.OFF)
set(value) = prefHelper.setPref(ONE_HANDED_MODE, value)
get() = prefs.getPref(ONE_HANDED_MODE, OneHandedMode.OFF)
set(value) = prefs.setPref(ONE_HANDED_MODE, value)
var oneHandedModeScaleFactor: Int
get() = prefHelper.getPref(ONE_HANDED_MODE_SCALE_FACTOR, 87)
set(v) = prefHelper.setPref(ONE_HANDED_MODE_SCALE_FACTOR, v)
get() = prefs.getPref(ONE_HANDED_MODE_SCALE_FACTOR, 87)
set(v) = prefs.setPref(ONE_HANDED_MODE_SCALE_FACTOR, v)
var popupEnabled: Boolean = false
get() = prefHelper.getPref(POPUP_ENABLED, true)
get() = prefs.getPref(POPUP_ENABLED, true)
private set
var soundEnabled: Boolean = false
get() = prefHelper.getPref(SOUND_ENABLED, true)
get() = prefs.getPref(SOUND_ENABLED, true)
private set
var soundEnabledSystem: Boolean = false
var soundVolume: Int = 0
get() = prefHelper.getPref(SOUND_VOLUME, -1)
get() = prefs.getPref(SOUND_VOLUME, -1)
private set
var spaceBarSwitchesToCharacters: Boolean
get() = prefHelper.getPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, true)
set(v) = prefHelper.setPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, v)
get() = prefs.getPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, true)
set(v) = prefs.setPref(SPACE_BAR_SWITCHES_TO_CHARACTERS, v)
var utilityKeyAction: UtilityKeyAction
get() = UtilityKeyAction.fromString(prefHelper.getPref(UTILITY_KEY_ACTION, UtilityKeyAction.DYNAMIC_SWITCH_LANGUAGE_EMOJIS.toString()))
set(v) = prefHelper.setPref(UTILITY_KEY_ACTION, v)
get() = UtilityKeyAction.fromString(prefs.getPref(UTILITY_KEY_ACTION, UtilityKeyAction.DYNAMIC_SWITCH_LANGUAGE_EMOJIS.toString()))
set(v) = prefs.setPref(UTILITY_KEY_ACTION, v)
var utilityKeyEnabled: Boolean
get() = prefHelper.getPref(UTILITY_KEY_ENABLED, true)
set(v) = prefHelper.setPref(UTILITY_KEY_ENABLED, v)
get() = prefs.getPref(UTILITY_KEY_ENABLED, true)
set(v) = prefs.setPref(UTILITY_KEY_ENABLED, v)
var vibrationEnabled: Boolean = false
get() = prefHelper.getPref(VIBRATION_ENABLED, true)
get() = prefs.getPref(VIBRATION_ENABLED, true)
private set
var vibrationEnabledSystem: Boolean = false
var vibrationDuration: Int = 0
get() = prefHelper.getPref(VIBRATION_DURATION, -1)
get() = prefs.getPref(VIBRATION_DURATION, -1)
private set
var vibrationStrength: Int = 0
get() = prefHelper.getPref(VIBRATION_STRENGTH, -1)
get() = prefs.getPref(VIBRATION_STRENGTH, -1)
private set
}
/**
* Wrapper class for localization preferences.
*/
class Localization(private val prefHelper: PrefHelper) {
class Localization(private val prefs: Preferences) {
companion object {
const val ACTIVE_SUBTYPE_ID = "localization__active_subtype_id"
const val SUBTYPES = "localization__subtypes"
}
var activeSubtypeId: Int
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, Subtype.DEFAULT.id)
set(v) = prefHelper.setPref(ACTIVE_SUBTYPE_ID, v)
get() = prefs.getPref(ACTIVE_SUBTYPE_ID, Subtype.DEFAULT.id)
set(v) = prefs.setPref(ACTIVE_SUBTYPE_ID, v)
var subtypes: String
get() = prefHelper.getPref(SUBTYPES, "")
set(v) = prefHelper.setPref(SUBTYPES, v)
get() = prefs.getPref(SUBTYPES, "")
set(v) = prefs.setPref(SUBTYPES, v)
}
/**
* Wrapper class for Smartbar preferences.
*/
class Smartbar(private val prefHelper: PrefHelper) {
class Smartbar(private val prefs: Preferences) {
companion object {
const val ENABLED = "smartbar__enabled"
}
var enabled: Boolean
get() = prefHelper.getPref(ENABLED, true)
set(v) = prefHelper.setPref(ENABLED, v)
get() = prefs.getPref(ENABLED, true)
set(v) = prefs.setPref(ENABLED, v)
}
/**
* Wrapper class for suggestion preferences.
*/
class Suggestion(private val prefHelper: PrefHelper) {
class Suggestion(private val prefs: Preferences) {
companion object {
const val BLOCK_POSSIBLY_OFFENSIVE = "suggestion__block_possibly_offensive"
const val CLIPBOARD_CONTENT_ENABLED = "suggestion__clipboard_content_enabled"
@@ -485,29 +531,29 @@ class PrefHelper(
}
var blockPossiblyOffensive: Boolean
get() = prefHelper.getPref(BLOCK_POSSIBLY_OFFENSIVE, true)
set(v) = prefHelper.setPref(BLOCK_POSSIBLY_OFFENSIVE, v)
get() = prefs.getPref(BLOCK_POSSIBLY_OFFENSIVE, true)
set(v) = prefs.setPref(BLOCK_POSSIBLY_OFFENSIVE, v)
var clipboardContentEnabled: Boolean
get() = prefHelper.getPref(CLIPBOARD_CONTENT_ENABLED, false)
set(v) = prefHelper.setPref(CLIPBOARD_CONTENT_ENABLED, v)
get() = prefs.getPref(CLIPBOARD_CONTENT_ENABLED, false)
set(v) = prefs.setPref(CLIPBOARD_CONTENT_ENABLED, v)
var clipboardContentTimeout: Int
get() = prefHelper.getPref(CLIPBOARD_CONTENT_TIMEOUT, 30)
set(v) = prefHelper.setPref(CLIPBOARD_CONTENT_TIMEOUT, v)
get() = prefs.getPref(CLIPBOARD_CONTENT_TIMEOUT, 30)
set(v) = prefs.setPref(CLIPBOARD_CONTENT_TIMEOUT, v)
var displayMode: CandidateView.DisplayMode
get() = CandidateView.DisplayMode.fromString(prefHelper.getPref(DISPLAY_MODE, CandidateView.DisplayMode.DYNAMIC_SCROLLABLE.toString()))
set(v) = prefHelper.setPref(DISPLAY_MODE, v)
get() = CandidateView.DisplayMode.fromString(prefs.getPref(DISPLAY_MODE, CandidateView.DisplayMode.DYNAMIC_SCROLLABLE.toString()))
set(v) = prefs.setPref(DISPLAY_MODE, v)
var enabled: Boolean
get() = prefHelper.getPref(ENABLED, true)
set(v) = prefHelper.setPref(ENABLED, v)
get() = prefs.getPref(ENABLED, true)
set(v) = prefs.setPref(ENABLED, v)
var usePrevWords: Boolean
get() = prefHelper.getPref(USE_PREV_WORDS, true)
set(v) = prefHelper.setPref(USE_PREV_WORDS, v)
get() = prefs.getPref(USE_PREV_WORDS, true)
set(v) = prefs.setPref(USE_PREV_WORDS, v)
}
/**
* Wrapper class for theme preferences.
*/
class Theme(private val prefHelper: PrefHelper) {
class Theme(private val prefs: Preferences) {
companion object {
const val MODE = "theme__mode"
const val DAY_THEME_REF = "theme__day_theme_ref"
@@ -519,32 +565,32 @@ class PrefHelper(
}
var mode: ThemeMode
get() = ThemeMode.fromString(prefHelper.getPref(MODE, ThemeMode.FOLLOW_SYSTEM.toString()))
set(v) = prefHelper.setPref(MODE, v)
get() = ThemeMode.fromString(prefs.getPref(MODE, ThemeMode.FOLLOW_SYSTEM.toString()))
set(v) = prefs.setPref(MODE, v)
var dayThemeRef: String
get() = prefHelper.getPref(DAY_THEME_REF, "assets:ime/theme/floris_day.json")
set(v) = prefHelper.setPref(DAY_THEME_REF, v)
get() = prefs.getPref(DAY_THEME_REF, "assets:ime/theme/floris_day.json")
set(v) = prefs.setPref(DAY_THEME_REF, v)
var dayThemeAdaptToApp: Boolean
get() = prefHelper.getPref(DAY_THEME_ADAPT_TO_APP, false)
set(v) = prefHelper.setPref(DAY_THEME_ADAPT_TO_APP, v)
get() = prefs.getPref(DAY_THEME_ADAPT_TO_APP, false)
set(v) = prefs.setPref(DAY_THEME_ADAPT_TO_APP, v)
var nightThemeRef: String
get() = prefHelper.getPref(NIGHT_THEME_REF, "assets:ime/theme/floris_night.json")
set(v) = prefHelper.setPref(NIGHT_THEME_REF, v)
get() = prefs.getPref(NIGHT_THEME_REF, "assets:ime/theme/floris_night.json")
set(v) = prefs.setPref(NIGHT_THEME_REF, v)
var nightThemeAdaptToApp: Boolean
get() = prefHelper.getPref(NIGHT_THEME_ADAPT_TO_APP, false)
set(v) = prefHelper.setPref(NIGHT_THEME_ADAPT_TO_APP, v)
get() = prefs.getPref(NIGHT_THEME_ADAPT_TO_APP, false)
set(v) = prefs.setPref(NIGHT_THEME_ADAPT_TO_APP, v)
var sunriseTime: Int
get() = prefHelper.getPref(SUNRISE_TIME, TimeUtil.encode(6, 0))
set(v) = prefHelper.setPref(SUNRISE_TIME, v)
get() = prefs.getPref(SUNRISE_TIME, TimeUtil.encode(6, 0))
set(v) = prefs.setPref(SUNRISE_TIME, v)
var sunsetTime: Int
get() = prefHelper.getPref(SUNSET_TIME, TimeUtil.encode(18, 0))
set(v) = prefHelper.setPref(SUNSET_TIME, v)
get() = prefs.getPref(SUNSET_TIME, TimeUtil.encode(18, 0))
set(v) = prefs.setPref(SUNSET_TIME, v)
}
/**
* Wrapper class for clipboard preferences
*/
class Clipboard(private val prefHelper: PrefHelper) {
class Clipboard(private val prefs: Preferences) {
companion object {
const val ENABLE_INTERNAL = "clipboard__enable_internal"
const val SYNC_TO_SYSTEM = "clipboard__sync_to_system"
@@ -557,35 +603,35 @@ class PrefHelper(
}
var enableInternal: Boolean
get() = prefHelper.getPref(ENABLE_INTERNAL, false)
set(v) = prefHelper.setPref(ENABLE_INTERNAL, v)
get() = prefs.getPref(ENABLE_INTERNAL, false)
set(v) = prefs.setPref(ENABLE_INTERNAL, v)
var syncToSystem: Boolean
get() = prefHelper.getPref(SYNC_TO_SYSTEM, false)
set(v) = prefHelper.setPref(SYNC_TO_SYSTEM, v)
get() = prefs.getPref(SYNC_TO_SYSTEM, false)
set(v) = prefs.setPref(SYNC_TO_SYSTEM, v)
var syncToFloris: Boolean
get() = prefHelper.getPref(SYNC_TO_FLORIS, true)
set(v) = prefHelper.setPref(SYNC_TO_FLORIS, v)
get() = prefs.getPref(SYNC_TO_FLORIS, true)
set(v) = prefs.setPref(SYNC_TO_FLORIS, v)
var enableHistory: Boolean
get() = prefHelper.getPref(ENABLE_HISTORY, false)
set(v) = prefHelper.setPref(ENABLE_HISTORY, v)
get() = prefs.getPref(ENABLE_HISTORY, false)
set(v) = prefs.setPref(ENABLE_HISTORY, v)
var cleanUpOld: Boolean
get() = prefHelper.getPref(CLEAN_UP_OLD, false)
set(v) = prefHelper.setPref(CLEAN_UP_OLD, v)
get() = prefs.getPref(CLEAN_UP_OLD, false)
set(v) = prefs.setPref(CLEAN_UP_OLD, v)
var limitHistorySize: Boolean
get() = prefHelper.getPref(LIMIT_HISTORY_SIZE, true)
set(v) = prefHelper.setPref(LIMIT_HISTORY_SIZE, v)
get() = prefs.getPref(LIMIT_HISTORY_SIZE, true)
set(v) = prefs.setPref(LIMIT_HISTORY_SIZE, v)
var cleanUpAfter: Int
get() = prefHelper.getPref(CLEAN_UP_AFTER, 20)
set(v) = prefHelper.setPref(CLEAN_UP_AFTER, v)
get() = prefs.getPref(CLEAN_UP_AFTER, 20)
set(v) = prefs.setPref(CLEAN_UP_AFTER, v)
var maxHistorySize: Int
get() = prefHelper.getPref(MAX_HISTORY_SIZE, 20)
set(v) = prefHelper.setPref(MAX_HISTORY_SIZE, v)
get() = prefs.getPref(MAX_HISTORY_SIZE, 20)
set(v) = prefs.setPref(MAX_HISTORY_SIZE, v)
}
}

View File

@@ -37,10 +37,10 @@ import kotlin.collections.ArrayList
* @property subtypes The currently active subtypes.
*/
class SubtypeManager(
private val packageName: String,
private val prefs: PrefHelper
private val packageName: String
) : CoroutineScope by MainScope() {
private val assetManager get() = AssetManager.default()
private val prefs get() = Preferences.default()
companion object {
const val IME_CONFIG_FILE_PATH = "ime/config.json"
@@ -48,8 +48,8 @@ class SubtypeManager(
private var instance: SubtypeManager? = null
fun init(context: Context, prefs: PrefHelper): SubtypeManager {
val defaultInstance = SubtypeManager(context.packageName, prefs)
fun init(context: Context): SubtypeManager {
val defaultInstance = SubtypeManager(context.packageName)
instance = defaultInstance
return defaultInstance
}

View File

@@ -17,15 +17,24 @@
package dev.patrickgold.florisboard.ime.dictionary
import android.content.Context
import androidx.room.Room
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.extension.AssetRef
import timber.log.Timber
import java.lang.ref.WeakReference
/**
* TODO: document
*/
class DictionaryManager private constructor(private val applicationContext: Context) {
class DictionaryManager private constructor(context: Context) {
private val applicationContext: WeakReference<Context> = WeakReference(context.applicationContext ?: context)
private val prefs get() = Preferences.default()
private val dictionaryCache: MutableMap<String, Dictionary<String, Int>> = mutableMapOf()
private var florisUserDictionaryDatabase: FlorisUserDictionaryDatabase? = null
private var systemUserDictionaryDatabase: SystemUserDictionaryDatabase? = null
companion object {
private var defaultInstance: DictionaryManager? = null
@@ -53,16 +62,83 @@ class DictionaryManager private constructor(private val applicationContext: Cont
}
if (ref.path.endsWith(".flict")) {
// Assume this is a Flictionary
Flictionary.load(applicationContext, ref).onSuccess { flict ->
dictionaryCache[ref.toString()] = flict
return Result.success(flict)
}.onFailure { err ->
Timber.i(err)
return Result.failure(err)
applicationContext.get()?.let {
Flictionary.load(it, ref).onSuccess { flict ->
dictionaryCache[ref.toString()] = flict
return Result.success(flict)
}.onFailure { err ->
Timber.i(err)
return Result.failure(err)
}
}
} else {
return Result.failure(Exception("Unable to determine supported type for given AssetRef!"))
}
return Result.failure(Exception("If this message is ever thrown, something is completely broken..."))
}
@Synchronized
fun florisUserDictionaryDao(): UserDictionaryDao? {
return if (prefs.suggestion.enabled && prefs.dictionary.enableFlorisUserDictionary) {
florisUserDictionaryDatabase?.userDictionaryDao()
} else {
null
}
}
@Synchronized
fun florisUserDictionaryDatabase(): FlorisUserDictionaryDatabase? {
return if (prefs.suggestion.enabled && prefs.dictionary.enableFlorisUserDictionary) {
florisUserDictionaryDatabase
} else {
null
}
}
@Synchronized
fun systemUserDictionaryDao(): UserDictionaryDao? {
return if (prefs.suggestion.enabled && prefs.dictionary.enableSystemUserDictionary) {
systemUserDictionaryDatabase?.userDictionaryDao()
} else {
null
}
}
@Synchronized
fun systemUserDictionaryDatabase(): SystemUserDictionaryDatabase? {
return if (prefs.suggestion.enabled && prefs.dictionary.enableSystemUserDictionary) {
systemUserDictionaryDatabase
} else {
null
}
}
@Synchronized
fun loadUserDictionariesIfNecessary() {
val context = applicationContext.get() ?: return
if (prefs.suggestion.enabled) {
if (florisUserDictionaryDatabase == null && prefs.dictionary.enableFlorisUserDictionary) {
florisUserDictionaryDatabase = Room.databaseBuilder(
context,
FlorisUserDictionaryDatabase::class.java,
FlorisUserDictionaryDatabase.DB_FILE_NAME
).allowMainThreadQueries().build()
}
if (systemUserDictionaryDatabase == null && prefs.dictionary.enableSystemUserDictionary) {
systemUserDictionaryDatabase = SystemUserDictionaryDatabase(context)
}
}
}
@Synchronized
fun unloadUserDictionariesIfNecessary() {
if (florisUserDictionaryDatabase != null) {
florisUserDictionaryDatabase?.close()
florisUserDictionaryDatabase = null
}
if (systemUserDictionaryDatabase != null) {
systemUserDictionaryDatabase = null
}
}
}

View File

@@ -21,7 +21,6 @@ import dev.patrickgold.florisboard.ime.extension.AssetRef
import dev.patrickgold.florisboard.ime.extension.AssetSource
import dev.patrickgold.florisboard.ime.nlp.*
import java.io.InputStream
import java.util.*
import kotlin.jvm.Throws
/**
@@ -307,7 +306,7 @@ class Flictionary private constructor(
return if (currentToken.data.isNotEmpty()) {
val retList = languageModel.matchAllNgrams(
ngram = Ngram(
_tokens = listOf(Token(currentToken.data.toLowerCase(Locale.ENGLISH))),
_tokens = listOf(Token(currentToken.data.lowercase())),
_freq = -1
),
maxEditDistance = 2,

View File

@@ -0,0 +1,445 @@
/*
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.dictionary
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.UserDictionary
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Delete
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.RoomDatabase
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import androidx.room.Update
import dev.patrickgold.florisboard.ime.extension.ExternalContentUtils
import dev.patrickgold.florisboard.util.LocaleUtils
import java.lang.ref.WeakReference
import java.util.*
private const val WORDS_TABLE = "words"
const val FREQUENCY_MIN = 1
const val FREQUENCY_MAX = 255
const val FREQUENCY_DEFAULT = 128
private const val SORT_BY_WORD_ASC = "${UserDictionary.Words.WORD} ASC"
private const val SORT_BY_WORD_DESC = "${UserDictionary.Words.WORD} DESC"
private const val SORT_BY_FREQ_ASC = "${UserDictionary.Words.FREQUENCY} ASC"
private const val SORT_BY_FREQ_DESC = "${UserDictionary.Words.FREQUENCY} DESC"
private val PROJECTIONS: Array<String> = arrayOf(
UserDictionary.Words._ID,
UserDictionary.Words.WORD,
UserDictionary.Words.FREQUENCY,
UserDictionary.Words.LOCALE,
UserDictionary.Words.SHORTCUT,
)
private val PROJECTIONS_LANGUAGE: Array<String> = arrayOf(
UserDictionary.Words.LOCALE,
)
@Entity(tableName = WORDS_TABLE)
data class UserDictionaryEntry(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = UserDictionary.Words._ID, index = true)
val id: Long,
@ColumnInfo(name = UserDictionary.Words.WORD)
val word: String,
@ColumnInfo(name = UserDictionary.Words.FREQUENCY)
val freq: Int,
@ColumnInfo(name = UserDictionary.Words.LOCALE)
val locale: String?,
@ColumnInfo(name = UserDictionary.Words.SHORTCUT)
val shortcut: String?,
)
@Dao
interface UserDictionaryDao {
companion object {
private const val SELECT_ALL_FROM_WORDS =
"SELECT * FROM $WORDS_TABLE"
private const val LOCALE_MATCHES =
"(${UserDictionary.Words.LOCALE} = :locale OR ${UserDictionary.Words.LOCALE} IS NULL)"
}
@Query("$SELECT_ALL_FROM_WORDS WHERE ${UserDictionary.Words.WORD} LIKE '%' || :word || '%'")
fun query(word: String): List<UserDictionaryEntry>
@Query("$SELECT_ALL_FROM_WORDS WHERE ${UserDictionary.Words.WORD} LIKE '%' || :word || '%' AND $LOCALE_MATCHES")
fun query(word: String, locale: Locale?): List<UserDictionaryEntry>
@Query("$SELECT_ALL_FROM_WORDS WHERE ${UserDictionary.Words.SHORTCUT} = :shortcut")
fun queryShortcut(shortcut: String): List<UserDictionaryEntry>
@Query("$SELECT_ALL_FROM_WORDS WHERE ${UserDictionary.Words.SHORTCUT} = :shortcut AND $LOCALE_MATCHES")
fun queryShortcut(shortcut: String, locale: Locale?): List<UserDictionaryEntry>
@Query(SELECT_ALL_FROM_WORDS)
fun queryAll(): List<UserDictionaryEntry>
@Query("$SELECT_ALL_FROM_WORDS WHERE (${UserDictionary.Words.LOCALE} = :locale AND :locale IS NOT NULL) OR (${UserDictionary.Words.LOCALE} IS NULL AND :locale IS NULL)")
fun queryAll(locale: Locale?): List<UserDictionaryEntry>
@Query("$SELECT_ALL_FROM_WORDS WHERE ${UserDictionary.Words.WORD} = :word")
fun queryExact(word: String): List<UserDictionaryEntry>
@Query("$SELECT_ALL_FROM_WORDS WHERE ${UserDictionary.Words.WORD} = :word AND (${UserDictionary.Words.LOCALE} = :locale OR (${UserDictionary.Words.LOCALE} IS NULL AND :locale IS NULL))")
fun queryExact(word: String, locale: Locale?): List<UserDictionaryEntry>
@Query("SELECT DISTINCT ${UserDictionary.Words.LOCALE} FROM $WORDS_TABLE")
fun queryLanguageList(): List<Locale?>
@Insert
fun insert(entry: UserDictionaryEntry)
@Update
fun update(entry: UserDictionaryEntry)
@Delete
fun delete(entry: UserDictionaryEntry)
@Query("DELETE FROM $WORDS_TABLE")
fun deleteAll()
}
interface UserDictionaryDatabase {
fun userDictionaryDao(): UserDictionaryDao
fun reset()
fun importCombinedList(context: Context, uri: Uri): Result<Unit> {
return ExternalContentUtils.readFromUri(context, uri,2048) { src ->
var isFirstLine = true
src.forEachLine { line ->
if (isFirstLine) {
// Ignore
isFirstLine = false
} else {
var word: String? = null
var freq: Int? = null
var locale: String? = null
var shortcut: String? = null
line.split(';').forEach { property ->
val keyValuePair = property.split('=')
if (keyValuePair.size == 2) {
val key = keyValuePair[0].trim().lowercase()
val value = keyValuePair[1].trim()
when (key) {
"w", "word" -> word = value.ifBlank { null }
"f", "freq" -> runCatching { value.toInt(10) }.onSuccess {
freq = it.coerceIn(FREQUENCY_MIN, FREQUENCY_MAX)
}
"l", "locale" -> locale = when (value) {
"all", "null", "" -> null
else -> value.ifBlank { null }
}
"s", "shortcut" -> shortcut = value.ifBlank { null }
}
}
}
if (word != null && freq != null) {
val alreadyExistingEntries = userDictionaryDao().queryExact(
word!!, locale?.let { LocaleUtils.stringToLocale(it) }
)
if (alreadyExistingEntries.isNotEmpty()) {
userDictionaryDao().update(UserDictionaryEntry(alreadyExistingEntries[0].id, word!!, freq!!, locale, shortcut))
} else {
userDictionaryDao().insert(UserDictionaryEntry(0, word!!, freq!!, locale, shortcut))
}
}
}
}
}
}
fun exportCombinedList(context: Context, uri: Uri): Result<Unit> {
return ExternalContentUtils.writeToUri(context, uri) { dst ->
StringBuilder().apply {
append("dictionary=")
append(uri.lastPathSegment)
append(";date=")
append(System.currentTimeMillis())
append(";generated-by=")
append(context.packageName)
append(";version=1")
appendLine()
dst.write(toString())
}
for (entry in userDictionaryDao().queryAll()) {
StringBuilder().apply {
append(" w=")
append(entry.word)
append(";f=")
append(entry.freq)
append(";l=")
append(entry.locale) // always append locale even if null
if (entry.shortcut != null) {
append(";s=")
append(entry.shortcut)
}
appendLine()
dst.write(toString())
}
}
}
}
}
@Database(entities = [UserDictionaryEntry::class], version = 1)
@TypeConverters(FlorisUserDictionaryDatabase.Converters::class)
abstract class FlorisUserDictionaryDatabase : RoomDatabase(), UserDictionaryDatabase {
companion object {
const val DB_FILE_NAME = "floris_user_dictionary"
}
abstract override fun userDictionaryDao(): UserDictionaryDao
override fun reset() {
TODO("Not yet implemented")
}
class Converters {
@TypeConverter
fun localeToString(locale: Locale?): String? {
return when (locale) {
null -> null
else -> locale.toString()
}
}
@TypeConverter
fun stringToLocale(string: String?): Locale? {
return when (string) {
null, "all", "null", "" -> null
else -> LocaleUtils.stringToLocale(string)
}
}
}
}
class SystemUserDictionaryDatabase(context: Context) : UserDictionaryDatabase {
private val applicationContext: WeakReference<Context> = WeakReference(context.applicationContext ?: context)
private val dao = object : UserDictionaryDao {
override fun query(word: String): List<UserDictionaryEntry> {
return queryResolver(
selection = "${UserDictionary.Words.WORD} LIKE ?",
selectionArgs = arrayOf("%$word%"),
sortOrder = SORT_BY_FREQ_DESC,
)
}
override fun query(word: String, locale: Locale?): List<UserDictionaryEntry> {
return if (locale == null) {
queryResolver(
selection = "${UserDictionary.Words.WORD} LIKE ? AND ${UserDictionary.Words.LOCALE} IS NULL",
selectionArgs = arrayOf("%$word%"),
sortOrder = SORT_BY_FREQ_DESC,
)
} else {
queryResolver(
selection = "${UserDictionary.Words.WORD} LIKE ? AND (${UserDictionary.Words.LOCALE} = ? OR ${UserDictionary.Words.LOCALE} = ? OR ${UserDictionary.Words.LOCALE} IS NULL)",
selectionArgs = arrayOf("%$word%", locale.toString(), locale.language.toString()),
sortOrder = SORT_BY_FREQ_DESC,
)
}
}
override fun queryShortcut(shortcut: String): List<UserDictionaryEntry> {
return queryResolver(
selection = "${UserDictionary.Words.SHORTCUT} = ?",
selectionArgs = arrayOf(shortcut),
sortOrder = SORT_BY_FREQ_DESC,
)
}
override fun queryShortcut(shortcut: String, locale: Locale?): List<UserDictionaryEntry> {
return if (locale == null) {
queryResolver(
selection = "${UserDictionary.Words.SHORTCUT} = ? AND ${UserDictionary.Words.LOCALE} IS NULL",
selectionArgs = arrayOf(shortcut),
sortOrder = SORT_BY_FREQ_DESC,
)
} else {
queryResolver(
selection = "${UserDictionary.Words.SHORTCUT} = ? AND (${UserDictionary.Words.LOCALE} = ? OR ${UserDictionary.Words.LOCALE} = ? OR ${UserDictionary.Words.LOCALE} IS NULL)",
selectionArgs = arrayOf(shortcut, locale.toString(), locale.language.toString()),
sortOrder = SORT_BY_FREQ_DESC,
)
}
}
override fun queryAll(): List<UserDictionaryEntry> {
return queryResolver(
selection = null,
selectionArgs = null,
sortOrder = SORT_BY_FREQ_DESC,
)
}
override fun queryAll(locale: Locale?): List<UserDictionaryEntry> {
return if (locale == null) {
queryResolver(
selection = "${UserDictionary.Words.LOCALE} IS NULL",
selectionArgs = null,
sortOrder = SORT_BY_FREQ_DESC,
)
} else {
queryResolver(
selection = "${UserDictionary.Words.LOCALE} = ?",
selectionArgs = arrayOf(locale.toString()),
sortOrder = SORT_BY_FREQ_DESC,
)
}
}
override fun queryExact(word: String): List<UserDictionaryEntry> {
return queryResolver(
selection = "${UserDictionary.Words.WORD} = ?",
selectionArgs = arrayOf(word),
sortOrder = null,
)
}
override fun queryExact(word: String, locale: Locale?): List<UserDictionaryEntry> {
return if (locale == null) {
queryResolver(
selection = "${UserDictionary.Words.WORD} = ? AND ${UserDictionary.Words.LOCALE} IS NULL",
selectionArgs = arrayOf(word),
sortOrder = SORT_BY_FREQ_DESC,
)
} else {
queryResolver(
selection = "${UserDictionary.Words.WORD} LIKE ? AND ${UserDictionary.Words.LOCALE} = ?",
selectionArgs = arrayOf(word, locale.toString()),
sortOrder = SORT_BY_FREQ_DESC,
)
}
}
override fun queryLanguageList(): List<Locale?> {
val resolver = applicationContext.get()?.contentResolver ?: return listOf()
val cursor = resolver.query(
UserDictionary.Words.CONTENT_URI,
PROJECTIONS_LANGUAGE,
null,
null,
null
) ?: return listOf()
if (cursor.count <= 0) {
return listOf()
}
val localeIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE)
val retList = mutableSetOf<Locale?>()
while (cursor.moveToNext()) {
val localeStr = cursor.getString(localeIndex)
if (localeStr == null) {
retList.add(null)
} else {
retList.add(LocaleUtils.stringToLocale(localeStr))
}
}
cursor.close()
return retList.toList()
}
private fun queryResolver(selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): List<UserDictionaryEntry> {
val resolver = applicationContext.get()?.contentResolver ?: return listOf()
val cursor = resolver.query(
UserDictionary.Words.CONTENT_URI,
PROJECTIONS,
selection,
selectionArgs,
sortOrder
) ?: return listOf()
return parseEntries(cursor).also { cursor.close() }
}
private fun parseEntries(cursor: Cursor): List<UserDictionaryEntry> {
if (cursor.count <= 0) {
return listOf()
}
val idIndex = cursor.getColumnIndex(UserDictionary.Words._ID)
val wordIndex = cursor.getColumnIndex(UserDictionary.Words.WORD)
val freqIndex = cursor.getColumnIndex(UserDictionary.Words.FREQUENCY)
val localeIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE)
val shortcutIndex = cursor.getColumnIndex(UserDictionary.Words.SHORTCUT)
val retList = mutableListOf<UserDictionaryEntry>()
while (cursor.moveToNext()) {
retList.add(
UserDictionaryEntry(
id = cursor.getLong(idIndex),
word = cursor.getString(wordIndex),
freq = cursor.getInt(freqIndex),
locale = cursor.getString(localeIndex),
shortcut = cursor.getString(shortcutIndex)
)
)
}
return retList
}
override fun insert(entry: UserDictionaryEntry) {
val resolver = applicationContext.get()?.contentResolver ?: return
val contentValues = ContentValues(5).apply {
put(UserDictionary.Words.WORD, entry.word)
put(UserDictionary.Words.FREQUENCY, entry.freq)
put(UserDictionary.Words.LOCALE, entry.locale)
put(UserDictionary.Words.APP_ID, 0)
put(UserDictionary.Words.SHORTCUT, entry.shortcut)
}
resolver.insert(UserDictionary.Words.CONTENT_URI, contentValues)
}
override fun update(entry: UserDictionaryEntry) {
val resolver = applicationContext.get()?.contentResolver ?: return
val contentValues = ContentValues(4).apply {
put(UserDictionary.Words.WORD, entry.word)
put(UserDictionary.Words.FREQUENCY, entry.freq)
put(UserDictionary.Words.LOCALE, entry.locale)
put(UserDictionary.Words.SHORTCUT, entry.shortcut)
}
resolver.update(UserDictionary.Words.CONTENT_URI, contentValues, "${UserDictionary.Words._ID} = ${entry.id}", null)
}
override fun delete(entry: UserDictionaryEntry) {
val resolver = applicationContext.get()?.contentResolver ?: return
resolver.delete(UserDictionary.Words.CONTENT_URI, "${UserDictionary.Words._ID} = ${entry.id}", null)
}
override fun deleteAll() {
// Unsupported action
}
}
override fun userDictionaryDao(): UserDictionaryDao {
return dao
}
override fun reset() {
TODO("Not yet implemented")
}
}

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.extension
import java.util.*
/**
* Sealed class which specifies where an asset comes from. There are 3 different types, all of which
* require a different approach on how to access the actual asset.
@@ -50,7 +48,7 @@ sealed class AssetSource {
private val externalRegex: Regex = """^external\\(([a-z]+\\.)*[a-z]+\\)\$""".toRegex()
fun fromString(str: String): Result<AssetSource> {
return when (val string = str.toLowerCase(Locale.ENGLISH)) {
return when (val string = str.lowercase()) {
"assets" -> Result.success(Assets)
"internal" -> Result.success(Internal)
else -> {

View File

@@ -14,35 +14,58 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalContracts::class)
package dev.patrickgold.florisboard.ime.extension
import android.content.Context
import android.net.Uri
import java.io.BufferedReader
import java.io.BufferedWriter
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
class ExternalContentUtils private constructor() {
companion object {
fun readTextFromUri(context: Context, uri: Uri, maxSize: Int): Result<String> {
val contentResolver = context.contentResolver
?: return Result.failure(NullPointerException("System content resolver not available"))
val inputStream = contentResolver.openInputStream(uri)
?: return Result.failure(NullPointerException("Cannot open input stream for given uri '$uri'"))
val assetFileDescriptor = contentResolver.openAssetFileDescriptor(uri, "r")
?: return Result.failure(NullPointerException("Cannot open asset file descriptor for given uri '$uri'"))
if (assetFileDescriptor.length > maxSize) {
return Result.failure(Exception("Contents of given uri '$uri' exceeds maximum size of $maxSize bytes!"))
inline fun <R> readFromUri(context: Context, uri: Uri, maxSize: Int, block: (it: BufferedReader) -> R): Result<R> {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return runCatching {
val contentResolver = context.contentResolver
?: throw NullPointerException("System content resolver not available")
val inputStream = contentResolver.openInputStream(uri)
?: throw NullPointerException("Cannot open input stream for given uri '$uri'")
val assetFileDescriptor = contentResolver.openAssetFileDescriptor(uri, "r")
?: throw NullPointerException("Cannot open asset file descriptor for given uri '$uri'")
if (assetFileDescriptor.length > maxSize) {
throw Exception("Contents of given uri '$uri' exceeds maximum size of $maxSize bytes!")
}
inputStream.bufferedReader(Charsets.UTF_8).use { block(it) }
}
}
fun readTextFromUri(context: Context, uri: Uri, maxSize: Int): Result<String> {
return readFromUri(context, uri, maxSize) { it.readText() }
}
inline fun writeToUri(context: Context, uri: Uri, block: (it: BufferedWriter) -> Unit): Result<Unit> {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return runCatching {
val contentResolver = context.contentResolver
?: throw NullPointerException("System content resolver not available")
// Must use "rwt" mode to ensure destination file length is truncated after writing.
val outputStream = contentResolver.openOutputStream(uri, "rwt")
?: throw NullPointerException("Cannot open output stream for given uri '$uri'")
outputStream.bufferedWriter(Charsets.UTF_8).use { block(it) }
}
val rawText = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
return Result.success(rawText)
}
fun writeTextToUri(context: Context, uri: Uri, text: String): Result<Unit> {
val contentResolver = context.contentResolver
?: return Result.failure(NullPointerException("System content resolver not available"))
// Must use "rwt" mode to ensure destination file length is truncated after writing.
val outputStream = contentResolver.openOutputStream(uri, "rwt")
?: return Result.failure(NullPointerException("Cannot open output stream for given uri '$uri'"))
outputStream.bufferedWriter(Charsets.UTF_8).use { it.write(text) }
return Result.success(Unit)
return writeToUri(context, uri) { it.write(text) }
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.
*/
@file:OptIn(ExperimentalContracts::class)
package dev.patrickgold.florisboard.ime.keyboard
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Abstract class definition for providing icons to a keyboard view. This class has been introduced to remove the need
* for keyboard vies to re-fetch drawable resources every time they draw on the canvas. The exact implementation is
* dependent on the subclass.
*/
abstract class KeyboardIconSet {
/**
* Get the drawable for the given [id].
*
* @param id The Android resource id of the drawable which should be returned.
*
* @return The drawable for given [id] or null if this icon set does not contain a drawable for this id.
*/
abstract fun getDrawable(@DrawableRes id: Int): Drawable?
/**
* Performs [block] on the drawable with the given [id]. If no drawable for the id exists,[block] will not be
* called at all.
*
* @param id The Android resource id of the drawable which should be used to execute block with.
* @param block The block which should be executed with the returned drawable.
*/
inline fun withDrawable(@DrawableRes id: Int, block: Drawable.() -> Unit) {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
val drawable = getDrawable(id)
if (drawable != null) {
synchronized(drawable) {
block(drawable)
}
}
}
}

View File

@@ -22,7 +22,7 @@ import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
@@ -30,13 +30,12 @@ import kotlinx.coroutines.channels.sendBlocking
@Suppress("MemberVisibilityCanBePrivate")
abstract class KeyboardView : View, ThemeManager.OnThemeUpdatedListener {
protected val florisboard: FlorisBoard?
get() = FlorisBoard.getInstanceOrNull()
protected val prefs: PrefHelper
get() = PrefHelper.getDefaultInstance(context)
protected val themeManager: ThemeManager?
get() = ThemeManager.defaultOrNull()
protected val florisboard get() = FlorisBoard.getInstanceOrNull()
protected val prefs get() = Preferences.default()
protected val themeManager get() = ThemeManager.defaultOrNull()
var isMeasured: Boolean = false
private set
protected var isTouchable: Boolean = true
protected val touchEventChannel: Channel<MotionEvent> = Channel(16)
protected val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
@@ -85,6 +84,11 @@ abstract class KeyboardView : View, ThemeManager.OnThemeUpdatedListener {
protected abstract fun onTouchEventInternal(event: MotionEvent)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
isMeasured = true
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
onLayoutInternal()

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.landscapeinput
import java.util.*
enum class LandscapeInputUiMode {
DYNAMICALLY_SHOW,
NEVER_SHOW,
@@ -25,7 +23,7 @@ enum class LandscapeInputUiMode {
companion object {
fun fromString(string: String): LandscapeInputUiMode {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
}

View File

@@ -161,9 +161,9 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
*/
private fun createTabViewFor(tab: Tab): LinearLayout {
return when (tab) {
Tab.EMOJI -> EmojiKeyboardView(florisboard)
Tab.EMOTICON -> EmoticonKeyboardView(florisboard)
else -> LinearLayout(florisboard).apply {
Tab.EMOJI -> EmojiKeyboardView(florisboard.themeContext)
Tab.EMOTICON -> EmoticonKeyboardView(florisboard.themeContext)
else -> LinearLayout(florisboard.themeContext).apply {
addView(TextView(context).apply {
text = "not yet implemented"
})

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.media.emoji
import java.util.*
/**
* Enum for emoji category.
* List taken from https://unicode.org/Public/emoji/13.0/emoji-test.txt
@@ -39,7 +37,7 @@ enum class EmojiCategory {
companion object {
fun fromString(string: String): EmojiCategory {
return valueOf(string.replace(" & ", "_").toUpperCase(Locale.ENGLISH))
return valueOf(string.replace(" & ", "_").uppercase())
}
}
}

View File

@@ -26,6 +26,10 @@ class EmojiKey(override val data: KeyData) : Key(data) {
var computedPopups: PopupSet<EmojiKeyData> = PopupSet()
private set
companion object {
val EMPTY = EmojiKey(EmojiKeyData.EMPTY)
}
fun dummyCompute() {
computedData = data as? EmojiKeyData ?: computedData
computedPopups = PopupSet(relevant = (data as? EmojiKeyData)?.popup ?: listOf())

View File

@@ -0,0 +1,59 @@
package dev.patrickgold.florisboard.ime.media.emoji
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.flexbox.FlexboxLayoutManager
class EmojiKeyAdapter(
private val dataSet: List<EmojiKey>,
private val emojiKeyboardView: EmojiKeyboardView,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val PLACEHOLDER_EMOJI_COUNT = 24
}
class EmojiKeyViewHolder(view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return EmojiKeyViewHolder(EmojiKeyView(emojiKeyboardView, EmojiKey(EmojiKeyData.EMPTY)))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (position < dataSet.size) {
(holder.itemView as EmojiKeyView).key = dataSet[position]
holder.itemView.layoutParams = FlexboxLayoutManager.LayoutParams(
emojiKeyboardView.emojiKeyWidth, emojiKeyboardView.emojiKeyHeight
)
} else {
(holder.itemView as EmojiKeyView).key = EmojiKey.EMPTY
holder.itemView.layoutParams = FlexboxLayoutManager.LayoutParams(
emojiKeyboardView.emojiKeyWidth, 0
)
}
}
override fun getItemCount(): Int {
// Add empty placeholder emojis at the end so the grid view. Below is an illustration how
// the UI looks with and without an placeholder (e = emoji):
// Without placeholder With placeholder
// e e e e e e e e e e e e e e
// ............. .............
// e e e e e e e e e e e e e e
// e e e e e e e e
//
// Based on this SO's answer idea (by La Nube - Luis R. Díaz Muñiz):
// https://stackoverflow.com/a/31478004/6801193
//
// 24 items are chosen here because that's probably the max items that will be shown per
// row, even in landscape mode.
return dataSet.size + PLACEHOLDER_EMOJI_COUNT
}
override fun getItemViewType(position: Int): Int {
return 0
}
}

View File

@@ -41,13 +41,22 @@ class EmojiKeyData(
return null
}
private var string: String? = null
override fun asString(isForDisplay: Boolean): String {
return StringBuilder().run {
for (codePoint in codePoints) {
append(Character.toChars(codePoint))
if (string == null) {
string = StringBuilder().run {
for (codePoint in codePoints) {
append(Character.toChars(codePoint))
}
toString()
}
toString()
}
return string!!
}
companion object {
val EMPTY = EmojiKeyData(listOf())
}
override fun toString(): String {

View File

@@ -23,11 +23,11 @@ import android.os.Handler
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.widget.ScrollView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
@@ -45,16 +45,22 @@ import kotlinx.coroutines.MainScope
@SuppressLint("ViewConstructor")
class EmojiKeyView(
private val emojiKeyboardView: EmojiKeyboardView,
val key: EmojiKey
key: EmojiKey
) : androidx.appcompat.widget.AppCompatTextView(emojiKeyboardView.context), CoroutineScope by MainScope(),
FlorisBoard.EventListener, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val prefs get() = Preferences.default()
private var isCancelled: Boolean = false
private var osHandler: Handler? = null
private var triangleDrawable: Drawable? = null
var key: EmojiKey = key
set(value) {
field = value
text = value.data.asString(true)
}
init {
background = null
gravity = Gravity.CENTER
@@ -95,7 +101,7 @@ class EmojiKeyView(
osHandler = Handler()
}
osHandler?.postDelayed({
(parent.parent as ScrollView)
(parent as RecyclerView)
.requestDisallowInterceptTouchEvent(true)
emojiKeyboardView.isScrollBlocked = true
emojiKeyboardView.popupManager.show(key, KeyHintMode.DISABLED)
@@ -126,7 +132,8 @@ class EmojiKeyView(
emojiKeyboardView.popupManager.getActiveEmojiKeyData(key)
emojiKeyboardView.popupManager.hide()
if (event.actionMasked != MotionEvent.ACTION_CANCEL &&
retData != null && !isCancelled) {
retData != null && !isCancelled
) {
if (!emojiKeyboardView.isScrollBlocked) {
florisboard?.keyPressVibrate()
florisboard?.keyPressSound()

View File

@@ -21,13 +21,10 @@ import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.*
import com.google.android.flexbox.FlexDirection
import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.JustifyContent
import androidx.recyclerview.widget.RecyclerView
import com.google.android.flexbox.*
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
@@ -51,12 +48,13 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
private var activeCategory: EmojiCategory = EmojiCategory.SMILEYS_EMOTION
private var emojiViewFlipper: ViewFlipper
private val emojiKeyWidth = resources.getDimension(R.dimen.emoji_key_width).toInt()
private val emojiKeyHeight = resources.getDimension(R.dimen.emoji_key_height).toInt()
val emojiKeyWidth = resources.getDimension(R.dimen.emoji_key_width).toInt()
val emojiKeyHeight = resources.getDimension(R.dimen.emoji_key_height).toInt()
private var layouts: Deferred<EmojiLayoutDataMap>
private val mainScope = MainScope()
private val tabLayout: TabLayout
private val uiLayouts = EnumMap<EmojiCategory, ScrollView>(EmojiCategory::class.java)
private val uiLayouts = EnumMap<EmojiCategory, RecyclerView>(EmojiCategory::class.java)
private val layoutAdapters = EnumMap<EmojiCategory, EmojiKeyAdapter>(EmojiCategory::class.java)
var isScrollBlocked: Boolean = false
var popupManager = PopupManager(this, florisboard?.popupLayerView)
@@ -83,18 +81,20 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
ViewGroup.inflate(context, R.layout.media_input_emoji_tabs, null) as TabLayout
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
setActiveCategory(when (tab?.position) {
0 -> EmojiCategory.SMILEYS_EMOTION
1 -> EmojiCategory.PEOPLE_BODY
2 -> EmojiCategory.ANIMALS_NATURE
3 -> EmojiCategory.FOOD_DRINK
4 -> EmojiCategory.TRAVEL_PLACES
5 -> EmojiCategory.ACTIVITIES
6 -> EmojiCategory.OBJECTS
7 -> EmojiCategory.SYMBOLS
8 -> EmojiCategory.FLAGS
else -> EmojiCategory.SMILEYS_EMOTION
})
setActiveCategory(
when (tab?.position) {
0 -> EmojiCategory.SMILEYS_EMOTION
1 -> EmojiCategory.PEOPLE_BODY
2 -> EmojiCategory.ANIMALS_NATURE
3 -> EmojiCategory.FOOD_DRINK
4 -> EmojiCategory.TRAVEL_PLACES
5 -> EmojiCategory.ACTIVITIES
6 -> EmojiCategory.OBJECTS
7 -> EmojiCategory.SYMBOLS
8 -> EmojiCategory.FLAGS
else -> EmojiCategory.SMILEYS_EMOTION
}
)
}
override fun onTabReselected(tab: TabLayout.Tab?) {}
@@ -118,6 +118,8 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
override fun onDetachedFromWindow() {
themeManager.unregisterOnThemeUpdatedListener(this)
florisboard?.removeEventListener(this)
layoutAdapters.clear()
uiLayouts.clear()
super.onDetachedFromWindow()
}
@@ -127,11 +129,13 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
* when it attaches a built category layout to the view hierarchy.
*/
private suspend fun buildLayout() = withContext(Dispatchers.Default) {
val recycledViewPool = RecyclerView.RecycledViewPool()
recycledViewPool.setMaxRecycledViews(0, 64)
for (category in EmojiCategory.values()) {
val scrollView = buildLayoutForCategory(category)
uiLayouts[category] = scrollView
val recyclerView = buildLayoutForCategory(category, recycledViewPool)
uiLayouts[category] = recyclerView
withContext(Dispatchers.Main) {
emojiViewFlipper.addView(scrollView)
emojiViewFlipper.addView(recyclerView)
}
}
}
@@ -145,48 +149,27 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
*/
@SuppressLint("ClickableViewAccessibility")
private suspend fun buildLayoutForCategory(
category: EmojiCategory
): ScrollView = withContext(Dispatchers.Default) {
val scrollView = ScrollView(context)
scrollView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
val flexboxLayout = FlexboxLayout(context)
flexboxLayout.layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
flexboxLayout.flexDirection = FlexDirection.ROW
flexboxLayout.justifyContent = JustifyContent.SPACE_BETWEEN
flexboxLayout.flexWrap = FlexWrap.WRAP
for (emojiKeyData in layouts.await()[category].orEmpty()) {
val emojiKeyView =
EmojiKeyView(this@EmojiKeyboardView, EmojiKey(emojiKeyData).also { it.dummyCompute() })
emojiKeyView.layoutParams = FlexboxLayout.LayoutParams(
emojiKeyWidth, emojiKeyHeight
)
flexboxLayout.addView(emojiKeyView)
}
// Add empty placeholder emojis at the end so the grid view. Below is an illustration how
// the UI looks with and without an placeholder (e = emoji):
// Without placeholder With placeholder
// e e e e e e e e e e e e e e
// ............. .............
// e e e e e e e e e e e e e e
// e e e e e e e e
//
// Based on this SO's answer idea (by La Nube - Luis R. Díaz Muñiz):
// https://stackoverflow.com/a/31478004/6801193
//
// 24 items are chosen here because that's probably the max items that will be shown per
// row, even in landscape mode.
for (n in 0 until 24) {
val gridPlaceholderView = View(context).apply {
layoutParams = LayoutParams(emojiKeyWidth, 0)
}
flexboxLayout.addView(gridPlaceholderView)
}
scrollView.setOnTouchListener { _, _ ->
category: EmojiCategory,
recycledViewPool: RecyclerView.RecycledViewPool
): RecyclerView = withContext(Dispatchers.Default) {
val recyclerView = RecyclerView(context)
val layoutManager = FlexboxLayoutManager(context)
recyclerView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
layoutManager.justifyContent = JustifyContent.SPACE_BETWEEN
layoutManager.flexWrap = FlexWrap.WRAP
recyclerView.layoutManager = layoutManager
recyclerView.setRecycledViewPool(recycledViewPool)
val adapter = EmojiKeyAdapter(
layouts.await()[category].orEmpty().map { data -> EmojiKey(data).also { it.dummyCompute() } },
this@EmojiKeyboardView
)
layoutAdapters[category] = adapter
recyclerView.adapter = adapter
recyclerView.setOnTouchListener { _, _ ->
return@setOnTouchListener isScrollBlocked
}
scrollView.addView(flexboxLayout)
return@withContext scrollView
return@withContext recyclerView
}
/**
@@ -195,6 +178,8 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
* @param newActiveCategory The new active category.
*/
fun setActiveCategory(newActiveCategory: EmojiCategory) {
// setting adapter forces recyclerview to return its views, so it can be used for new category
uiLayouts[activeCategory]?.adapter = layoutAdapters[activeCategory]
emojiViewFlipper.displayedChild =
emojiViewFlipper.indexOfChild(uiLayouts[newActiveCategory])
activeCategory = newActiveCategory
@@ -219,9 +204,11 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
* of focus and prevents the HorizontalScrollView to scroll within this MotionEvent.
*/
fun dismissKeyView(keyView: EmojiKeyView) {
keyView.onTouchEvent(MotionEvent.obtain(
0, 0, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0
))
keyView.onTouchEvent(
MotionEvent.obtain(
0, 0, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0
)
)
isScrollBlocked = true
}

View File

@@ -106,7 +106,7 @@ fun parseRawEmojiSpecsFile(
if (line.startsWith(GROUP_IDENTIFIER, true)) {
// A new group begins
val rawGroupName = line.trim().substring(GROUP_IDENTIFIER.length)
if (rawGroupName.toUpperCase(Locale.ENGLISH) == "COMPONENT") {
if (rawGroupName.uppercase() == "COMPONENT") {
skipUntilNextGroup = true
continue
} else {
@@ -130,7 +130,7 @@ fun parseRawEmojiSpecsFile(
val dataC = data2[0].trim()
val dataQ = data2[1].trim()
val dataN = data[1].split(NAME_JUNK_SPLIT_REGEX)[1]
if (dataQ.toLowerCase(Locale.ENGLISH) == FULLY_QUALIFIED) {
if (dataQ.lowercase() == FULLY_QUALIFIED) {
// Only fully-qualified emojis are accepted
val dataCPs = dataC.split(" ")
val key = EmojiKeyData(listStringToListInt(dataCPs), dataN)

View File

@@ -24,12 +24,14 @@ import android.widget.ImageButton
import android.widget.LinearLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
class OneHandedPanel : LinearLayout, ThemeManager.OnThemeUpdatedListener {
private var florisboard: FlorisBoard? = null
private var themeManager: ThemeManager? = null
private val prefs get() = Preferences.default()
private var closeBtn: ImageButton? = null
private var moveBtn: ImageButton? = null
@@ -55,14 +57,14 @@ class OneHandedPanel : LinearLayout, ThemeManager.OnThemeUpdatedListener {
closeBtn = findViewWithTag("one_handed_ctrl_close")
closeBtn?.setOnClickListener {
florisboard?.let {
it.prefs.keyboard.oneHandedMode = OneHandedMode.OFF
prefs.keyboard.oneHandedMode = OneHandedMode.OFF
it.updateOneHandedPanelVisibility()
}
}
moveBtn = findViewWithTag("one_handed_ctrl_move")
moveBtn?.setOnClickListener {
florisboard?.let {
it.prefs.keyboard.oneHandedMode = panelSide
prefs.keyboard.oneHandedMode = panelSide
it.updateOneHandedPanelVisibility()
}
}
@@ -96,7 +98,7 @@ class OneHandedPanel : LinearLayout, ThemeManager.OnThemeUpdatedListener {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val florisboard = florisboard ?: return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = (florisboard.inputView?.measuredWidth ?: 0) *
((100 - florisboard.prefs.keyboard.oneHandedModeScaleFactor) / 100.0f)
((100 - prefs.keyboard.oneHandedModeScaleFactor) / 100.0f)
super.onMeasure(MeasureSpec.makeMeasureSpec(width.toInt(), MeasureSpec.EXACTLY), heightMeasureSpec)
}
}

View File

@@ -42,6 +42,7 @@ import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
import dev.patrickgold.florisboard.ime.text.smartbar.SmartbarView
import kotlinx.coroutines.*
import org.json.JSONArray
import java.util.*
import kotlin.math.roundToLong
/**
@@ -59,8 +60,8 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
FlorisBoard.EventListener, SmartbarView.EventListener {
var isGlidePostEffect: Boolean = false
private val florisboard = FlorisBoard.getInstance()
private val prefs: PrefHelper get() = florisboard.prefs
private val florisboard get() = FlorisBoard.getInstance()
private val prefs get() = Preferences.default()
val symbolsWithSpaceAfter: List<String>
private val activeEditorInstance: EditorInstance
get() = florisboard.activeEditorInstance
@@ -226,7 +227,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
}
for (subtype in subtypes) {
for (mode in KeyboardMode.values()) {
keyboards.set(mode, subtype, keyboard = layoutManager.computeKeyboardAsync(mode, subtype, prefs))
keyboards.set(mode, subtype, keyboard = layoutManager.computeKeyboardAsync(mode, subtype))
}
}
}
@@ -299,6 +300,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
keyboards.clear()
inputEventDispatcher.keyEventReceiver = null
inputEventDispatcher.close()
dictionaryManager.unloadUserDictionariesIfNecessary()
cancel()
layoutManager.onDestroy()
instance = null
@@ -375,6 +377,9 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
}
override fun onWindowShown() {
launch(Dispatchers.Default) {
dictionaryManager.loadUserDictionariesIfNecessary()
}
smartbarView?.updateSmartbarState()
}
@@ -404,8 +409,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
val activeKeyboard = keyboards.getOrElseAsync(mode, subtype) {
layoutManager.computeKeyboardAsync(
keyboardMode = mode,
subtype = subtype,
prefs = prefs
subtype = subtype
).await()
}.await()
withContext(Dispatchers.Main) {
@@ -422,7 +426,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
}
}
}
if (PrefHelper.getDefaultInstance(florisboard).glide.enabled) {
if (prefs.glide.enabled) {
GlideTypingManager.getInstance().setWordData(newSubtype)
}
setActiveKeyboard(getActiveKeyboardMode(), newSubtype)
@@ -440,18 +444,22 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
}
smartbarView?.updateSmartbarState()
flogInfo(LogTopic.IMS_EVENTS) { "current word: ${activeEditorInstance.cachedInput.currentWord.text}" }
if (activeEditorInstance.isComposingEnabled && !inputEventDispatcher.isPressed(KeyCode.DELETE)) {
if (activeEditorInstance.isComposingEnabled && !inputEventDispatcher.isPressed(KeyCode.DELETE) && !isGlidePostEffect) {
if (activeEditorInstance.shouldReevaluateComposingSuggestions) {
activeEditorInstance.shouldReevaluateComposingSuggestions = false
activeDictionary?.let {
launch(Dispatchers.Default) {
val startTime = System.nanoTime()
val suggestions = it.getTokenPredictions(
val suggestions = queryUserDictionary(
activeEditorInstance.cachedInput.currentWord.text,
florisboard.activeSubtype.locale
).toMutableList()
suggestions.addAll(it.getTokenPredictions(
precedingTokens = listOf(),
currentToken = Token(activeEditorInstance.cachedInput.currentWord.text),
maxSuggestionCount = 16,
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive
).toStringList()
).toStringList())
if (BuildConfig.DEBUG) {
val elapsed = (System.nanoTime() - startTime) / 1000.0
flogInfo { "sugg fetch time: $elapsed us" }
@@ -472,6 +480,40 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
smartbarView?.onPrimaryClipChanged()
}
private fun queryUserDictionary(word: String, locale: Locale): Set<String> {
val florisDao = dictionaryManager.florisUserDictionaryDao()
val systemDao = dictionaryManager.systemUserDictionaryDao()
if (florisDao == null && systemDao == null) {
return setOf()
}
val retList = mutableSetOf<String>()
if (prefs.dictionary.enableFlorisUserDictionary) {
florisDao?.query(word, locale)?.let {
for (entry in it) {
retList.add(entry.word)
}
}
florisDao?.queryShortcut(word, locale)?.let {
for (entry in it) {
retList.add(entry.word)
}
}
}
if (prefs.dictionary.enableSystemUserDictionary) {
systemDao?.query(word, locale)?.let {
for (entry in it) {
retList.add(entry.word)
}
}
systemDao?.queryShortcut(word, locale)?.let {
for (entry in it) {
retList.add(entry.word)
}
}
}
return retList
}
/**
* Updates the current caps state according to the [EditorInstance.cursorCapsMode], while
* respecting [capsLock] property and the correction.autoCapitalization preference.
@@ -918,8 +960,8 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(), In
*/
fun fixCase(word: String): String {
return when {
capsLock -> word.toUpperCase(florisboard.activeSubtype.locale)
caps -> word.capitalize(florisboard.activeSubtype.locale)
capsLock -> word.uppercase(florisboard.activeSubtype.locale)
caps -> word.replaceFirstChar { if (it.isLowerCase()) it.titlecase(florisboard.activeSubtype.locale) else it.toString() }
else -> word
}
}

View File

@@ -31,7 +31,7 @@ import androidx.appcompat.widget.AppCompatImageButton
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.InputKeyEvent
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.ime.theme.Theme
@@ -45,7 +45,7 @@ import dev.patrickgold.florisboard.util.postAtScheduledRate
*/
class EditingKeyView : AppCompatImageButton, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val prefs get() = Preferences.default()
private val themeManager: ThemeManager = ThemeManager.default()
private val data: TextKeyData = when (id) {
R.id.arrow_down -> TextKeyData.ARROW_DOWN

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.text.gestures
import java.util.*
/**
* Enum for declaring the distance thresholds for swipe gestures.
*/
@@ -30,11 +28,11 @@ enum class DistanceThreshold {
companion object {
fun fromString(string: String): DistanceThreshold {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ENGLISH)
return super.toString().lowercase()
}
}

View File

@@ -2,6 +2,7 @@ package dev.patrickgold.florisboard.ime.text.gestures
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.keyboard.Key
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
/**
* Inherit this to be able to handle gesture typing. Takes in raw pointer data, and
@@ -17,7 +18,7 @@ interface GlideTypingClassifier {
/**
* Change the layout of the gesture classifier.
*/
fun setLayout(keyViews: Sequence<Key>, subtype: Subtype)
fun setLayout(keyViews: List<TextKey>, subtype: Subtype)
/**
* Change the word data of the gesture classifier.
@@ -38,5 +39,4 @@ interface GlideTypingClassifier {
fun getSuggestions(maxSuggestionCount: Int, gestureCompleted: Boolean): List<String>
fun clear()
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.view.MotionEvent
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
import kotlin.math.pow
import kotlin.math.sqrt
@@ -33,7 +34,7 @@ class GlideTypingGesture {
* Method which evaluates if a given [event] is a gesture.
* @return whether or not the event was interpreted as part of a gesture.
*/
fun onTouchEvent(event: MotionEvent, initialKeyCodes: MutableMap<Int, Int>): Boolean {
fun onTouchEvent(event: MotionEvent, initialKey: TextKey?): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
@@ -69,12 +70,12 @@ class GlideTypingGesture {
// evaluate whether is actually a gesture
val dist = pointerData.positions[0].dist(pos)
val time = (System.currentTimeMillis() - pointerData.startTime) + 1
if (dist > keySize && (dist / time) > VELOCITY_THRESHOLD && (initialKeyCodes[pointerId] !in SWIPE_GESTURE_KEYS)) {
if (dist > keySize && (dist / time) > VELOCITY_THRESHOLD && (initialKey?.computedData?.code !in SWIPE_GESTURE_KEYS)) {
pointerData.isActuallyGesture = true
// Let listener know all those points need to be added.
pointerData.positions.take(pointerData.positions.size - 1).forEach { point ->
listeners.forEach {
it.onGestureAdd(point)
it.onGlideAddPoint(point)
}
}
} else if (time > MAX_DETECT_TIME) {
@@ -84,7 +85,7 @@ class GlideTypingGesture {
}
if (pointerData.isActuallyGesture == true)
pointerData.positions.last().let { point -> listeners.forEach { it.onGestureAdd(point) } }
pointerData.positions.last().let { point -> listeners.forEach { it.onGlideAddPoint(point) } }
}
return pointerData.isActuallyGesture ?: false
}
@@ -95,14 +96,14 @@ class GlideTypingGesture {
return false
}
if (pointerData.isActuallyGesture == true) {
listeners.forEach { listener -> listener.onGestureComplete(pointerData) }
listeners.forEach { listener -> listener.onGlideComplete(pointerData) }
}
resetState()
return false
}
MotionEvent.ACTION_CANCEL -> {
if (pointerData.isActuallyGesture == true) {
listeners.forEach { it.onGestureCancelled() }
listeners.forEach { it.onGlideCancelled() }
}
resetState()
}
@@ -112,7 +113,11 @@ class GlideTypingGesture {
}
fun registerListener(listener: Listener) {
this.listeners.add(listener)
listeners.add(listener)
}
fun unregisterListener(listener: Listener) {
listeners.remove(listener)
}
private fun resetState() {
@@ -145,18 +150,17 @@ class GlideTypingGesture {
/**
* Called when a gesture is complete.
*/
fun onGestureComplete(data: Detector.PointerData) {}
fun onGlideComplete(data: Detector.PointerData) {}
/**
* Called when a point is added to a gesture.
* Will not be called before a series of events is detected as a gesture.
*/
fun onGestureAdd(point: Detector.Position) {}
fun onGlideAddPoint(point: Detector.Position) {}
/**
* Called to cancel a gesture
*/
fun onGestureCancelled() {}
fun onGlideCancelled() {}
}
}

View File

@@ -1,26 +1,24 @@
package dev.patrickgold.florisboard.ime.text.gestures
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.extension.AssetRef
import dev.patrickgold.florisboard.ime.extension.AssetSource
import dev.patrickgold.florisboard.ime.keyboard.Key
import dev.patrickgold.florisboard.ime.text.TextInputManager
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
import kotlinx.coroutines.*
import org.json.JSONObject
import kotlin.math.min
/**
* Handles the [GlideTypingClassifier]. Basically responsible for linking [GlideTypingGesture.Detector]
* with [GlideTypingClassifier].
*/
class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainScope() {
private var glideTypingClassifier = StatisticalGlideTypingClassifier()
private val initialDimensions: HashMap<Subtype, Dimensions> = hashMapOf()
private var currentDimensions: Dimensions = Dimensions(0f, 0f)
private lateinit var prefHelper: PrefHelper
private val prefs get() = Preferences.default()
companion object {
private const val MAX_SUGGESTION_COUNT = 8
@@ -29,30 +27,29 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
fun getInstance(): GlideTypingManager {
if (!this::glideTypingManager.isInitialized) {
glideTypingManager = GlideTypingManager()
glideTypingManager.prefHelper = FlorisBoard.getInstance().prefs
}
return glideTypingManager
}
}
override fun onGestureComplete(data: GlideTypingGesture.Detector.PointerData) {
override fun onGlideComplete(data: GlideTypingGesture.Detector.PointerData) {
updateSuggestionsAsync(MAX_SUGGESTION_COUNT, true) {
glideTypingClassifier.clear()
}
}
override fun onGestureCancelled() {
override fun onGlideCancelled() {
glideTypingClassifier.clear()
}
private var lastTime = System.currentTimeMillis()
override fun onGestureAdd(point: GlideTypingGesture.Detector.Position) {
val normalized = GlideTypingGesture.Detector.Position(normalizeX(point.x), normalizeY(point.y))
override fun onGlideAddPoint(point: GlideTypingGesture.Detector.Position) {
val normalized = GlideTypingGesture.Detector.Position(point.x, point.y)
this.glideTypingClassifier.addGesturePoint(normalized)
val time = System.currentTimeMillis()
if (prefHelper.glide.showPreview && time - lastTime > prefHelper.glide.previewRefreshDelay) {
if (prefs.glide.showPreview && time - lastTime > prefs.glide.previewRefreshDelay) {
updateSuggestionsAsync(1, false) {}
lastTime = time
}
@@ -61,14 +58,12 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
/**
* Change the layout of the internal gesture classifier
*/
fun setLayout(keys: Sequence<Key>, dimensions: Dimensions) {
fun setLayout(keys: List<TextKey>) {
glideTypingClassifier.setLayout(keys, FlorisBoard.getInstance().activeSubtype)
initialDimensions.getOrPut(FlorisBoard.getInstance().activeSubtype, {
dimensions
})
}
private val wordDataCache = hashMapOf<String, Int>()
/**
* Set the word data for the internal gesture classifier
*/
@@ -77,7 +72,8 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
if (wordDataCache.isEmpty()) {
// FIXME: get this info from dictionary.
val data =
AssetManager.default().loadTextAsset(AssetRef(AssetSource.Assets, "ime/dict/data.json")).getOrThrow()
AssetManager.default().loadTextAsset(AssetRef(AssetSource.Assets, "ime/dict/data.json"))
.getOrThrow()
val json = JSONObject(data)
wordDataCache.putAll(json.keys().asSequence().map { Pair(it, json.getInt(it)) })
}
@@ -85,48 +81,6 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
}
}
fun updateDimensions(dimensions: Dimensions) {
this.currentDimensions = dimensions
}
/**
* To avoid constantly having to regenerate Pruners every time we switch between landscape and portrait or enable/
* disable one handed mode, we just normalize the x, y coordinates to the same range as the original which were
* active when the Pruner was created.
*/
private fun normalizeX(x: Float): Float {
val initial = initialDimensions[FlorisBoard.getInstance().activeSubtype] ?: return x
return scaleRange(
x,
0f,
currentDimensions.width,
0f,
initial.width
)
}
/**
* To avoid constantly having to regenerate Pruners every time we switch between landscape and portrait or enable/
* disable one handed mode, we just normalize the x, y coordinates to the same range as the original which were
* active when the Pruner was created.
*/
private fun normalizeY(y: Float): Float {
val initial = initialDimensions[FlorisBoard.getInstance().activeSubtype] ?: return y
return scaleRange(
y,
0f,
currentDimensions.height,
0f,
initial.height
)
}
private fun scaleRange(x: Float, oldMin: Float, oldMax: Float, newMin: Float, newMax: Float): Float {
return (((x - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin
}
/**
* Asks gesture classifier for suggestions and then passes that on to the smartbar.
* Also commits the most confident suggestion if [commit] is set. All happens on an async executor.
@@ -151,7 +105,10 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
textInputManager.isGlidePostEffect = true
textInputManager.smartbarView?.setCandidateSuggestionWords(
time,
suggestions.take(maxSuggestionsToShow).map { textInputManager.fixCase(it) }
suggestions.subList(
1.coerceAtMost(min(commit.compareTo(false), suggestions.size)),
maxSuggestionsToShow.coerceAtMost(suggestions.size)
).map { textInputManager.fixCase(it) }
)
textInputManager.smartbarView?.updateCandidateSuggestionCapsState()
if (commit && suggestions.isNotEmpty()) {
@@ -162,8 +119,3 @@ class GlideTypingManager : GlideTypingGesture.Listener, CoroutineScope by MainSc
}
}
}
data class Dimensions(
val width: Float,
val height: Float
)

View File

@@ -2,25 +2,31 @@ package dev.patrickgold.florisboard.ime.text.gestures
import android.util.SparseArray
import androidx.collection.LruCache
import androidx.core.util.set
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.keyboard.Key
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import java.text.Normalizer
import java.util.*
import kotlin.collections.HashMap
import kotlin.math.*
private fun TextKey.baseCode(): Int {
return (data as? TextKeyData)?.code ?: KeyCode.UNSPECIFIED
}
/**
* Classifies gestures by comparing them with an "ideal gesture".
*
* Check out Étienne Desticourt's excellent write up at https://github.com/AnySoftKeyboard/AnySoftKeyboard/pull/1870
*/
class StatisticalGlideTypingClassifier : GlideTypingClassifier {
private val gesture = Gesture()
private var keysByCharacter: SparseArray<Key> = SparseArray()
private var keysByCharacter: SparseArray<TextKey> = SparseArray()
private var words: Set<String> = setOf()
private var wordFrequencies: Map<String, Int> = hashMapOf()
private var keys: ArrayList<Key> = arrayListOf()
private var keys: ArrayList<TextKey> = arrayListOf()
private lateinit var pruner: Pruner
private var wordDataSubtype: Subtype? = null
private var layoutSubtype: Subtype? = null
@@ -33,7 +39,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
*/
private var distanceThresholdSquared = 0
companion object {
/**
* Describes the allowed length variance in a gesture. If a gesture is too long or too short, it is immediately
@@ -85,27 +90,27 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
}
}
override fun setLayout(keyViews: Sequence<Key>, subtype: Subtype) {
override fun setLayout(keyViews: List<TextKey>, subtype: Subtype) {
// stop duplicate calls
if (this.layoutSubtype == subtype) {
if (layoutSubtype == subtype) {
return
}
keysByCharacter.clear()
keys.clear()
/*keyViews.forEach {
keysByCharacter[it.data.code] = it
this.keys.add(it)
keyViews.forEach {
keysByCharacter[it.baseCode()] = it
keys.add(it)
}
layoutSubtype = subtype
distanceThresholdSquared = (keyViews.first().width / 4)*/
distanceThresholdSquared = (keyViews.first().visibleBounds.width() / 4)
distanceThresholdSquared *= distanceThresholdSquared
initializePruner()
}
override fun setWordData(words: HashMap<String, Int>, subtype: Subtype) {
// stop duplicate calls..
if (this.wordDataSubtype == subtype) {
if (wordDataSubtype == subtype) {
return
}
@@ -164,7 +169,7 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
val candidates = arrayListOf<String>()
val candidateWeights = arrayListOf<Float>()
val key = keys.firstOrNull() ?: return listOf()
val radius = 0//min(key.height, key.width)
val radius = min(key.visibleBounds.height(), key.visibleBounds.width())
var remainingWords = pruner.pruneByExtremities(gesture, this.keys)
val userGesture = gesture.resample(SAMPLING_POINTS)
val normalizedUserGesture: Gesture = userGesture.normalizeByBoxSide()
@@ -213,7 +218,7 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
}
override fun clear() {
this.gesture.clear()
gesture.clear()
}
private fun calcLocationDistance(gesture1: Gesture, gesture2: Gesture): Float {
@@ -227,7 +232,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
totalDistance += distance
}
return totalDistance / SAMPLING_POINTS / 2
}
private fun calcGaussianProbability(value: Float, mean: Float, standardDeviation: Float): Float {
@@ -256,7 +260,7 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
* The length difference between a user gesture and a word gesture above which a word will
* be pruned.
*/
private val lengthThreshold: Double, words: Set<String>, keysByCharacter: SparseArray<Key>
private val lengthThreshold: Double, words: Set<String>, keysByCharacter: SparseArray<TextKey>
) {
/** A tree that provides fast access to words based on their first and last letter. */
@@ -271,7 +275,7 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
* @return A list of likely words.
*/
fun pruneByExtremities(
userGesture: Gesture, keys: Iterable<Key>
userGesture: Gesture, keys: Iterable<TextKey>
): ArrayList<String> {
val remainingWords = ArrayList<String>()
val startX = userGesture.getFirstX()
@@ -303,13 +307,13 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
fun pruneByLength(
userGesture: Gesture,
words: ArrayList<String>,
keysByCharacter: SparseArray<Key>,
keys: List<Key>
keysByCharacter: SparseArray<TextKey>,
keys: List<TextKey>
): ArrayList<String> {
val remainingWords = ArrayList<String>()
val key = keys.firstOrNull() ?: return arrayListOf()
val radius = 0//min(key.height, key.width)
val radius = min(key.visibleBounds.height(), key.visibleBounds.width())
val userLength = userGesture.getLength()
for (word in words) {
val idealGestures = Gesture.generateIdealGestures(word, keysByCharacter)
@@ -325,21 +329,20 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
companion object {
private fun getFirstKeyLastKey(
word: String, keysByCharacter: SparseArray<Key>
word: String, keysByCharacter: SparseArray<TextKey>
): Pair<Int, Int>? {
val firstLetter = word[0]
val lastLetter = word[word.length - 1]
val firstBaseChar = Normalizer.normalize(firstLetter.toString(), Normalizer.Form.NFD)[0]
val lastBaseChar = Normalizer.normalize(lastLetter.toString(), Normalizer.Form.NFD)[0]
return when {
keysByCharacter.indexOfKey(firstBaseChar.toInt()) < 0 || keysByCharacter.indexOfKey(lastBaseChar.toInt()) < 0 -> {
keysByCharacter.indexOfKey(firstBaseChar.code) < 0 || keysByCharacter.indexOfKey(lastBaseChar.code) < 0 -> {
null
}
else -> {
val firstKey = keysByCharacter[firstBaseChar.toInt()]
val lastKey = keysByCharacter[lastBaseChar.toInt()]
//Pair(firstKey.data.code, lastKey.data.code)
Pair(0, 0)
val firstKey = keysByCharacter[firstBaseChar.code]
val lastKey = keysByCharacter[lastBaseChar.code]
Pair(firstKey.baseCode(), lastKey.baseCode())
}
}
}
@@ -354,17 +357,16 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
* @return A list of the n closest keys.
*/
private fun findNClosestKeys(
x: Float, y: Float, n: Int, keys: Iterable<Key>
x: Float, y: Float, n: Int, keys: Iterable<TextKey>
): Iterable<Int> {
val keyDistances = HashMap<Key, Float>()
/*for (key in keys) {
val distance = Gesture.distance(key.centerX, key.centerY, x, y)
val keyDistances = HashMap<TextKey, Float>()
for (key in keys) {
val distance = Gesture.distance(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat(), x, y)
keyDistances[key] = distance
}
return keyDistances.entries.sortedWith { c1, c2 -> c1.value.compareTo(c2.value) }.take(n)
.map { it.key.data.code }*/
return listOf()
.map { it.key.baseCode() }
}
}
@@ -387,7 +389,7 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
companion object {
private const val MAX_SIZE = 300
fun generateIdealGestures(word: String, keysByCharacter: SparseArray<Key>): List<Gesture> {
fun generateIdealGestures(word: String, keysByCharacter: SparseArray<TextKey>): List<Gesture> {
val idealGesture = Gesture()
val idealGestureWithLoops = Gesture()
var previousLetter = '\u0000'
@@ -396,11 +398,11 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
// Add points for each key
for (c in word) {
val lc = Character.toLowerCase(c)
var key = keysByCharacter[lc.toInt()]
var key = keysByCharacter[lc.code]
if (key == null) {
// Try finding the base character instead, e.g., the "e" key instead of "é"
val baseCharacter: Char = Normalizer.normalize(lc.toString(), Normalizer.Form.NFD)[0]
key = keysByCharacter[baseCharacter.toInt()]
key = keysByCharacter[baseCharacter.code]
if (key == null) {
continue
}
@@ -408,30 +410,30 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
// We adda little loop on the key for duplicate letters
// so that we can differentiate words like pool and poll, lull and lul, etc...
/*if (previousLetter == lc) {
if (previousLetter == lc) {
// bottom right
idealGestureWithLoops.addPoint(
key.centerX + key.width / 4.0f, key.centerY + key.height / 4.0f
key.visibleBounds.centerX() + key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() + key.visibleBounds.height() / 4.0f
)
// top right
idealGestureWithLoops.addPoint(
key.centerX + key.width / 4.0f, key.centerY - key.height / 4.0f
key.visibleBounds.centerX() + key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() - key.visibleBounds.height() / 4.0f
)
// top left
idealGestureWithLoops.addPoint(
key.centerX - key.width / 4.0f, key.centerY - key.height / 4.0f
key.visibleBounds.centerX() - key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() - key.visibleBounds.height() / 4.0f
)
// bottom left
idealGestureWithLoops.addPoint(
key.centerX - key.width / 4.0f, key.centerY + key.height / 4.0f
key.visibleBounds.centerX() - key.visibleBounds.width() / 4.0f, key.visibleBounds.centerY() + key.visibleBounds.height() / 4.0f
)
hasLoops = true
idealGesture.addPoint(key.centerX, key.centerY)
idealGesture.addPoint(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat())
} else {
idealGesture.addPoint(key.centerX, key.centerY)
idealGestureWithLoops.addPoint(key.centerX, key.centerY)
}*/
idealGesture.addPoint(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat())
idealGestureWithLoops.addPoint(key.visibleBounds.centerX().toFloat(), key.visibleBounds.centerY().toFloat())
}
previousLetter = lc
}
return when (hasLoops) {
@@ -443,7 +445,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
fun distance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
return sqrt((x1 - x2).pow(2) + (y1 - y2).pow(2))
}
}
fun addPoint(x: Float, y: Float) {
@@ -455,7 +456,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
size += 1
}
/**
* Resamples the gesture into a new gesture with the chosen number of points by oversampling
* it.
@@ -481,7 +481,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
}
}
for (i in 0 until size - 1) {
// We calculate the unit vector from the two points we're between in the actual
// gesture
@@ -515,7 +514,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
}
fun normalizeByBoxSide(): Gesture {
val normalizedGesture = Gesture()
var maxX = -1.0f
@@ -532,7 +530,7 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
val width = maxX - minX
val height = maxY - minY
val longestSide = max(width, height)
val longestSide = max(max(width, height), 0.00001f)
val centroidX = (width / 2 + minX) / longestSide
val centroidY = (height / 2 + minY) / longestSide
@@ -575,7 +573,6 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
return Gesture(xs.clone(), ys.clone(), size)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@@ -598,6 +595,4 @@ class StatisticalGlideTypingClassifier : GlideTypingClassifier {
return result
}
}
}

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.text.gestures
import java.util.*
/**
* Enum for declaring the possible actions for swipe gestures.
*/
@@ -49,11 +47,11 @@ enum class SwipeAction {
companion object {
fun fromString(string: String): SwipeAction {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ENGLISH)
return super.toString().lowercase()
}
}

View File

@@ -18,7 +18,11 @@ package dev.patrickgold.florisboard.ime.text.gestures
import android.content.Context
import android.view.MotionEvent
import android.view.VelocityTracker
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.debug.LogTopic
import dev.patrickgold.florisboard.debug.flogDebug
import dev.patrickgold.florisboard.util.ViewLayoutUtils
import kotlin.math.abs
import kotlin.math.atan2
@@ -64,8 +68,10 @@ abstract class SwipeGesture {
*/
class Detector(private val context: Context, private val listener: Listener) {
private var pointerDataMap: MutableMap<Int, PointerData> = mutableMapOf()
private var thresholdSpeed: Double = numericValue(context, VelocityThreshold.NORMAL)
private var thresholdWidth: Double = numericValue(context, DistanceThreshold.NORMAL)
private var unitWidth: Double = thresholdWidth / 4.0
private val velocityTracker: VelocityTracker = VelocityTracker.obtain()
var distanceThreshold: DistanceThreshold = DistanceThreshold.NORMAL
set(value) {
@@ -74,6 +80,10 @@ abstract class SwipeGesture {
unitWidth = thresholdWidth / 4.0
}
var velocityThreshold: VelocityThreshold = VelocityThreshold.NORMAL
set(value) {
field = value
thresholdSpeed = numericValue(context, value)
}
/**
* Method which evaluates if a given [event] is a gesture.
@@ -90,7 +100,9 @@ abstract class SwipeGesture {
MotionEvent.ACTION_POINTER_DOWN -> {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
resetState()
velocityTracker.clear()
}
velocityTracker.addMovement(event)
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
pointerDataMap[pointerId] = PointerData().apply {
@@ -101,6 +113,7 @@ abstract class SwipeGesture {
}
}
MotionEvent.ACTION_MOVE -> {
velocityTracker.addMovement(event)
for (pointerIndex in 0 until event.pointerCount) {
val pointerId = event.getPointerId(pointerIndex)
pointerDataMap[pointerId]?.apply {
@@ -135,19 +148,17 @@ abstract class SwipeGesture {
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
velocityTracker.addMovement(event)
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
pointerDataMap.remove(pointerId)?.apply {
val absDiffX = event.getX(pointerIndex) - firstX
val absDiffY = event.getY(pointerIndex) - firstY
/*val velocityThresholdNV = numericValue(velocityThreshold)
val velocity =
((convertPixelsToDp(
sqrt(diffX.pow(2) + diffY.pow(2)),
context
) / event.downTime) * 10.0f.pow(8)).toInt()*/
// return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) && velocity >= velocityThresholdNV) {
return if ((abs(absDiffX) > thresholdWidth || abs(absDiffY) > thresholdWidth)) {
velocityTracker.computeCurrentVelocity(1000)
val velocityX = ViewLayoutUtils.convertDpToPixel(velocityTracker.getXVelocity(pointerId), context)
val velocityY = ViewLayoutUtils.convertDpToPixel(velocityTracker.getYVelocity(pointerId), context)
flogDebug(LogTopic.GESTURES) { "Velocity: $velocityX $velocityY dp/s" }
return if ((abs(absDiffX) > thresholdWidth || abs(absDiffY) > thresholdWidth) && (abs(velocityX) > thresholdSpeed || abs(velocityY) > thresholdSpeed)) {
val direction = detectDirection(absDiffX.toDouble(), absDiffY.toDouble())
absUnitCountX = (absDiffX / unitWidth).toInt()
absUnitCountY = (absDiffY / unitWidth).toInt()

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.text.gestures
import java.util.*
/**
* Enum for declaring the velocity thresholds for swipe gestures.
*/
@@ -30,11 +28,11 @@ enum class VelocityThreshold {
companion object {
fun fromString(string: String): VelocityThreshold {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ENGLISH)
return super.toString().lowercase()
}
}

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.text.key
import java.util.*
/**
* Enum for the key hint modes.
*/
@@ -29,11 +27,11 @@ enum class KeyHintMode {
companion object {
fun fromString(string: String): KeyHintMode {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ENGLISH)
return super.toString().lowercase()
}
}

View File

@@ -23,7 +23,6 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.util.*
/**
* Enum for declaring the type of the key.
@@ -44,12 +43,12 @@ enum class KeyType {
UNSPECIFIED;
override fun toString(): String {
return super.toString().toLowerCase(Locale.ENGLISH)
return super.toString().lowercase()
}
companion object {
fun fromString(string: String): KeyType {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
}

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.text.key
import java.util.*
/**
* Enum for declaring the utility key actions.
*/
@@ -30,7 +28,7 @@ enum class UtilityKeyAction {
companion object {
fun fromString(string: String): UtilityKeyAction {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
}

View File

@@ -61,7 +61,7 @@ class TextKey(override val data: KeyData) : Key(data) {
"~right"
}
else -> {
computed.label.toLowerCase()
computed.label.lowercase(evaluator.getActiveSubtype().locale)
}
}
val extendedPopupsDefault = evaluator.getKeyboard().extendedPopupMappingDefault

View File

@@ -342,9 +342,9 @@ class AutoTextKeyData(
val popup: PopupSet<TextKeyData>? = null
) : TextKeyData {
@Transient private val lower: BasicTextKeyData =
BasicTextKeyData(type, Character.toLowerCase(code), label.toLowerCase(Locale.getDefault()), groupId, popup)
BasicTextKeyData(type, Character.toLowerCase(code), label.lowercase(Locale.getDefault()), groupId, popup)
@Transient private val upper: BasicTextKeyData =
BasicTextKeyData(type, Character.toUpperCase(code), label.toUpperCase(Locale.getDefault()), groupId, popup)
BasicTextKeyData(type, Character.toUpperCase(code), label.uppercase(Locale.getDefault()), groupId, popup)
override fun computeTextKeyData(evaluator: TextComputingEvaluator): TextKeyData? {
return if (evaluator.isSlot(this)) {

View File

@@ -34,8 +34,8 @@ class TextKeyboard(
get() = arrangement.size
companion object {
fun layoutDrawableBounds(key: TextKey) {
layoutForegroundBounds(key, key.visibleDrawableBounds, 0.21, isLabel = false)
fun layoutDrawableBounds(key: TextKey, factor: Double) {
layoutForegroundBounds(key, key.visibleDrawableBounds, 0.21 * (1.0 / factor), isLabel = false)
}
fun layoutLabelBounds(key: TextKey) {
@@ -131,7 +131,7 @@ class TextKeyboard(
}
bottom = key.touchBounds.bottom - abs(desiredTouchBounds.bottom - desiredVisibleBounds.bottom)
}
layoutDrawableBounds(key)
layoutDrawableBounds(key, keyboardView.fontSizeMultiplier)
layoutLabelBounds(key)
posX += keyWidth
// After-adjust touch bounds for the row margin
@@ -165,7 +165,7 @@ class TextKeyboard(
right = key.touchBounds.right - abs(desiredTouchBounds.right - desiredVisibleBounds.right)
bottom = key.touchBounds.bottom - abs(desiredTouchBounds.bottom - desiredVisibleBounds.bottom)
}
layoutDrawableBounds(key)
layoutDrawableBounds(key, keyboardView.fontSizeMultiplier)
layoutLabelBounds(key)
posX += keyWidth
// After-adjust touch bounds for the row margin

View File

@@ -14,8 +14,6 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalContracts::class)
package dev.patrickgold.florisboard.ime.text.keyboard
import android.content.Context
@@ -23,51 +21,14 @@ import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import dev.patrickgold.florisboard.R
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Abstract class definition for providing icons to a keyboard view. This class has been introduced to remove the need
* for keyboard vies to re-fetch drawable resources every time they draw on the canvas. The exact implementation is
* dependent on the subclass.
*/
abstract class KeyboardIconSet {
/**
* Get the drawable for the given [id].
*
* @param id The Android resource id of the drawable which should be returned.
*
* @return The drawable for given [id] or null if this icon set does not contain a drawable for this id.
*/
abstract fun getDrawable(@DrawableRes id: Int): Drawable?
/**
* Performs [block] on the drawable with the given [id]. If no drawable for the id exists,[block] will not be
* called at all.
*
* @param id The Android resource id of the drawable which should be used to execute block with.
* @param block The block which should be executed with the returned drawable.
*/
inline fun withDrawable(@DrawableRes id: Int, block: Drawable.() -> Unit) {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
val drawable = getDrawable(id)
if (drawable != null) {
synchronized(drawable) {
block(drawable)
}
}
}
}
import dev.patrickgold.florisboard.ime.keyboard.KeyboardIconSet
/**
* An icon set for [TextKeyboardView].
*/
class TextKeyboardIconSet private constructor(context: Context) : KeyboardIconSet() {
private val drawableCache: Array<Drawable?> = Array(RES_ICON_IDS.size) {
ContextCompat.getDrawable(context.applicationContext, RES_ICON_IDS[it])
ContextCompat.getDrawable(context.applicationContext, RES_ICON_IDS[it])?.mutate()
}
companion object {

View File

@@ -16,17 +16,22 @@
package dev.patrickgold.florisboard.ime.text.keyboard
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.graphics.*
import android.graphics.drawable.PaintDrawable
import android.os.Handler
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.AccelerateInterpolator
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.debug.*
import dev.patrickgold.florisboard.ime.core.*
import dev.patrickgold.florisboard.ime.keyboard.KeyboardView
import dev.patrickgold.florisboard.ime.popup.PopupManager
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingGesture
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
import dev.patrickgold.florisboard.ime.text.key.*
@@ -36,9 +41,11 @@ import dev.patrickgold.florisboard.util.ViewLayoutUtils
import dev.patrickgold.florisboard.util.cancelAll
import dev.patrickgold.florisboard.util.postDelayed
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sqrt
class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
class TextKeyboardView : KeyboardView, SwipeGesture.Listener, GlideTypingGesture.Listener {
private var computedKeyboard: TextKeyboard? = null
private var iconSet: TextKeyboardIconSet? = null
@@ -106,10 +113,18 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
private var initSelectionStart: Int = 0
private var initSelectionEnd: Int = 0
private var isGliding: Boolean = false
private var hasTriggeredGestureMove: Boolean = false
private var hasTriggeredGestureUp: Boolean = false
private var shouldBlockNextUp: Boolean = false
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
private val glideTypingDetector = GlideTypingGesture.Detector(context)
private val glideTypingManager: GlideTypingManager
get() = GlideTypingManager.getInstance()
private val glideDataForDrawing: MutableList<GlideTypingGesture.Detector.Position> = mutableListOf()
private val fadingGlide: MutableList<GlideTypingGesture.Detector.Position> = mutableListOf()
private var fadingGlideRadius: Float = 0.0f
val desiredKey: TextKey = TextKey(data = TextKeyData.UNSPECIFIED)
private var keyBackgroundDrawable: PaintDrawable = PaintDrawable().apply {
@@ -117,10 +132,13 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
private var backgroundDrawable: PaintDrawable = PaintDrawable()
private val baselineTextSize = resources.getDimension(R.dimen.key_textSize)
var fontSizeMultiplier: Double = 1.0
private set
private val glideTrailPaint: Paint = Paint()
private var labelPaintTextSize: Float = resources.getDimension(R.dimen.key_textSize)
private var labelPaintSpaceTextSize: Float = resources.getDimension(R.dimen.key_textSize)
private var labelPaint: Paint = Paint().apply {
color = 0
private val labelPaint: Paint = Paint().apply {
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
@@ -128,8 +146,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
typeface = Typeface.DEFAULT
}
private var hintedLabelPaintTextSize: Float = resources.getDimension(R.dimen.key_textHintSize)
private var hintedLabelPaint: Paint = Paint().apply {
color = 0
private val hintedLabelPaint: Paint = Paint().apply {
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
@@ -165,6 +182,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
fun setComputedKeyboard(keyboard: TextKeyboard) {
flogInfo(LogTopic.TEXT_KEYBOARD_VIEW) { keyboard.toString() }
computedKeyboard = keyboard
initGlideClassifier(keyboard)
notifyStateChanged()
}
@@ -176,16 +194,49 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
fun notifyStateChanged() {
flogInfo(LogTopic.TEXT_KEYBOARD_VIEW)
isRecomputingRequested = true
onLayoutInternal()
invalidate()
swipeGestureDetector.apply {
distanceThreshold = prefs.gestures.swipeDistanceThreshold
velocityThreshold = prefs.gestures.swipeVelocityThreshold
}
if (isMeasured) {
onLayoutInternal()
invalidate()
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
glideTypingDetector.let {
it.registerListener(this)
it.registerListener(glideTypingManager)
it.velocityThreshold = prefs.gestures.swipeVelocityThreshold
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
cachedTheme = null
glideTypingDetector.let {
it.unregisterListener(this)
it.unregisterListener(glideTypingManager)
}
}
override fun onTouchEventInternal(event: MotionEvent) {
if (prefs.glide.enabled &&
computedKeyboard?.mode == KeyboardMode.CHARACTERS &&
glideTypingDetector.onTouchEvent(event, initialKey) &&
event.actionMasked != MotionEvent.ACTION_UP
) {
if (activePointerId != null) {
val pointerIndex = event.actionIndex
onTouchCancelInternal(event, pointerIndex, activePointerId!!)
}
isGliding = true
invalidate()
return
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
@@ -193,8 +244,10 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
val pointerId = event.getPointerId(pointerIndex)
if (activePointerId != null) {
onTouchUpInternal(event, pointerIndex, activePointerId!!)
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = false)
} else {
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = true)
}
onTouchDownInternal(event, pointerIndex, pointerId)
}
MotionEvent.ACTION_MOVE -> {
for (pointerIndex in 0 until event.pointerCount) {
@@ -212,16 +265,35 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
if (activePointerId == pointerId) {
onTouchUpInternal(event, pointerIndex, pointerId)
}
if (event.actionMasked == MotionEvent.ACTION_UP) {
florisboard?.let {
if (it.textInputManager.inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
if (initialKey?.computedData?.code == KeyCode.SHIFT && activeKey?.computedData?.code == KeyCode.SHIFT) {
it.textInputManager.inputEventDispatcher.send(InputKeyEvent.up(TextKeyData.SHIFT))
} else {
it.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(TextKeyData.SHIFT))
}
}
}
activePointerId = null
}
}
MotionEvent.ACTION_CANCEL -> {
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
onTouchCancelInternal(event, pointerIndex, pointerId)
florisboard?.let {
if (it.textInputManager.inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
it.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(TextKeyData.SHIFT))
}
}
activePointerId = null
}
}
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "initialKey: ${initialKey?.computedData?.label} activeKey: ${activeKey?.computedData?.label}" }
}
private fun onTouchDownInternal(event: MotionEvent, pointerIndex: Int, pointerId: Int) {
private fun onTouchDownInternal(event: MotionEvent, pointerIndex: Int, pointerId: Int, resetInitialKey: Boolean) {
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW) { "index=$pointerIndex id=$pointerId event=$event" }
val florisboard = florisboard ?: return
@@ -245,7 +317,9 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
florisboard.keyPressVibrate()
florisboard.keyPressSound(key.computedData)
key.isPressed = true
initialKey = key
if (resetInitialKey) {
initialKey = key
}
activeKey = key
val delayMillis = prefs.keyboard.longPressDelay.toLong()
when (key.computedData.code) {
@@ -259,7 +333,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
else -> {
florisboard.executeSwipeAction(prefs.gestures.spaceBarLongPress)
hasTriggeredGestureUp = true
shouldBlockNextUp = true
}
}
}
@@ -273,7 +347,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
KeyCode.LANGUAGE_SWITCH -> {
longPressHandler.postDelayed((delayMillis * 2.0).toLong()) {
hasTriggeredGestureUp = true
shouldBlockNextUp = true
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(TextKeyData.SHOW_INPUT_METHOD_PICKER))
}
}
@@ -291,7 +365,9 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
swipeGestureDetector.onTouchEvent(event)
}
} else {
initialKey = null
if (resetInitialKey) {
initialKey = null
}
activeKey = null
}
invalidate()
@@ -307,12 +383,12 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
val alwaysTriggerOnMove = (hasTriggeredGestureMove
&& (initialKey?.computedData?.code == KeyCode.DELETE
&& prefs.gestures.deleteKeySwipeLeft == SwipeAction.DELETE_CHARACTERS_PRECISELY
|| initialKey?.computedData?.code == KeyCode.SPACE))
|| initialKey?.computedData?.code == KeyCode.SPACE || (initialKey?.computedData?.code == KeyCode.SHIFT && activeKey?.computedData?.code == KeyCode.SPACE)))
if (swipeGestureDetector.onTouchEvent(event, alwaysTriggerOnMove) || hasTriggeredGestureMove) {
longPressHandler.cancelAll()
hasTriggeredGestureMove = true
initialKey?.let {
if (florisboard.textInputManager.inputEventDispatcher.isPressed(it.computedData.code)) {
if (it.computedData.code != KeyCode.SHIFT && florisboard.textInputManager.inputEventDispatcher.isPressed(it.computedData.code)) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(it.computedData))
}
}
@@ -322,7 +398,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
if (popupManager.isShowingExtendedPopup) {
if (!popupManager.propagateMotionEvent(key, event, pointerIndex)) {
onTouchCancelInternal(event, pointerIndex, pointerId)
onTouchDownInternal(event, pointerIndex, pointerId)
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = false)
}
} else {
if ((event.getX(pointerIndex) < key.visibleBounds.left - 0.1f * key.visibleBounds.width())
@@ -331,7 +407,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
|| (event.getY(pointerIndex) > key.visibleBounds.bottom + 0.35f * key.visibleBounds.height())
) {
onTouchCancelInternal(event, pointerIndex, pointerId)
onTouchDownInternal(event, pointerIndex, pointerId)
onTouchDownInternal(event, pointerIndex, pointerId, resetInitialKey = false)
}
}
}
@@ -345,7 +421,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
longPressHandler.cancelAll()
val key = activeKey
if (key != null) {
if (swipeGestureDetector.onTouchEvent(event) || hasTriggeredGestureMove || hasTriggeredGestureUp) {
if (swipeGestureDetector.onTouchEvent(event) || hasTriggeredGestureMove || shouldBlockNextUp) {
if (hasTriggeredGestureMove && initialKey?.computedData?.code == KeyCode.DELETE) {
florisboard.textInputManager.isGlidePostEffect = false
florisboard.activeEditorInstance.apply {
@@ -358,28 +434,28 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
return
}
key.isPressed = false
val retData = popupManager.getActiveKeyData(key)
if (retData != null) {
if (retData == key.computedData) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.up(key.computedData))
if (activeKey?.computedData?.code != KeyCode.SHIFT) {
val retData = popupManager.getActiveKeyData(key)
if (retData != null) {
if (retData == key.computedData) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.up(key.computedData))
} else {
if (florisboard.textInputManager.inputEventDispatcher.isPressed(key.computedData.code)) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
}
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(retData))
}
} else {
if (florisboard.textInputManager.inputEventDispatcher.isPressed(key.computedData.code)) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
}
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.downUp(retData))
}
} else {
if (florisboard.textInputManager.inputEventDispatcher.isPressed(key.computedData.code)) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
}
}
}
popupManager.hide()
initialKey = null
activeKey = null
activePointerId = null
hasTriggeredGestureMove = false
hasTriggeredGestureUp = false
shouldBlockNextUp = false
invalidate()
}
@@ -391,40 +467,41 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
val key = activeKey
if (key != null) {
key.isPressed = false
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
if (activeKey?.computedData?.code != KeyCode.SHIFT) {
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(key.computedData))
}
}
popupManager.hide()
initialKey = null
activeKey = null
activePointerId = null
hasTriggeredGestureMove = false
hasTriggeredGestureUp = false
shouldBlockNextUp = false
invalidate()
}
override fun onSwipe(event: SwipeGesture.Event): Boolean {
val florisboard = florisboard ?: return false
val initialKey = initialKey ?: return false
if (activePointerId != event.pointerId) return false
flogDebug(LogTopic.TEXT_KEYBOARD_VIEW)
return when (initialKey.computedData.code) {
KeyCode.DELETE -> handleDeleteSwipe(event)
KeyCode.SPACE -> handleSpaceSwipe(event)
else -> when {
initialKey.computedData.code == KeyCode.SHIFT && activeKey?.computedData?.code == KeyCode.SPACE &&
event.type == SwipeGesture.Type.TOUCH_MOVE -> handleSpaceSwipe(event)
initialKey.computedData.code == KeyCode.SHIFT && activeKey?.computedData?.code != KeyCode.SHIFT &&
event.type == SwipeGesture.Type.TOUCH_UP -> {
/*activeKeyViews[event.pointerId]?.let {
florisboard?.textInputManager?.inputEventDispatcher?.send(
InputKeyEvent.up(
it.popupManager.getActiveKeyData(it)
?: it.data
)
activeKey?.let {
florisboard.textInputManager.inputEventDispatcher.send(
InputKeyEvent.up(popupManager.getActiveKeyData(it) ?: it.computedData)
)
florisboard?.textInputManager?.inputEventDispatcher?.send(InputKeyEvent.cancel(KeyData.SHIFT))
}*/
}
florisboard.textInputManager.inputEventDispatcher.send(InputKeyEvent.cancel(TextKeyData.SHIFT))
true
}
initialKey.computedData.code > KeyCode.SPACE && !popupManager.isShowingExtendedPopup -> when {
!prefs.glide.enabled -> when (event.type) {
!prefs.glide.enabled && !hasTriggeredGestureMove -> when (event.type) {
SwipeGesture.Type.TOUCH_UP -> {
val swipeAction = when (event.direction) {
SwipeGesture.Direction.UP -> prefs.gestures.swipeUp
@@ -463,7 +540,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
(selection.end + event.absUnitCountX + 1).coerceIn(0, selection.end),
selection.end
)
hasTriggeredGestureUp = true
shouldBlockNextUp = true
}
true
}
@@ -473,7 +550,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
florisboard.activeEditorInstance.apply {
leftAppendWordToSelection()
}
hasTriggeredGestureUp = true
shouldBlockNextUp = true
true
}
SwipeGesture.Direction.RIGHT -> {
@@ -481,7 +558,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
florisboard.activeEditorInstance.apply {
leftPopWordFromSelection()
}
hasTriggeredGestureUp = true
shouldBlockNextUp = true
true
}
else -> false
@@ -536,7 +613,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
true
}
else -> false
else -> true // To prevent the popup display of nearby keys
}
SwipeGesture.Type.TOUCH_UP -> {
if (event.absUnitCountY.times(-1) > 6) {
@@ -619,7 +696,7 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
}
}
TextKeyboard.layoutDrawableBounds(desiredKey)
TextKeyboard.layoutDrawableBounds(desiredKey, 1.0)
TextKeyboard.layoutLabelBounds(desiredKey)
var spaceKey: TextKey? = null
@@ -634,13 +711,24 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
}
fontSizeMultiplier = when (resources.configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> {
prefs.keyboard.fontSizeMultiplierPortrait.toFloat() / 100.0
}
Configuration.ORIENTATION_LANDSCAPE -> {
prefs.keyboard.fontSizeMultiplierLandscape.toFloat() / 100.0
}
else -> 1.0
}
keyboard.layout(this)
setTextSizeFor(
labelPaint,
desiredKey.visibleLabelBounds.width().toFloat(),
desiredKey.visibleLabelBounds.height().toFloat(),
"X"
"X",
fontSizeMultiplier
)
labelPaintTextSize = labelPaint.textSize
@@ -649,7 +737,8 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
labelPaint,
spaceKey.visibleLabelBounds.width().toFloat(),
spaceKey.visibleLabelBounds.height().toFloat(),
spaceKey.label ?: "X"
spaceKey.label ?: "X",
fontSizeMultiplier.coerceAtMost(1.0)
)
labelPaintSpaceTextSize = labelPaint.textSize
}
@@ -658,7 +747,8 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
hintedLabelPaint,
desiredKey.visibleBounds.width() * 1.0f / 5.0f,
desiredKey.visibleBounds.height() * 1.0f / 5.0f,
"X"
"X",
fontSizeMultiplier
)
hintedLabelPaintTextSize = hintedLabelPaint.textSize
}
@@ -674,26 +764,40 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
* @param boxWidth The max width for the surrounding box of [text].
* @param boxHeight The max height for the surrounding box of [text].
* @param text The text for which the size should be calculated.
* @param multiplier The factor by which the resulting text size should be multiplied with.
*/
private fun setTextSizeFor(boxPaint: Paint, boxWidth: Float, boxHeight: Float, text: String, multiplier: Float = 1.0f): Float {
var stage = 1
var textSize = 0.0f
while (stage < 3) {
if (stage == 1) {
textSize += 10.0f
} else if (stage == 2) {
textSize -= 1.0f
private fun setTextSizeFor(boxPaint: Paint, boxWidth: Float, boxHeight: Float, text: String, multiplier: Double = 1.0): Float {
var size = baselineTextSize
boxPaint.textSize = size
boxPaint.getTextBounds(text, 0, text.length, tempRect)
val w = tempRect.width().toFloat()
val h = tempRect.height().toFloat()
val diffW = abs(boxWidth - w)
val diffH = abs(boxHeight - h)
if (w < boxWidth && h < boxHeight) {
// Text fits, scale up on axis which has less room
size *= if (diffW < diffH) {
1.0f + diffW / w
} else {
1.0f + diffH / h
}
boxPaint.textSize = textSize
boxPaint.getTextBounds(text, 0, text.length, tempRect)
val fits = tempRect.width() < boxWidth && tempRect.height() < boxHeight
if (stage == 1 && !fits || stage == 2 && fits) {
stage++
} else if (w >= boxWidth && h < boxHeight) {
// Text does not fit on x-axis
size *= (1.0f - diffW / w)
} else if (w < boxWidth && h >= boxHeight) {
// Text does not fit on y-axis
size *= (1.0f - diffH / h)
} else {
// Text does not fit at all, scale down on axis which has most overshoot
size *= if (diffW < diffH) {
1.0f - diffH / h
} else {
1.0f - diffW / w
}
}
textSize *= multiplier
boxPaint.textSize = textSize
return textSize
size *= multiplier.toFloat()
boxPaint.textSize = size
return size
}
override fun onThemeUpdated(theme: Theme) {
@@ -703,6 +807,12 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
paint.color = theme.getAttr(Theme.Attr.KEYBOARD_BACKGROUND).toSolidColor().color
}
}
if (theme.getAttr(Theme.Attr.GLIDE_TRAIL_COLOR).toSolidColor().color == 0) {
glideTrailPaint.color = theme.getAttr(Theme.Attr.WINDOW_COLOR_PRIMARY).toSolidColor().color
glideTrailPaint.alpha = 32
} else {
glideTrailPaint.color = theme.getAttr(Theme.Attr.GLIDE_TRAIL_COLOR).toSolidColor().color
}
invalidate()
}
@@ -1000,4 +1110,96 @@ class TextKeyboardView : KeyboardView, SwipeGesture.Listener {
}
}
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
if (prefs.glide.enabled && prefs.glide.showTrail && !isSmartbarKeyboardView) {
val targetDist = 5.0f
val maxPoints = prefs.glide.trailMaxLength
val radius = 20.0f
// the tip of the trail will be 1px
val radiusReductionFactor = (1.0f /radius).pow(1.0f / maxPoints)
if (fadingGlideRadius > 0) {
drawGlideTrail(fadingGlide, maxPoints, targetDist, fadingGlideRadius, canvas, radiusReductionFactor)
}
if (isGliding && glideDataForDrawing.isNotEmpty()) {
drawGlideTrail(glideDataForDrawing, maxPoints, targetDist, radius, canvas, radiusReductionFactor)
}
}
}
private fun drawGlideTrail(
gestureData: MutableList<GlideTypingGesture.Detector.Position>,
maxPoints: Int,
targetDist: Float,
initialRadius: Float,
canvas: Canvas?,
radiusReductionFactor: Float
) {
var radius = initialRadius
var drawnPoints = 0
var prevX = gestureData.lastOrNull()?.x ?: 0.0f
var prevY = gestureData.lastOrNull()?.y ?: 0.0f
outer@ for (i in gestureData.size - 1 downTo 1) {
val dx = prevX - gestureData[i - 1].x
val dy = prevY - gestureData[i - 1].y
val dist = sqrt(dx * dx + dy * dy)
val numPoints = (dist / targetDist).toInt()
for (j in 0 until numPoints) {
if (drawnPoints > maxPoints) break@outer
radius *= radiusReductionFactor
val intermediateX =
gestureData[i].x * (1 - j.toFloat() / numPoints) + gestureData[i - 1].x * (j.toFloat() / numPoints)
val intermediateY =
gestureData[i].y * (1 - j.toFloat() / numPoints) + gestureData[i - 1].y * (j.toFloat() / numPoints)
canvas?.drawCircle(intermediateX, intermediateY, radius,glideTrailPaint)
drawnPoints += 1
prevX = intermediateX
prevY = intermediateY
}
}
}
private fun initGlideClassifier(keyboard: TextKeyboard) {
if (isSmartbarKeyboardView || isPreviewMode || keyboard.mode != KeyboardMode.CHARACTERS) {
return
}
post {
val keys = keyboard.keys().asSequence().toList()
GlideTypingManager.getInstance().setLayout(keys)
}
}
override fun onGlideAddPoint(point: GlideTypingGesture.Detector.Position) {
if (prefs.glide.enabled) {
glideDataForDrawing.add(point)
}
}
override fun onGlideComplete(data: GlideTypingGesture.Detector.PointerData) {
onGlideCancelled()
}
override fun onGlideCancelled() {
if (prefs.glide.showTrail) {
fadingGlide.clear()
fadingGlide.addAll(glideDataForDrawing)
val animator = ValueAnimator.ofFloat(20.0f, 0.0f)
animator.interpolator = AccelerateInterpolator()
animator.duration = prefs.glide.trailDuration.toLong()
animator.addUpdateListener {
fadingGlideRadius = it.animatedValue as Float
invalidate()
}
animator.start()
glideDataForDrawing.clear()
isGliding = false
invalidate()
}
}
}

View File

@@ -19,7 +19,7 @@ package dev.patrickgold.florisboard.ime.text.layout
import dev.patrickgold.florisboard.debug.LogTopic
import dev.patrickgold.florisboard.debug.flogDebug
import dev.patrickgold.florisboard.debug.flogWarning
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.extension.AssetRef
@@ -43,8 +43,8 @@ private data class LTN(
* Class which manages layout loading and caching.
*/
class LayoutManager {
private val assetManager: AssetManager
get() = AssetManager.default()
private val assetManager get() = AssetManager.default()
private val prefs get() = Preferences.default()
private val layoutCache: HashMap<String, Deferred<Result<Layout>>> = hashMapOf()
private val layoutCacheGuard: Mutex = Mutex(locked = false)
@@ -143,8 +143,7 @@ class LayoutManager {
subtype: Subtype,
main: LTN? = null,
modifier: LTN? = null,
extension: LTN? = null,
prefs: PrefHelper
extension: LTN? = null
): TextKeyboard {
val extendedPopupsDefault = loadExtendedPopupsAsync()
val extendedPopups = loadExtendedPopupsAsync(subtype)
@@ -207,7 +206,7 @@ class LayoutManager {
// Add hints to keys
if (keyboardMode == KeyboardMode.CHARACTERS) {
val symbolsComputedArrangement = computeKeyboardAsync(KeyboardMode.SYMBOLS, subtype, prefs).await().arrangement
val symbolsComputedArrangement = computeKeyboardAsync(KeyboardMode.SYMBOLS, subtype).await().arrangement
val minRow = if (prefs.keyboard.numberRow) { 1 } else { 0 }
for ((r, row) in computedArrangement.withIndex()) {
if (r >= (3 + minRow) || r < minRow) {
@@ -249,8 +248,7 @@ class LayoutManager {
*/
fun computeKeyboardAsync(
keyboardMode: KeyboardMode,
subtype: Subtype,
prefs: PrefHelper
subtype: Subtype
): Deferred<TextKeyboard> = ioScope.async {
var main: LTN? = null
var modifier: LTN? = null
@@ -297,7 +295,7 @@ class LayoutManager {
}
}
return@async mergeLayoutsAsync(keyboardMode, subtype, main, modifier, extension, prefs)
return@async mergeLayoutsAsync(keyboardMode, subtype, main, modifier, extension)
}
/**

View File

@@ -23,7 +23,6 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.util.*
/**
* Defines the type of the layout.
@@ -44,12 +43,12 @@ enum class LayoutType {
SYMBOLS2_MOD;
override fun toString(): String {
return super.toString().replace("_", "/").toLowerCase(Locale.ENGLISH)
return super.toString().replace("_", "/").lowercase()
}
companion object {
fun fromString(string: String): LayoutType {
return valueOf(string.replace("/", "_").toUpperCase(Locale.ENGLISH))
return valueOf(string.replace("/", "_").uppercase())
}
}
}

View File

@@ -40,7 +40,6 @@ import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.ime.theme.ThemeValue
import java.lang.ref.WeakReference
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.abs
@@ -533,12 +532,12 @@ class CandidateView : View, ThemeManager.OnThemeUpdatedListener {
companion object {
fun fromString(string: String): DisplayMode {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ENGLISH)
return super.toString().lowercase()
}
}
}

View File

@@ -26,7 +26,7 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SmartbarBinding
import dev.patrickgold.florisboard.ime.clip.provider.ClipboardItem
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
@@ -46,8 +46,8 @@ import kotlin.math.roundToInt
* needs to decide when a specific feature component is shown.
*/
class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val florisboard = FlorisBoard.getInstanceOrNull()
private val prefs get() = Preferences.default()
private val themeManager = ThemeManager.default()
private var eventListener: WeakReference<EventListener?> = WeakReference(null)
private val mainScope = MainScope()
@@ -105,8 +105,7 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
florisboard?.let {
val layout = florisboard.textInputManager.layoutManager.computeKeyboardAsync(
KeyboardMode.SMARTBAR_CLIPBOARD_CURSOR_ROW,
Subtype.DEFAULT,
prefs
Subtype.DEFAULT
).await()
withContext(Dispatchers.Main) {
binding.clipboardCursorRow.setComputingEvaluator(florisboard.textInputManager.evaluator)
@@ -120,8 +119,7 @@ class SmartbarView : ConstraintLayout, ThemeManager.OnThemeUpdatedListener {
florisboard?.let {
val layout = florisboard.textInputManager.layoutManager.computeKeyboardAsync(
KeyboardMode.SMARTBAR_NUMBER_ROW,
Subtype.DEFAULT,
prefs
Subtype.DEFAULT
).await()
withContext(Dispatchers.Main) {
binding.numberRow.setComputingEvaluator(florisboard.textInputManager.evaluator)

View File

@@ -34,50 +34,50 @@ class AdaptiveThemeOverlay(
Attr.KEY_BACKGROUND_PRESSED,
Attr.SMARTBAR_BACKGROUND,
Attr.WINDOW_NAVIGATION_BAR_COLOR -> {
themeManager.remoteColorPrimaryVariant ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorPrimaryVariant ?: super.getAttr(ref, s1, s2)
}
Attr.KEY_FOREGROUND_PRESSED,
Attr.SMARTBAR_FOREGROUND -> {
themeManager.remoteColorPrimaryVariant?.complimentaryTextColor() ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorPrimaryVariant?.complimentaryTextColor() ?: super.getAttr(ref, s1, s2)
}
Attr.SMARTBAR_FOREGROUND_ALT -> {
themeManager.remoteColorPrimaryVariant?.complimentaryTextColor(true) ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorPrimaryVariant?.complimentaryTextColor(true) ?: super.getAttr(ref, s1, s2)
}
Attr.KEY_BACKGROUND,
Attr.SMARTBAR_BUTTON_BACKGROUND -> {
themeManager.remoteColorPrimary ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorPrimary ?: super.getAttr(ref, s1, s2)
}
Attr.KEY_FOREGROUND,
Attr.SMARTBAR_BUTTON_FOREGROUND -> {
if (s1 == "shift" && s2 == "capslock") {
themeManager.remoteColorSecondary ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorSecondary ?: super.getAttr(ref, s1, s2)
} else {
themeManager.remoteColorPrimary?.complimentaryTextColor() ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorPrimary?.complimentaryTextColor() ?: super.getAttr(ref, s1, s2)
}
}
Attr.KEY_SHOW_BORDER -> {
if (themeManager.remoteColorPrimary != null) {
if (themeManager.remote.colorPrimary != null) {
ThemeValue.OnOff(true)
} else {
super.getAttr(ref, s1, s2)
}
}
Attr.WINDOW_NAVIGATION_BAR_LIGHT -> {
if (themeManager.remoteColorPrimaryVariant != null) {
ThemeValue.OnOff(themeManager.remoteColorPrimaryVariant?.complimentaryTextColor()?.color == Color.BLACK)
if (themeManager.remote.colorPrimaryVariant != null) {
ThemeValue.OnOff(themeManager.remote.colorPrimaryVariant?.complimentaryTextColor()?.color == Color.BLACK)
} else {
super.getAttr(ref, s1, s2)
}
}
Attr.POPUP_BACKGROUND,
Attr.GLIDE_TRAIL_COLOR -> {
themeManager.remoteColorSecondary ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorSecondary ?: super.getAttr(ref, s1, s2)
}
Attr.POPUP_BACKGROUND_ACTIVE -> {
themeManager.remoteColorSecondary?.complimentaryTextColor(true) ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorSecondary?.complimentaryTextColor(true) ?: super.getAttr(ref, s1, s2)
}
Attr.POPUP_FOREGROUND -> {
themeManager.remoteColorSecondary?.complimentaryTextColor() ?: super.getAttr(ref, s1, s2)
themeManager.remote.colorSecondary?.complimentaryTextColor() ?: super.getAttr(ref, s1, s2)
}
else -> super.getAttr(ref, s1, s2)
}

View File

@@ -16,14 +16,15 @@
package dev.patrickgold.florisboard.ime.theme
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri
import dev.patrickgold.florisboard.ime.core.PrefHelper
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.core.content.ContextCompat
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.extension.AssetRef
import dev.patrickgold.florisboard.ime.extension.AssetSource
@@ -37,9 +38,9 @@ import java.util.concurrent.CopyOnWriteArrayList
*/
class ThemeManager private constructor(
private val applicationContext: Context,
private val assetManager: AssetManager,
private val prefs: PrefHelper
private val assetManager: AssetManager
) {
private val prefs get() = Preferences.default()
private val callbackReceivers: CopyOnWriteArrayList<OnThemeUpdatedListener> = CopyOnWriteArrayList()
private val packageManager: PackageManager? = applicationContext.packageManager
@@ -50,12 +51,9 @@ class ThemeManager private constructor(
var isAdaptiveThemeEnabled: Boolean = false
private set
var remoteColorPrimary: ThemeValue.SolidColor? = null
private set
var remoteColorPrimaryVariant: ThemeValue.SolidColor? = null
private set
var remoteColorSecondary: ThemeValue.SolidColor? = null
var remote: RemoteColors = RemoteColors.DEFAULT
private set
private val remoteCache: ArrayList<RemoteColors> = arrayListOf()
companion object {
/** The static relative path where a theme is located, regardless of the [AssetSource]. */
@@ -67,10 +65,9 @@ class ThemeManager private constructor(
fun init(
applicationContext: Context,
assetManager: AssetManager,
prefs: PrefHelper
assetManager: AssetManager
): ThemeManager {
val instance = ThemeManager(applicationContext, assetManager, prefs)
val instance = ThemeManager(applicationContext, assetManager)
defaultInstance = instance
return instance
}
@@ -81,7 +78,7 @@ class ThemeManager private constructor(
return instance
} else {
throw UninitializedPropertyAccessException(
"${ThemeManager::class.simpleName} has not been initialized previously. Make sure to call init(prefs) before using default()."
"${ThemeManager::class.simpleName} has not been initialized previously. Make sure to call init() before using default()."
)
}
}
@@ -101,127 +98,95 @@ class ThemeManager private constructor(
indexThemeRefs()
val ref = evaluateActiveThemeRef()
Timber.i(ref.toString())
activeTheme = AdaptiveThemeOverlay(this, if (ref == null) {
Theme.BASE_THEME
} else {
loadTheme(ref).getOrDefault(Theme.BASE_THEME)
})
activeTheme = AdaptiveThemeOverlay(
this, if (ref == null) {
Theme.BASE_THEME
} else {
loadTheme(ref).getOrDefault(Theme.BASE_THEME)
}
)
Timber.i(activeTheme.label)
notifyCallbackReceivers()
}
/**
* Gets the primary and ark variants of the app with given [packageName].
* Based on a Stock Overflow answer by adneal.
* Source: https://stackoverflow.com/a/27138913/6801193
* Gets the primary and ark variants of the app with given [remotePackageName].
* Based AnySoftKeyboard's way of getting remote colors:
* https://github.com/AnySoftKeyboard/AnySoftKeyboard/blob/master/ime/overlay/src/main/java/com/anysoftkeyboard/overlay/OverlyDataCreatorForAndroid.java
*
* @param packageName The package name from which the colors should be extracted.
* @param remotePackageName The package name from which the colors should be extracted.
*/
@SuppressLint("ResourceType")
@Suppress("UNNECESSARY_SAFE_CALL")
fun updateRemoteColorValues(packageName: String) {
return // See why: https://github.com/florisboard/florisboard/issues/763
fun updateRemoteColorValues(remotePackageName: String) {
if (!isAdaptiveThemeEnabled) return
try {
val tempRemote = remoteCache.find { it.packageName == remotePackageName }
if (tempRemote != null) {
remote = tempRemote
return
}
val colorPrimary: Int?
val colorPrimaryVariant: Int?
val colorSecondary: Int?
val pm = packageManager ?: return
val res = pm.getResourcesForApplication(packageName)
val attrs = listOf(
res.getIdentifier("colorPrimary", "attr", packageName),
android.R.attr.colorPrimary,
res.getIdentifier("colorPrimaryDark", "attr", packageName),
android.R.attr.colorPrimaryDark,
res.getIdentifier("colorPrimaryVariant", "attr", packageName),
res.getIdentifier("colorAccent", "attr", packageName),
android.R.attr.colorAccent,
res.getIdentifier("colorSecondary", "attr", packageName)
val remoteApp = pm.getLaunchIntentForPackage(remotePackageName)?.component ?: return
val activityInfo = pm.getActivityInfo(remoteApp, PackageManager.GET_META_DATA)
val remoteContext = applicationContext.createPackageContext(
remoteApp.packageName,
Context.CONTEXT_IGNORE_SECURITY
)
val androidTheme = res.newTheme()
val defColor = if (activeTheme.isNightTheme) {
Color.BLACK
} else {
Color.WHITE
}
val themeIds = mutableListOf<Int>()
pm.getLaunchIntentForPackage(packageName)?.component?.let { cn ->
pm.getActivityInfo(cn, 0)?.let { launchActivity ->
if (launchActivity.targetActivity != null) {
pm.getActivityInfo(ComponentName(packageName, launchActivity.targetActivity), 0)?.let {
themeIds.add(it.theme)
}
} else {
themeIds.add(launchActivity.theme)
}
}
}
pm.getApplicationInfo(packageName, 0)?.let { applicationInfo ->
themeIds.add(applicationInfo.theme)
}
remoteColorPrimary = null
remoteColorPrimaryVariant = null
remoteColorSecondary = null
for (themeId in themeIds) {
if (remoteColorPrimary != null && remoteColorPrimaryVariant != null &&
remoteColorSecondary != null) {
break
}
androidTheme.applyStyle(themeId, false)
androidTheme.obtainStyledAttributes(attrs.toIntArray()).let { a ->
remoteColorPrimary = when {
a.hasValue(0) -> {
ThemeValue.SolidColor(a.getColor(0, defColor))
}
a.hasValue(1) -> {
ThemeValue.SolidColor(a.getColor(1, defColor))
}
else -> {
remoteColorPrimary
}
}
remoteColorPrimaryVariant = when {
a.hasValue(2) -> {
ThemeValue.SolidColor(a.getColor(2, defColor))
}
a.hasValue(3) -> {
ThemeValue.SolidColor(a.getColor(3, defColor))
}
a.hasValue(4) -> {
ThemeValue.SolidColor(a.getColor(4, defColor))
}
else -> {
remoteColorPrimaryVariant
}
}
remoteColorSecondary = when {
a.hasValue(5) -> {
ThemeValue.SolidColor(a.getColor(5, defColor))
}
a.hasValue(6) -> {
ThemeValue.SolidColor(a.getColor(6, defColor))
}
a.hasValue(7) -> {
ThemeValue.SolidColor(a.getColor(7, defColor))
}
else -> {
remoteColorSecondary
}
}
a.recycle()
}
}
remoteContext.setTheme(activityInfo.themeResource)
val res = remoteContext.resources
val attrs = intArrayOf(
res.getIdentifier("colorPrimary", "attr", remotePackageName),
android.R.attr.colorPrimary,
res.getIdentifier("colorPrimaryDark", "attr", remotePackageName),
android.R.attr.colorPrimaryDark,
res.getIdentifier("colorPrimaryVariant", "attr", remotePackageName),
res.getIdentifier("colorAccent", "attr", remotePackageName),
android.R.attr.colorAccent,
res.getIdentifier("colorSecondary", "attr", remotePackageName)
)
val typedValue = TypedValue()
colorPrimary =
getColorFromThemeAttribute(remoteContext, typedValue, attrs[0]) ?:
getColorFromThemeAttribute(remoteContext, typedValue, attrs[1])
colorPrimaryVariant =
getColorFromThemeAttribute(remoteContext, typedValue, attrs[2]) ?:
getColorFromThemeAttribute(remoteContext, typedValue, attrs[3]) ?:
getColorFromThemeAttribute(remoteContext, typedValue, attrs[4])
colorSecondary =
getColorFromThemeAttribute(remoteContext, typedValue, attrs[5]) ?:
getColorFromThemeAttribute(remoteContext, typedValue, attrs[6]) ?:
getColorFromThemeAttribute(remoteContext, typedValue, attrs[7])
val newRemote = RemoteColors(
packageName = remotePackageName,
colorPrimary = colorPrimary?.let { ThemeValue.SolidColor(it or Color.BLACK) },
colorPrimaryVariant = colorPrimaryVariant?.let { ThemeValue.SolidColor(it or Color.BLACK) },
colorSecondary = colorSecondary?.let { ThemeValue.SolidColor(it or Color.BLACK) }
)
remoteCache.add(newRemote)
remote = newRemote
} catch (e: Exception) {
remote = RemoteColors.DEFAULT
e.printStackTrace()
}
remoteColorPrimary?.let {
remoteColorPrimary = ThemeValue.SolidColor(it.color or Color.BLACK)
}
remoteColorPrimaryVariant?.let {
remoteColorPrimaryVariant = ThemeValue.SolidColor(it.color or Color.BLACK)
}
remoteColorSecondary?.let {
remoteColorSecondary = ThemeValue.SolidColor(it.color or Color.BLACK)
}
notifyCallbackReceivers()
}
private fun getColorFromThemeAttribute(
context: Context, typedValue: TypedValue, @AttrRes attr: Int
): Int? {
return if (context.theme.resolveAttribute(attr, typedValue, true)) {
if (typedValue.type == TypedValue.TYPE_REFERENCE) {
ContextCompat.getColor(context, typedValue.resourceId)
} else {
typedValue.data
}
} else {
null
}
}
/**
* Sends a theme update to the given [onThemeUpdatedListener], regardless if it is currently
* registered or not.
@@ -274,37 +239,39 @@ class ThemeManager private constructor(
Timber.i(prefs.theme.mode.toString())
Timber.i(prefs.theme.dayThemeRef)
Timber.i(prefs.theme.nightThemeRef)
return AssetRef.fromString(when (prefs.theme.mode) {
ThemeMode.ALWAYS_DAY -> {
isAdaptiveThemeEnabled = prefs.theme.dayThemeAdaptToApp
prefs.theme.dayThemeRef
}
ThemeMode.ALWAYS_NIGHT -> {
isAdaptiveThemeEnabled = prefs.theme.nightThemeAdaptToApp
prefs.theme.nightThemeRef
}
ThemeMode.FOLLOW_SYSTEM -> if (applicationContext.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
) {
isAdaptiveThemeEnabled = prefs.theme.nightThemeAdaptToApp
prefs.theme.nightThemeRef
} else {
isAdaptiveThemeEnabled = prefs.theme.dayThemeAdaptToApp
prefs.theme.dayThemeRef
}
ThemeMode.FOLLOW_TIME -> {
val current = TimeUtil.currentLocalTime()
val sunrise = TimeUtil.decode(prefs.theme.sunriseTime)
val sunset = TimeUtil.decode(prefs.theme.sunsetTime)
if (TimeUtil.isNightTime(sunrise, sunset, current)) {
return AssetRef.fromString(
when (prefs.theme.mode) {
ThemeMode.ALWAYS_DAY -> {
isAdaptiveThemeEnabled = prefs.theme.dayThemeAdaptToApp
prefs.theme.dayThemeRef
}
ThemeMode.ALWAYS_NIGHT -> {
isAdaptiveThemeEnabled = prefs.theme.nightThemeAdaptToApp
prefs.theme.nightThemeRef
}
ThemeMode.FOLLOW_SYSTEM -> if (applicationContext.resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
) {
isAdaptiveThemeEnabled = prefs.theme.nightThemeAdaptToApp
prefs.theme.nightThemeRef
} else {
isAdaptiveThemeEnabled = prefs.theme.dayThemeAdaptToApp
prefs.theme.dayThemeRef
}
ThemeMode.FOLLOW_TIME -> {
val current = TimeUtil.currentLocalTime()
val sunrise = TimeUtil.decode(prefs.theme.sunriseTime)
val sunset = TimeUtil.decode(prefs.theme.sunsetTime)
if (TimeUtil.isNightTime(sunrise, sunset, current)) {
isAdaptiveThemeEnabled = prefs.theme.nightThemeAdaptToApp
prefs.theme.nightThemeRef
} else {
isAdaptiveThemeEnabled = prefs.theme.dayThemeAdaptToApp
prefs.theme.dayThemeRef
}
}
}
}).onFailure { Timber.e(it) }.getOrDefault(null)
).onFailure { Timber.e(it) }.getOrDefault(null)
}
private fun indexThemeRefs() {
@@ -338,6 +305,17 @@ class ThemeManager private constructor(
}
}
data class RemoteColors(
val packageName: String,
val colorPrimary: ThemeValue.SolidColor?,
val colorPrimaryVariant: ThemeValue.SolidColor?,
val colorSecondary: ThemeValue.SolidColor?
) {
companion object {
val DEFAULT = RemoteColors("undefined", null, null, null)
}
}
/**
* Functional interface which should be implemented by event listeners to be able to receive
* theme updates.

View File

@@ -16,8 +16,6 @@
package dev.patrickgold.florisboard.ime.theme
import java.util.*
/**
* Enum class which specifies all theme modes available. Used in the Settings to properly manage
* different use cases when the day or night theme should be active.
@@ -30,7 +28,7 @@ enum class ThemeMode {
companion object {
fun fromString(string: String): ThemeMode {
return valueOf(string.toUpperCase(Locale.ENGLISH))
return valueOf(string.uppercase())
}
}
}

View File

@@ -23,21 +23,19 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.AdvancedActivityBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.util.PackageManagerUtils
class AdvancedActivity : AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var binding: AdvancedActivityBinding
private lateinit var prefs: PrefHelper
private val prefs get() = Preferences.default()
companion object {
const val RESULT_APPLY_THEME = 0x322D
}
override fun onCreate(savedInstanceState: Bundle?) {
prefs = PrefHelper.getDefaultInstance(this)
super.onCreate(savedInstanceState)
binding = AdvancedActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -61,7 +59,7 @@ class AdvancedActivity : AppCompatActivity(),
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
prefs.sync()
if (key == PrefHelper.Advanced.SETTINGS_THEME) {
if (key == Preferences.Advanced.SETTINGS_THEME) {
setResult(RESULT_APPLY_THEME)
finish()
}

View File

@@ -31,7 +31,7 @@ import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.bottomnavigation.BottomNavigationView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsActivityBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
import dev.patrickgold.florisboard.settings.fragments.*
@@ -52,11 +52,10 @@ class SettingsMainActivity : AppCompatActivity(),
lateinit var binding: SettingsActivityBinding
lateinit var layoutManager: LayoutManager
private lateinit var prefs: PrefHelper
private val prefs get() = Preferences.default()
val subtypeManager: SubtypeManager get() = SubtypeManager.default()
override fun onCreate(savedInstanceState: Bundle?) {
prefs = PrefHelper.getDefaultInstance(this)
prefs.initDefaultPreferences()
prefs.sync()
layoutManager = LayoutManager()

View File

@@ -32,10 +32,8 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.ThemeEditorActivityBinding
import dev.patrickgold.florisboard.databinding.ThemeEditorGroupViewBinding
import dev.patrickgold.florisboard.databinding.ThemeEditorMetaDialogBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.extension.AssetRef
import dev.patrickgold.florisboard.ime.text.key.CurrencySet
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
import dev.patrickgold.florisboard.ime.theme.Theme
@@ -54,7 +52,6 @@ class ThemeEditorActivity : AppCompatActivity() {
private lateinit var binding: ThemeEditorActivityBinding
private val mainScope = MainScope()
private lateinit var layoutManager: LayoutManager
private lateinit var prefs: PrefHelper
private val themeManager: ThemeManager = ThemeManager.default()
private var editedTheme: Theme = Theme.empty()
@@ -79,8 +76,6 @@ class ThemeEditorActivity : AppCompatActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
prefs = PrefHelper.getDefaultInstance(this)
super.onCreate(savedInstanceState)
binding = ThemeEditorActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -322,7 +317,7 @@ class ThemeEditorActivity : AppCompatActivity() {
}
mainScope.launch {
binding.keyboardPreview.setComputedKeyboard(layoutManager.computeKeyboardAsync(
KeyboardMode.CHARACTERS, Subtype.DEFAULT, prefs
KeyboardMode.CHARACTERS, Subtype.DEFAULT
).await())
binding.keyboardPreview.onThemeUpdated(editedTheme)
}

View File

@@ -33,13 +33,14 @@ import androidx.core.view.forEach
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.ThemeManagerActivityBinding
import dev.patrickgold.florisboard.ime.core.FlorisActivity
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.extension.AssetManager
import dev.patrickgold.florisboard.ime.extension.AssetRef
import dev.patrickgold.florisboard.ime.extension.AssetSource
import dev.patrickgold.florisboard.ime.text.key.CurrencySet
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.keyboard.*
import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
@@ -53,6 +54,21 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
private val themeManager: ThemeManager get() = ThemeManager.default()
private val assetManager: AssetManager get() = AssetManager.default()
private lateinit var textKeyboardIconSet: TextKeyboardIconSet
private val textComputingEvaluator = object : TextComputingEvaluator by DefaultTextComputingEvaluator {
override fun evaluateVisible(data: TextKeyData): Boolean {
return data.code != KeyCode.SWITCH_TO_MEDIA_CONTEXT
}
override fun isSlot(data: TextKeyData): Boolean {
return CurrencySet.isCurrencySlot(data.code)
}
override fun getSlotData(data: TextKeyData): TextKeyData {
return BasicTextKeyData(label = "$")
}
}
private var key: String = ""
private var defaultValue: String = ""
private var selectedTheme: Theme = Theme.empty()
@@ -131,8 +147,8 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
supportActionBar?.setTitle(
when (key) {
PrefHelper.Theme.DAY_THEME_REF -> R.string.settings__theme_manager__title_day
PrefHelper.Theme.NIGHT_THEME_REF -> R.string.settings__theme_manager__title_night
Preferences.Theme.DAY_THEME_REF -> R.string.settings__theme_manager__title_day
Preferences.Theme.NIGHT_THEME_REF -> R.string.settings__theme_manager__title_night
else -> R.string.settings__title
}
)
@@ -145,6 +161,10 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
binding.themeEditBtn.setOnClickListener { onActionClicked(it) }
binding.themeExportBtn.setOnClickListener { onActionClicked(it) }
textKeyboardIconSet = TextKeyboardIconSet.new(this)
binding.keyboardPreview.setIconSet(textKeyboardIconSet)
binding.keyboardPreview.setComputingEvaluator(textComputingEvaluator)
buildUi()
}
@@ -172,15 +192,15 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
private fun evaluateSelectedRef(ignorePrefs: Boolean = false): AssetRef? {
return if (ignorePrefs) {
when (key) {
PrefHelper.Theme.DAY_THEME_REF -> themeManager.indexedDayThemeRefs.keys.firstOrNull()
PrefHelper.Theme.NIGHT_THEME_REF -> themeManager.indexedNightThemeRefs.keys.firstOrNull()
Preferences.Theme.DAY_THEME_REF -> themeManager.indexedDayThemeRefs.keys.firstOrNull()
Preferences.Theme.NIGHT_THEME_REF -> themeManager.indexedNightThemeRefs.keys.firstOrNull()
else -> null
}
} else {
AssetRef.fromString(
when (key) {
PrefHelper.Theme.DAY_THEME_REF -> prefs.theme.dayThemeRef
PrefHelper.Theme.NIGHT_THEME_REF -> prefs.theme.nightThemeRef
Preferences.Theme.DAY_THEME_REF -> prefs.theme.dayThemeRef
Preferences.Theme.NIGHT_THEME_REF -> prefs.theme.nightThemeRef
else -> ""
}
).getOrDefault(null)
@@ -189,8 +209,8 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
private fun setThemeRefInPrefs(ref: AssetRef?) {
when (key) {
PrefHelper.Theme.DAY_THEME_REF -> prefs.theme.dayThemeRef = ref.toString()
PrefHelper.Theme.NIGHT_THEME_REF -> prefs.theme.nightThemeRef = ref.toString()
Preferences.Theme.DAY_THEME_REF -> prefs.theme.dayThemeRef = ref.toString()
Preferences.Theme.NIGHT_THEME_REF -> prefs.theme.nightThemeRef = ref.toString()
}
}
@@ -236,7 +256,7 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
name = "theme-$timestamp",
label = resources.getString(R.string.settings__theme_manager__theme_new_title),
authors = listOf("@me"),
isNightTheme = key == PrefHelper.Theme.NIGHT_THEME_REF
isNightTheme = key == Preferences.Theme.NIGHT_THEME_REF
)
val newAssetRef =
AssetRef(
@@ -351,8 +371,8 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
private fun buildUi() {
val metaIndex = when (key) {
PrefHelper.Theme.DAY_THEME_REF -> themeManager.indexedDayThemeRefs
PrefHelper.Theme.NIGHT_THEME_REF -> themeManager.indexedNightThemeRefs
Preferences.Theme.DAY_THEME_REF -> themeManager.indexedDayThemeRefs
Preferences.Theme.NIGHT_THEME_REF -> themeManager.indexedNightThemeRefs
else -> mutableMapOf()
}
binding.themeList.removeAllViews()
@@ -388,7 +408,7 @@ class ThemeManagerActivity : FlorisActivity<ThemeManagerActivityBinding>() {
}
launch {
binding.keyboardPreview.setComputedKeyboard(layoutManager.computeKeyboardAsync(
KeyboardMode.CHARACTERS, Subtype.DEFAULT, prefs
KeyboardMode.CHARACTERS, Subtype.DEFAULT
).await())
binding.keyboardPreview.onThemeUpdated(selectedTheme)
}

View File

@@ -0,0 +1,455 @@
/*
* 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.settings
import android.app.AlertDialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.UdmActivityBinding
import dev.patrickgold.florisboard.databinding.UdmEntryDialogBinding
import dev.patrickgold.florisboard.ime.core.FlorisActivity
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FREQUENCY_DEFAULT
import dev.patrickgold.florisboard.ime.dictionary.FREQUENCY_MAX
import dev.patrickgold.florisboard.ime.dictionary.FREQUENCY_MIN
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryDao
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryDatabase
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryEntry
import dev.patrickgold.florisboard.ime.text.keyboard.*
import dev.patrickgold.florisboard.util.LocaleUtils
import java.util.*
interface OnListItemCLickListener {
fun onListItemClick(pos: Int)
}
class LanguageEntryAdapter(
private val data: List<String>,
private val onListItemCLickListener: OnListItemCLickListener
) : RecyclerView.Adapter<LanguageEntryAdapter.ViewHolder>() {
class ViewHolder(view: View, private val onListItemCLickListener: OnListItemCLickListener) :
RecyclerView.ViewHolder(view) {
val titleView: TextView = view.findViewById(android.R.id.title)
val summaryView: TextView = view.findViewById(android.R.id.summary)
init {
view.setOnClickListener {
onListItemCLickListener.onListItemClick(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val listItemView = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ViewHolder(listItemView, onListItemCLickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.titleView.text = data[position]
holder.summaryView.isVisible = false
}
override fun getItemCount(): Int {
return data.size
}
}
class UserDictionaryEntryAdapter(
private val data: List<UserDictionaryEntry>,
private val onListItemCLickListener: OnListItemCLickListener
) : RecyclerView.Adapter<UserDictionaryEntryAdapter.ViewHolder>() {
class ViewHolder(view: View, private val onListItemCLickListener: OnListItemCLickListener) :
RecyclerView.ViewHolder(view) {
val titleView: TextView = view.findViewById(android.R.id.title)
val summaryView: TextView = view.findViewById(android.R.id.summary)
init {
view.setOnClickListener {
onListItemCLickListener.onListItemClick(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val listItemView = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ViewHolder(listItemView, onListItemCLickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.titleView.text = data[position].word
val shortcut = data[position].shortcut
holder.summaryView.text = if (shortcut == null) {
String.format(
holder.summaryView.context.resources.getString(R.string.settings__udm__word_summary_freq),
data[position].freq
)
} else {
String.format(
holder.summaryView.context.resources.getString(R.string.settings__udm__word_summary_freq_shortcut),
data[position].freq,
shortcut
)
}
}
override fun getItemCount(): Int {
return data.size
}
}
class UdmActivity : FlorisActivity<UdmActivityBinding>() {
private val dictionaryManager: DictionaryManager get() = DictionaryManager.default()
private var userDictionaryType: Int = -1
private var currentLevel: Int = LEVEL_LANGUAGES
private var currentLocale: Locale? = null
private var activeDialogWindow: AlertDialog? = null
private var languageList: List<Locale?> = listOf()
private var wordList: List<UserDictionaryEntry> = listOf()
private val languageListItemClickListener = object : OnListItemCLickListener {
override fun onListItemClick(pos: Int) {
if (currentLevel == LEVEL_LANGUAGES) {
currentLocale = languageList[pos]
currentLevel = LEVEL_WORDS
buildUi()
}
}
}
private val wordListItemClickListener = object : OnListItemCLickListener {
override fun onListItemClick(pos: Int) {
if (currentLevel == LEVEL_WORDS) {
val entry = wordList[pos]
showEditWordDialog(entry)
}
}
}
private val importUserDictionary = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: 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@registerForActivityResult
val db: UserDictionaryDatabase? = when (userDictionaryType) {
USER_DICTIONARY_TYPE_FLORIS -> {
dictionaryManager.florisUserDictionaryDatabase()
}
USER_DICTIONARY_TYPE_SYSTEM -> {
dictionaryManager.systemUserDictionaryDatabase()
}
else -> {
showError(Exception("User dictionary type '$userDictionaryType' is not valid!"))
return@registerForActivityResult
}
}
if (db == null) {
showError(NullPointerException("Database handle is null!"))
return@registerForActivityResult
}
db.importCombinedList(this, uri).onSuccess {
showMessage(R.string.settings__udm__dictionary_export_success)
}.onFailure {
showError(it)
}
}
private val exportUserDictionary = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri: 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@registerForActivityResult
val db: UserDictionaryDatabase? = when (userDictionaryType) {
USER_DICTIONARY_TYPE_FLORIS -> {
dictionaryManager.florisUserDictionaryDatabase()
}
USER_DICTIONARY_TYPE_SYSTEM -> {
dictionaryManager.systemUserDictionaryDatabase()
}
else -> {
showError(Exception("User dictionary type '$userDictionaryType' is not valid!"))
return@registerForActivityResult
}
}
if (db == null) {
showError(NullPointerException("Database handle is null!"))
return@registerForActivityResult
}
db.exportCombinedList(this, uri).onSuccess {
showMessage(R.string.settings__udm__dictionary_export_success)
}.onFailure {
showError(it)
}
}
companion object {
const val EXTRA_USER_DICTIONARY_TYPE: String = "key"
const val USER_DICTIONARY_TYPE_SYSTEM: Int = 1
const val USER_DICTIONARY_TYPE_FLORIS: Int = 2
private const val LEVEL_LANGUAGES: Int = 1
private const val LEVEL_WORDS: Int = 2
private const val SYSTEM_USER_DICTIONARY_SETTINGS_INTENT_ACTION: String =
"android.settings.USER_DICTIONARY_SETTINGS"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userDictionaryType = intent.getIntExtra(EXTRA_USER_DICTIONARY_TYPE, -1)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setTitle(
when (userDictionaryType) {
USER_DICTIONARY_TYPE_FLORIS -> R.string.settings__udm__title_floris
USER_DICTIONARY_TYPE_SYSTEM -> R.string.settings__udm__title_system
else -> R.string.settings__title
}
)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
dictionaryManager.loadUserDictionariesIfNecessary()
binding.fabAddWord.setOnClickListener { showAddWordDialog() }
binding.recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
buildUi()
}
override fun onCreateBinding(): UdmActivityBinding {
return UdmActivityBinding.inflate(layoutInflater)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.udm_extra_menu, menu)
if (userDictionaryType == USER_DICTIONARY_TYPE_FLORIS) {
menu?.findItem(R.id.udm__open_system_manager_ui)?.isVisible = false
}
return true
}
override fun onDestroy() {
super.onDestroy()
activeDialogWindow?.dismiss()
activeDialogWindow = null
currentLocale = null
}
override fun onResume() {
super.onResume()
buildUi()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
R.id.udm__import -> {
importUserDictionary.launch("*/*")
true
}
R.id.udm__export -> {
exportUserDictionary.launch("my-personal-dictionary.clb")
true
}
R.id.udm__open_system_manager_ui -> {
startActivity(Intent(SYSTEM_USER_DICTIONARY_SETTINGS_INTENT_ACTION))
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onBackPressed() {
if (currentLevel == LEVEL_WORDS) {
currentLevel = LEVEL_LANGUAGES
currentLocale = null
buildUi()
} else {
super.onBackPressed()
}
}
private fun userDictionaryDao(): UserDictionaryDao? {
return when (userDictionaryType) {
USER_DICTIONARY_TYPE_FLORIS -> dictionaryManager.florisUserDictionaryDao()
USER_DICTIONARY_TYPE_SYSTEM -> dictionaryManager.systemUserDictionaryDao()
else -> null
}
}
private fun buildUi() {
when (currentLevel) {
LEVEL_LANGUAGES -> {
languageList = userDictionaryDao()?.queryLanguageList()?.sortedBy { it?.displayLanguage } ?: listOf()
binding.recyclerView.adapter = LanguageEntryAdapter(
languageList.map { getDisplayNameForLocale(it) },
languageListItemClickListener
)
supportActionBar?.subtitle = null
}
LEVEL_WORDS -> {
wordList = userDictionaryDao()?.queryAll(currentLocale) ?: listOf()
binding.recyclerView.adapter = UserDictionaryEntryAdapter(
wordList,
wordListItemClickListener
)
supportActionBar?.subtitle = getDisplayNameForLocale(currentLocale)
}
}
}
private fun getDisplayNameForLocale(locale: Locale?): String {
return locale?.displayName ?: resources.getString(R.string.settings__udm__all_languages)
}
private fun showAddWordDialog() {
val dialogBinding = UdmEntryDialogBinding.inflate(layoutInflater)
dialogBinding.freq.setText(FREQUENCY_DEFAULT.toString())
dialogBinding.freqLabel.hint = String.format(
resources.getString(R.string.settings__udm__dialog__freq_label),
FREQUENCY_MIN,
FREQUENCY_MAX
)
if (currentLevel == LEVEL_WORDS) {
currentLocale?.let {
dialogBinding.locale.setText(it.toString())
}
}
AlertDialog.Builder(this).apply {
setTitle(R.string.settings__udm__dialog__title_add)
setCancelable(true)
setView(dialogBinding.root)
setPositiveButton(R.string.assets__action__add, null)
setNegativeButton(R.string.assets__action__cancel, null)
setOnDismissListener { activeDialogWindow = null }
create()
activeDialogWindow = show()
activeDialogWindow?.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
if (processInput(dialogBinding, null)) {
activeDialogWindow?.dismiss()
activeDialogWindow = null
buildUi()
}
}
}
}
private fun showEditWordDialog(entry: UserDictionaryEntry) {
val dialogBinding = UdmEntryDialogBinding.inflate(layoutInflater)
dialogBinding.word.setText(entry.word)
dialogBinding.freq.setText(entry.freq.toString())
dialogBinding.freqLabel.hint = String.format(
resources.getString(R.string.settings__udm__dialog__freq_label),
FREQUENCY_MIN,
FREQUENCY_MAX
)
dialogBinding.shortcut.setText(entry.shortcut ?: "")
dialogBinding.locale.setText(entry.locale ?: "")
AlertDialog.Builder(this).apply {
setTitle(R.string.settings__udm__dialog__title_edit)
setCancelable(true)
setView(dialogBinding.root)
setPositiveButton(R.string.assets__action__apply, null)
setNegativeButton(R.string.assets__action__cancel, null)
setNeutralButton(R.string.assets__action__delete) { _, _ ->
userDictionaryDao()?.delete(entry)
buildUi()
}
setOnDismissListener { activeDialogWindow = null }
create()
activeDialogWindow = show()
activeDialogWindow?.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
if (processInput(dialogBinding, entry)) {
activeDialogWindow?.dismiss()
activeDialogWindow = null
buildUi()
}
}
}
}
private fun processInput(dialogBinding: UdmEntryDialogBinding, entry: UserDictionaryEntry?): Boolean {
val word = dialogBinding.word.text?.toString()?.ifBlank { null }
val freq = dialogBinding.freq.text?.toString()?.ifBlank { null }
val shortcut = dialogBinding.shortcut.text?.toString()?.ifBlank { null }
val locale = dialogBinding.locale.text?.toString()?.ifBlank { null }
var isValid = true
if (word == null) {
dialogBinding.word.error = resources.getString(R.string.settings__udm__dialog__word_error_empty)
isValid = false
} else if (word.contains(' ') || word.contains(';') || word.contains('=')) {
dialogBinding.word.error = resources.getString(R.string.settings__udm__dialog__word_error_invalid)
isValid = false
}
if (freq == null) {
dialogBinding.freq.error = resources.getString(R.string.settings__udm__dialog__freq_error_empty)
isValid = false
} else if (runCatching { freq.toInt(10) }.getOrNull() !in FREQUENCY_MIN..FREQUENCY_MAX) {
dialogBinding.freq.error = resources.getString(R.string.settings__udm__dialog__freq_error_invalid)
isValid = false
}
if (shortcut != null && (shortcut.contains(' ') || shortcut.contains(';') || shortcut.contains('='))) {
dialogBinding.shortcut.error = resources.getString(R.string.settings__udm__dialog__shortcut_error_invalid)
isValid = false
}
if (locale != null && (runCatching { LocaleUtils.stringToLocale(locale).isO3Language.ifBlank { null } }.getOrNull() == null)) {
dialogBinding.locale.error = resources.getString(R.string.settings__udm__dialog__locale_error_invalid)
isValid = false
}
if (isValid) {
val localeStr = if (locale == null) { null } else {
LocaleUtils.stringToLocale(locale).toString()
}
if (entry != null) {
userDictionaryDao()?.update(
UserDictionaryEntry(entry.id, word!!, freq!!.toInt(10), localeStr, shortcut)
)
} else {
userDictionaryDao()?.insert(
UserDictionaryEntry(0, word!!, freq!!.toInt(10), localeStr, shortcut)
)
}
}
return isValid
}
}

View File

@@ -25,7 +25,7 @@ import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceClickListener
import androidx.preference.PreferenceManager
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.extension.AssetRef
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.settings.ThemeManagerActivity
@@ -36,13 +36,13 @@ import dev.patrickgold.florisboard.settings.ThemeManagerActivity
*/
class ThemeSelectorPreference : Preference, SharedPreferences.OnSharedPreferenceChangeListener {
private var defaultValue: String = when (key) {
PrefHelper.Theme.DAY_THEME_REF -> "assets:ime/theme/floris_day.json"
PrefHelper.Theme.NIGHT_THEME_REF -> "assets:ime/theme/floris_night.json"
Preferences.Theme.DAY_THEME_REF -> "assets:ime/theme/floris_day.json"
Preferences.Theme.NIGHT_THEME_REF -> "assets:ime/theme/floris_night.json"
else -> ""
}
private var dialog: AlertDialog? = null
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val themeManager: ThemeManager = ThemeManager.default()
private val prefs get() = Preferences.default()
private val themeManager get() = ThemeManager.default()
@Suppress("unused")
constructor(context: Context) : this(context, null)
@@ -79,13 +79,13 @@ class ThemeSelectorPreference : Preference, SharedPreferences.OnSharedPreference
*/
private fun generateSummaryText(): String {
when (key) {
PrefHelper.Theme.DAY_THEME_REF -> {
Preferences.Theme.DAY_THEME_REF -> {
val metaIndex = themeManager.indexedDayThemeRefs
AssetRef.fromString(prefs.theme.dayThemeRef).onSuccess { ref ->
metaIndex[ref]?.label?.let { return it }
}
}
PrefHelper.Theme.NIGHT_THEME_REF -> {
Preferences.Theme.NIGHT_THEME_REF -> {
val metaIndex = themeManager.indexedNightThemeRefs
AssetRef.fromString(prefs.theme.nightThemeRef).onSuccess { ref ->
metaIndex[ref]?.label?.let { return it }

View File

@@ -17,11 +17,38 @@
package dev.patrickgold.florisboard.settings.fragments
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
class AdvancedFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_advanced)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
Preferences.Devtools.CLEAR_UDM_INTERNAL_DATABASE -> {
AlertDialog.Builder(requireContext()).apply {
setTitle(R.string.assets__action__delete_confirm_title)
setMessage(String.format(resources.getString(R.string.assets__action__delete_confirm_message), FlorisUserDictionaryDatabase.DB_FILE_NAME))
setPositiveButton(R.string.assets__action__delete) { _, _ ->
DictionaryManager.default().let {
it.loadUserDictionariesIfNecessary()
it.florisUserDictionaryDao()?.deleteAll()
}
}
setNegativeButton(android.R.string.cancel, null)
create()
show()
}
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
}

View File

@@ -22,7 +22,7 @@ import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.settings.components.DialogSeekBarPreference
@@ -37,12 +37,12 @@ class KeyboardFragment : PreferenceFragmentCompat(),
addPreferencesFromResource(R.xml.prefs_keyboard)
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
heightFactorCustom = findPreference(PrefHelper.Keyboard.HEIGHT_FACTOR_CUSTOM)
oneHandedModeScaleFactor = findPreference(PrefHelper.Keyboard.ONE_HANDED_MODE_SCALE_FACTOR)
utilityKeyAction = findPreference(PrefHelper.Keyboard.UTILITY_KEY_ACTION)
onSharedPreferenceChanged(null, PrefHelper.Keyboard.HEIGHT_FACTOR)
onSharedPreferenceChanged(null, PrefHelper.Keyboard.ONE_HANDED_MODE)
onSharedPreferenceChanged(null, PrefHelper.Keyboard.UTILITY_KEY_ENABLED)
heightFactorCustom = findPreference(Preferences.Keyboard.HEIGHT_FACTOR_CUSTOM)
oneHandedModeScaleFactor = findPreference(Preferences.Keyboard.ONE_HANDED_MODE_SCALE_FACTOR)
utilityKeyAction = findPreference(Preferences.Keyboard.UTILITY_KEY_ACTION)
onSharedPreferenceChanged(null, Preferences.Keyboard.HEIGHT_FACTOR)
onSharedPreferenceChanged(null, Preferences.Keyboard.ONE_HANDED_MODE)
onSharedPreferenceChanged(null, Preferences.Keyboard.UTILITY_KEY_ENABLED)
}
override fun onResume() {
@@ -57,13 +57,13 @@ class KeyboardFragment : PreferenceFragmentCompat(),
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
PrefHelper.Keyboard.HEIGHT_FACTOR -> {
Preferences.Keyboard.HEIGHT_FACTOR -> {
heightFactorCustom?.isVisible = sharedPrefs?.getString(key, "") == "custom"
}
PrefHelper.Keyboard.ONE_HANDED_MODE -> {
Preferences.Keyboard.ONE_HANDED_MODE -> {
oneHandedModeScaleFactor?.isEnabled = sharedPrefs?.getString(key, "") != OneHandedMode.OFF
}
PrefHelper.Keyboard.UTILITY_KEY_ENABLED -> {
Preferences.Keyboard.UTILITY_KEY_ENABLED -> {
utilityKeyAction?.isVisible = sharedPrefs?.getBoolean(key, false) == true
}
}

View File

@@ -22,7 +22,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.settings.components.ThemeSelectorPreference
@@ -34,19 +34,18 @@ class ThemeFragment : PreferenceFragmentCompat(),
private var nightThemeRef: ThemeSelectorPreference? = null
private var sunrisePref: Preference? = null
private var sunsetPref: Preference? = null
private lateinit var prefs: PrefHelper
private val prefs get() = Preferences.default()
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_theme)
prefs = PrefHelper.getDefaultInstance(requireContext())
dayThemeGroup = findPreference("theme__day_group")
nightThemeGroup = findPreference("theme__night_group")
dayThemeRef = findPreference(PrefHelper.Theme.DAY_THEME_REF)
nightThemeRef = findPreference(PrefHelper.Theme.NIGHT_THEME_REF)
sunrisePref = findPreference(PrefHelper.Theme.SUNRISE_TIME)
sunsetPref = findPreference(PrefHelper.Theme.SUNSET_TIME)
onSharedPreferenceChanged(prefs.shared, PrefHelper.Theme.MODE)
dayThemeRef = findPreference(Preferences.Theme.DAY_THEME_REF)
nightThemeRef = findPreference(Preferences.Theme.NIGHT_THEME_REF)
sunrisePref = findPreference(Preferences.Theme.SUNRISE_TIME)
sunsetPref = findPreference(Preferences.Theme.SUNSET_TIME)
onSharedPreferenceChanged(prefs.shared, Preferences.Theme.MODE)
}
private fun refreshUi() {
@@ -87,7 +86,7 @@ class ThemeFragment : PreferenceFragmentCompat(),
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
prefs.sync()
key ?: return
if (key == PrefHelper.Theme.MODE) {
if (key == Preferences.Theme.MODE) {
refreshUi()
}
}

View File

@@ -60,7 +60,7 @@ class TypingFragment : SettingsMainActivity.SettingsFragment() {
.beginTransaction()
.replace(
binding.prefsFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_typing)
TypingInnerFragment()
)
.commit()

View File

@@ -0,0 +1,49 @@
/*
* 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.settings.fragments
import android.content.Intent
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.settings.UdmActivity
class TypingInnerFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_typing)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
Preferences.Dictionary.MANAGE_SYSTEM_USER_DICTIONARY -> {
val intent = Intent(context, UdmActivity::class.java)
intent.putExtra(UdmActivity.EXTRA_USER_DICTIONARY_TYPE, UdmActivity.USER_DICTIONARY_TYPE_SYSTEM)
startActivity(intent)
true
}
Preferences.Dictionary.MANAGE_FLORIS_USER_DICTIONARY -> {
val intent = Intent(context, UdmActivity::class.java)
intent.putExtra(UdmActivity.EXTRA_USER_DICTIONARY_TYPE, UdmActivity.USER_DICTIONARY_TYPE_FLORIS)
startActivity(intent)
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
}

View File

@@ -29,7 +29,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SetupActivityBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
import dev.patrickgold.florisboard.settings.SettingsMainActivity
class SetupActivity : AppCompatActivity() {
@@ -40,12 +40,12 @@ class SetupActivity : AppCompatActivity() {
private lateinit var adapter: ViewPagerAdapter
private lateinit var binding: SetupActivityBinding
lateinit var imm: InputMethodManager
lateinit var prefs: PrefHelper
lateinit var prefs: Preferences
private var shouldFinish: Boolean = false
private var shouldLaunchSettings: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
prefs = PrefHelper(this)
prefs = Preferences(this)
prefs.initDefaultPreferences()
imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager

View File

@@ -17,7 +17,7 @@
package dev.patrickgold.florisboard.util
import android.content.Context
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Preferences
object AppVersionUtils {
fun getRawVersionName(context: Context): String {
@@ -28,7 +28,7 @@ object AppVersionUtils {
}
}
fun shouldShowChangelog(context: Context, prefs: PrefHelper): Boolean {
fun shouldShowChangelog(context: Context, prefs: Preferences): Boolean {
val installVersion =
VersionName.fromString(prefs.internal.versionOnInstall) ?: VersionName.DEFAULT
val lastChangelogVersion =
@@ -39,14 +39,14 @@ object AppVersionUtils {
return lastChangelogVersion < currentVersion && installVersion != currentVersion
}
fun updateVersionOnInstallAndLastUse(context: Context, prefs: PrefHelper) {
fun updateVersionOnInstallAndLastUse(context: Context, prefs: Preferences) {
if (prefs.internal.versionOnInstall == VersionName.DEFAULT_RAW) {
prefs.internal.versionOnInstall = getRawVersionName(context)
}
prefs.internal.versionLastUse = getRawVersionName(context)
}
fun updateVersionLastChangelog(context: Context, prefs: PrefHelper) {
fun updateVersionLastChangelog(context: Context, prefs: Preferences) {
prefs.internal.versionLastChangelog = getRawVersionName(context)
}
}

View File

@@ -109,10 +109,11 @@
</LinearLayout>
<LinearLayout
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
app:flexDirection="row"
app:flexWrap="wrap">
<Button
android:id="@+id/theme_export_btn"
@@ -129,7 +130,7 @@
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.0"/>
app:layout_flexGrow="1"/>
<Button
android:id="@+id/theme_delete_btn"
@@ -155,7 +156,7 @@
android:drawablePadding="8dp"
android:drawableTint="?colorAccent"/>
</LinearLayout>
</com.google.android.flexbox.FlexboxLayout>
</LinearLayout>

View File

@@ -0,0 +1,33 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:theme="@style/SettingsTheme"
tools:context=".settings.UdmActivity">
<include layout="@layout/toolbar"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground"
android:layout_marginTop="?actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/fab_margin"
android:layout_marginEnd="@dimen/fab_margin"
android:backgroundTint="?colorPrimary"
app:borderWidth="0dp"
android:src="@drawable/ic_add"
android:contentDescription=""/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,94 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingTop="16dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/word_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings__udm__dialog__word_label"
app:boxBackgroundMode="outline"
app:boxBackgroundColor="?android:windowBackground"
app:boxStrokeColor="?colorAccent"
app:boxStrokeErrorColor="?colorError"
app:boxStrokeWidth="1dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textFilter"
android:imeOptions="flagNoExtractUi"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/freq_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings__udm__dialog__freq_label"
app:boxBackgroundMode="outline"
app:boxBackgroundColor="?android:windowBackground"
app:boxStrokeColor="?colorAccent"
app:boxStrokeErrorColor="?colorError"
app:boxStrokeWidth="1dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/freq"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textFilter"
android:imeOptions="flagNoExtractUi"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/shortcut_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings__udm__dialog__shortcut_label"
app:boxBackgroundMode="outline"
app:boxBackgroundColor="?android:windowBackground"
app:boxStrokeColor="?colorAccent"
app:boxStrokeErrorColor="?colorError"
app:boxStrokeWidth="1dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/shortcut"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textFilter"
android:imeOptions="flagNoExtractUi"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/locale_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings__udm__dialog__locale_label"
app:boxBackgroundMode="outline"
app:boxBackgroundColor="?android:windowBackground"
app:boxStrokeColor="?colorAccent"
app:boxStrokeErrorColor="?colorError"
app:boxStrokeWidth="1dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/locale"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textFilter"
android:imeOptions="flagNoExtractUi"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:icon="@drawable/ic_more_vert"
android:title="@string/settings__menu"
app:showAsAction="always">
<menu>
<item
android:id="@+id/udm__import"
android:orderInCategory="1"
android:title="@string/assets__action__import"/>
<item
android:id="@+id/udm__export"
android:orderInCategory="2"
android:title="@string/assets__action__export"/>
<item
android:id="@+id/udm__open_system_manager_ui"
android:orderInCategory="3"
android:title="@string/settings__udm__open_system_manager_ui"/>
</menu>
</item>
</menu>

View File

@@ -52,7 +52,7 @@
<string name="settings__home__title" comment="Title of the Home fragment">مرحبا بكم في %s</string>
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">لم يتم تمكين FlorisBoard في النظام وبالتالي لن يكون متاحًا كطريقة إدخال في منتقي الإدخال. انقر هنا لحل هذه المشكلة.</string>
<string name="settings__home__ime_not_selected" comment="Warning message shown in Home fragment when FlorisBoard is not selected as the default keyboard">لم يتم اختيار FlorisBoard كطريقة الإدخال الافتراضية. انقر هنا لحل هذه المشكلة.</string>
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">شكرا على تجربة FlorisBoard! هذا المشروع لا يزال في المرحلة ألفا وبالتالي يفتقد الميزات. إذا وجدت أي أخطاء أو تريد تقديم اقتراح ، فالرجاء مراجعة المخزن على GitHub وطرح مشكلة. هذا يساعد في جعل FlorisBoard أفضل. شكرا جزيلا!</string>
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">شكرا على تجربة FlorisBoard! هذا المشروع لا يزال في قيد التطوير وبالتالي يفتقد بعض الميزات. إذا وجدت أي أخطاء أو تريد تقديم اقتراح ، فالرجاء مراجعة المشروع على GitHub وطرح مشكلتك. هذا يساعد في جعل FlorisBoard أفضل. شكرا جزيلا!</string>
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">اللغات وتخطيطات لوحة المفاتيح</string>
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">يبدو أنك لم تقم بإختيار أية أنواع فرعية. كبديل، سيتم استخدام النوع الفرعي English / QWERTY!</string>
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">إضافة</string>
@@ -210,6 +210,15 @@
<string name="pref__smartbar__enabled__label" comment="Preference title">تفعيل الشريط الذكي</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">سوف يظهر أعلى لوحة المفاتيح</string>
<string name="pref__suggestion__title" comment="Preference group title">الإقتراحات</string>
<string name="pref__dictionary__title" comment="Preference group title">القاموس</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">تفعيل قاموس مستخدم النظام</string>
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">اقترح كلمات مخزنة في قاموس مستخدم النظام</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">إدارة قاموس مستخدم النظام</string>
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">إضافة وعرض وإزالة الإدخالات لقاموس مستخدم النظام</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">تفعيل القاموس الداخلي للمستخدم</string>
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">اقترح كلمات مخزنة في القاموس الداخلي للمستخدم</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">إدارة قاموس الداخلي للمستخدم</string>
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">إضافة وعرض وإزالة الإدخالات لقاموس المستخدم الداخلي</string>
<string name="pref__correction__title" comment="Preference group title">الإصلاحات</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">استخدام الأحرف الكبيرة تلقائيًا</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">استخدام الأحرف الكبيرة في الكلمات على حسب سياق نص الإدخال الحالي</string>
@@ -217,6 +226,26 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">يضل وضع الأحرف الكبيرة قيد التشغيل عند الإنتقال إلى حقل نصي آخر</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">نقطة المسافة المزدوجة</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">الضغط مرتين على مفتاح المسافة يضيف نقطة متبوعة بمسافة</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">القاموس الداخلي للمستخدم</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">قاموس مستخدم النظام</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">التكرار: %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">التردد: %d | الإختصار: %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">لجميع اللغات</string>
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">افتح واجهة نظام ادارة المستخدم</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">تم استيراد قاموس المستخدم بنجاح!</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">تم تصدير قاموس المستخدم بنجاح!</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">أضف كلمة إدخال</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">تعديل كلمة إدخال</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">كلمة</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">الرجاء إدخال كلمة!</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">تحتوي هذه الكلمة على أحرف غير صالحة.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">التكرار (من %d الى %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">الرجاء ادخال قيمة معدل التكرار!</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">الرجاء إدخال رقم صحيح ضمن الحدود المذكورة!</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">الاختصار (اختياري)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">يحتوي هذه الاختصار على أحرف غير صالحة.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">رمز اللغة(اختياري)</string>
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">رمز اللغة هذا لا يتوافق مع بناء الجملة المتوقع. يجب أن يكون الرمز إما لغة فقط (مثل en) أو لغة وبلد (مثل en_US) أو لغة وبلد ونص (مثل en_US-script).</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">الإيماءات و الكتابة بالتمرير</string>
<string name="pref__glide__title" comment="Preference group title">الكتابة بالتمرير</string>
<string name="pref__glide__enabled__label" comment="Preference title">تفعيل الكتابة بالتمرير</string>
@@ -278,6 +307,12 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">إظهار أيقونة البرنامج في صفحة الهاتف الرئيسية</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">فرض الوضع الخاص</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">سيتم تعطيل أي ميزات تعمل مؤقتًا باستعمال بيانات الإدخال الخاصة بك</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">تفعيل أدوات المطور</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">أدوات مصممة خصيصًا لتصحيح الأخطاء واستكشاف العلل وإصلاحها</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">إظهار إحصائيات كومة الذاكرة</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">يعرض طبقة تراكبية في الزاوية العلوية اليمنى لاستخدام ذاكرة الكومة والحجم الأقصى لها</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">مسح قاعدة بيانات القاموس الداخلي للمستخدم</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">يمسح كل الكلمات من جدول قاعدة بيانات القاموس</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">حول التطبيق</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">أيقونة التطبيق FlorisBoard</string>
@@ -297,6 +332,7 @@
<string name="assets__file__name">الاسم</string>
<string name="assets__file__source">المصدر</string>
<string name="assets__action__add">إضافة</string>
<string name="assets__action__apply">تطبيق</string>
<string name="assets__action__cancel">إلغاء</string>
<string name="assets__action__cancel_confirm_title">تأكيد الإلغاء</string>
<string name="assets__action__cancel_confirm_message">هل أنت متأكد أنك تريد تجاهل أي تغييرات غير محفوظة؟ لا يمكن التراجع عن هذا الإجراء بمجرد تنفيذه.</string>
@@ -338,7 +374,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">تقرير حول خطأ في FlorisBoard</string>
<string name="crash_dialog__description" comment="Description of crash dialog">نأسف للإزعاج ، ولكن FlorisBoard تعطل بسبب خطأ غير متوقع.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">إذا كنت ترغب في الإبلاغ عن هذا الخطأ ، فتحقق أولاً من أداة تعقب المشكلات على GitHub إذا لم يتم الإبلاغ عن العطل بالفعل. \n إذا لم يتم الإبلاغ عن هذا الخطأ ، فقم بنسخ سجل التعطل الذي تم إنشاؤه وافتح مشكلة جديدة. استخدم القالب واملأ الوصف وخطوات إعادة إنتاج ولصق سجل العطل الذي تم إنشاؤه في النهاية. هذا يساعد\"%s\" في جعل FlorisBoard أفضل وأكثر استقرارًا للجميع. شكرا لك!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">نسخ إلى الحافظة</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">نسخ الى حافظة النظام</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">تم النسخ الى حافظة النظام</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">لا يمكن النسخ إلى حافظة النظام: لم يتم العثور على جلسة مدير الحافظة</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">فتح نموذج تقرير الخطأ (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">إغلاق</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">تقارير حول خطأ في FlorisBoard</string>

View File

@@ -155,15 +155,15 @@
<string name="settings__keyboard__title" comment="Title of Keyboard preferences fragment">Настройки на клавиатурата</string>
<string name="pref__keyboard__group_keys__label" comment="Preference group title">Клавиши</string>
<string name="pref__keyboard__number_row__label" comment="Preference title">Ред с цифрите</string>
<string name="pref__keyboard__number_row__summary" comment="Preference summary">Показване на ред с цифрите над първия ред</string>
<string name="pref__keyboard__number_row__summary" comment="Preference summary">Показва се ред с цифрите над първия ред</string>
<string name="pref__keyboard__hinted_number_row_mode__label" comment="Preference title">Ред с цифрите в подсказка</string>
<string name="pref__keyboard__hinted_symbols_mode__label" comment="Preference title">Символи в подсказка</string>
<string name="pref__keyboard__hint_mode__disabled" comment="Preference value">Изключено</string>
<string name="pref__keyboard__hint_mode__enabled_hint_priority" comment="Preference value">Включено (подсказка с приоритет)</string>
<string name="pref__keyboard__hint_mode__enabled_accent_priority" comment="Preference value">Включено (акцент с приоритет)</string>
<string name="pref__keyboard__hint_mode__enabled_smart_priority" comment="Preference value">Включено (интелигентен приоритет)</string>
<string name="pref__keyboard__utility_key_enabled__label" comment="Preference title">Показване на помощен клавиш</string>
<string name="pref__keyboard__utility_key_enabled__summary" comment="Preference summary">Показване на помощен клавиш до клавиша за интервал</string>
<string name="pref__keyboard__utility_key_enabled__label" comment="Preference title">Помощен клавиш</string>
<string name="pref__keyboard__utility_key_enabled__summary" comment="Preference summary">Показва се помощен клавиш до клавиша за интервал</string>
<string name="pref__keyboard__utility_key_action__label" comment="Preference title">Действие на помощния клавиш</string>
<string name="pref__keyboard__utility_key_action__switch_to_emojis" comment="Preference value">Превключва емоджи</string>
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Превключва езика</string>
@@ -178,9 +178,9 @@
<string name="pref__keyboard__one_handed_mode__left" comment="Preference value">За лява ръка</string>
<string name="pref__keyboard__one_handed_mode_scale_factor__label" comment="Preference title">Ширина на клавиатурата при работа с една ръка</string>
<string name="pref__keyboard__landscape_input_ui_mode__label" comment="Preference value">Клавиатура на цял екран (пейзаж)</string>
<string name="pref__keyboard__landscape_input_ui_mode__never_show" comment="Preference value">Никога да не се показва</string>
<string name="pref__keyboard__landscape_input_ui_mode__always_show" comment="Preference value">Винаги да се показва</string>
<string name="pref__keyboard__landscape_input_ui_mode__dynamically_show" comment="Preference value">Да се показва динамично</string>
<string name="pref__keyboard__landscape_input_ui_mode__never_show" comment="Preference value">Никога не се показва</string>
<string name="pref__keyboard__landscape_input_ui_mode__always_show" comment="Preference value">Винаги се показва</string>
<string name="pref__keyboard__landscape_input_ui_mode__dynamically_show" comment="Preference value">Показва се динамично</string>
<string name="pref__keyboard__height_factor__label" comment="Preference title">Височина на клавиатурата</string>
<string name="pref__keyboard__height_factor__extra_short" comment="Preference value">Много ниска</string>
<string name="pref__keyboard__height_factor__short" comment="Preference value">Ниска</string>
@@ -202,14 +202,19 @@
<string name="pref__keyboard__vibration_duration__label" comment="Preference title">Продължителност на вибрацията при докосване на клавиш</string>
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">Сила на вибрацията при докосване на клавиш</string>
<string name="pref__keyboard__popup_visible__label" comment="Preference title">Изскачащ прозорец</string>
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Показване на изскачащ прозорец при докосване на клавиш</string>
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Показва се изскачащ прозорец при докосване на клавиш</string>
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Закъснение при задържане на клавиш</string>
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Клавишът интервал превключва към букви</string>
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Клавишът за интервал превключва към букви ако се въвеждат символи или цифри</string>
<string name="settings__typing__title" comment="Title of Typing experience fragment">Изживяване при въвеждане</string>
<string name="pref__smartbar__enabled__label" comment="Preference title">Интелигентна лента</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Показване над първия ред</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Показва се над първия ред</string>
<string name="pref__suggestion__title" comment="Preference group title">Предложения</string>
<string name="pref__dictionary__title" comment="Preference group title">Речник</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Използване на системния потребителски речник</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Управление на системния потребителски речник</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Използване на вътрешния потребителски речник</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Управление на вътрешния потребителски речник</string>
<string name="pref__correction__title" comment="Preference group title">Корекции</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Автоматични главни букви</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Думите се изписват с главни букви въз основа на текущия контекст</string>
@@ -217,12 +222,30 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Клавишът Caps Lock да остане включен при преместване в друго текстово поле</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">Точка при двоен интервал</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Двукратното докосване на интервала вмъква точка, последвана от интервал</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Вътрешен потребителски речник</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Системен потребителски речник</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Честота: %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Честота: %d | ключова дума: %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">За всички езици</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Потребителският речник е изнесен!</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Потребителският речник е внесен!</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Добавяне на дума</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Променяне на дума</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Дума</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Въведете дума.</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Думата съдържа недействителни знаци.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Честота (между %d и %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Въведете стойност за честота.</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Въведете стойност в посочения диапазон.</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Ключова дума (по избор)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Ключовата дума съдържа недействителни знаци.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Код на езика (по избор)</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">Жестове и писане чрез плъзгане</string>
<string name="pref__glide__title" comment="Preference group title">Въвеждане чрез плъзгане</string>
<string name="pref__glide__enabled__label" comment="Preference title">Въвеждане чрез плъзгане</string>
<string name="pref__glide__enabled__summary" comment="Preference summary">Въвеждане на дума чрез плъзгане на пръст върху буквите ѝ</string>
<string name="pref__glide__show_trail__label" comment="Preference title">Показване на следа от плъзгане</string>
<string name="pref__glide__show_trail__summary" comment="Preference summary">Ще изчезва след въведена дума</string>
<string name="pref__glide__enabled__summary" comment="Preference summary">Думите се въвеждат чрез плъзгане на пръст върху буквите ѝ</string>
<string name="pref__glide__show_trail__label" comment="Preference title">Следа от плъзгане</string>
<string name="pref__glide__show_trail__summary" comment="Preference summary">Изчезва след въведена дума</string>
<string name="pref__gestures__general_title" comment="Preference group title">Основни жестове</string>
<string name="pref__gestures__space_bar_title" comment="Preference group title">Жестове с клавиша за интервал</string>
<string name="pref__gestures__other_title" comment="Preference group title">Други жестове и прагове на жеста</string>
@@ -232,16 +255,16 @@
<string name="pref__gestures__swipe_action__delete_characters_precisely" comment="Preference value for swipe action">Прецизно изтриване на символи</string>
<string name="pref__gestures__swipe_action__delete_word" comment="Preference value for swipe action">Изтриване на текущата дума</string>
<string name="pref__gestures__swipe_action__delete_words_precisely" comment="Preference value for swipe action">Прецизно изтриване на думи</string>
<string name="pref__gestures__swipe_action__hide_keyboard" comment="Preference value for swipe action">Скриване на клавиатурата</string>
<string name="pref__gestures__swipe_action__hide_keyboard" comment="Preference value for swipe action">Клавиатурата се скрива</string>
<string name="pref__gestures__swipe_action__insert_space" comment="Preference value for swipe action">Вмъкване на интервал</string>
<string name="pref__gestures__swipe_action__move_cursor_up" comment="Preference value for swipe action">Преместване курсора нагоре</string>
<string name="pref__gestures__swipe_action__move_cursor_down" comment="Preference value for swipe action">Преместване курсора надолу</string>
<string name="pref__gestures__swipe_action__move_cursor_left" comment="Preference value for swipe action">Преместване курсора наляво</string>
<string name="pref__gestures__swipe_action__move_cursor_right" comment="Preference value for swipe action">Преместване курсора надясно</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_line" comment="Preference value for swipe action">Преместване курсора в началото на реда</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_line" comment="Preference value for swipe action">Преместване курсора в края на реда</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_page" comment="Preference value for swipe action">Преместване курсора в началото на страницата</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_page" comment="Preference value for swipe action">Преместване курсора в края на страницата</string>
<string name="pref__gestures__swipe_action__move_cursor_up" comment="Preference value for swipe action">Курсорът се премества нагоре</string>
<string name="pref__gestures__swipe_action__move_cursor_down" comment="Preference value for swipe action">Курсорът се премества надолу</string>
<string name="pref__gestures__swipe_action__move_cursor_left" comment="Preference value for swipe action">Курсорът се премества наляво</string>
<string name="pref__gestures__swipe_action__move_cursor_right" comment="Preference value for swipe action">Курсорът се премества надясно</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_line" comment="Preference value for swipe action">Курсорът се премества в началото на реда</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_line" comment="Preference value for swipe action">Курсорът се премества в края на реда</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_page" comment="Preference value for swipe action">Курсорът се премества в началото на страницата</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_page" comment="Preference value for swipe action">Курсорът се премества в края на страницата</string>
<string name="pref__gestures__swipe_action__switch_to_clipboard_context" comment="Preference value for swipe action">Съдържание на буфера</string>
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">Shift</string>
<string name="pref__gestures__swipe_action__redo" comment="Preference value for swipe action">Възстановяване</string>
@@ -277,7 +300,13 @@
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Тъмна</string>
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Показване на иконата на приложението в стартовия панел</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Принуждаване на поверителен режим</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Изключване на всички функции, които временно работят с вашите входни данни</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Изключват се всички функции, които временно работят с вашите входни данни</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Включване на развойни инструменти</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Инструменти, в помощ при за отстраняване на дефекти</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Статистика за динамичната памет</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Показва се информация за динамичната памет в горния десен ъгъл</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Изтриване на данните за вътрешния речник</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Премахва всички думи от таблицата с речника</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Относно</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Икона на приложението FlorisBoard</string>
@@ -293,6 +322,7 @@
<string name="assets__file__name">Име</string>
<string name="assets__file__source">Източник</string>
<string name="assets__action__add">Добавяне</string>
<string name="assets__action__apply">Прилагане</string>
<string name="assets__action__cancel">Отказ</string>
<string name="assets__action__cancel_confirm_title">Потвърждаване на отказ</string>
<string name="assets__action__cancel_confirm_message">Наистина ли искате да отхвърлите всички незапазени промени? Това действие веднъж изпълнено не може да бъде отменено.</string>
@@ -334,7 +364,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">Отчет за грешка на FlorisBoard</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Извинявайте за неудобството, но поради непредвидена грешка FlorisBoard се срина.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">При желание от ваша страна да съобщите за възникналата грешка първо проверете системата за дефекти в GitHub дали подобна вече не е докладвана.\nАко не е, копирайте генерирания журнал на грешката и направете нов долкад за нея, използвайки шаблона \"%s\". Попълнете описанието, стъпките за възпроизвеждане и накрая поставете копирания журнал.\nПо този начин помагате да направим приложението FlorisBoard по-стабилно за всички. Благодарим ви!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Копиране в буфера</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Копиране в системната междинна памет</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Копирано в системната междинна памет</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Грешка при копиране в системната междинна памет</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Система за дефекти (github)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Затваряне</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Доклади за грешки на FlorisBoard</string>
@@ -353,13 +385,13 @@
<string name="clip__cant_paste">Приложението не поддържа поставяне на такова съдържание.</string>
<string name="pref__clipboard__clipboard_category__label">Междинна памет</string>
<string name="pref__clipboard__use_internal_clipboard___label">Междинна памет</string>
<string name="pref__clipboard__use_internal_clipboard_title__summary">Използване на междинната памет на приложението вместо системната</string>
<string name="pref__clipboard__use_internal_clipboard_title__summary">Използва се междинната памет на приложението вместо системната</string>
<string name="pref__clipboard__sync_from_system_clipboard__label">Внасяне от системната междинна памет</string>
<string name="pref__clipboard__keyboard_sync_from_system_clipboard__summary">Промените в системната памет се отразяват в тази на приложението</string>
<string name="pref__clipboard__sync_to_system_clipboard__label">Изнасяне в системната междинна памет</string>
<string name="pref__clipboard__sync_to_system_clipboard__summary">Промените в междинната памет на приложението се отразяват в системната</string>
<string name="pref__clipboard__enable_clipboard_history__label">Пазене на история</string>
<string name="pref__clipboard__enable_clipboard_history__summary">Запазване на елементите от междинната памет</string>
<string name="pref__clipboard__enable_clipboard_history__label">История</string>
<string name="pref__clipboard__enable_clipboard_history__summary">Запазват се елементите от междинната памет</string>
<string name="pref__clipboard__clean_up_old_clipboard_items__label">Премахване на стари елементи</string>
<string name="pref__clipboard__clean_up_old_after__label">Премахване на старите елементи след</string>
<string name="pref__unit_minutes">" минути"</string>

View File

@@ -180,7 +180,6 @@
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">Omogući FlorisBoard</string>
<string name="setup__make_default__text_switch_button" comment="Label of switch button in Make IME default fragment">Promijeni tastaturu</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopiraj u međuspremnik</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Zatvori</string>
<!-- clipboard strings -->
<!-- glide strings -->

View File

@@ -334,7 +334,6 @@
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBorad ڕاپۆرتی هەڵەی</string>
<string name="crash_dialog__description" comment="Description of crash dialog">بەداخەوە، بەهۆی هەڵەیەکی لەناکاو تەختەکلیلی FlorisBoard لەکارکەوت.</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">لەبەرگرتنەوە</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">داخستن</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBorad ڕاپۆرتی هەڵەی</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">تەختەکلیلی FlorisBoard لە کارکەوت…</string>

View File

@@ -328,7 +328,6 @@
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Nastavení dokončeno!</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">Zpráva o chybě florisboardu</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopírovat do schránky</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Uzavřít</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard chybové hlášení</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard přestal fungovat…</string>

View File

@@ -278,6 +278,7 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Zeige die App in der Übersicht</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Privaten Modus erzwingen</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Wird alle Funktionen deaktivieren, die vorübergehend mit deinen Eingabedaten arbeiten</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Entwickler-Werkzeuge ein/aus</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Über</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">App-Icon von FlorisBoard</string>
@@ -334,7 +335,8 @@
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard Fehlermeldung</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Entschuldigung für die Unannehmlichkeiten, aber FlorisBoard ist aufgrund eines unerwarteten Fehlers abgestürzt.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Wenn sie diesen Fehler melden möchten, checken sie als erstes die Issue Tracker bei Github, ob der Fehler noch nicht gemeldet wurde. \nlf Wenn nicht, dann kopieren sie den generierten Fehler-log und erstellen sie einen neuen Issue. Benutzen sie diese \"%s\" Vorlage und füllen sie die Beschreibung aus, die Schritte zur Reproduzieren und fügen sie die generierte Fehler-log am Ende ein. Dies hilft FlorisBoard besser und noch Stabiler zu machen für alle. Dankeschön!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">In die Zwischenablage kopieren</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">In die System-Zwischenablage kopieren</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">In die System-Zwischenablage kopiert</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Öffne \"Issue Tracker\" (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Schließen</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard Fehlermeldungen</string>

View File

@@ -314,7 +314,6 @@
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Η ρύθμιση ολοκληρώθηκε!</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">Αναφορά σφάλματος FlorisBoard</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Αντιγραφή στο πρόχειρο</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Κλείσιμο</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Αναφορές σφάλματος FlorisBoard</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">Το FlorisBoard έχει σταματήσει να λειτουργεί…</string>

View File

@@ -160,7 +160,6 @@
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Malfermi Lingvajn &amp; Enigajn agordojn</string>
<string name="setup__make_default__text_switch_button" comment="Label of switch button in Make IME default fragment">Ŝalti klavaro</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopii al tondejo</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Fermi</string>
<!-- clipboard strings -->
<!-- glide strings -->

View File

@@ -278,6 +278,10 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Mostrar icono de la aplicación en el launcher</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Forzar modo privado</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Desactivará cualquier característica que tenga que trabajar temporalmente con sus datos de entrada</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Habilitar herramienta de desarrollador</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Herramientas diseñadas específicamente para depurar y detectar problemas</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Mostrar estadísticas de memoria</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Sobreponer el uso de la memoria y el tamaño maximo en la esquina superior derecha</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Acerca de</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Icono de la aplicación de FlorisBoard</string>
@@ -334,7 +338,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">Informe de errores de FlorisBoard</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Lamentamos los inconvenientes, pero FlorisBoard se ha cerrado inesperadamente por un error.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Si deseas reportar este fallo, primero comprueba si ya ha sido reportado en el Issue Tracker en GitHub.\nSi no, copie el log del error generado y abre un Issue. Usa la plantilla \"%s\" y rellenala con tu descripción, los pasos para reproducirlo, y pega el log generado al final. Esto ayudará a mejorar FlorisBoard y hacerlo más estable para todo el mundo. ¡Gracias!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copiar al portapapeles</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copiar al portapapeles del sistema</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Copiado al portapapeles del sistema</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">No se puede copiar al portapapeles del sistema: Instancia del administrador de portapapeles no encontrada</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Abrir issue tracker (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Cerrar</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Informes de errores de FlorisBoard</string>

View File

@@ -210,6 +210,15 @@
<string name="pref__smartbar__enabled__label" comment="Preference title">فعال سازی نوار هوشمند</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">بالای صفحه‌کلید نمایش داده خواهند شد</string>
<string name="pref__suggestion__title" comment="Preference group title">پیشنهادات</string>
<string name="pref__dictionary__title" comment="Preference group title">لغتنامه</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">فعالسازی سیستم لغتنامه کاربر</string>
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">پیشنهاد دادن کلمات ذخیره شده از سیستم لغتنامه کاربر</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">مدیریت سیستم لغتنامه کاربر</string>
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">افزودن، مشاهده و حذف کردن لغات برای سیستم لغتنامه کاربر</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">فعالسازی لغتنامه کاربر داخلی</string>
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">پیشنهاد دادن کلمات ذخیره شده در لغتنامه داخلی کاربر</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">مدیریت لغتنامه داخلی کاربر</string>
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">افزودن، مشاهده و حذف کردن لغات از لغتنامه داخلی کاربر</string>
<string name="pref__correction__title" comment="Preference group title">تصحیح</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">حرف اول بزرگ به صورت خودکار</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">بزرگ کردن حرف اول بر اساس محتوای ورودی</string>
@@ -217,6 +226,26 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Caps lock با رفتن به محل نوشتن بعدی همانگونه بماند</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">فاصله زمانی دوبار-فاصله</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">فشردن دو بار فاصله در فاصله زمانی باعث ادامه دادن فاصله گذاشتن شود</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">لغتنامه داخلی کاربر</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">لغتنامه سیستم کاربر</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">شدت تکرار: %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">شدت تکرار: %d | اختصار: %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">برای همه زبان ها</string>
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">بازکردن مدیریت سیستم رابط کاربری</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">لغتنامه کاربر با موفقیت وارد شد!</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">لغتنامه کاربر با موفقیت استخراج شد!</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">افزودن لغت</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">ویرایش لغت</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">کلمه</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">لطفا یک کلمه وارد کنید!</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">این کلمه دارای کاراکتر های غیرمجاز می‌باشد.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">شدت تکرار (بین %d و %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">لطفا یک مقدار شدت تکرار وارد کنید!</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">لطفا یک مقدار عددی معتبر بین دامنه مقادیر مشخص شده وارد کنید!</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">اختصار(اختیاری)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">این اختصار شامل حروف غیرمجاز میباشد.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">کد زبان (اختیاری)</string>
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">این کد زبان داده شده از حالت پیشبینی شده پشتیبانی نمی کند. کد داده شده یا باید فقط زبان باشد (مثل fa) یا یک زبان و کشور باشد(مثل fa_IR) یا یک زبان و کشور به همراه اسکریپت باشد (fa_IR-script).</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">اشارات &amp; نوشتن گلایدی</string>
<string name="pref__glide__title" comment="Preference group title">نوشتن گلایدی</string>
<string name="pref__glide__enabled__label" comment="Preference title">فعال سازی دنباله نویسی</string>
@@ -278,6 +307,12 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">قرار دادن آیکون برنامه در لانچر</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">حالت خصوصی اجباری</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">هر ویژگی که که باید با ورودی شما کار کنند را موقتا غیرفعال می کند</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">فعال‌سازی ابزار های توسعه دهنده</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">ابزار هایی که مخصوصا برای اشکال‌یابی و اشکال‌زدایی استفاده می‌شود</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">نمایش وضعیت حافظه هیپ</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">استفاده از حافظه هیپ را رونویسی می کند و اندازه را حداکثری می کند در گوشه بالا سمت راست</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">پاک کردن پایگاه داده لغتنامه داخلی کاربر</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">پاک کردن تمام کلمات از جدول پایگاه داده لغتنامه</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">درباره</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">آیکون برنامه فلوریس بورد</string>
@@ -293,6 +328,7 @@
<string name="assets__file__name">نام</string>
<string name="assets__file__source">منبع</string>
<string name="assets__action__add">اضافه کردن</string>
<string name="assets__action__apply">اعمال</string>
<string name="assets__action__cancel">لغو</string>
<string name="assets__action__cancel_confirm_title">تائید لغو</string>
<string name="assets__action__cancel_confirm_message">آیا مطمئنید که می خواهید هر یک از تغییرات ذخیره نشده را لغو کنید؟ این عمل در صورت اجرا غیرقابل بازگشت خواهد بود.</string>
@@ -334,7 +370,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">گزارش خطای فلوریس بورد</string>
<string name="crash_dialog__description" comment="Description of crash dialog">با عرض پوزش برای این مشکل به وجود آمده، اما فلوریس بورد به یک دلیل پیش‌بینی نشده، متوقف شد.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">اگر شما دوست دارید که این خطا را گزارش کنید، لطفا اول به صفحه ایرادات ردیابی شده را در گیت هاب ما سری بزنید و از اینکه این گزارش از قبل ارائه نشده مطمعن شوید.\nاگر که گزارشی نبود، لاگ تولید شده پس از متوقف شدن را کپی کنید و یک گزارش ایراد جدید را بسازید. از نمونه \"%s\" استفاده کنید و توضیحات، مراحل بازتولید، و لاگ تولید شده پس از متوقف شدن را در نهایت درج کنید. این به فلوریس بورد کمک شایانی در بهتر شدن و پایدار تر شدن آن برای همه می کند. بسیار از شما متشکریم!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">کپی در حافطه موقت</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">کپی به کلیپبورد سیستم</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">به کلیپبورد سیستم کپی شد</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">نمی توان به کلیپبورد سیستم کپی کرد: نمایه مدیرت کلیپبورد یافت نشد</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">بازکردن ایرادات ردیابی شده (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">بستن</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">گزارش های خطای فلوریس بورد</string>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">FlorisBoard</string>
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Tauko</string>
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Odota</string>
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Kolmen pisteen kuvake. Se tarkoittaa, että lisää kirjaimia on saatavilla pitkällä painalluksella.</string>
@@ -8,6 +9,7 @@
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Siirrä näppäimistö vasemmalle.</string>
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Siirrä näppäimistö oikealle.</string>
<!-- Private mode info dialog strings -->
<string name="private_mode_dialog__title" comment="Title of the private mode dialog">Yksityinen tila</string>
<!--
<string name="private_mode_dialog__text" comment="Text of the private mode dialog">The icon you just clicked at indicates that FlorisBoard works in the private mode. This means that all features which require to process and temporarily save your input stop working. This applies at minimum to the following features (if they\'ve been turned on previously):\n\n - Next word algorithm adjustments\n\n - Clipboard paste suggestions\n\n - Clipboard history\n\nFlorisBoard enters this mode either if an app requests it or if it was specifically enabled in the advanced settings.</string>
-->
@@ -32,10 +34,14 @@
<string name="smartbar__quick_action__open_settings" comment="Content-description for the settings quick action in Smartbar">Avaa asetukset.</string>
<string name="smartbar__quick_action__switch_to_editing_context" comment="Content-description for the editing quick action in Smartbar">Näytä tekstinmuokkauspaneeli.</string>
<string name="smartbar__quick_action__switch_to_media_context" comment="Content-description for the media quick action in Smartbar">Vaihda mediansyöttönäkymään.</string>
<string name="smartbar__quick_action__undo" comment="Content-description for the undo quick action in Smartbar">Kumoa viimeisin toiminto</string>
<string name="smartbar__quick_action__redo" comment="Content-description for the redo quick action in Smartbar">Tee kumottu toiminto uudelleen</string>
<string name="smartbar__quick_action__private_mode" comment="Content-description for the private mode button in Smartbar">Yksityistila on aktiivinen, jos tämä on näkyvillä. Klikatessa näytetään tietoa yksityistilasta.</string>
<!-- Settings UI strings -->
<string name="settings__title" comment="Title of Settings">Asetukset</string>
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">Lisää asetuksia</string>
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Ohjeet &amp; palaute</string>
<string name="settings__help" comment="General label for help buttons in Settings">Ohje</string>
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">Etusivu</string>
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">Näppäimistö</string>
<string name="settings__navigation__typing" comment="Long-press hint of bottom nav item Typing in Settings">Kirjoittaminen</string>
@@ -56,16 +62,90 @@
<string name="settings__localization__subtype_delete" comment="Subtype dialog delete button">Poista</string>
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Muokkaa asettelua</string>
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Kielialue</string>
<string name="settings__localization__subtype_characters_layout" comment="Label for layout dropdown in subtype dialog">Kirjainten asettelu</string>
<string name="settings__localization__subtype_currency_set" comment="Label for currency set dropdown in subtype dialog. 'set' is used as a noun here and can be compared to a group of elements (in this case currency symbols).">Valuutta</string>
<string name="settings__localization__subtype_numeric_layout" comment="Label for layout dropdown in subtype dialog">Numeroasettelu</string>
<string name="settings__localization__subtype_numeric_row_layout" comment="Label for layout dropdown in subtype dialog">Numerorivin asettelu</string>
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">Tämä asettelu on jo lisätty!</string>
<string name="settings__theme__title" comment="Title of the Theme fragment">Näppäimistön teema</string>
<string name="settings__theme__undefined" comment="General string for an undefined preference value">Ei määritelty</string>
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">Teema</string>
<string name="pref__theme__mode__always_day" comment="Preference value for theme mode">Aina päivä</string>
<string name="pref__theme__mode__always_night" comment="Preference value for theme mode">Aina yö</string>
<string name="pref__theme__mode__follow_system" comment="Preference value for theme mode">Noudata järjestelmän teemaa</string>
<string name="pref__theme__mode__follow_time" comment="Preference value for theme mode">Vaihda kellonajan mukaan</string>
<string name="pref__theme__sunrise_time__label" comment="Label of the sunrise time preference">Aurinko nousee</string>
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Aurinko laskee</string>
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Päiväteema</string>
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Yöteema</string>
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Valittu teema</string>
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Mukauta sovelluksen väreihin</string>
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Teeman värit mukautetaan avoinna olevan sovelluksen mukaan, mikäli sovellus tukee ominaisuutta.</string>
<string name="pref__theme__source_assets" comment="Label for the theme source field">FlorisBoard-sovellus</string>
<string name="pref__theme__source_internal" comment="Label for the theme source field">Sisäinen tallennustila</string>
<string name="pref__theme__source_external" comment="Label for the theme source field">Ulkoinen lähde</string>
<string name="settings__theme_manager__title_day" comment="Title of the theme manager activity for day theme">Teeman hallinta (päivä)</string>
<string name="settings__theme_manager__title_night" comment="Title of the theme manager activity for night theme">Teeman hallinta (yö)</string>
<string name="settings__theme_manager__create_empty" comment="Label of the Create empty FAB action">Luo uusi teema</string>
<string name="settings__theme_manager__create_from_selected" comment="Label of the Create from selected FAB action">Luo valitun teeman pohjalta</string>
<string name="settings__theme_manager__theme_custom_title" comment="Title template for a custom theme">Mukautettu (perustuu teemaan %s)</string>
<string name="settings__theme_manager__theme_new_title" comment="Title template for a new theme">Uusi teema</string>
<string name="settings__theme_manager__theme_import_success" comment="Message for theme import success">Teema tuotu onnistuneesti!</string>
<string name="settings__theme_manager__theme_export_success" comment="Message for theme export success">Teema viety onnistuneesti!</string>
<string name="settings__theme_editor__title" comment="Title of the edit theme activity">Muokkaa teemaa</string>
<string name="settings__theme_editor__name_label" comment="Label of name input">Nimi</string>
<string name="settings__theme_editor__type_label" comment="Label of type input">Tyyppi</string>
<string name="settings__theme_editor__add_group_dialog_title" comment="Title of the add group dialog in the theme editor">Lisää ryhmä</string>
<string name="settings__theme_editor__edit_group_dialog_title" comment="Title of the edit group dialog in the theme editor">Muokkaa ryhmää</string>
<string name="settings__theme_editor__add_attr_dialog_title" comment="Title of the add attribute dialog in the theme editor">Lisää määrite</string>
<string name="settings__theme_editor__edit_attr_dialog_title" comment="Title of the edit attribute dialog in the theme editor">Muokkaa määritettä</string>
<string name="settings__theme_editor__edit_theme_name_dialog_title" comment="Title of the edit theme name dialog in the theme editor">Muokkaa teeman nimeä</string>
<string name="settings__theme_editor__value_type_reference" comment="Theme value type">Referenssi</string>
<string name="settings__theme_editor__value_type_reference_group" comment="Theme value type sub-field">Ryhmä</string>
<string name="settings__theme_editor__value_type_reference_attr" comment="Theme value type sub-field">Määrite</string>
<string name="settings__theme_editor__value_type_solid_color" comment="Theme value type">Tasainen väri</string>
<string name="settings__theme_editor__value_type_lin_grad" comment="Theme value type">Lineaarinen liukuväri</string>
<string name="settings__theme_editor__value_type_rad_grad" comment="Theme value type">Säteittäinen liukuväri</string>
<string name="settings__theme_editor__value_type_on_off" comment="Theme value type">Kytkin</string>
<string name="settings__theme_editor__value_type_on_off_state" comment="Theme value type sub-field">Tila</string>
<string name="settings__theme_editor__value_type_other" comment="Theme value type">Muu</string>
<string name="settings__theme_editor__value_type_other_text" comment="Theme value type sub-field">Teksti</string>
<string name="settings__theme_editor__error_theme_label_empty" comment="Error text for an empty theme label">Syötä teeman nimi.</string>
<string name="settings__theme_editor__error_group_name" comment="Error text for an invalid group name">Syötä ryhmälle nimi, joka sisältää vain kirjaimia (az ja/tai AZ), kaksoispisteitä (:) alaryhmitystä varten tai numeroita (09), tilde-merkkejä (~) tai alaviivoja (_).</string>
<string name="settings__theme_editor__error_group_name_empty" comment="Error text for an empty group name">Syötä ryhmän nimi.</string>
<string name="settings__theme_editor__error_group_name_already_exists" comment="Error text for a duplicate group name">Tässä teemassa on jo tämän niminen ryhmä. Valitse toinen nimi.</string>
<string name="settings__theme_editor__error_attr_name" comment="Error text for an invalid attribute name">Syötä määritteelle nimi, jossa on vain kirjaimia a-z ja/tai A-Z.</string>
<string name="settings__theme_editor__error_attr_name_empty" comment="Error text for an empty attribute name">Syötä määritteen nimi.</string>
<string name="settings__theme_editor__error_attr_name_already_exists" comment="Error text for a duplicate attribute name">Tässä ryhmässä on jo tämän niminen määre. Valitse toinen nimi.</string>
<string name="settings__theme__group_window" comment="Theme group label">Ikkuna &amp; järjestelmä</string>
<string name="settings__theme__group_keyboard" comment="Theme group label">Näppäimistö</string>
<string name="settings__theme__group_key" comment="Theme group label">Painike</string>
<string name="settings__theme__group_key_specific" comment="Theme group label (%s is specific modifier)">Näppäin (%s)</string>
<string name="settings__theme__group_media" comment="Theme group label">Mediakonteksti</string>
<string name="settings__theme__group_oneHanded" comment="Theme group label">Yksikätinen</string>
<string name="settings__theme__group_privateMode" comment="Theme group label">Yksityinen tila</string>
<string name="settings__theme__group_smartbar" comment="Theme group label">Älypalkki</string>
<string name="settings__theme__group_smartbarButton" comment="Theme group label">Älypalkin painike</string>
<string name="settings__theme__attr_background" comment="Theme attribute label">Taustaväri</string>
<string name="settings__theme__attr_backgroundActive" comment="Theme attribute label">Taustaväri (aktiivisena)</string>
<string name="settings__theme__attr_backgroundPressed" comment="Theme attribute label">Taustaväri (painettuna)</string>
<string name="settings__theme__attr_showBorder" comment="Theme attribute label">Näytä reunukset</string>
<string name="settings__theme__attr_colorPrimary" comment="Theme attribute label">Ensisijainen väri</string>
<string name="settings__theme__attr_colorPrimaryDark" comment="Theme attribute label">Ensisijainen väri (tumma teema)</string>
<string name="settings__theme__attr_colorAccent" comment="Theme attribute label">Korostusväri</string>
<string name="settings__theme__attr_navBarColor" comment="Theme attribute label">Navigointipalkin väri</string>
<string name="settings__theme__attr_textColor" comment="Theme attribute label">Tekstin väri</string>
<string name="settings__keyboard__title" comment="Title of Keyboard preferences fragment">Näppäimistön asetukset</string>
<string name="pref__keyboard__group_keys__label" comment="Preference group title">Painikkeet</string>
<string name="pref__keyboard__number_row__label" comment="Preference title">Numerorivi</string>
<string name="pref__keyboard__hint_mode__disabled" comment="Preference value">Poissa käytöstä</string>
<string name="pref__keyboard__utility_key_enabled__label" comment="Preference title">Näytä toimintopainike</string>
<string name="pref__keyboard__utility_key_enabled__summary" comment="Preference summary">Näyttää muokattavan toimintopainikkeen välilyönnin vieressä</string>
<string name="pref__keyboard__utility_key_action__label" comment="Preference title">Toimintopainikkeen toiminto</string>
<string name="pref__keyboard__utility_key_action__switch_to_emojis" comment="Preference value">Vaihda emojeihin</string>
<string name="pref__keyboard__utility_key_action__switch_language" comment="Preference value">Vaihda kieltä</string>
<string name="pref__keyboard__utility_key_action__switch_keyboard_app" comment="Preference value">Vaihda näppäimistösovellusta</string>
<string name="pref__keyboard__utility_key_action__dynamic_switch_language_emojis" comment="Preference value">Dynaaminen: Vaihda emojeihin / vaihda kieltä</string>
<string name="pref__keyboard__font_size_multiplier_portrait__label" comment="Preference title">Fonttikoon kerroin (pystysuunnassa)</string>
<string name="pref__keyboard__font_size_multiplier_landscape__label" comment="Preference title">Fonttikoon kerroin (vaakasuunnassa)</string>
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Ulkoasu</string>
@@ -73,6 +153,10 @@
<string name="pref__keyboard__one_handed_mode__off" comment="Preference value">Pois päältä</string>
<string name="pref__keyboard__one_handed_mode__right" comment="Preference value">Oikean käden tila</string>
<string name="pref__keyboard__one_handed_mode__left" comment="Preference value">Vasemman käden tila</string>
<string name="pref__keyboard__one_handed_mode_scale_factor__label" comment="Preference title">Yksikätisen tilan näppäimistön leveys</string>
<string name="pref__keyboard__landscape_input_ui_mode__never_show" comment="Preference value">Älä näytä koskaan</string>
<string name="pref__keyboard__landscape_input_ui_mode__always_show" comment="Preference value">Näytä aina</string>
<string name="pref__keyboard__landscape_input_ui_mode__dynamically_show" comment="Preference value">Näytä dynaamisesti</string>
<string name="pref__keyboard__height_factor__label" comment="Preference title">Näppäimistön korkeus</string>
<string name="pref__keyboard__height_factor__extra_short" comment="Preference value">Extra-matala</string>
<string name="pref__keyboard__height_factor__short" comment="Preference value">Matala</string>
@@ -83,15 +167,24 @@
<string name="pref__keyboard__height_factor__extra_tall" comment="Preference value">Erittäin korkea</string>
<string name="pref__keyboard__height_factor__custom" comment="Preference value">Mukautettu</string>
<string name="pref__keyboard__height_factor_custom__label" comment="Preference title">Mukautettu näppäimistön korkeus</string>
<string name="pref__keyboard__bottom_offset_portrait__label" comment="Preference title">Näppäimistön ja ruudun alareunan väli (pystysuunta)</string>
<string name="pref__keyboard__bottom_offset_landscape__label" comment="Preference title">Näppäimistön ja ruudun alareunan väli (vaakataso)</string>
<string name="pref__keyboard__key_spacing_vertical__label" comment="Preference title">Näppäinten väli (pysty)</string>
<string name="pref__keyboard__key_spacing_horizontal__label" comment="Preference title">Näppäinten väli (vaaka)</string>
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Painallus</string>
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">Painikeääni</string>
<string name="pref__keyboard__sound_volume__label" comment="Preference title">Painikeäänen voimakkuus</string>
<string name="pref__keyboard__vibration_enabled__label" comment="Preference title">Värinä painalluksella</string>
<string name="pref__keyboard__vibration_duration__label" comment="Preference title">Värinän pituus painalluksella</string>
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">Värinän voimakkuus painalluksella</string>
<string name="pref__keyboard__popup_visible__label" comment="Preference title">Ponnahdusikkunoiden Näkyvyys</string>
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Näytä ponnahdusikkuna, kun painat näppäintä</string>
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Pitkän painalluksen viive</string>
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Palaa kirjainasetteluun välilyönnillä</string>
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Välilyönnin painaminen symboli- tai numeroasettelussa vaihtaa näppäimistön automaattisesti kirjainasetteluun</string>
<string name="settings__typing__title" comment="Title of Typing experience fragment">Kirjoituskokemus</string>
<string name="pref__smartbar__enabled__label" comment="Preference title">Ota älypalkki käyttöön</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Näkyy näppäimistön yläpuolella</string>
<string name="pref__suggestion__title" comment="Preference group title">Ehdotukset</string>
<string name="pref__correction__title" comment="Preference group title">Korjaukset</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Automaattiset isot kirjaimet</string>
@@ -102,26 +195,44 @@
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Kun välilyöntiä napautetaan kahdesti, lisätään piste ja välilyönti</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">Eleet &amp; liukukirjoitus</string>
<string name="pref__glide__title" comment="Preference group title">Liukukirjoitus</string>
<string name="pref__glide__enabled__label" comment="Preference title">Ota liukukirjoitus käyttöön</string>
<string name="pref__glide__enabled__summary" comment="Preference summary">Syötä sana liu\'uttamalla sormea sen kirjaimilla</string>
<string name="pref__glide__show_trail__label" comment="Preference title">Näytä liu\'un jälki</string>
<string name="pref__glide__show_trail__summary" comment="Preference summary">Häviää joka sanan jälkeen</string>
<string name="pref__gestures__general_title" comment="Preference group title">Yleiset eleet</string>
<string name="pref__gestures__space_bar_title" comment="Preference group title">Välilyönnin eleet</string>
<string name="pref__gestures__swipe_action__no_action" comment="Preference value for swipe action">Ei toimintoa</string>
<string name="pref__gestures__swipe_action__cycle_to_previous_keyboard_mode" comment="Preference value for swipe action">Siirry edelliseen näppäimistötilaan</string>
<string name="pref__gestures__swipe_action__cycle_to_next_keyboard_mode" comment="Preference value for swipe action">Siirry seuraavaan näppäimistötilaan</string>
<string name="pref__gestures__swipe_action__delete_characters_precisely" comment="Preference value for swipe action">Tarkka merkkien poisto</string>
<string name="pref__gestures__swipe_action__delete_word" comment="Preference value for swipe action">Poista nykyinen sana</string>
<string name="pref__gestures__swipe_action__delete_words_precisely" comment="Preference value for swipe action">Poista sanat tarkasti</string>
<string name="pref__gestures__swipe_action__hide_keyboard" comment="Preference value for swipe action">Piilota näppäimistö</string>
<string name="pref__gestures__swipe_action__insert_space" comment="Preference value for swipe action">Lisää välilyönti</string>
<string name="pref__gestures__swipe_action__move_cursor_up" comment="Preference value for swipe action">Siirrä kursori ylös</string>
<string name="pref__gestures__swipe_action__move_cursor_down" comment="Preference value for swipe action">Siirrä kursori alas</string>
<string name="pref__gestures__swipe_action__move_cursor_left" comment="Preference value for swipe action">Siirrä kursori vasemmalle</string>
<string name="pref__gestures__swipe_action__move_cursor_right" comment="Preference value for swipe action">Siirrä kursori oikealle</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_line" comment="Preference value for swipe action">Siirrä osoitin rivin alkuun</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_line" comment="Preference value for swipe action">Siirrä osoitin rivin loppuun</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_page" comment="Preference value for swipe action">Siirrä osoitin sivun alkuun</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_page" comment="Preference value for swipe action">Siirrä osoitin sivun loppuun</string>
<string name="pref__gestures__swipe_action__switch_to_clipboard_context" comment="Preference value for swipe action">Avaa leikepöytämanageri/historia</string>
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">Shift</string>
<string name="pref__gestures__swipe_action__redo" comment="Preference value for swipe action">Tee uudelleen</string>
<string name="pref__gestures__swipe_action__undo" comment="Preference value for swipe action">Kumoa</string>
<string name="pref__gestures__swipe_action__show_input_method_picker" comment="Preference value for swipe action">Näytä syöttötavan valinta</string>
<string name="pref__gestures__swipe_action__switch_to_prev_keyboard" comment="Preference value for swipe action">Vaihda edelliseen näppäimistöön</string>
<string name="pref__gestures__swipe_action__switch_to_prev_subtype" comment="Preference value for swipe action">Vaihda edelliseen asetelmaan</string>
<string name="pref__gestures__swipe_action__switch_to_next_subtype" comment="Preference value for swipe action">Vaihda seuraavaan asetelmaan</string>
<string name="pref__gestures__swipe_up__label" comment="Preference title">Pyyhkäisy ylös</string>
<string name="pref__gestures__swipe_down__label" comment="Preference title">Pyyhkäisy alas</string>
<string name="pref__gestures__swipe_left__label" comment="Preference title">Pyyhkäisy vasemmalle</string>
<string name="pref__gestures__swipe_right__label" comment="Preference title">Pyyhkäisy oikealle</string>
<string name="pref__gestures__space_bar_swipe_up__label" comment="Preference title">Pyyhkäisy ylös välilyöntinäppäimessä</string>
<string name="pref__gestures__space_bar_swipe_left__label" comment="Preference title">Pyyhkäisy vasemmalle välilyöntinäppäimessä</string>
<string name="pref__gestures__space_bar_swipe_right__label" comment="Preference title">Pyyhkäisy oikealle välilyöntinäppäimessä</string>
<string name="pref__gestures__space_bar_long_press__label" comment="Preference title">Pitkä painallus välilyöntinäppäimessä</string>
<string name="pref__gestures__delete_key_swipe_left__label" comment="Preference title">Pyyhkäisy vasemmalle delete-painikkeessa</string>
<string name="pref__gestures__swipe_velocity_threshold__label" comment="Preference title">Pyyhkäisynopeuden kynnys</string>
<string name="pref__gestures__swipe_velocity_threshold__very_slow" comment="Preference value for swipe velocity threshold">Erittäin hidas</string>
@@ -140,6 +251,7 @@
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">Vaalea</string>
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Tumma</string>
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Näytä sovellus sovellusvalikossa</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Pakota yksityinen tila</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Tietoa</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">FlorisBoardin kuvake</string>
@@ -148,6 +260,21 @@
<string name="about__view_source_code" comment="Label of View source code button in About">Lähdekoodi</string>
<string name="about__license__title" comment="Title of Open-source licenses dialog">Avoimen lähdekoodin lisenssit</string>
<!-- Assets strings -->
<string name="assets__file__name">Nimi</string>
<string name="assets__action__add">Lisää</string>
<string name="assets__action__cancel">Peruuta</string>
<string name="assets__action__cancel_confirm_title">Varmista peruutus</string>
<string name="assets__action__cancel_confirm_message">Haluatko varmasti hylätä kaikki tallentamattomat muutokset? Tätä ei voi hylkäämisen jälkeen enää peruuttaa.</string>
<string name="assets__action__delete">Poista</string>
<string name="assets__action__delete_confirm_title">Vahvista poistaminen</string>
<string name="assets__action__edit">Muokkaa</string>
<string name="assets__action__export">Vie</string>
<string name="assets__action__import">Tuo</string>
<string name="assets__action__no">Ei</string>
<string name="assets__action__save">Tallenna</string>
<string name="assets__action__yes">Kyllä</string>
<string name="assets__error__details">Tiedot</string>
<string name="assets__error__snackbar_message">Jokin meni pieleen</string>
<!-- Setup UI strings -->
<string name="setup__title" comment="Title of Setup">Asennus</string>
<string name="setup__prev_button" comment="Label of Previous button in Setup (try to find a short translation due to limited space in UI)">Edellinen</string>
@@ -172,7 +299,10 @@
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Asennus valmis!</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoardin virheraportti</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopioi leikepöydälle</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Pahoittelut, FlorisBoard kaatui odottamattoman virheen vuoksi.</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopioi järjestelmän leikepöydälle</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Kopioitu järjestelmän leikepöydälle</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Avaa ongelmien seuranta (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Sulje</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard-virheraportit</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard on lopettanut toimintansa…</string>
@@ -180,5 +310,30 @@
<string name="crash_multiple_notification__title" comment="Title of the notification for consecutive crashes">FlorisBoard vaikuttaa lopettavan toimintansa toistuvasti…</string>
<string name="crash_multiple_notification__body" comment="Body of the notification for consecutive crashes">Vaihdetaan edelliseen näppäimistöön, jotta kaatumiskierre loppuu. Napauta nähdäksesi virheen tiedot</string>
<!-- clipboard strings -->
<string name="clip__context_header">Leikepöydän historia</string>
<string name="clip__clear_history">Tyhjennä historia</string>
<string name="clip__unpin_item">Poista kohteen kiinnitys</string>
<string name="clip__pin_item">Kiinnitä kohde</string>
<string name="clip__delete_item">Poista</string>
<string name="clip__paste_item">Liitä</string>
<string name="clip__back_to_text_input">Takaisin tekstinsyöttöön</string>
<string name="clip__cant_paste">Sovellus ei hyväksy tämän sisällön liittämistä.</string>
<string name="pref__clipboard__clipboard_category__label">Leikepöytä</string>
<string name="pref__clipboard__use_internal_clipboard___label">Käytä sisäistä leikepöytää</string>
<string name="pref__clipboard__use_internal_clipboard_title__summary">Käytä järjestelmän leikepöydän sijasta sisäistä leikepöytää</string>
<string name="pref__clipboard__sync_from_system_clipboard__label">Synkronoi järjestelmän leikepöydältä</string>
<string name="pref__clipboard__keyboard_sync_from_system_clipboard__summary">Järjestelmän leikepöytä päivittää myös Floriksen leikepöydän</string>
<string name="pref__clipboard__sync_to_system_clipboard__label">Synkronoi järjestelmän leikepöydälle</string>
<string name="pref__clipboard__sync_to_system_clipboard__summary">Floriksen leikepöytä päivittää myös järjestelmän leikepöydän</string>
<string name="pref__clipboard__enable_clipboard_history__label">Ota leikepöydän historia käyttöön</string>
<string name="pref__clipboard__enable_clipboard_history__summary">Säilytä leikepöydän kohteet</string>
<string name="pref__clipboard__clean_up_old_clipboard_items__label">Poista vanhat kohteet</string>
<string name="pref__clipboard__clean_up_old_after__label">Poista vanhat kohteet, kun on kulunut</string>
<string name="pref__unit_minutes">" minuuttia"</string>
<string name="pref__clipboard__limit_history_size__label">Rajoita historian kokoa</string>
<string name="pref__clipboard__max_history_size__label">Historian maksimikoko</string>
<string name="pref__unit_items">" kohdetta"</string>
<!-- glide strings -->
<string name="pref__glide_trail_fade_duration">Liu\'un jäljen häivytysaika</string>
<string name="pref__glide_trail_max_length">Liu\'un jäljen maksimipituus</string>
</resources>

View File

@@ -210,6 +210,15 @@
<string name="pref__smartbar__enabled__label" comment="Preference title">Activer la Barre Intelligente</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">S\'affichera en haut du clavier</string>
<string name="pref__suggestion__title" comment="Preference group title">Suggestions</string>
<string name="pref__dictionary__title" comment="Preference group title">Dictionnaire</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Activer le dictionnaire d\'utilisateur système</string>
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Suggérer des mots du dictionnaire d\'utilisateur système</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Gérer le dictionnaire d\'utilisateur système</string>
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Ajouter, voir et supprimer des entrées du dictionnaire d\'utilisateur système</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Activer le dictionnaire d\'utilisateur interne</string>
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Suggérer des mots du dictionnaire d\'utilisateur interne</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Gérer le dictionnaire d\'utilisateur interne</string>
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Ajouter, voir et supprimer des entrées du dictionnaire d\'utilisateur interne</string>
<string name="pref__correction__title" comment="Preference group title">Corrections</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Auto-capitalisation</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Capitaliser les mots en fonction du contexte de saisie actuel</string>
@@ -217,6 +226,26 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Le verrouillage des majuscules reste activé lorsque l\'on passe à un autre champ de texte</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">Point de double espace</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">En tappant deux fois sur la barre espace, insère un point suivi d\'un espace</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dictionnaire d\'utilisateur interne</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Dictionnaire d\'utilisateur système</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Fréquence : %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Fréquence : %d | Raccourci : %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Pour toutes les langues</string>
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Ouvrir l\'interface de gestion du système</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Dictionnaire d\'utilisateur importé avec succès !</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Dictionnaire d\'utilisateur exporté avec succès !</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Ajouter un mot</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Modifier un mot</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Mot</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Veuillez saisir un mot !</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Ce mot contient des caractères non valides.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Fréquence (entre %d et %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Veuillez saisir une valeur de fréquence !</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Veuillez entrer un nombre valide dans les limites spécifiées !</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Raccourci (facultatif)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Ce raccourci contient des caractères non valides.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Code de langue (facultatif)</string>
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Ce code de langue n\'est pas conforme à la syntaxe attendue. Le code doit être une langue uniquement (comme en), une langue et un pays (comme en_US) ou une langue, un pays et un script (comme en_US-script).</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestes &amp; Saisie en glissant</string>
<string name="pref__glide__title" comment="Preference group title">Saisie en glissant</string>
<string name="pref__glide__enabled__label" comment="Preference title">Activer la saisie en glissant</string>
@@ -278,6 +307,12 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Afficher l\'icône de l\'application dans le lanceur</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Forcer le mode privé</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Désactivera toutes les fonctions qui doivent fonctionner temporairement avec vos données de saisie</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Activer les outils de développeur</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Outils spécialement conçus pour le débogage et le dépannage</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Montrer les stats de mémoire tas</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Afficher en superposé l\'utilisation de mémoire tas et sa taille maximale dans le coin haut droit</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Effacer la base de données du dictionnaire utilisateur interne</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Efface tous les mots de la table de la base de données du dictionnaire</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">À propos</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Icône de l\'application FlorisBoard</string>
@@ -334,7 +369,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">Rapport d\'erreur de FlorisBoard</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Désolé pour le désagrément, mais FlorisBoard a crashé en raison d\'une erreur inattendue.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Si vous souhaitez signaler cette erreur, vérifier avant tout si votre problème n\'a pas déjà été signalé sur le tracker d\'erreur de github.\nSi ce n\'est pas le cas, copiez le journal d\'erreur généré et ouvrez une nouvelle issue. Utilisez le modèle \"%s\" et remplissez la description, les étapes de reproduction puis collez le journal d\'erreur généré à la fin. Ceci contribue à améliorer FlorisBoard et à le rendre plus stable pour tous. Merci de votre aide!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copier dans le presse-papiers</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copier vers le presse papier système</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Copié vers le presse papier système</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Impossible de copier vers le presse papier système : Instance du gestionnaire de presse papier introuvable</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Ouvrir le tracker d\'erreur (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Fermer</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Rapports d\'erreur de FlorisBoard</string>

View File

@@ -334,7 +334,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard hibajelentés</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Elnézést a kellemetlenségért, de a FlorisBoard összeomlott egy váratlan hiba miatt.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Ha szeretné jelenteni ezt a hibát, elsőnek nézze meg a hibakövetőt a GitHubon, hogy az összeomlása nem volt-e már jelentve.\nHa nem volt, másolja ki a generált összeomlásnaplót, és nyisson egy új hibajelentést. Használja a \"%s\" sablont, és töltse ki a leírást, írja le, hogy hogyan lehet előhozni a hibát, és illessze be az összeomlási naplót is a végére. Ez segít jobbá és stabilabbá tenni a FlorisBoardot mindenki számára. Köszönöm!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Másolás a vágólapra</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Rendszervágólapra másolás</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Rendszervágólapra másolva</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Nem lehet rendszervágólapra másolni: vágólap kezelő nem található</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Hibakövető megnyitása (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Bezárás</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard hibajelentések</string>

View File

@@ -137,9 +137,9 @@
<string name="settings__theme__group_extractActionButton" comment="Theme group label">Pulsante d\'azione a schermo intero orizzontale</string>
<string name="settings__theme__group_glideTrail" comment="Theme group label">Percorso di scorrimento</string>
<string name="settings__theme__group_custom" comment="Theme group label (%s is custom group name)">Gruppo personalizzato (%s)</string>
<string name="settings__theme__attr_background" comment="Theme attribute label">Colore di sfondo</string>
<string name="settings__theme__attr_backgroundActive" comment="Theme attribute label">Colore di sfondo (attivo)</string>
<string name="settings__theme__attr_backgroundPressed" comment="Theme attribute label">Colore di sfondo (premuto)</string>
<string name="settings__theme__attr_background" comment="Theme attribute label">Colore sfondo</string>
<string name="settings__theme__attr_backgroundActive" comment="Theme attribute label">Colore sfondo (attivo)</string>
<string name="settings__theme__attr_backgroundPressed" comment="Theme attribute label">Colore sfondo (premuto)</string>
<string name="settings__theme__attr_foreground" comment="Theme attribute label">Colore di testo</string>
<string name="settings__theme__attr_foregroundAlt" comment="Theme attribute label">Colore di testo (alternativo)</string>
<string name="settings__theme__attr_foregroundPressed" comment="Theme attribute label">Colore di testo (premuto)</string>
@@ -210,6 +210,15 @@
<string name="pref__smartbar__enabled__label" comment="Preference title">Abilità Smartbar</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Verrà mostrato in cima alla tastiera</string>
<string name="pref__suggestion__title" comment="Preference group title">Suggerimenti</string>
<string name="pref__dictionary__title" comment="Preference group title">Dizionario</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Abilita il dizionario utente di sistema</string>
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Suggerisci le parole contenute nel dizionario utente di sistema</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Gestisci il dizionario utente di sistema</string>
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Aggiungi, visualizza e rimuovi le voci del dizionario utente di sistema</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Abilita il dizionario utente interno</string>
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Suggerisci le parole contenute nel dizionario utente interno</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Gestisci il dizionario utente interno</string>
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Aggiungi, visualizza e rimuovi le voci del dizionario utente interno</string>
<string name="pref__correction__title" comment="Preference group title">Correzioni</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Auto-maiuscolo</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Rendi maiuscole le parole in base al contesto attuale</string>
@@ -217,6 +226,26 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Il maiuscolo rimarrà attivo quando ti sposti ad un altro campo di testo</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">Doppio tocco barra spaziatrice</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Doppio tocco su barra spaziatrice per mettere il punto (.) seguito da uno spazio</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dizionario utente interno</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Dizionario utente di sistema</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Frequenza: %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Frequenza: %d | Scorciatoia: %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Per tutte le lingue</string>
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Apri l\'interfaccia di gestione del sistema</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Dizionario utente importato con successo!</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Dizionario utente esportato con successo!</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Aggiungi una parola</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Modifica una parola</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Parola</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Per favore inserisci una parola!</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Questa parola contiene caratteri non validi.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Frequenza (tra %d e %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Per favore inserisci un valore di frequenza!</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Per favore inserisci un numero valido entro i limiti specificati!</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Scorciatoia (facoltativo)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Questa scorciatoia contiene caratteri non validi.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Codice lingua (facoltativo)</string>
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Questo codice di lingua non rispetta la sintassi richiesta. Il codice deve contenere solo la lingua (come it), la lingua e il paese (come it_IT) oppure la lingua, il paese e lo script (come it_IT-script).</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gesti &amp; Digitazione a scorrimento</string>
<string name="pref__glide__title" comment="Preference group title">Scrittura con swype</string>
<string name="pref__glide__enabled__label" comment="Preference title">Attiva digitazione a scorrimento</string>
@@ -227,6 +256,8 @@
<string name="pref__gestures__space_bar_title" comment="Preference group title">Gesti della barra di spazio</string>
<string name="pref__gestures__other_title" comment="Preference group title">Altri gesti / Sogli dei gesti</string>
<string name="pref__gestures__swipe_action__no_action" comment="Preference value for swipe action">Nessuna azione</string>
<string name="pref__gestures__swipe_action__cycle_to_previous_keyboard_mode" comment="Preference value for swipe action">Passa alla modalità precedente</string>
<string name="pref__gestures__swipe_action__cycle_to_next_keyboard_mode" comment="Preference value for swipe action">Passa alla modalità successiva</string>
<string name="pref__gestures__swipe_action__delete_characters_precisely" comment="Preference value for swipe action">Cancella lettere con precisione</string>
<string name="pref__gestures__swipe_action__delete_word" comment="Preference value for swipe action">Cancella la parola attuale</string>
<string name="pref__gestures__swipe_action__delete_words_precisely" comment="Preference value for swipe action">Cancella parole con precisione</string>
@@ -240,6 +271,7 @@
<string name="pref__gestures__swipe_action__move_cursor_end_of_line" comment="Preference value for swipe action">Spazia il cursore alla fine della linea</string>
<string name="pref__gestures__swipe_action__move_cursor_start_of_page" comment="Preference value for swipe action">Sposta il cursore all\'inizio della pagina</string>
<string name="pref__gestures__swipe_action__move_cursor_end_of_page" comment="Preference value for swipe action">Sposta il cursore alla fine della pagina</string>
<string name="pref__gestures__swipe_action__switch_to_clipboard_context" comment="Preference value for swipe action">Apri gestore/cronologia appunti</string>
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">Shift</string>
<string name="pref__gestures__swipe_action__redo" comment="Preference value for swipe action">Ripristina</string>
<string name="pref__gestures__swipe_action__undo" comment="Preference value for swipe action">Annulla</string>
@@ -275,6 +307,12 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Mostra icona nel launcher</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Forza la modalità privata</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Disabiliterà tutte le funzionalità che temporanemaente funzionano con i tuoi dati</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Abilita strumenti sviluppatore</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Strumenti specifici per il debug e la risoluzione dei problemi</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Mostra statistiche della memoria heap</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Sovrapponi l\'utilizzo e la dimensione massima della memoria heap nell\'angolo in alto a destra</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Cancella il dizionario utente interno</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Cancella tutte le parole dal dizionario</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Informazioni su</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Icona dell\'app FlorisBoard</string>
@@ -290,6 +328,7 @@
<string name="assets__file__name">Nome</string>
<string name="assets__file__source">Fonte</string>
<string name="assets__action__add">Aggiungi</string>
<string name="assets__action__apply">Applica</string>
<string name="assets__action__cancel">Annulla</string>
<string name="assets__action__cancel_confirm_title">Conferma anulla</string>
<string name="assets__action__cancel_confirm_message">Sei sicuro di volere cancellare gli cambiamenti non salvati? Questo non può essere annullato.</string>
@@ -331,7 +370,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">Segnalazione errore</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Ci dispiace per l\'inconveniente, ma FlorisBoard si è chiuso a causa di un errore inaspettato.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Se desideri segnalare questo errore, prima controlla se è già stato segnalato nel centro problemi su GitHub.\nSe non lo è, copia il registro di \'interruzione generato e apri un nuovo problema. Usa il modello \"%s\" e compila la descrizione, i passi per riprodurre il problema, e incolla il registro di interruzione generato alla fine. Questo aiuta a migliorare FlorisBoard e a renderlo più stabile per tutti. Grazie!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copia negli appunti</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copia negli appunti di sistema</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Copiato negli appunti di sistema</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Impossibile copiare sugli appunti di sistema: l\'istanza del gestore appunti non è stata trovata</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Apri il centro problemi (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Chiudi</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Segnalazioni di errore di FlorisBoard</string>

View File

@@ -295,7 +295,6 @@
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">ההתקנה הושלמה!</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard דוח שגיאה</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">העתקה ללוח העריכה</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">סגור</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard דוח שגיאה</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard הפסיק לעבוד…</string>

View File

@@ -327,7 +327,6 @@
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Sazkirin qediya!</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">Raporta xeletiya FlorisBoard</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Ji ber bibe panelê</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Bigire</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Raportên xeletiya FlorisBoard</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorîsBoardê dest ji kar berda…</string>

View File

@@ -210,6 +210,15 @@
<string name="pref__smartbar__enabled__label" comment="Preference title">Iespējot viedjoslu</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Tiks rādīta tastatūras augšpusē</string>
<string name="pref__suggestion__title" comment="Preference group title">Ieteikumi</string>
<string name="pref__dictionary__title" comment="Preference group title">Vārdnīca</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Iespējot sistēmas lietotāja vārdnīcu</string>
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Ieteikt vārdus, kas tiek glabāti sistēmas lietotāja vārdnīcā</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Pārvaldīt sistēmas lietotāja vārdnīcu</string>
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Pievienot, skatīt un noņemt ierakstus sistēmas lietotāja vārdnīcā</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Iespējot iekšējo lietotāja vārdnīcu</string>
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Ieteikt vārdus, kas tiek glabāti iekšējā lietotāja vārdnīcā</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Pārvaldīt iekšējo lietotāja vārdnīcu</string>
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Pievienot, skatīt un noņemt ierakstus iekšējā lietotāja vārdnīcā</string>
<string name="pref__correction__title" comment="Preference group title">Labojumi</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Sākumburta pārveidošana par lielo</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Pārveidot vārdu sākumburtus par lielajiem, balstoties uz pašreizējo ievades kopumu</string>
@@ -217,6 +226,26 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Burtslēgs paliks ieslēgts pēc pārvietošanās uz citu ievades lauku</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">Divkāršas atstarpes punkts</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Divreiz piesitot atstarpes taustiņam, tiks ievietots punkts un atstarpe</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Iekšējā lietotāja vārdnīca</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Sistēmas lietotāja vārdnīca</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Biežums: %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Biežums: %d | Saīsne: %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Visām valodām</string>
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Atvērt sistēmas pārvaldnieka lietotāja saskarni</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Lietotāja vārdnīca tika veiksmīgi ievietota.</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Lietotāja vārdnīca tika veiksmīgi izdota.</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Pievienot vārdu</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Labot vārdu</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Vārds</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Lūgums ievadīt vārdu.</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Šis vārds satur nederīgas rakstzīmes.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Biežums (starp %d un %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Lūgums ievadīt biežuma vērtību.</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Lūgums ievadīt derīgu skaitli, kas ir norādītajās robežās.</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Saīsne (pēc izvēles)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Šī saīsne satur nederīgas rakstzīmes.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Valodas kods (pēc izvēles)</string>
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Šis valodas kods neatbilst vēlamajam veidolam. Kodam ir vai nu jāborāda tikai valoda (piemēram, en), vai valoda un valsts (piemēram, en_US), vai arī valoda, valsts un raksts (piemēram, en_US-script).</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">Kustību un slīdošā rakstīšana</string>
<string name="pref__glide__title" comment="Preference group title">Slīdošā rakstīšana</string>
<string name="pref__glide__enabled__label" comment="Preference title">Iespējot slīdošo rakstīšanu</string>
@@ -278,6 +307,12 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Rādīt lietotnes ikonu palaidējā</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Piespiest izmantot slepeno stāvokli</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Tiks atspējotas visas iespējas, kam ir īslaicīgi jāapstrādā ievades dati</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Iespējot izstrādātāju rīkus</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Rīki, kas ir izstrādāti traucējumnovēršanai un nepilnību atklāšanai un novēršanai</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Rādīt grēdas atmiņas statistiku</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Rāda grēdas atmiņas izmantojumu un lielāko iespējamo izmēru augšējā labajā stūrī</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Iztīrīt iekšējās lietotāja vārdnīcas datubāzi</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Tiek izņemti visi vārdi no vārdnīcas datubāzes tabulas</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Par</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">FlorisBoard lietotnes ikona</string>
@@ -294,6 +329,7 @@
<string name="assets__file__name">Nosaukums</string>
<string name="assets__file__source">Avots</string>
<string name="assets__action__add">Pievienot</string>
<string name="assets__action__apply">Pielietot</string>
<string name="assets__action__cancel">Atcelt</string>
<string name="assets__action__cancel_confirm_title">Apstiprināt atcelšanu</string>
<string name="assets__action__cancel_confirm_message">Vai tiešām atmest nesaglabātās izmaiņas? Šī darbība pēc izpildīšanas vairs nevar tikt atsaukta.</string>
@@ -335,7 +371,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard kļūdu ziņojums</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Atvainojamies par neērtībām, bet FlorsBoard avarēja neparedzētas kļūdas dēļ.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Ja ir vēlme ziņot par šo kļūdu, vispirms jāpārliecinās, vai GitHub pieteikumu ceļrādī par šādu avāriju jau ir ziņots.\nJa nē, starpliktuvē jāievieto izveidotais avārijas žurnāls un jāatver jauns pieteikums. Jāizmanto \"%s\" veidne un jānorāda apraksts, darbības atkārtošanai un beigās jāielīmē avārijas žurnāls. Tas palīdz padarīt FlorisBoard labāku un noturīgāku ikvienam. Paldies!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Ielikt starpliktuvē</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Ievietot sistēmas starpliktuvē</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Ievietots sistēmas starpliktuvē</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Nav iespējams ievietot sistēmas starpliktuvē: nav atrasts starpliktuves pārvaldnieks</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Atvērt pieteikumu ceļrādi (GitHub.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Aizvērt</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard kļūdu ziņojumi</string>

View File

@@ -278,6 +278,10 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">App-pictogram in launcher tonen</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Forceer privémodus</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Schakelt alle functies uit die tijdelijk gebruik maken van uw invoerdata</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Activeer ontwikkelaarshulpmiddelen</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Hulpmiddelen specifiek voor debuggen en problemen verhelpen</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Toon heap memory stats</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Toont het heap-memorygebruik en maximale grootte in de rechterbovenhoek</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Over</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">App-pictogram van FlorisBoard</string>
@@ -333,7 +337,10 @@
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">Foutenrapport FlorisBoard</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Sorry voor het ongemak, maar FlorisBoard is gecrashed omwille van een onverwachte fout.</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopiëren naar klembord</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopieer naar systeemklembord</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Gekopieerd naar systeemklembord</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Kan niet kopiëren naar systeemklembord: Klembordbeheerinstantie niet gevonden</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Open probleemtracker (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Sluiten</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Foutenrapporten van FlorisBoard</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard is gestopt met werken…</string>
@@ -354,6 +361,16 @@
<string name="pref__clipboard__use_internal_clipboard_title__summary">Gebruik een intern klembord in plaats van het systeemklembord</string>
<string name="pref__clipboard__sync_from_system_clipboard__label">Synchroniseer met systeemklembord</string>
<string name="pref__clipboard__keyboard_sync_from_system_clipboard__summary">Systeemklembord updatet ook het Floris-klembord</string>
<string name="pref__clipboard__sync_to_system_clipboard__label">Synchroniseer met systeemklembord</string>
<string name="pref__clipboard__sync_to_system_clipboard__summary">Floris-klembord updatet ook het systeemklembord</string>
<string name="pref__clipboard__enable_clipboard_history__label">Schakel klembordgeschiedenis in</string>
<string name="pref__clipboard__enable_clipboard_history__summary">Bewaar klembord-items</string>
<string name="pref__clipboard__clean_up_old_clipboard_items__label">Kuis oude items op</string>
<string name="pref__clipboard__clean_up_old_after__label">Kuis oude items op na</string>
<string name="pref__unit_minutes">" minuten"</string>
<string name="pref__clipboard__limit_history_size__label">Beperk geschiedenisgrootte</string>
<string name="pref__clipboard__max_history_size__label">Maximale grootte geschiedenis</string>
<string name="pref__unit_items">" items"</string>
<!-- glide strings -->
<string name="pref__glide_trail_fade_duration">Vervaagtijd veegspoor</string>
<string name="pref__glide_preview_refresh_delay">Vertraging vernieuwing voorbeeld</string>

View File

@@ -304,7 +304,6 @@
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Konfiguracja ukończona!</string>
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">Pobierz raport o błędach</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Kopiuj do schowka</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Zamknij</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Raporty o błędach FlorisBoard</string>
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard przestał działać…</string>

View File

@@ -210,6 +210,15 @@
<string name="pref__smartbar__enabled__label" comment="Preference title">Ativar barra inteligente</string>
<string name="pref__smartbar__enabled__summary" comment="Preference summary">Mostrar na parte superior do teclado</string>
<string name="pref__suggestion__title" comment="Preference group title">Sugestões</string>
<string name="pref__dictionary__title" comment="Preference group title">Dicionário</string>
<string name="pref__dictionary__enable_system_user_dictionary__label" comment="Preference title">Ativar dicionário de usuário do sistema</string>
<string name="pref__dictionary__enable_system_user_dictionary__summary" comment="Preference summary">Sugerir palavras armazenadas no dicionário de usuário do sistema</string>
<string name="pref__dictionary__manage_system_user_dictionary__label" comment="Preference title">Gerenciar dicionário de usuário do sistema</string>
<string name="pref__dictionary__manage_system_user_dictionary__summary" comment="Preference summary">Adicionar, visualizar e remover entradas para o dicionário de usuário do sistema</string>
<string name="pref__dictionary__enable_internal_user_dictionary__label" comment="Preference title">Ativar dicionário interno do usuário</string>
<string name="pref__dictionary__enable_internal_user_dictionary__summary" comment="Preference summary">Sugerir palavras armazenadas no dicionário interno do usuário</string>
<string name="pref__dictionary__manage_floris_user_dictionary__label" comment="Preference title">Gerenciar dicionário interno do usuário</string>
<string name="pref__dictionary__manage_floris_user_dictionary__summary" comment="Preference summary">Adicionar, visualizar e remover entradas para o dicionário interno do usuário</string>
<string name="pref__correction__title" comment="Preference group title">Correções</string>
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Capitalização automática</string>
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Deixar palavras em maiúsculo com base no contexto de entrada atual</string>
@@ -217,6 +226,26 @@
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">O caps lock permanecerá ligado ao mudar para outro campo de texto</string>
<string name="pref__correction__double_space_period__label" comment="Preference title">Dois espaços para ponto final</string>
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Tocar duas vezes na barra de espaço insere um ponto final seguido por um espaço</string>
<string name="settings__udm__title_floris" comment="Title of the User Dictionary Manager activity for internal">Dicionário Interno do Usuário</string>
<string name="settings__udm__title_system" comment="Title of the User Dictionary Manager activity for system">Dicionário de Usuário do Sistema</string>
<string name="settings__udm__word_summary_freq" comment="Summary label for a word entry. The decimal placeholder inserts the frequency for the word it summarizes.">Frequência: %d</string>
<string name="settings__udm__word_summary_freq_shortcut" comment="Summary label for a word entry. The first placeholder inserts the frequency for the word it summarizes, the second placeholder the shortcut defined.">Frequência: %d | Atalho: %s</string>
<string name="settings__udm__all_languages" comment="Label of the For all languages entry in the language list">Para todas as línguas</string>
<string name="settings__udm__open_system_manager_ui" comment="Label of the Open system manager UI menu option">Abrir interface do gerenciador do sistema</string>
<string name="settings__udm__dictionary_import_success" comment="Message for dictionary import success">Dicionário de usuário importado com sucesso!</string>
<string name="settings__udm__dictionary_export_success" comment="Message for dictionary export success">Dicionário de usuário exportado com sucesso!</string>
<string name="settings__udm__dialog__title_add" comment="Label for the title (when in adding mode) in the user dictionary add/edit dialog">Adicionar entrada de palavra</string>
<string name="settings__udm__dialog__title_edit" comment="Label for the title (when in editing mode) in the user dictionary add/edit dialog">Editar entrada de palavra</string>
<string name="settings__udm__dialog__word_label" comment="Label for the word in the user dictionary add/edit dialog">Palavra</string>
<string name="settings__udm__dialog__word_error_empty" comment="Error label for the word in the user dictionary add/edit dialog">Por favor, digite uma palavra!</string>
<string name="settings__udm__dialog__word_error_invalid" comment="Error label for the word in the user dictionary add/edit dialog">Esta palavra contém caracteres inválidos.</string>
<string name="settings__udm__dialog__freq_label" comment="Label for the frequency in the user dictionary add/edit dialog. The two decimal placeholders are the minimum and maximum frequency, both inclusive.">Frequência (entre %d e %d)</string>
<string name="settings__udm__dialog__freq_error_empty" comment="Error label for the frequency in the user dictionary add/edit dialog">Por favor, digite um valor de frequência!</string>
<string name="settings__udm__dialog__freq_error_invalid" comment="Error label for the frequency in the user dictionary add/edit dialog">Digite um número válido dentro dos limites especificados!</string>
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Atalho (opcional)</string>
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Este atalho contém caracteres inválidos.</string>
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Código de idioma (opcional)</string>
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Este código de idioma não está de acordo com a sintaxe esperada. O código deve ser apenas uma língua (como en), uma língua e um país (como en_US) ou uma língua, país e script (como en_US-script).</string>
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos &amp; Digitação deslizante</string>
<string name="pref__glide__title" comment="Preference group title">Digitação deslizante</string>
<string name="pref__glide__enabled__label" comment="Preference title">Ativar digitação deslizante</string>
@@ -278,6 +307,12 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Mostrar ícone do aplicativo no launcher</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Forçar o modo privado</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Isso desativará quaisquer recursos que tenham que trabalhar temporariamente com seus dados de digitação</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Ativar ferramentas de desenvolvedor</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Ferramentas especificamente projetadas para depuração e solução de problemas</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Mostrar estatísticas de memória heap</string>
<string name="pref__devtools__show_heap_memory_stats__summary" comment="Summary of Show heap memory stats in Advanced">Sobrepõe o uso da memória heap e o tamanho máximo no canto superior direito</string>
<string name="pref__devtools__clear_udm_internal_database__label" comment="Label of Clear internal user dictionary database in Advanced">Limpar banco de dados do dicionário de usuário interno</string>
<string name="pref__devtools__clear_udm_internal_database__summary" comment="Summary of Clear internal user dictionary database in Advanced">Limpa todas as palavras da tabela do banco de dados do dicionário</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Sobre</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Ícone do aplicativo FlorisBoard</string>
@@ -293,6 +328,7 @@
<string name="assets__file__name">Nome</string>
<string name="assets__file__source">Fonte</string>
<string name="assets__action__add">Adicionar</string>
<string name="assets__action__apply">Aplicar</string>
<string name="assets__action__cancel">Cancelar</string>
<string name="assets__action__cancel_confirm_title">Confirmar cancelamento</string>
<string name="assets__action__cancel_confirm_message">Tem certeza que quer descartar alguma mudança não salva? Esta ação não pode ser desfeita uma vez executada.</string>
@@ -334,7 +370,9 @@
<string name="crash_dialog__title" comment="Title of crash dialog">Relatório de erro do FlorisBoard</string>
<string name="crash_dialog__description" comment="Description of crash dialog">Desculpe pelo inconveniente, mas o FlorisBoard travou devido a um erro inesperado.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Se você deseja relatar esse erro, primeiro verifique o issue tracker (rastreador de problemas) no GitHub para conferir se o seu travamento ainda não foi relatado.\nSe ainda não, copie o registro de travamento gerado e abra um novo issue. Use o modelo \"%s\" e preencha a descrição, as etapas para reproduzir e cole o registro de travamento gerado no final. Isso ajuda a tornar o FlorisBoard melhor e mais estável para todos. Obrigado!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copiar para a área de transferência</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copiar para a área de transferência do sistema</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Copiado para a área de transferência do sistema</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Não é possível copiar para a área de transferência do sistema: Instância do gerenciador de área de transferência não encontrada</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Abrir issue tracker (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Fechar</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Relatórios de erros do FlorisBoard</string>

View File

@@ -278,6 +278,9 @@
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Mostrar ícone na página inicial</string>
<string name="pref__advanced__force_private_mode__label" comment="Label of Force private mode preference in Advanced">Impor modo privado</string>
<string name="pref__advanced__force_private_mode__summary" comment="Summary of Force private mode preference in Advanced">Desativa todas as funcionalidades que necessitam de aceder aos dados de introdução</string>
<string name="pref__devtools__enabled__label" comment="Label of Enable developer tools in Advanced">Ativar ferramentas de programador</string>
<string name="pref__devtools__enabled__summary" comment="Summary of Enable developer tools in Advanced">Ferramentas criadas para efeitos de depuração e resolução de erros</string>
<string name="pref__devtools__show_heap_memory_stats__label" comment="Label of Show heap memory stats in Advanced">Mostrar estatísticas de memória \'heap\'</string>
<!-- About UI strings -->
<string name="about__title" comment="Title of About activity">Sobre o FlorisBoard</string>
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Ícone do FlorisBoard</string>
@@ -335,6 +338,8 @@
<string name="crash_dialog__description" comment="Description of crash dialog">Desculpe o inconveniente mas a aplicação encerrou devido a um problema desconhecido.</string>
<string name="crash_dialog__report_instructions" comment="Issue tracker report instructions for the crash dialog. The %s placeholder is the name of the crash report template and always in English/LTR.">Se quiser reportar este erro, analise o registo de erros reportados no GitHub e verifique se este problema já foi reportado.\nSe não o encontrar, copie o relatório de erro e abra uma ocorrência. Utilize o modelo \"%s\", preencha a descrição, os passos necessários para reproduzir o erro e cole o relatório no final. Desta forma, pode ajudar a melhorar o Floriboard. Obrigado!</string>
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copiar para a área de transferência</string>
<string name="crash_dialog__copy_to_clipboard_success" comment="Label of Copy to clipboard success message in crash dialog">Copiado para a área de transferência</string>
<string name="crash_dialog__copy_to_clipboard_failure" comment="Label of Copy to clipboard failure message in crash dialog">Não foi possível copiar. Não existe um gestor para a área de transferência.</string>
<string name="crash_dialog__open_issue_tracker" comment="Label of Open issue tracker button in crash dialog">Abrir registo de erros (github.com)</string>
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Fechar</string>
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Relatórios de erros do FlorisBoard</string>

Some files were not shown because too many files have changed in this diff Show More