Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddc4f7f1ba | ||
|
|
afea8c721f | ||
|
|
7dedfd4f7a | ||
|
|
ef37194900 | ||
|
|
58134b1ceb | ||
|
|
53cfbad404 | ||
|
|
9cffcea246 | ||
|
|
5acf80db0f | ||
|
|
d6f724e518 | ||
|
|
6c4aa36b06 | ||
|
|
edc38b6c2c | ||
|
|
891a2c6bac | ||
|
|
229237153b | ||
|
|
290fbb5239 | ||
|
|
409d4f9348 | ||
|
|
82938cda5b | ||
|
|
f7b0a30271 | ||
|
|
575f359a85 | ||
|
|
22591163b3 | ||
|
|
8104ae60ca | ||
|
|
165b682732 | ||
|
|
eb770fac6c | ||
|
|
39c27426a4 | ||
|
|
228d5055cc | ||
|
|
b400e04560 | ||
|
|
27c1bbf039 | ||
|
|
f61b655f7d | ||
|
|
f82af63e97 | ||
|
|
0fbd950f6e | ||
|
|
e97b5f54ac | ||
|
|
b611360dd5 | ||
|
|
1b9d260020 | ||
|
|
d74fe62bc0 | ||
|
|
fe6f61a282 | ||
|
|
8b4239d9be | ||
|
|
a0c7cf2794 | ||
|
|
7480d14a0f | ||
|
|
7274228a46 | ||
|
|
a38f6a2c76 | ||
|
|
eda6c09538 | ||
|
|
9e42d16cb0 | ||
|
|
11ba51c354 | ||
|
|
51f5196b8a | ||
|
|
56bbe9d13c | ||
|
|
4d1ae52dc0 | ||
|
|
1e1916194b | ||
|
|
80fb20885b | ||
|
|
bae3c8ec9d | ||
|
|
9ff7d86a8d | ||
|
|
89ab0731d2 | ||
|
|
887a75a482 | ||
|
|
e52bea2456 | ||
|
|
2171e16346 | ||
|
|
566b6fbae3 | ||
|
|
5215227793 | ||
|
|
671f97eddb | ||
|
|
b6c9469826 | ||
|
|
77e4414467 | ||
|
|
db85e05714 | ||
|
|
51890c93d4 | ||
|
|
1f16ac2c3b | ||
|
|
199211fdbf | ||
|
|
c5ee414ec6 | ||
|
|
f1c5b1802b | ||
|
|
0450c8c7a1 | ||
|
|
6da6da74fc | ||
|
|
d7fca0aad1 | ||
|
|
1af519e01d | ||
|
|
989d2884b1 | ||
|
|
d137155ab0 | ||
|
|
270ab4fe5f |
@@ -9,7 +9,7 @@ insert_final_newline = true
|
||||
max_line_length = 120
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.har,*.json,*yml}]
|
||||
[{*.har,*.json,*yml,*.sh}]
|
||||
indent_size = 2
|
||||
|
||||
[*.kt]
|
||||
|
||||
17
.github/workflows/android.yml
vendored
17
.github/workflows/android.yml
vendored
@@ -2,7 +2,7 @@ name: FlorisBoard CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
paths-ignore:
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".github/FUNDING.yml"
|
||||
@@ -14,31 +14,28 @@ on:
|
||||
- "README.md"
|
||||
- "ROADMAP.md"
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: gradle/actions/wrapper-validation@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
cache: gradle
|
||||
- name: Set up CMake and Ninja
|
||||
uses: lukka/get-cmake@latest
|
||||
- name: Build with Gradle
|
||||
# MUST call gradlew separately because of an OSS license plugin issue.
|
||||
# See https://github.com/google/play-services-plugins/issues/199
|
||||
run: ./gradlew clean && ./gradlew assembleDebug
|
||||
- uses: actions/upload-artifact@v3
|
||||
run: ./gradlew clean assembleDebug
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-debug.apk
|
||||
path: app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
6
.github/workflows/crowdin-upload.yml
vendored
6
.github/workflows/crowdin-upload.yml
vendored
@@ -2,7 +2,7 @@ name: Crowdin Upload Sources
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- "app/src/main/res/values/strings.xml"
|
||||
- ".github/workflows/crowdin-upload.yml"
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Upload
|
||||
uses: crowdin/github-action@1.4.0
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: "crowdin.yml"
|
||||
upload_sources: true
|
||||
|
||||
61
.github/workflows/validate-strings-no-translations.yml
vendored
Normal file
61
.github/workflows/validate-strings-no-translations.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Validate no translated strings.xml included
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Precheck if validation is required
|
||||
id: precheck
|
||||
run: |
|
||||
pr_author="${{ github.event.pull_request.user.login }}"
|
||||
if [[ "$pr_author" == "florisboard-bot" ]]; then
|
||||
echo "PR is by florisboard-bot, skipping validation!"
|
||||
echo "require_validation=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "PR is not by florisboard-bot, requiring validation!"
|
||||
echo "require_validation=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Fetch PR changed files manually
|
||||
id: fetch_changed_files
|
||||
if: steps.precheck.outputs.require_validation == 'true'
|
||||
run: |
|
||||
pr_files="$(curl -sSf https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files?per_page=1000)" || exit 11
|
||||
changed_files="$(jq -r '.[].filename' <<< "$pr_files")" || exit 12
|
||||
illegal_changes_list="$(grep -E '^app/src/main/res/values-.+/strings.xml$' <<< "$changed_files")" || true
|
||||
if [ -n "$illegal_changes_list" ]; then
|
||||
echo -e "Illegal changes detected:\n$illegal_changes_list"
|
||||
else
|
||||
echo "No illegal changes detected"
|
||||
fi
|
||||
echo "illegal_changes_list<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$illegal_changes_list" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create comment if illegal files detected
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
if: steps.precheck.outputs.require_validation == 'true' && steps.fetch_changed_files.outputs.illegal_changes_list != ''
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
⚠️ Illegal changes detected
|
||||
|
||||
Hey there!
|
||||
|
||||
We detected illegal changes that disobey the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md#translation). This is a kind reminder that pull requests must not contain translated `strings.xml` files, as those are exclusively managed from Crowdin.
|
||||
|
||||
Please remove changes to the following files:
|
||||
```
|
||||
${{ steps.fetch_changed_files.outputs.illegal_changes_list }}
|
||||
```
|
||||
|
||||
- name: Fail workflow if illegal files detected
|
||||
if: steps.precheck.outputs.require_validation == 'true' && steps.fetch_changed_files.outputs.illegal_changes_list != ''
|
||||
run: echo -e "Illegal changes detected:\n${{ steps.fetch_changed_files.outputs.illegal_changes_list }}" && exit 1
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,11 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aab
|
||||
*.ap_
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
@@ -54,6 +58,5 @@ result
|
||||
# Rust
|
||||
debug/
|
||||
target/
|
||||
Cargo.lock
|
||||
**/*.rs.bk
|
||||
*.pdb
|
||||
|
||||
50
README.md
50
README.md
@@ -1,21 +1,19 @@
|
||||
<img align="left" width="80" height="80"
|
||||
src=".github/repo_icon.png" alt="App icon">
|
||||
|
||||
# FlorisBoard [](https://crowdin.florisboard.patrickgold.dev) [](https://matrix.to/#/#florisboard:matrix.org) [](CODE_OF_CONDUCT.md) 
|
||||
# FlorisBoard [](https://crowdin.florisboard.patrickgold.dev) [](https://matrix.to/#/#florisboard:matrix.org) [](CODE_OF_CONDUCT.md) [](https://github.com/florisboard/florisboard/actions/workflows/android.yml)
|
||||
|
||||
**FlorisBoard** is a free and open-source keyboard for Android 7.0+
|
||||
devices. It aims at being modern, user-friendly and customizable while
|
||||
fully respecting your privacy. Currently in early-beta state.
|
||||
|
||||
*Note: Due to various reasons development has been quite stuck in the past year, but things are slowly improving again and new releases/features will follow in the near future, please see the [roadmap](ROADMAP.md) for details!*
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th align="center" width="50%">
|
||||
<h3>Stable <a href="https://github.com/florisboard/florisboard/releases/latest"><img alt="Latest stable release" src="https://img.shields.io/github/v/release/florisboard/florisboard"></a></h3>
|
||||
<h3>Stable <a href="https://github.com/florisboard/florisboard/releases/latest"><img alt="Latest stable release" src="https://img.shields.io/github/v/release/florisboard/florisboard?sort=semver&display_name=tag&color=28a745"></a></h3>
|
||||
</th>
|
||||
<th align="center" width="50%">
|
||||
<h3>Beta <a href="https://github.com/florisboard/florisboard/releases"><img alt="Latest beta release" src="https://img.shields.io/github/v/release/florisboard/florisboard?include_prereleases"></a></h3>
|
||||
<h3>Preview <a href="https://github.com/florisboard/florisboard/releases"><img alt="Latest preview release" src="https://img.shields.io/github/v/release/florisboard/florisboard?include_prereleases&sort=semver&display_name=tag&color=fd7e14"></a></h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -23,7 +21,7 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
<p><i>Major versions only</i><br><br>Updates are more polished, new features are matured and tested through to ensure a stable experience.</p>
|
||||
</td>
|
||||
<td valign="top">
|
||||
<p><i>Alpha/Beta versions</i><br><br>Updates contain new features that may not be fully matured yet and bugs are more likely to occur. Allows you to give early feedback.</p>
|
||||
<p><i>Major + Alpha/Beta/Rc versions</i><br><br>Updates contain new features that may not be fully matured yet and bugs are more likely to occur. Allows you to give early feedback.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -36,6 +34,11 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
</p>
|
||||
<p>
|
||||
|
||||
**Obtainium**: [Auto-import stable config][obtainium_stable]
|
||||
|
||||
</p>
|
||||
<p>
|
||||
|
||||
**Manual**: Download and install the APK from the release page.
|
||||
|
||||
</p>
|
||||
@@ -44,7 +47,12 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
<p><a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard.beta"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a></p>
|
||||
<p>
|
||||
|
||||
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [beta testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
|
||||
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [preview testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
|
||||
|
||||
</p>
|
||||
<p>
|
||||
|
||||
**Obtainium**: [Auto-import preview config][obtainium_preview]
|
||||
|
||||
</p>
|
||||
<p>
|
||||
@@ -56,16 +64,17 @@ fully respecting your privacy. Currently in early-beta state.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Beginning with v0.4.0 FlorisBoard will follow [SemVer](https://semver.org/#summary) versioning scheme.
|
||||
Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
|
||||
|
||||
## Highlighted features
|
||||
- Integrated clipboard manager / history
|
||||
- Advanced theming support and customization
|
||||
- Integrated extension support (still evolving)
|
||||
- Emoji keyboard
|
||||
- Emoji keyboard / history / suggestions
|
||||
|
||||
Word suggestions/spell checking are not included in the current releases and are a major goal for the v0.5.0 milestone.
|
||||
> [!IMPORTANT]
|
||||
> Word suggestions/spell checking are not included in the current releases
|
||||
> and are a major goal for the v0.5 milestone.
|
||||
|
||||
Feature roadmap: See [ROADMAP.md](ROADMAP.md)
|
||||
|
||||
@@ -73,6 +82,16 @@ Feature roadmap: See [ROADMAP.md](ROADMAP.md)
|
||||
Want to contribute to FlorisBoard? That's great to hear! There are lots of
|
||||
different ways to help out, please see the [contribution guidelines](CONTRIBUTING.md) for more info.
|
||||
|
||||
## Addons Store
|
||||
The official [Addons Store](https://beta.addons.florisboard.org) offers the possibility for the community to share and download FlorisBoard extensions.
|
||||
Instructions on how to publish addons can be found [here](https://github.com/florisboard/florisboard/wiki/How-to-publish-on-FlorisBoard-Addons).
|
||||
|
||||
Many thanks to Ali ([@4H1R](https://github.com/4H1R)) for implementing the store!
|
||||
|
||||
> [!NOTE]
|
||||
> During the initial beta release phase, the Addons Store _will_ only accept theme extensions.
|
||||
> Later on we plan to add support for language packs and keyboard extensions.
|
||||
|
||||
## List of permissions FlorisBoard requests
|
||||
Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List-of-permissions-FlorisBoard-requests)
|
||||
to get more information on this topic.
|
||||
@@ -80,8 +99,6 @@ to get more information on this topic.
|
||||
## Used libraries, components and icons
|
||||
* [AndroidX libraries](https://github.com/androidx/androidx) by
|
||||
[Android Jetpack](https://github.com/androidx)
|
||||
* [Accompanist Compose UI libraries](https://github.com/google/accompanist/) by
|
||||
[Google](https://github.com/google)
|
||||
* [AboutLibraries](https://github.com/mikepenz/AboutLibraries) by
|
||||
[mikepenz](https://github.com/mikepenz)
|
||||
* [Google Material icons](https://github.com/google/material-design-icons) by
|
||||
@@ -92,8 +109,6 @@ to get more information on this topic.
|
||||
[Kotlin](https://github.com/Kotlin)
|
||||
* [KotlinX serialization library](https://github.com/Kotlin/kotlinx.serialization) by
|
||||
[Kotlin](https://github.com/Kotlin)
|
||||
* [ICU4C](https://github.com/unicode-org/icu) by
|
||||
[The Unicode Consortium](https://github.com/unicode-org)
|
||||
|
||||
Many thanks to [Nikolay Anzarov](https://www.behance.net/nikolayanzarov) ([@BloodRaven0](https://github.com/BloodRaven0)) for designing and providing the main app icons to this project!
|
||||
|
||||
@@ -113,3 +128,10 @@ 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.
|
||||
```
|
||||
|
||||
<!-- BEGIN SECTION: obtainium_links -->
|
||||
<!-- auto-generated link templates, do NOT edit by hand -->
|
||||
<!-- see fastlane/update-readme.sh -->
|
||||
[obtainium_preview]: https://apps.obtainium.imranr.dev/redirect.html?r=obtainium://app/%7B%22id%22%3A%22dev.patrickgold.florisboard.beta%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fflorisboard%2Fflorisboard%22%2C%22author%22%3A%22florisboard%22%2C%22name%22%3A%22FlorisBoard%20Preview%22%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Atrue%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22preview%5C%22%7D%22%7D%0A
|
||||
[obtainium_stable]: https://apps.obtainium.imranr.dev/redirect.html?r=obtainium://app/%7B%22id%22%3A%22dev.patrickgold.florisboard%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fflorisboard%2Fflorisboard%22%2C%22author%22%3A%22florisboard%22%2C%22name%22%3A%22FlorisBoard%20Stable%22%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22stable%5C%22%7D%22%7D%0A
|
||||
<!-- END SECTION: obtainium_links -->
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.plugin.compose)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.mannodermaus.android.junit5)
|
||||
@@ -32,7 +34,7 @@ val projectBuildToolsVersion: String by project
|
||||
val projectNdkVersion: String by project
|
||||
val projectVersionCode: String by project
|
||||
val projectVersionName: String by project
|
||||
val projectVersionNameSuffix: String by project
|
||||
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "")
|
||||
|
||||
android {
|
||||
namespace = "dev.patrickgold.florisboard"
|
||||
@@ -48,7 +50,6 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xallow-result-return-type",
|
||||
"-opt-in=kotlin.contracts.ExperimentalContracts",
|
||||
"-Xjvm-default=all-compatibility",
|
||||
)
|
||||
@@ -59,13 +60,13 @@ android {
|
||||
minSdk = projectMinSdk.toInt()
|
||||
targetSdk = projectTargetSdk.toInt()
|
||||
versionCode = projectVersionCode.toInt()
|
||||
versionName = projectVersionName
|
||||
versionName = projectVersionName.substringBefore("-")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField("String", "BUILD_COMMIT_HASH", "\"${getGitCommitHash()}\"")
|
||||
buildConfigField("String", "FLADDONS_API_VERSION", "\"v~draft2\"")
|
||||
buildConfigField("String", "FLADDONS_STORE_URL", "\"fladdonstest.patrickgold.dev\"")
|
||||
buildConfigField("String", "FLADDONS_STORE_URL", "\"beta.addons.florisboard.org\"")
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
@@ -99,14 +100,10 @@ android {
|
||||
compose = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("debug") {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug-${getGitCommitHash(short = true)}"
|
||||
versionNameSuffix = "-debug+${getGitCommitHash(short = true)}"
|
||||
|
||||
isDebuggable = true
|
||||
isJniDebuggable = false
|
||||
@@ -165,14 +162,14 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
composeCompiler {
|
||||
// DO NOT ENABLE STRONG SKIPPING! This project currently relies on
|
||||
// recomposition on parent state change to update the UI correctly.
|
||||
featureFlags.add(ComposeFeatureFlag.StrongSkipping.disabled())
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "282a1b421e498fd0e21c055b6a4315e0",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "clipboard_history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL, `isRemoteDevice` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "_id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "text",
|
||||
"columnName": "text",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "uri",
|
||||
"columnName": "uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "creationTimestampMs",
|
||||
"columnName": "creationTimestampMs",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isPinned",
|
||||
"columnName": "isPinned",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mimeTypes",
|
||||
"columnName": "mimeTypes",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isSensitive",
|
||||
"columnName": "isSensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isRemoteDevice",
|
||||
"columnName": "isRemoteDevice",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_clipboard_history__id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '282a1b421e498fd0e21c055b6a4315e0')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,11 @@
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/floris_app_icon"
|
||||
android:label="@string/floris_app_name"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:roundIcon="@mipmap/floris_app_icon_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/FlorisAppTheme"
|
||||
tools:targetApi="s">
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<!-- Allow app to be profiled for benchmarking and baseline profile generation -->
|
||||
<profileable android:shell="true"/>
|
||||
@@ -86,7 +87,10 @@
|
||||
android:theme="@style/FlorisAppTheme.Splash"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<data android:scheme="florisboard" android:host="app-ui"/>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="ui" android:host="florisboard" android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="Import Extension">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
@@ -128,7 +132,7 @@
|
||||
|
||||
<!-- Copy to Clipboard Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.FlorisCopyToClipboardActivity"
|
||||
android:name="dev.patrickgold.florisboard.ime.clipboard.FlorisCopyToClipboardActivity"
|
||||
android:theme="@style/FlorisAppTheme.Transparent"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
||||
@@ -392,6 +392,12 @@
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "tamil",
|
||||
"label": "Tamil",
|
||||
"authors": [ "Clem0908" ],
|
||||
"direction": "ltr"
|
||||
},
|
||||
{
|
||||
"id": "thai_kedmanee",
|
||||
"label": "Thai Kedmanee",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"all": {
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "$": "auto_text_key", "code": 269, "label": "č" },
|
||||
{ "$": "auto_text_key", "code": 269, "label": "č" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;grinsendes Gesicht;Gesicht|grinsendes Gesicht|lol|lustig
|
||||
😃;grinsendes Gesicht mit großen Augen;Gesicht|grinsendes Gesicht mit großen Augen|lächeln|lol|lustig
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;grinning face;face|grin|grinning face
|
||||
😃;grinning face with big eyes;face|grinning face with big eyes|mouth|open|smile
|
||||
@@ -3788,3 +3791,4 @@
|
||||
🏴;flag: England;flag|flag: England
|
||||
🏴;flag: Scotland;flag|flag: Scotland
|
||||
🏴;flag: Wales;flag|flag: Wales
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;cara sonriendo;cara|cara sonriendo|divertido|feliz|sonrisa
|
||||
😃;cara sonriendo con ojos grandes;cara|cara sonriendo con ojos grandes|divertido|risa|sonriendo
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;visage rieur;sourire|visage rieur
|
||||
😃;visage souriant avec de grands yeux;sourire|visage souriant avec de grands yeux
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;faccina con un gran sorriso;faccina|faccina che sogghigna|faccina con un gran sorriso|risata|sogghignare
|
||||
😃;faccina con un gran sorriso e occhi spalancati;faccina|faccina con un gran sorriso e occhi spalancati|faccina sorridente|risata|sorridere
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;rosto risonho;lol|rindo|risada|rosto|rosto risonho
|
||||
😃;rosto risonho com olhos bem abertos;aberto|boca|rosto|rosto risonho com olhos bem abertos|sorriso
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Auto-generated by emojicon.py using CLDR v45
|
||||
# DO NOT EDIT MANUALLY!
|
||||
|
||||
[smileys_emotion]
|
||||
😀;;
|
||||
😃;;
|
||||
@@ -3788,3 +3791,4 @@
|
||||
🏴;;
|
||||
🏴;;
|
||||
🏴;;
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@@ -87,6 +87,7 @@ import dev.patrickgold.florisboard.ime.keyboard.ProvideKeyboardRowBaseHeight
|
||||
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
|
||||
import dev.patrickgold.florisboard.ime.lifecycle.LifecycleInputMethodService
|
||||
import dev.patrickgold.florisboard.ime.media.MediaInputLayout
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedPanel
|
||||
import dev.patrickgold.florisboard.ime.sheet.BottomSheetHostUi
|
||||
@@ -370,7 +371,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
flogInfo { "(no args)" }
|
||||
super.onFinishInput()
|
||||
editorInstance.handleFinishInput()
|
||||
nlpManager.clearInlineSuggestions()
|
||||
NlpInlineAutofill.clearInlineSuggestions()
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
@@ -437,23 +438,26 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? {
|
||||
return if (prefs.smartbar.enabled.get() && prefs.suggestion.api30InlineSuggestionsEnabled.get()) {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"Creating inline suggestions request because Smartbar and inline suggestions are enabled."
|
||||
}
|
||||
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
|
||||
val spec = InlinePresentationSpec.Builder(InlineSuggestionUiSmallestSize, InlineSuggestionUiBiggestSize)
|
||||
.setStyle(stylesBundle)
|
||||
.build()
|
||||
InlineSuggestionsRequest.Builder(listOf(spec)).let { request ->
|
||||
request.setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
|
||||
request.build()
|
||||
}
|
||||
} else {
|
||||
if (!prefs.smartbar.enabled.get() || !prefs.suggestion.api30InlineSuggestionsEnabled.get()) {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"Ignoring inline suggestions request because Smartbar and/or inline suggestions are disabled."
|
||||
}
|
||||
null
|
||||
return null
|
||||
}
|
||||
|
||||
flogInfo(LogTopic.IMS_EVENTS) { "Creating inline suggestions request" }
|
||||
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
|
||||
val spec = InlinePresentationSpec.Builder(
|
||||
InlineSuggestionUiSmallestSize,
|
||||
InlineSuggestionUiBiggestSize,
|
||||
).run {
|
||||
setStyle(stylesBundle)
|
||||
build()
|
||||
}
|
||||
|
||||
return InlineSuggestionsRequest.Builder(listOf(spec)).run {
|
||||
setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
|
||||
build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,8 +467,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
flogInfo(LogTopic.IMS_EVENTS) {
|
||||
"Received inline suggestions response with ${inlineSuggestions.size} suggestion(s) provided."
|
||||
}
|
||||
nlpManager.showInlineSuggestions(inlineSuggestions)
|
||||
return true
|
||||
return NlpInlineAutofill.showInlineSuggestions(this, inlineSuggestions)
|
||||
}
|
||||
|
||||
override fun onComputeInsets(outInsets: Insets?) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
* Copyright (C) 2021-2024 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,8 +31,9 @@ import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.ime.keyboard.SpaceBarMode
|
||||
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHairStyle
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiRecentlyUsedHelper
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
@@ -56,6 +57,8 @@ import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceType
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
|
||||
|
||||
@@ -171,6 +174,10 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "devtools__show_spelling_overlay",
|
||||
default = false,
|
||||
)
|
||||
val showInlineAutofillOverlay = boolean(
|
||||
key = "devtools__show_inline_autofill_overlay",
|
||||
default = false,
|
||||
)
|
||||
val showKeyTouchBoundaries = boolean(
|
||||
key = "devtools__show_touch_boundaries",
|
||||
default = false,
|
||||
@@ -193,6 +200,67 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
)
|
||||
}
|
||||
|
||||
val emoji = Emoji()
|
||||
inner class Emoji {
|
||||
val preferredSkinTone = enum(
|
||||
key = "emoji__preferred_skin_tone",
|
||||
default = EmojiSkinTone.DEFAULT,
|
||||
)
|
||||
val preferredHairStyle = enum(
|
||||
key = "emoji__preferred_hair_style",
|
||||
default = EmojiHairStyle.DEFAULT,
|
||||
)
|
||||
val historyEnabled = boolean(
|
||||
key = "emoji__history_enabled",
|
||||
default = true,
|
||||
)
|
||||
val historyData = custom(
|
||||
key = "emoji__history_data",
|
||||
default = EmojiHistory.Empty,
|
||||
serializer = EmojiHistory.Serializer,
|
||||
)
|
||||
val historyPinnedUpdateStrategy = enum(
|
||||
key = "emoji__history_pinned_update_strategy",
|
||||
default = EmojiHistory.UpdateStrategy.MANUAL_SORT_PREPEND,
|
||||
)
|
||||
val historyPinnedMaxSize = int(
|
||||
key = "emoji__history_pinned_max_size",
|
||||
default = EmojiHistory.MaxSizeUnlimited,
|
||||
)
|
||||
val historyRecentUpdateStrategy = enum(
|
||||
key = "emoji__history_recent_update_strategy",
|
||||
default = EmojiHistory.UpdateStrategy.AUTO_SORT_PREPEND,
|
||||
)
|
||||
val historyRecentMaxSize = int(
|
||||
key = "emoji__history_recent_max_size",
|
||||
default = 90,
|
||||
)
|
||||
val suggestionEnabled = boolean(
|
||||
key = "emoji__suggestion_enabled",
|
||||
default = true,
|
||||
)
|
||||
val suggestionType = enum(
|
||||
key = "emoji__suggestion_type",
|
||||
default = EmojiSuggestionType.LEADING_COLON,
|
||||
)
|
||||
val suggestionUpdateHistory = boolean(
|
||||
key = "emoji__suggestion_update_history",
|
||||
default = true,
|
||||
)
|
||||
val suggestionCandidateShowName = boolean(
|
||||
key = "emoji__suggestion_candidate_show_name",
|
||||
default = false,
|
||||
)
|
||||
val suggestionQueryMinLength = int(
|
||||
key = "emoji__suggestion_query_min_length",
|
||||
default = 3,
|
||||
)
|
||||
val suggestionCandidateMaxCount = int(
|
||||
key = "emoji__suggestion_candidate_max_count",
|
||||
default = 5,
|
||||
)
|
||||
}
|
||||
|
||||
val gestures = Gestures()
|
||||
inner class Gestures {
|
||||
val swipeUp = enum(
|
||||
@@ -530,27 +598,6 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
)
|
||||
}
|
||||
|
||||
val media = Media()
|
||||
inner class Media {
|
||||
val emojiRecentlyUsed = custom(
|
||||
key = "media__emoji_recently_used",
|
||||
default = emptyList(),
|
||||
serializer = EmojiRecentlyUsedHelper.Serializer,
|
||||
)
|
||||
val emojiRecentlyUsedMaxSize = int(
|
||||
key = "media__emoji_recently_used_max_size",
|
||||
default = 90,
|
||||
)
|
||||
val emojiPreferredSkinTone = enum(
|
||||
key = "media__emoji_preferred_skin_tone",
|
||||
default = EmojiSkinTone.DEFAULT,
|
||||
)
|
||||
val emojiPreferredHairStyle = enum(
|
||||
key = "media__emoji_preferred_hair_style",
|
||||
default = EmojiHairStyle.DEFAULT,
|
||||
)
|
||||
}
|
||||
|
||||
val smartbar = Smartbar()
|
||||
inner class Smartbar {
|
||||
val enabled = boolean(
|
||||
@@ -574,6 +621,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
key = "smartbar__shared_actions_expanded",
|
||||
default = false,
|
||||
)
|
||||
@Deprecated("Always enabled due to UX issues")
|
||||
val sharedActionsAutoExpandCollapse = boolean(
|
||||
key = "smartbar__shared_actions_auto_expand_collapse",
|
||||
default = true,
|
||||
@@ -683,8 +731,7 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
"gestures__space_bar_swipe_right", "gestures__space_bar_long_press", "gestures__delete_key_swipe_left",
|
||||
"gestures__delete_key_long_press", "keyboard__hinted_number_row_mode", "keyboard__hinted_symbols_mode",
|
||||
"keyboard__utility_key_action", "keyboard__one_handed_mode", "keyboard__landscape_input_ui_mode",
|
||||
"localization__display_language_names_in", "media__emoji_preferred_skin_tone",
|
||||
"media__emoji_preferred_hair_style", "smartbar__primary_actions_row_type",
|
||||
"localization__display_language_names_in", "smartbar__primary_actions_row_type",
|
||||
"smartbar__secondary_actions_placement", "smartbar__secondary_actions_row_type", "spelling__language_mode",
|
||||
"suggestion__display_mode", "theme__mode", "theme__editor_display_colors_as",
|
||||
"theme__editor_display_kbd_after_dialogs", "theme__editor_level",
|
||||
@@ -706,6 +753,32 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate media prefs to emoji prefs
|
||||
// Keep migration rule until: 0.6 dev cycle
|
||||
"media__emoji_recently_used" -> {
|
||||
val emojiValues = entry.rawValue.split(";")
|
||||
val recent = emojiValues.map {
|
||||
dev.patrickgold.florisboard.ime.media.emoji.Emoji(it, "", emptyList())
|
||||
}
|
||||
val data = EmojiHistory(emptyList(), recent)
|
||||
entry.transform(key = "emoji__history_data", rawValue = Json.encodeToString(data))
|
||||
}
|
||||
"media__emoji_recently_used_max_size" -> {
|
||||
entry.transform(key = "emoji__history_recent_max_size")
|
||||
}
|
||||
"media__emoji_preferred_skin_tone" -> {
|
||||
entry.transform(
|
||||
key = "emoji__preferred_skin_tone",
|
||||
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
|
||||
)
|
||||
}
|
||||
"media__emoji_preferred_hair_style" -> {
|
||||
entry.transform(
|
||||
key = "emoji__preferred_hair_style",
|
||||
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
|
||||
)
|
||||
}
|
||||
|
||||
// Default: keep entry
|
||||
else -> entry.keepAsIs()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
|
||||
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
|
||||
import dev.patrickgold.florisboard.ime.keyboard.SpaceBarMode
|
||||
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
|
||||
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
|
||||
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
@@ -138,6 +140,30 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
)
|
||||
}
|
||||
},
|
||||
EmojiHistory.UpdateStrategy::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
key = EmojiHistory.UpdateStrategy.AUTO_SORT_PREPEND,
|
||||
label = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_prepend),
|
||||
description = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_prepend__description),
|
||||
)
|
||||
entry(
|
||||
key = EmojiHistory.UpdateStrategy.AUTO_SORT_APPEND,
|
||||
label = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_append),
|
||||
description = stringRes(R.string.enum__emoji_history_update_strategy__auto_sort_append__description),
|
||||
)
|
||||
entry(
|
||||
key = EmojiHistory.UpdateStrategy.MANUAL_SORT_PREPEND,
|
||||
label = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_prepend),
|
||||
description = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_prepend__description),
|
||||
)
|
||||
entry(
|
||||
key = EmojiHistory.UpdateStrategy.MANUAL_SORT_APPEND,
|
||||
label = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_append),
|
||||
description = stringRes(R.string.enum__emoji_history_update_strategy__manual_sort_append__description),
|
||||
)
|
||||
}
|
||||
},
|
||||
EmojiSkinTone::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
@@ -184,6 +210,20 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
|
||||
)
|
||||
}
|
||||
},
|
||||
EmojiSuggestionType::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
key = EmojiSuggestionType.LEADING_COLON,
|
||||
label = stringRes(R.string.enum__emoji_suggestion_type__leading_colon),
|
||||
description = stringRes(R.string.enum__emoji_suggestion_type__leading_colon__description),
|
||||
)
|
||||
entry(
|
||||
key = EmojiSuggestionType.INLINE_TEXT,
|
||||
label = stringRes(R.string.enum__emoji_suggestion_type__inline_text),
|
||||
description = stringRes(R.string.enum__emoji_suggestion_type__inline_text__description),
|
||||
)
|
||||
}
|
||||
},
|
||||
ExtendedActionsPlacement::class to DEFAULT to {
|
||||
listPrefEntries {
|
||||
entry(
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -36,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.NavController
|
||||
@@ -46,17 +48,18 @@ import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
|
||||
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
|
||||
import dev.patrickgold.florisboard.cacheManager
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.hideAppIcon
|
||||
import org.florisboard.lib.android.showAppIcon
|
||||
import dev.patrickgold.florisboard.lib.compose.LocalPreviewFieldController
|
||||
import dev.patrickgold.florisboard.lib.compose.PreviewKeyboardField
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
import dev.patrickgold.florisboard.lib.compose.conditional
|
||||
import dev.patrickgold.florisboard.lib.compose.rememberPreviewFieldController
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.AppVersionUtils
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ProvideDefaultDialogPrefStrings
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.hideAppIcon
|
||||
import org.florisboard.lib.android.showAppIcon
|
||||
|
||||
enum class AppTheme(val id: String) {
|
||||
AUTO("auto"),
|
||||
@@ -76,7 +79,7 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
private var appTheme by mutableStateOf(AppTheme.AUTO)
|
||||
private var showAppIcon = true
|
||||
private var resourcesContext by mutableStateOf(this as Context)
|
||||
private var fileImportIntent by mutableStateOf<Intent?>(null)
|
||||
private var intentToBeHandled by mutableStateOf<Intent?>(null)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Splash screen should be installed before calling super.onCreate()
|
||||
@@ -141,19 +144,23 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
|
||||
if (intent?.action == Intent.ACTION_VIEW && intent.data != null) {
|
||||
fileImportIntent = intent
|
||||
if (intent.action == Intent.ACTION_VIEW && intent.categories?.contains(Intent.CATEGORY_BROWSABLE) == true) {
|
||||
intentToBeHandled = intent
|
||||
return
|
||||
}
|
||||
if (intent?.action == Intent.ACTION_SEND && intent.clipData != null) {
|
||||
fileImportIntent = intent
|
||||
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
|
||||
intentToBeHandled = intent
|
||||
return
|
||||
}
|
||||
fileImportIntent = null
|
||||
if (intent.action == Intent.ACTION_SEND && intent.clipData != null) {
|
||||
intentToBeHandled = intent
|
||||
return
|
||||
}
|
||||
intentToBeHandled = null
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -176,6 +183,9 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
modifier = Modifier
|
||||
//.statusBarsPadding()
|
||||
.navigationBarsPadding()
|
||||
.conditional(LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
displayCutoutPadding()
|
||||
}
|
||||
.imePadding(),
|
||||
) {
|
||||
Routes.AppNavHost(
|
||||
@@ -188,18 +198,22 @@ class FlorisAppActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(fileImportIntent) {
|
||||
val intent = fileImportIntent
|
||||
LaunchedEffect(intentToBeHandled) {
|
||||
val intent = intentToBeHandled
|
||||
if (intent != null) {
|
||||
val data = if (intent.action == Intent.ACTION_VIEW) {
|
||||
intent.data!!
|
||||
if (intent.action == Intent.ACTION_VIEW && intent.categories?.contains(Intent.CATEGORY_BROWSABLE) == true) {
|
||||
navController.handleDeepLink(intent)
|
||||
} else {
|
||||
intent.clipData!!.getItemAt(0).uri
|
||||
val data = if (intent.action == Intent.ACTION_VIEW) {
|
||||
intent.data!!
|
||||
} else {
|
||||
intent.clipData!!.getItemAt(0).uri
|
||||
}
|
||||
val workspace = runCatching { cacheManager.readFromUriIntoCache(data) }.getOrNull()
|
||||
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, workspace?.uuid))
|
||||
}
|
||||
val workspace = runCatching { cacheManager.readFromUriIntoCache(data) }.getOrNull()
|
||||
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, workspace?.uuid))
|
||||
}
|
||||
fileImportIntent = null
|
||||
intentToBeHandled = null
|
||||
}
|
||||
|
||||
SideEffect {
|
||||
|
||||
@@ -16,15 +16,25 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app
|
||||
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import dev.patrickgold.florisboard.app.devtools.AndroidLocalesScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.AndroidSettingsScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.DevtoolsScreen
|
||||
import dev.patrickgold.florisboard.app.devtools.ExportDebugLogScreen
|
||||
import dev.patrickgold.florisboard.app.ext.CheckUpdatesScreen
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionEditScreen
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionExportScreen
|
||||
import dev.patrickgold.florisboard.app.ext.ExtensionHomeScreen
|
||||
@@ -144,6 +154,8 @@ object Routes {
|
||||
|
||||
const val View = "ext/view/{id}"
|
||||
fun View(id: String) = View.curlyFormat("id" to id)
|
||||
|
||||
const val CheckUpdates = "ext/check-updates"
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -152,83 +164,106 @@ object Routes {
|
||||
navController: NavHostController,
|
||||
startDestination: String,
|
||||
) {
|
||||
fun NavGraphBuilder.composableWithDeepLink(
|
||||
route: String,
|
||||
content: @Composable (AnimatedContentScope.(NavBackStackEntry) -> Unit),
|
||||
) {
|
||||
composable(
|
||||
route = route,
|
||||
deepLinks = listOf(navDeepLink { uriPattern = "ui://florisboard/$route" }),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
NavHost(
|
||||
modifier = modifier,
|
||||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
enterTransition = {
|
||||
slideIn { IntOffset(it.width, 0) } + fadeIn()
|
||||
},
|
||||
exitTransition = {
|
||||
slideOut { IntOffset(-it.width, 0) } + fadeOut()
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideIn { IntOffset(-it.width, 0) } + fadeIn()
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOut { IntOffset(it.width, 0) } + fadeOut()
|
||||
}
|
||||
) {
|
||||
composable(Setup.Screen) { SetupScreen() }
|
||||
|
||||
composable(Settings.Home) { HomeScreen() }
|
||||
composableWithDeepLink(Settings.Home) { HomeScreen() }
|
||||
|
||||
composable(Settings.Localization) { LocalizationScreen() }
|
||||
composable(Settings.SelectLocale) { SelectLocaleScreen() }
|
||||
composable(Settings.LanguagePackManager) { navBackStack ->
|
||||
composableWithDeepLink(Settings.Localization) { LocalizationScreen() }
|
||||
composableWithDeepLink(Settings.SelectLocale) { SelectLocaleScreen() }
|
||||
composableWithDeepLink(Settings.LanguagePackManager) { navBackStack ->
|
||||
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
|
||||
LanguagePackManagerScreenAction.entries.firstOrNull { it.id == actionId }
|
||||
}
|
||||
LanguagePackManagerScreen(action)
|
||||
}
|
||||
composable(Settings.SubtypeAdd) { SubtypeEditorScreen(null) }
|
||||
composable(Settings.SubtypeEdit) { navBackStack ->
|
||||
composableWithDeepLink(Settings.SubtypeAdd) { SubtypeEditorScreen(null) }
|
||||
composableWithDeepLink(Settings.SubtypeEdit) { navBackStack ->
|
||||
val id = navBackStack.arguments?.getString("id")?.toLongOrNull()
|
||||
SubtypeEditorScreen(id)
|
||||
}
|
||||
|
||||
composable(Settings.Theme) { ThemeScreen() }
|
||||
composable(Settings.ThemeManager) { navBackStack ->
|
||||
composableWithDeepLink(Settings.Theme) { ThemeScreen() }
|
||||
composableWithDeepLink(Settings.ThemeManager) { navBackStack ->
|
||||
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
|
||||
ThemeManagerScreenAction.entries.firstOrNull { it.id == actionId }
|
||||
}
|
||||
ThemeManagerScreen(action)
|
||||
}
|
||||
|
||||
composable(Settings.Keyboard) { KeyboardScreen() }
|
||||
composable(Settings.InputFeedback) { InputFeedbackScreen() }
|
||||
composableWithDeepLink(Settings.Keyboard) { KeyboardScreen() }
|
||||
composableWithDeepLink(Settings.InputFeedback) { InputFeedbackScreen() }
|
||||
|
||||
composable(Settings.Smartbar) { SmartbarScreen() }
|
||||
composableWithDeepLink(Settings.Smartbar) { SmartbarScreen() }
|
||||
|
||||
composable(Settings.Typing) { TypingScreen() }
|
||||
composableWithDeepLink(Settings.Typing) { TypingScreen() }
|
||||
|
||||
composable(Settings.Dictionary) { DictionaryScreen() }
|
||||
composable(Settings.UserDictionary) { navBackStack ->
|
||||
composableWithDeepLink(Settings.Dictionary) { DictionaryScreen() }
|
||||
composableWithDeepLink(Settings.UserDictionary) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
UserDictionaryType.entries.firstOrNull { it.id == typeId }
|
||||
}
|
||||
UserDictionaryScreen(type!!)
|
||||
}
|
||||
|
||||
composable(Settings.Gestures) { GesturesScreen() }
|
||||
composableWithDeepLink(Settings.Gestures) { GesturesScreen() }
|
||||
|
||||
composable(Settings.Clipboard) { ClipboardScreen() }
|
||||
composableWithDeepLink(Settings.Clipboard) { ClipboardScreen() }
|
||||
|
||||
composable(Settings.Media) { MediaScreen() }
|
||||
composableWithDeepLink(Settings.Media) { MediaScreen() }
|
||||
|
||||
composable(Settings.Advanced) { AdvancedScreen() }
|
||||
composable(Settings.Backup) { BackupScreen() }
|
||||
composable(Settings.Restore) { RestoreScreen() }
|
||||
composableWithDeepLink(Settings.Advanced) { AdvancedScreen() }
|
||||
composableWithDeepLink(Settings.Backup) { BackupScreen() }
|
||||
composableWithDeepLink(Settings.Restore) { RestoreScreen() }
|
||||
|
||||
composable(Settings.About) { AboutScreen() }
|
||||
composable(Settings.ProjectLicense) { ProjectLicenseScreen() }
|
||||
composable(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
|
||||
composableWithDeepLink(Settings.About) { AboutScreen() }
|
||||
composableWithDeepLink(Settings.ProjectLicense) { ProjectLicenseScreen() }
|
||||
composableWithDeepLink(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
|
||||
|
||||
composable(Devtools.Home) { DevtoolsScreen() }
|
||||
composable(Devtools.AndroidLocales) { AndroidLocalesScreen() }
|
||||
composable(Devtools.AndroidSettings) { navBackStack ->
|
||||
composableWithDeepLink(Devtools.Home) { DevtoolsScreen() }
|
||||
composableWithDeepLink(Devtools.AndroidLocales) { AndroidLocalesScreen() }
|
||||
composableWithDeepLink(Devtools.AndroidSettings) { navBackStack ->
|
||||
val name = navBackStack.arguments?.getString("name")
|
||||
AndroidSettingsScreen(name)
|
||||
}
|
||||
composable(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
|
||||
composableWithDeepLink(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
|
||||
|
||||
composable(Ext.Home) { ExtensionHomeScreen() }
|
||||
composable(Ext.List) { navBackStack ->
|
||||
composableWithDeepLink(Ext.Home) { ExtensionHomeScreen() }
|
||||
composableWithDeepLink(Ext.List) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
ExtensionListScreenType.entries.firstOrNull { it.id == typeId }
|
||||
} ?: error("unknown type")
|
||||
val showUpdate = navBackStack.arguments?.getString("showUpdate")
|
||||
ExtensionListScreen(type, showUpdate == "true")
|
||||
}
|
||||
composable(Ext.Edit) { navBackStack ->
|
||||
composableWithDeepLink(Ext.Edit) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
val serialType = navBackStack.arguments?.getString("serial_type")
|
||||
ExtensionEditScreen(
|
||||
@@ -236,21 +271,24 @@ object Routes {
|
||||
createSerialType = serialType.takeIf { !it.isNullOrBlank() },
|
||||
)
|
||||
}
|
||||
composable(Ext.Export) { navBackStack ->
|
||||
composableWithDeepLink(Ext.Export) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
ExtensionExportScreen(id = extensionId.toString())
|
||||
}
|
||||
composable(Ext.Import) { navBackStack ->
|
||||
composableWithDeepLink(Ext.Import) { navBackStack ->
|
||||
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
|
||||
ExtensionImportScreenType.entries.firstOrNull { it.id == typeId }
|
||||
} ?: ExtensionImportScreenType.EXT_ANY
|
||||
val uuid = navBackStack.arguments?.getString("uuid")?.takeIf { it != "null" }
|
||||
ExtensionImportScreen(type, uuid)
|
||||
}
|
||||
composable(Ext.View) { navBackStack ->
|
||||
composableWithDeepLink(Ext.View) { navBackStack ->
|
||||
val extensionId = navBackStack.arguments?.getString("id")
|
||||
ExtensionViewScreen(id = extensionId.toString())
|
||||
}
|
||||
composableWithDeepLink(Ext.CheckUpdates) {
|
||||
CheckUpdatesScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,13 @@ package dev.patrickgold.florisboard.app.apptheme
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.devtools
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
@@ -40,10 +42,12 @@ import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import dev.patrickgold.florisboard.editorInstance
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -57,6 +61,7 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
val showPrimaryClip by prefs.devtools.showPrimaryClip.observeAsState()
|
||||
val showInputStateOverlay by prefs.devtools.showInputStateOverlay.observeAsState()
|
||||
val showSpellingOverlay by prefs.devtools.showSpellingOverlay.observeAsState()
|
||||
val showInlineAutofillOverlay by prefs.devtools.showInlineAutofillOverlay.observeAsState()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides Color.White,
|
||||
@@ -72,6 +77,9 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
|
||||
if (showSpellingOverlay) {
|
||||
DevtoolsSpellingOverlay()
|
||||
}
|
||||
if (showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
|
||||
DevtoolsInlineAutofillOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +125,6 @@ private fun DevtoolsInputStateOverlay() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun DevtoolsSpellingOverlay() {
|
||||
val context = LocalContext.current
|
||||
@@ -160,6 +167,25 @@ private fun DevtoolsSpellingOverlay() {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@Composable
|
||||
private fun DevtoolsInlineAutofillOverlay() {
|
||||
val inlineSuggestions by NlpInlineAutofill.suggestions.collectAsState()
|
||||
|
||||
DevtoolsOverlayBox(title = "Inline autofill overlay (${inlineSuggestions.size})") {
|
||||
for (inlineSuggestion in inlineSuggestions) {
|
||||
DevtoolsSubGroup(title = "NlpInlineSuggestion") {
|
||||
val info = inlineSuggestion.info
|
||||
DevtoolsText(text = "info.type: ${info.type}")
|
||||
DevtoolsText(text = "info.source: ${info.source}")
|
||||
DevtoolsText(text = "info.isPinned: ${info.isPinned}")
|
||||
val view = inlineSuggestion.view
|
||||
DevtoolsText(text = "view: ${view?.javaClass?.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DevtoolsOverlayBox(
|
||||
title: String,
|
||||
|
||||
@@ -36,6 +36,7 @@ import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
|
||||
class DebugOnPurposeCrashException : Exception(
|
||||
"Success! The app crashed purposely to display this beautiful screen we all love :)"
|
||||
@@ -84,6 +85,13 @@ fun DevtoolsScreen() = FlorisScreen {
|
||||
summary = stringRes(R.string.devtools__show_spelling_overlay__summary),
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.devtools.showInlineAutofillOverlay,
|
||||
title = stringRes(R.string.devtools__show_inline_autofill_overlay__label),
|
||||
summary = stringRes(R.string.devtools__show_inline_autofill_overlay__summary),
|
||||
enabledIf = { prefs.devtools.enabled isEqualTo true },
|
||||
visibleIf = { AndroidVersion.ATLEAST_API30_R },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.devtools.showKeyTouchBoundaries,
|
||||
title = stringRes(R.string.devtools__show_key_touch_boundaries__label),
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.devtools
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -35,19 +37,21 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.sp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisButton
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.devtools.Devtools
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
|
||||
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI and in localization
|
||||
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI
|
||||
@Composable
|
||||
fun ExportDebugLogScreen() = FlorisScreen {
|
||||
title = "Debug log"
|
||||
title = stringRes(R.string.devtools__debuglog__title)
|
||||
scrollable = false
|
||||
|
||||
val prefs by florisPreferenceModel()
|
||||
@@ -55,21 +59,36 @@ fun ExportDebugLogScreen() = FlorisScreen {
|
||||
val clipboardManager by context.clipboardManager()
|
||||
|
||||
var debugLog by remember { mutableStateOf<List<String>?>(null) }
|
||||
var formattedDebugLog by remember { mutableStateOf<List<String>?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
debugLog = Devtools.generateDebugLog(context, prefs, includeLogcat = true).lines()
|
||||
formattedDebugLog = Devtools.generateDebugLogForGithub(context, prefs, includeLogcat = true).lines()
|
||||
}
|
||||
|
||||
bottomBar {
|
||||
FlorisButton(
|
||||
onClick = {
|
||||
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
|
||||
context.showShortToast("Copied debug log to clipboard")
|
||||
},
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Export (copy to clipboard)",
|
||||
enabled = debugLog != null,
|
||||
)
|
||||
) {
|
||||
FlorisButton(
|
||||
onClick = {
|
||||
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
|
||||
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
|
||||
},
|
||||
modifier = Modifier,
|
||||
text = stringRes(R.string.devtools__debuglog__copy_log),
|
||||
enabled = debugLog != null,
|
||||
)
|
||||
FlorisButton(
|
||||
onClick = {
|
||||
clipboardManager.addNewPlaintext(formattedDebugLog!!.joinToString("\n"))
|
||||
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
|
||||
},
|
||||
text = stringRes(R.string.devtools__debuglog__copy_for_github),
|
||||
enabled = debugLog != null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
content {
|
||||
@@ -86,7 +105,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
|
||||
val log = debugLog
|
||||
if (log == null) {
|
||||
item {
|
||||
Text("Loading...")
|
||||
Text(stringRes(R.string.devtools__debuglog__loading))
|
||||
}
|
||||
} else {
|
||||
items(log) { logLine ->
|
||||
|
||||
@@ -16,13 +16,13 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.ext.Extension
|
||||
import dev.patrickgold.florisboard.lib.ext.generateUpdateUrl
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import org.florisboard.lib.kotlin.curlyFormat
|
||||
|
||||
@Composable
|
||||
@@ -43,7 +43,7 @@ fun UpdateBox(extensionIndex: List<Extension>) {
|
||||
) {
|
||||
FlorisTextButton(
|
||||
onClick = {
|
||||
context.launchUrl(extensionIndex.generateUpdateUrl(version = "v~draft2", host = "fladdonstest.patrickgold.dev"))
|
||||
context.launchUrl(extensionIndex.generateUpdateUrl())
|
||||
},
|
||||
icon = Icons.Outlined.FileDownload,
|
||||
text = stringRes(id = R.string.ext__update_box__search_for_updates)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package dev.patrickgold.florisboard.app.ext
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
|
||||
@Composable
|
||||
fun CheckUpdatesScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.ext__check_updates__title)
|
||||
|
||||
val context = LocalContext.current
|
||||
val extensionManager by context.extensionManager()
|
||||
val extensionIndex = extensionManager.combinedExtensionList()
|
||||
|
||||
content {
|
||||
UpdateBox(extensionIndex)
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.outlined.LibraryBooks
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material.icons.outlined.LibraryBooks
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
@@ -322,17 +322,17 @@ private fun EditScreen(
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
) {
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
onClick = { workspace.currentAction = EditorAction.ManageMetaData },
|
||||
icon = Icons.Default.Code,
|
||||
title = stringRes(R.string.ext__editor__metadata__title),
|
||||
)
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
onClick = { workspace.currentAction = EditorAction.ManageDependencies },
|
||||
icon = Icons.Outlined.LibraryBooks,
|
||||
icon = Icons.AutoMirrored.Outlined.LibraryBooks,
|
||||
title = stringRes(R.string.ext__editor__dependencies__title),
|
||||
)
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
onClick = { workspace.currentAction = EditorAction.ManageFiles },
|
||||
icon = vectorResource(R.drawable.ic_file_blank),
|
||||
title = stringRes(R.string.ext__editor__files__title),
|
||||
|
||||
@@ -21,14 +21,13 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.extensionManager
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
|
||||
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.florisboard.lib.util.launchUrl
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
|
||||
@Composable
|
||||
fun ExtensionHomeScreen() = FlorisScreen {
|
||||
@@ -74,28 +73,26 @@ fun ExtensionHomeScreen() = FlorisScreen {
|
||||
|
||||
UpdateBox(extensionIndex = extensionIndex)
|
||||
|
||||
PreferenceGroup(title = stringRes(id = R.string.ext__home__visit_store)) {
|
||||
Preference(
|
||||
icon = Icons.Default.Palette,
|
||||
title = stringRes(R.string.ext__list__ext_theme),
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_THEME,false))
|
||||
},
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Default.Keyboard,
|
||||
title = stringRes(R.string.ext__list__ext_keyboard),
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_KEYBOARD,false))
|
||||
},
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Default.Language,
|
||||
title = stringRes(R.string.ext__list__ext_languagepack),
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_LANGUAGEPACK,false))
|
||||
},
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
icon = Icons.Default.Palette,
|
||||
title = stringRes(R.string.ext__list__ext_theme),
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_THEME, false))
|
||||
},
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Default.Keyboard,
|
||||
title = stringRes(R.string.ext__list__ext_keyboard),
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_KEYBOARD, false))
|
||||
},
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Default.Language,
|
||||
title = stringRes(R.string.ext__list__ext_languagepack),
|
||||
onClick = {
|
||||
navController.navigate(Routes.Ext.List(ExtensionListScreenType.EXT_LANGUAGEPACK, false))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -224,7 +224,7 @@ private fun ExtensionMetaRowSimpleText(
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
if (showDividerAbove) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
Row(
|
||||
modifier = modifier
|
||||
@@ -246,7 +246,7 @@ private fun ExtensionMetaRowScrollableChips(
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
if (showDividerAbove) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
||||
@@ -25,13 +25,17 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.TriStateCheckbox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.FileProvider
|
||||
@@ -86,10 +90,23 @@ object Backup {
|
||||
var clipboardTextItems by mutableStateOf(false)
|
||||
var clipboardImageItems by mutableStateOf(false)
|
||||
var clipboardVideoItems by mutableStateOf(false)
|
||||
var clipboardData by mutableStateOf(false)
|
||||
|
||||
fun validateClipboardCheckbox(): Boolean {
|
||||
return clipboardTextItems && clipboardImageItems && clipboardVideoItems
|
||||
private var _clipboardData: MutableState<ToggleableState> = mutableStateOf(ToggleableState.Off)
|
||||
val clipboardData: State<ToggleableState> = _clipboardData
|
||||
|
||||
fun updateCheckboxState() {
|
||||
val newValue = if (
|
||||
!clipboardVideoItems && !clipboardImageItems && !clipboardTextItems
|
||||
) {
|
||||
ToggleableState.Off
|
||||
} else if (
|
||||
clipboardVideoItems && clipboardImageItems && clipboardTextItems
|
||||
) {
|
||||
ToggleableState.On
|
||||
} else {
|
||||
ToggleableState.Indeterminate
|
||||
}
|
||||
_clipboardData.value = newValue
|
||||
}
|
||||
|
||||
fun provideClipboardItems(): Boolean {
|
||||
@@ -309,28 +326,31 @@ internal fun BackupFilesSelector(
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_ime_theme),
|
||||
)
|
||||
|
||||
CheckboxListItem(
|
||||
TriStateCheckboxListItem(
|
||||
onClick = {
|
||||
if (!filesSelector.clipboardData) {
|
||||
filesSelector.clipboardTextItems = true
|
||||
if (
|
||||
filesSelector.clipboardData.value == ToggleableState.Off ||
|
||||
filesSelector.clipboardData.value == ToggleableState.Indeterminate
|
||||
) {
|
||||
filesSelector.clipboardImageItems = true
|
||||
filesSelector.clipboardVideoItems = true
|
||||
filesSelector.clipboardTextItems = true
|
||||
} else {
|
||||
filesSelector.clipboardTextItems = false
|
||||
filesSelector.clipboardImageItems = false
|
||||
filesSelector.clipboardVideoItems = false
|
||||
filesSelector.clipboardTextItems = false
|
||||
}
|
||||
filesSelector.clipboardData = filesSelector.validateClipboardCheckbox()
|
||||
filesSelector.updateCheckboxState()
|
||||
},
|
||||
checked = filesSelector.clipboardTextItems && filesSelector.clipboardImageItems && filesSelector.clipboardVideoItems,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history)
|
||||
state = filesSelector.clipboardData.value,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history),
|
||||
)
|
||||
|
||||
|
||||
CheckboxListItem(
|
||||
onClick = {
|
||||
filesSelector.clipboardTextItems = !filesSelector.clipboardTextItems
|
||||
filesSelector.clipboardData = filesSelector.validateClipboardCheckbox()
|
||||
filesSelector.updateCheckboxState()
|
||||
},
|
||||
checked = filesSelector.clipboardTextItems,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history__clipboard_text_items),
|
||||
@@ -339,7 +359,7 @@ internal fun BackupFilesSelector(
|
||||
CheckboxListItem(
|
||||
onClick = {
|
||||
filesSelector.clipboardImageItems = !filesSelector.clipboardImageItems
|
||||
filesSelector.clipboardData = filesSelector.validateClipboardCheckbox()
|
||||
filesSelector.updateCheckboxState()
|
||||
},
|
||||
checked = filesSelector.clipboardImageItems,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history__clipboard_image_items),
|
||||
@@ -348,7 +368,7 @@ internal fun BackupFilesSelector(
|
||||
CheckboxListItem(
|
||||
onClick = {
|
||||
filesSelector.clipboardVideoItems = !filesSelector.clipboardVideoItems
|
||||
filesSelector.clipboardData = filesSelector.validateClipboardCheckbox()
|
||||
filesSelector.updateCheckboxState()
|
||||
},
|
||||
checked = filesSelector.clipboardVideoItems,
|
||||
text = stringRes(R.string.backup_and_restore__back_up__files_clipboard_history__clipboard_video_items),
|
||||
@@ -382,6 +402,30 @@ internal fun CheckboxListItem(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TriStateCheckboxListItem(
|
||||
onClick: () -> Unit,
|
||||
state: ToggleableState,
|
||||
text: String,
|
||||
isSecondaryListItem: Boolean = false,
|
||||
) {
|
||||
JetPrefListItem(
|
||||
modifier = Modifier.rippleClickable(onClick = onClick),
|
||||
icon = {
|
||||
Row {
|
||||
if (isSecondaryListItem) {
|
||||
Spacer(modifier = Modifier.width(40.dp))
|
||||
}
|
||||
TriStateCheckbox(
|
||||
state = state,
|
||||
onClick = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
text = text,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun RadioListItem(
|
||||
onClick: () -> Unit,
|
||||
|
||||
@@ -138,7 +138,10 @@ fun RestoreScreen() = FlorisScreen {
|
||||
}
|
||||
restoreWorkspace = workspace
|
||||
}.onFailure { error ->
|
||||
context.showLongToast(R.string.backup_and_restore__restore__failure, "error_message" to error.localizedMessage)
|
||||
context.showLongToast(
|
||||
R.string.backup_and_restore__restore__failure,
|
||||
"error_message" to error.localizedMessage,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -176,15 +179,20 @@ fun RestoreScreen() = FlorisScreen {
|
||||
srcDir.copyRecursively(dstDir, overwrite = true)
|
||||
}
|
||||
}
|
||||
val clipboardManager = context.clipboardManager().value
|
||||
if (shouldReset) {
|
||||
clipboardManager.clearFullHistory()
|
||||
ClipboardFileStorage.resetClipboardFileStorage(context)
|
||||
}
|
||||
|
||||
if (restoreFilesSelector.provideClipboardItems()) {
|
||||
val clipboardFilesDir = workspace.outputDir.subDir("clipboard")
|
||||
val clipboardManager = context.clipboardManager().value
|
||||
|
||||
if (restoreFilesSelector.clipboardTextItems) {
|
||||
val clipboardItems = clipboardFilesDir.subFile(Backup.CLIPBOARD_TEXT_ITEMS_JSON_NAME)
|
||||
if (clipboardItems.exists()) {
|
||||
val clipboardItemsList = clipboardItems.readJson<List<ClipboardItem>>()
|
||||
clipboardManager.restoreHistory(shouldReset = shouldReset, items = clipboardItemsList.filter { it.type == ItemType.TEXT }, itemType = ItemType.TEXT)
|
||||
clipboardManager.restoreHistory(items = clipboardItemsList.filter { it.type == ItemType.TEXT })
|
||||
}
|
||||
}
|
||||
if (restoreFilesSelector.clipboardImageItems) {
|
||||
@@ -192,14 +200,18 @@ fun RestoreScreen() = FlorisScreen {
|
||||
if (clipboardItems.exists()) {
|
||||
val clipboardItemsList = clipboardItems.readJson<List<ClipboardItem>>()
|
||||
for (item in clipboardItemsList.filter { it.type == ItemType.IMAGE }) {
|
||||
ClipboardFileStorage.instertFileFromBackup(
|
||||
ClipboardFileStorage.insertFileFromBackupIfNotExisting(
|
||||
context,
|
||||
clipboardFilesDir.subFile(
|
||||
relPath = "${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/${item.uri!!.path!!.split('/').last()}"
|
||||
relPath = "${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/${
|
||||
item.uri!!.path!!.split(
|
||||
'/'
|
||||
).last()
|
||||
}"
|
||||
)
|
||||
)
|
||||
}
|
||||
clipboardManager.restoreHistory(shouldReset = shouldReset, items = clipboardItemsList.filter { it.type == ItemType.IMAGE }, itemType = ItemType.IMAGE)
|
||||
clipboardManager.restoreHistory(items = clipboardItemsList.filter { it.type == ItemType.IMAGE })
|
||||
}
|
||||
}
|
||||
if (restoreFilesSelector.clipboardVideoItems) {
|
||||
@@ -207,14 +219,18 @@ fun RestoreScreen() = FlorisScreen {
|
||||
if (clipboardItems.exists()) {
|
||||
val clipboardItemsList = clipboardItems.readJson<List<ClipboardItem>>()
|
||||
for (item in clipboardItemsList.filter { it.type == ItemType.VIDEO }) {
|
||||
ClipboardFileStorage.instertFileFromBackup(
|
||||
ClipboardFileStorage.insertFileFromBackupIfNotExisting(
|
||||
context,
|
||||
clipboardFilesDir.subFile(
|
||||
relPath = "${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/${item.uri!!.path!!.split('/').last()}"
|
||||
relPath = "${ClipboardFileStorage.CLIPBOARD_FILES_PATH}/${
|
||||
item.uri!!.path!!.split(
|
||||
'/'
|
||||
).last()
|
||||
}"
|
||||
)
|
||||
)
|
||||
}
|
||||
clipboardManager.restoreHistory(shouldReset = shouldReset, items = clipboardItemsList.filter { it.type == ItemType.VIDEO }, itemType = ItemType.VIDEO)
|
||||
clipboardManager.restoreHistory(items = clipboardItemsList.filter { it.type == ItemType.VIDEO })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +254,11 @@ fun RestoreScreen() = FlorisScreen {
|
||||
context.showLongToast(R.string.backup_and_restore__restore__success)
|
||||
navController.navigateUp()
|
||||
} catch (e: Throwable) {
|
||||
context.showLongToast(R.string.backup_and_restore__restore__failure, "error_message" to e.localizedMessage)
|
||||
e.printStackTrace()
|
||||
context.showLongToast(
|
||||
R.string.backup_and_restore__restore__failure,
|
||||
"error_message" to e.localizedMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -273,7 +293,10 @@ fun RestoreScreen() = FlorisScreen {
|
||||
runCatching {
|
||||
restoreDataFromFileSystemLauncher.launch("*/*")
|
||||
}.onFailure { error ->
|
||||
context.showLongToast(R.string.backup_and_restore__restore__failure, "error_message" to error.localizedMessage)
|
||||
context.showLongToast(
|
||||
R.string.backup_and_restore__restore__failure,
|
||||
"error_message" to error.localizedMessage,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
@@ -295,15 +318,15 @@ fun RestoreScreen() = FlorisScreen {
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
title = stringRes(R.string.backup_and_restore__restore__metadata),
|
||||
) {
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
icon = Icons.Default.Code,
|
||||
title = workspace.metadata.packageName,
|
||||
)
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
icon = Icons.Outlined.Info,
|
||||
title = "${workspace.metadata.versionName} (${workspace.metadata.versionCode})",
|
||||
)
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
icon = Icons.Default.Schedule,
|
||||
title = remember(workspace.metadata.timestamp) {
|
||||
val formatter = DateFormat.getDateTimeInstance()
|
||||
|
||||
@@ -24,8 +24,8 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
@@ -193,7 +193,7 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
|
||||
icon = if (currentLocale != null) {
|
||||
Icons.Default.Close
|
||||
} else {
|
||||
Icons.Default.ArrowBack
|
||||
Icons.AutoMirrored.Filled.ArrowBack
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ fun KeyboardScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.pref__keyboard__hinted_number_row_mode__label),
|
||||
summarySwitchDisabled = stringRes(R.string.state__disabled),
|
||||
entries = enumDisplayEntriesOf(KeyHintMode::class),
|
||||
enabledIf = { prefs.keyboard.numberRow.isFalse() }
|
||||
)
|
||||
ListPreference(
|
||||
listPref = prefs.keyboard.hintedSymbolsMode,
|
||||
|
||||
@@ -112,7 +112,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
|
||||
FlorisOutlinedBox(
|
||||
modifier = Modifier.defaultFlorisOutlinedBox(),
|
||||
) {
|
||||
this@content.Preference(
|
||||
Preference(
|
||||
onClick = { navController.navigate(
|
||||
Routes.Ext.Import(ExtensionImportScreenType.EXT_LANGUAGEPACK, null)
|
||||
) },
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.localization
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
@@ -24,8 +26,13 @@ import androidx.compose.material3.FloatingActionButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -33,8 +40,8 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.cacheManager
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.keyboard.LayoutType
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
@@ -46,7 +53,20 @@ import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.Preference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
internal val SubtypeSaver = Saver<MutableState<Subtype?>, String>(
|
||||
save = {
|
||||
Json.encodeToString<Subtype?>(it.value)
|
||||
},
|
||||
restore = {
|
||||
mutableStateOf(Json.decodeFromString(it))
|
||||
},
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LocalizationScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__localization__title)
|
||||
@@ -57,7 +77,7 @@ fun LocalizationScreen() = FlorisScreen {
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val subtypeManager by context.subtypeManager()
|
||||
val cacheManager by context.cacheManager()
|
||||
var chosenSubtypeToDelete: Subtype? by rememberSaveable(saver = SubtypeSaver) { mutableStateOf(null) }
|
||||
|
||||
floatingActionButton {
|
||||
ExtendedFloatingActionButton(
|
||||
@@ -84,7 +104,6 @@ fun LocalizationScreen() = FlorisScreen {
|
||||
entries = enumDisplayEntriesOf(DisplayLanguageNamesIn::class),
|
||||
)
|
||||
Preference(
|
||||
// icon = R.drawable.ic_edit,
|
||||
title = stringRes(R.string.settings__localization__language_pack_title),
|
||||
summary = stringRes(R.string.settings__localization__language_pack_summary),
|
||||
onClick = {
|
||||
@@ -118,17 +137,50 @@ fun LocalizationScreen() = FlorisScreen {
|
||||
DisplayLanguageNamesIn.NATIVE_LOCALE -> subtype.primaryLocale.displayName(subtype.primaryLocale)
|
||||
},
|
||||
summary = summary,
|
||||
onClick = {
|
||||
navController.navigate(
|
||||
Routes.Settings.SubtypeEdit(subtype.id)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.combinedClickable(
|
||||
onClick = {
|
||||
navController.navigate(
|
||||
Routes.Settings.SubtypeEdit(subtype.id)
|
||||
)
|
||||
},
|
||||
onLongClick = {
|
||||
chosenSubtypeToDelete = subtype
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//PreferenceGroup(title = stringRes(R.string.settings__localization__group_layouts__label)) {
|
||||
//}
|
||||
DeleteSubtypeConfirmationDialog(
|
||||
subtypeToDelete = chosenSubtypeToDelete,
|
||||
onDismiss = {
|
||||
chosenSubtypeToDelete = null
|
||||
},
|
||||
onConfirm = {
|
||||
chosenSubtypeToDelete?.let { subtypeManager.removeSubtype(subtypeToRemove = it) }
|
||||
chosenSubtypeToDelete = null
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeleteSubtypeConfirmationDialog(
|
||||
subtypeToDelete: Subtype?,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
subtypeToDelete?.let {
|
||||
JetPrefAlertDialog(
|
||||
title = stringRes(R.string.settings__localization__subtype_delete_confirmation_title),
|
||||
confirmLabel = stringRes(R.string.action__yes),
|
||||
dismissLabel = stringRes(R.string.action__no),
|
||||
onDismiss = onDismiss,
|
||||
onConfirm = onConfirm,
|
||||
) {
|
||||
Text(stringRes(R.string.settings__localization__subtype_delete_confirmation_warning))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +47,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.Routes
|
||||
@@ -207,7 +207,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
var layoutMap by subtypeEditor.layoutMap
|
||||
var nlpProviders by subtypeEditor.nlpProviders
|
||||
|
||||
var showSubtypePresetsDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showSubtypePresetsDialog by rememberSaveable { mutableStateOf(id == null) }
|
||||
var showSelectAsError by rememberSaveable { mutableStateOf(false) }
|
||||
var errorDialogStrId by rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
|
||||
@@ -225,6 +225,24 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
onDispose { selectLocaleScreenResult?.removeObserver(observer) }
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun SubtypePropertyDropdown(
|
||||
title: String,
|
||||
layoutType: LayoutType
|
||||
) {
|
||||
SubtypeProperty(title) {
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actions {
|
||||
if (id != null) {
|
||||
IconButton(onClick = {
|
||||
@@ -362,17 +380,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
onDismissRequest = { expanded = false },
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_characters_layout)) {
|
||||
val layoutType = LayoutType.CHARACTERS
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_characters_layout), LayoutType.CHARACTERS)
|
||||
|
||||
SubtypeGroupSpacer()
|
||||
|
||||
@@ -408,28 +416,9 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
|
||||
SubtypeGroupSpacer()
|
||||
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_symbols_layout)) {
|
||||
val layoutType = LayoutType.SYMBOLS
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_symbols2_layout)) {
|
||||
val layoutType = LayoutType.SYMBOLS2
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_symbols_layout), LayoutType.SYMBOLS)
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_symbols2_layout), LayoutType.SYMBOLS2)
|
||||
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_composer)) {
|
||||
val composerIds = remember(composers) {
|
||||
SelectListKeys + composers.keys
|
||||
@@ -469,64 +458,17 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
|
||||
|
||||
SubtypeGroupSpacer()
|
||||
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_numeric_layout)) {
|
||||
val layoutType = LayoutType.NUMERIC
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_numeric_advanced_layout)) {
|
||||
val layoutType = LayoutType.NUMERIC_ADVANCED
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_numeric_row_layout)) {
|
||||
val layoutType = LayoutType.NUMERIC_ROW
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_numeric_layout), LayoutType.NUMERIC)
|
||||
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_numeric_advanced_layout), LayoutType.NUMERIC_ADVANCED)
|
||||
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_numeric_row_layout), LayoutType.NUMERIC_ROW)
|
||||
|
||||
SubtypeGroupSpacer()
|
||||
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_phone_layout)) {
|
||||
val layoutType = LayoutType.PHONE
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypeProperty(stringRes(R.string.settings__localization__subtype_phone2_layout)) {
|
||||
val layoutType = LayoutType.PHONE2
|
||||
SubtypeLayoutDropdown(
|
||||
layoutType = layoutType,
|
||||
layouts = layoutExtensions[layoutType] ?: mapOf(),
|
||||
showSelectAsError = showSelectAsError,
|
||||
layoutMap = layoutMap,
|
||||
onLayoutMapChanged = { layoutMap = it },
|
||||
selectListValues = selectListValues,
|
||||
)
|
||||
}
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_phone_layout), LayoutType.PHONE)
|
||||
|
||||
SubtypePropertyDropdown(stringRes(R.string.settings__localization__subtype_phone2_layout), LayoutType.PHONE2)
|
||||
}
|
||||
|
||||
if (showSubtypePresetsDialog) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Patrick Goldinger
|
||||
* Copyright (C) 2024 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,43 +16,126 @@
|
||||
|
||||
package dev.patrickgold.florisboard.app.settings.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.EmojiSymbols
|
||||
import androidx.compose.material.icons.outlined.Schedule
|
||||
import androidx.compose.runtime.Composable
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
|
||||
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
|
||||
import dev.patrickgold.florisboard.lib.compose.pluralsRes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
|
||||
import dev.patrickgold.jetpref.datastore.ui.ListPreference
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
|
||||
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
|
||||
|
||||
@OptIn(ExperimentalJetPrefDatastoreUi::class)
|
||||
@Composable
|
||||
fun MediaScreen() = FlorisScreen {
|
||||
title = stringRes(R.string.settings__media__title)
|
||||
previewFieldVisible = true
|
||||
iconSpaceReserved = false
|
||||
iconSpaceReserved = true
|
||||
|
||||
content {
|
||||
ListPreference(
|
||||
prefs.media.emojiPreferredSkinTone,
|
||||
prefs.emoji.preferredSkinTone,
|
||||
title = stringRes(R.string.prefs__media__emoji_preferred_skin_tone),
|
||||
entries = enumDisplayEntriesOf(EmojiSkinTone::class),
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.media.emojiRecentlyUsedMaxSize,
|
||||
title = stringRes(R.string.prefs__media__emoji_recently_used_max_size),
|
||||
valueLabel = { maxSize ->
|
||||
if (maxSize == 0) {
|
||||
stringRes(R.string.general__unlimited)
|
||||
} else {
|
||||
pluralsRes(R.plurals.unit__items__written, maxSize, "v" to maxSize)
|
||||
}
|
||||
},
|
||||
min = 0,
|
||||
max = 120,
|
||||
stepIncrement = 1,
|
||||
)
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.prefs__media__emoji_history__title)) {
|
||||
SwitchPreference(
|
||||
prefs.emoji.historyEnabled,
|
||||
icon = Icons.Outlined.Schedule,
|
||||
title = stringRes(R.string.prefs__media__emoji_history_enabled),
|
||||
summary = stringRes(R.string.prefs__media__emoji_history_enabled__summary),
|
||||
)
|
||||
ListPreference(
|
||||
prefs.emoji.historyPinnedUpdateStrategy,
|
||||
title = stringRes(R.string.prefs__media__emoji_history_pinned_update_strategy),
|
||||
entries = enumDisplayEntriesOf(EmojiHistory.UpdateStrategy::class),
|
||||
enabledIf = { prefs.emoji.historyEnabled.isTrue() },
|
||||
)
|
||||
ListPreference(
|
||||
prefs.emoji.historyRecentUpdateStrategy,
|
||||
title = stringRes(R.string.prefs__media__emoji_history_recent_update_strategy),
|
||||
entries = enumDisplayEntriesOf(EmojiHistory.UpdateStrategy::class),
|
||||
enabledIf = { prefs.emoji.historyEnabled.isTrue() },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
primaryPref = prefs.emoji.historyPinnedMaxSize,
|
||||
secondaryPref = prefs.emoji.historyRecentMaxSize,
|
||||
title = stringRes(R.string.prefs__media__emoji_history_max_size),
|
||||
primaryLabel = stringRes(R.string.emoji__history__pinned),
|
||||
secondaryLabel = stringRes(R.string.emoji__history__recent),
|
||||
valueLabel = { maxSize ->
|
||||
if (maxSize == EmojiHistory.MaxSizeUnlimited) {
|
||||
stringRes(R.string.general__unlimited)
|
||||
} else {
|
||||
pluralsRes(R.plurals.unit__items__written, maxSize, "v" to maxSize)
|
||||
}
|
||||
},
|
||||
min = 0,
|
||||
max = 120,
|
||||
stepIncrement = 1,
|
||||
enabledIf = { prefs.emoji.historyEnabled.isTrue() },
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceGroup(title = stringRes(R.string.prefs__media__emoji_suggestion__title)) {
|
||||
SwitchPreference(
|
||||
prefs.emoji.suggestionEnabled,
|
||||
icon = Icons.Outlined.EmojiSymbols,
|
||||
title = stringRes(R.string.prefs__media__emoji_suggestion_enabled),
|
||||
summary = stringRes(R.string.prefs__media__emoji_suggestion_enabled__summary),
|
||||
)
|
||||
ListPreference(
|
||||
prefs.emoji.suggestionType,
|
||||
title = stringRes(R.string.prefs__media__emoji_suggestion_type),
|
||||
entries = enumDisplayEntriesOf(EmojiSuggestionType::class),
|
||||
enabledIf = { prefs.emoji.suggestionEnabled.isTrue() },
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.emoji.suggestionUpdateHistory,
|
||||
title = stringRes(R.string.prefs__media__emoji_suggestion_update_history),
|
||||
summary = stringRes(R.string.prefs__media__emoji_suggestion_update_history__summary),
|
||||
enabledIf = {
|
||||
prefs.emoji.suggestionEnabled.isTrue() && prefs.emoji.historyEnabled.isTrue()
|
||||
},
|
||||
)
|
||||
SwitchPreference(
|
||||
prefs.emoji.suggestionCandidateShowName,
|
||||
title = stringRes(R.string.prefs__media__emoji_suggestion_candidate_show_name),
|
||||
summary = stringRes(R.string.prefs__media__emoji_suggestion_candidate_show_name__summary),
|
||||
enabledIf = { prefs.emoji.suggestionEnabled.isTrue() },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.emoji.suggestionQueryMinLength,
|
||||
title = stringRes(R.string.prefs__media__emoji_suggestion_query_min_length),
|
||||
valueLabel = { length ->
|
||||
pluralsRes(R.plurals.unit__characters__written, length, "v" to length)
|
||||
},
|
||||
min = 1,
|
||||
max = 5,
|
||||
stepIncrement = 1,
|
||||
enabledIf = { prefs.emoji.suggestionEnabled.isTrue() },
|
||||
)
|
||||
DialogSliderPreference(
|
||||
prefs.emoji.suggestionCandidateMaxCount,
|
||||
title = stringRes(R.string.prefs__media__emoji_suggestion_candidate_max_count),
|
||||
valueLabel = { count ->
|
||||
pluralsRes(R.plurals.unit__candidates__written, count, "v" to count)
|
||||
},
|
||||
min = 1,
|
||||
max = 10,
|
||||
stepIncrement = 1,
|
||||
enabledIf = { prefs.emoji.suggestionEnabled.isTrue() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package dev.patrickgold.florisboard.app.settings.smartbar
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
|
||||
import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
|
||||
@@ -64,11 +65,16 @@ fun SmartbarScreen() = FlorisScreen {
|
||||
prefs.smartbar.layout isEqualTo SmartbarLayout.SUGGESTIONS_ACTIONS_EXTENDED
|
||||
},
|
||||
)
|
||||
// TODO: schedule to remove this preference in the future, but keep it for now so users
|
||||
// know why the setting is not available anymore. Also force enable it for UI display.
|
||||
SideEffect {
|
||||
prefs.smartbar.sharedActionsAutoExpandCollapse.set(true)
|
||||
}
|
||||
SwitchPreference(
|
||||
prefs.smartbar.sharedActionsAutoExpandCollapse,
|
||||
title = stringRes(R.string.pref__smartbar__shared_actions_auto_expand_collapse__label),
|
||||
summary = stringRes(R.string.pref__smartbar__shared_actions_auto_expand_collapse__summary),
|
||||
enabledIf = { prefs.smartbar.enabled isEqualTo true },
|
||||
summary = "[Since v0.4.1] Always enabled due to UX issues",
|
||||
enabledIf = { false },
|
||||
visibleIf = { prefs.smartbar.layout isEqualTo SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED },
|
||||
)
|
||||
ListPreference(
|
||||
|
||||
@@ -31,7 +31,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.HelpOutline
|
||||
import androidx.compose.material.icons.automirrored.filled.HelpOutline
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -455,7 +455,7 @@ private fun PropertyValueEditor(
|
||||
FlorisIconButton(
|
||||
onClick = { showSyntaxHelp = !showSyntaxHelp },
|
||||
modifier = Modifier.offset(x = 12.dp),
|
||||
icon = Icons.Default.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Filled.HelpOutline,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -40,8 +40,8 @@ import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||
import androidx.compose.foundation.text.selection.TextSelectionColors
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.HelpOutline
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.HelpOutline
|
||||
import androidx.compose.material.icons.filled.Pageview
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -225,7 +225,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.snygg__rule_selector__pressed)
|
||||
},
|
||||
selected = pressedSelector,
|
||||
color = if (pressedSelector) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { focusSelector = !focusSelector },
|
||||
@@ -235,7 +234,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.snygg__rule_selector__focus)
|
||||
},
|
||||
selected = focusSelector,
|
||||
color = if (focusSelector) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { disabledSelector = !disabledSelector },
|
||||
@@ -244,7 +242,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.snygg__rule_selector__disabled)
|
||||
},
|
||||
selected = disabledSelector,
|
||||
color = if (disabledSelector) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -291,7 +288,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.enum__input_shift_state__unshifted)
|
||||
},
|
||||
selected = shiftStateUnshifted,
|
||||
color = if (shiftStateUnshifted) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { shiftStateShiftedManual = !shiftStateShiftedManual },
|
||||
@@ -302,7 +298,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.enum__input_shift_state__shifted_manual)
|
||||
},
|
||||
selected = shiftStateShiftedManual,
|
||||
color = if (shiftStateShiftedManual) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { shiftStateShiftedAutomatic = !shiftStateShiftedAutomatic },
|
||||
@@ -313,7 +308,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.enum__input_shift_state__shifted_automatic)
|
||||
},
|
||||
selected = shiftStateShiftedAutomatic,
|
||||
color = if (shiftStateShiftedAutomatic) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
FlorisChip(
|
||||
onClick = { shiftStateCapsLock = !shiftStateCapsLock },
|
||||
@@ -324,7 +318,6 @@ internal fun EditRuleDialog(
|
||||
else -> stringRes(R.string.enum__input_shift_state__caps_lock)
|
||||
},
|
||||
selected = shiftStateCapsLock,
|
||||
color = if (shiftStateCapsLock) MaterialTheme.colorScheme.secondary else Color.Unspecified,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -479,7 +472,7 @@ private fun EditCodeValueDialog(
|
||||
FlorisIconButton(
|
||||
onClick = { showKeyCodesHelp = !showKeyCodesHelp },
|
||||
modifier = Modifier.offset(x = 12.dp),
|
||||
icon = Icons.Default.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Filled.HelpOutline,
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -48,15 +48,16 @@ import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.outlined.Backspace
|
||||
import androidx.compose.material.icons.filled.ClearAll
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.ToggleOff
|
||||
import androidx.compose.material.icons.filled.ToggleOn
|
||||
import androidx.compose.material.icons.filled.Videocam
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -87,6 +88,8 @@ import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.media.KeyboardLikeButton
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
@@ -193,6 +196,13 @@ fun ClipboardInputLayout(
|
||||
iconColor = headerStyle.foreground.solidColor(context),
|
||||
enabled = !deviceLocked && historyEnabled && !isPopupSurfaceActive(),
|
||||
)
|
||||
KeyboardLikeButton(
|
||||
inputEventDispatcher = keyboardManager.inputEventDispatcher,
|
||||
keyData = TextKeyData.DELETE,
|
||||
element = FlorisImeUi.ClipboardHeader,
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Outlined.Backspace, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +222,7 @@ fun ClipboardInputLayout(
|
||||
clip = true,
|
||||
clickAndSemanticsModifier = Modifier.combinedClickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(),
|
||||
indication = ripple(),
|
||||
enabled = popupItem == null,
|
||||
onLongClick = {
|
||||
popupItem = item
|
||||
@@ -307,7 +317,7 @@ fun ClipboardInputLayout(
|
||||
.fillMaxWidth()
|
||||
.run { if (contentScrollInsteadOfClip) this.florisVerticalScroll() else this }
|
||||
.padding(ItemPadding),
|
||||
text = text,
|
||||
text = item.displayText(),
|
||||
style = TextStyle(textDirection = TextDirection.ContentOrLtr),
|
||||
color = style.foreground.solidColor(context),
|
||||
fontSize = style.fontSize.spSize(),
|
||||
@@ -577,7 +587,7 @@ fun ClipboardInputLayout(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
.height(FlorisImeSizing.imeUiHeight()),
|
||||
) {
|
||||
HeaderRow()
|
||||
if (deviceLocked) {
|
||||
|
||||
@@ -28,11 +28,6 @@ import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardHistoryDao
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardHistoryDatabase
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
import org.florisboard.lib.android.AndroidClipboardManager
|
||||
import org.florisboard.lib.android.AndroidClipboardManager_OnPrimaryClipChangedListener
|
||||
import org.florisboard.lib.android.setOrClearPrimaryClip
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -45,6 +40,11 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.florisboard.lib.android.AndroidClipboardManager
|
||||
import org.florisboard.lib.android.AndroidClipboardManager_OnPrimaryClipChangedListener
|
||||
import org.florisboard.lib.android.setOrClearPrimaryClip
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
import org.florisboard.lib.kotlin.tryOrNull
|
||||
import java.io.Closeable
|
||||
|
||||
@@ -110,7 +110,9 @@ class ClipboardManager(
|
||||
val primaryClipFlow = _primaryClipFlow.asStateFlow()
|
||||
inline var primaryClip
|
||||
get() = primaryClipFlow.value
|
||||
private set(v) { _primaryClipFlow.value = v }
|
||||
private set(v) {
|
||||
_primaryClipFlow.value = v
|
||||
}
|
||||
|
||||
init {
|
||||
systemClipboardManager.addPrimaryClipChangedListener(this)
|
||||
@@ -278,6 +280,9 @@ class ClipboardManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unpinned items from the clipboard history
|
||||
*/
|
||||
fun clearHistory() {
|
||||
ioScope.launch {
|
||||
for (item in history().all) {
|
||||
@@ -287,6 +292,9 @@ class ClipboardManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the full clipboard history
|
||||
*/
|
||||
fun clearFullHistory() {
|
||||
ioScope.launch {
|
||||
for (item in history().all) {
|
||||
@@ -300,26 +308,15 @@ class ClipboardManager(
|
||||
/**
|
||||
* Restore the clipboard history from a [List]
|
||||
*
|
||||
* @param shouldReset if the history should be reset
|
||||
* @param items the [ClipboardItem] list with the new items
|
||||
*/
|
||||
fun restoreHistory(items: List<ClipboardItem>, shouldReset: Boolean, itemType: ItemType) {
|
||||
fun restoreHistory(items: List<ClipboardItem>) {
|
||||
ioScope.launch {
|
||||
if (shouldReset) {
|
||||
for (item in history().all) {
|
||||
item.close(appContext)
|
||||
}
|
||||
clipHistoryDao?.deleteAllFromType(itemType)
|
||||
for (item in items) {
|
||||
val currentHistory = this@ClipboardManager.history().all
|
||||
for (item in items) {
|
||||
if (!currentHistory.map { it.copy(id = 0) }.contains(item.copy(id = 0))) {
|
||||
this@ClipboardManager.insertClip(item.copy(id = 0))
|
||||
}
|
||||
} else {
|
||||
val currentHistory = this@ClipboardManager.history().all
|
||||
for (item in items) {
|
||||
if (!currentHistory.map { it.copy(id = 0) }.contains(item.copy(id = 0))) {
|
||||
this@ClipboardManager.insertClip(item.copy(id = 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,7 +341,7 @@ class ClipboardManager(
|
||||
|
||||
fun unpinClip(item: ClipboardItem) {
|
||||
ioScope.launch {
|
||||
clipHistoryDao?.update(item.copy(isPinned = false))
|
||||
clipHistoryDao?.update(item.copy(isPinned = false))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package dev.patrickgold.florisboard
|
||||
package dev.patrickgold.florisboard.ime.clipboard
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
@@ -10,9 +10,11 @@ import android.provider.MediaStore
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
@@ -28,6 +30,7 @@ import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
|
||||
@@ -84,6 +87,7 @@ class FlorisCopyToClipboardActivity : ComponentActivity() {
|
||||
} else {
|
||||
val uri: Uri? =
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
} else {
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
@@ -114,12 +118,14 @@ class FlorisCopyToClipboardActivity : ComponentActivity() {
|
||||
)
|
||||
}
|
||||
bitmap?.let {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(start = 64.dp, end = 64.dp, top = 32.dp, bottom = 8.dp),
|
||||
bitmap = bitmap!!.asImageBitmap(),
|
||||
contentDescription = null
|
||||
)
|
||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(start = 64.dp, end = 64.dp, top = 32.dp, bottom = 8.dp),
|
||||
bitmap = bitmap!!.asImageBitmap(),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
package dev.patrickgold.florisboard.ime.clipboard.provider
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipDescription.EXTRA_IS_REMOTE_DEVICE
|
||||
import android.content.ClipDescription.EXTRA_IS_SENSITIVE
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
@@ -24,6 +26,8 @@ import android.net.Uri
|
||||
import android.provider.BaseColumns
|
||||
import android.provider.MediaStore.Images.Media
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.database.getStringOrNull
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.ColumnInfo
|
||||
@@ -39,9 +43,14 @@ import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.Update
|
||||
import dev.patrickgold.florisboard.R
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.android.UriSerializer
|
||||
import org.florisboard.lib.android.query
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.florisboard.lib.android.stringRes
|
||||
import org.florisboard.lib.kotlin.tryOrNull
|
||||
|
||||
private const val CLIPBOARD_HISTORY_TABLE = "clipboard_history"
|
||||
@@ -67,7 +76,7 @@ enum class ItemType(val value: Int) {
|
||||
*/
|
||||
@Serializable
|
||||
@Entity(tableName = CLIPBOARD_HISTORY_TABLE)
|
||||
data class ClipboardItem(
|
||||
data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = BaseColumns._ID, index = true)
|
||||
var id: Long = 0,
|
||||
@@ -78,6 +87,10 @@ data class ClipboardItem(
|
||||
val creationTimestampMs: Long,
|
||||
val isPinned: Boolean,
|
||||
val mimeTypes: Array<String>,
|
||||
@EncodeDefault
|
||||
val isSensitive: Boolean = false,
|
||||
@EncodeDefault
|
||||
val isRemoteDevice: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
@@ -113,6 +126,18 @@ data class ClipboardItem(
|
||||
else -> ItemType.TEXT
|
||||
}
|
||||
|
||||
val isSensitive = if (AndroidVersion.ATLEAST_API33_T) {
|
||||
data.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
val isRemoteDevice = if (AndroidVersion.ATLEAST_API34_U) {
|
||||
data.description?.extras?.getBoolean(EXTRA_IS_REMOTE_DEVICE) ?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
val uri = if (type == ItemType.IMAGE || type == ItemType.VIDEO) {
|
||||
if (dataItem.uri.authority == ClipboardMediaProvider.AUTHORITY || !cloneUri) {
|
||||
dataItem.uri
|
||||
@@ -151,7 +176,21 @@ data class ClipboardItem(
|
||||
}
|
||||
}
|
||||
|
||||
return ClipboardItem(0, type, text, uri, System.currentTimeMillis(), false, mimeTypes)
|
||||
return ClipboardItem(0, type, text, uri, System.currentTimeMillis(), false, mimeTypes, isSensitive, isRemoteDevice)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
inline fun displayText(): String {
|
||||
val context = LocalContext.current
|
||||
return displayText(context)
|
||||
}
|
||||
|
||||
fun displayText(context: Context): String {
|
||||
return if (isSensitive) {
|
||||
context.stringRes(R.string.clipboard__sensitive_clip_content)
|
||||
} else {
|
||||
stringRepresentation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +332,7 @@ interface ClipboardHistoryDao {
|
||||
fun deleteAllUnpinned()
|
||||
}
|
||||
|
||||
@Database(entities = [ClipboardItem::class], version = 2)
|
||||
@Database(entities = [ClipboardItem::class], version = 3)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class ClipboardHistoryDatabase : RoomDatabase() {
|
||||
abstract fun clipboardItemDao(): ClipboardHistoryDao
|
||||
|
||||
@@ -61,7 +61,28 @@ object ClipboardFileStorage {
|
||||
return context.clipboardFilesDir.subFile(id.toString())
|
||||
}
|
||||
|
||||
fun instertFileFromBackup(context: Context, file: FsFile) {
|
||||
file.copyTo(context.clipboardFilesDir.subFile(file.name), overwrite = false)
|
||||
|
||||
/**
|
||||
* Insert file from backup if not existing
|
||||
*
|
||||
* @param context the application context
|
||||
* @param file the file to be inserted
|
||||
*/
|
||||
fun insertFileFromBackupIfNotExisting(context: Context, file: FsFile) {
|
||||
if (!context.clipboardFilesDir.subFile(file.name).isFile) {
|
||||
file.copyTo(context.clipboardFilesDir.subFile(file.name), overwrite = false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all files from the clipboard subdirectory
|
||||
*
|
||||
* @param context the application context
|
||||
*/
|
||||
fun resetClipboardFileStorage(context: Context) {
|
||||
context.clipboardFilesDir.listFiles()?.forEach {
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,15 @@ package dev.patrickgold.florisboard.ime.keyboard
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowRightAlt
|
||||
import androidx.compose.material.icons.filled.Backspace
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowRightAlt
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardReturn
|
||||
import androidx.compose.material.icons.automirrored.filled.Redo
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.automirrored.filled.Undo
|
||||
import androidx.compose.material.icons.automirrored.outlined.Assignment
|
||||
import androidx.compose.material.icons.automirrored.outlined.Backspace
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.ContentCut
|
||||
@@ -28,23 +35,16 @@ import androidx.compose.material.icons.filled.DeleteSweep
|
||||
import androidx.compose.material.icons.filled.Done
|
||||
import androidx.compose.material.icons.filled.FontDownload
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.KeyboardCapslock
|
||||
import androidx.compose.material.icons.filled.KeyboardReturn
|
||||
import androidx.compose.material.icons.filled.KeyboardVoice
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material.icons.filled.MoreHoriz
|
||||
import androidx.compose.material.icons.filled.Redo
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SelectAll
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material.icons.filled.SentimentSatisfiedAlt
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.SpaceBar
|
||||
import androidx.compose.material.icons.filled.Undo
|
||||
import androidx.compose.material.icons.outlined.Assignment
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
|
||||
@@ -179,10 +179,10 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
val evaluator = this
|
||||
return when (data.code) {
|
||||
KeyCode.ARROW_LEFT -> {
|
||||
Icons.Default.KeyboardArrowLeft
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowLeft
|
||||
}
|
||||
KeyCode.ARROW_RIGHT -> {
|
||||
Icons.Default.KeyboardArrowRight
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowRight
|
||||
}
|
||||
KeyCode.ARROW_UP -> {
|
||||
Icons.Default.KeyboardArrowUp
|
||||
@@ -213,23 +213,23 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
Icons.Default.KeyboardVoice
|
||||
}
|
||||
KeyCode.DELETE -> {
|
||||
Icons.Default.Backspace
|
||||
Icons.AutoMirrored.Outlined.Backspace
|
||||
}
|
||||
KeyCode.ENTER -> {
|
||||
val imeOptions = evaluator.editorInfo.imeOptions
|
||||
val inputAttributes = evaluator.editorInfo.inputAttributes
|
||||
if (imeOptions.flagNoEnterAction || inputAttributes.flagTextMultiLine) {
|
||||
Icons.Default.KeyboardReturn
|
||||
Icons.AutoMirrored.Filled.KeyboardReturn
|
||||
} else {
|
||||
when (imeOptions.action) {
|
||||
ImeOptions.Action.DONE -> Icons.Default.Done
|
||||
ImeOptions.Action.GO -> Icons.Default.ArrowRightAlt
|
||||
ImeOptions.Action.NEXT -> Icons.Default.ArrowRightAlt
|
||||
ImeOptions.Action.NONE -> Icons.Default.KeyboardReturn
|
||||
ImeOptions.Action.PREVIOUS -> Icons.Default.ArrowRightAlt
|
||||
ImeOptions.Action.GO -> Icons.AutoMirrored.Filled.ArrowRightAlt
|
||||
ImeOptions.Action.NEXT -> Icons.AutoMirrored.Filled.ArrowRightAlt
|
||||
ImeOptions.Action.NONE -> Icons.AutoMirrored.Filled.KeyboardReturn
|
||||
ImeOptions.Action.PREVIOUS -> Icons.AutoMirrored.Filled.ArrowRightAlt
|
||||
ImeOptions.Action.SEARCH -> Icons.Default.Search
|
||||
ImeOptions.Action.SEND -> Icons.Default.Send
|
||||
ImeOptions.Action.UNSPECIFIED -> Icons.Default.KeyboardReturn
|
||||
ImeOptions.Action.SEND -> Icons.AutoMirrored.Filled.Send
|
||||
ImeOptions.Action.UNSPECIFIED -> Icons.AutoMirrored.Filled.KeyboardReturn
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +237,7 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
Icons.Default.SentimentSatisfiedAlt
|
||||
}
|
||||
KeyCode.IME_UI_MODE_CLIPBOARD -> {
|
||||
Icons.Outlined.Assignment
|
||||
Icons.AutoMirrored.Outlined.Assignment
|
||||
}
|
||||
KeyCode.LANGUAGE_SWITCH -> {
|
||||
Icons.Default.Language
|
||||
@@ -263,10 +263,10 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
|
||||
}
|
||||
}
|
||||
KeyCode.UNDO -> {
|
||||
Icons.Default.Undo
|
||||
Icons.AutoMirrored.Filled.Undo
|
||||
}
|
||||
KeyCode.REDO -> {
|
||||
Icons.Default.Redo
|
||||
Icons.AutoMirrored.Filled.Redo
|
||||
}
|
||||
KeyCode.TOGGLE_ACTIONS_OVERFLOW -> {
|
||||
Icons.Default.MoreHoriz
|
||||
|
||||
@@ -142,9 +142,15 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
prefs.keyboard.utilityKeyEnabled.observeForever {
|
||||
updateActiveEvaluators()
|
||||
}
|
||||
prefs.keyboard.utilityKeyAction.observeForever {
|
||||
updateActiveEvaluators()
|
||||
}
|
||||
activeState.collectLatestIn(scope) {
|
||||
updateActiveEvaluators()
|
||||
}
|
||||
subtypeManager.subtypesFlow.collectLatestIn(scope) {
|
||||
updateActiveEvaluators()
|
||||
}
|
||||
subtypeManager.activeSubtypeFlow.collectLatestIn(scope) {
|
||||
reevaluateInputShiftState()
|
||||
updateActiveEvaluators()
|
||||
|
||||
@@ -29,7 +29,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Backspace
|
||||
import androidx.compose.material.icons.automirrored.outlined.Backspace
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -101,7 +101,7 @@ fun MediaInputLayout(
|
||||
inputEventDispatcher = keyboardManager.inputEventDispatcher,
|
||||
keyData = TextKeyData.DELETE,
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.Backspace, contentDescription = null)
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Backspace, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,12 +113,13 @@ internal fun KeyboardLikeButton(
|
||||
modifier: Modifier = Modifier,
|
||||
inputEventDispatcher: InputEventDispatcher,
|
||||
keyData: KeyData,
|
||||
element: String = FlorisImeUi.EmojiKey,
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
var isPressed by remember { mutableStateOf(false) }
|
||||
val keyStyle = FlorisImeTheme.style.get(
|
||||
element = FlorisImeUi.EmojiKey,
|
||||
element = element,
|
||||
code = keyData.code,
|
||||
isPressed = isPressed,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Patrick Goldinger
|
||||
* Copyright (C) 2024 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,11 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyData
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupSet
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyType
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.stream.IntStream
|
||||
import kotlin.streams.toList
|
||||
|
||||
@@ -42,7 +47,7 @@ enum class EmojiHairStyle(val id: Int) {
|
||||
BALD(0x1F9B3);
|
||||
}
|
||||
|
||||
data class Emoji(val value: String, val name: String, val keywords: List<String>) : KeyData {
|
||||
class Emoji(val value: String, val name: String, val keywords: List<String>) : KeyData {
|
||||
override val type = KeyType.CHARACTER
|
||||
override val code = KeyCode.UNSPECIFIED
|
||||
override val label = value
|
||||
@@ -73,4 +78,24 @@ data class Emoji(val value: String, val name: String, val keywords: List<String>
|
||||
override fun toString(): String {
|
||||
return "Emoji { value=$value, name=$name, keywords=$keywords }"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Emoji && value == other.value
|
||||
}
|
||||
|
||||
object ValueOnlySerializer : KSerializer<Emoji> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("EmojiValueOnly", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Emoji) {
|
||||
encoder.encodeString(value.value)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Emoji {
|
||||
return Emoji(decoder.decodeString(), "", emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +95,11 @@ data class EmojiData(
|
||||
// Assume it is a data line
|
||||
val data = line.split(";")
|
||||
if (data.size == 3) {
|
||||
val base = emojiEditorList?.first()
|
||||
val emoji = Emoji(
|
||||
value = data[0].trim(),
|
||||
name = data[1].trim(),
|
||||
keywords = data[2].split("|").map { it.trim() }
|
||||
name = base?.name ?: data[1].trim(),
|
||||
keywords = data[2].split("|").map { it.trim() },
|
||||
)
|
||||
if (emojiEditorList != null) {
|
||||
emojiEditorList!!.add(emoji)
|
||||
@@ -113,6 +114,14 @@ data class EmojiData(
|
||||
|
||||
for (category in byCategory.keys) {
|
||||
for (emojiSet in byCategory[category]!!) {
|
||||
if (emojiSet.emojis.size == 1) {
|
||||
// No variations provided, we fallback to using the base for all skin tones
|
||||
val base = emojiSet.emojis.first()
|
||||
for (skinTone in EmojiSkinTone.entries) {
|
||||
bySkinTone[skinTone]!!.add(base)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for (emoji in emojiSet.emojis) {
|
||||
bySkinTone[emoji.skinTone]!!.add(emoji)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.media.emoji
|
||||
|
||||
import dev.patrickgold.florisboard.app.AppPrefs
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceSerializer
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Serializable
|
||||
data class EmojiHistory(
|
||||
val pinned: List<@Serializable(with = Emoji.ValueOnlySerializer::class) Emoji>,
|
||||
val recent: List<@Serializable(with = Emoji.ValueOnlySerializer::class) Emoji>,
|
||||
) {
|
||||
fun edit(): Editor {
|
||||
return Editor(pinned.toMutableList(), recent.toMutableList())
|
||||
}
|
||||
|
||||
data class Editor(
|
||||
val pinned: MutableList<Emoji>,
|
||||
val recent: MutableList<Emoji>,
|
||||
) {
|
||||
fun build(): EmojiHistory {
|
||||
return EmojiHistory(pinned.toList(), recent.toList())
|
||||
}
|
||||
}
|
||||
|
||||
enum class UpdateStrategy(val isAutomatic: Boolean, val isPrepend: Boolean) {
|
||||
AUTO_SORT_PREPEND(isAutomatic = true, isPrepend = true),
|
||||
AUTO_SORT_APPEND(isAutomatic = true, isPrepend = false),
|
||||
MANUAL_SORT_PREPEND(isAutomatic = false, isPrepend = true),
|
||||
MANUAL_SORT_APPEND(isAutomatic = false, isPrepend = false);
|
||||
}
|
||||
|
||||
object Serializer : PreferenceSerializer<EmojiHistory> {
|
||||
override fun serialize(value: EmojiHistory): String {
|
||||
return Json.encodeToString(value)
|
||||
}
|
||||
|
||||
override fun deserialize(value: String): EmojiHistory {
|
||||
try {
|
||||
return Json.decodeFromString(value)
|
||||
} catch (e: Exception) {
|
||||
flogError { "Failed to deserialize EmojiHistory: $e" }
|
||||
return Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Empty = EmojiHistory(emptyList(), emptyList())
|
||||
|
||||
@Suppress("ConstPropertyName")
|
||||
const val MaxSizeUnlimited: Int = 0
|
||||
}
|
||||
}
|
||||
|
||||
object EmojiHistoryHelper {
|
||||
private var emojiGuard = Mutex(locked = false)
|
||||
|
||||
suspend fun markEmojiUsed(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
|
||||
if (!prefs.emoji.historyEnabled.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
val dataMut = prefs.emoji.historyData.get().edit()
|
||||
val pinnedUS = prefs.emoji.historyPinnedUpdateStrategy.get()
|
||||
val recentUS = prefs.emoji.historyRecentUpdateStrategy.get()
|
||||
val pinnedMaxSize = prefs.emoji.historyPinnedMaxSize.get().let { maxSize ->
|
||||
if (maxSize == EmojiHistory.MaxSizeUnlimited) Int.MAX_VALUE else maxSize
|
||||
}
|
||||
val recentMaxSize = prefs.emoji.historyRecentMaxSize.get().let { maxSize ->
|
||||
if (maxSize == EmojiHistory.MaxSizeUnlimited) Int.MAX_VALUE else maxSize
|
||||
}
|
||||
|
||||
val pinnedIndex = dataMut.pinned.indexOf(emoji)
|
||||
if (pinnedIndex != -1) {
|
||||
if (pinnedUS.isAutomatic) {
|
||||
dataMut.pinned.removeAt(pinnedIndex)
|
||||
dataMut.pinned.addWithStrategy(pinnedUS, emoji)
|
||||
} else {
|
||||
// manual sort, keep item in place
|
||||
}
|
||||
} else {
|
||||
val recentIndex = dataMut.recent.indexOf(emoji)
|
||||
if (recentIndex != -1) {
|
||||
if (recentUS.isAutomatic) {
|
||||
dataMut.recent.removeAt(recentIndex)
|
||||
dataMut.recent.addWithStrategy(recentUS, emoji)
|
||||
} else {
|
||||
// manual sort, keep item in place
|
||||
}
|
||||
} else {
|
||||
dataMut.recent.addWithStrategy(recentUS, emoji)
|
||||
}
|
||||
}
|
||||
|
||||
prefs.emoji.historyData.set(
|
||||
EmojiHistory(
|
||||
pinned = dataMut.pinned.takeWithStrategy(pinnedUS, pinnedMaxSize),
|
||||
recent = dataMut.recent.takeWithStrategy(recentUS, recentMaxSize),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun pinEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
|
||||
if (!prefs.emoji.historyEnabled.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
val dataMut = prefs.emoji.historyData.get().edit()
|
||||
val pinnedUS = prefs.emoji.historyPinnedUpdateStrategy.get()
|
||||
|
||||
val recentIndex = dataMut.recent.indexOf(emoji)
|
||||
if (recentIndex != -1) {
|
||||
dataMut.recent.removeAt(recentIndex)
|
||||
dataMut.pinned.addWithStrategy(pinnedUS, emoji)
|
||||
}
|
||||
|
||||
prefs.emoji.historyData.set(dataMut.build())
|
||||
}
|
||||
|
||||
suspend fun unpinEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
|
||||
if (!prefs.emoji.historyEnabled.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
val dataMut = prefs.emoji.historyData.get().edit()
|
||||
val recentUS = prefs.emoji.historyRecentUpdateStrategy.get()
|
||||
|
||||
val pinnedIndex = dataMut.pinned.indexOf(emoji)
|
||||
if (pinnedIndex != -1) {
|
||||
dataMut.pinned.removeAt(pinnedIndex)
|
||||
dataMut.recent.addWithStrategy(recentUS, emoji)
|
||||
}
|
||||
|
||||
prefs.emoji.historyData.set(dataMut.build())
|
||||
}
|
||||
|
||||
suspend fun moveEmoji(prefs: AppPrefs, emoji: Emoji, offset: Int) = emojiGuard.withLock {
|
||||
if (!prefs.emoji.historyEnabled.get() || offset == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val dataMut = prefs.emoji.historyData.get().edit()
|
||||
|
||||
val pinnedIndex = dataMut.pinned.indexOf(emoji)
|
||||
if (pinnedIndex != -1) {
|
||||
dataMut.pinned.move(pinnedIndex, offset)
|
||||
} else {
|
||||
val recentIndex = dataMut.recent.indexOf(emoji)
|
||||
if (recentIndex != -1) {
|
||||
dataMut.recent.move(recentIndex, offset)
|
||||
}
|
||||
}
|
||||
|
||||
prefs.emoji.historyData.set(dataMut.build())
|
||||
}
|
||||
|
||||
suspend fun removeEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
|
||||
if (!prefs.emoji.historyEnabled.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
val dataMut = prefs.emoji.historyData.get().edit()
|
||||
|
||||
val pinnedIndex = dataMut.pinned.indexOf(emoji)
|
||||
if (pinnedIndex != -1) {
|
||||
dataMut.pinned.removeAt(pinnedIndex)
|
||||
} else {
|
||||
val recentIndex = dataMut.recent.indexOf(emoji)
|
||||
if (recentIndex != -1) {
|
||||
dataMut.recent.removeAt(recentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
prefs.emoji.historyData.set(dataMut.build())
|
||||
}
|
||||
|
||||
private fun MutableList<Emoji>.addWithStrategy(strategy: EmojiHistory.UpdateStrategy, emoji: Emoji) {
|
||||
if (strategy.isPrepend) {
|
||||
add(0, emoji)
|
||||
} else {
|
||||
add(emoji)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<Emoji>.takeWithStrategy(
|
||||
strategy: EmojiHistory.UpdateStrategy,
|
||||
n: Int,
|
||||
): List<Emoji> {
|
||||
return if (strategy.isPrepend) {
|
||||
take(n)
|
||||
} else {
|
||||
takeLast(n)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<Emoji>.move(itemIndex: Int, offset: Int) {
|
||||
val newIndex = (itemIndex + offset).coerceIn(0..<size)
|
||||
val item = removeAt(itemIndex)
|
||||
if (newIndex == size) {
|
||||
add(item)
|
||||
} else {
|
||||
add(newIndex, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,14 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.PushPin
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.TabRowDefaults
|
||||
@@ -61,6 +67,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@@ -85,12 +92,11 @@ import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
|
||||
import dev.patrickgold.florisboard.lib.compose.header
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import dev.patrickgold.florisboard.lib.compose.stringRes
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.florisboard.lib.android.AndroidKeyguardManager
|
||||
import org.florisboard.lib.android.showShortToast
|
||||
import org.florisboard.lib.android.systemService
|
||||
@@ -117,6 +123,12 @@ private val VariantsTriangleShapeRtl = GenericShape { size, _ ->
|
||||
lineTo(x = 0f, y = size.height)
|
||||
}
|
||||
|
||||
data class EmojiMappingForView(
|
||||
val pinned: List<EmojiSet>,
|
||||
val recent: List<EmojiSet>,
|
||||
val simple: List<EmojiSet>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun EmojiPaletteView(
|
||||
fullEmojiMappings: EmojiData,
|
||||
@@ -150,16 +162,61 @@ fun EmojiPaletteView(
|
||||
|
||||
val deviceLocked = androidKeyguardManager.let { it.isDeviceLocked || it.isKeyguardLocked }
|
||||
|
||||
var activeCategory by remember { mutableStateOf(EmojiCategory.RECENTLY_USED) }
|
||||
val lazyListState = rememberLazyGridState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val preferredSkinTone by prefs.media.emojiPreferredSkinTone.observeAsState()
|
||||
val preferredSkinTone by prefs.emoji.preferredSkinTone.observeAsState()
|
||||
val emojiHistoryEnabled by prefs.emoji.historyEnabled.observeAsState()
|
||||
val fontSizeMultiplier = prefs.keyboard.fontSizeMultiplier()
|
||||
val emojiKeyStyle = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiKey)
|
||||
val emojiKeyFontSize = emojiKeyStyle.fontSize.spSize(default = EmojiDefaultFontSize) safeTimes fontSizeMultiplier
|
||||
val contentColor = emojiKeyStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
|
||||
var activeCategory by remember(emojiHistoryEnabled) {
|
||||
if (emojiHistoryEnabled) {
|
||||
mutableStateOf(EmojiCategory.RECENTLY_USED)
|
||||
} else {
|
||||
mutableStateOf(EmojiCategory.SMILEYS_EMOTION)
|
||||
}
|
||||
}
|
||||
var recentlyUsedVersion by remember { mutableIntStateOf(0) }
|
||||
val lazyListState = rememberLazyGridState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@Composable
|
||||
fun GridHeader(text: String) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = contentColor,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmojiKeyWrapper(
|
||||
emojiSet: EmojiSet,
|
||||
isPinned: Boolean = false,
|
||||
isRecent: Boolean = false,
|
||||
) {
|
||||
EmojiKey(
|
||||
emojiSet = emojiSet,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
preferredSkinTone = preferredSkinTone,
|
||||
isPinned = isPinned,
|
||||
isRecent = isRecent,
|
||||
contentColor = contentColor,
|
||||
fontSize = emojiKeyFontSize,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
onEmojiInput = { emoji ->
|
||||
keyboardManager.inputEventDispatcher.sendDownUp(emoji)
|
||||
scope.launch {
|
||||
EmojiHistoryHelper.markEmojiUsed(prefs, emoji)
|
||||
}
|
||||
},
|
||||
onHistoryAction = {
|
||||
recentlyUsedVersion++
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
EmojiCategoriesTabRow(
|
||||
activeCategory = activeCategory,
|
||||
@@ -167,6 +224,7 @@ fun EmojiPaletteView(
|
||||
scope.launch { lazyListState.scrollToItem(0) }
|
||||
activeCategory = category
|
||||
},
|
||||
emojiHistoryEnabled = emojiHistoryEnabled,
|
||||
)
|
||||
|
||||
Box(
|
||||
@@ -174,16 +232,25 @@ fun EmojiPaletteView(
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
) {
|
||||
var recentlyUsedVersion by remember { mutableIntStateOf(0) }
|
||||
val emojiMapping = if (activeCategory == EmojiCategory.RECENTLY_USED) {
|
||||
// Purposely using remember here to prevent recomposition, as this would cause rapid
|
||||
// emoji changes for the user when in recently used category.
|
||||
remember(recentlyUsedVersion) {
|
||||
prefs.media.emojiRecentlyUsed.get().map { EmojiSet(listOf(it)) }
|
||||
val data = prefs.emoji.historyData.get()
|
||||
EmojiMappingForView(
|
||||
pinned = data.pinned.map { EmojiSet(listOf(it)) },
|
||||
recent = data.recent.map { EmojiSet(listOf(it)) },
|
||||
simple = emptyList(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emojiMappings[activeCategory]!!
|
||||
EmojiMappingForView(
|
||||
pinned = emptyList(),
|
||||
recent = emptyList(),
|
||||
simple = emojiMappings[activeCategory]!!,
|
||||
)
|
||||
}
|
||||
val isEmojiHistoryEmpty = emojiMapping.pinned.isEmpty() && emojiMapping.recent.isEmpty()
|
||||
if (activeCategory == EmojiCategory.RECENTLY_USED && deviceLocked) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -191,29 +258,28 @@ fun EmojiPaletteView(
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__recently_used__phone_locked_message),
|
||||
text = stringRes(R.string.emoji__history__phone_locked_message),
|
||||
color = contentColor,
|
||||
)
|
||||
}
|
||||
} else if (activeCategory == EmojiCategory.RECENTLY_USED && emojiMapping.isEmpty()) {
|
||||
} else if (activeCategory == EmojiCategory.RECENTLY_USED && isEmojiHistoryEmpty) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.emoji__recently_used__empty_message),
|
||||
text = stringRes(R.string.emoji__history__empty_message),
|
||||
color = contentColor,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
text = stringRes(R.string.emoji__recently_used__removal_tip),
|
||||
text = stringRes(R.string.emoji__history__usage_tip),
|
||||
color = contentColor,
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
}
|
||||
}
|
||||
else key(emojiMapping) {
|
||||
} else key(emojiMapping) {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier
|
||||
@@ -222,35 +288,26 @@ fun EmojiPaletteView(
|
||||
columns = GridCells.Adaptive(minSize = EmojiBaseWidth),
|
||||
state = lazyListState,
|
||||
) {
|
||||
items(emojiMapping) { emojiSet ->
|
||||
EmojiKey(
|
||||
emojiSet = emojiSet,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
preferredSkinTone = preferredSkinTone,
|
||||
contentColor = contentColor,
|
||||
fontSize = emojiKeyFontSize,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
onEmojiInput = { emoji ->
|
||||
keyboardManager.inputEventDispatcher.sendDownUp(emoji)
|
||||
scope.launch {
|
||||
EmojiRecentlyUsedHelper.addEmoji(prefs, emoji)
|
||||
}
|
||||
},
|
||||
onLongPress = { emoji ->
|
||||
if (activeCategory == EmojiCategory.RECENTLY_USED) {
|
||||
scope.launch {
|
||||
EmojiRecentlyUsedHelper.removeEmoji(prefs, emoji)
|
||||
recentlyUsedVersion++
|
||||
withContext(Dispatchers.Main) {
|
||||
context.showShortToast(
|
||||
R.string.emoji__recently_used__removal_success_message,
|
||||
"emoji" to emoji.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
if (emojiMapping.pinned.isNotEmpty()) {
|
||||
header("header_pinned") {
|
||||
GridHeader(text = stringRes(R.string.emoji__history__pinned))
|
||||
}
|
||||
items(emojiMapping.pinned) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet, isPinned = true)
|
||||
}
|
||||
}
|
||||
if (emojiMapping.recent.isNotEmpty()) {
|
||||
header("header_recent") {
|
||||
GridHeader(text = stringRes(R.string.emoji__history__recent))
|
||||
}
|
||||
items(emojiMapping.recent) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet, isRecent = true)
|
||||
}
|
||||
}
|
||||
if (emojiMapping.simple.isNotEmpty()) {
|
||||
items(emojiMapping.simple) { emojiSet ->
|
||||
EmojiKeyWrapper(emojiSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,6 +320,7 @@ fun EmojiPaletteView(
|
||||
private fun EmojiCategoriesTabRow(
|
||||
activeCategory: EmojiCategory,
|
||||
onCategoryChange: (EmojiCategory) -> Unit,
|
||||
emojiHistoryEnabled: Boolean,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
@@ -271,7 +329,11 @@ private fun EmojiCategoriesTabRow(
|
||||
val unselectedContentColor = tabStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
val selectedContentColor = tabStyleFocused.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor())
|
||||
|
||||
val selectedTabIndex = EmojiCategoryValues.indexOf(activeCategory)
|
||||
val selectedTabIndex = if (emojiHistoryEnabled) {
|
||||
EmojiCategoryValues.indexOf(activeCategory)
|
||||
} else {
|
||||
EmojiCategoryValues.indexOf(activeCategory) - 1
|
||||
}
|
||||
TabRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -288,6 +350,9 @@ private fun EmojiCategoriesTabRow(
|
||||
},
|
||||
) {
|
||||
for (category in EmojiCategoryValues) {
|
||||
if (category == EmojiCategory.RECENTLY_USED && !emojiHistoryEnabled) {
|
||||
continue
|
||||
}
|
||||
Tab(
|
||||
onClick = {
|
||||
inputFeedbackController.keyPress(TextKeyData.UNSPECIFIED)
|
||||
@@ -311,11 +376,13 @@ private fun EmojiKey(
|
||||
emojiSet: EmojiSet,
|
||||
emojiCompatInstance: EmojiCompat?,
|
||||
preferredSkinTone: EmojiSkinTone,
|
||||
isPinned: Boolean,
|
||||
isRecent: Boolean,
|
||||
contentColor: Color,
|
||||
fontSize: TextUnit,
|
||||
fontSizeMultiplier: Float,
|
||||
onEmojiInput: (Emoji) -> Unit,
|
||||
onLongPress: (Emoji) -> Unit,
|
||||
onHistoryAction: () -> Unit,
|
||||
) {
|
||||
val inputFeedbackController = LocalInputFeedbackController.current
|
||||
val base = emojiSet.base(withSkinTone = preferredSkinTone)
|
||||
@@ -335,8 +402,7 @@ private fun EmojiKey(
|
||||
},
|
||||
onLongPress = {
|
||||
inputFeedbackController.keyLongPress(TextKeyData.UNSPECIFIED)
|
||||
onLongPress(base)
|
||||
if (variations.isNotEmpty()) {
|
||||
if (variations.isNotEmpty() || isPinned || isRecent) {
|
||||
showVariantsBox = true
|
||||
}
|
||||
},
|
||||
@@ -350,7 +416,7 @@ private fun EmojiKey(
|
||||
color = contentColor,
|
||||
fontSize = fontSize,
|
||||
)
|
||||
if (variations.isNotEmpty()) {
|
||||
if (variations.isNotEmpty() || isPinned || isRecent) {
|
||||
val shape = when (LocalLayoutDirection.current) {
|
||||
LayoutDirection.Ltr -> VariantsTriangleShapeLtr
|
||||
LayoutDirection.Rtl -> VariantsTriangleShapeRtl
|
||||
@@ -364,19 +430,34 @@ private fun EmojiKey(
|
||||
)
|
||||
}
|
||||
|
||||
EmojiVariationsPopup(
|
||||
variations = variations,
|
||||
visible = showVariantsBox,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
onEmojiTap = { emoji ->
|
||||
onEmojiInput(emoji)
|
||||
showVariantsBox = false
|
||||
},
|
||||
onDismiss = {
|
||||
showVariantsBox = false
|
||||
},
|
||||
)
|
||||
if (isPinned || isRecent) {
|
||||
EmojiHistoryPopup(
|
||||
emoji = base,
|
||||
visible = showVariantsBox,
|
||||
isCurrentlyPinned = isPinned,
|
||||
onHistoryAction = {
|
||||
onHistoryAction()
|
||||
showVariantsBox = false
|
||||
},
|
||||
onDismiss = {
|
||||
showVariantsBox = false
|
||||
},
|
||||
)
|
||||
} else {
|
||||
EmojiVariationsPopup(
|
||||
variations = variations,
|
||||
visible = showVariantsBox,
|
||||
emojiCompatInstance = emojiCompatInstance,
|
||||
fontSizeMultiplier = fontSizeMultiplier,
|
||||
onEmojiTap = { emoji ->
|
||||
onEmojiInput(emoji)
|
||||
showVariantsBox = false
|
||||
},
|
||||
onDismiss = {
|
||||
showVariantsBox = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +515,113 @@ private fun EmojiVariationsPopup(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun EmojiHistoryPopup(
|
||||
emoji: Emoji,
|
||||
visible: Boolean,
|
||||
isCurrentlyPinned: Boolean,
|
||||
onHistoryAction: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val prefs by florisPreferenceModel()
|
||||
val scope = rememberCoroutineScope()
|
||||
val popupStyle = FlorisImeTheme.style.get(element = FlorisImeUi.EmojiKeyPopup)
|
||||
val emojiKeyHeight = FlorisImeSizing.smartbarHeight
|
||||
val context = LocalContext.current
|
||||
val pinnedUS by prefs.emoji.historyPinnedUpdateStrategy.observeAsState()
|
||||
val recentUS by prefs.emoji.historyRecentUpdateStrategy.observeAsState()
|
||||
val showMoveLeft = isCurrentlyPinned && !pinnedUS.isAutomatic || !recentUS.isAutomatic
|
||||
val showMoveRight = isCurrentlyPinned && !pinnedUS.isAutomatic || !recentUS.isAutomatic
|
||||
|
||||
@Composable
|
||||
fun Action(icon: ImageVector, action: suspend () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
scope.launch {
|
||||
action()
|
||||
onHistoryAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
.width(EmojiBaseWidth)
|
||||
.height(emojiKeyHeight)
|
||||
.padding(all = 4.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = popupStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val numActions = 1
|
||||
if (visible) {
|
||||
Popup(
|
||||
alignment = Alignment.TopCenter,
|
||||
offset = with(LocalDensity.current) {
|
||||
val y = -emojiKeyHeight * ceil(numActions / 6f)
|
||||
IntOffset(x = 0, y = y.toPx().toInt())
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.widthIn(max = EmojiBaseWidth * 6)
|
||||
.snyggShadow(popupStyle)
|
||||
.snyggBorder(context, popupStyle)
|
||||
.snyggBackground(context, popupStyle, fallbackColor = FlorisImeTheme.fallbackSurfaceColor()),
|
||||
) {
|
||||
if (isCurrentlyPinned) {
|
||||
Action(
|
||||
icon = Icons.Outlined.PushPin,
|
||||
action = {
|
||||
EmojiHistoryHelper.unpinEmoji(prefs, emoji)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Action(
|
||||
icon = Icons.Outlined.PushPin,
|
||||
action = {
|
||||
EmojiHistoryHelper.pinEmoji(prefs, emoji)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showMoveLeft) {
|
||||
Action(
|
||||
icon = Icons.AutoMirrored.Default.KeyboardArrowLeft,
|
||||
action = {
|
||||
EmojiHistoryHelper.moveEmoji(prefs, emoji, -1)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (showMoveRight) {
|
||||
Action(
|
||||
icon = Icons.AutoMirrored.Default.KeyboardArrowRight,
|
||||
action = {
|
||||
EmojiHistoryHelper.moveEmoji(prefs, emoji, 1)
|
||||
},
|
||||
)
|
||||
}
|
||||
Action(
|
||||
icon = Icons.Outlined.Delete,
|
||||
action = {
|
||||
EmojiHistoryHelper.removeEmoji(prefs, emoji)
|
||||
context.showShortToast(
|
||||
R.string.emoji__history__removal_success_message,
|
||||
"emoji" to emoji.value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmojiText(
|
||||
text: String,
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.media.emoji
|
||||
|
||||
import dev.patrickgold.florisboard.app.AppPrefs
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceSerializer
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
object EmojiRecentlyUsedHelper {
|
||||
private const val DELIMITER = ";"
|
||||
|
||||
private var emojiGuard = Mutex(locked = false)
|
||||
|
||||
suspend fun addEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
|
||||
val maxSize = prefs.media.emojiRecentlyUsedMaxSize.get()
|
||||
val list = prefs.media.emojiRecentlyUsed.get().toMutableList()
|
||||
list.add(0, emoji)
|
||||
if (maxSize > 0) {
|
||||
while (list.size > maxSize) {
|
||||
list.removeLast()
|
||||
}
|
||||
}
|
||||
prefs.media.emojiRecentlyUsed.set(list.distinctBy { it.value })
|
||||
}
|
||||
|
||||
suspend fun removeEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
|
||||
val list = prefs.media.emojiRecentlyUsed.get().toMutableList()
|
||||
list.remove(emoji)
|
||||
prefs.media.emojiRecentlyUsed.set(list.distinctBy { it.value })
|
||||
}
|
||||
|
||||
object Serializer : PreferenceSerializer<List<Emoji>> {
|
||||
override fun serialize(value: List<Emoji>): String {
|
||||
return value.joinToString(DELIMITER) { it.value }
|
||||
}
|
||||
|
||||
override fun deserialize(value: String): List<Emoji> {
|
||||
return value.split(DELIMITER).mapNotNull { rawValue ->
|
||||
rawValue.trim().let { if (it.isBlank()) null else Emoji(it.trim(), "", emptyList()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.media.emoji
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -13,10 +29,6 @@ import dev.patrickgold.florisboard.ime.nlp.SuggestionProvider
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
import io.github.reactivecircus.cache4k.Cache
|
||||
|
||||
const val EMOJI_SUGGESTION_INDICATOR = ':'
|
||||
const val EMOJI_SUGGESTION_MAX_COUNT = 5
|
||||
private const val EMOJI_SUGGESTION_QUERY_MIN_LENGTH = 3
|
||||
|
||||
/**
|
||||
* Provides emoji suggestions within a text input context.
|
||||
*
|
||||
@@ -30,7 +42,7 @@ class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider
|
||||
override val providerId = "org.florisboard.nlp.providers.emoji"
|
||||
|
||||
private val prefs by florisPreferenceModel()
|
||||
private val lettersRegex = "^:[A-Za-z]*$".toRegex()
|
||||
private val lettersRegex = "^[A-Za-z]*$".toRegex()
|
||||
|
||||
private val cachedEmojiMappings = Cache.Builder().build<FlorisLocale, EmojiDataBySkinTone>()
|
||||
|
||||
@@ -52,7 +64,8 @@ class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider
|
||||
allowPossiblyOffensive: Boolean,
|
||||
isPrivateSession: Boolean
|
||||
): List<SuggestionCandidate> {
|
||||
val preferredSkinTone = prefs.media.emojiPreferredSkinTone.get()
|
||||
val preferredSkinTone = prefs.emoji.preferredSkinTone.get()
|
||||
val showName = prefs.emoji.suggestionCandidateShowName.get()
|
||||
val query = validateInputQuery(content.composingText) ?: return emptyList()
|
||||
val emojis = cachedEmojiMappings.get(subtype.primaryLocale)?.get(preferredSkinTone) ?: emptyList()
|
||||
val candidates = withContext(Dispatchers.Default) {
|
||||
@@ -62,14 +75,24 @@ class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider
|
||||
emoji.keywords.any { it.contains(query, ignoreCase = true) }
|
||||
}
|
||||
.limit(maxCandidateCount.toLong())
|
||||
.map { EmojiSuggestionCandidate(it) }
|
||||
.map { emoji ->
|
||||
EmojiSuggestionCandidate(
|
||||
emoji = emoji,
|
||||
showName = showName,
|
||||
sourceProvider = this@EmojiSuggestionProvider,
|
||||
)
|
||||
}
|
||||
.collect(Collectors.toList())
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
override suspend fun notifySuggestionAccepted(subtype: Subtype, candidate: SuggestionCandidate) {
|
||||
// No-op
|
||||
val updateHistory = prefs.emoji.suggestionUpdateHistory.get()
|
||||
if (!updateHistory || candidate !is EmojiSuggestionCandidate) {
|
||||
return
|
||||
}
|
||||
EmojiHistoryHelper.markEmojiUsed(prefs, candidate.emoji)
|
||||
}
|
||||
|
||||
override suspend fun notifySuggestionReverted(subtype: Subtype, candidate: SuggestionCandidate) {
|
||||
@@ -90,15 +113,18 @@ class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider
|
||||
* Validates the user input query for emoji suggestions.
|
||||
*/
|
||||
private fun validateInputQuery(composingText: CharSequence): String? {
|
||||
if (!composingText.startsWith(EMOJI_SUGGESTION_INDICATOR)) {
|
||||
val prefix = prefs.emoji.suggestionType.get().prefix
|
||||
val queryMinLength = prefs.emoji.suggestionQueryMinLength.get() + prefix.length
|
||||
if (prefix.isNotEmpty() && !composingText.startsWith(prefix)) {
|
||||
return null
|
||||
}
|
||||
if (composingText.length <= EMOJI_SUGGESTION_QUERY_MIN_LENGTH) {
|
||||
if (composingText.length < queryMinLength) {
|
||||
return null
|
||||
}
|
||||
if (!lettersRegex.matches(composingText)) {
|
||||
val emojiPartialName = composingText.substring(prefix.length)
|
||||
if (!lettersRegex.matches(emojiPartialName)) {
|
||||
return null
|
||||
}
|
||||
return composingText.substring(1)
|
||||
return emojiPartialName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.media.emoji
|
||||
|
||||
enum class EmojiSuggestionType(val prefix: String) {
|
||||
LEADING_COLON(":"),
|
||||
INLINE_TEXT(""),
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Size
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.view.inputmethod.InlineSuggestionInfo
|
||||
import android.widget.inline.InlineContentView
|
||||
import androidx.annotation.RequiresApi
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogInfo
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogWarning
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
data class NlpInlineAutofillSuggestion(
|
||||
val info: InlineSuggestionInfo,
|
||||
val view: InlineContentView?,
|
||||
)
|
||||
|
||||
object NlpInlineAutofill {
|
||||
private val currentSequenceId = AtomicInteger(0)
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
private val setterGuard = Mutex()
|
||||
private val _suggestions = MutableStateFlow<List<NlpInlineAutofillSuggestion>>(emptyList())
|
||||
val suggestions = _suggestions
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun showInlineSuggestions(context: Context, rawSuggestions: List<InlineSuggestion>): Boolean {
|
||||
val sequenceId = generateSequenceId()
|
||||
|
||||
if (rawSuggestions.isEmpty()) {
|
||||
clearInlineSuggestions(sequenceId)
|
||||
return false
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val size = Size(ViewGroup.LayoutParams.WRAP_CONTENT, FlorisImeSizing.Static.smartbarHeightPx)
|
||||
val latch = CountDownLatch(rawSuggestions.size)
|
||||
val suggestionsArray = Array<NlpInlineAutofillSuggestion?>(rawSuggestions.size) { null }
|
||||
|
||||
flogInfo { "showInlineSuggestions: [${sequenceId}] start inflating suggestions" }
|
||||
for ((index, rawSuggestion) in rawSuggestions.withIndex()) {
|
||||
rawSuggestion.inflate(context, size, context.mainExecutor) { view ->
|
||||
suggestionsArray[index] = NlpInlineAutofillSuggestion(rawSuggestion.info, view)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
if (!latch.await(2_000, TimeUnit.MILLISECONDS)) {
|
||||
flogWarning { "showInlineSuggestions: [${sequenceId}] timed out while waiting for all " +
|
||||
"suggestions to inflate" }
|
||||
return@launch
|
||||
}
|
||||
|
||||
val suggestions = suggestionsArray.filterNotNull().sortedByDescending { it.info.isPinned }
|
||||
setterGuard.lock()
|
||||
flogInfo { "showInlineSuggestions: [${sequenceId}] successfully inflated " +
|
||||
"${suggestions.count { it.view != null }} out of ${suggestions.size} suggestions" }
|
||||
if (currentSequenceId.get() == sequenceId) {
|
||||
flogInfo { "showInlineSuggestions: [${sequenceId}] setting suggestions" }
|
||||
_suggestions.value = suggestions
|
||||
} else {
|
||||
flogWarning { "showInlineSuggestions: [${sequenceId}] seqId != current, skip setting suggestions" }
|
||||
}
|
||||
setterGuard.unlock()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun clearInlineSuggestions() {
|
||||
// Increment sequence id to invalidate eventual pending suggestions
|
||||
clearInlineSuggestions(generateSequenceId())
|
||||
}
|
||||
|
||||
private fun clearInlineSuggestions(sequenceId: Int) {
|
||||
scope.launch {
|
||||
setterGuard.lock()
|
||||
flogInfo { "clearInlineSuggestions: [${sequenceId}] clearing suggestions" }
|
||||
_suggestions.value = emptyList()
|
||||
setterGuard.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateSequenceId(): Int {
|
||||
return currentSequenceId.incrementAndGet()
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,8 @@
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import android.util.LruCache
|
||||
import android.util.Size
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.widget.inline.InlineContentView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.clipboardManager
|
||||
@@ -34,12 +28,10 @@ import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorContent
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorRange
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EMOJI_SUGGESTION_MAX_COUNT
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionProvider
|
||||
import dev.patrickgold.florisboard.ime.nlp.han.HanShapeBasedLanguageProvider
|
||||
import dev.patrickgold.florisboard.ime.nlp.latin.LatinLanguageProvider
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogError
|
||||
import dev.patrickgold.florisboard.lib.util.NetworkUtils
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -53,7 +45,6 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.florisboard.lib.kotlin.collectLatestIn
|
||||
import org.florisboard.lib.kotlin.guardedByLock
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.properties.Delegates
|
||||
@@ -70,7 +61,7 @@ class NlpManager(context: Context) {
|
||||
private val subtypeManager by context.subtypeManager()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
private val clipboardSuggestionProvider = ClipboardSuggestionProvider()
|
||||
private val clipboardSuggestionProvider = ClipboardSuggestionProvider(context)
|
||||
private val emojiSuggestionProvider = EmojiSuggestionProvider(context)
|
||||
private val providers = guardedByLock {
|
||||
mapOf(
|
||||
@@ -94,10 +85,6 @@ class NlpManager(context: Context) {
|
||||
_activeCandidatesFlow.value = v
|
||||
}
|
||||
|
||||
private val inlineContentViews = Collections.synchronizedMap<InlineSuggestion, InlineContentView>(hashMapOf())
|
||||
private val _inlineSuggestions = MutableLiveData<List<InlineSuggestion>>(emptyList())
|
||||
val inlineSuggestions: LiveData<List<InlineSuggestion>> get() = _inlineSuggestions
|
||||
|
||||
val debugOverlaySuggestionsInfos = LruCache<Long, Pair<String, SpellingResult>>(10)
|
||||
var debugOverlayVersion = MutableLiveData(0)
|
||||
private val debugOverlayVersionSource = AtomicInteger(0)
|
||||
@@ -112,6 +99,9 @@ class NlpManager(context: Context) {
|
||||
prefs.suggestion.clipboardContentEnabled.observeForever {
|
||||
assembleCandidates()
|
||||
}
|
||||
prefs.emoji.suggestionEnabled.observeForever {
|
||||
assembleCandidates()
|
||||
}
|
||||
subtypeManager.activeSubtypeFlow.collectLatestIn(scope) { subtype ->
|
||||
preload(subtype)
|
||||
}
|
||||
@@ -202,21 +192,45 @@ class NlpManager(context: Context) {
|
||||
}
|
||||
|
||||
fun isSuggestionOn(): Boolean =
|
||||
prefs.suggestion.enabled.get() || providerForcesSuggestionOn(subtypeManager.activeSubtype)
|
||||
prefs.suggestion.enabled.get()
|
||||
|| prefs.emoji.suggestionEnabled.get()
|
||||
|| providerForcesSuggestionOn(subtypeManager.activeSubtype)
|
||||
|
||||
fun suggest(subtype: Subtype, content: EditorContent) {
|
||||
val reqTime = SystemClock.uptimeMillis()
|
||||
scope.launch {
|
||||
val suggestions = getSuggestionProvider(subtype).suggest(
|
||||
subtype = subtype,
|
||||
content = content,
|
||||
maxCandidateCount = 8,
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive.get(),
|
||||
isPrivateSession = keyboardManager.activeState.isIncognitoMode,
|
||||
)
|
||||
val emojiSuggestions = when {
|
||||
prefs.emoji.suggestionEnabled.get() -> {
|
||||
emojiSuggestionProvider.suggest(
|
||||
subtype = subtype,
|
||||
content = content,
|
||||
maxCandidateCount = prefs.emoji.suggestionCandidateMaxCount.get(),
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive.get(),
|
||||
isPrivateSession = keyboardManager.activeState.isIncognitoMode,
|
||||
)
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
val suggestions = when {
|
||||
emojiSuggestions.isNotEmpty() && prefs.emoji.suggestionType.get().prefix.isNotEmpty() -> {
|
||||
emptyList()
|
||||
}
|
||||
else -> {
|
||||
getSuggestionProvider(subtype).suggest(
|
||||
subtype = subtype,
|
||||
content = content,
|
||||
maxCandidateCount = 8,
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive.get(),
|
||||
isPrivateSession = keyboardManager.activeState.isIncognitoMode,
|
||||
)
|
||||
}
|
||||
}
|
||||
internalSuggestionsGuard.withLock {
|
||||
if (internalSuggestions.first < reqTime) {
|
||||
internalSuggestions = reqTime to suggestions
|
||||
internalSuggestions = reqTime to buildList {
|
||||
addAll(emojiSuggestions)
|
||||
addAll(suggestions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,24 +281,16 @@ class NlpManager(context: Context) {
|
||||
runBlocking {
|
||||
val candidates = when {
|
||||
isSuggestionOn() -> {
|
||||
emojiSuggestionProvider.suggest(
|
||||
subtype = subtypeManager.activeSubtype,
|
||||
clipboardSuggestionProvider.suggest(
|
||||
subtype = Subtype.DEFAULT,
|
||||
content = editorInstance.activeContent,
|
||||
maxCandidateCount = EMOJI_SUGGESTION_MAX_COUNT,
|
||||
maxCandidateCount = 8,
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive.get(),
|
||||
isPrivateSession = keyboardManager.activeState.isIncognitoMode,
|
||||
).ifEmpty {
|
||||
clipboardSuggestionProvider.suggest(
|
||||
subtype = Subtype.DEFAULT,
|
||||
content = editorInstance.activeContent,
|
||||
maxCandidateCount = 8,
|
||||
allowPossiblyOffensive = !prefs.suggestion.blockPossiblyOffensive.get(),
|
||||
isPrivateSession = keyboardManager.activeState.isIncognitoMode,
|
||||
).ifEmpty {
|
||||
buildList {
|
||||
internalSuggestionsGuard.withLock {
|
||||
addAll(internalSuggestions.second)
|
||||
}
|
||||
buildList {
|
||||
internalSuggestionsGuard.withLock {
|
||||
addAll(internalSuggestions.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,68 +298,27 @@ class NlpManager(context: Context) {
|
||||
else -> emptyList()
|
||||
}
|
||||
activeCandidates = candidates
|
||||
autoExpandCollapseSmartbarActions(candidates, inlineSuggestions.value)
|
||||
autoExpandCollapseSmartbarActions(candidates, NlpInlineAutofill.suggestions.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the given inline suggestions. Once all provided views are ready, the suggestions
|
||||
* strip is updated and the Smartbar update cycle is triggered.
|
||||
*
|
||||
* @param inlineSuggestions A collection of inline suggestions to be inflated and shown.
|
||||
*/
|
||||
fun showInlineSuggestions(inlineSuggestions: List<InlineSuggestion>) {
|
||||
inlineContentViews.clear()
|
||||
_inlineSuggestions.postValue(inlineSuggestions)
|
||||
autoExpandCollapseSmartbarActions(activeCandidates, inlineSuggestions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the inline suggestions and triggers the Smartbar update cycle.
|
||||
*/
|
||||
fun clearInlineSuggestions() {
|
||||
inlineContentViews.clear()
|
||||
_inlineSuggestions.postValue(emptyList())
|
||||
autoExpandCollapseSmartbarActions(activeCandidates, null)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun inflateOrGet(
|
||||
context: Context,
|
||||
size: Size,
|
||||
inlineSuggestion: InlineSuggestion,
|
||||
callback: (InlineContentView) -> Unit,
|
||||
) {
|
||||
val view = inlineContentViews[inlineSuggestion]
|
||||
if (view != null) {
|
||||
callback(view)
|
||||
} else {
|
||||
try {
|
||||
inlineSuggestion.inflate(context, size, context.mainExecutor) { inflatedView ->
|
||||
if (inflatedView != null) {
|
||||
inlineContentViews[inlineSuggestion] = inflatedView
|
||||
callback(inflatedView)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
flogError { e.toString() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun autoExpandCollapseSmartbarActions(list1: List<*>?, list2: List<*>?) {
|
||||
if (prefs.smartbar.enabled.get() && prefs.smartbar.sharedActionsAutoExpandCollapse.get()) {
|
||||
if (keyboardManager.inputEventDispatcher.isRepeatableCodeLastDown()
|
||||
|| keyboardManager.activeState.isActionsOverflowVisible
|
||||
) {
|
||||
return // We do not auto switch if a repeatable action key was last pressed or if the actions overflow
|
||||
// menu is visible to prevent annoying UI changes
|
||||
}
|
||||
val isSelection = editorInstance.activeContent.selection.isSelectionMode
|
||||
val isExpanded = list1.isNullOrEmpty() && list2.isNullOrEmpty() || isSelection
|
||||
prefs.smartbar.sharedActionsExpandWithAnimation.set(false)
|
||||
prefs.smartbar.sharedActionsExpanded.set(isExpanded)
|
||||
fun autoExpandCollapseSmartbarActions(list1: List<*>?, list2: List<*>?) {
|
||||
if (!prefs.smartbar.enabled.get()) {// || !prefs.smartbar.sharedActionsAutoExpandCollapse.get()) {
|
||||
return
|
||||
}
|
||||
// TODO: this is a mess and needs to be cleaned up in v0.5 with the NLP development
|
||||
/*if (keyboardManager.inputEventDispatcher.isRepeatableCodeLastDown()
|
||||
&& !keyboardManager.inputEventDispatcher.isPressed(KeyCode.DELETE)
|
||||
&& !keyboardManager.inputEventDispatcher.isPressed(KeyCode.FORWARD_DELETE)
|
||||
|| keyboardManager.activeState.isActionsOverflowVisible
|
||||
) {
|
||||
return // We do not auto switch if a repeatable action key was last pressed or if the actions overflow
|
||||
// menu is visible to prevent annoying UI changes
|
||||
}*/
|
||||
val isSelection = editorInstance.activeContent.selection.isSelectionMode
|
||||
val isExpanded = list1.isNullOrEmpty() && list2.isNullOrEmpty() || isSelection
|
||||
prefs.smartbar.sharedActionsExpandWithAnimation.set(false)
|
||||
prefs.smartbar.sharedActionsExpanded.set(isExpanded)
|
||||
}
|
||||
|
||||
fun addToDebugOverlay(word: String, info: SpellingResult) {
|
||||
@@ -384,7 +349,7 @@ class NlpManager(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
inner class ClipboardSuggestionProvider internal constructor() : SuggestionProvider {
|
||||
inner class ClipboardSuggestionProvider internal constructor(private val context: Context) : SuggestionProvider {
|
||||
private var lastClipboardItemId: Long = -1
|
||||
|
||||
override val providerId = "org.florisboard.nlp.providers.clipboard"
|
||||
@@ -413,7 +378,10 @@ class NlpManager(context: Context) {
|
||||
return buildList {
|
||||
val now = System.currentTimeMillis()
|
||||
if ((now - currentItem.creationTimestampMs) < prefs.suggestion.clipboardContentTimeout.get() * 1000) {
|
||||
add(ClipboardSuggestionCandidate(currentItem, sourceProvider = this@ClipboardSuggestionProvider))
|
||||
add(ClipboardSuggestionCandidate(currentItem, sourceProvider = this@ClipboardSuggestionProvider, context = context))
|
||||
if (currentItem.isSensitive) {
|
||||
return@buildList
|
||||
}
|
||||
if (currentItem.type == ItemType.TEXT) {
|
||||
val text = currentItem.stringRepresentation()
|
||||
val matches = buildList {
|
||||
@@ -437,6 +405,7 @@ class NlpManager(context: Context) {
|
||||
}
|
||||
),
|
||||
sourceProvider = this@ClipboardSuggestionProvider,
|
||||
context = context,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.icu.text.BreakIterator
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorContent
|
||||
import dev.patrickgold.florisboard.ime.editor.EditorRange
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EMOJI_SUGGESTION_INDICATOR
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSuggestionType
|
||||
|
||||
/**
|
||||
* Base interface for any NLP provider implementation. NLP providers maintain their own internal state and only receive
|
||||
@@ -221,7 +221,7 @@ interface SuggestionProvider : NlpProvider {
|
||||
// Include Emoji indicator in local composing. This is required so that emoji suggestion indicator'
|
||||
// can be detected in the composing text.
|
||||
(pos - 1).takeIf { updatedPos ->
|
||||
textBeforeSelection.getOrNull(updatedPos) == EMOJI_SUGGESTION_INDICATOR
|
||||
textBeforeSelection.getOrNull(updatedPos) == EmojiSuggestionType.LEADING_COLON.prefix.first()
|
||||
} ?: pos
|
||||
}
|
||||
EditorRange(start, end)
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.nlp
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Assignment
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Image
|
||||
import androidx.compose.material.icons.filled.Link
|
||||
import androidx.compose.material.icons.filled.Phone
|
||||
import androidx.compose.material.icons.filled.Videocam
|
||||
import androidx.compose.material.icons.outlined.Assignment
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
|
||||
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
|
||||
@@ -123,8 +124,9 @@ data class WordSuggestionCandidate(
|
||||
data class ClipboardSuggestionCandidate(
|
||||
val clipboardItem: ClipboardItem,
|
||||
override val sourceProvider: SuggestionProvider?,
|
||||
val context: Context,
|
||||
) : SuggestionCandidate {
|
||||
override val text: CharSequence = clipboardItem.stringRepresentation()
|
||||
override val text: CharSequence = clipboardItem.displayText(context)
|
||||
|
||||
override val secondaryText: CharSequence? = null
|
||||
|
||||
@@ -139,7 +141,7 @@ data class ClipboardSuggestionCandidate(
|
||||
NetworkUtils.isEmailAddress(text) -> Icons.Default.Email
|
||||
NetworkUtils.isUrl(text) -> Icons.Default.Link
|
||||
NetworkUtils.isPhoneNumber(text) -> Icons.Default.Phone
|
||||
else -> Icons.Outlined.Assignment
|
||||
else -> Icons.AutoMirrored.Outlined.Assignment
|
||||
}
|
||||
ItemType.IMAGE -> Icons.Default.Image
|
||||
ItemType.VIDEO -> Icons.Default.Videocam
|
||||
@@ -157,6 +159,7 @@ data class ClipboardSuggestionCandidate(
|
||||
*/
|
||||
data class EmojiSuggestionCandidate(
|
||||
val emoji: Emoji,
|
||||
val showName: Boolean,
|
||||
override val confidence: Double = 1.0,
|
||||
override val isEligibleForAutoCommit: Boolean = false,
|
||||
override val isEligibleForUserRemoval: Boolean = false,
|
||||
@@ -164,5 +167,5 @@ data class EmojiSuggestionCandidate(
|
||||
override val sourceProvider: SuggestionProvider? = null,
|
||||
) : SuggestionCandidate {
|
||||
override val text = emoji.value
|
||||
override val secondaryText = emoji.name
|
||||
override val secondaryText = if (showName) emoji.name else null
|
||||
}
|
||||
|
||||
@@ -24,14 +24,13 @@ import dev.patrickgold.florisboard.ime.nlp.SpellingProvider
|
||||
import dev.patrickgold.florisboard.ime.nlp.SpellingResult
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
|
||||
import dev.patrickgold.florisboard.ime.nlp.SuggestionProvider
|
||||
import dev.patrickgold.florisboard.ime.nlp.WordSuggestionCandidate
|
||||
import org.florisboard.lib.android.readText
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogDebug
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.florisboard.lib.android.readText
|
||||
import org.florisboard.lib.kotlin.guardedByLock
|
||||
|
||||
class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProvider {
|
||||
@@ -106,7 +105,8 @@ class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProv
|
||||
allowPossiblyOffensive: Boolean,
|
||||
isPrivateSession: Boolean,
|
||||
): List<SuggestionCandidate> {
|
||||
val word = content.composingText.ifBlank { "next" }
|
||||
return emptyList()
|
||||
/*val word = content.composingText.ifBlank { "next" }
|
||||
val suggestions = buildList {
|
||||
for (n in 0 until maxCandidateCount) {
|
||||
add(WordSuggestionCandidate(
|
||||
@@ -119,7 +119,7 @@ class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProv
|
||||
))
|
||||
}
|
||||
}
|
||||
return suggestions
|
||||
return suggestions*/
|
||||
}
|
||||
|
||||
override suspend fun notifySuggestionAccepted(subtype: Subtype, candidate: SuggestionCandidate) {
|
||||
|
||||
@@ -22,8 +22,8 @@ import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.ZoomOutMap
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -81,9 +81,9 @@ fun RowScope.OneHandedPanel(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (panelSide == OneHandedMode.START) {
|
||||
Icons.Default.KeyboardArrowLeft
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowLeft
|
||||
} else {
|
||||
Icons.Default.KeyboardArrowRight
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowRight
|
||||
},
|
||||
contentDescription = stringRes(
|
||||
if (panelSide == OneHandedMode.START) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardState
|
||||
import dev.patrickgold.florisboard.lib.compose.conditional
|
||||
|
||||
private val SheetOutOfBoundsBgColorInactive = Color(0x00000000)
|
||||
private val SheetOutOfBoundsBgColorActive = Color(0x52000000)
|
||||
@@ -53,16 +54,13 @@ fun BottomSheetHostUi(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.then(
|
||||
if (isShowing) {
|
||||
Modifier.pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
onHide()
|
||||
}
|
||||
.conditional(isShowing) {
|
||||
pointerInput(Unit) {
|
||||
detectTapGestures {
|
||||
onHide()
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}),
|
||||
}
|
||||
},
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = isShowing,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
* Copyright (C) 2024 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -55,18 +55,17 @@ import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
|
||||
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.conditional
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.compose.safeTimes
|
||||
import dev.patrickgold.florisboard.lib.observeAsNonNullState
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.solidColor
|
||||
import org.florisboard.lib.snygg.ui.spSize
|
||||
|
||||
private val CandidatesRowScrollbarHeight = 2.dp
|
||||
val CandidatesRowScrollbarHeight = 2.dp
|
||||
|
||||
@Composable
|
||||
fun CandidatesRow(modifier: Modifier = Modifier) {
|
||||
@@ -78,89 +77,71 @@ fun CandidatesRow(modifier: Modifier = Modifier) {
|
||||
|
||||
val displayMode by prefs.suggestion.displayMode.observeAsState()
|
||||
val candidates by nlpManager.activeCandidatesFlow.collectAsState()
|
||||
val inlineSuggestions by nlpManager.inlineSuggestions.observeAsNonNullState()
|
||||
|
||||
val rowStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarCandidatesRow)
|
||||
val spacerStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarCandidateSpacer)
|
||||
|
||||
if (AndroidVersion.ATLEAST_API30_R && inlineSuggestions.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.florisHorizontalScroll(scrollbarHeight = CandidatesRowScrollbarHeight),
|
||||
) {
|
||||
for (inlineSuggestion in inlineSuggestions) {
|
||||
InlineSuggestionView(inlineSuggestion = inlineSuggestion)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.snyggBackground(context, rowStyle)
|
||||
.then(
|
||||
if (displayMode == CandidatesDisplayMode.DYNAMIC_SCROLLABLE && candidates.size > 1) {
|
||||
Modifier.florisHorizontalScroll(scrollbarHeight = CandidatesRowScrollbarHeight)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
horizontalArrangement = if (candidates.size > 1) {
|
||||
Arrangement.Start
|
||||
} else {
|
||||
Arrangement.Center
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.snyggBackground(context, rowStyle)
|
||||
.conditional(displayMode == CandidatesDisplayMode.DYNAMIC_SCROLLABLE && candidates.size > 1) {
|
||||
florisHorizontalScroll(scrollbarHeight = CandidatesRowScrollbarHeight)
|
||||
},
|
||||
) {
|
||||
if (candidates.isNotEmpty()) {
|
||||
val candidateModifier = if (candidates.size == 1) {
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f, fill = false)
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.then(
|
||||
if (displayMode == CandidatesDisplayMode.CLASSIC) {
|
||||
Modifier.weight(1f)
|
||||
} else {
|
||||
Modifier.wrapContentWidth().widthIn(max = 160.dp)
|
||||
}
|
||||
)
|
||||
}
|
||||
val list = when (displayMode) {
|
||||
CandidatesDisplayMode.CLASSIC -> candidates.subList(0, 3.coerceAtMost(candidates.size))
|
||||
else -> candidates
|
||||
}
|
||||
for ((n, candidate) in list.withIndex()) {
|
||||
if (n > 0) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(1.dp)
|
||||
.fillMaxHeight(0.6f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.snyggBackground(context, spacerStyle),
|
||||
)
|
||||
horizontalArrangement = if (candidates.size > 1) {
|
||||
Arrangement.Start
|
||||
} else {
|
||||
Arrangement.Center
|
||||
},
|
||||
) {
|
||||
if (candidates.isNotEmpty()) {
|
||||
val candidateModifier = if (candidates.size == 1) {
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f, fill = false)
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.conditional(displayMode == CandidatesDisplayMode.CLASSIC) {
|
||||
weight(1f)
|
||||
}
|
||||
CandidateItem(
|
||||
modifier = candidateModifier,
|
||||
candidate = candidate,
|
||||
displayMode = displayMode,
|
||||
onClick = {
|
||||
// Can't use candidate directly
|
||||
keyboardManager.commitCandidate(candidates[n])
|
||||
},
|
||||
onLongPress = {
|
||||
// Can't use candidate directly
|
||||
val candidateItem = candidates[n]
|
||||
if (candidateItem.isEligibleForUserRemoval) {
|
||||
nlpManager.removeSuggestion(subtypeManager.activeSubtype, candidateItem)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
longPressDelay = prefs.keyboard.longPressDelay.get().toLong(),
|
||||
.conditional(displayMode != CandidatesDisplayMode.CLASSIC) {
|
||||
wrapContentWidth().widthIn(max = 160.dp)
|
||||
}
|
||||
}
|
||||
val list = when (displayMode) {
|
||||
CandidatesDisplayMode.CLASSIC -> candidates.subList(0, 3.coerceAtMost(candidates.size))
|
||||
else -> candidates
|
||||
}
|
||||
for ((n, candidate) in list.withIndex()) {
|
||||
if (n > 0) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(1.dp)
|
||||
.fillMaxHeight(0.6f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.snyggBackground(context, spacerStyle),
|
||||
)
|
||||
}
|
||||
CandidateItem(
|
||||
modifier = candidateModifier,
|
||||
candidate = candidate,
|
||||
displayMode = displayMode,
|
||||
onClick = {
|
||||
// Can't use candidate directly
|
||||
keyboardManager.commitCandidate(candidates[n])
|
||||
},
|
||||
onLongPress = {
|
||||
// Can't use candidate directly
|
||||
val candidateItem = candidates[n]
|
||||
if (candidateItem.isEligibleForUserRemoval) {
|
||||
nlpManager.removeSuggestion(subtypeManager.activeSubtype, candidateItem)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
longPressDelay = prefs.keyboard.longPressDelay.get().toLong(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.smartbar
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Size
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.widget.inline.InlineContentView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@Composable
|
||||
fun InlineSuggestionView(inlineSuggestion: InlineSuggestion) = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
val nlpManager by context.nlpManager()
|
||||
|
||||
val size = Size(ViewGroup.LayoutParams.WRAP_CONTENT, FlorisImeSizing.smartbarHeight.toPx().toInt())
|
||||
var inlineContentView by remember { mutableStateOf<InlineContentView?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
nlpManager.inflateOrGet(context, size, inlineSuggestion) { view ->
|
||||
inlineContentView = view
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineContentView != null) {
|
||||
AndroidView(
|
||||
factory = { inlineContentView!! },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.smartbar
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofillSuggestion
|
||||
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
|
||||
import dev.patrickgold.florisboard.lib.toIntOffset
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@Composable
|
||||
fun InlineSuggestionsUi(
|
||||
inlineSuggestions: List<NlpInlineAutofillSuggestion>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val almostEmptyRect = remember { android.graphics.Rect(0, 0, 1, 1) }
|
||||
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.florisHorizontalScroll(
|
||||
state = scrollState,
|
||||
scrollbarHeight = CandidatesRowScrollbarHeight,
|
||||
),
|
||||
) {
|
||||
val xMin = scrollState.value
|
||||
val xMax = scrollState.value + scrollState.viewportSize
|
||||
for (inlineSuggestion in inlineSuggestions) {
|
||||
if (inlineSuggestion.view == null) {
|
||||
continue
|
||||
}
|
||||
var chipPos by remember { mutableStateOf(IntOffset.Zero) }
|
||||
AndroidView(
|
||||
modifier = Modifier.onGloballyPositioned { chipPos = it.positionInParent().toIntOffset() },
|
||||
factory = { inlineSuggestion.view },
|
||||
update = { view ->
|
||||
view.clipBounds = android.graphics.Rect(
|
||||
(xMin - chipPos.x).coerceAtLeast(0),
|
||||
0,
|
||||
(xMax - chipPos.x).coerceAtMost(view.width),
|
||||
view.height,
|
||||
)
|
||||
// The empty rect is a workaround for a bug (I suppose) where an empty rect causes
|
||||
// no clipping, but we actually want to completely hide the view.
|
||||
// Thus we just show the topmost pixel of the view, which due to the round shape
|
||||
// of the theme should be transparent anyways.
|
||||
if (view.clipBounds.isEmpty) {
|
||||
view.clipBounds = almostEmptyRect
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import androidx.compose.material.icons.filled.UnfoldMore
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -56,6 +57,7 @@ import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
|
||||
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionButton
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsRow
|
||||
import dev.patrickgold.florisboard.ime.smartbar.quickaction.ToggleOverflowPanelAction
|
||||
@@ -64,8 +66,10 @@ import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.compose.horizontalTween
|
||||
import dev.patrickgold.florisboard.lib.compose.verticalTween
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import dev.patrickgold.jetpref.datastore.model.observeAsState
|
||||
import dev.patrickgold.jetpref.datastore.ui.vectorResource
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
import org.florisboard.lib.snygg.ui.snyggBackground
|
||||
import org.florisboard.lib.snygg.ui.snyggBorder
|
||||
import org.florisboard.lib.snygg.ui.snyggShadow
|
||||
@@ -138,6 +142,13 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
val prefs by florisPreferenceModel()
|
||||
val context = LocalContext.current
|
||||
val keyboardManager by context.keyboardManager()
|
||||
val nlpManager by context.nlpManager()
|
||||
|
||||
val inlineSuggestions by NlpInlineAutofill.suggestions.collectAsState()
|
||||
LaunchedEffect(inlineSuggestions) {
|
||||
nlpManager.autoExpandCollapseSmartbarActions(null, inlineSuggestions)
|
||||
}
|
||||
val shouldShowInlineSuggestionsUi = AndroidVersion.ATLEAST_API30_R && inlineSuggestions.isNotEmpty()
|
||||
|
||||
val smartbarLayout by prefs.smartbar.layout.observeAsState()
|
||||
val flipToggles by prefs.smartbar.flipToggles.observeAsState()
|
||||
@@ -223,7 +234,11 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
enter = enterTransition,
|
||||
exit = exitTransition,
|
||||
) {
|
||||
CandidatesRow()
|
||||
if (shouldShowInlineSuggestionsUi) {
|
||||
InlineSuggestionsUi(inlineSuggestions)
|
||||
} else {
|
||||
CandidatesRow()
|
||||
}
|
||||
}
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = expanded,
|
||||
@@ -331,11 +346,19 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
|
||||
) {
|
||||
when (smartbarLayout) {
|
||||
SmartbarLayout.SUGGESTIONS_ONLY -> {
|
||||
CandidatesRow()
|
||||
if (shouldShowInlineSuggestionsUi) {
|
||||
InlineSuggestionsUi(inlineSuggestions)
|
||||
} else {
|
||||
CandidatesRow()
|
||||
}
|
||||
}
|
||||
|
||||
SmartbarLayout.ACTIONS_ONLY -> {
|
||||
QuickActionsRow(elementName = FlorisImeUi.SmartbarSharedActionsRow)
|
||||
if (shouldShowInlineSuggestionsUi) {
|
||||
InlineSuggestionsUi(inlineSuggestions)
|
||||
} else {
|
||||
QuickActionsRow(elementName = FlorisImeUi.SmartbarSharedActionsRow)
|
||||
}
|
||||
}
|
||||
|
||||
SmartbarLayout.SUGGESTIONS_ACTIONS_SHARED -> {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.smartbar.quickaction
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -33,7 +32,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -76,7 +75,6 @@ private const val ItemNotFound = -1
|
||||
private val NoopAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.NOOP))
|
||||
private val DragMarkerAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.DRAG_MARKER))
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
val prefs by florisPreferenceModel()
|
||||
@@ -105,17 +103,20 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
val headerStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsEditorHeader)
|
||||
val subheaderStyle = FlorisImeTheme.style.get(FlorisImeUi.SmartbarActionsEditorSubheader)
|
||||
|
||||
fun findItemForOffset(offset: IntOffset): LazyGridItemInfo? {
|
||||
fun findItemForOffsetOrClosestInRow(offset: IntOffset): LazyGridItemInfo? {
|
||||
var closestItemInRow: LazyGridItemInfo? = null
|
||||
// Using manual for loop with indices instead of firstOrNull() because this method gets
|
||||
// called a lot and firstOrNull allocates an iterator for each call
|
||||
for (index in gridState.layoutInfo.visibleItemsInfo.indices) {
|
||||
val item = gridState.layoutInfo.visibleItemsInfo[index]
|
||||
if (offset.x in item.offset.x..(item.offset.x + item.size.width) &&
|
||||
offset.y in item.offset.y..(item.offset.y + item.size.height)) {
|
||||
return item
|
||||
if (offset.y in item.offset.y..(item.offset.y + item.size.height)) {
|
||||
if (offset.x in item.offset.x..(item.offset.x + item.size.width)) {
|
||||
return item
|
||||
}
|
||||
closestItemInRow = item
|
||||
}
|
||||
}
|
||||
return null
|
||||
return closestItemInRow
|
||||
}
|
||||
|
||||
fun indexOfStickyAction(item: LazyGridItemInfo): Int {
|
||||
@@ -158,7 +159,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
fun beginDragGesture(pos: IntOffset) {
|
||||
val item = findItemForOffset(pos) ?: return
|
||||
val item = findItemForOffsetOrClosestInRow(pos) ?: return
|
||||
val stickyActionIndex = indexOfStickyAction(item)
|
||||
val dynamicActionIndex = indexOfDynamicAction(item)
|
||||
val hiddenActionIndex = indexOfHiddenAction(item)
|
||||
@@ -182,7 +183,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
if (activeDragAction == null) return
|
||||
val pos = activeDragPosition + posChange
|
||||
activeDragPosition = pos
|
||||
val item = findItemForOffset(pos) ?: return
|
||||
val item = findItemForOffsetOrClosestInRow(pos) ?: return
|
||||
val stickyActionIndex = indexOfStickyAction(item)
|
||||
val dynamicActionIndex = indexOfDynamicAction(item)
|
||||
val hiddenActionIndex = indexOfHiddenAction(item)
|
||||
@@ -263,7 +264,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
onClick = {
|
||||
keyboardManager.activeState.isActionsEditorVisible = false
|
||||
},
|
||||
icon = Icons.Default.KeyboardArrowLeft,
|
||||
icon = Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||
iconColor = headerStyle.foreground.solidColor(context, default = FlorisImeTheme.fallbackContentColor()),
|
||||
)
|
||||
Text(
|
||||
@@ -299,7 +300,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
}
|
||||
item(key = keyOf(stickyAction)) {
|
||||
QuickActionButton(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
action = stickyAction,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
@@ -314,7 +315,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
}
|
||||
itemsIndexed(dynamicActions, key = { i, a -> keyOf(a) ?: i }) { _, action ->
|
||||
QuickActionButton(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
action = action,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
@@ -329,7 +330,7 @@ fun QuickActionsEditorPanel(modifier: Modifier = Modifier) {
|
||||
}
|
||||
itemsIndexed(hiddenActions, key = { i, a -> keyOf(a) ?: i }) { _, action ->
|
||||
QuickActionButton(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
modifier = Modifier.animateItem(),
|
||||
action = action,
|
||||
evaluator = evaluator,
|
||||
type = QuickActionBarType.STATIC_TILE,
|
||||
|
||||
@@ -19,7 +19,6 @@ package dev.patrickgold.florisboard.ime.text
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -32,7 +31,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.ime.keyboard.KeyboardMode
|
||||
@@ -74,7 +72,7 @@ fun TextInputLayout(
|
||||
val indicatorStyle = FlorisImeTheme.style.get(FlorisImeUi.IncognitoModeIndicator)
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.requiredSize(192.dp)
|
||||
.matchParentSize()
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(R.drawable.ic_incognito),
|
||||
contentDescription = null,
|
||||
|
||||
@@ -220,31 +220,39 @@ fun TextKeyboardLayout(
|
||||
}
|
||||
},
|
||||
) {
|
||||
val keyMarginH by prefs.keyboard.keySpacingHorizontal.observeAsTransformingState { it.dp.toPx() }
|
||||
val keyMarginV by prefs.keyboard.keySpacingVertical.observeAsTransformingState { it.dp.toPx() }
|
||||
val desiredKey = remember { TextKey(data = TextKeyData.UNSPECIFIED) }
|
||||
val keyboardWidth = constraints.maxWidth.toFloat()
|
||||
val keyboardHeight = constraints.maxHeight.toFloat()
|
||||
desiredKey.touchBounds.apply {
|
||||
if (isSmartbarKeyboard) {
|
||||
width = keyboardWidth / 8f
|
||||
height = FlorisImeSizing.smartbarHeight.toPx()
|
||||
} else {
|
||||
width = keyboardWidth / 10f
|
||||
height = when (keyboard.mode) {
|
||||
KeyboardMode.CHARACTERS,
|
||||
KeyboardMode.NUMERIC_ADVANCED,
|
||||
KeyboardMode.SYMBOLS,
|
||||
KeyboardMode.SYMBOLS2 -> {
|
||||
(FlorisImeSizing.keyboardUiHeight() / keyboard.rowCount)
|
||||
.coerceAtMost(FlorisImeSizing.keyboardRowBaseHeight * 1.12f).toPx()
|
||||
val keyMarginH by prefs.keyboard.keySpacingHorizontal.observeAsTransformingState { it.dp.toPx() }
|
||||
val keyMarginV by prefs.keyboard.keySpacingVertical.observeAsTransformingState { it.dp.toPx() }
|
||||
val keyboardRowBaseHeight = FlorisImeSizing.keyboardRowBaseHeight
|
||||
|
||||
val desiredKey = remember(
|
||||
keyboard, isSmartbarKeyboard, keyboardWidth, keyboardHeight, keyMarginH, keyMarginV,
|
||||
keyboardRowBaseHeight
|
||||
) {
|
||||
TextKey(data = TextKeyData.UNSPECIFIED).also { desiredKey ->
|
||||
desiredKey.touchBounds.apply {
|
||||
if (isSmartbarKeyboard) {
|
||||
width = keyboardWidth / 8f
|
||||
height = keyboardHeight
|
||||
} else {
|
||||
width = keyboardWidth / 10f
|
||||
height = when (keyboard.mode) {
|
||||
KeyboardMode.CHARACTERS,
|
||||
KeyboardMode.NUMERIC_ADVANCED,
|
||||
KeyboardMode.SYMBOLS,
|
||||
KeyboardMode.SYMBOLS2 -> {
|
||||
(keyboardHeight / keyboard.rowCount)
|
||||
.coerceAtMost(keyboardRowBaseHeight.toPx() * 1.12f)
|
||||
}
|
||||
else -> keyboardRowBaseHeight.toPx()
|
||||
}
|
||||
}
|
||||
else -> FlorisImeSizing.keyboardRowBaseHeight.toPx()
|
||||
}
|
||||
desiredKey.visibleBounds.applyFrom(desiredKey.touchBounds).deflateBy(keyMarginH, keyMarginV)
|
||||
keyboard.layout(keyboardWidth, keyboardHeight, desiredKey, !isSmartbarKeyboard)
|
||||
}
|
||||
}
|
||||
desiredKey.visibleBounds.applyFrom(desiredKey.touchBounds).deflateBy(keyMarginH, keyMarginV)
|
||||
keyboard.layout(keyboardWidth, keyboardHeight, desiredKey, !isSmartbarKeyboard)
|
||||
|
||||
val fontSizeMultiplier = prefs.keyboard.fontSizeMultiplier()
|
||||
val popupUiController = rememberPopupUiController(
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.theme
|
||||
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import dev.patrickgold.florisboard.lib.ext.Extension
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionEditor
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
|
||||
@@ -41,14 +43,14 @@ class ThemeExtension(
|
||||
override fun edit() = ThemeExtensionEditor(
|
||||
meta = meta,
|
||||
dependencies = dependencies?.toMutableList() ?: mutableListOf(),
|
||||
themes = themes.map { it.edit() }.toMutableList(),
|
||||
themes = mutableStateListOf(*themes.map { it.edit() }.toTypedArray()),
|
||||
)
|
||||
}
|
||||
|
||||
class ThemeExtensionEditor(
|
||||
override var meta: ExtensionMeta,
|
||||
override val dependencies: MutableList<String>,
|
||||
val themes: MutableList<ThemeExtensionComponentEditor>,
|
||||
val themes: SnapshotStateList<ThemeExtensionComponentEditor>,
|
||||
) : ExtensionEditor {
|
||||
|
||||
override fun build() = ThemeExtension(
|
||||
|
||||
@@ -199,70 +199,68 @@ class ThemeManager(context: Context) {
|
||||
context: Context,
|
||||
style: SnyggStylesheet = activeThemeInfo.value?.stylesheet ?: FlorisImeThemeBaseStyle,
|
||||
): Bundle {
|
||||
val chipStyle = style.getStatic(FlorisImeUi.SmartbarSharedActionsToggle)
|
||||
val bgColor = chipStyle.background.solidColor(context)
|
||||
val fgColor = chipStyle.foreground.solidColor(context)
|
||||
val snyggStyle = style.getStatic(FlorisImeUi.SmartbarSharedActionsToggle)
|
||||
val bgColor = snyggStyle.background.solidColor(context)
|
||||
val fgColor = snyggStyle.foreground.solidColor(context)
|
||||
|
||||
val bgDrawableId = androidx.autofill.R.drawable.autofill_inline_suggestion_chip_background
|
||||
val stylesBuilder = UiVersions.newStylesBuilder()
|
||||
val suggestionStyle = InlineSuggestionUi.newStyleBuilder()
|
||||
.setSingleIconChipStyle(
|
||||
ViewStyle.Builder()
|
||||
.setBackground(
|
||||
Icon.createWithResource(context, bgDrawableId).setTint(bgColor.toArgb())
|
||||
)
|
||||
.setPadding(0, 0, 0, 0)
|
||||
.build()
|
||||
val bgDrawable = Icon.createWithResource(context, bgDrawableId).apply {
|
||||
setTint(bgColor.toArgb())
|
||||
}
|
||||
val singleIconChipStyle = ViewStyle.Builder().run {
|
||||
setBackground(bgDrawable)
|
||||
setPadding(0, 0, 0, 0)
|
||||
build()
|
||||
}
|
||||
val chipStyle = ViewStyle.Builder().run {
|
||||
setBackground(bgDrawable)
|
||||
setPadding(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_bottom).toInt(),
|
||||
)
|
||||
.setChipStyle(
|
||||
ViewStyle.Builder()
|
||||
.setBackground(
|
||||
Icon.createWithResource(context, bgDrawableId).setTint(bgColor.toArgb())
|
||||
)
|
||||
.setPadding(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_bg_padding_bottom).toInt(),
|
||||
)
|
||||
.build()
|
||||
build()
|
||||
}
|
||||
val iconStyle = ImageViewStyle.Builder().run {
|
||||
setLayoutMargin(0, 0, 0, 0)
|
||||
build()
|
||||
}
|
||||
val titleStyle = TextViewStyle.Builder().run {
|
||||
setLayoutMargin(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_bottom).toInt(),
|
||||
)
|
||||
.setStartIconStyle(
|
||||
ImageViewStyle.Builder()
|
||||
.setLayoutMargin(0, 0, 0, 0)
|
||||
.build()
|
||||
setTextColor(fgColor.toArgb())
|
||||
setTextSize(16f)
|
||||
build()
|
||||
}
|
||||
val subtitleStyle = TextViewStyle.Builder().run {
|
||||
setLayoutMargin(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_bottom).toInt(),
|
||||
)
|
||||
.setTitleStyle(
|
||||
TextViewStyle.Builder()
|
||||
.setLayoutMargin(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_title_margin_bottom).toInt(),
|
||||
)
|
||||
.setTextColor(fgColor.toArgb())
|
||||
.setTextSize(16f)
|
||||
.build()
|
||||
)
|
||||
.setSubtitleStyle(
|
||||
TextViewStyle.Builder()
|
||||
.setLayoutMargin(
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_start).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_top).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_end).toInt(),
|
||||
context.resources.getDimension(R.dimen.suggestion_chip_fg_subtitle_margin_bottom).toInt(),
|
||||
)
|
||||
.setTextColor(ColorUtils.setAlphaComponent(fgColor.toArgb(), 150))
|
||||
.setTextSize(14f)
|
||||
.build()
|
||||
)
|
||||
.setEndIconStyle(
|
||||
ImageViewStyle.Builder()
|
||||
.setLayoutMargin(0, 0, 0, 0)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
stylesBuilder.addStyle(suggestionStyle)
|
||||
return stylesBuilder.build()
|
||||
setTextColor(ColorUtils.setAlphaComponent(fgColor.toArgb(), 150))
|
||||
setTextSize(14f)
|
||||
build()
|
||||
}
|
||||
val suggestionStyle = InlineSuggestionUi.newStyleBuilder().run {
|
||||
setSingleIconChipStyle(singleIconChipStyle)
|
||||
setChipStyle(chipStyle)
|
||||
setStartIconStyle(iconStyle)
|
||||
setEndIconStyle(iconStyle)
|
||||
setTitleStyle(titleStyle)
|
||||
setSubtitleStyle(subtitleStyle)
|
||||
build()
|
||||
}
|
||||
return UiVersions.newStylesBuilder().run {
|
||||
addStyle(suggestionStyle)
|
||||
build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getColorFromThemeAttribute(
|
||||
|
||||
@@ -361,10 +361,13 @@ class FlorisLocale private constructor(val base: Locale) {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.lowercase(locale: FlorisLocale): String = this.lowercase(locale.base)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.uppercase(locale: FlorisLocale): String = this.uppercase(locale.base)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun String.titlecase(locale: FlorisLocale = FlorisLocale.ROOT): String {
|
||||
return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale.base) else it.toString() }
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.structuralEqualityPolicy
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceData
|
||||
import dev.patrickgold.jetpref.datastore.model.PreferenceObserver
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.provider.OpenableColumns
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.neverEqualPolicy
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.patrickgold.florisboard.app.ext.EditorAction
|
||||
import dev.patrickgold.florisboard.app.settings.advanced.Backup
|
||||
@@ -188,7 +187,7 @@ class CacheManager(context: Context) {
|
||||
|
||||
var currentAction by mutableStateOf<EditorAction?>(null)
|
||||
var ext: Extension? = null
|
||||
var editor by mutableStateOf<T?>(null, neverEqualPolicy())
|
||||
var editor by mutableStateOf<T?>(null)
|
||||
var version by mutableIntStateOf(0)
|
||||
|
||||
val isModified get() = version > 0
|
||||
@@ -202,7 +201,6 @@ class CacheManager(context: Context) {
|
||||
inline fun <R> update(block: T.() -> R): R {
|
||||
// Method is designed to only be called when editor has been previously initialized
|
||||
val ret = block(editor!!)
|
||||
editor = editor
|
||||
version++
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package dev.patrickgold.florisboard.lib.compose
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
@@ -32,7 +32,7 @@ fun Modifier.rippleClickable(
|
||||
) = composed {
|
||||
this.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(),
|
||||
indication = ripple(),
|
||||
enabled = enabled,
|
||||
onClickLabel = onClickLabel,
|
||||
role = role,
|
||||
|
||||
@@ -19,9 +19,9 @@ package dev.patrickgold.florisboard.lib.compose
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
|
||||
@@ -25,9 +25,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -39,15 +37,10 @@ fun FlorisChip(
|
||||
onClick: () -> Unit = { },
|
||||
selected: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
color: Color = Color.Unspecified,
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
leadingIcons: List<ImageVector> = listOf(),
|
||||
trailingIcons: List<ImageVector> = listOf(),
|
||||
) {
|
||||
val backgroundColor = color.takeOrElse {
|
||||
MaterialTheme.colorScheme.onSurface.copy()
|
||||
}
|
||||
|
||||
InputChip(
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
@@ -88,42 +81,4 @@ fun FlorisChip(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/*Surface(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
color = backgroundColor,
|
||||
shape = shape,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 4.dp, horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
for (leadingIcon in leadingIcons) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.size(16.dp),
|
||||
imageVector = leadingIcon,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
for (trailingIcon in trailingIcons) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(16.dp),
|
||||
imageVector = trailingIcon,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -41,6 +42,7 @@ import dev.patrickgold.florisboard.app.LocalNavController
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
|
||||
import dev.patrickgold.jetpref.datastore.ui.PreferenceUiContent
|
||||
import org.florisboard.lib.android.AndroidVersion
|
||||
|
||||
@Composable
|
||||
fun FlorisScreen(builder: @Composable FlorisScreenScope.() -> Unit) {
|
||||
@@ -93,7 +95,7 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
|
||||
FlorisIconButton(
|
||||
onClick = { navController.popBackStack() },
|
||||
modifier = Modifier.autoMirrorForRtl(),
|
||||
icon = Icons.Default.ArrowBack,
|
||||
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,12 +124,18 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
|
||||
fun Render() {
|
||||
val context = LocalContext.current
|
||||
val previewFieldController = LocalPreviewFieldController.current
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
SideEffect {
|
||||
val window = (context as Activity).window
|
||||
previewFieldController?.isVisible = previewFieldVisible
|
||||
window.statusBarColor = Color.Transparent.toArgb()
|
||||
window.navigationBarColor = Color.Transparent.toArgb()
|
||||
if (AndroidVersion.ATLEAST_API29_Q) {
|
||||
window.navigationBarColor = Color.Transparent.toArgb()
|
||||
window.isNavigationBarContrastEnforced = true
|
||||
} else {
|
||||
window.navigationBarColor = colorScheme.scrim.toArgb()
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.lib.compose
|
||||
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
fun LazyGridScope.header(
|
||||
key: Any? = null,
|
||||
content: @Composable LazyGridItemScope.() -> Unit,
|
||||
) {
|
||||
item(key, span = { GridItemSpan(this.maxLineSpan) }, content = content)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dev.patrickgold.florisboard.lib.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun Modifier.conditional(
|
||||
condition: Boolean,
|
||||
modifier: @Composable Modifier.() -> Modifier
|
||||
): Modifier =
|
||||
if (condition) then(modifier(Modifier)) else this
|
||||
@@ -128,7 +128,7 @@ fun PreviewKeyboardField(
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { focusManager.clearFocus() },
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(autoCorrect = true),
|
||||
keyboardOptions = KeyboardOptions(autoCorrectEnabled = true),
|
||||
singleLine = true,
|
||||
shape = RectangleShape,
|
||||
colors = TextFieldDefaults.colors(
|
||||
|
||||
@@ -6,9 +6,9 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import org.florisboard.lib.android.AndroidSettingsHelper
|
||||
import org.florisboard.lib.android.SystemSettingsObserver
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ import android.os.Debug
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.app.AppPrefs
|
||||
import org.florisboard.lib.android.systemService
|
||||
import dev.patrickgold.florisboard.lib.titlecase
|
||||
import dev.patrickgold.florisboard.lib.util.TimeUtils
|
||||
import dev.patrickgold.florisboard.lib.util.UnitUtils
|
||||
import org.florisboard.lib.android.systemService
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
@@ -49,6 +49,36 @@ object Devtools {
|
||||
}
|
||||
}
|
||||
|
||||
fun generateDebugLogForGithub(context: Context, prefs: AppPrefs? = null, includeLogcat: Boolean = false): String {
|
||||
return buildString {
|
||||
appendLine("<details>")
|
||||
appendLine("<summary>Detailed info (Debug log header)</summary>")
|
||||
appendLine()
|
||||
appendLine("```")
|
||||
append(generateSystemInfoLog(context))
|
||||
appendLine()
|
||||
append(generateAppInfoLog(context))
|
||||
if (prefs != null) {
|
||||
appendLine()
|
||||
append(generateFeatureConfigLog(prefs))
|
||||
}
|
||||
appendLine()
|
||||
appendLine("```")
|
||||
appendLine("</details>")
|
||||
if (includeLogcat) {
|
||||
appendLine()
|
||||
appendLine("<details>")
|
||||
appendLine("<summary>Debug log content</summary>")
|
||||
appendLine()
|
||||
appendLine("```")
|
||||
append(generateLogcatDump())
|
||||
appendLine()
|
||||
appendLine("```")
|
||||
appendLine("</details>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun generateSystemInfoLog(context: Context, withTitle: Boolean = true): String {
|
||||
return buildString {
|
||||
if (withTitle) appendLine("======= SYSTEM INFO =======")
|
||||
|
||||
@@ -129,8 +129,9 @@ internal fun List<Extension>.generateUpdateUrl(
|
||||
return Uri.Builder().run {
|
||||
scheme("https")
|
||||
authority(host)
|
||||
appendPath("updates")
|
||||
appendPath(version)
|
||||
appendPath("check-updates")
|
||||
// TODO: Uncomment when version is supported by the addons store api
|
||||
//appendPath(version)
|
||||
encodedFragment(
|
||||
buildString {
|
||||
append("data={")
|
||||
|
||||
@@ -21,9 +21,6 @@ import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
|
||||
import dev.patrickgold.florisboard.lib.ValidationRule
|
||||
import org.florisboard.lib.snygg.SnyggStylesheet
|
||||
import org.florisboard.lib.snygg.value.SnyggDpShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggPercentShapeValue
|
||||
import org.florisboard.lib.snygg.value.SnyggSolidColorValue
|
||||
import dev.patrickgold.florisboard.lib.validate
|
||||
import org.florisboard.lib.snygg.value.SnyggVarValue
|
||||
|
||||
@@ -157,7 +154,7 @@ object ExtensionValidation {
|
||||
}
|
||||
|
||||
val SnyggSolidColorValue = ValidationRule<String> {
|
||||
forKlass = SnyggSolidColorValue::class
|
||||
forKlass = org.florisboard.lib.snygg.value.SnyggSolidColorValue::class
|
||||
forProperty = "color"
|
||||
validator { input ->
|
||||
val str = input.trim()
|
||||
@@ -172,7 +169,7 @@ object ExtensionValidation {
|
||||
}
|
||||
|
||||
val SnyggDpShapeValue = ValidationRule<String> {
|
||||
forKlass = SnyggDpShapeValue::class
|
||||
forKlass = org.florisboard.lib.snygg.value.SnyggDpShapeValue::class
|
||||
forProperty = "corner"
|
||||
validator { str ->
|
||||
val floatValue = str.toFloatOrNull()
|
||||
@@ -186,7 +183,7 @@ object ExtensionValidation {
|
||||
}
|
||||
|
||||
val SnyggPercentShapeValue = ValidationRule<String> {
|
||||
forKlass = SnyggPercentShapeValue::class
|
||||
forKlass = org.florisboard.lib.snygg.value.SnyggPercentShapeValue::class
|
||||
forProperty = "corner"
|
||||
validator { str ->
|
||||
val intValue = str.toIntOrNull()
|
||||
|
||||
@@ -13,9 +13,24 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">الرموز التعبيرية</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">وجوه تعبيرية</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">كاوموجي</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">الحد الأقصى لحجم السجل للرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">لون الرموز التعبيرية المفضل</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">نوع الشعر المفضل للرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_history__title" comment="Preference group title">سجل الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_history_enabled" comment="Preference title">تمكين سجل الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_history_enabled__summary" comment="Preference summary">الاحتفاظ بالرموز التعبيرية المستخدمة مؤخرًا للوصول السريع</string>
|
||||
<string name="prefs__media__emoji_history_pinned_update_strategy" comment="Preference title">تحديث الاستراتيجية (مثبت)</string>
|
||||
<string name="prefs__media__emoji_history_recent_update_strategy" comment="Preference title">إستراتيجية التحديث (الأخيرة)</string>
|
||||
<string name="prefs__media__emoji_history_max_size">الحد الأقصى للعناصر التي يجب الاحتفاظ بها</string>
|
||||
<string name="prefs__media__emoji_suggestion__title" comment="Preference group title">اقتراحات الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled" comment="Preference title">تمكين اقتراحات الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">تقديم اقتراحات الرموز التعبيرية أثناء الكتابة</string>
|
||||
<string name="prefs__media__emoji_suggestion_type" comment="Preference title">نوع التفعيل</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history" comment="Preference title">تحديث سجل الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history__summary" comment="Preference summary">قَبُول الرموز التعبيرية المقترحة يضيفها إلى تاريخ الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name" comment="Preference title">عرض اسم الرمز التعبيري</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name__summary" comment="Preference summary">تعرض اقتراحات الرموز التعبيرية اسمها جنبًا إلى جنب مع الرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_suggestion_query_min_length" comment="Preference title">الحد الأدنى لطول الاستعلام</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">الحد الأقصى لعدد المرشحين</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">وجوه تعبيرية و عواطف</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">أشخاص و أجسام</string>
|
||||
@@ -26,10 +41,12 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">أشياء</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">الرموز</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">أعلام</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">لم يتم العثور على رموز تعبيرية مستخدمة مؤخرًا. بمجرد أن تبدأ في استخدامها الرموز التعبيرية فإنها ستظهر تلقائيًا هنا.</string>
|
||||
<string name="emoji__recently_used__phone_locked_message" comment="Message to show if phone is locked">للوصول إلى سجل الرموز التعبيرية الخاص بك، يرجى أولاً إلغاء قُفْل جهازك.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">نصيحة للمحترفين: الضغط لفترة طويلة على الرموز التعبيرية المستخدمة مؤخرًا لإزالتها من هذا العرض مرة أخرى!</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">تمت إزالة {emoji} من الرموز التعبيرية المستخدمة مؤخرًا</string>
|
||||
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">لم يتم العثور على رموز تعبيرية مستخدمة مؤخرًا. بمجرد بَدْء كتابة الرموز التعبيرية، ستظهر تلقائيًا هنا.</string>
|
||||
<string name="emoji__history__phone_locked_message" comment="Message to show if phone is locked">للوصول إلى سجل الرموز التعبيرية الخاص بك، يرجى أولاً إلغاء قُفْل جهازك.</string>
|
||||
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">نصيحة احترافية: اضغط لفترة طويلة على الرموز التعبيرية في سجل الرموز التعبيرية لتثبيتها أو إزالتها!</string>
|
||||
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">تمت إزالة {emoji} من سجل الرموز التعبيرية</string>
|
||||
<string name="emoji__history__pinned">مثبّت</string>
|
||||
<string name="emoji__history__recent">الحديثة</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">سهم للأعلى</string>
|
||||
<string name="quick_action__arrow_up__tooltip">تنفيذ السهم للأعلى</string>
|
||||
@@ -83,7 +100,7 @@
|
||||
<string name="incognito_mode__toast_after_disabled">الوضع الخاص هو الان معطل افتراضيا</string>
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title" comment="Title of Settings">الإعدادات</string>
|
||||
<string name="settings__preview_keyboard" comment="Hint for try your setup box">قم بتجربة اعداداتك</string>
|
||||
<string name="settings__preview_keyboard" comment="Hint for try your setup box">قم بتجربة أعداداتك</string>
|
||||
<string name="settings__help" comment="General label for help buttons in Settings">مساعدة</string>
|
||||
<string name="settings__default" comment="General string which is used when a preference has the default value set">الإفتراضي</string>
|
||||
<string name="settings__system_default" comment="General string which is used when a preference has the system default value set">الإعداد الافتراضي</string>
|
||||
@@ -124,21 +141,20 @@
|
||||
<string name="settings__localization__subtype_error_fields_no_value" comment="Error message shown in subtype editor if at least one field is set to '- select -' (means no value specified)">حقل واحد على الأقل ليس له قيمة محددة. الرجاء اختيار قيمة للحقل (الحقول).</string>
|
||||
<string name="settings__localization__subtype_error_layout_not_installed" comment="Error message shown in subtype list when a layout is not installed, where %s will be replaced by the layout ID">{layout_id} (غير مثبت)</string>
|
||||
<string name="settings__localization__group_layouts__label" comment="Label of layouts group">التخطيطات</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_title" comment="Title of the subtype delete confirmation dialog">تأكيد الحذف</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_warning" comment="Warning message in the confirmation dialog to confirm the user's intent to delete">هل أنت متيقِّن أنك تريد حذف النوع الفرعي؟</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme screen">المظهر</string>
|
||||
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">وضع الثيم</string>
|
||||
<string name="pref__theme__sunrise_time__label" comment="Label of the sunrise time preference">وقت الشروق</string>
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">وقت الغروب</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">نسق نهاري</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">نسق ليلي</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">النسق المحدد</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">تكييف الألوان مع التطبيق</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">تتكيف ألوان الثيم مع تلك الموجودة في التطبيق الحالي ، إذا كان التطبيق المستهدف يدعم ذلك.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">أدارة السمات المثبتة</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">أصول تطبيق FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">الذاكرة الداخلية</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">مزود خارجي</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">اختيار النمط الصباحي</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">اختيار النمط الليلي</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">أدارة السمات المثبتة</string>
|
||||
<string name="settings__theme_editor__fine_tune__title">تعديل متقدم</string>
|
||||
<string name="settings__theme_editor__fine_tune__level">مستوى التعديل</string>
|
||||
<string name="settings__theme_editor__fine_tune__display_colors_as">عرض اللون كـ</string>
|
||||
@@ -311,6 +327,7 @@
|
||||
<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="pref__keyboard__incognito_indicator__label" comment="Preference title">علامة وضع التخفي</string>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="settings__smartbar__title" comment="Title of Smartbar screen">الشريط الذكـي</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">تفعيل الشريط الذكي</string>
|
||||
@@ -413,6 +430,7 @@
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">فاتح</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">داكن</string>
|
||||
<string name="pref__advanced__settings_theme__amoled_dark" comment="Possible value of Settings theme preference in Advanced">أسود قاتم</string>
|
||||
<string name="pref__advanced__settings_material_you__label" comment="Label of Material You preference in Advanced">استخدام تصميم Material You</string>
|
||||
<string name="pref__advanced__settings_language__label" comment="Label of Settings language preference in Advanced">إعدادات اللغة</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">إظهار أيقونة البرنامج في درج التطبيقات</string>
|
||||
<string name="pref__advanced__show_app_icon__summary_atleast_q" comment="Summary of Show app icon preference in Advanced for Android 10+">ممكّن دائمًا على اندرويد 10+ فما فوق بسبب قيود النظام</string>
|
||||
@@ -471,6 +489,10 @@
|
||||
<string name="backup_and_restore__back_up__files_ime_keyboard">ملحقات لوحة المفاتيح</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_spelling">ملحقات التهجئة/ القاموس</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_theme">ملحقات السمات</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history">سجل الحافظة</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_text_items">عناصر النص</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_image_items">صور</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_video_items">فيديوهات</string>
|
||||
<string name="backup_and_restore__back_up__success">تم تصدير أرشيف النسخة الاحتياطية بنجاح!</string>
|
||||
<string name="backup_and_restore__back_up__failure">فشل في تصدير أرشيف الاضافات: {error_message}</string>
|
||||
<string name="backup_and_restore__restore__title">إستعادة البيانات</string>
|
||||
@@ -558,6 +580,8 @@
|
||||
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">إظهار حالة تراكب الإدخال لأغراض التطوير</string>
|
||||
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">أظهر طبقات التهجئة</string>
|
||||
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">إظهار نتائج تراكب التهجئة لأغراض التطوير</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__label">عرض تراكب الملء التلقائي المضمن</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__summary">تراكب مع نتائج الملء التلقائي المضمنة الحالية للتصحيح</string>
|
||||
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">عرض حدود لمس الازرار</string>
|
||||
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">رسم حد خارجي للمس الازرار باللون الاحمر</string>
|
||||
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">إظهار مساعد السحب والإفلات</string>
|
||||
@@ -573,7 +597,16 @@
|
||||
<string name="devtools__android_settings_secure__title" comment="Title of Android settings (secure) screen">إعدادات أندرويد الأمنة</string>
|
||||
<string name="devtools__android_settings_system__title" comment="Title of Android settings (system) screen">إعدادات نظام أندرويد</string>
|
||||
<string name="devtools__android_locales__title" comment="Title of Android locales screen">لغات النظام</string>
|
||||
<string name="devtools__debuglog__title">سجل التصحيح</string>
|
||||
<string name="devtools__debuglog__copied_to_clipboard">تم نسخ سجل التصحيح إلى الحافظة</string>
|
||||
<string name="devtools__debuglog__copy_log">نسخ السجل</string>
|
||||
<string name="devtools__debuglog__copy_for_github">نسخ السجل (تنسيق GitHub)</string>
|
||||
<string name="devtools__debuglog__loading">تحميل…</string>
|
||||
<!-- Extension strings -->
|
||||
<string name="ext__home__title">الإضافات والملحقات</string>
|
||||
<string name="ext__list__ext_theme">ملحقات السمات</string>
|
||||
<string name="ext__list__ext_keyboard">ملحقات لوحة المفاتيح</string>
|
||||
<string name="ext__list__ext_languagepack">ملحقات حزم اللغة</string>
|
||||
<string name="ext__meta__authors">المؤلفون</string>
|
||||
<string name="ext__meta__components">العناصر المجمعة</string>
|
||||
<string name="ext__meta__components_theme">الثيمات المجمعة</string>
|
||||
@@ -626,6 +659,40 @@
|
||||
<string name="ext__import__file_skip_ext_not_supported" comment="Reason string when file is loaded in incorrect context">من المتوقع وجود ملف وسائط (صورة ، صوت ، خط ، إلخ) ولكن تم العثور على أرشيف ملحق.</string>
|
||||
<string name="ext__import__file_skip_media_not_supported" comment="Reason string when file is loaded in incorrect context">من المتوقع وجود أرشيف ملحق ولكن تم العثور على ملف وسائط (صورة ، صوت ، خط ، إلخ).</string>
|
||||
<string name="ext__import__error_unexpected_exception" comment="Label when an error occurred during import. The error message will be appended below this text view">حدث خطأ غير متوقع أثناء الاستيراد. تم توفير التفاصيل التالية:</string>
|
||||
<string name="ext__validation__enter_package_name">يرجى إدخال اسم الحُزْمَة</string>
|
||||
<string name="ext__validation__error_package_name">اسم الحُزْمَة لا يتطابق مع المدى {id_regex}</string>
|
||||
<string name="ext__validation__enter_version">الرجاء إدخال إصدار</string>
|
||||
<string name="ext__validation__enter_title">الرجاء إدخال عنوان</string>
|
||||
<string name="ext__validation__enter_maintainer">يرجى إدخال مشرف صيانة صالح واحد على الأقل</string>
|
||||
<string name="ext__validation__enter_license">يرجى إدخال معرف الترخيص</string>
|
||||
<string name="ext__validation__enter_component_id">يرجى إدخال معرّف المكون</string>
|
||||
<string name="ext__validation__error_component_id">يرجى إدخال معرف مكون مطابق {component_id_regex}</string>
|
||||
<string name="ext__validation__enter_component_label">يرجى إدخال تسمية المكون</string>
|
||||
<string name="ext__validation__hint_component_label_to_long">عنوان المكون الخاص بك طويل جدًا، مما قد يؤدي إلى أقتصاصه في واجهة المستخدم</string>
|
||||
<string name="ext__validation__error_author">يرجى إدخال منشئ صالح واحد على الأقل</string>
|
||||
<string name="ext__validation__error_stylesheet_path_blank">يجب ألا يكون مسار ورقة الأنماط فارغًا</string>
|
||||
<string name="ext__validation__error_stylesheet_path">يرجى إدخال مسار ورقة أنماط صالح يطابق {stylesheet_path_regex}</string>
|
||||
<string name="ext__validation__enter_property">يرجى إدخال اسم المتغير</string>
|
||||
<string name="ext__validation__error_property">يرجى إدخال اسم متغير صالح يطابق {variable_name_regex}</string>
|
||||
<string name="ext__validation__hint_property" tools:ignore="TypographyDashes">حسب الاتفاقية، يبدأ اسم متغير FlorisCSS بشرطتين (--)</string>
|
||||
<string name="ext__validation__enter_color">يرجى إدخال سلسلة ألوان</string>
|
||||
<string name="ext__validation__error_color">يرجى إدخال سلسلة ألوان صالحة</string>
|
||||
<string name="ext__validation__enter_dp_size">يرجى إدخال حجم dp</string>
|
||||
<string name="ext__validation__enter_valid_number">يرجى إدخال رقم صحيح</string>
|
||||
<string name="ext__validation__enter_positive_number">يرجى إدخال رَقْم موجب (>=0)</string>
|
||||
<string name="ext__validation__enter_percent_size">يرجى إدخال النسبة المئوية للحجم</string>
|
||||
<string name="ext__validation__enter_number_between_0_100">يرجى إدخال رَقْم موجب بين 0 و 100</string>
|
||||
<string name="ext__validation__hint_value_above_50_percent">أي قيمة أعلى من 50 ٪ ستتصرف كما لو أنك قمت بتعيين 50 ٪، فكر في خفض النسبة المئوية لحجمك</string>
|
||||
<string name="ext__update_box__internet_permission_hint">نظرًا لأن هذا التطبيق ليس لديه إذن إنترنت، يجب التحقق من تحديثات الإضافات المثبتة يدويًا.</string>
|
||||
<string name="ext__update_box__search_for_updates">البحث عن التحديثات</string>
|
||||
<string name="ext__addon_management_box__managing_placeholder">إدارة {extensions}</string>
|
||||
<string name="ext__addon_management_box__addon_manager_info">يمكن التعامل مع جميع المهام المتعلقة باستيراد الملحقات وتصديرها وإنشائها وتخصيصها وإزالتها بواسطة مدير الإضافات المركزي.</string>
|
||||
<string name="ext__addon_management_box__go_to_page">انتقل إلى {ext_home_title}</string>
|
||||
<string name="ext__home__info">يمكنك تنزيل الإضافات وتثبيتها من متجر إضافات FlorisBoard أو استيراد أي مِلَفّ ملحق قمت بتنزيله من الإنترنت.</string>
|
||||
<string name="ext__home__visit_store">زيارة متجر الإضافات</string>
|
||||
<string name="ext__home__manage_extensions">إدارة الإضافات المثبتة</string>
|
||||
<string name="ext__list__view_details">عرض التفاصيل</string>
|
||||
<string name="ext__check_updates__title">التحقق من وجود تحديثات</string>
|
||||
<!-- Action strings -->
|
||||
<string name="action__add">إضافة</string>
|
||||
<string name="action__apply">تطبيق</string>
|
||||
@@ -693,6 +760,14 @@
|
||||
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">يتم عرض أسماء اللغات عبر واجهة مستخدم التطبيق ولوحة المفاتيح باللغة المحلية التي تم تعيينها للجهاز بأكمله</string>
|
||||
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">اللغة الأصلية</string>
|
||||
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">يتم عرض أسماء اللغات عبر واجهة مستخدم التطبيق ولوحة المفاتيح باللغة المحلية المشار إليها بنفسها</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend" comment="Enum value label">الفرز التلقائي (الإعداد المسبق)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">إعادة ترتيب الرموز التعبيرية تلقائيًا عند استخدام الرموز التعبيرية. تمت إضافة رموز تعبيرية جديدة إلى البداية.</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append" comment="Enum value label">الفرز التلقائي (إلحاق)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append__description" comment="Enum value description">إعادة ترتيب الرموز التعبيرية تلقائيًا عند استخدام الرموز التعبيرية. تتم إضافة رموز تعبيرية جديدة إلى النهاية.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend" comment="Enum value label">الفرز اليدوي (التمهيدي)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend__description" comment="Enum value description">لا تقم بإعادة جلب الرموز التعبيرية تلقائيًا عند استخدام الرموز التعبيرية. تمت إضافة رموز تعبيرية جديدة إلى البداية.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append" comment="Enum value label">الفرز اليدوي (إلحاق)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append__description" comment="Enum value description">لا تقم بإعادة ترتيب الرموز التعبيرية تلقائيًا عند استخدام الرموز التعبيرية. تتم إضافة رموز تعبيرية جديدة إلى النهاية.</string>
|
||||
<string name="enum__emoji_skin_tone__default" comment="Enum value label">{emoji} اللون الافتراضي للبشرة</string>
|
||||
<string name="enum__emoji_skin_tone__light_skin_tone" comment="Enum value label">{emoji} اللون الفاتح للبشرة</string>
|
||||
<string name="enum__emoji_skin_tone__medium_light_skin_tone" comment="Enum value label">{emoji} اللون الفاتح المتوسط للبشرة</string>
|
||||
@@ -704,6 +779,10 @@
|
||||
<string name="enum__emoji_hair_style__curly_hair" comment="Enum value label">{emoji} شعر مجعد</string>
|
||||
<string name="enum__emoji_hair_style__white_hair" comment="Enum value label">{emoji} شعر ابيض</string>
|
||||
<string name="enum__emoji_hair_style__bald" comment="Enum value label">{emoji} اصلع</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon">فاصلة متقدمة</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon__description" comment="Keep the :emoji_name while translating, this is a syntax guide">اقترح الرموز التعبيرية باستخدام بناء جملة:emoji_name</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text">نص المعلومات المضمنة</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text__description">اقترح الرموز التعبيرية ببساطة عن طريق كتابة اسم الرموز التعبيرية ككلمة</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates" comment="Enum value label">تجاهل المرشح</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates__description" comment="Enum value description">يضع صف الإجراءات الموسعة بين واجهة مستخدم التطبيق والصف المرشح</string>
|
||||
<string name="enum__extended_actions_placement__below_candidates" comment="Enum value label">تحت المرشحون</string>
|
||||
@@ -720,6 +799,8 @@
|
||||
<string name="enum__key_hint_mode__hint_priority__description" comment="Enum value description">دائمًا ما يكون الحرف الأولي المحدد بعد الضغط لفترة طويلة هو رمز التلميح ، أو العلامة الأساسية في حالة عدم توفر رمز تلميح</string>
|
||||
<string name="enum__key_hint_mode__smart_priority" comment="Enum value label">تحديد الاولويات الذكي</string>
|
||||
<string name="enum__key_hint_mode__smart_priority__description" comment="Enum value description">يتم تحديد الحرف الأولي المحدد بعد الضغط لفترة طويلة ديناميكيًا ليكون إما العلامة الأساسية أو رمز التلميح ، بناءً على اللغة والتخطيط الحاليين</string>
|
||||
<string name="enum__incognito_display_mode__replace_shared_actions_toggle" comment="Enum value label">استبدل أيقونة تبديل الإجراءات المشتركة بمؤشر التصفح المتخفي</string>
|
||||
<string name="enum__incognito_display_mode__display_behind_keyboard" comment="Enum value label">عرض مؤشر التصفح المتخفي خلف لوحة المفاتيح</string>
|
||||
<string name="enum__incognito_mode__force_off" comment="Enum value label">إيقاف قسري</string>
|
||||
<string name="enum__incognito_mode__force_off__description" comment="Enum value description">سيتم دائما تعطيل وضع التصفح المتخفي، بغض النظر عن الخيارات التي تم تمريرها في التطبيق المستهدف. لن يتوفر الإجراء السريع للتصفح المتخفي في الشريط الذكي مع هذا الخِيار.</string>
|
||||
<string name="enum__incognito_mode__force_on" comment="Enum value label">تفعيل قسري</string>
|
||||
@@ -833,4 +914,20 @@
|
||||
<item quantity="many">{v} عناصر</item>
|
||||
<item quantity="other">{v} عناصر</item>
|
||||
</plurals>
|
||||
<plurals name="unit__characters__written">
|
||||
<item quantity="zero">{v} حرف</item>
|
||||
<item quantity="one">{v} حرف</item>
|
||||
<item quantity="two">{v} حرفان</item>
|
||||
<item quantity="few">{v} أحرف</item>
|
||||
<item quantity="many">{v} احرف</item>
|
||||
<item quantity="other">{v} احرف</item>
|
||||
</plurals>
|
||||
<plurals name="unit__candidates__written">
|
||||
<item quantity="zero">{v} مرشح</item>
|
||||
<item quantity="one">{v} مرشح</item>
|
||||
<item quantity="two">{v} مرشحان</item>
|
||||
<item quantity="few">{v} مرشحات</item>
|
||||
<item quantity="many">{v} مرشحات</item>
|
||||
<item quantity="other">{v} مرشحون</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Fustaxes</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Fustaxes ASCII</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">لون البشرة المفضل للرموز التعبيرية</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">تصفيفة الشعر الرموز التعبيرية المفضلة</string>
|
||||
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Activar l\'historial de fustaxes</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Sorrises y fustaxes</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Persones y cuerpu</string>
|
||||
@@ -23,18 +26,20 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Oxetos</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Símbolos</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Banderes</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">Nun s\'atopó nengún fustaxe que s\'usare apocayá. Namás que comiences a usalos, apaecen equí.</string>
|
||||
<string name="emoji__recently_used__phone_locked_message" comment="Message to show if phone is locked">P\'acceder al historial de fustaxes, desbloquia\'l preséu primero.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">Conseyu: ¡ten primíos los fustaxes usaos apocayá pa volver quitalos d\'esta seición!</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">Quitóse «{emoji}» de los fustaxes usaos apocayá</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">سهم لأعلى</string>
|
||||
<string name="quick_action__arrow_up__tooltip">أداء السهم لأعلى</string>
|
||||
<string name="quick_action__arrow_down" maxLength="12">سهم لأسفل</string>
|
||||
<string name="quick_action__arrow_down__tooltip">تنفيذ السهم لأسفل</string>
|
||||
<string name="quick_action__arrow_left" maxLength="12">Flecha esq.</string>
|
||||
<string name="quick_action__arrow_right" maxLength="12">Flecha der.</string>
|
||||
<string name="quick_action__clipboard_copy" maxLength="12">Copiar</string>
|
||||
<string name="quick_action__clipboard_cut" maxLength="12">Cortar</string>
|
||||
<string name="quick_action__clipboard_paste" maxLength="12">Apegar</string>
|
||||
<string name="quick_action__ime_ui_mode_clipboard" maxLength="12">Cartafueyu</string>
|
||||
<string name="quick_action__ime_ui_mode_clipboard__tooltip">Abrir l\'historial de fustaxes</string>
|
||||
<string name="quick_action__ime_ui_mode_media__tooltip">Abrir el panel de fustaxes</string>
|
||||
<string name="quick_action__settings" maxLength="12">Axustes</string>
|
||||
<string name="quick_action__settings" maxLength="12">Opciones</string>
|
||||
<string name="quick_action__settings__tooltip">Abrir la configuración</string>
|
||||
<string name="quick_action__undo" maxLength="12">Desfacer</string>
|
||||
<string name="quick_action__redo" maxLength="12">Refacer</string>
|
||||
@@ -45,12 +50,14 @@
|
||||
<!-- Incognito mode strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title" comment="Title of Settings">Configuración</string>
|
||||
<string name="settings__preview_keyboard" comment="Hint for try your setup box">Toca equí pa probar la configuración</string>
|
||||
<string name="settings__help" comment="General label for help buttons in Settings">Ayuda</string>
|
||||
<string name="settings__default" comment="General string which is used when a preference has the default value set">Por defeutu</string>
|
||||
<string name="settings__home__title" comment="Title of the Home screen">Afáyate en {app_name}</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and Layout screen">Llingües y distribuciones</string>
|
||||
<string name="settings__localization__display_language_names_in__label" comment="Label of Display language names in preference">Amosar los nomes de les llingües na</string>
|
||||
<string name="settings__localization__group_subtypes__label" comment="Label of subtypes group">Sotipos</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Amestar un sotipu</string>
|
||||
<string name="settings__localization__language_pack_title" comment="Title of the language pack manager screen for managing installed and custom language packs">Xestionar los paquetes de llingua instalaos</string>
|
||||
<string name="settings__localization__language_pack_summary" comment="Summary of preference item for adding a new language pack">Opción esperimental: xestiona les estensiones qu\'amiesten compatibilidá pa llingües específiques (pel momentu, entrada china basada en formes)</string>
|
||||
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Llingua primaria</string>
|
||||
@@ -65,6 +72,12 @@
|
||||
<string name="settings__localization__subtype_phone_layout" comment="Label for layout dropdown in subtype dialog">Distribución primaria del tecláu telefónicu</string>
|
||||
<string name="settings__localization__subtype_phone2_layout" comment="Label for layout dropdown in subtype dialog">Distribución secundaria del tecláu telefónicu</string>
|
||||
<string name="settings__localization__subtype_summary" comment="Subtype summary">{characters_name} / {symbols_name} / {currency_set_name}</string>
|
||||
<string name="settings__localization__suggested_subtype_presets" comment="Suggested presets title">Sotipos preconfiguraos suxeríos</string>
|
||||
<string name="settings__localization__suggested_subtype_presets_none_found" comment="Suggested presets none found">Nun hai nengún stipu suxeríu. Usa\'l botón d\'abaxo pa ver tolos sotipos preconfiguraos.</string>
|
||||
<string name="settings__localization__subtype_presets" comment="Subtype presets dialog title">Sotipos preconfiguraos</string>
|
||||
<string name="settings__localization__subtype_presets_view_all" comment="View all presets button">Amosar too</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined">Paez que nun configuresti nengún sotipu. ¡Va usase\'l sotipu «English/QWERTY» como alternativa!</string>
|
||||
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">¡Esti sotipu yá esiste!</string>
|
||||
<string name="settings__localization__group_layouts__label" comment="Label of layouts group">Distribuciones</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme screen">Estilu</string>
|
||||
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">Mou del estilu</string>
|
||||
@@ -72,10 +85,9 @@
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Hora del aséu</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Estilu diurnu</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Estilu nocherniegu</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Estilu seleicionáu</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Xestionar los estilos instalaos</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Almacenamientu internu</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Fornidor esternu</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Xestionar los estilos instalaos</string>
|
||||
<string name="settings__theme_editor__no_rules_defined">Esta fueya d\'estilu nun tien nenguna regla definida. Amiesta una regla pa comenzar a personalizar la fueya d\'estilu.</string>
|
||||
<string name="settings__theme_editor__rule_element">Elementu de destín</string>
|
||||
<string name="settings__theme_editor__rule_groups">Grupos</string>
|
||||
@@ -145,6 +157,7 @@
|
||||
<string name="pref__gestures__space_bar_title" comment="Preference group title">Xestos de la barra d\'espaciu</string>
|
||||
<string name="settings__advanced__title" comment="Title of Advanced settings">Configuración avanzada</string>
|
||||
<string name="pref__advanced__settings_theme__label" comment="Label of Settings theme preference in Advanced">Estilu de la configuración</string>
|
||||
<string name="pref__advanced__settings_material_you__label" comment="Label of Material You preference in Advanced">Usar «Material You»</string>
|
||||
<string name="pref__advanced__settings_language__label" comment="Label of Settings language preference in Advanced">Llingua de la configuración</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Amosar l\'iconu l\'aplicación nel llanzador</string>
|
||||
<!-- About UI strings -->
|
||||
@@ -156,7 +169,7 @@
|
||||
<string name="about__version_copied__title" comment="Title of the toast for copying the version string">La versión copióse al cartafueyu</string>
|
||||
<string name="about__version_copied__error" comment="Title of the error toast for copying the version string">Prodúxose daqué malo: {error_message}</string>
|
||||
<string name="about__changelog__title" comment="Preference title">Rexistru de cambeos</string>
|
||||
<string name="about__changelog__summary" comment="Preference summary">Les novedaes</string>
|
||||
<string name="about__changelog__summary" comment="Preference summary">Contién les novedaes</string>
|
||||
<string name="about__repository__title" comment="Preference title">Depósitu (GitHub)</string>
|
||||
<string name="about__repository__summary" comment="Preference summary">El códigu fonte, los discutinios, los problemes y más información</string>
|
||||
<string name="about__privacy_policy__title" comment="Preference title">Política de privacidá</string>
|
||||
@@ -167,15 +180,25 @@
|
||||
<string name="about__third_party_licenses__summary" comment="Preference summary">Les llicencies de les biblioteques de terceros que s\'inclúin nesta aplicación</string>
|
||||
<!-- Setup UI strings -->
|
||||
<string name="setup__title" comment="Title of Setup">¡Afáyate!</string>
|
||||
<string name="setup__intro_message" comment="Short intro message welcoming new users">¡Gracies por usar {app_name}! Esta configuración rápida va guiate pelos pasos necesarios pa usar {app_name} nel preséu.</string>
|
||||
<string name="setup__footer__privacy_policy" comment="Privacy policy label for URL">Política de privacidá</string>
|
||||
<string name="setup__footer__repository" comment="Repository label for URL">Depósitu</string>
|
||||
<string name="setup__enable_ime__title">Activación de {app_name}</string>
|
||||
<string name="setup__enable_ime__description">Android rique que tolos teclaos personalizaos s\'activen per separtao enantes de poder usalos. Vete a la configuración de <i>Llingua y entrada</i> p\'activar «{app_name}» ellí.</string>
|
||||
<string name="setup__enable_ime__open_settings_btn">Abrir la configuración del sistema</string>
|
||||
<string name="setup__select_ime__title">Seleición de {app_name}</string>
|
||||
<string name="setup__select_ime__description">{app_name} yá ta activáu nel sistema. Pa usalu, escueyi «{app_name}» nel diálogu del selector d\'entrada.</string>
|
||||
<string name="setup__select_ime__switch_keyboard_btn">Cambiar de tecláu</string>
|
||||
<string name="setup__grant_notification_permission__title">Avisos pa informar de casques</string>
|
||||
<string name="setup__grant_notification_permission__description">A partir d\'Android 13, les aplicaciones tienen de pidir
|
||||
permisu pa unviar avisos. En FlorisBoard, esti permisu namás s\'usa p\'abrir la pantalla d\'informar casques al producise dalgún.
|
||||
Pues camudar esti permisu cuando quieras na configuración del sistema.
|
||||
</string>
|
||||
<string name="setup__grant_notification_permission__btn">Conceder el permisu</string>
|
||||
<string name="setup__finish_up__title">Fin</string>
|
||||
<string name="setup__finish_up__description_p1">{app_name} ta activáu nel sistema y yá pues personalizalu.</string>
|
||||
<string name="setup__finish_up__description_p2">Si atopes cualesquier problema, fallu, casque o namás quier facer dalguna suxerencia, revisa\'l depósitu del proyeutu na pantalla «Tocante a».</string>
|
||||
<string name="setup__finish_up__description_p2">Si atopes cualesquier problema, fallu, casque o namás quier facer dalguna suxerencia, consulta\'l depósitu del proyeutu na pantalla «Tocante a».</string>
|
||||
<string name="setup__finish_up__finish_btn">Comenzar a personalizar</string>
|
||||
<!-- Back up & Restore -->
|
||||
<string name="backup_and_restore__back_up__destination_file_sys">Sistema de ficheros llocal</string>
|
||||
<string name="backup_and_restore__back_up__destination_share_intent">Aplicación de terceros pel menú de compartición</string>
|
||||
@@ -205,7 +228,7 @@
|
||||
<string name="send_to_clipboard__type_not_supported_error">Esti elementu multimedia nun ye compatible.</string>
|
||||
<!-- Devtools strings -->
|
||||
<string name="devtools__title" comment="Title of Devtools screen. Translators: treat this string as 'Developer tools' for translation, except a similar short term is available for your language.">Ferramientes de desendolcu</string>
|
||||
<string name="devtools__enabled__summary" comment="Summary of Enable developer tools in Devtools">Ferramientes diseñaes específicamente pa depurar y iguar problemes</string>
|
||||
<string name="devtools__enabled__summary" comment="Summary of Enable developer tools in Devtools">Ferramientes diseñaes específicamente pa depurar ya iguar problemes</string>
|
||||
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Amosar les llendes táctiles de les tecles</string>
|
||||
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Amosar los ayudantes de la función «drag&drop»</string>
|
||||
<string name="devtools__reset_flag_is_ime_set_up__summary" comment="Summary of Reset is IME set up flag in Devtools">Una aición de depuración pa volver amosar la pantalla de configuración</string>
|
||||
@@ -217,6 +240,7 @@
|
||||
<string name="devtools__android_settings_system__title" comment="Title of Android settings (system) screen">Configuración del sistema Android</string>
|
||||
<string name="devtools__android_locales__title" comment="Title of Android locales screen">Locales del sistema</string>
|
||||
<!-- Extension strings -->
|
||||
<string name="ext__home__title">Complementos y estensiones</string>
|
||||
<string name="ext__meta__description">Descripción</string>
|
||||
<string name="ext__meta__id">ID</string>
|
||||
<string name="ext__meta__keywords">Pallabres clave</string>
|
||||
@@ -226,13 +250,20 @@
|
||||
<string name="ext__error__not_found_description">Nun se pudo atopar nenguna estensión cola ID «{id}».</string>
|
||||
<string name="ext__editor__title_create_any">Creación d\'una estensión</string>
|
||||
<string name="ext__editor__metadata__title_invalid">Los metadatos nun son válidos</string>
|
||||
<string name="ext__editor__metadata__message_invalid">Los metadatos d\'esta estensión nun son válidos. ¡Revisa l\'editor de metadatos pa consiguir más detalles!</string>
|
||||
<string name="ext__editor__metadata__message_invalid">Los metadatos d\'esta estensión nun son válidos. ¡Consulta l\'editor de metadatos pa consiguir más detalles!</string>
|
||||
<string name="ext__editor__dependencies__title">Xestionar les dependencies</string>
|
||||
<string name="ext__editor__create_component__title_theme">Creación d\'un estilu</string>
|
||||
<string name="ext__export__failure">La esportación de la estensión falló: {error_message}</string>
|
||||
<string name="ext__import__failure">La importación de la estensión falló: {error_message}</string>
|
||||
<string name="ext__import__file_skip" comment="Label when a file cannot be imported in the current context. The actual reason string is in a separate text view below this string.">El ficheru nun se pue importar. Motivu:</string>
|
||||
<string name="ext__import__error_unexpected_exception" comment="Label when an error occurred during import. The error message will be appended below this text view">Prodúxose un error inesperáu demientres la importación. Forniéronse los detalles siguientes:</string>
|
||||
<string name="ext__validation__enter_version">Introduz una versión</string>
|
||||
<string name="ext__validation__enter_title">Introduz un títulu</string>
|
||||
<string name="ext__validation__enter_valid_number">Introduz nun númberu válidu</string>
|
||||
<string name="ext__validation__enter_positive_number">Introduz un númberu positivu (>=0)</string>
|
||||
<string name="ext__update_box__search_for_updates">Buscar anovamientos</string>
|
||||
<string name="ext__addon_management_box__go_to_page">Dir a «{ext_home_title}»</string>
|
||||
<string name="ext__home__visit_store">Visitar la tienda de complementos</string>
|
||||
<!-- Action strings -->
|
||||
<string name="action__add">Amestar</string>
|
||||
<string name="action__apply">Aplicar</string>
|
||||
|
||||
@@ -13,9 +13,24 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Емоции</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Емоции</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Каомоджи</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">Дължина на историята на емоциите</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">Предпочитан цвят на кожата за емоции</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">Предпочитана прическа за емоции</string>
|
||||
<string name="prefs__media__emoji_history__title" comment="Preference group title">История на емоциите</string>
|
||||
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Включване на историята на емоциите</string>
|
||||
<string name="prefs__media__emoji_history_enabled__summary" comment="Preference summary">Запазване на използваните емоции за по-бърз достъп</string>
|
||||
<string name="prefs__media__emoji_history_pinned_update_strategy" comment="Preference title">Стратегия за обновяване (закачени)</string>
|
||||
<string name="prefs__media__emoji_history_recent_update_strategy" comment="Preference title">Стратегия за обновяване (последни)</string>
|
||||
<string name="prefs__media__emoji_history_max_size">Максимален брой елементи</string>
|
||||
<string name="prefs__media__emoji_suggestion__title" comment="Preference group title">Предложения за емоции</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled" comment="Preference title">Включване на предложенията за емоции</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">Показва предложения за емоции докато въвеждате</string>
|
||||
<string name="prefs__media__emoji_suggestion_type" comment="Preference title">Вид на задействането</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history" comment="Preference title">Обновяване на историята на емоциите</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history__summary" comment="Preference summary">Приемане на предложената емиция я добавя в историята на емоции</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name" comment="Preference title">Показване на името на емоцията</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name__summary" comment="Preference summary">Предложенията за емоции включват и името</string>
|
||||
<string name="prefs__media__emoji_suggestion_query_min_length" comment="Preference title">Минимална дължина на заявка</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">Максимален брой кандидати</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Усмивки и емоции</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Хора и тяло</string>
|
||||
@@ -26,10 +41,12 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Предмети</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Символи</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Знамена</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">Скоро не сте ползвали еможи. Като започнете да използвате ще се показват тук.</string>
|
||||
<string name="emoji__recently_used__phone_locked_message" comment="Message to show if phone is locked">За достъп до последно използваните емоции отключете устройството.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">Подсказка: За да премахнете емоциите от този списък задръжте върху него!</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">Премахнато е {emoji} от последно използваните</string>
|
||||
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">Скоро не сте използвали емоции. Започнете да използвате и ще бъдат показани тук.</string>
|
||||
<string name="emoji__history__phone_locked_message" comment="Message to show if phone is locked">За достъп до последно използваните емоции отключете устройството.</string>
|
||||
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">Подсказка: Задръжте върху емоция в историята, за да я закачите или премахнате!</string>
|
||||
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">Емоцията {emoji} е премахната от историята</string>
|
||||
<string name="emoji__history__pinned">Закачени</string>
|
||||
<string name="emoji__history__recent">Последни</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">Нагоре</string>
|
||||
<string name="quick_action__arrow_up__tooltip">Извършва стрелка нагоре</string>
|
||||
@@ -89,13 +106,13 @@
|
||||
<string name="settings__system_default" comment="General string which is used when a preference has the system default value set">По подразбиране от системата</string>
|
||||
<string name="settings__home__title" comment="Title of the Home screen">Добре дошли във {app_name}</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__ime_not_selected" comment="Warning message shown in Home fragment when FlorisBoard is not selected as the default keyboard">Клавиатурата FlorisBoard не е избрана като подразбиран метод за въвеждане. Докоснете, за да направите промяната.</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and Layout screen">Езици и подредби</string>
|
||||
<string name="settings__localization__display_language_names_in__label" comment="Label of Display language names in preference">Изписване на езиците на</string>
|
||||
<string name="settings__localization__group_subtypes__label" comment="Label of subtypes group">Подредби</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Добавяне на подредба</string>
|
||||
<string name="settings__localization__language_pack_title" comment="Title of the language pack manager screen for managing installed and custom language packs">Управление на инсталираните езикови пакети</string>
|
||||
<string name="settings__localization__language_pack_summary" comment="Summary of preference item for adding a new language pack">Ексоериментално: управление на добавки за поддръжка на ооределени езици (въвеждане чрез фигури на китайски език)</string>
|
||||
<string name="settings__localization__language_pack_summary" comment="Summary of preference item for adding a new language pack">Експериментално: управление на добавки за поддръжка на определени езици (въвеждане чрез фигури на китайски език)</string>
|
||||
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Променяне на подредба</string>
|
||||
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Основен език</string>
|
||||
<string name="settings__localization__subtype_popup_mapping" comment="Label for popup mapping dropdown in subtype screen">Подсказки</string>
|
||||
@@ -124,21 +141,20 @@
|
||||
<string name="settings__localization__subtype_error_fields_no_value" comment="Error message shown in subtype editor if at least one field is set to '- select -' (means no value specified)">Има поле е без стойност. Изберете стойност за всички полета.</string>
|
||||
<string name="settings__localization__subtype_error_layout_not_installed" comment="Error message shown in subtype list when a layout is not installed, where %s will be replaced by the layout ID">{layout_id} (не е инсталирано)</string>
|
||||
<string name="settings__localization__group_layouts__label" comment="Label of layouts group">Подредби</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_title" comment="Title of the subtype delete confirmation dialog">Потвърждение при премахване</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_warning" comment="Warning message in the confirmation dialog to confirm the user's intent to delete">Желаете ли да премахнете този подвид?</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme screen">Тема</string>
|
||||
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">Режим на тема</string>
|
||||
<string name="pref__theme__sunrise_time__label" comment="Label of the sunrise time preference">Време на изгрев</string>
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Време на залез</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Дневна тема</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Нощна тема</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Избрана тема</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Адаптиращи се цветове</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Темата се адаптира спрямо текущото приложение, ако то го поддържа.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Управление на теми</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">Активи на FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Вътрешно хранилище</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Външен доставчик</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">Избор на дневна тема</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">Избор на нощна тема</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Управление на теми</string>
|
||||
<string name="settings__theme_editor__fine_tune__title">Настройки на редактора</string>
|
||||
<string name="settings__theme_editor__fine_tune__level">Режим</string>
|
||||
<string name="settings__theme_editor__fine_tune__display_colors_as">Формат на цветовете</string>
|
||||
@@ -311,6 +327,7 @@
|
||||
<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="pref__keyboard__incognito_indicator__label" comment="Preference title">Индикатор за инкогнито</string>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="settings__smartbar__title" comment="Title of Smartbar screen">Интелигентна лента</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Интелигентна лента</string>
|
||||
@@ -413,6 +430,7 @@
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">Светла</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Тъмна</string>
|
||||
<string name="pref__advanced__settings_theme__amoled_dark" comment="Possible value of Settings theme preference in Advanced">Тъмна AMOLED</string>
|
||||
<string name="pref__advanced__settings_material_you__label" comment="Label of Material You preference in Advanced">Използване на Material You</string>
|
||||
<string name="pref__advanced__settings_language__label" comment="Label of Settings language preference in Advanced">Език на настройките</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Икона на приложението в стартовия панел</string>
|
||||
<string name="pref__advanced__show_app_icon__summary_atleast_q" comment="Summary of Show app icon preference in Advanced for Android 10+">Винаги видима за Android 10+ поради ограничения на системата</string>
|
||||
@@ -470,6 +488,10 @@
|
||||
<string name="backup_and_restore__back_up__files_ime_keyboard">Добавки за клавиатури</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_spelling">Добавки за правопис / речници</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_theme">Добавки за теми</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history">История на междинната памет</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_text_items">Текстови елементи</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_image_items">Изображения</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_video_items">Видеоклипове</string>
|
||||
<string name="backup_and_restore__back_up__success">Резервното копие е изнесено успешно!</string>
|
||||
<string name="backup_and_restore__back_up__failure">Грешка при изнасяне на резервно копие: {error_message}</string>
|
||||
<string name="backup_and_restore__restore__title">Възстановяване на данни</string>
|
||||
@@ -510,7 +532,7 @@
|
||||
<string name="clipboard__locked__message">За достъп до междинната памет отключете устройството.</string>
|
||||
<string name="clipboard__group_pinned">Закачени</string>
|
||||
<string name="clipboard__group_recent">Последни</string>
|
||||
<string name="clipboard__group_other">Друго</string>
|
||||
<string name="clipboard__group_other">Други</string>
|
||||
<string name="clipboard__item_description_email">Електронна поща</string>
|
||||
<string name="clipboard__item_description_url">Адрес</string>
|
||||
<string name="clipboard__item_description_phone">Телефон</string>
|
||||
@@ -537,7 +559,7 @@
|
||||
<string name="pref__clipboard__enable_clipboard_history__summary">Запазване на копираното за по-бърз достъп до него</string>
|
||||
<string name="pref__clipboard__clean_up_old__label">Премахване на стари елементи</string>
|
||||
<string name="pref__clipboard__clean_up_after__label">Премахване на старите елементи след</string>
|
||||
<string name="pref__clipboard__limit_history_size__label">Ограничаване на броя</string>
|
||||
<string name="pref__clipboard__limit_history_size__label">Ограничаване на броя елементи</string>
|
||||
<string name="pref__clipboard__max_history_size__label">Максимален брой елементи</string>
|
||||
<string name="pref__clipboard__clear_primary_clip_deletes_last_item__label">Изчистване на текущата межд. памет влияе на историята</string>
|
||||
<string name="pref__clipboard__clear_primary_clip_deletes_last_item__summary">Изчистване на текущото съдържание на междинната памет изчиства и последния елемент от историята</string>
|
||||
@@ -557,6 +579,8 @@
|
||||
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Слой със състоянието на полето за въвеждане, за отстраняване на дефекти</string>
|
||||
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Слой за проверка на правописа</string>
|
||||
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Слой със съдържанието от проверката на правопис, за отстраняване на дефекти</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__label">Автоматично попълване в слой</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__summary">Слой с резултатите от автоматичното попълване, за отстраняване на дефекти</string>
|
||||
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Видим контур на клавишите</string>
|
||||
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Оцветяване на контура на клавишите в червено</string>
|
||||
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Манипулатори за влачене и пускане</string>
|
||||
@@ -572,7 +596,16 @@
|
||||
<string name="devtools__android_settings_secure__title" comment="Title of Android settings (secure) screen">Настройки на сигурността на Android</string>
|
||||
<string name="devtools__android_settings_system__title" comment="Title of Android settings (system) screen">Системни настройки на Android</string>
|
||||
<string name="devtools__android_locales__title" comment="Title of Android locales screen">Езици на системата</string>
|
||||
<string name="devtools__debuglog__title">Дневник за отстраняване на дефекти</string>
|
||||
<string name="devtools__debuglog__copied_to_clipboard">Съдържанието на дневника е копирано</string>
|
||||
<string name="devtools__debuglog__copy_log">Копиране</string>
|
||||
<string name="devtools__debuglog__copy_for_github">Копиране (форматирано за Гитхъб)</string>
|
||||
<string name="devtools__debuglog__loading">Зареждане…</string>
|
||||
<!-- Extension strings -->
|
||||
<string name="ext__home__title">Добавки и разширения</string>
|
||||
<string name="ext__list__ext_theme">Добавки за теми</string>
|
||||
<string name="ext__list__ext_keyboard">Добавки за клавиатури</string>
|
||||
<string name="ext__list__ext_languagepack">Езикови пакети</string>
|
||||
<string name="ext__meta__authors">Автори</string>
|
||||
<string name="ext__meta__components">Придружаващи компоненти</string>
|
||||
<string name="ext__meta__components_theme">Придружаващи теми</string>
|
||||
@@ -625,13 +658,47 @@
|
||||
<string name="ext__import__file_skip_ext_not_supported" comment="Reason string when file is loaded in incorrect context">Очакваше се медиен файл (изображение, аудио, шрифт, т.н.), но бе намерен архив с разширение.</string>
|
||||
<string name="ext__import__file_skip_media_not_supported" comment="Reason string when file is loaded in incorrect context">Очакваше се архив с разширение, но бе намерен медиен файл (изображение, аудио, шрифт, т.н.).</string>
|
||||
<string name="ext__import__error_unexpected_exception" comment="Label when an error occurred during import. The error message will be appended below this text view">Неочаквана грешка при внасяне. Предоставени са следните подробности:</string>
|
||||
<string name="ext__validation__enter_package_name">Въведете име на пакета</string>
|
||||
<string name="ext__validation__error_package_name">Името на пакетът не удовлетворява регулярния израз {id_regex}</string>
|
||||
<string name="ext__validation__enter_version">Въведете издание</string>
|
||||
<string name="ext__validation__enter_title">Въведете заглавие</string>
|
||||
<string name="ext__validation__enter_maintainer">Въведете най-малко един отговорник</string>
|
||||
<string name="ext__validation__enter_license">Въведете идентификатор на лиценз</string>
|
||||
<string name="ext__validation__enter_component_id">Въведете идентификатор на компонент</string>
|
||||
<string name="ext__validation__error_component_id">Въведете идентификатор на компонент, удовлетворяващ регулярния израз {component_id_regex}</string>
|
||||
<string name="ext__validation__enter_component_label">Въведете етикет на компонент</string>
|
||||
<string name="ext__validation__hint_component_label_to_long">Етикетът на компонента е твърде дълъг, което може да доведе до отрязване в интерфейса</string>
|
||||
<string name="ext__validation__error_author">Въведете най-малко един автор</string>
|
||||
<string name="ext__validation__error_stylesheet_path_blank">Пътят към стиловия лист не трябва да е празен</string>
|
||||
<string name="ext__validation__error_stylesheet_path">Въведете път към стилов лист, удовлетворяващ регулярния израз {stylesheet_path_regex}</string>
|
||||
<string name="ext__validation__enter_property">Въведете име на променливата</string>
|
||||
<string name="ext__validation__error_property">Въведете име на променлива, удовлетворяващо регулярния израз {variable_name_regex}</string>
|
||||
<string name="ext__validation__hint_property" tools:ignore="TypographyDashes">По правило имената на променливите на FlorisCSS започват с две тирета (--)</string>
|
||||
<string name="ext__validation__enter_color">Въведете низ на цвят</string>
|
||||
<string name="ext__validation__error_color">Въведете низ на съществуващ цвят</string>
|
||||
<string name="ext__validation__enter_dp_size">Въведете размер на dp</string>
|
||||
<string name="ext__validation__enter_valid_number">Въведете число</string>
|
||||
<string name="ext__validation__enter_positive_number">Въведете положително число (>=0)</string>
|
||||
<string name="ext__validation__enter_percent_size">Въведете размер в проценти</string>
|
||||
<string name="ext__validation__enter_number_between_0_100">Въведете положително число между 0 и 100</string>
|
||||
<string name="ext__validation__hint_value_above_50_percent">Стойности над 50% ще бъдат приравнени на 50%, така че намалете стойността</string>
|
||||
<string name="ext__update_box__internet_permission_hint">Понеже приложението няма достъп до интернет, проверката за обновяване на инсталираните разширения се извършва ръчно.</string>
|
||||
<string name="ext__update_box__search_for_updates">Проверка за обновяване</string>
|
||||
<string name="ext__addon_management_box__managing_placeholder">Управление на {extensions}</string>
|
||||
<string name="ext__addon_management_box__addon_manager_info">Всички дейности, свързани с внасяне, изнасяне, създаване, промяна и премахване на разширения могат да бъдат извършване през Управление на разширения.</string>
|
||||
<string name="ext__addon_management_box__go_to_page">Към {ext_home_title}</string>
|
||||
<string name="ext__home__info">Можете да изтегляте и инсталирате разширения от магазина за добавки на FlorisBoard или да внесете файл на разширение, който сте изтеглили от интернет.</string>
|
||||
<string name="ext__home__visit_store">Магазин за добавки</string>
|
||||
<string name="ext__home__manage_extensions">Управление на инсталирани разширения</string>
|
||||
<string name="ext__list__view_details">Подробности</string>
|
||||
<string name="ext__check_updates__title">Проверка за обновяване</string>
|
||||
<!-- Action strings -->
|
||||
<string name="action__add">Добавяне</string>
|
||||
<string name="action__apply">Прилагане</string>
|
||||
<string name="action__back_up">Резервно копие</string>
|
||||
<string name="action__cancel">Отказ</string>
|
||||
<string name="action__create">Създаване</string>
|
||||
<string name="action__default">По подразбиране</string>
|
||||
<string name="action__default">Стандартни</string>
|
||||
<string name="action__delete">Премахване</string>
|
||||
<string name="action__delete_confirm_title">Потвърждаване на изтриване</string>
|
||||
<string name="action__delete_confirm_message">Наистина ли искате да изтриете „{name}“? Това действие веднъж изпълнено не може да бъде отменено.</string>
|
||||
@@ -692,6 +759,14 @@
|
||||
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Имената на езиците в приложението и клавиатурата са на подразбирания за устройствто език</string>
|
||||
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">Присъщия за езика</string>
|
||||
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Имената на езиците в приложението и клавиатурата са на присъщия за езика</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend" comment="Enum value label">Автом. сортиране (добавяне отпред)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">Автоматично сортиране при използване на емоция. Новите емоции биват добавени в началото.</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append" comment="Enum value label">Автом. сортиране (добавяне отзад)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append__description" comment="Enum value description">Автоматично сортиране при използване на емоция. Новите емоции биват добавени в края.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend" comment="Enum value label">Ръчно сортиране (добавяне отпред)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend__description" comment="Enum value description">Без автоматично сортиране при използване на емоция. Новите емоции биват добавени в началото.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append" comment="Enum value label">Ръчно сортиране (добавяне отзад)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append__description" comment="Enum value description">Без автоматично сортиране при използване на емоция. Новите емоции биват добавени в края.</string>
|
||||
<string name="enum__emoji_skin_tone__default" comment="Enum value label">{emoji} Подразбиран цвят на кожата</string>
|
||||
<string name="enum__emoji_skin_tone__light_skin_tone" comment="Enum value label">{emoji} Светъл цвят на кожа</string>
|
||||
<string name="enum__emoji_skin_tone__medium_light_skin_tone" comment="Enum value label">{emoji} Средно светъл цвят на кожата</string>
|
||||
@@ -703,6 +778,10 @@
|
||||
<string name="enum__emoji_hair_style__curly_hair" comment="Enum value label">{emoji} Къдрава коса</string>
|
||||
<string name="enum__emoji_hair_style__white_hair" comment="Enum value label">{emoji} Бял цвят на косата</string>
|
||||
<string name="enum__emoji_hair_style__bald" comment="Enum value label">{emoji} Без коса</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon">Водещо двоеточие</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon__description" comment="Keep the :emoji_name while translating, this is a syntax guide">Предлагане на емоции чрез синтаксиса :име_на_емоция</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text">Обикновен текст</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text__description">Предлагане на емоции чрез въвеждане на името на емоцията като дума</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates" comment="Enum value label">Над кандидатите</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates__description" comment="Enum value description">Добавя се реда с допълнителни действия между приложението и реда с кандидати</string>
|
||||
<string name="enum__extended_actions_placement__below_candidates" comment="Enum value label">Под кандидатите</string>
|
||||
@@ -719,6 +798,8 @@
|
||||
<string name="enum__key_hint_mode__hint_priority__description" comment="Enum value description">Първоначално избраният знак след задържане на клавиш винаги е знака от подсказката или основния акцент ако в подсказката няма знак</string>
|
||||
<string name="enum__key_hint_mode__smart_priority" comment="Enum value label">Интелигентен приоритет</string>
|
||||
<string name="enum__key_hint_mode__smart_priority__description" comment="Enum value description">Първоначално избраният знак при задържане на клавиш се определя между основния акцент или знака от подсказката на базата на текущия език и подредба</string>
|
||||
<string name="enum__incognito_display_mode__replace_shared_actions_toggle" comment="Enum value label">Заменя пиктограмата на клавиша за превкл. на действията с индикатора за инкогнито</string>
|
||||
<string name="enum__incognito_display_mode__display_behind_keyboard" comment="Enum value label">Показва индикатора за инкогнито зад клавиатурата</string>
|
||||
<string name="enum__incognito_mode__force_off" comment="Enum value label">Принудително изключено</string>
|
||||
<string name="enum__incognito_mode__force_off__description" comment="Enum value description">Режим „инкогнито“ винаги ще бъде изключен, независимо какво подава целевото приложение. Клавишът за действието няма да бъде достъпен в интелигентната лента.</string>
|
||||
<string name="enum__incognito_mode__force_on" comment="Enum value label">Принудително включено</string>
|
||||
@@ -816,4 +897,12 @@
|
||||
<item quantity="one">{v} елемент</item>
|
||||
<item quantity="other">{v} елемента</item>
|
||||
</plurals>
|
||||
<plurals name="unit__characters__written">
|
||||
<item quantity="one">{v} знак</item>
|
||||
<item quantity="other">{v} знака</item>
|
||||
</plurals>
|
||||
<plurals name="unit__candidates__written">
|
||||
<item quantity="one">{v} кандидат</item>
|
||||
<item quantity="other">{v} кандидата</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Vrijeme zalaska sunca</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Dnevna tema</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Noćna tema</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Odabrana tema</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Adaptiraj boje prema aplikaciji</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Interna memorija</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences screen">Tastatura</string>
|
||||
<string name="pref__keyboard__number_row__label" comment="Preference title">Red sa brojevima</string>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Emojis</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Emoticones</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">Mida màxima de l\'historial d\'emojis</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">To de pell preferit dels emojis</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">Pentinat preferit dels emojis</string>
|
||||
<!-- Emoji strings -->
|
||||
@@ -26,9 +25,6 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Objectes</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Símbols</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Banderes</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">No s\'han trobat emojis recents. En començar a escriure emojis, apareixeran automàticament aquí.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">Consell: Manteniu premut els emojis recents per a suprimir-los d\'aquesta llista!</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">S\'ha suprimit {emoji} dels emojis recents</string>
|
||||
<!-- Quick action strings -->
|
||||
<!-- Incognito mode strings -->
|
||||
<!-- Settings UI strings -->
|
||||
@@ -80,15 +76,12 @@
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Hora de posta de sol</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Tema diürn</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Tema nocturn</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Selecció del tema</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Adapta els colors a l\'aplicació</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Els colors del tema s\'adapten als de l\'aplicació actual, si l\'aplicació de destinació ho admet.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Gestiona els temes instal·lats</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">Actius d\'aplicacions FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Emmagatzematge intern</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Proveïdor extern</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">Selecciona el tema del dia</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">Selecciona el tema de nit</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Gestiona els temes instal·lats</string>
|
||||
<string name="settings__theme_editor__fine_tune__display_colors_as">Mostrar els colors com</string>
|
||||
<string name="settings__theme_editor__add_rule">Afegir norma</string>
|
||||
<string name="settings__theme_editor__edit_rule">Editar norma</string>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">خەندەکان</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">هێمای دەربڕینی هەستەکان</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">کامۆجی</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">ڕێژەی پیشاندانی خەندە لە مێژووی خەندەکان</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">ڕەنگی بنەڕەتی خەندەکان</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">ڕەنگی بنەڕەتی قژی خەندەکان</string>
|
||||
<!-- Emoji strings -->
|
||||
@@ -26,9 +25,6 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">ئامانجەکان</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">هێماکان</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">ئاڵاکان</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">هیچ ئیمۆجیەک بەکارنەهاتووە، هەرکاتێ ئیمۆجیەکانت بەکارهێنا بەشێوەیەکی خۆکاری لێرەدا دەردەکەون.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">زانیاری: بۆ سڕینەوەی ئیمۆجیەکان لەم بەشەدا کەمێک دەستی لەسەر ڕابگرە.</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">ئیمۆجی {emoji} لابرا لەلیستی دوایین بەکارهاتووەکان</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">چونە سەرەوە</string>
|
||||
<string name="quick_action__arrow_up__tooltip">چوونە سەرەتای دێڕ</string>
|
||||
@@ -129,15 +125,12 @@
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">کاتی خۆر ئاوابوون</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">ڕووناک</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">تاریک</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">ڕووکاری هەڵبژێردراو</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">گۆڕینی ڕەنگی ڕووکار بەپێی بەرنامەکان</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">ڕەنگی ڕووکار بەخۆکاری دەگۆڕێت لەگەڵ ڕەنگی بەرنامەکان ئەگەر بەرنامەکە پشتگیری هەمەڕەنگ بێت.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">ڕێکخستنی رووکارەکان</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">ڕەنگی بنەڕەتی بەرنامەکە</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">بیرگەی ناخۆیی</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">بیرگەی دەرەکی</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">ڕووکارەکانی دۆخی ڕووناک</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">ڕووکارەکانی دۆخی تاریک</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">ڕێکخستنی رووکارەکان</string>
|
||||
<string name="settings__theme_editor__fine_tune__title">گۆڕینی ڕەنگ</string>
|
||||
<string name="settings__theme_editor__fine_tune__level">ئاستی دەستکاریکردن</string>
|
||||
<string name="settings__theme_editor__fine_tune__display_colors_as">پیشاندانی ڕەنگەکان بەشێوەی</string>
|
||||
|
||||
@@ -13,9 +13,24 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Emotikony</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Emotikony</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">Maximální velikost historie emotikonů</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">Preferovaná barva pokožky emotikonů</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">Preferovaný styl vlasů emotikonů</string>
|
||||
<string name="prefs__media__emoji_history__title" comment="Preference group title">Historie emotikonů</string>
|
||||
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Zapnout historii emotikonů</string>
|
||||
<string name="prefs__media__emoji_history_enabled__summary" comment="Preference summary">Uchovávat nedávno použité emotikony pro rychlý přístup</string>
|
||||
<string name="prefs__media__emoji_history_pinned_update_strategy" comment="Preference title">Strategie aktualizace (připnuté)</string>
|
||||
<string name="prefs__media__emoji_history_recent_update_strategy" comment="Preference title">Strategie aktualizace (nedávné)</string>
|
||||
<string name="prefs__media__emoji_history_max_size">Maximální počet položek k uchování</string>
|
||||
<string name="prefs__media__emoji_suggestion__title" comment="Preference group title">Návrhy emotikonů</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled" comment="Preference title">Povolit návrhy emotikonů</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">Poskytovat návrhy emotikonů, zatímco píšete</string>
|
||||
<string name="prefs__media__emoji_suggestion_type" comment="Preference title">Typ spouštěče</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history" comment="Preference title">Aktualizovat historii emotikonů</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history__summary" comment="Preference summary">Přijetí navržených emotikonů je přidá do historie emotikonů</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name" comment="Preference title">Zobrazit název emotikonu</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name__summary" comment="Preference summary">Návrhy emotikonů zobrazují název vedle emotikonu</string>
|
||||
<string name="prefs__media__emoji_suggestion_query_min_length" comment="Preference title">Minimální délka textu</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">Maximální počet kandidátů</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Smajlíky a emotikony</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Lidé a tělo</string>
|
||||
@@ -26,10 +41,12 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Předměty</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Symboly</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Vlajky</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">Nenalezeny žádné nedávno použité emotikony. Jakmile je začnete psát, objeví se automaticky zde.</string>
|
||||
<string name="emoji__recently_used__phone_locked_message" comment="Message to show if phone is locked">Pro přístup k historii smajlíků nejprve prosím odemkněte své zařízení.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">Tip: Stiskněte a držte prst na naposledy použitých emotikonech pro jejich odebrání z daného seznamu!</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">Emotikon {emoji} bylo odebráno z nedávno použitých</string>
|
||||
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">Nenalezeny žádné nedávno použité emotikony. Jakmile je začnete psát, objeví se automaticky zde.</string>
|
||||
<string name="emoji__history__phone_locked_message" comment="Message to show if phone is locked">Pro přístup k historii emotikonů nejprve prosím odemkněte své zařízení.</string>
|
||||
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">Tip: dlouze stiskněte emotikony v historii emotikonů pro jejich připnutí nebo odstranění!</string>
|
||||
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">Emotikon {emoji} odstraněn z historie</string>
|
||||
<string name="emoji__history__pinned">Připnuté</string>
|
||||
<string name="emoji__history__recent">Nedávné</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">Šipka nahoru</string>
|
||||
<string name="quick_action__arrow_up__tooltip">Vykonat šipku nahoru</string>
|
||||
@@ -124,21 +141,20 @@
|
||||
<string name="settings__localization__subtype_error_fields_no_value" comment="Error message shown in subtype editor if at least one field is set to '- select -' (means no value specified)">Minimálně jedno pole nemá vybranou hodnotu. Vyberte ji prosím.</string>
|
||||
<string name="settings__localization__subtype_error_layout_not_installed" comment="Error message shown in subtype list when a layout is not installed, where %s will be replaced by the layout ID">{layout_id} (nenainstalováno)</string>
|
||||
<string name="settings__localization__group_layouts__label" comment="Label of layouts group">Rozložení</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_title" comment="Title of the subtype delete confirmation dialog">Potvrzení odstranění</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_warning" comment="Warning message in the confirmation dialog to confirm the user's intent to delete">Opravdu chcete odstranit tento podtyp?</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme screen">Motiv</string>
|
||||
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">Režim motivu</string>
|
||||
<string name="pref__theme__sunrise_time__label" comment="Label of the sunrise time preference">Čas východu slunce</string>
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Čas západu slunce</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Denní motiv</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Noční motiv</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Vybraný motiv</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Přizpůsobit barvy aplikaci</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Barvy motivu se přizpůsobí barvám používané aplikace, pokud to daná aplikace podporuje.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Spravovat nainstalované motivy</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">Podklady aplikace FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Interní úložiště</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Externí poskytovatel</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">Vyberte denní motiv</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">Vyberte noční motiv</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Spravovat nainstalované motivy</string>
|
||||
<string name="settings__theme_editor__fine_tune__title">Jemné doladění editoru</string>
|
||||
<string name="settings__theme_editor__fine_tune__level">Úprava úrovně</string>
|
||||
<string name="settings__theme_editor__fine_tune__display_colors_as">Zobrazit barvy jako</string>
|
||||
@@ -311,6 +327,7 @@
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Délka dlouhého stisku</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Mezerník přepne na znaky</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Automaticky se vrátí z nabídky čísel/symbolů ke klasické klávesnici</string>
|
||||
<string name="pref__keyboard__incognito_indicator__label" comment="Preference title">Indikátor anonymního režimu</string>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="settings__smartbar__title" comment="Title of Smartbar screen">Chytrá lišta</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Povolit chytrou lištu</string>
|
||||
@@ -413,6 +430,7 @@
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">Světlý</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Tmavý</string>
|
||||
<string name="pref__advanced__settings_theme__amoled_dark" comment="Possible value of Settings theme preference in Advanced">AMOLED Temná</string>
|
||||
<string name="pref__advanced__settings_material_you__label" comment="Label of Material You preference in Advanced">Použít Material You</string>
|
||||
<string name="pref__advanced__settings_language__label" comment="Label of Settings language preference in Advanced">Jazyk nastavení</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Zobrazit ikonu aplikace na domovské obrazovce</string>
|
||||
<string name="pref__advanced__show_app_icon__summary_atleast_q" comment="Summary of Show app icon preference in Advanced for Android 10+">Vždy povoleno na Android 10+ kvůli omezením systému</string>
|
||||
@@ -472,6 +490,10 @@
|
||||
<string name="backup_and_restore__back_up__files_ime_keyboard">Rozšíření klávesnice</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_spelling">Rozšíření kontroly pravopisu / slovníky</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_theme">Rozšíření témat</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history">Historie schránky</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_text_items">Textové položky</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_image_items">Obrázky</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_video_items">Videa</string>
|
||||
<string name="backup_and_restore__back_up__success">Archiv zálohy byl úspěšně exportován!</string>
|
||||
<string name="backup_and_restore__back_up__failure">Nepodařilo se exportovat archiv zálohy: {error_message}</string>
|
||||
<string name="backup_and_restore__restore__title">Obnovit data</string>
|
||||
@@ -560,6 +582,8 @@
|
||||
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Zobrazí aktuální stav vstupu pro ladění</string>
|
||||
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Zobrazit překrytí s pravopisem</string>
|
||||
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Zobrazí aktuální výsledky pravopisu pro ladění</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__label">Zobrazit překrytí automatického vyplňování na řádku</string>
|
||||
<string name="devtools__show_inline_autofill_overlay__summary">Zobrazí aktuální výsledky automatického vyplňování na řádku pro ladění</string>
|
||||
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Zobrazit hranice dotyku kláves</string>
|
||||
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Zobrazit červené ohraničení hranic dotyku kláves</string>
|
||||
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Zobrazit pomocníky drag&drop</string>
|
||||
@@ -575,7 +599,16 @@
|
||||
<string name="devtools__android_settings_secure__title" comment="Title of Android settings (secure) screen">Bezpečná nastavení Androidu</string>
|
||||
<string name="devtools__android_settings_system__title" comment="Title of Android settings (system) screen">Systémová nastavení Androidu</string>
|
||||
<string name="devtools__android_locales__title" comment="Title of Android locales screen">Systémové jazyky</string>
|
||||
<string name="devtools__debuglog__title">Protokol ladění</string>
|
||||
<string name="devtools__debuglog__copied_to_clipboard">Protokol ladění zkopírován do schránky</string>
|
||||
<string name="devtools__debuglog__copy_log">Kopírovat protokol</string>
|
||||
<string name="devtools__debuglog__copy_for_github">Kopírovat protokol (formátování GitHub)</string>
|
||||
<string name="devtools__debuglog__loading">Načítání…</string>
|
||||
<!-- Extension strings -->
|
||||
<string name="ext__home__title">Doplňky a rozšíření</string>
|
||||
<string name="ext__list__ext_theme">Rozšíření témat</string>
|
||||
<string name="ext__list__ext_keyboard">Rozšíření klávesnice</string>
|
||||
<string name="ext__list__ext_languagepack">Rozšíření jazykových balíčků</string>
|
||||
<string name="ext__meta__authors">Autoři</string>
|
||||
<string name="ext__meta__components">Zabalené komponenty</string>
|
||||
<string name="ext__meta__components_theme">Zabalená témata</string>
|
||||
@@ -628,6 +661,40 @@
|
||||
<string name="ext__import__file_skip_ext_not_supported" comment="Reason string when file is loaded in incorrect context">Byl očekáván soubor médií (obrázek, zvuk, písmo atd.), ale byl nalezen archiv rozšíření.</string>
|
||||
<string name="ext__import__file_skip_media_not_supported" comment="Reason string when file is loaded in incorrect context">Byl očekáván archiv rozšíření, ale byl nalezen soubor médií (obrázek, zvuk, písmo atd.).</string>
|
||||
<string name="ext__import__error_unexpected_exception" comment="Label when an error occurred during import. The error message will be appended below this text view">Při importování se vyskytla chyba. Byly poskytnuty následující podrobnosti:</string>
|
||||
<string name="ext__validation__enter_package_name">Zadejtze prosím název balíčku</string>
|
||||
<string name="ext__validation__error_package_name">Název balíčku neodpovídá regexu {id_regex}</string>
|
||||
<string name="ext__validation__enter_version">Zadejte prosím verzi</string>
|
||||
<string name="ext__validation__enter_title">Zadejte prosím název</string>
|
||||
<string name="ext__validation__enter_maintainer">Zadejte prosím alespoň jednoho platného správce</string>
|
||||
<string name="ext__validation__enter_license">Zadejte prosím identifikátor licence</string>
|
||||
<string name="ext__validation__enter_component_id">Zadejte prosím ID komponenty</string>
|
||||
<string name="ext__validation__error_component_id">Zadejte prosím ID komponenty odpovídající {component_id_regex}</string>
|
||||
<string name="ext__validation__enter_component_label">Zadejte prosím štítek komponenty</string>
|
||||
<string name="ext__validation__hint_component_label_to_long">Váš štítek komponenty je poměrně dlouhý, což může vést k problémům v rozhraní</string>
|
||||
<string name="ext__validation__error_author">Zadejte prosím alespoň jednoho platného autora</string>
|
||||
<string name="ext__validation__error_stylesheet_path_blank">Cesta tabulky stylů nemůže být prázdná</string>
|
||||
<string name="ext__validation__error_stylesheet_path">Zadejte prosím platnou cestu tabulky stylů odpovídající {stylesheet_path_regex}</string>
|
||||
<string name="ext__validation__enter_property">Zadejte prosím název proměnné</string>
|
||||
<string name="ext__validation__error_property">Zadejte prosím platný název proměnné odpovídající {variable_name_regex}</string>
|
||||
<string name="ext__validation__hint_property" tools:ignore="TypographyDashes">Názvy proměnných ve FlorisCSS obvykle začínají dvěma spojovníky (--)</string>
|
||||
<string name="ext__validation__enter_color">Zadejte prosím řetězec barvy</string>
|
||||
<string name="ext__validation__error_color">Zadejte prosím platný řetězec barvy</string>
|
||||
<string name="ext__validation__enter_dp_size">Zadejte prosím velikost dp</string>
|
||||
<string name="ext__validation__enter_valid_number">Zadejte prosím platné číslo</string>
|
||||
<string name="ext__validation__enter_positive_number">Zadejte prosím kladné číslo (>=0)</string>
|
||||
<string name="ext__validation__enter_percent_size">Zadejte prosím velikost v procentech</string>
|
||||
<string name="ext__validation__enter_number_between_0_100">Zadejte prosím kladné číslo mezi 0 a 100</string>
|
||||
<string name="ext__validation__hint_value_above_50_percent">Jakákoli hodnota nad 50 % se bude chovat, jako kdybyste nastavili 50 %, zvažte snížení velikosti v procentech</string>
|
||||
<string name="ext__update_box__internet_permission_hint">Jelikož tato aplikace nemá přístup k internetu, musíte aktualizace nainstalovaných rozšíření kontrolovat ručně.</string>
|
||||
<string name="ext__update_box__search_for_updates">Vyhledat aktualizace</string>
|
||||
<string name="ext__addon_management_box__managing_placeholder">Správa {extensions}</string>
|
||||
<string name="ext__addon_management_box__addon_manager_info">Všechny úlohy spojené s importováním, exportováním, vytvářením, přizpůsobováním a odstraňováním rozšíření mohou být prováděny skrze centralizovaného správce doplňků.</string>
|
||||
<string name="ext__addon_management_box__go_to_page">Přejít na {ext_home_title}</string>
|
||||
<string name="ext__home__info">Rozšíření můžete stahovat a instalovat z Obchodu s doplňky FlorisBoard nebo importovat jakýkoli soubor rozšíření, který jste si stáhli z internetu.</string>
|
||||
<string name="ext__home__visit_store">Navštívit Obchod s doplňky</string>
|
||||
<string name="ext__home__manage_extensions">Správa nainstalovaných doplňků</string>
|
||||
<string name="ext__list__view_details">Zobrazit podrobnosti</string>
|
||||
<string name="ext__check_updates__title">Zkontrolovat aktualizace</string>
|
||||
<!-- Action strings -->
|
||||
<string name="action__add">Přidat</string>
|
||||
<string name="action__apply">Použít</string>
|
||||
@@ -695,6 +762,14 @@
|
||||
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Názvy jazyků napříč aplikací a rozhraním klávesnice jsou zobrazeny v jazyce, který je nastaven pro celé zařízení</string>
|
||||
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">Nativní jazyk</string>
|
||||
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Názvy jazyků napříč aplikací a rozhraním klávesnice jsou zobrazeny ve svém vlastním jazyce</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend" comment="Enum value label">Automatické řazení (na začátku)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">Automaticky seřazovat emotikony podle jejich používání. Nové emotikony budou přidávány na začátek.</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append" comment="Enum value label">Automatické řazení (na konci)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append__description" comment="Enum value description">Automaticky seřazovat emotikony podle jejich používání. Nové emotikony budou přidávány na konec.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend" comment="Enum value label">Ruční řazení (na začátku)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend__description" comment="Enum value description">Neseřazovat automaticky emotikony podle jejich používání. Nové emotikony budou přidávány na začátek.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append" comment="Enum value label">Ruční řazení (na konci)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append__description" comment="Enum value description">Neseřazovat automaticky emotikony podle jejich používání. Nové emotikony budou přidávány na konec.</string>
|
||||
<string name="enum__emoji_skin_tone__default" comment="Enum value label">{emoji} Výchozí barva pleti</string>
|
||||
<string name="enum__emoji_skin_tone__light_skin_tone" comment="Enum value label">{emoji} Světlá barva pleti</string>
|
||||
<string name="enum__emoji_skin_tone__medium_light_skin_tone" comment="Enum value label">{emoji} Středně světlá barva pleti</string>
|
||||
@@ -706,9 +781,13 @@
|
||||
<string name="enum__emoji_hair_style__curly_hair" comment="Enum value label">{emoji} Kurdnaté vlasy</string>
|
||||
<string name="enum__emoji_hair_style__white_hair" comment="Enum value label">{emoji} Bílé vlasy</string>
|
||||
<string name="enum__emoji_hair_style__bald" comment="Enum value label">{emoji} Pleš</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon">Počáteční dvojtečka</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon__description" comment="Keep the :emoji_name while translating, this is a syntax guide">Navrhovat emotikony pomocí syntaxe :emoji_name</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text">Text v řádku</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text__description">Navrhovat emotikony jednoduše zadáním názvu emotikonu jako slova</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates" comment="Enum value label">Nad navrženými slovy</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates__description" comment="Enum value description">Umístí řádek rozšířených akcí mezi UI aplikace a řádek navrhnutých slov</string>
|
||||
<string name="enum__extended_actions_placement__below_candidates" comment="Enum value label">Pod navrhnutými slovy</string>
|
||||
<string name="enum__extended_actions_placement__below_candidates" comment="Enum value label">Pod navrženými slovy</string>
|
||||
<string name="enum__extended_actions_placement__below_candidates__description" comment="Enum value description">Umístí řádek rozšířených akcí mezi řádek s navrhnutými slovy a textovou klávesnici</string>
|
||||
<string name="enum__extended_actions_placement__overlay_app_ui" comment="Enum value label">Překrýt rozhraní aplikace</string>
|
||||
<string name="enum__extended_actions_placement__overlay_app_ui__description" comment="Enum value description">Umístí řádek rozšířených akcí jako překrytí nad UI aplikace, bez ovlivnění výšky rozhraní klávesnice. Vezměte prosím na vědomí, že toto umístění může způsobit částečné překreslení vstupního pole aplikace</string>
|
||||
@@ -722,6 +801,8 @@
|
||||
<string name="enum__key_hint_mode__hint_priority__description" comment="Enum value description">Počáteční znak vybraný po dlouhém stisku je vždy znak nápovědy, nebo primární přízvuk, když není dostupný symbol nápovědy</string>
|
||||
<string name="enum__key_hint_mode__smart_priority" comment="Enum value label">Chytrá prioritizace</string>
|
||||
<string name="enum__key_hint_mode__smart_priority__description" comment="Enum value description">Počáteční znak vybraný po dlouhém stisku je dynamicky vybrán, zda bude primárním přízvukem, nebo o symbolem nápovědy, a to na základě aktuálního jazyka a rozložení</string>
|
||||
<string name="enum__incognito_display_mode__replace_shared_actions_toggle" comment="Enum value label">Nahradit ikonu přepínače sdílených akcí indikátorem anonymního režimu</string>
|
||||
<string name="enum__incognito_display_mode__display_behind_keyboard" comment="Enum value label">Zobrazit indikátor anonymního režimu v pozadí klávesnice</string>
|
||||
<string name="enum__incognito_mode__force_off" comment="Enum value label">Vynutit vypnutí</string>
|
||||
<string name="enum__incognito_mode__force_off__description" comment="Enum value description">Anonymní režim bude vždy zakázán, nehledě na požadavky cílové aplikace. S touto možností nebude v chytré liště zobrazena rychlá akce anonymního režimu.</string>
|
||||
<string name="enum__incognito_mode__force_on" comment="Enum value label">Vynutit zapnutí</string>
|
||||
@@ -827,4 +908,16 @@
|
||||
<item quantity="many">{v} položek</item>
|
||||
<item quantity="other">{v} položek</item>
|
||||
</plurals>
|
||||
<plurals name="unit__characters__written">
|
||||
<item quantity="one">{v} znak</item>
|
||||
<item quantity="few">{v} znaky</item>
|
||||
<item quantity="many">{v} znaků</item>
|
||||
<item quantity="other">{v} znaků</item>
|
||||
</plurals>
|
||||
<plurals name="unit__candidates__written">
|
||||
<item quantity="one">{v} návrh</item>
|
||||
<item quantity="few">{v} návrhy</item>
|
||||
<item quantity="many">{v} návrhů</item>
|
||||
<item quantity="other">{v} návrhů</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Flyt tastatur til venstre.</string>
|
||||
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Flyt tastatur til højre.</string>
|
||||
<!-- Media strings -->
|
||||
<string name="settings__media__title">Emojis</string>
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Emojis</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Humørikoner</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Tekst-emoji</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">Foretrukken emoji-hudfarve</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">Foretrukken emoji-frisure</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Smileys & Følelser</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Personer & Krop</string>
|
||||
@@ -23,23 +26,55 @@
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Symboler</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Flag</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">Pil op</string>
|
||||
<string name="quick_action__arrow_up__tooltip">Udfør pil op</string>
|
||||
<string name="quick_action__arrow_down" maxLength="12">Pil ned</string>
|
||||
<string name="quick_action__arrow_down__tooltip">Udfør pil ned</string>
|
||||
<string name="quick_action__arrow_left" maxLength="12">Pil venstre</string>
|
||||
<string name="quick_action__arrow_left__tooltip">Udfør pil venstre</string>
|
||||
<string name="quick_action__arrow_right" maxLength="12">Pil højre</string>
|
||||
<string name="quick_action__arrow_right__tooltip">Udfør pil højre</string>
|
||||
<string name="quick_action__clipboard_clear_primary_clip__tooltip">Udfør ryd primær udklipsholder</string>
|
||||
<string name="quick_action__clipboard_copy" maxLength="12">Kopiér</string>
|
||||
<string name="quick_action__clipboard_cut" maxLength="12">Klip</string>
|
||||
<string name="quick_action__clipboard_paste" maxLength="12">Indsæt</string>
|
||||
<string name="quick_action__clipboard_select_all" maxLength="12">Vælg alle</string>
|
||||
<string name="quick_action__ime_ui_mode_media" maxLength="12">Emoji</string>
|
||||
<string name="quick_action__ime_ui_mode_media__tooltip">Åbn emoji-panel</string>
|
||||
<string name="quick_action__settings__tooltip">Åbn indstillinger</string>
|
||||
<string name="quick_action__undo" maxLength="12">Fortryd</string>
|
||||
<string name="quick_action__toggle_incognito_mode" maxLength="12">Inkognito</string>
|
||||
<string name="quick_action__toggle_incognito_mode__tooltip">Slå inkognito-tilstand til/fra</string>
|
||||
<string name="quick_action__voice_input" maxLength="12">Stemmeinput</string>
|
||||
<string name="quick_action__voice_input__tooltip" comment="IME stands for Input Method Editor and is indirectly equivalent to 'keyboard'.">Åbn stemmeinput-udbyder</string>
|
||||
<string name="quick_action__one_handed_mode__tooltip">Skift enhåndstilstand</string>
|
||||
<string name="quick_action__drag_marker__tooltip" comment="This action is only used as a placeholder in the actions editor drag and drop screen and only visible in debug mode">Nuværende træk-markør position</string>
|
||||
<string name="quick_action__noop" maxLength="12" comment="Noop=no operation; this action is only used as a placeholder in the actions editor drag and drop screen">Ingen</string>
|
||||
<!-- Incognito mode strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title" comment="Title of Settings">Indstillinger</string>
|
||||
<string name="settings__preview_keyboard" comment="Hint for try your setup box">Prøv din opsætning</string>
|
||||
<string name="settings__help" comment="General label for help buttons in Settings">Hjælp</string>
|
||||
<string name="settings__default" comment="General string which is used when a preference has the default value set">Standard</string>
|
||||
<string name="settings__system_default" comment="General string which is used when a preference has the system default value set">Systemstandard</string>
|
||||
<string name="settings__home__title" comment="Title of the Home screen">Velkommen til {app_name}</string>
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">FlorisBoard er ikke slået til i systemet og er derfor ikke tilgængelig som inputsmetode i inputvælgeren. Klik her for at løse problemet.</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 er ikke valgt som standard inputmetode. Klik her for at løse problemet.</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and Layout screen">Sprog & Layouts</string>
|
||||
<string name="settings__localization__display_language_names_in__label" comment="Label of Display language names in preference">Vis sprognavne i</string>
|
||||
<string name="settings__localization__group_subtypes__label" comment="Label of subtypes group">Undertyper</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Tilføj undertastatur</string>
|
||||
<string name="settings__localization__language_pack_title" comment="Title of the language pack manager screen for managing installed and custom language packs">Administrér installerede sprogpakker</string>
|
||||
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Ændre undertastatur</string>
|
||||
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Primært sprog</string>
|
||||
<string name="settings__localization__subtype_suggestion_provider" comment="Label for suggestion provider dropdown in subtype dialog">Inkognito-tilstand er nu aktiveret. Vil ikke lærer ord fra dine input mens denne tilstand er aktiv</string>
|
||||
<string name="settings__localization__subtype_select_locale" comment="Subtype select language title">Vælg sprog</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined">Det ser ud til, at der ikke er konfigureret nogle undertastaturer. I dette tilfælde faldes der tilbage på Engelsk/QWERTY!</string>
|
||||
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">Dette undertastatur findes allerede!</string>
|
||||
<string name="settings__localization__subtype_error_layout_not_installed" comment="Error message shown in subtype list when a layout is not installed, where %s will be replaced by the layout ID">{layout_id} (ikke installeret)</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme screen">Tema</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Valgte tema</string>
|
||||
<string name="settings__theme_editor__rule_selectors"></string>
|
||||
<string name="snygg__rule_element__keyboard">Tastatur vindue</string>
|
||||
<string name="pref__input_feedback__audio_enabled__label" comment="Preference title">Aktivér lydfeedback</string>
|
||||
<string name="pref__input_feedback__haptic_vibration_duration__label" comment="Preference title">Vibrationsvarighed</string>
|
||||
<string name="pref__input_feedback__haptic_vibration_strength__label" comment="Preference title">Vibrationsstyrke</string>
|
||||
|
||||
@@ -13,9 +13,24 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Emojis</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Emoticons</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">Maximale Größe des Emoji-Verlaufs</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">Bevorzugte Emoji-Hautfarbe</string>
|
||||
<string name="prefs__media__emoji_preferred_hair_style">Bevorzugte Emoji-Haarfarbe</string>
|
||||
<string name="prefs__media__emoji_history__title" comment="Preference group title">Emoji-Historie</string>
|
||||
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Aktiviere Emoji-Historie</string>
|
||||
<string name="prefs__media__emoji_history_enabled__summary" comment="Preference summary">Behalte kürzlich genutzte Emojis für einen schnellen Zugriff</string>
|
||||
<string name="prefs__media__emoji_history_pinned_update_strategy" comment="Preference title">Update Strategie (gepinnt)</string>
|
||||
<string name="prefs__media__emoji_history_recent_update_strategy" comment="Preference title">Update Strategie (kürzlich)</string>
|
||||
<string name="prefs__media__emoji_history_max_size">Maximale zu behaltende Emojis</string>
|
||||
<string name="prefs__media__emoji_suggestion__title" comment="Preference group title">Emoji Vorschläge</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled" comment="Preference title">Aktiviere Emoji Vorschläge</string>
|
||||
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">Erhalte Emoji Vorschläge während des Tippens</string>
|
||||
<string name="prefs__media__emoji_suggestion_type" comment="Preference title">Auslösetyp</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history" comment="Preference title">Update Emoji-Historie</string>
|
||||
<string name="prefs__media__emoji_suggestion_update_history__summary" comment="Preference summary">Angenommene vorgeschlagene Emojis werden zur Emoji-Historie hinzugefügt</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name" comment="Preference title">Zeige Emoji Name</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_show_name__summary" comment="Preference summary">Emoji Vorschläge zeigen den Namen neben dem Emoji</string>
|
||||
<string name="prefs__media__emoji_suggestion_query_min_length" comment="Preference title">Minimale Suchbegriff-Länge</string>
|
||||
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">Maximale Anzahl von Vorschlägen</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Smileys & Emotionen</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Personen & Körper</string>
|
||||
@@ -26,10 +41,12 @@
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Objekte</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Symbole</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Flaggen</string>
|
||||
<string name="emoji__recently_used__empty_message" comment="Message if no recently used emojis exist">Keine kürzlich verwendeten Emojis gefunden. Sobald Sie Emojis verwendet haben erscheinen diese hier.</string>
|
||||
<string name="emoji__recently_used__phone_locked_message" comment="Message to show if phone is locked">Um auf deinen Emoji-verlauf zuzugreifen, entsperre zuerst dein Gerät.</string>
|
||||
<string name="emoji__recently_used__removal_tip" comment="Feature discoverability for removal of recently used emojis">Pro Tipp: Halten Sie kürzlich verwendete Emojis gedrückt um diese wieder aus der Ansicht zu entfernen!</string>
|
||||
<string name="emoji__recently_used__removal_success_message" comment="Toast message if user has long pressed emoji in recently used collection to remove it">{emoji} wurde aus \"kürzlich verwendeten Emojis\" entfernt</string>
|
||||
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">Keine kürzlich benutzen Emojis gefunden. Sobald Emojis benutzt werden, erscheinen diese hier.</string>
|
||||
<string name="emoji__history__phone_locked_message" comment="Message to show if phone is locked">Um auf deinen Emoji-Historie zuzugreifen, entsperre das Gerät.</string>
|
||||
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">Tipp: Tippe lange auf Emojis in der Historie um sie anzupinnen oder zu entfernen!</string>
|
||||
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">{emoji} aus Historie gelöscht</string>
|
||||
<string name="emoji__history__pinned">Angepinnt</string>
|
||||
<string name="emoji__history__recent">Kürzlich</string>
|
||||
<!-- Quick action strings -->
|
||||
<string name="quick_action__arrow_up" maxLength="12">Pfeil hoch</string>
|
||||
<string name="quick_action__arrow_up__tooltip">Pfeil nach oben ausführen</string>
|
||||
@@ -46,13 +63,13 @@
|
||||
<string name="quick_action__clipboard_cut" maxLength="12">Ausschneiden</string>
|
||||
<string name="quick_action__clipboard_cut__tooltip">Zur Zwischenablage ausschneiden</string>
|
||||
<string name="quick_action__clipboard_paste" maxLength="12">Einfügen</string>
|
||||
<string name="quick_action__clipboard_paste__tooltip">Einfügen von der Zwischenablage</string>
|
||||
<string name="quick_action__clipboard_paste__tooltip">Einfügen aus der Zwischenablage</string>
|
||||
<string name="quick_action__clipboard_select_all" maxLength="12">Alles ausw.</string>
|
||||
<string name="quick_action__clipboard_select_all__tooltip">Alles auswählen in der Zwischenablage</string>
|
||||
<string name="quick_action__ime_ui_mode_clipboard" maxLength="12">Zwischenab.</string>
|
||||
<string name="quick_action__ime_ui_mode_clipboard__tooltip">Zwischenablage Verlauf öffnen</string>
|
||||
<string name="quick_action__ime_ui_mode_media" maxLength="12">Emoji</string>
|
||||
<string name="quick_action__ime_ui_mode_media__tooltip">Öffne Emoji-Palette</string>
|
||||
<string name="quick_action__ime_ui_mode_media__tooltip">Öffne Emoji-Panel</string>
|
||||
<string name="quick_action__settings" maxLength="12">Einstellung</string>
|
||||
<string name="quick_action__settings__tooltip">Einstellungen öffnen</string>
|
||||
<string name="quick_action__undo" maxLength="12">Rückgängig</string>
|
||||
@@ -71,7 +88,7 @@
|
||||
<string name="quick_action__one_handed_mode__tooltip">Einhandmodus umschalten</string>
|
||||
<string name="quick_action__drag_marker" maxLength="12" comment="This action is only used as a placeholder in the actions editor drag and drop screen and only visible in debug mode">Zug-Marker</string>
|
||||
<string name="quick_action__drag_marker__tooltip" comment="This action is only used as a placeholder in the actions editor drag and drop screen and only visible in debug mode">Aktuelle Marker-Position</string>
|
||||
<string name="quick_action__noop" maxLength="12" comment="Noop=no operation; this action is only used as a placeholder in the actions editor drag and drop screen">Kein</string>
|
||||
<string name="quick_action__noop" maxLength="12" comment="Noop=no operation; this action is only used as a placeholder in the actions editor drag and drop screen">Keine</string>
|
||||
<string name="quick_action__noop__tooltip" comment="Noop=no operation; this action is only used as a placeholder in the actions editor drag and drop screen">Keine Operation</string>
|
||||
<string name="quick_actions_overflow__customize_actions_button">Aktionen neu ordnen</string>
|
||||
<string name="quick_actions_editor__header">Passe Reihenfolge der Aktionen an</string>
|
||||
@@ -104,8 +121,8 @@
|
||||
<string name="settings__localization__subtype_symbols_layout" comment="Label for layout dropdown in subtype dialog">Primäre Symbolanordnung</string>
|
||||
<string name="settings__localization__subtype_symbols2_layout" comment="Label for layout dropdown in subtype dialog">Sekundäre Symbolanordnung</string>
|
||||
<string name="settings__localization__subtype_composer" comment="Label for composer dropdown in subtype dialog.">Verfasser</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).">Währung setzen</string>
|
||||
<string name="settings__localization__subtype_numeric_layout" comment="Label for layout dropdown in subtype dialog">Numerische Anordnung</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).">Währungssatz</string>
|
||||
<string name="settings__localization__subtype_numeric_layout" comment="Label for layout dropdown in subtype dialog">Zahlenlayout</string>
|
||||
<string name="settings__localization__subtype_numeric_advanced_layout" comment="Label for layout dropdown in subtype dialog">(Erweiterte) Numerische Anordnung</string>
|
||||
<string name="settings__localization__subtype_numeric_row_layout" comment="Label for layout dropdown in subtype dialog">Anordnung Zahlenreihe</string>
|
||||
<string name="settings__localization__subtype_phone_layout" comment="Label for layout dropdown in subtype dialog">Primäres Wählfeld</string>
|
||||
@@ -124,21 +141,20 @@
|
||||
<string name="settings__localization__subtype_error_fields_no_value" comment="Error message shown in subtype editor if at least one field is set to '- select -' (means no value specified)">In mindestens einem Feld ist kein Wert ausgewählt. Bitte wähle einen Wert für das Feld / die Felder.</string>
|
||||
<string name="settings__localization__subtype_error_layout_not_installed" comment="Error message shown in subtype list when a layout is not installed, where %s will be replaced by the layout ID">{layout_id} (nicht installiert)</string>
|
||||
<string name="settings__localization__group_layouts__label" comment="Label of layouts group">Layouts</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_title" comment="Title of the subtype delete confirmation dialog">Löschen bestätigen</string>
|
||||
<string name="settings__localization__subtype_delete_confirmation_warning" comment="Warning message in the confirmation dialog to confirm the user's intent to delete">Bist du sicher, dass du diesen Untertyp löschen willst?</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme screen">Design</string>
|
||||
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">Design-Modus</string>
|
||||
<string name="pref__theme__sunrise_time__label" comment="Label of the sunrise time preference">Sonnenaufgangszeit</string>
|
||||
<string name="pref__theme__sunrise_time__label" comment="Label of the sunrise time preference">Sonnenuntergangszeit</string>
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Sonnenuntergangszeit</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Helles Design</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Dunkles Design</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Ausgewähltes Design</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Farbdesign an eine App anpassen</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Farbdesign passt sich an die verwendeten App an. Nur möglich wenn die verwendete App diese Funktionalität unterstützt.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Installierte Designs verwalten</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">FlorisBoard App Ressourcen</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Interner Speicher</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Externer Anbieter</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">Tages-Design auswählen</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">Nacht-Design auswählen</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Installierte Designs verwalten</string>
|
||||
<string name="settings__theme_editor__fine_tune__title">Editoreinstellungen</string>
|
||||
<string name="settings__theme_editor__fine_tune__level">Bearbeitungsmodus</string>
|
||||
<string name="settings__theme_editor__fine_tune__display_colors_as">Zeige Farben als</string>
|
||||
@@ -163,7 +179,7 @@
|
||||
<string name="settings__theme_editor__code_recording_help_text">Um den Code einer Taste zu ermitteln, verwenden Sie die Schaltfläche neben dem Code-Eingabefeld. Sobald sie aktiviert ist, zeichnet sie den nächsten Tastendruck auf und fügt den Code in das Eingabefeld ein.</string>
|
||||
<string name="settings__theme_editor__code_recording_started">Key code aufnahme gestartet</string>
|
||||
<string name="settings__theme_editor__code_recording_stopped">Key code aufnahme beendet</string>
|
||||
<string name="settings__theme_editor__code_recording_requires_default_ime_floris">{app_name} muss die standard Tastatur-App sein, um eine Taste aufzunehmen</string>
|
||||
<string name="settings__theme_editor__code_recording_requires_default_ime_floris">{app_name} muss die Standard-Tastatur-App sein, um eine Taste aufzunehmen</string>
|
||||
<string name="settings__theme_editor__code_recording_placeholder">Aufnahme läuft …</string>
|
||||
<string name="settings__theme_editor__add_property">Eigenschaft hinzufügen</string>
|
||||
<string name="settings__theme_editor__edit_property">Eigenschaft bearbeiten</string>
|
||||
@@ -311,6 +327,7 @@
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Verzögerung bei langem Tastendruck</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__label" comment="Preference title">Leertaste wechselt zu Buchstaben zurück</string>
|
||||
<string name="pref__keyboard__space_bar_switches_to_characters__summary" comment="Preference summary">Leertaste wechselt aus der Symbol- oder Zahlenansicht zurück zur Buchstabenansicht</string>
|
||||
<string name="pref__keyboard__incognito_indicator__label" comment="Preference title">Inkognito-Indikator</string>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="settings__smartbar__title" comment="Title of Smartbar screen">Schnellzugriffsleiste</string>
|
||||
<string name="pref__smartbar__enabled__label" comment="Preference title">Schnellzugriffsleiste einschalten</string>
|
||||
@@ -380,7 +397,7 @@
|
||||
<string name="settings__udm__dialog__shortcut_label" comment="Label for the shortcut in the user dictionary add/edit dialog">Abkürzung (optional)</string>
|
||||
<string name="settings__udm__dialog__shortcut_error_invalid" comment="Error label for the shortcut in the user dictionary add/edit dialog">Bitte einen Shortcut eingeben, der zu {regex} passt</string>
|
||||
<string name="settings__udm__dialog__locale_label" comment="Label for the language code in the user dictionary add/edit dialog">Sprachcode (optional)</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Dieses Sprachcode entspicht nicht denn enstpächende Syntax. Der Code muss entsprechend von Land (wie en), Land und Region (wie en_US) oder Land, Region und Script (wie en_US-script).</string>
|
||||
<string name="settings__udm__dialog__locale_error_invalid" comment="Error label for the language code in the user dictionary add/edit dialog">Dieser Sprachcode entspricht nicht der Syntax. Der Code muss entweder ein Land (wie en), Land und Region (wie en_US) oder Land, Region und Script (wie en_US-script) sein.</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures screen">Gesten & Glide Typing</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Glide Typing</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">Glide Typing aktivieren</string>
|
||||
@@ -413,6 +430,7 @@
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">Hell</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Dunkel</string>
|
||||
<string name="pref__advanced__settings_theme__amoled_dark" comment="Possible value of Settings theme preference in Advanced">AMOLED Dunkel</string>
|
||||
<string name="pref__advanced__settings_material_you__label" comment="Label of Material You preference in Advanced">Nutze Material You</string>
|
||||
<string name="pref__advanced__settings_language__label" comment="Label of Settings language preference in Advanced">App-Sprache</string>
|
||||
<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__show_app_icon__summary_atleast_q" comment="Summary of Show app icon preference in Advanced for Android 10+">Immer aktiviert in Android 10+ aufgrund von System-Beschränkungen</string>
|
||||
@@ -434,7 +452,7 @@
|
||||
<string name="about__privacy_policy__title" comment="Preference title">Datenschutzrichtlinie</string>
|
||||
<string name="about__privacy_policy__summary" comment="Preference summary">Datenschutzrichtlinie für dieses Projekt</string>
|
||||
<string name="about__project_license__title" comment="Preference title">Projekt-Lizenz</string>
|
||||
<string name="about__project_license__summary" comment="Preference summary">FlorisBoard ist unter {license_name} lizensiert</string>
|
||||
<string name="about__project_license__summary" comment="Preference summary">FlorisBoard ist unter {license_name} lizenziert</string>
|
||||
<string name="about__project_license__error_license_text_failed" comment="Error text for license text loading failure">Fehler: Lizenztext konnte nicht geladen werden.\n-> Grund: {error_message}</string>
|
||||
<string name="about__project_license__error_reason_asset_manager_null" comment="Error text if asset manager is null">Assetmanager Referenz ist null</string>
|
||||
<string name="about__third_party_licenses__title" comment="Preference title">Drittanbieter-Lizenzen</string>
|
||||
@@ -450,6 +468,8 @@
|
||||
<string name="setup__select_ime__title">{app_name} auswählen</string>
|
||||
<string name="setup__select_ime__description">{app_name} ist nun auf deinem System aktiviert. Um es zu benutzen, wähle bei der Standard-Eingabemethode {app_name} aus!</string>
|
||||
<string name="setup__select_ime__switch_keyboard_btn">Tastatur wechseln</string>
|
||||
<string name="setup__grant_notification_permission__title">Fehlerberichts-Benachrichtigungen zulassen</string>
|
||||
<string name="setup__grant_notification_permission__description">Ab Android 13+ müssen Apps um Erlaubnis bitten, um Benachrichtigungen zu senden. FlorisBoard verwendet diese nur, um im Falle eines App-Absturzes einen Fehlerbericht zu senden. Diese Berechtigung kann jederzeit in den Systemeinstellungen widerrufen werden. </string>
|
||||
<string name="setup__grant_notification_permission__btn">Berechtigung erteilen</string>
|
||||
<string name="setup__finish_up__title">Fertigstellen</string>
|
||||
<string name="setup__finish_up__description_p1">{app_name} ist nun im System aktiviert und bereit von dir angepasst zu werden.</string>
|
||||
@@ -467,6 +487,10 @@
|
||||
<string name="backup_and_restore__back_up__files_ime_keyboard">Tastaturerweiterungen</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_spelling">Rechtschreibeerweiterungen / Wörterbücher</string>
|
||||
<string name="backup_and_restore__back_up__files_ime_theme">Design-Erweiterungen</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history">Zwischenablageverlauf</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_text_items">Text Inhalte</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_image_items">Bilder</string>
|
||||
<string name="backup_and_restore__back_up__files_clipboard_history__clipboard_video_items">Videos</string>
|
||||
<string name="backup_and_restore__back_up__success">Sicherung wurde erfolgreich exportiert!</string>
|
||||
<string name="backup_and_restore__back_up__failure">Exportieren des Sicherungsarchivs fehlgeschlagen: {error_message}</string>
|
||||
<string name="backup_and_restore__restore__title">Daten wiederherstellen</string>
|
||||
@@ -540,6 +564,8 @@
|
||||
<string name="pref__clipboard__clear_primary_clip_deletes_last_item__summary">Durch das Löschen des aktiven Elements wird auch das neueste Verlaufselement entfernt</string>
|
||||
<string name="send_to_clipboard__unknown_error">Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut!</string>
|
||||
<string name="send_to_clipboard__type_not_supported_error">Dieser Media-Typ wird nicht unterstützt.</string>
|
||||
<string name="send_to_clipboard__android_version_to_old_error">Die Androidversion ist zu alt für diese Funktion. </string>
|
||||
<string name="send_to_clipboard__description__copied_image_to_clipboard">Nachfolgendes Bild wurde in die Zwischenablage kopiert.</string>
|
||||
<!-- Devtools strings -->
|
||||
<string name="devtools__title" comment="Title of Devtools screen. Translators: treat this string as 'Developer tools' for translation, except a similar short term is available for your language.">Entwicklerwerkzeuge</string>
|
||||
<string name="devtools__enabled__label" comment="Label of Enable developer tools in Devtools">Entwickler-Werkzeuge ein/aus</string>
|
||||
@@ -567,7 +593,16 @@
|
||||
<string name="devtools__android_settings_secure__title" comment="Title of Android settings (secure) screen">Sichere Android-Einstellungen</string>
|
||||
<string name="devtools__android_settings_system__title" comment="Title of Android settings (system) screen">Android System-Einstellungen</string>
|
||||
<string name="devtools__android_locales__title" comment="Title of Android locales screen">Systemgebietsschemata</string>
|
||||
<string name="devtools__debuglog__title">Debug Log</string>
|
||||
<string name="devtools__debuglog__copied_to_clipboard">Debug Log wurde in die Zwischenablage kopiert</string>
|
||||
<string name="devtools__debuglog__copy_log">Log kopieren</string>
|
||||
<string name="devtools__debuglog__copy_for_github">Log kopieren (GitHub Formatierung)</string>
|
||||
<string name="devtools__debuglog__loading">Lädt…</string>
|
||||
<!-- Extension strings -->
|
||||
<string name="ext__home__title">Add-ons & Erweiterungen</string>
|
||||
<string name="ext__list__ext_theme">Design-Erweiterungen</string>
|
||||
<string name="ext__list__ext_keyboard">Tastatur-Erweiterungen</string>
|
||||
<string name="ext__list__ext_languagepack">Sprachpaket-Erweiterungen</string>
|
||||
<string name="ext__meta__authors">Ersteller</string>
|
||||
<string name="ext__meta__components">Gebündelte Komponenten</string>
|
||||
<string name="ext__meta__components_theme">Enthaltene Designs</string>
|
||||
@@ -620,6 +655,40 @@
|
||||
<string name="ext__import__file_skip_ext_not_supported" comment="Reason string when file is loaded in incorrect context">Eine Mediendatei (Bild, Audio, Text, etc.) wurde erwartet, aber ein Erweiterungsarchiv wurde gefunden.</string>
|
||||
<string name="ext__import__file_skip_media_not_supported" comment="Reason string when file is loaded in incorrect context">Ein Erweiterungsarchiv wurde erwartet, aber eine Mediendatei (Bild, Audio, Text, etc.) wurde gefunden.</string>
|
||||
<string name="ext__import__error_unexpected_exception" comment="Label when an error occurred during import. The error message will be appended below this text view">Beim Import ist ein unerwarteter Fehler aufgetreten. Folgende Angaben wurden gemacht:</string>
|
||||
<string name="ext__validation__enter_package_name">Bitte gib einen Paketnamen ein</string>
|
||||
<string name="ext__validation__error_package_name">Paketname stimmt nicht mit Regex {id_regex} überein</string>
|
||||
<string name="ext__validation__enter_version">Bitte gib eine Version an</string>
|
||||
<string name="ext__validation__enter_title">Bitte gib einen Titel ein</string>
|
||||
<string name="ext__validation__enter_maintainer">Bitte gib mindestens einen gültigen Maintainer an</string>
|
||||
<string name="ext__validation__enter_license">Bitte gib eine Lizenzbezeichnung an</string>
|
||||
<string name="ext__validation__enter_component_id">Bitte gib eine Komponenten-ID ein</string>
|
||||
<string name="ext__validation__error_component_id">Bitte eine Komponenten-ID eingeben, welche zu {component_id_regex} passt</string>
|
||||
<string name="ext__validation__enter_component_label">Bitte gib eine Komponentenbezeichnung ein</string>
|
||||
<string name="ext__validation__hint_component_label_to_long">Die Komponentenbezeichnung ist recht lang, was zu einem Abschneiden auf der Benutzeroberfläche führen kann</string>
|
||||
<string name="ext__validation__error_author">Bitte gib mindestens einen gültigen Autor an</string>
|
||||
<string name="ext__validation__error_stylesheet_path_blank">Der Stylesheet-Pfad darf nicht leer sein</string>
|
||||
<string name="ext__validation__error_stylesheet_path">Bitte einen Stylesheet-Pfad eingeben, welcher zu {stylesheet_path_regex} passt</string>
|
||||
<string name="ext__validation__enter_property">Bitte gib einen Variablennamen an</string>
|
||||
<string name="ext__validation__error_property">Bitte einen gültigen Variablennamen eingeben, welcher zu {variable_name_regex} passt</string>
|
||||
<string name="ext__validation__hint_property" tools:ignore="TypographyDashes">Nach FlorisCSS-Konvention beginnt ein Variablenname mit zwei Bindestrichen (--)</string>
|
||||
<string name="ext__validation__enter_color">Bitte gib einen Farbstring ein</string>
|
||||
<string name="ext__validation__error_color">Bitte gib einen gültigen Farbstring ein</string>
|
||||
<string name="ext__validation__enter_dp_size">Bitte gib eine dp-Größe an</string>
|
||||
<string name="ext__validation__enter_valid_number">Bitte gib eine gültige Nummer ein</string>
|
||||
<string name="ext__validation__enter_positive_number">Bitte gib eine positive Zahl ein (>=0)</string>
|
||||
<string name="ext__validation__enter_percent_size">Bitte gib eine Prozentzahl an</string>
|
||||
<string name="ext__validation__enter_number_between_0_100">Bitte gib eine positive Zahl zwischen 0 und 100 an</string>
|
||||
<string name="ext__validation__hint_value_above_50_percent">Jeder Wert über 50 % verhält sich so, als ob 50 % eingestellt wären. Überlege den Wert zu senken</string>
|
||||
<string name="ext__update_box__internet_permission_hint">Weil diese App keine Internetberechtigungen hat, muss manuell nach Updates für Erweiterungen gesucht werden.</string>
|
||||
<string name="ext__update_box__search_for_updates">Nach Updates suchen</string>
|
||||
<string name="ext__addon_management_box__managing_placeholder">Verwalte {extensions}</string>
|
||||
<string name="ext__addon_management_box__addon_manager_info">Alle Aufgaben im Zusammenhang mit dem Importieren, Exportieren, Erstellen, Anpassen und Entfernen von Erweiterungen können über den Add-on-Manager abgewickelt werden.</string>
|
||||
<string name="ext__addon_management_box__go_to_page">Gehe zu {ext_home_title}</string>
|
||||
<string name="ext__home__info">Erweiterungen können über den FlorisBoard Add-on Store heruntergeladen und installiert werden oder importiere beliebige Erweiterungen aus dem Internet.</string>
|
||||
<string name="ext__home__visit_store">Besuche den Add-ons Store</string>
|
||||
<string name="ext__home__manage_extensions">Installierte Erweiterungen verwalten</string>
|
||||
<string name="ext__list__view_details">Details anzeigen</string>
|
||||
<string name="ext__check_updates__title">Auf Updates prüfen</string>
|
||||
<!-- Action strings -->
|
||||
<string name="action__add">Hinzufügen</string>
|
||||
<string name="action__apply">Übernehmen</string>
|
||||
@@ -687,6 +756,14 @@
|
||||
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Sprachnamen in der App und in der Tastatur werden in der Systemsprache des Geräts dargestellt</string>
|
||||
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">Systemsprache</string>
|
||||
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Sprachnamen in der App und in der Tastatur werden in der referenzierten Sprache dargestellt</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend" comment="Enum value label">Automatisch Sortieren (Voranstellen)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">Emojis werden bei Verwendung neu sortiert. Neue Emojis werden am Anfang hinzugefügt.</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append" comment="Enum value label">Automatisch Sortieren (Anhängen)</string>
|
||||
<string name="enum__emoji_history_update_strategy__auto_sort_append__description" comment="Enum value description">Emojis werden bei Verwendung neu sortiert. Neue Emojis werden am Ende hinzugefügt.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend" comment="Enum value label">Manuelles Sortieren (Voranstellen)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_prepend__description" comment="Enum value description">Emojis werden bei Verwendung nicht neu sortiert. Neue Emojis werden am Anfang hinzugefügt.</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append" comment="Enum value label">Manuelles Sortieren (Anhängen)</string>
|
||||
<string name="enum__emoji_history_update_strategy__manual_sort_append__description" comment="Enum value description">Emojis werden bei Verwendung nicht neu sortiert. Neue Emojis werden am Ende hinzugefügt.</string>
|
||||
<string name="enum__emoji_skin_tone__default" comment="Enum value label">{emoji} Standard Hautfarbe</string>
|
||||
<string name="enum__emoji_skin_tone__light_skin_tone" comment="Enum value label">{emoji} Helle Hautfarbe</string>
|
||||
<string name="enum__emoji_skin_tone__medium_light_skin_tone" comment="Enum value label">{emoji} Mittel helle Hautfarbe</string>
|
||||
@@ -698,6 +775,10 @@
|
||||
<string name="enum__emoji_hair_style__curly_hair" comment="Enum value label">{emoji} Lockige Haare</string>
|
||||
<string name="enum__emoji_hair_style__white_hair" comment="Enum value label">{emoji} Weiße Haare</string>
|
||||
<string name="enum__emoji_hair_style__bald" comment="Enum value label">{emoji} Glatze</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon">Führender Doppelpunkt</string>
|
||||
<string name="enum__emoji_suggestion_type__leading_colon__description" comment="Keep the :emoji_name while translating, this is a syntax guide">Emojis mit der Syntax :emoji_name vorschlagen</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text">Eingebetteter Text</string>
|
||||
<string name="enum__emoji_suggestion_type__inline_text__description">Schlage Emojis vor, indem einfach der Emoji-Namen als Wort eingeben wird</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates" comment="Enum value label">Über Kandidaten</string>
|
||||
<string name="enum__extended_actions_placement__above_candidates__description" comment="Enum value description">Platziert die erweiterte Aktionsleiste zwischen der App-Oberfläche und der Kandidatenleiste</string>
|
||||
<string name="enum__extended_actions_placement__below_candidates" comment="Enum value label">Unter Kandidaten</string>
|
||||
@@ -714,6 +795,8 @@
|
||||
<string name="enum__key_hint_mode__hint_priority__description" comment="Enum value description">Das erste Zeichen, das nach langem Drücken ausgewählt wird, ist immer das Hinweissymbol, oder der primäre Akzent, wenn kein Hinweissymbol verfügbar ist</string>
|
||||
<string name="enum__key_hint_mode__smart_priority" comment="Enum value label">Intelligente Priorisierung</string>
|
||||
<string name="enum__key_hint_mode__smart_priority__description" comment="Enum value description">Das erste Zeichen, das nach langem Drücken ausgewählt wird, wird je nach Sprache und Layout dynamisch, entweder als Hauptakzent oder als Hinweissymbol festgelegt</string>
|
||||
<string name="enum__incognito_display_mode__replace_shared_actions_toggle" comment="Enum value label">Symbol für \"Geteilte Aktionen\"-Schalter mit dem Inkognito-Indikator ersetzen</string>
|
||||
<string name="enum__incognito_display_mode__display_behind_keyboard" comment="Enum value label">Inkognito-Indikator hinter die Tastatur anzeigen</string>
|
||||
<string name="enum__incognito_mode__force_off" comment="Enum value label">Zwingend aus</string>
|
||||
<string name="enum__incognito_mode__force_off__description" comment="Enum value description">Inkognito-Modus ist immer deaktiviert, egal, was die aktuelle App sagt. Die Inkognito-Aktion in der Smartbar ist nicht verfügbar.</string>
|
||||
<string name="enum__incognito_mode__force_on" comment="Enum value label">Zwingend an</string>
|
||||
@@ -811,4 +894,12 @@
|
||||
<item quantity="one">{v} Element</item>
|
||||
<item quantity="other">{v} Elemente</item>
|
||||
</plurals>
|
||||
<plurals name="unit__characters__written">
|
||||
<item quantity="one">{v} Zeichen</item>
|
||||
<item quantity="other">{v} Zeichen</item>
|
||||
</plurals>
|
||||
<plurals name="unit__candidates__written">
|
||||
<item quantity="one">{v} Vorschlag</item>
|
||||
<item quantity="other">{v} Vorschläge</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Εικονίδια Emoji</string>
|
||||
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Εικονίδια emoticon</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
|
||||
<string name="prefs__media__emoji_recently_used_max_size">Μέγιστο μέγεθος ιστορικού εικονιδίων emoji</string>
|
||||
<string name="prefs__media__emoji_preferred_skin_tone">Προτιμώμενος τόνος επιδερμίδας εικονιδίων emoji</string>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Φατσούλες & Συναισθήματα</string>
|
||||
@@ -72,15 +71,12 @@
|
||||
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Ώρα δύσης</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Θέμα ημέρας</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Θέμα νύχτας</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Επιλεγμένο θέμα</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__label" comment="Label of the theme adapt to app preference">Προσαρμογή χρωμάτων στην εφαρμογή</string>
|
||||
<string name="pref__theme__any_theme_adapt_to_app__summary" comment="Summary of the theme adapt to app preference">Τα χρώματα του θέματος προσαρμόζονται σε αυτά της τρέχουσας εφαρμογής, εάν αυτή η εφαρμογή το υποστηρίζει.</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Διαχείριση εγκατεστημένων θεμάτων</string>
|
||||
<string name="pref__theme__source_assets" comment="Label for the theme source field">Πηγές της Εφαρμογής FlorisBoard</string>
|
||||
<string name="pref__theme__source_internal" comment="Label for the theme source field">Εσωτερικός αποθηκευτικός χώρος</string>
|
||||
<string name="pref__theme__source_external" comment="Label for the theme source field">Εξωτερικός Προμηθευτής</string>
|
||||
<string name="settings__theme_manager__title_day" comment="Title of the theme manager screen for day theme selection">Επιλογή θέματος μέρας</string>
|
||||
<string name="settings__theme_manager__title_night" comment="Title of the theme manager screen for night theme selection">Επιλογή θέματος νύχτας</string>
|
||||
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Διαχείριση εγκατεστημένων θεμάτων</string>
|
||||
<string name="snygg__rule_element__glide_trail">Διαδρομή ολίσθησης</string>
|
||||
<string name="snygg__rule_selector__disabled">Απενεργοποιημένο</string>
|
||||
<string name="snygg__property_name__var_primary">Πρωτεύον χρώμα</string>
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<string name="pref__theme__mode__label" comment="Label of the theme mode preference">Etosa reĝimo</string>
|
||||
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Taga etoso</string>
|
||||
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Nokta etoso</string>
|
||||
<string name="pref__theme__any_theme__label" comment="Label of the theme selector preference">Selektado etoso</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences screen">Klavaro</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Aranĝo</string>
|
||||
<string name="pref__keyboard__height_factor__label" comment="Preference title">Klavaro alteco</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user