Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1307f401cc | ||
|
|
ca6006767b | ||
|
|
2202db53ba | ||
|
|
321f19272e | ||
|
|
06a8a04020 | ||
|
|
2a1f7c3217 | ||
|
|
76952d55fe | ||
|
|
1f560f8b6b | ||
|
|
33bdc52354 | ||
|
|
97b795aed0 | ||
|
|
bb44362701 | ||
|
|
bab20c5baa | ||
|
|
a3000fe111 | ||
|
|
d4d2f52683 | ||
|
|
10ef340559 | ||
|
|
5b77262186 | ||
|
|
8ce56b1bf9 | ||
|
|
94667e8363 | ||
|
|
970b5eb82a | ||
|
|
a2ceed4521 | ||
|
|
6d7825e129 | ||
|
|
10c1a82995 | ||
|
|
267a39e870 | ||
|
|
f6fcbbcc34 | ||
|
|
f98b3cec4b | ||
|
|
e5a942be9f | ||
|
|
edc63aa680 | ||
|
|
23def145b2 | ||
|
|
3f7bd4f65d | ||
|
|
7b91d4f9d3 | ||
|
|
175369f7d7 | ||
|
|
79c5acc007 | ||
|
|
94d470dd96 | ||
|
|
ee9d61ad1e | ||
|
|
a3c7b538d0 | ||
|
|
ca4cd38bb2 | ||
|
|
7046c500ff | ||
|
|
0374a82f99 | ||
|
|
217acbd6f1 | ||
|
|
ef27d511be | ||
|
|
f9a4ffa5eb | ||
|
|
5533badd19 | ||
|
|
0f1b4b081d | ||
|
|
3feae09df0 | ||
|
|
34bb28d1fc | ||
|
|
551a294b05 | ||
|
|
671ff1d8b4 |
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,34 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help fix a bug
|
||||
about: Create a report to help FlorisBoard improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Short description of bug
|
||||
A short but clear and concise description of what the bug is.
|
||||
<!--
|
||||
- Describe the bug in a short but concise way.
|
||||
- If you have a screenshot or screen recording of the bug, link them at
|
||||
the end of this issue.
|
||||
- Please search existing bug reports to avoid creating duplicates.
|
||||
- Thank you for your help in making FlorisBoard better!
|
||||
-->
|
||||
|
||||
#### Steps to reproduce
|
||||
**Environment information**
|
||||
- FlorisBoard Version: <!-- e.g. 0.1.0 -->
|
||||
- Install Source: <!-- Google PlayStore/F-Droid/GitHub/? -->
|
||||
- Device: <!-- e.g. OnePlus 7T -->
|
||||
- Android version, ROM: <!-- e.g. 10, Stock -->
|
||||
|
||||
**Steps to reproduce**
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
#### Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
#### What happened instead?
|
||||
A detailed description of what you expected to happen. If you have screenshots or a screen recording, add it here.
|
||||
|
||||
#### Additional info
|
||||
- Version: [e.g. 0.1.0]
|
||||
- Source: [e.g. Google PlayStore/F-Droid/GitHub/?]
|
||||
- Device: [e.g. OnePlus 7T]
|
||||
- Android version, ROM: [e.g. 10, Stock]
|
||||
|
||||
#### Log
|
||||
<!-- (remove this line if you paste a log)
|
||||
```
|
||||
If applicable, paste the captured debug log here.
|
||||
```
|
||||
(remove this line if you paste a log) -->
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: General feedback
|
||||
url: https://github.com/florisboard/florisboard/blob/master/CONTRIBUTING.md
|
||||
about: Give general feedback about this project
|
||||
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,11 +1,19 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea or enhancement for this project
|
||||
name: Feature request / Suggestion
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: proposal
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Describe your idea in a short but concise way. If you have multiple ideas which are not directly connected to each other, file an issue per idea. This makes it easy to implement one feature proposal at a time. If you have any examples, e.g. screenshots or other keyboards which have the proposed feature implemented, link them here.
|
||||
Thank you for your help in making FlorisBoard better!
|
||||
<!--
|
||||
- Describe your idea in a short but concise way.
|
||||
- If you have multiple ideas which are not directly connected to each
|
||||
other, file an issue per idea. This makes it easy to implement one
|
||||
feature proposal at a time.
|
||||
- If you have any examples, e.g. screenshots or other keyboards which
|
||||
have the proposed feature implemented, link them here.
|
||||
- Please search existing proposals to avoid creating duplicates.
|
||||
- Thank you for your help in making FlorisBoard better!
|
||||
-->
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask here if you have a question about FlorisBoard
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
- If you need assistance in using FlorisBoard, ask it here!
|
||||
- If you want to suggest an idea for this project, please use the
|
||||
Feature request template instead.
|
||||
- Please search existing questions to avoid creating duplicates.
|
||||
- Thank you for your help in making FlorisBoard better!
|
||||
-->
|
||||
@@ -2,62 +2,42 @@
|
||||
|
||||
First off, thanks for considering contributing to FlorisBoard!
|
||||
|
||||
There are several ways to contribute to FlorisBoard. This document provides some
|
||||
general guidelines for each type of contribution.
|
||||
There are several ways to contribute to FlorisBoard. This document
|
||||
provides some general guidelines for each type of contribution.
|
||||
|
||||
## Giving general feedback
|
||||
|
||||
Either use the review function within Google Play or email me at
|
||||
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev). I
|
||||
love to hear from you!
|
||||
|
||||
## Translations
|
||||
|
||||
To make FlorisBoard accessible in as many languages as possible, the
|
||||
platform  is used
|
||||
to crowdsource and manage translations. This is the only source of
|
||||
translations from now on - **PRs that add/update translations are no
|
||||
longer accepted.** The list of languages in Crowdin covers the top 20
|
||||
languages, but feel free to email me at
|
||||
[florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to
|
||||
request a language and I'll add it.
|
||||
|
||||
## Adding a new feature or making large changes
|
||||
|
||||
If you intend to add a new feature or to make large changes, please discuss this
|
||||
first through a proposal on GitHub. Discussing your idea enables both you and the
|
||||
dev team that we are on the same page before you start on working on your change.
|
||||
If you have any questions, feel free to ask for help at any time!
|
||||
If you intend to add a new feature or to make large changes, please
|
||||
discuss this first through a proposal on GitHub. Discussing your idea
|
||||
enables both you and the dev team that we are on the same page before
|
||||
you start on working on your change. If you have any questions, feel
|
||||
free to ask for help at any time!
|
||||
|
||||
## Adding a new keyboard layout / dictionary for locale
|
||||
|
||||
As FlorisBoard is currently in alpha stage, things might change drastically. This
|
||||
also includes the config scheme of keyboard layouts. To prevent incompatible
|
||||
configs because some features and structures may change, please do not add this
|
||||
kind of content yet. As FlorisBoard's state progresses and its core stabilizes,
|
||||
you will be able to add keyboard layouts.
|
||||
|
||||
## Translating FlorisBoard
|
||||
|
||||
Before starting to translate, when adding a new translation please file
|
||||
an issue stating that you want to translate FlorisBoard into a language.
|
||||
Once this gets approved you can start translating. When updating an
|
||||
already existing translation file you can just send a PR directly.
|
||||
|
||||
If you are not familiar with PRs, check out this guide:
|
||||
[https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request](https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request)
|
||||
|
||||
Notes for tips below:
|
||||
- Replace `<language>` with the language you want to add
|
||||
- Replace `<code>` with the ISO 639-1 code of the language you want to
|
||||
add
|
||||
([List of codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes))
|
||||
|
||||
### Tips when adding a new translation
|
||||
|
||||
- To add the new translation file, navigate to `app/src/main/res/values`
|
||||
and copy the file `strings.xml` into the folder
|
||||
`app/src/main/res/values-<code>` (you have to create this folder)
|
||||
- Translate only the phrases inside the brackets, leave the name
|
||||
attribute as it is
|
||||
E.g.: `<string name="hello_string">Hello World!</string>`
|
||||
`<string name="hello_string">Ciao mondo!</string>`
|
||||
- When finished translating, commit your changes locally, as the commit
|
||||
message use `Add <language> translation`
|
||||
- Push your change(s) and create the PR. When everything checks out, it
|
||||
will get accepted.
|
||||
|
||||
### Tips when updating a translation
|
||||
- To update a translation, check the `strings.xml` in
|
||||
`app/src/main/res/values` for newly added strings and add them to the
|
||||
translation file in `app/src/main/res/values-<code>`
|
||||
- When finished translating, commit your changes locally, as the commit
|
||||
message use `Update <language> translation`
|
||||
- Push your change(s) and create the PR. When everything checks out, it
|
||||
will get accepted.
|
||||
As FlorisBoard is currently in alpha stage, things might change
|
||||
drastically. This also includes the config scheme of keyboard layouts.
|
||||
To prevent incompatible configs because some features and structures may
|
||||
change, please do not add this kind of content yet. As FlorisBoard's
|
||||
state progresses and its core stabilizes, you will be able to add
|
||||
keyboard layouts.
|
||||
|
||||
## Bug reporting
|
||||
|
||||
@@ -68,6 +48,11 @@ use the premade [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
|
||||
for bug reporting. This makes it easy for us to understand what the bug
|
||||
is and how to solve it.
|
||||
|
||||
### Capturing ADB debug logs
|
||||
### Capturing error logs
|
||||
|
||||
[[ TODO: create tutorial ]]
|
||||
Logs are captured by FlorisBoard's crash handler, which gives you the
|
||||
ability to copy it to the clipboard and paste it in GitHub. This is the
|
||||
preferred way to capture logs.
|
||||
|
||||
Alternatively, you can also use ADB (Android Debug Bridge) to capture
|
||||
the error log. This is recommended for experienced users only.
|
||||
|
||||
50
README.md
50
README.md
@@ -1,8 +1,13 @@
|
||||
# FlorisBoard
|
||||
<img align="left" width="80" height="80"
|
||||
src="fastlane/metadata/android/en-US/images/icon.png" alt="App icon">
|
||||
|
||||
An open-source keyboard for Android. Currently in alpha stage.
|
||||
# FlorisBoard  [](https://crowdin.florisboard.patrickgold.dev)
|
||||
|
||||
#### Public Alpha Test Programme
|
||||
**FlorisBoard** is a free and open-source keyboard for Android 6.0+
|
||||
devices. It aims at being modern, user-friendly and customizable while
|
||||
fully respecting your privacy. Currently in alpha/early-beta state.
|
||||
|
||||
## Public Alpha Test Programme
|
||||
Wanna try it out on your device? Use one of the following options:
|
||||
|
||||
_A. IzzySoft's repo for F-Droid_:
|
||||
@@ -28,25 +33,20 @@ tester, follow these steps:
|
||||
|
||||
_C. Use the APK provided in the release section of this repo_
|
||||
|
||||
##### Giving feedback
|
||||
If you want to give feedback to FlorisBoard, there are 2 ways to do so,
|
||||
as listed below:
|
||||
- *General feedback:* use the private feedback to developer section on
|
||||
the PlayStore listing.
|
||||
- *Bug reports or feature requests:* see the
|
||||
[contribution guidelines](CONTRIBUTING.md)
|
||||
### Giving feedback
|
||||
If you want to give feedback to FlorisBoard, there are several ways to
|
||||
do so, as listed [here](CONTRIBUTING.md#giving-general-feedback).
|
||||
|
||||
Thank you for contributing to FlorisBoard!
|
||||
|
||||
##### Note on F-Droid release
|
||||
### Note on F-Droid release
|
||||
FlorisBoard is currently available through Google Play and IzzySoft's
|
||||
repo for F-Droid, but is currently in the inclusion process for the main
|
||||
F-Droid repo. Planned proper F-Droid release is version 0.3.0.
|
||||
repo for F-Droid, but is in the inclusion process for the main F-Droid
|
||||
repo. Planned proper F-Droid release is version 0.3.0.
|
||||
|
||||
---
|
||||
|
||||
<img src="https://patrickgold.dev/media/previews/florisboard.png"
|
||||
height="256" alt="Preview Image">
|
||||
<img align="right" height="256"
|
||||
src="https://patrickgold.dev/media/previews/florisboard-preview-day.png"
|
||||
alt="Preview image">
|
||||
|
||||
## Feature roadmap
|
||||
|
||||
@@ -73,7 +73,7 @@ height="256" alt="Preview Image">
|
||||
* [x] Phone number layout
|
||||
* [x] Emoji layout (tweaks: 0.3.0)
|
||||
* [x] Emoticon layout
|
||||
* [ ] Kaomoji layout (0.3.0)
|
||||
* [ ] Kaomoji layout (0.5.0)
|
||||
|
||||
### Preferences
|
||||
* [x] Setup wizard
|
||||
@@ -96,9 +96,10 @@ height="256" alt="Preview Image">
|
||||
### Other useful features
|
||||
* [x] One-handed mode
|
||||
* [x] Clipboard/cursor tools
|
||||
* [x] Integrated number row / symbols in character layouts (0.3.0)
|
||||
* [ ] Floating keyboard (0.4.0)
|
||||
* [x] Gesture support (0.3.0)
|
||||
* [ ] Glide typing (0.3.0)
|
||||
* [ ] Glide typing (0.4.0)
|
||||
* [x] Full integration in IME service list of Android (xml/method)
|
||||
(integration is internal-only, because Android's default subtype
|
||||
implementation not really allows for dynamic language/layout
|
||||
@@ -113,6 +114,17 @@ Note:
|
||||
|
||||
(0.x.0) = planned version when feature will be implemented.
|
||||
|
||||
## Contributing
|
||||
Wanna contribute to FlorisBoard? That's great to hear! There are lots of
|
||||
different ways to help out. Bug reporting, making pull requests,
|
||||
translating FlorisBoard to make it more accessible, etc. For more
|
||||
information see the . Thank
|
||||
you for your help!
|
||||
|
||||
## 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.
|
||||
|
||||
## Used libraries, components and icons
|
||||
* [Google Flexbox Layout for Android](https://github.com/google/flexbox-layout)
|
||||
by [google](https://github.com/google)
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
applicationId "dev.patrickgold.florisboard"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 13
|
||||
versionName "0.2.1"
|
||||
versionCode 18
|
||||
versionName "0.2.6"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -33,18 +33,18 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'androidx.test:core:1.2.0'
|
||||
testImplementation 'androidx.test:core:1.3.0'
|
||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
implementation 'com.google.android:flexbox:2.0.1'
|
||||
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
|
||||
implementation 'com.google.android.material:material:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
|
||||
implementation 'com.jaredrummler:colorpicker:1.1.0'
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
<application
|
||||
android:name=".ime.core.FlorisApplication"
|
||||
android:allowBackup="false"
|
||||
android:extractNativeLibs="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -90,6 +91,13 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/SettingsTheme"/>
|
||||
|
||||
<!-- Crash Dialog Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.crashutility.CrashDialogActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/crash_dialog__title"
|
||||
android:theme="@style/CrashDialogTheme"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
@@ -13,37 +13,37 @@
|
||||
{ "code": 240, "label": "ð" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"l": [
|
||||
{ "code": 322, "label": "ł" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
"s": [
|
||||
@@ -52,9 +52,9 @@
|
||||
{ "code": 353, "label": "š" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
@@ -69,6 +69,7 @@
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -83,14 +84,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" }
|
||||
@@ -13,46 +13,47 @@
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 244, "label": "ô" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -67,14 +68,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
@@ -13,44 +13,45 @@
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 244, "label": "ô" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 223, "label": "ß" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -65,14 +66,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,43 +1,44 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 261, "label": "ą" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
@@ -45,20 +46,20 @@
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 223, "label": "ß" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 64, "label": "@" },
|
||||
@@ -73,14 +74,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
{ "code": 1610, "label": "ي" }
|
||||
],
|
||||
"ا": [
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1649, "label": "ٱ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1571, "label": "أ" },
|
||||
{ "code": 1573, "label": "إ" }
|
||||
],
|
||||
@@ -44,8 +44,8 @@
|
||||
{ "code": 1577, "label": "ة" }
|
||||
],
|
||||
"ک": [
|
||||
{ "code": 1603, "label": "ك" },
|
||||
{ "code": 1706, "label": "ڪ"}
|
||||
{ "code": 1706, "label": "ڪ"},
|
||||
{ "code": 1603, "label": "ك" }
|
||||
],
|
||||
"ز": [
|
||||
{ "code": 1688, "label": "ژ" }
|
||||
@@ -54,6 +54,7 @@
|
||||
{ "code": 1572, "label": "ؤ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 1611, "label": "ً" },
|
||||
{ "code": 1622, "label": "ٖ" },
|
||||
{ "code": 1648, "label": "ٰ" },
|
||||
{ "code": 1619, "label": "ٓ" },
|
||||
@@ -66,15 +67,14 @@
|
||||
{ "code": 1617, "label": "ّ" },
|
||||
{ "code": 1612, "label": "ٌ" },
|
||||
{ "code": 1613, "label": "ٍ" },
|
||||
{ "code": 1611, "label": "ً" },
|
||||
{ "code": 1620, "label": "ٔ" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".ir"},
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" },
|
||||
{ "code": -255, "label": ".ir"}
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 224, "label": "à" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
],
|
||||
"s": [
|
||||
@@ -42,15 +42,15 @@
|
||||
{ "code": 347, "label": "ś" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
"z": [
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 382, "label": "ž" },
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 378, "label": "ź" }
|
||||
],
|
||||
"ä": [
|
||||
@@ -60,6 +60,7 @@
|
||||
{ "code": 248, "label": "ø" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -74,14 +75,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
@@ -44,18 +45,17 @@
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 339, "label": "œ" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
@@ -63,6 +63,7 @@
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -77,14 +78,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 229, "label": "å" }
|
||||
@@ -13,39 +13,39 @@
|
||||
{ "code": 240, "label": "ð" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
"t": [
|
||||
{ "code": 254, "label": "þ" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
@@ -54,6 +54,7 @@
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -68,14 +69,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 230, "label": "æ" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
@@ -39,17 +40,17 @@
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 243, "label": "ó" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -64,14 +65,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
@@ -13,28 +13,28 @@
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
@@ -45,6 +45,7 @@
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -59,14 +60,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
@@ -13,11 +13,11 @@
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
@@ -25,19 +25,19 @@
|
||||
{ "code": 236, "label": "ì" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
@@ -51,6 +51,7 @@
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -65,14 +66,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 263, "label": "ć" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 263, "label": "ć" },
|
||||
{ "code": 269, "label": "č" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 281, "label": "ę" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
@@ -43,17 +44,17 @@
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -68,14 +69,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 261, "label": "ą" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
],
|
||||
"d": [
|
||||
@@ -19,36 +19,36 @@
|
||||
{ "code": 271, "label": "ď" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 281, "label": "ę" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 236, "label": "ì" }
|
||||
],
|
||||
"l": [
|
||||
{ "code": 322, "label": "ł" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 328, "label": "ň" },
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 328, "label": "ň" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
],
|
||||
"r": [
|
||||
@@ -65,9 +65,9 @@
|
||||
{ "code": 254, "label": "þ" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
@@ -76,18 +76,19 @@
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
"z": [
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 378, "label": "ź" },
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 382, "label": "ž" }
|
||||
],
|
||||
"ä": [
|
||||
{ "code": 230, "label": "æ" }
|
||||
],
|
||||
"ö": [
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 339, "label": "œ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
@@ -102,14 +103,13 @@
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"type": "extension",
|
||||
"name": "clipboard_cursor_row",
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -135, "label": "clipboard_select_all", "type": "enter_editing" },
|
||||
{ "code": -130, "label": "clipboard_copy", "type": "enter_editing" },
|
||||
{ "code": -20, "label": "arrow_left", "type": "navigation" },
|
||||
{ "code": -21, "label": "arrow_right", "type": "navigation" },
|
||||
{ "code": -131, "label": "clipboard_cut", "type": "enter_editing" },
|
||||
{ "code": -132, "label": "clipboard_paste", "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -12,7 +12,9 @@
|
||||
{ "code": 44, "label": ",", "popup": [] },
|
||||
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
|
||||
{ "code": 32, "label": " ", "popup": [] },
|
||||
{ "code": 46, "label": ".", "popup": [] },
|
||||
{ "code": 46, "label": ".", "popup": [
|
||||
{ "code": 8230, "label": "…" }
|
||||
] },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.crashutility
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.databinding.CrashDialogBinding
|
||||
|
||||
class CrashDialogActivity : AppCompatActivity() {
|
||||
private lateinit var binding: CrashDialogBinding
|
||||
private var stacktrace: String = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = CrashDialogBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
stacktrace = CrashUtility.getUnhandledStacktrace(this)
|
||||
binding.stacktrace.text = stacktrace
|
||||
|
||||
binding.copyToClipboard.setOnClickListener {
|
||||
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE)
|
||||
if (clipboardManager != null && clipboardManager is ClipboardManager) {
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(stacktrace, stacktrace))
|
||||
}
|
||||
}
|
||||
|
||||
binding.openBugReportForm.setOnClickListener {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(resources.getString(R.string.florisboard__issue_tracker_new_issue_url))
|
||||
)
|
||||
startActivity(browserIntent)
|
||||
}
|
||||
|
||||
binding.close.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.crashutility
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.*
|
||||
import android.app.Application.ActivityLifecycleCallbacks
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Abstract class which holds several static methods used for handling unexpected errors.
|
||||
*
|
||||
* Parts of this class (especially the install() function and the uncaughtException() handler) have
|
||||
* been inspired by the great CustomActivityOnCrash library:
|
||||
* https://github.com/Ereza/CustomActivityOnCrash (licensed under Apache 2.0)
|
||||
* https://github.com/Ereza/CustomActivityOnCrash/blob/master/library/src/main/java/cat/ereza/customactivityoncrash/CustomActivityOnCrash.java
|
||||
*/
|
||||
abstract class CrashUtility private constructor() {
|
||||
companion object {
|
||||
private const val SHARED_PREFS_FILE = "crash_utility"
|
||||
private const val SHARED_PREFS_LAST_CRASH_TIMESTAMP = "last_crash_timestamp"
|
||||
|
||||
private const val NOTIFICATION_CHANNEL_ID = "dev.patrickgold.florisboard.crashutility"
|
||||
private const val NOTIFICATION_ID = 0xFBAD0100
|
||||
|
||||
private const val UNHANDLED_STACKTRACE_FILE_EXT = "stacktrace"
|
||||
private const val TAG = "CrashUtility"
|
||||
|
||||
private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)
|
||||
|
||||
/**
|
||||
* Installs the CrashUtility crash handler for the given package [context]. Also registers
|
||||
* a notification channel for devices with Android 8.0+.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function does
|
||||
* nothing.
|
||||
* @return True if the installation was successful, false otherwise.
|
||||
*/
|
||||
fun install(context: Context?): Boolean {
|
||||
if (context == null) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"install($context): Can't install crash handler with a null Context object, doing nothing!"
|
||||
)
|
||||
return false
|
||||
}
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
if (oldHandler is UncaughtExceptionHandler) {
|
||||
Log.i(TAG, "install($context): Crash handler is already installed, doing nothing!")
|
||||
} else {
|
||||
val application = context.applicationContext
|
||||
if (application != null && application is Application) {
|
||||
try {
|
||||
Thread.setDefaultUncaughtExceptionHandler(
|
||||
UncaughtExceptionHandler(
|
||||
WeakReference(application),
|
||||
WeakReference(oldHandler),
|
||||
application.filesDir.absolutePath
|
||||
)
|
||||
)
|
||||
Log.i(
|
||||
TAG,
|
||||
"install($context): Successfully installed crash handler for this application!"
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"install($context): Failed to install crash handler, probably due to missing runtime permission 'setDefaultUncaughtExceptionHandler':\n$e"
|
||||
)
|
||||
return false
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"install($context): Failed to install crash handler due to an unspecified error:\n$e"
|
||||
)
|
||||
return false
|
||||
}
|
||||
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
||||
override fun onActivityCreated(
|
||||
activity: Activity,
|
||||
savedInstanceState: Bundle?
|
||||
) {
|
||||
if (activity !is CrashDialogActivity) {
|
||||
lastActivityCreated = WeakReference(activity)
|
||||
}
|
||||
}
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
override fun onActivityResumed(activity: Activity) {}
|
||||
override fun onActivityPaused(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(
|
||||
activity: Activity,
|
||||
outState: Bundle
|
||||
) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
})
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
|
||||
if (notificationManager != null && notificationManager is NotificationManager) {
|
||||
val notificationChannel = NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
context.resources.getString(R.string.crash_notification_channel__title),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
Log.i(
|
||||
TAG,
|
||||
"install($context): Successfully created crash handler notification channel!"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"install($context): Failed to create crash handler notification channel due to an unspecified error:\n$e"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(
|
||||
TAG,
|
||||
"install($context): Can't install crash handler with a null Application object, doing nothing!"
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns all unhandled stacktrace files.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function returns
|
||||
* an empty string.
|
||||
* @return All unhandled stacktrace files or an empty string.
|
||||
*/
|
||||
fun getUnhandledStacktrace(context: Context?): String {
|
||||
context ?: return ""
|
||||
val retString: StringBuilder = StringBuilder()
|
||||
val ustDir = getUstDir(context)
|
||||
if (ustDir.isDirectory) {
|
||||
(ustDir.listFiles { pathname ->
|
||||
pathname.name.endsWith(".$UNHANDLED_STACKTRACE_FILE_EXT")
|
||||
})?.forEach { file ->
|
||||
val newLine = System.lineSeparator()
|
||||
Log.i(TAG, "Reading unhandled stacktrace: ${file.name}")
|
||||
retString.append("~~~ ${file.name} ~~~$newLine$newLine")
|
||||
retString.append(readFile(file))
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
return retString.toString()
|
||||
}
|
||||
|
||||
fun hasUnhandledStacktraceFiles(context: Context): Boolean {
|
||||
val ustDir = getUstDir(context)
|
||||
return if (ustDir.isDirectory) {
|
||||
(ustDir.listFiles { pathname ->
|
||||
pathname.name.endsWith(".$UNHANDLED_STACKTRACE_FILE_EXT")
|
||||
})?.isNotEmpty() ?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last crash timestamp from the shared preferences.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function returns
|
||||
* the default value for the timestamp (0).
|
||||
* @return The last time crash timestamp or 0.
|
||||
*/
|
||||
private fun getLastCrashTimestamp(context: Context?): Long {
|
||||
context ?: return 0
|
||||
return context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE)
|
||||
.getLong(SHARED_PREFS_LAST_CRASH_TIMESTAMP, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last crash timestamp in the shared preferences.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function does
|
||||
* nothing.
|
||||
* @param value The timestamp of the current crash.
|
||||
*/
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun setLastCrashTimestamp(context: Context?, value: Long) {
|
||||
context ?: return
|
||||
// Note: must use commit() instead of apply(), as the value must be immediately written
|
||||
// to be possibly instantly read again.
|
||||
context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putLong(SHARED_PREFS_LAST_CRASH_TIMESTAMP, value)
|
||||
.commit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the current unhandled stacktrace directory.
|
||||
*
|
||||
* @param context The current package context.
|
||||
* @return The File object for the directory.
|
||||
*/
|
||||
private fun getUstDir(context: Context): File {
|
||||
val path = context.filesDir.absolutePath
|
||||
return File(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the stacktrace file for given [timestamp].
|
||||
*
|
||||
* @param context The current package context.
|
||||
* @param timestamp The timestamp of the stacktrace file to get.
|
||||
* @return The File object for the stacktrace file.
|
||||
*/
|
||||
private fun getUstFile(context: Context, timestamp: Long): File {
|
||||
val path = context.filesDir.absolutePath
|
||||
return File("$path/$timestamp.$UNHANDLED_STACKTRACE_FILE_EXT")
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a notification which opens [CrashDialogActivity] with given parameters.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function does
|
||||
* nothing.
|
||||
* @param id The ID of the notification.
|
||||
* @param title The title of the notification.
|
||||
* @param body The body of the notification.
|
||||
*/
|
||||
private fun pushNotification(context: Context?, id: Int, title: String, body: String) {
|
||||
context ?: return
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
|
||||
if (notificationManager != null && notificationManager is NotificationManager) {
|
||||
val notificationBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Notification.Builder(context.applicationContext, NOTIFICATION_CHANNEL_ID)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
Notification.Builder(context.applicationContext).apply {
|
||||
setPriority(Notification.PRIORITY_MAX)
|
||||
}
|
||||
}
|
||||
val crashDialogIntent = Intent(context, CrashDialogActivity::class.java)
|
||||
val notification = notificationBuilder.run {
|
||||
setContentTitle(title)
|
||||
style = Notification.BigTextStyle().bigText(body)
|
||||
setContentText(body)
|
||||
setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
setContentIntent(PendingIntent.getActivity(context, 0, crashDialogIntent, 0)).setAutoCancel(
|
||||
true
|
||||
)
|
||||
build()
|
||||
}
|
||||
notificationManager.notify(id, notification)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a notification configured for a single crash.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function does
|
||||
* nothing.
|
||||
*/
|
||||
private fun pushCrashOnceNotification(context: Context?) {
|
||||
context ?: return
|
||||
pushNotification(
|
||||
context,
|
||||
NOTIFICATION_ID.toInt(),
|
||||
context.resources.getString(R.string.crash_once_notification__title),
|
||||
context.resources.getString(R.string.crash_once_notification__body)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a notification configured for multiple crashes.
|
||||
*
|
||||
* @param context The current package context. If null is supplied, this function does
|
||||
* nothing.
|
||||
*/
|
||||
private fun pushCrashMultipleNotification(context: Context?) {
|
||||
context ?: return
|
||||
pushNotification(
|
||||
context,
|
||||
NOTIFICATION_ID.toInt(),
|
||||
context.resources.getString(R.string.crash_multiple_notification__title),
|
||||
context.resources.getString(R.string.crash_multiple_notification__body)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a given [file] and returns its content.
|
||||
*
|
||||
* @param file The file object.
|
||||
* @return The contents of the file or an empty string, if the file does not exist.
|
||||
*/
|
||||
private fun readFile(file: File): String {
|
||||
val retText = StringBuilder()
|
||||
if (file.exists()) {
|
||||
val newLine = System.lineSeparator()
|
||||
file.forEachLine {
|
||||
retText.append(it)
|
||||
retText.append(newLine)
|
||||
}
|
||||
}
|
||||
return retText.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes given [text] to given [file]. If the file already exists, its current content
|
||||
* will be overwritten.
|
||||
*
|
||||
* @param file The file object.
|
||||
* @param text The text to write to the file.
|
||||
* @return The contents of the file or an empty string, if the file does not exist.
|
||||
*/
|
||||
private fun writeToFile(file: File, text: String) {
|
||||
try {
|
||||
file.writeText(text)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom UncaughtExceptionHandler, which writes the captured stacktrace of the crash to the
|
||||
* internal storage, pushes a crash notification and kills the current process.
|
||||
*/
|
||||
class UncaughtExceptionHandler(
|
||||
private val application: WeakReference<Application>,
|
||||
private val oldHandler: WeakReference<Thread.UncaughtExceptionHandler?>,
|
||||
private val path: String
|
||||
) : Thread.UncaughtExceptionHandler {
|
||||
override fun uncaughtException(thread: Thread?, throwable: Throwable?) {
|
||||
Log.e(TAG, "Detected application crash, executing custom crash handler.")
|
||||
thread ?: return
|
||||
throwable ?: return
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val stacktrace = Log.getStackTraceString(throwable)
|
||||
val ustFile = File("$path/$timestamp.$UNHANDLED_STACKTRACE_FILE_EXT")
|
||||
writeToFile(ustFile, stacktrace)
|
||||
val application = application.get()
|
||||
if (application != null) {
|
||||
val lastTimestamp = getLastCrashTimestamp(application)
|
||||
if (lastTimestamp > 0) {
|
||||
val lastFile = getUstFile(application, lastTimestamp)
|
||||
val lastStacktrace = readFile(lastFile)
|
||||
if (lastStacktrace == stacktrace) {
|
||||
// Delete last stacktrace if it matches previous unhandled one
|
||||
lastFile.delete()
|
||||
}
|
||||
}
|
||||
setLastCrashTimestamp(application, timestamp)
|
||||
if (timestamp - lastTimestamp < 5000) {
|
||||
pushCrashMultipleNotification(application)
|
||||
val florisboard = FlorisBoard.getInstanceOrNull()
|
||||
if (florisboard != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
florisboard.switchToPreviousInputMethod()
|
||||
} else {
|
||||
val imm = application.getSystemService(Context.INPUT_METHOD_SERVICE)
|
||||
if (imm != null && imm is InputMethodManager) {
|
||||
@Suppress("DEPRECATION")
|
||||
imm.switchToNextInputMethod(
|
||||
florisboard.window?.window?.attributes?.token,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pushCrashOnceNotification(application)
|
||||
}
|
||||
}
|
||||
val lastActivity = lastActivityCreated.get()
|
||||
if (lastActivity != null) {
|
||||
//oldHandler.get()?.uncaughtException(thread, throwable)
|
||||
lastActivity.finish()
|
||||
lastActivityCreated.clear()
|
||||
}
|
||||
Process.killProcess(Process.myPid())
|
||||
exitProcess(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,876 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.InputType
|
||||
import android.view.KeyEvent
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.ExtractedTextRequest
|
||||
import android.view.inputmethod.InputContentInfo
|
||||
import androidx.annotation.RequiresApi
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
|
||||
// Constants for detectLastUnicodeCharacterLengthBeforeCursor method
|
||||
private const val LIGHT_SKIN_TONE = 0x1F3FB
|
||||
private const val MEDIUM_LIGHT_SKIN_TONE = 0x1F3FC
|
||||
private const val MEDIUM_SKIN_TONE = 0x1F3FD
|
||||
private const val MEDIUM_DARK_SKIN_TONE = 0x1F3FE
|
||||
private const val DARK_SKIN_TONE = 0x1F3FF
|
||||
private const val RED_HAIR = 0x1F9B0
|
||||
private const val CURLY_HAIR = 0x1F9B1
|
||||
private const val WHITE_HAIR = 0x1F9B2
|
||||
private const val BALD = 0x1F9B3
|
||||
private const val ZERO_WIDTH_JOINER = 0x200D
|
||||
private const val VARIATION_SELECTOR = 0xFE0F
|
||||
|
||||
// Array which holds all variations for easier checking (convenience only)
|
||||
private val emojiVariationArray: Array<Int> = arrayOf(
|
||||
LIGHT_SKIN_TONE,
|
||||
MEDIUM_LIGHT_SKIN_TONE,
|
||||
MEDIUM_SKIN_TONE,
|
||||
MEDIUM_DARK_SKIN_TONE,
|
||||
DARK_SKIN_TONE,
|
||||
RED_HAIR,
|
||||
CURLY_HAIR,
|
||||
WHITE_HAIR,
|
||||
BALD
|
||||
)
|
||||
|
||||
/**
|
||||
* Class which holds information relevant to an editor instance like the input [cachedText], [selection],
|
||||
* [inputAttributes], [imeOptions], etc. This class is thought to be an improved [EditorInfo]
|
||||
* object which also holds the state of the currently focused input editor.
|
||||
*/
|
||||
class EditorInstance private constructor(private val ims: InputMethodService?) {
|
||||
var contentMimeTypes: Array<out String?>? = null
|
||||
val cursorCapsMode: InputAttributes.CapsMode
|
||||
get() {
|
||||
val ic = ims?.currentInputConnection ?: return InputAttributes.CapsMode.NONE
|
||||
return InputAttributes.CapsMode.fromFlags(
|
||||
ic.getCursorCapsMode(inputAttributes.capsMode.toFlags())
|
||||
)
|
||||
}
|
||||
var currentWord: Region = Region(this)
|
||||
private set
|
||||
var imeOptions: ImeOptions = ImeOptions.fromImeOptionsInt(EditorInfo.IME_NULL)
|
||||
private set
|
||||
var inputAttributes: InputAttributes = InputAttributes.fromInputTypeInt(InputType.TYPE_NULL)
|
||||
private set
|
||||
var isComposingEnabled: Boolean = false
|
||||
set(v) {
|
||||
field = v
|
||||
reevaluateCurrentWord()
|
||||
if (v && !isRawInputEditor) {
|
||||
markComposingRegion(currentWord)
|
||||
} else {
|
||||
markComposingRegion(null)
|
||||
}
|
||||
}
|
||||
var isNewSelectionInBoundsOfOld: Boolean = false
|
||||
private set
|
||||
var isRawInputEditor: Boolean = true
|
||||
private set
|
||||
var packageName: String = "undefined"
|
||||
private set
|
||||
var selection: Selection = Selection(this)
|
||||
private set
|
||||
var cachedText: String = ""
|
||||
|
||||
private var clipboardManager: ClipboardManager? = null
|
||||
|
||||
init {
|
||||
val tmpClipboardManager = ims?.getSystemService(Context.CLIPBOARD_SERVICE)
|
||||
if (tmpClipboardManager != null && tmpClipboardManager is ClipboardManager) {
|
||||
clipboardManager = tmpClipboardManager
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun default(): EditorInstance {
|
||||
return EditorInstance(null)
|
||||
}
|
||||
|
||||
fun from(editorInfo: EditorInfo?, ims: InputMethodService?): EditorInstance {
|
||||
return if (editorInfo == null) { default() } else {
|
||||
EditorInstance(ims).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
contentMimeTypes = editorInfo.contentMimeTypes
|
||||
}
|
||||
imeOptions = ImeOptions.fromImeOptionsInt(editorInfo.imeOptions)
|
||||
inputAttributes = InputAttributes.fromInputTypeInt(editorInfo.inputType)
|
||||
packageName = editorInfo.packageName
|
||||
/*selection = Selection(this).apply {
|
||||
start = editorInfo.initialSelStart
|
||||
end = editorInfo.initialSelEnd
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
updateEditorState()
|
||||
reevaluateCurrentWord()
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler which reacts to selection updates coming from the target app's editor.
|
||||
*/
|
||||
fun onUpdateSelection(
|
||||
oldSelStart: Int, oldSelEnd: Int,
|
||||
newSelStart: Int, newSelEnd: Int
|
||||
) {
|
||||
updateEditorState()
|
||||
isNewSelectionInBoundsOfOld =
|
||||
newSelStart >= (oldSelStart - 1) &&
|
||||
newSelStart <= (oldSelStart + 1) &&
|
||||
newSelEnd >= (oldSelEnd - 1) &&
|
||||
newSelEnd <= (oldSelEnd + 1)
|
||||
selection.apply {
|
||||
start = newSelStart
|
||||
end = newSelEnd
|
||||
}
|
||||
reevaluateCurrentWord()
|
||||
if (selection.isCursorMode && isComposingEnabled && !isRawInputEditor) {
|
||||
markComposingRegion(currentWord)
|
||||
} else {
|
||||
markComposingRegion(null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the given [text] in the current composing region. Does nothing if the current
|
||||
* composing region is of zero length or null.
|
||||
*
|
||||
* @param text The text to complete in this editor's composing region.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun commitCompletion(text: String): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return if (isRawInputEditor) {
|
||||
false
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
ic.setComposingText(text, 1)
|
||||
markComposingRegion(null)
|
||||
updateEditorState()
|
||||
reevaluateCurrentWord()
|
||||
ic.endBatchEdit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the given [content] to this editor instance and adjusts both the cursor position and
|
||||
* composing region, if any.
|
||||
*
|
||||
* @param content The content to commit.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun commitContent(content: Uri, description: ClipDescription): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
val contentMimeTypes = contentMimeTypes
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1 || contentMimeTypes == null || contentMimeTypes.isEmpty()) {
|
||||
commitText(content.toString())
|
||||
} else {
|
||||
var mimeTypesDoMatch = false
|
||||
for (contentMimeType in contentMimeTypes) {
|
||||
if (description.hasMimeType(contentMimeType)) {
|
||||
mimeTypesDoMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (mimeTypesDoMatch) {
|
||||
ic.beginBatchEdit()
|
||||
markComposingRegion(null)
|
||||
val ret = ic.commitContent(InputContentInfo(content, description), 0, null)
|
||||
ic.endBatchEdit()
|
||||
ret
|
||||
} else {
|
||||
commitText(content.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the given [text] to this editor instance and adjusts both the cursor position and
|
||||
* composing region, if any.
|
||||
*
|
||||
* This method overwrites any selected text and replaces it with given [text]. If there is no
|
||||
* text selected (selection is in cursor mode), then this method will insert the [text] after
|
||||
* the cursor, then set the cursor position to the first character after the inserted text.
|
||||
*
|
||||
* @param text The text to commit.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun commitText(text: String): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return if (isRawInputEditor) {
|
||||
ic.commitText(text, 1)
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
markComposingRegion(null)
|
||||
ic.commitText(text, 1)
|
||||
updateEditorState()
|
||||
reevaluateCurrentWord()
|
||||
if (isComposingEnabled) {
|
||||
markComposingRegion(currentWord)
|
||||
}
|
||||
ic.endBatchEdit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a backward delete on this editor's text. If a text selection is active, all
|
||||
* characters inside this selection will be removed, else only the left-most character from
|
||||
* the cursor's position.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun deleteBackwards(): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
if (isRawInputEditor) {
|
||||
return sendSystemKeyEvent(KeyEvent.KEYCODE_DEL)
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
markComposingRegion(null)
|
||||
if (selection.isCursorMode && selection.start > 0) {
|
||||
val length = detectLastUnicodeCharacterLengthBeforeCursor()
|
||||
ic.deleteSurroundingText(length, 0)
|
||||
} else if (selection.isSelectionMode) {
|
||||
ic.commitText("", 1)
|
||||
}
|
||||
updateEditorState()
|
||||
reevaluateCurrentWord()
|
||||
if (isComposingEnabled) {
|
||||
markComposingRegion(currentWord)
|
||||
}
|
||||
ic.endBatchEdit()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes [n] words before the current cursor's position.
|
||||
* NOTE: this implementation does currently only delete currentWord. This is due to change in
|
||||
* future versions.
|
||||
*
|
||||
* @param n The number of words to delete before the cursor. Must be greater than 0 or this
|
||||
* method will fail.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun deleteWordsBeforeCursor(n: Int): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return if (n < 1 || isRawInputEditor) {
|
||||
false
|
||||
} else {
|
||||
ic.beginBatchEdit()
|
||||
markComposingRegion(null)
|
||||
if (currentWord.isValid) {
|
||||
ic.setSelection(currentWord.start, currentWord.end)
|
||||
ic.commitText("", 1)
|
||||
}
|
||||
reevaluateCurrentWord()
|
||||
ic.endBatchEdit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets [n] characters after the cursor's current position. The resulting string may be any
|
||||
* length ranging from 0 to n.
|
||||
*
|
||||
* @param n The number of characters to get after the cursor. Must be greater than 0 or this
|
||||
* method will fail.
|
||||
*
|
||||
* @return [n] or less characters after the cursor.
|
||||
*/
|
||||
fun getTextAfterCursor(n: Int): String {
|
||||
if (!selection.isValid || n < 1 || isRawInputEditor) {
|
||||
return ""
|
||||
}
|
||||
val from = selection.end.coerceIn(0, cachedText.length)
|
||||
val to = (selection.end + n).coerceIn(0, cachedText.length)
|
||||
return cachedText.substring(from, to)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets [n] characters before the cursor's current position. The resulting string may be any
|
||||
* length ranging from 0 to n.
|
||||
*
|
||||
* @param n The number of characters to get before the cursor. Must be greater than 0 or this
|
||||
* method will fail.
|
||||
*
|
||||
* @return [n] or less characters after the cursor.
|
||||
*/
|
||||
fun getTextBeforeCursor(n: Int): String {
|
||||
if (!selection.isValid || n < 1 || isRawInputEditor) {
|
||||
return ""
|
||||
}
|
||||
val from = (selection.start - n).coerceIn(0, cachedText.length)
|
||||
val to = selection.start.coerceIn(0, cachedText.length)
|
||||
return cachedText.substring(from, to)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a cut command on this editor instance and adjusts both the cursor position and
|
||||
* composing region, if any.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performClipboardCut(): Boolean {
|
||||
return if (selection.isSelectionMode) {
|
||||
val clipData: ClipData = ClipData.newPlainText(selection.text, selection.text)
|
||||
clipboardManager?.setPrimaryClip(clipData)
|
||||
deleteBackwards()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a copy command on this editor instance and adjusts both the cursor position and
|
||||
* composing region, if any.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performClipboardCopy(): Boolean {
|
||||
return if (selection.isSelectionMode) {
|
||||
val clipData: ClipData = ClipData.newPlainText(selection.text, selection.text)
|
||||
clipboardManager?.setPrimaryClip(clipData)
|
||||
setSelection(selection.end, selection.end)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a paste command on this editor instance and adjusts both the cursor position and
|
||||
* composing region, if any.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performClipboardPaste(): Boolean {
|
||||
val clipData: ClipData? = clipboardManager?.primaryClip
|
||||
val item: ClipData.Item? = clipData?.getItemAt(0)
|
||||
return when {
|
||||
item?.text != null -> {
|
||||
commitText(item.text.toString())
|
||||
}
|
||||
item?.uri != null -> {
|
||||
commitContent(item.uri, clipData.description)
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an enter key press on the current input editor.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performEnter(): Boolean {
|
||||
return if (isRawInputEditor) {
|
||||
sendSystemKeyEvent(KeyEvent.KEYCODE_ENTER)
|
||||
} else {
|
||||
commitText("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a given [action] on the current input editor.
|
||||
*
|
||||
* @param action The action to be performed on this editor instance.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun performEnterAction(action: ImeOptions.Action): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return ic.performEditorAction(action.toInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a given [keyCode] as a [KeyEvent.ACTION_DOWN].
|
||||
*
|
||||
* @param keyCode The key code to send, use a key code defined in Android's [KeyEvent], not in
|
||||
* [KeyCode] or this call may send a weird character, as this key codes do not match!!
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun sendSystemKeyEvent(keyCode: Int): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return ic.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a given [keyCode] as a [KeyEvent.ACTION_DOWN] with ALT pressed.
|
||||
*
|
||||
* @param keyCode The key code to send, use a key code defined in Android's [KeyEvent], not in
|
||||
* [KeyCode] or this call may send a weird character, as this key codes do not match!!
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun sendSystemKeyEventAlt(keyCode: Int): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return ic.sendKeyEvent(
|
||||
KeyEvent(
|
||||
0,
|
||||
1,
|
||||
KeyEvent.ACTION_DOWN, keyCode,
|
||||
0,
|
||||
KeyEvent.META_ALT_LEFT_ON
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection region of this instance and notifies the input connection.
|
||||
*
|
||||
* @param from The start index of the selection in characters (inclusive).
|
||||
* @param to The end index of the selection in characters (exclusive).
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
fun setSelection(from: Int, to: Int): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return if (isRawInputEditor) {
|
||||
selection.apply {
|
||||
start = -1
|
||||
end = -1
|
||||
}
|
||||
false
|
||||
} else {
|
||||
selection.apply {
|
||||
start = from
|
||||
end = to
|
||||
}
|
||||
ic.setSelection(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the length of the character before the cursor, as many Unicode characters nowadays
|
||||
* are longer than 1 Java char and thus the length has to be calculated in order to avoid
|
||||
* deleting only half of an emoji...
|
||||
* Is used primarily in [deleteBackwards].
|
||||
*
|
||||
* @return The length of the last Unicode character, in Java characters or 0 if the current
|
||||
* selection is invalid.
|
||||
*/
|
||||
private fun detectLastUnicodeCharacterLengthBeforeCursor(): Int {
|
||||
if (!selection.isValid) {
|
||||
return 0
|
||||
}
|
||||
var charIndex = 0
|
||||
var charLength = 0
|
||||
var charShouldGlue = false
|
||||
val textToSearch = cachedText.substring(0, selection.start.coerceAtMost(cachedText.length))
|
||||
var i = 0
|
||||
while (i < textToSearch.length) {
|
||||
val cp = textToSearch.codePointAt(i)
|
||||
val cpLength = Character.charCount(cp)
|
||||
when {
|
||||
charShouldGlue || cp == VARIATION_SELECTOR || emojiVariationArray.contains(cp) -> {
|
||||
charLength += cpLength
|
||||
charShouldGlue = false
|
||||
}
|
||||
cp == ZERO_WIDTH_JOINER -> {
|
||||
charLength += cpLength
|
||||
charShouldGlue = true
|
||||
}
|
||||
else -> {
|
||||
charIndex = i
|
||||
charLength = 0
|
||||
charShouldGlue = false
|
||||
}
|
||||
}
|
||||
i += cpLength
|
||||
}
|
||||
return textToSearch.length - charIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a given [region] as composing and notifies the input connection.
|
||||
*
|
||||
* @param region The region which should be marked as composing.
|
||||
*
|
||||
* @return True on success, false if an error occurred or the input connection is invalid.
|
||||
*/
|
||||
private fun markComposingRegion(region: Region?): Boolean {
|
||||
val ic = ims?.currentInputConnection ?: return false
|
||||
return when (region) {
|
||||
null -> ic.finishComposingText()
|
||||
else -> if (region.isValid) {
|
||||
ic.setComposingRegion(region.start, region.end)
|
||||
} else {
|
||||
ic.finishComposingText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the current word in this editor instance based on the current cursor position and
|
||||
* given delimiter [regex].
|
||||
*
|
||||
* @param regex The delimiter regex which should be used to split up the content text and find
|
||||
* words. May differ from locale to locale.
|
||||
*
|
||||
* @return True on success, false if no current word could be found.
|
||||
*/
|
||||
private fun reevaluateCurrentWord(regex: Regex): Boolean {
|
||||
var foundValidWord = false
|
||||
if (selection.isValid && selection.isCursorMode) {
|
||||
val words = cachedText.split("((?<=$regex)|(?=$regex))".toRegex())
|
||||
var pos = 0
|
||||
for (word in words) {
|
||||
if (selection.start >= pos && selection.start <= pos + word.length &&
|
||||
word.isNotEmpty() && !word.matches(regex)) {
|
||||
currentWord.apply {
|
||||
start = pos
|
||||
end = pos + word.length
|
||||
}
|
||||
foundValidWord = true
|
||||
break
|
||||
} else {
|
||||
pos += word.length
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundValidWord) {
|
||||
currentWord.apply {
|
||||
start = -1
|
||||
end = -1
|
||||
}
|
||||
}
|
||||
return foundValidWord
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the current word with the correct delimiter regex for current subtype.
|
||||
* TODO: currently only supports en-US
|
||||
*/
|
||||
private fun reevaluateCurrentWord() {
|
||||
val regex = "[^\\p{L}]".toRegex()
|
||||
reevaluateCurrentWord(regex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current text from the app's editor view.
|
||||
*
|
||||
* @return The target editor's content string.
|
||||
*/
|
||||
private fun updateEditorState() {
|
||||
val ic = ims?.currentInputConnection
|
||||
val et = ic?.getExtractedText(
|
||||
ExtractedTextRequest(), 0
|
||||
)
|
||||
val text = et?.text
|
||||
if (ic == null || et == null || text == null) {
|
||||
isRawInputEditor = true
|
||||
cachedText = ""
|
||||
selection.apply {
|
||||
start = -1
|
||||
end = -1
|
||||
}
|
||||
} else {
|
||||
isRawInputEditor = false
|
||||
cachedText = text.toString()
|
||||
selection.apply {
|
||||
start = et.selectionStart.coerceAtMost(cachedText.length)
|
||||
end = et.selectionEnd.coerceAtMost(cachedText.length)
|
||||
}
|
||||
}
|
||||
reevaluateCurrentWord()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which holds the same information as an [EditorInfo.imeOptions] int but more accessible and
|
||||
* readable.
|
||||
*/
|
||||
class ImeOptions private constructor(imeOptions: Int) {
|
||||
val action: Action = Action.fromInt(imeOptions)
|
||||
val flagForceAscii: Boolean = imeOptions and EditorInfo.IME_FLAG_FORCE_ASCII > 0
|
||||
val flagNavigateNext: Boolean = imeOptions and EditorInfo.IME_FLAG_NAVIGATE_NEXT > 0
|
||||
val flagNavigatePrevious: Boolean = imeOptions and EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS > 0
|
||||
val flagNoAccessoryAction: Boolean = imeOptions and EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION > 0
|
||||
val flagNoEnterAction: Boolean = imeOptions and EditorInfo.IME_FLAG_NO_ENTER_ACTION > 0
|
||||
val flagNoExtractUi: Boolean = imeOptions and EditorInfo.IME_FLAG_NO_EXTRACT_UI > 0
|
||||
val flagNoFullscreen: Boolean = imeOptions and EditorInfo.IME_FLAG_NO_FULLSCREEN > 0
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
val flagNoPersonalizedLearning: Boolean = imeOptions and EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING > 0
|
||||
|
||||
companion object {
|
||||
fun default(): ImeOptions {
|
||||
return fromImeOptionsInt(EditorInfo.IME_NULL)
|
||||
}
|
||||
|
||||
fun fromImeOptionsInt(imeOptions: Int): ImeOptions {
|
||||
return ImeOptions(imeOptions)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Action {
|
||||
DONE,
|
||||
GO,
|
||||
NEXT,
|
||||
NONE,
|
||||
PREVIOUS,
|
||||
SEARCH,
|
||||
SEND,
|
||||
UNSPECIFIED;
|
||||
|
||||
companion object {
|
||||
fun fromInt(raw: Int): Action {
|
||||
return when (raw and EditorInfo.IME_MASK_ACTION) {
|
||||
EditorInfo.IME_ACTION_DONE -> DONE
|
||||
EditorInfo.IME_ACTION_GO -> GO
|
||||
EditorInfo.IME_ACTION_NEXT -> NEXT
|
||||
EditorInfo.IME_ACTION_NONE -> NONE
|
||||
EditorInfo.IME_ACTION_PREVIOUS -> PREVIOUS
|
||||
EditorInfo.IME_ACTION_SEARCH -> SEARCH
|
||||
EditorInfo.IME_ACTION_SEND -> SEND
|
||||
EditorInfo.IME_ACTION_UNSPECIFIED -> UNSPECIFIED
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toInt(): Int {
|
||||
return when (this) {
|
||||
DONE -> EditorInfo.IME_ACTION_DONE
|
||||
GO -> EditorInfo.IME_ACTION_GO
|
||||
NEXT -> EditorInfo.IME_ACTION_NEXT
|
||||
NONE -> EditorInfo.IME_ACTION_NONE
|
||||
PREVIOUS -> EditorInfo.IME_ACTION_PREVIOUS
|
||||
SEARCH -> EditorInfo.IME_ACTION_SEARCH
|
||||
SEND -> EditorInfo.IME_ACTION_SEND
|
||||
UNSPECIFIED-> EditorInfo.IME_ACTION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which holds the same information as an [EditorInfo.inputType] int but more accessible and
|
||||
* readable.
|
||||
*/
|
||||
class InputAttributes private constructor(inputType: Int) {
|
||||
val type: Type
|
||||
val variation: Variation
|
||||
val capsMode: CapsMode
|
||||
var flagNumberDecimal: Boolean = false
|
||||
private set
|
||||
var flagNumberSigned: Boolean = false
|
||||
private set
|
||||
var flagTextAutoComplete: Boolean = false
|
||||
private set
|
||||
var flagTextAutoCorrect: Boolean = false
|
||||
private set
|
||||
var flagTextImeMultiLine: Boolean = false
|
||||
private set
|
||||
var flagTextMultiLine: Boolean = false
|
||||
private set
|
||||
var flagTextNoSuggestions: Boolean = false
|
||||
private set
|
||||
|
||||
init {
|
||||
when (inputType and InputType.TYPE_MASK_CLASS) {
|
||||
InputType.TYPE_CLASS_DATETIME -> {
|
||||
type = Type.DATETIME
|
||||
variation = when (inputType and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_DATETIME_VARIATION_DATE -> Variation.DATE
|
||||
InputType.TYPE_DATETIME_VARIATION_NORMAL -> Variation.NORMAL
|
||||
InputType.TYPE_DATETIME_VARIATION_TIME -> Variation.TIME
|
||||
else -> Variation.NORMAL
|
||||
}
|
||||
capsMode = CapsMode.NONE
|
||||
}
|
||||
InputType.TYPE_CLASS_NUMBER -> {
|
||||
type = Type.NUMBER
|
||||
variation = when (inputType and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_NUMBER_VARIATION_NORMAL -> Variation.NORMAL
|
||||
InputType.TYPE_NUMBER_VARIATION_PASSWORD -> Variation.PASSWORD
|
||||
else -> Variation.NORMAL
|
||||
}
|
||||
capsMode = CapsMode.NONE
|
||||
flagNumberDecimal = inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL > 0
|
||||
flagNumberSigned = inputType and InputType.TYPE_NUMBER_FLAG_SIGNED > 0
|
||||
}
|
||||
InputType.TYPE_CLASS_PHONE -> {
|
||||
type = Type.PHONE
|
||||
variation = Variation.NORMAL
|
||||
capsMode = CapsMode.NONE
|
||||
}
|
||||
InputType.TYPE_CLASS_TEXT -> {
|
||||
type = Type.TEXT
|
||||
variation = when (inputType and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS -> Variation.EMAIL_ADDRESS
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT -> Variation.EMAIL_SUBJECT
|
||||
InputType.TYPE_TEXT_VARIATION_FILTER -> Variation.FILTER
|
||||
InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE -> Variation.LONG_MESSAGE
|
||||
InputType.TYPE_TEXT_VARIATION_NORMAL -> Variation.NORMAL
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD -> Variation.PASSWORD
|
||||
InputType.TYPE_TEXT_VARIATION_PERSON_NAME -> Variation.PERSON_NAME
|
||||
InputType.TYPE_TEXT_VARIATION_PHONETIC -> Variation.PHONETIC
|
||||
InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS -> Variation.POSTAL_ADDRESS
|
||||
InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE -> Variation.SHORT_MESSAGE
|
||||
InputType.TYPE_TEXT_VARIATION_URI -> Variation.URI
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD -> Variation.VISIBLE_PASSWORD
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT -> Variation.WEB_EDIT_TEXT
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS -> Variation.WEB_EMAIL_ADDRESS
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD -> Variation.WEB_PASSWORD
|
||||
else -> Variation.NORMAL
|
||||
}
|
||||
capsMode = CapsMode.fromFlags(inputType)
|
||||
flagTextAutoComplete = inputType and InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE > 0
|
||||
flagTextAutoCorrect = inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT > 0
|
||||
flagTextImeMultiLine = inputType and InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE > 0
|
||||
flagTextMultiLine = inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE > 0
|
||||
flagTextNoSuggestions = inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS > 0
|
||||
}
|
||||
else -> {
|
||||
type = Type.TEXT
|
||||
variation = Variation.NORMAL
|
||||
capsMode = CapsMode.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromInputTypeInt(inputType: Int): InputAttributes {
|
||||
return InputAttributes(inputType)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
DATETIME,
|
||||
NUMBER,
|
||||
PHONE,
|
||||
TEXT;
|
||||
}
|
||||
|
||||
enum class Variation {
|
||||
DATE,
|
||||
EMAIL_ADDRESS,
|
||||
EMAIL_SUBJECT,
|
||||
FILTER,
|
||||
LONG_MESSAGE,
|
||||
NORMAL,
|
||||
PASSWORD,
|
||||
PERSON_NAME,
|
||||
PHONETIC,
|
||||
POSTAL_ADDRESS,
|
||||
SHORT_MESSAGE,
|
||||
TIME,
|
||||
URI,
|
||||
VISIBLE_PASSWORD,
|
||||
WEB_EDIT_TEXT,
|
||||
WEB_EMAIL_ADDRESS,
|
||||
WEB_PASSWORD;
|
||||
}
|
||||
|
||||
enum class CapsMode {
|
||||
ALL,
|
||||
NONE,
|
||||
SENTENCES,
|
||||
WORDS;
|
||||
|
||||
companion object {
|
||||
fun fromFlags(flags: Int): CapsMode {
|
||||
return when {
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS > 0 -> ALL
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES > 0 -> SENTENCES
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_WORDS > 0 -> WORDS
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toFlags(): Int {
|
||||
return when (this) {
|
||||
ALL -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
SENTENCES -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
WORDS -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which marks a region of the [text] in [editorInstance].
|
||||
*/
|
||||
open class Region(private val editorInstance: EditorInstance) {
|
||||
var start: Int = -1
|
||||
var end: Int = -1
|
||||
val isValid: Boolean
|
||||
get() = start >= 0 && end >= 0 && length >= 0
|
||||
val length: Int
|
||||
get() = end - start
|
||||
val text: String
|
||||
get() {
|
||||
val eiText = editorInstance.cachedText
|
||||
return if (!isValid || start >= eiText.length) {
|
||||
""
|
||||
} else {
|
||||
val end = if (end >= eiText.length) { eiText.length } else { end }
|
||||
editorInstance.cachedText.substring(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun equals(other: Any?): Boolean {
|
||||
return if (other is Region) {
|
||||
start == other.start && end == other.end
|
||||
} else {
|
||||
super.equals(other)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = start
|
||||
result = 31 * result + end
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which holds selection attributes and returns the correct text for set selection based on
|
||||
* the text in [editorInstance].
|
||||
*/
|
||||
class Selection(private val editorInstance: EditorInstance) : Region(editorInstance) {
|
||||
val isCursorMode: Boolean
|
||||
get() = length == 0 && isValid
|
||||
val isSelectionMode: Boolean
|
||||
get() = length != 0 && isValid
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Patrick Goldinger
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import android.app.Application
|
||||
import dev.patrickgold.florisboard.crashutility.CrashUtility
|
||||
|
||||
class FlorisApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
CrashUtility.install(this)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.media.AudioManager
|
||||
import android.os.*
|
||||
@@ -30,7 +31,6 @@ import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.CursorAnchorInfo
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.widget.ImageButton
|
||||
@@ -44,6 +44,7 @@ import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import dev.patrickgold.florisboard.settings.SettingsMainActivity
|
||||
import dev.patrickgold.florisboard.util.*
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Variable which holds the current [FlorisBoard] instance. To get this instance from another
|
||||
@@ -55,7 +56,7 @@ private var florisboardInstance: FlorisBoard? = null
|
||||
* Core class responsible to link together both the text and media input managers as well as
|
||||
* managing the one-handed UI.
|
||||
*/
|
||||
class FlorisBoard : InputMethodService() {
|
||||
class FlorisBoard : InputMethodService(), ClipboardManager.OnPrimaryClipChangedListener {
|
||||
lateinit var prefs: PrefHelper
|
||||
private set
|
||||
|
||||
@@ -64,13 +65,15 @@ class FlorisBoard : InputMethodService() {
|
||||
var inputView: InputView? = null
|
||||
private set
|
||||
private var inputWindowView: InputWindowView? = null
|
||||
private var eventListeners: MutableList<EventListener> = mutableListOf()
|
||||
private var eventListeners: MutableList<WeakReference<EventListener>> = mutableListOf()
|
||||
|
||||
private var audioManager: AudioManager? = null
|
||||
var clipboardManager: ClipboardManager? = null
|
||||
private var vibrator: Vibrator? = null
|
||||
private val osHandler = Handler()
|
||||
|
||||
var activeEditorInstance: EditorInstance = EditorInstance.default()
|
||||
|
||||
lateinit var subtypeManager: SubtypeManager
|
||||
lateinit var activeSubtype: Subtype
|
||||
private var currentThemeIsNight: Boolean = false
|
||||
@@ -88,6 +91,7 @@ class FlorisBoard : InputMethodService() {
|
||||
|
||||
companion object {
|
||||
private const val IME_ID: String = "dev.patrickgold.florisboard/.ime.core.FlorisBoard"
|
||||
private val TAG: String? = FlorisBoard::class.simpleName
|
||||
|
||||
fun checkIfImeIsEnabled(context: Context): Boolean {
|
||||
val activeImeIds = Settings.Secure.getString(
|
||||
@@ -144,10 +148,11 @@ class FlorisBoard : InputMethodService() {
|
||||
.build()
|
||||
)
|
||||
}
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreate()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onCreate()")
|
||||
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboardManager?.addPrimaryClipChangedListener(this)
|
||||
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
prefs = PrefHelper.getDefaultInstance(this)
|
||||
prefs.initDefaultPreferences()
|
||||
@@ -163,24 +168,24 @@ class FlorisBoard : InputMethodService() {
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
|
||||
super.onCreate()
|
||||
eventListeners.toList().forEach { it.onCreate() }
|
||||
eventListeners.toList().forEach { it.get()?.onCreate() }
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateInputView(): View? {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreateInputView()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onCreateInputView()")
|
||||
|
||||
baseContext.setTheme(currentThemeResId)
|
||||
|
||||
inputWindowView = layoutInflater.inflate(R.layout.florisboard, null) as InputWindowView
|
||||
|
||||
eventListeners.toList().forEach { it.onCreateInputView() }
|
||||
eventListeners.toList().forEach { it.get()?.onCreateInputView() }
|
||||
|
||||
return inputWindowView
|
||||
}
|
||||
|
||||
fun registerInputView(inputView: InputView) {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "registerInputView(inputView)")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "registerInputView($inputView)")
|
||||
|
||||
this.inputView = inputView
|
||||
initializeOneHandedEnvironment()
|
||||
@@ -188,36 +193,59 @@ class FlorisBoard : InputMethodService() {
|
||||
updateSoftInputWindowLayoutParameters()
|
||||
updateOneHandedPanelVisibility()
|
||||
|
||||
eventListeners.toList().forEach { it.onRegisterInputView(inputView) }
|
||||
eventListeners.toList().forEach { it.get()?.onRegisterInputView(inputView) }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onDestroy()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onDestroy()")
|
||||
|
||||
clipboardManager?.removePrimaryClipChangedListener(this)
|
||||
osHandler.removeCallbacksAndMessages(null)
|
||||
florisboardInstance = null
|
||||
|
||||
eventListeners.toList().forEach { it.onDestroy() }
|
||||
eventListeners.toList().forEach { it.get()?.onDestroy() }
|
||||
eventListeners.clear()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onStartInput($attribute, $restarting)")
|
||||
|
||||
super.onStartInput(attribute, restarting)
|
||||
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
|
||||
}
|
||||
|
||||
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
|
||||
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onStartInputView($info, $restarting)")
|
||||
Log.i(TAG, "onStartInputView: " + info?.debugSummarize())
|
||||
|
||||
super.onStartInputView(info, restarting)
|
||||
eventListeners.toList().forEach { it.onStartInputView(info, restarting) }
|
||||
activeEditorInstance = EditorInstance.from(info, this)
|
||||
eventListeners.toList().forEach {
|
||||
it.get()?.onStartInputView(activeEditorInstance, restarting)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInputView(finishingInput: Boolean) {
|
||||
currentInputConnection?.requestCursorUpdates(0)
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onFinishInputView($finishingInput)")
|
||||
|
||||
if (finishingInput) {
|
||||
activeEditorInstance = EditorInstance.default()
|
||||
}
|
||||
|
||||
super.onFinishInputView(finishingInput)
|
||||
eventListeners.toList().forEach { it.onFinishInputView(finishingInput) }
|
||||
eventListeners.toList().forEach { it.get()?.onFinishInputView(finishingInput) }
|
||||
}
|
||||
|
||||
override fun onFinishInput() {
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onFinishInput()")
|
||||
|
||||
super.onFinishInput()
|
||||
currentInputConnection?.requestCursorUpdates(0)
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowShown()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onWindowShown()")
|
||||
|
||||
prefs.sync()
|
||||
updateTheme()
|
||||
@@ -227,17 +255,18 @@ class FlorisBoard : InputMethodService() {
|
||||
setActiveInput(R.id.text_input)
|
||||
|
||||
super.onWindowShown()
|
||||
eventListeners.toList().forEach { it.onWindowShown() }
|
||||
eventListeners.toList().forEach { it.get()?.onWindowShown() }
|
||||
}
|
||||
|
||||
override fun onWindowHidden() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowHidden()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onWindowHidden()")
|
||||
|
||||
super.onWindowHidden()
|
||||
eventListeners.toList().forEach { it.onWindowHidden() }
|
||||
eventListeners.toList().forEach { it.get()?.onWindowHidden() }
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onConfigurationChanged($newConfig)")
|
||||
if (isInputViewShown) {
|
||||
updateOneHandedPanelVisibility()
|
||||
}
|
||||
@@ -245,43 +274,33 @@ class FlorisBoard : InputMethodService() {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
||||
super.onUpdateCursorAnchorInfo(cursorAnchorInfo)
|
||||
eventListeners.toList().forEach { it.onUpdateCursorAnchorInfo(cursorAnchorInfo) }
|
||||
}
|
||||
|
||||
override fun onUpdateSelection(
|
||||
oldSelStart: Int,
|
||||
oldSelEnd: Int,
|
||||
newSelStart: Int,
|
||||
newSelEnd: Int,
|
||||
candidatesStart: Int,
|
||||
candidatesEnd: Int
|
||||
oldSelStart: Int, oldSelEnd: Int,
|
||||
newSelStart: Int, newSelEnd: Int,
|
||||
candidatesStart: Int, candidatesEnd: Int
|
||||
) {
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onUpdateSelection($oldSelStart, $oldSelEnd, $newSelStart, $newSelEnd, $candidatesStart, $candidatesEnd)")
|
||||
|
||||
super.onUpdateSelection(
|
||||
oldSelStart,
|
||||
oldSelEnd,
|
||||
newSelStart,
|
||||
newSelEnd,
|
||||
candidatesStart,
|
||||
candidatesEnd
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd,
|
||||
candidatesStart, candidatesEnd
|
||||
)
|
||||
eventListeners.toList().forEach {
|
||||
it.onUpdateSelection(
|
||||
oldSelStart,
|
||||
oldSelEnd,
|
||||
newSelStart,
|
||||
newSelEnd,
|
||||
candidatesStart,
|
||||
candidatesEnd
|
||||
)
|
||||
}
|
||||
activeEditorInstance.onUpdateSelection(
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd
|
||||
)
|
||||
eventListeners.toList().forEach { it.get()?.onUpdateSelection() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Reapplies the supplies colors and settings from prefs to navigation bar.
|
||||
* Updates the theme of the IME Window, status and navigation bar, as well as the InputView and
|
||||
* some of its components.
|
||||
*/
|
||||
private fun updateTheme() {
|
||||
// Rebuild the UI if the theme has changed from day to night or vice versa to prevent
|
||||
// theme glitches with scrollbars and hints of buttons in the media UI. If the UI must be
|
||||
// rebuild, quit this method, as it will be called again by the newly created UI.
|
||||
val newThemeIsNightMode = prefs.internal.themeCurrentIsNight
|
||||
if (currentThemeIsNight != newThemeIsNightMode) {
|
||||
currentThemeResId = getDayNightBaseThemeId(newThemeIsNightMode)
|
||||
@@ -289,18 +308,34 @@ class FlorisBoard : InputMethodService() {
|
||||
setInputView(onCreateInputView())
|
||||
return
|
||||
}
|
||||
|
||||
// Get Window and the flags of the DecorView
|
||||
val w = window?.window ?: return
|
||||
inputView?.setBackgroundColor(prefs.theme.keyboardBgColor)
|
||||
var flags = w.decorView.systemUiVisibility
|
||||
|
||||
// Update navigation bar theme
|
||||
w.navigationBarColor = prefs.theme.navBarColor
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
var flags = w.decorView.systemUiVisibility
|
||||
flags = if (prefs.theme.navBarIsLight) {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
} else {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||
}
|
||||
w.decorView.systemUiVisibility = flags
|
||||
}
|
||||
|
||||
// Update status bar to be transparent
|
||||
// Done as starting with Android 11 the IME Window takes the primaryColorDark value and
|
||||
// colors the status bar, which isn't the desired behavior. (See issue #43)
|
||||
flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
w.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
w.statusBarColor = Color.TRANSPARENT
|
||||
|
||||
// Apply the new flags to the DecorView
|
||||
w.decorView.systemUiVisibility = flags
|
||||
|
||||
// Update InputView theme
|
||||
inputView?.setBackgroundColor(prefs.theme.keyboardBgColor)
|
||||
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(prefs.theme.oneHandedBgColor)
|
||||
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(prefs.theme.oneHandedBgColor)
|
||||
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_start)
|
||||
@@ -311,7 +346,7 @@ class FlorisBoard : InputMethodService() {
|
||||
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
|
||||
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_end)
|
||||
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
|
||||
eventListeners.toList().forEach { it.onApplyThemeAttributes() }
|
||||
eventListeners.toList().forEach { it.get()?.onApplyThemeAttributes() }
|
||||
}
|
||||
|
||||
override fun onComputeInsets(outInsets: Insets?) {
|
||||
@@ -523,25 +558,34 @@ class FlorisBoard : InputMethodService() {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
override fun onPrimaryClipChanged() {
|
||||
eventListeners.toList().forEach { it.get()?.onPrimaryClipChanged() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given [listener] to the list which will receive FlorisBoard events.
|
||||
*
|
||||
* @param listener The listener object which receives the events.
|
||||
* @returns True if the listener has been added successfully, false otherwise.
|
||||
* @return True if the listener has been added successfully, false otherwise.
|
||||
*/
|
||||
fun addEventListener(listener: EventListener): Boolean {
|
||||
return eventListeners.add(listener)
|
||||
return eventListeners.add(WeakReference(listener))
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given [listener] from the list which will receive FlorisBoard events.
|
||||
*
|
||||
* @param listener The same listener object which was used in [addEventListener].
|
||||
* @returns True if the listener has been removed successfully, false otherwise. A false return
|
||||
* @return True if the listener has been removed successfully, false otherwise. A false return
|
||||
* value may also indicate that the [listener] was not added previously.
|
||||
*/
|
||||
fun removeEventListener(listener: EventListener): Boolean {
|
||||
return eventListeners.remove(listener)
|
||||
eventListeners.toList().forEach {
|
||||
if (it.get() == listener) {
|
||||
return eventListeners.remove(it)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
interface EventListener {
|
||||
@@ -550,23 +594,16 @@ class FlorisBoard : InputMethodService() {
|
||||
fun onRegisterInputView(inputView: InputView) {}
|
||||
fun onDestroy() {}
|
||||
|
||||
fun onStartInputView(info: EditorInfo?, restarting: Boolean) {}
|
||||
fun onStartInputView(instance: EditorInstance, restarting: Boolean) {}
|
||||
fun onFinishInputView(finishingInput: Boolean) {}
|
||||
|
||||
fun onWindowShown() {}
|
||||
fun onWindowHidden() {}
|
||||
|
||||
fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {}
|
||||
fun onUpdateSelection(
|
||||
oldSelStart: Int,
|
||||
oldSelEnd: Int,
|
||||
newSelStart: Int,
|
||||
newSelEnd: Int,
|
||||
candidatesStart: Int,
|
||||
candidatesEnd: Int
|
||||
) {}
|
||||
fun onUpdateSelection() {}
|
||||
|
||||
fun onApplyThemeAttributes() {}
|
||||
fun onPrimaryClipChanged() {}
|
||||
fun onSubtypeChanged(newSubtype: Subtype) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,13 +19,16 @@ package dev.patrickgold.florisboard.ime.core
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
/**
|
||||
* Root view of the keyboard. Notifies [FlorisBoard] when it has been attached to a window.
|
||||
*/
|
||||
@@ -51,7 +54,11 @@ class InputView : LinearLayout {
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onAttachedToWindow()")
|
||||
@@ -67,7 +74,7 @@ class InputView : LinearLayout {
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val heightFactor = when (resources.configuration.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> 0.85f
|
||||
Configuration.ORIENTATION_LANDSCAPE -> 1.0f
|
||||
else -> if (prefs.keyboard.oneHandedMode != "off") {
|
||||
0.9f
|
||||
} else {
|
||||
@@ -83,12 +90,47 @@ class InputView : LinearLayout {
|
||||
"extra_tall" -> 1.15f
|
||||
else -> 1.00f
|
||||
}
|
||||
val height = (resources.getDimension(R.dimen.inputView_baseHeight) * heightFactor).roundToInt()
|
||||
var height = (calcInputViewHeight() * heightFactor).roundToInt()
|
||||
desiredInputViewHeight = height
|
||||
desiredSmartbarHeight = (0.16129 * height).roundToInt()
|
||||
desiredTextKeyboardViewHeight = height - desiredSmartbarHeight
|
||||
desiredMediaKeyboardViewHeight = height
|
||||
// Add bottom offset for curved screens here. As the desired heights have already been set,
|
||||
// adding a value to the height now will result in a bottom padding (aka offset).
|
||||
height += ViewLayoutUtils.convertDpToPixel(
|
||||
florisboard.prefs.keyboard.bottomOffset.toFloat(),
|
||||
context
|
||||
).toInt()
|
||||
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the input view height based on the current screen dimensions and the auto
|
||||
* selected dimension values.
|
||||
*
|
||||
* This method and the fraction values have been inspired by [OpenBoard](https://github.com/dslul/openboard)
|
||||
* but are not 1:1 the same. This implementation differs from the
|
||||
* [original](https://github.com/dslul/openboard/blob/90ae4c8aec034a8935e1fd02b441be25c7dba6ce/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ResourceUtils.java)
|
||||
* by calculating the average of the min and max height values, then taking at least the input
|
||||
* view base height and return this resulting value.
|
||||
*/
|
||||
private fun calcInputViewHeight(): Float {
|
||||
val dm: DisplayMetrics = resources.displayMetrics
|
||||
val minBaseSize: Float = when (resources.configuration.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> resources.getFraction(
|
||||
R.fraction.inputView_minHeightFraction, dm.heightPixels, dm.heightPixels
|
||||
)
|
||||
else -> resources.getFraction(
|
||||
R.fraction.inputView_minHeightFraction, dm.widthPixels, dm.widthPixels
|
||||
)
|
||||
}
|
||||
val maxBaseSize: Float = resources.getFraction(
|
||||
R.fraction.inputView_maxHeightFraction, dm.heightPixels, dm.heightPixels
|
||||
)
|
||||
return ((minBaseSize + maxBaseSize) / 2.0f).coerceAtLeast(
|
||||
resources.getDimension(R.dimen.inputView_baseHeight)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -180,12 +180,20 @@ class PrefHelper(
|
||||
*/
|
||||
class Correction(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val DOUBLE_SPACE_PERIOD = "correction__double_space_period"
|
||||
const val AUTO_CAPITALIZATION = "correction__auto_capitalization"
|
||||
const val DOUBLE_SPACE_PERIOD = "correction__double_space_period"
|
||||
const val REMEMBER_CAPS_LOCK_STATE = "correction__remember_caps_lock_state"
|
||||
}
|
||||
|
||||
var doubleSpacePeriod: Boolean = false
|
||||
get() = prefHelper.getPref(DOUBLE_SPACE_PERIOD, true)
|
||||
private set
|
||||
var autoCapitalization: Boolean
|
||||
get() = prefHelper.getPref(AUTO_CAPITALIZATION, true)
|
||||
set(v) = prefHelper.setPref(AUTO_CAPITALIZATION, v)
|
||||
var doubleSpacePeriod: Boolean
|
||||
get() = prefHelper.getPref(DOUBLE_SPACE_PERIOD, true)
|
||||
set(v) = prefHelper.setPref(DOUBLE_SPACE_PERIOD, v)
|
||||
var rememberCapsLockState: Boolean
|
||||
get() = prefHelper.getPref(REMEMBER_CAPS_LOCK_STATE, false)
|
||||
set(v) = prefHelper.setPref(REMEMBER_CAPS_LOCK_STATE, v)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +301,10 @@ class PrefHelper(
|
||||
*/
|
||||
class Keyboard(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val BOTTOM_OFFSET = "keyboard__bottom_offset"
|
||||
const val HEIGHT_FACTOR = "keyboard__height_factor"
|
||||
const val HINTED_NUMBER_ROW = "keyboard__hinted_number_row"
|
||||
const val HINTED_SYMBOLS = "keyboard__hinted_symbols"
|
||||
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
|
||||
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
|
||||
const val POPUP_ENABLED = "keyboard__popup_enabled"
|
||||
@@ -303,9 +314,18 @@ class PrefHelper(
|
||||
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
|
||||
}
|
||||
|
||||
var bottomOffset: Int = 0
|
||||
get() = prefHelper.getPref(BOTTOM_OFFSET, 0)
|
||||
private set
|
||||
var heightFactor: String = ""
|
||||
get() = prefHelper.getPref(HEIGHT_FACTOR, "normal")
|
||||
private set
|
||||
var hintedNumberRow: Boolean
|
||||
get() = prefHelper.getPref(HINTED_NUMBER_ROW, true)
|
||||
set(v) = prefHelper.setPref(HINTED_NUMBER_ROW, v)
|
||||
var hintedSymbols: Boolean
|
||||
get() = prefHelper.getPref(HINTED_SYMBOLS, true)
|
||||
set(v) = prefHelper.setPref(HINTED_SYMBOLS, v)
|
||||
var longPressDelay: Int = 0
|
||||
get() = prefHelper.getPref(LONG_PRESS_DELAY, 300)
|
||||
private set
|
||||
@@ -341,10 +361,10 @@ class PrefHelper(
|
||||
}
|
||||
|
||||
var activeSubtypeId: Int
|
||||
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, Subtype.DEFAULT.id)
|
||||
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, Subtype.DEFAULT.id)
|
||||
set(v) = prefHelper.setPref(ACTIVE_SUBTYPE_ID, v)
|
||||
var subtypes: String
|
||||
get() = prefHelper.getPref(SUBTYPES, "")
|
||||
get() = prefHelper.getPref(SUBTYPES, "")
|
||||
set(v) = prefHelper.setPref(SUBTYPES, v)
|
||||
}
|
||||
|
||||
@@ -353,20 +373,24 @@ class PrefHelper(
|
||||
*/
|
||||
class Suggestion(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ENABLED = "suggestion__enabled"
|
||||
const val SHOW_INSTEAD = "suggestion__show_instead"
|
||||
const val USE_PREV_WORDS = "suggestion__use_prev_words"
|
||||
const val ENABLED = "suggestion__enabled"
|
||||
const val SHOW_INSTEAD = "suggestion__show_instead"
|
||||
const val SUGGEST_CLIPBOARD_CONTENT = "suggestion__suggest_clipboard_content"
|
||||
const val USE_PREV_WORDS = "suggestion__use_prev_words"
|
||||
}
|
||||
|
||||
var enabled: Boolean = false
|
||||
get() = prefHelper.getPref(ENABLED, true)
|
||||
private set
|
||||
var showInstead: String = ""
|
||||
get() = prefHelper.getPref(SHOW_INSTEAD, "number_row")
|
||||
private set
|
||||
var usePrevWords: Boolean = false
|
||||
get() = prefHelper.getPref(USE_PREV_WORDS, true)
|
||||
private set
|
||||
var enabled: Boolean
|
||||
get() = prefHelper.getPref(ENABLED, true)
|
||||
set(v) = prefHelper.setPref(ENABLED, v)
|
||||
var showInstead: String
|
||||
get() = prefHelper.getPref(SHOW_INSTEAD, "number_row")
|
||||
set(v) = prefHelper.setPref(SHOW_INSTEAD, v)
|
||||
var suggestClipboardContent: Boolean
|
||||
get() = prefHelper.getPref(SUGGEST_CLIPBOARD_CONTENT, false)
|
||||
set(v) = prefHelper.setPref(SUGGEST_CLIPBOARD_CONTENT, v)
|
||||
var usePrevWords: Boolean
|
||||
get() = prefHelper.getPref(USE_PREV_WORDS, true)
|
||||
set(v) = prefHelper.setPref(USE_PREV_WORDS, v)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -71,7 +71,7 @@ class SubtypeManager(
|
||||
* Loads the [FlorisBoard.ImeConfig] from ime/config.json.
|
||||
*
|
||||
* @param path The path to to IME config file.
|
||||
* @returns The [FlorisBoard.ImeConfig] or a default config.
|
||||
* @return The [FlorisBoard.ImeConfig] or a default config.
|
||||
*/
|
||||
private fun loadImeConfig(path: String): FlorisBoard.ImeConfig {
|
||||
val rawJsonData: String = try {
|
||||
@@ -93,7 +93,7 @@ class SubtypeManager(
|
||||
* Adds a given [subtypeToAdd] to the subtype list, if it does not exist.
|
||||
*
|
||||
* @param subtypeToAdd The subtype which should be added.
|
||||
* @returns True if the subtype was added, false otherwise. A return value of false indicates
|
||||
* @return True if the subtype was added, false otherwise. A return value of false indicates
|
||||
* that the subtype already exists.
|
||||
*/
|
||||
private fun addSubtype(subtypeToAdd: Subtype): Boolean {
|
||||
@@ -112,7 +112,7 @@ class SubtypeManager(
|
||||
*
|
||||
* @param locale The locale of the subtype to be added.
|
||||
* @param layoutName The layout name of the subtype to be added.
|
||||
* @returns True if the subtype was added, false otherwise. A return value of false indicates
|
||||
* @return True if the subtype was added, false otherwise. A return value of false indicates
|
||||
* that the subtype already exists.
|
||||
*/
|
||||
fun addSubtype(locale: Locale, layoutName: String): Boolean {
|
||||
@@ -129,7 +129,7 @@ class SubtypeManager(
|
||||
* Gets the active subtype and returns it. If the activeSubtypeId points to a non-existent
|
||||
* subtype, this method tries to determine a new active subtype.
|
||||
*
|
||||
* @returns The active subtype or null, if the subtype list is empty or no new active subtype
|
||||
* @return The active subtype or null, if the subtype list is empty or no new active subtype
|
||||
* could be determined.
|
||||
*/
|
||||
fun getActiveSubtype(): Subtype? {
|
||||
@@ -152,7 +152,7 @@ class SubtypeManager(
|
||||
* Gets a subtype by the given [id].
|
||||
*
|
||||
* @param id The id of the subtype you want to get.
|
||||
* @returns The subtype or null, if no matching subtype could be found.
|
||||
* @return The subtype or null, if no matching subtype could be found.
|
||||
*/
|
||||
fun getSubtypeById(id: Int): Subtype? {
|
||||
for (subtype in subtypes) {
|
||||
@@ -167,7 +167,7 @@ class SubtypeManager(
|
||||
* Gets the default system subtype for a given [locale].
|
||||
*
|
||||
* @param locale The locale of the default system subtype to get.
|
||||
* @returns The default system locale or null, if no matching default system subtype could be
|
||||
* @return The default system locale or null, if no matching default system subtype could be
|
||||
* found.
|
||||
*/
|
||||
fun getDefaultSubtypeForLocale(locale: Locale): DefaultSubtype? {
|
||||
@@ -220,7 +220,7 @@ class SubtypeManager(
|
||||
/**
|
||||
* Switch to the previous subtype in the subtype list if possible.
|
||||
*
|
||||
* @returns The new active subtype or null if the determination process failed.
|
||||
* @return The new active subtype or null if the determination process failed.
|
||||
*/
|
||||
fun switchToPrevSubtype(): Subtype? {
|
||||
val subtypeList = subtypes
|
||||
@@ -248,7 +248,7 @@ class SubtypeManager(
|
||||
/**
|
||||
* Switch to the next subtype in the subtype list if possible.
|
||||
*
|
||||
* @returns The new active subtype or null if the determination process failed.
|
||||
* @return The new active subtype or null if the determination process failed.
|
||||
*/
|
||||
fun switchToNextSubtype(): Subtype? {
|
||||
val subtypeList = subtypes
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.widget.*
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.EditorInstance
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.InputView
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData
|
||||
@@ -50,6 +51,8 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
FlorisBoard.EventListener {
|
||||
|
||||
private val florisboard = FlorisBoard.getInstance()
|
||||
private val activeEditorInstance: EditorInstance
|
||||
get() = florisboard.activeEditorInstance
|
||||
|
||||
private var activeTab: Tab? = null
|
||||
private var mediaViewFlipper: ViewFlipper? = null
|
||||
@@ -108,15 +111,12 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
})
|
||||
|
||||
for (tab in Tab.values()) {
|
||||
val tabView = createTabViewFor(tab)
|
||||
tabViews[tab] = tabView
|
||||
withContext(Dispatchers.Main) {
|
||||
withContext(Dispatchers.Main) {
|
||||
for (tab in Tab.values()) {
|
||||
val tabView = createTabViewFor(tab)
|
||||
tabViews[tab] = tabView
|
||||
mediaViewFlipper?.addView(tabView)
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
tabLayout?.selectTab(tabLayout?.getTabAt(0))
|
||||
}
|
||||
}
|
||||
@@ -199,18 +199,14 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* Sends a given [emojiKeyData] to the current input editor.
|
||||
*/
|
||||
fun sendEmojiKeyPress(emojiKeyData: EmojiKeyData) {
|
||||
val ic = florisboard.currentInputConnection
|
||||
ic?.finishComposingText()
|
||||
ic?.commitText(emojiKeyData.getCodePointsAsString(), 1)
|
||||
activeEditorInstance.commitText(emojiKeyData.getCodePointsAsString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a given [emoticonKeyData] to the current input editor.
|
||||
*/
|
||||
fun sendEmoticonKeyPress(emoticonKeyData: EmoticonKeyData) {
|
||||
val ic = florisboard.currentInputConnection
|
||||
ic?.finishComposingText()
|
||||
ic?.commitText(emoticonKeyData.icon, 1)
|
||||
activeEditorInstance.commitText(emoticonKeyData.icon)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,7 +61,7 @@ class MediaInputView : LinearLayout, FlorisBoard.EventListener {
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val height = florisboard?.inputView?.desiredInputViewHeight ?: 0
|
||||
val height = florisboard?.inputView?.desiredMediaKeyboardViewHeight ?: 0
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,12 @@ package dev.patrickgold.florisboard.ime.media.emoji
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Handler
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.ScrollView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
@@ -91,7 +89,7 @@ class EmojiKeyView(
|
||||
osHandler = Handler()
|
||||
}
|
||||
osHandler?.postDelayed({
|
||||
(parent.parent as HorizontalScrollView)
|
||||
(parent.parent as ScrollView)
|
||||
.requestDisallowInterceptTouchEvent(true)
|
||||
emojiKeyboardView.isScrollBlocked = true
|
||||
emojiKeyboardView.popupManager.show(this)
|
||||
|
||||
@@ -21,14 +21,13 @@ import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
import android.widget.*
|
||||
import com.google.android.flexbox.FlexDirection
|
||||
import com.google.android.flexbox.FlexWrap
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
@@ -55,7 +54,7 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
private var layouts: Deferred<EmojiLayoutDataMap>
|
||||
private val mainScope = MainScope()
|
||||
private val tabLayout: TabLayout
|
||||
private val uiLayouts = EnumMap<EmojiCategory, HorizontalScrollView>(EmojiCategory::class.java)
|
||||
private val uiLayouts = EnumMap<EmojiCategory, ScrollView>(EmojiCategory::class.java)
|
||||
|
||||
var isScrollBlocked: Boolean = false
|
||||
var popupManager = KeyPopupManager<EmojiKeyboardView, EmojiKeyView>(this)
|
||||
@@ -66,12 +65,15 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
layouts = mainScope.async(Dispatchers.IO) {
|
||||
parseRawEmojiSpecsFile(context, "ime/media/emoji/emoji-test.txt")
|
||||
}
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
orientation = VERTICAL
|
||||
|
||||
emojiViewFlipper = ViewFlipper(context)
|
||||
emojiViewFlipper.layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
emojiViewFlipper.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0).apply {
|
||||
weight = 1.0f
|
||||
}
|
||||
emojiViewFlipper.measureAllChildren = false
|
||||
addView(emojiViewFlipper)
|
||||
|
||||
@@ -117,10 +119,10 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
*/
|
||||
private suspend fun buildLayout() = withContext(Dispatchers.Default) {
|
||||
for (category in EmojiCategory.values()) {
|
||||
val hsv = buildLayoutForCategory(category)
|
||||
uiLayouts[category] = hsv
|
||||
val scrollView = buildLayoutForCategory(category)
|
||||
uiLayouts[category] = scrollView
|
||||
withContext(Dispatchers.Main) {
|
||||
emojiViewFlipper.addView(hsv)
|
||||
emojiViewFlipper.addView(scrollView)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,18 +132,19 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
* context and will not block the main UI thread.
|
||||
*
|
||||
* @param category The category for which a layout should be built.
|
||||
* @return The layout (top-most view is a [HorizontalScrollView]).
|
||||
* @return The layout (top-most view is a [ScrollView]).
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private suspend fun buildLayoutForCategory(
|
||||
category: EmojiCategory
|
||||
): HorizontalScrollView = withContext(Dispatchers.Default) {
|
||||
val hsv = HorizontalScrollView(context)
|
||||
hsv.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
): ScrollView = withContext(Dispatchers.Default) {
|
||||
val scrollView = ScrollView(context)
|
||||
scrollView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
val flexboxLayout = FlexboxLayout(context)
|
||||
flexboxLayout.layoutParams =
|
||||
LayoutParams(LayoutParams.WRAP_CONTENT, emojiKeyHeight * 3)
|
||||
flexboxLayout.flexDirection = FlexDirection.COLUMN
|
||||
LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
flexboxLayout.flexDirection = FlexDirection.ROW
|
||||
flexboxLayout.justifyContent = JustifyContent.SPACE_BETWEEN
|
||||
flexboxLayout.flexWrap = FlexWrap.WRAP
|
||||
for (emojiKeyData in layouts.await()[category].orEmpty()) {
|
||||
val emojiKeyView =
|
||||
@@ -151,11 +154,30 @@ class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
)
|
||||
flexboxLayout.addView(emojiKeyView)
|
||||
}
|
||||
hsv.setOnTouchListener { _, _ ->
|
||||
// Add empty placeholder emojis at the end so the grid view. Below is an illustration how
|
||||
// the UI looks with and without an placeholder (e = emoji):
|
||||
// Without placeholder With placeholder
|
||||
// e e e e e e e e e e e e e e
|
||||
// ............. .............
|
||||
// e e e e e e e e e e e e e e
|
||||
// e e e e e e e e
|
||||
//
|
||||
// Based on this SO's answer idea (by La Nube - Luis R. Díaz Muñiz):
|
||||
// https://stackoverflow.com/a/31478004/6801193
|
||||
//
|
||||
// 24 items are chosen here because that's probably the max items that will be shown per
|
||||
// row, even in landscape mode.
|
||||
for (n in 0 until 24) {
|
||||
val gridPlaceholderView = View(context).apply {
|
||||
layoutParams = LayoutParams(emojiKeyWidth, 0)
|
||||
}
|
||||
flexboxLayout.addView(gridPlaceholderView)
|
||||
}
|
||||
scrollView.setOnTouchListener { _, _ ->
|
||||
return@setOnTouchListener isScrollBlocked
|
||||
}
|
||||
hsv.addView(flexboxLayout)
|
||||
return@withContext hsv
|
||||
scrollView.addView(flexboxLayout)
|
||||
return@withContext scrollView
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ import dev.patrickgold.florisboard.util.*
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class KeyPopupExtendedSingleView(
|
||||
context: Context, var isActive: Boolean = false
|
||||
context: Context, val adjustedIndex: Int, var isActive: Boolean = false
|
||||
) : androidx.appcompat.widget.AppCompatTextView(
|
||||
context, null, 0
|
||||
) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.core.content.ContextCompat.getDrawable
|
||||
import androidx.core.view.get
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import dev.patrickgold.florisboard.R
|
||||
@@ -42,7 +43,6 @@ import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
* @property keyboardView Reference to the keyboard view to which this manager class belongs to.
|
||||
*/
|
||||
class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD) {
|
||||
|
||||
private var anchorLeft: Boolean = false
|
||||
private var anchorRight: Boolean = false
|
||||
private var anchorOffset: Int = 0
|
||||
@@ -101,14 +101,14 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
isInitActive: Boolean = false,
|
||||
isWrapBefore: Boolean = false
|
||||
): KeyPopupExtendedSingleView? {
|
||||
val textView = KeyPopupExtendedSingleView(keyView.context, isInitActive)
|
||||
val lp = FlexboxLayout.LayoutParams(keyPopupWidth, keyView.measuredHeight)
|
||||
val textView = KeyPopupExtendedSingleView(keyView.context, k, isInitActive)
|
||||
val lp = FlexboxLayout.LayoutParams(keyPopupWidth, (keyPopupHeight * 0.4f).toInt())
|
||||
lp.isWrapBefore = isWrapBefore
|
||||
textView.layoutParams = lp
|
||||
textView.gravity = Gravity.CENTER
|
||||
val textSize = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
|
||||
if (keyView is KeyView) {
|
||||
when (keyView.data.popup[k].code) {
|
||||
when (keyView.dataPopupWithHint[k].code) {
|
||||
KeyCode.SETTINGS -> {
|
||||
textView.iconDrawable = getDrawable(
|
||||
keyView.context, R.drawable.ic_settings
|
||||
@@ -129,13 +129,13 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
}
|
||||
else -> {
|
||||
textView.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX, when (keyView.data.popup[k].code) {
|
||||
TypedValue.COMPLEX_UNIT_PX, when (keyView.dataPopupWithHint[k].code) {
|
||||
KeyCode.URI_COMPONENT_TLD,
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT -> textSize * 0.6f
|
||||
else -> textSize
|
||||
}
|
||||
)
|
||||
textView.text = keyView.getComputedLetter(keyView.data.popup[k])
|
||||
textView.text = keyView.getComputedLetter(keyView.dataPopupWithHint[k])
|
||||
}
|
||||
}
|
||||
} else if (keyView is EmojiKeyView) {
|
||||
@@ -171,12 +171,22 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
if (keyboardView is KeyboardView) {
|
||||
when (keyboardView.resources.configuration.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> {
|
||||
keyPopupWidth = (keyboardView.desiredKeyWidth * 0.6f).toInt()
|
||||
keyPopupHeight = (keyboardView.desiredKeyHeight * 3.0f).toInt()
|
||||
if (keyboardView.isSmartbarKeyboardView) {
|
||||
keyPopupWidth = (keyView.measuredWidth * 0.6f).toInt()
|
||||
keyPopupHeight = (keyboardView.desiredKeyHeight * 3.0f * 1.2f).toInt()
|
||||
} else {
|
||||
keyPopupWidth = (keyboardView.desiredKeyWidth * 0.6f).toInt()
|
||||
keyPopupHeight = (keyboardView.desiredKeyHeight * 3.0f).toInt()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
keyPopupWidth = (keyboardView.desiredKeyWidth * 1.1f).toInt()
|
||||
keyPopupHeight = (keyboardView.desiredKeyHeight * 2.5f).toInt()
|
||||
if (keyboardView.isSmartbarKeyboardView) {
|
||||
keyPopupWidth = (keyView.measuredWidth * 1.1f).toInt()
|
||||
keyPopupHeight = (keyboardView.desiredKeyHeight * 2.5f * 1.2f).toInt()
|
||||
} else {
|
||||
keyPopupWidth = (keyboardView.desiredKeyWidth * 1.1f).toInt()
|
||||
keyPopupHeight = (keyboardView.desiredKeyHeight * 2.5f).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (keyboardView is EmojiKeyboardView) {
|
||||
@@ -211,7 +221,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
if (keyView is KeyView) {
|
||||
popupView.findViewById<TextView>(R.id.key_popup_text)?.text = keyView.getComputedLetter()
|
||||
popupView.findViewById<ImageView>(R.id.key_popup_threedots)?.visibility = when {
|
||||
keyView.data.popup.isEmpty() -> View.INVISIBLE
|
||||
keyView.dataPopupWithHint.isEmpty() -> View.INVISIBLE
|
||||
else -> View.VISIBLE
|
||||
}
|
||||
} else if (keyView is EmojiKeyView) {
|
||||
@@ -256,17 +266,12 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
}
|
||||
|
||||
// Anchor left if keyView is in left half of keyboardView, else anchor right
|
||||
if (keyView is KeyView) {
|
||||
anchorLeft = keyView.x < keyboardView.measuredWidth / 2
|
||||
} else if (keyView is EmojiKeyView) {
|
||||
val hsv = (keyView.parent.parent as HorizontalScrollView)
|
||||
anchorLeft = (keyView.x - hsv.scrollX) < keyboardView.measuredWidth / 2
|
||||
}
|
||||
anchorLeft = keyView.x < keyboardView.measuredWidth / 2
|
||||
anchorRight = !anchorLeft
|
||||
|
||||
// Determine key counts for each row
|
||||
val n = when (keyView) {
|
||||
is KeyView -> keyView.data.popup.size
|
||||
is KeyView -> keyView.dataPopupWithHint.size
|
||||
is EmojiKeyView -> keyView.data.popup.size
|
||||
else -> 0
|
||||
}
|
||||
@@ -315,17 +320,29 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
// Build UI
|
||||
popupViewExt.removeAllViews()
|
||||
val indices = when (keyView) {
|
||||
is KeyView -> keyView.data.popup.indices
|
||||
is KeyView -> keyView.dataPopupWithHint.indices
|
||||
is EmojiKeyView -> keyView.data.popup.indices
|
||||
else -> IntRange(0, 0)
|
||||
}
|
||||
var hasShownFirst = false
|
||||
for (k in indices) {
|
||||
val isInitActive =
|
||||
anchorLeft && (k - row1count == anchorOffset) ||
|
||||
anchorRight && (k - row1count == row0count - 1 - anchorOffset)
|
||||
val kk = when (keyView) {
|
||||
is KeyView -> when {
|
||||
isInitActive -> {
|
||||
hasShownFirst = true
|
||||
0
|
||||
}
|
||||
hasShownFirst -> k
|
||||
else -> k + 1
|
||||
}
|
||||
else -> k
|
||||
}
|
||||
popupViewExt.addView(
|
||||
createTextView(
|
||||
keyView, k, isInitActive, (row1count > 0) && (k - row1count == 0)
|
||||
keyView, kk, isInitActive, (row1count > 0) && (k - row1count == 0)
|
||||
)
|
||||
)
|
||||
if (isInitActive) {
|
||||
@@ -337,9 +354,9 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
// Calculate layout params
|
||||
val extWidth = row0count * keyPopupWidth
|
||||
val extHeight = when {
|
||||
row1count > 0 -> keyView.measuredHeight * 2
|
||||
else -> keyView.measuredHeight
|
||||
}
|
||||
row1count > 0 -> keyPopupHeight * 0.4f * 2.0f
|
||||
else -> keyPopupHeight * 0.4f
|
||||
}.toInt()
|
||||
popupViewExt.justifyContent = if (anchorLeft) {
|
||||
JustifyContent.FLEX_START
|
||||
} else {
|
||||
@@ -359,7 +376,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
else -> 0
|
||||
}
|
||||
val y = -keyPopupHeight - when {
|
||||
row1count > 0 -> keyView.measuredHeight
|
||||
row1count > 0 -> (keyPopupHeight * 0.4f).toInt()
|
||||
else -> 0
|
||||
}
|
||||
|
||||
@@ -441,7 +458,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
}
|
||||
|
||||
if (keyView is KeyView) {
|
||||
for (k in keyView.data.popup.indices) {
|
||||
for (k in keyView.dataPopupWithHint.indices) {
|
||||
val view = popupViewExt.getChildAt(k)
|
||||
if (view != null) {
|
||||
val textView = view as KeyPopupExtendedSingleView
|
||||
@@ -471,7 +488,17 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
*/
|
||||
fun getActiveKeyData(keyView: T_KV): KeyData? {
|
||||
return if (keyView is KeyView) {
|
||||
keyView.data.popup.getOrNull(activeExtIndex ?: -1) ?: keyView.data
|
||||
val activeExtIndex = activeExtIndex
|
||||
if (activeExtIndex != null) {
|
||||
val singleView = popupViewExt[activeExtIndex]
|
||||
if (singleView is KeyPopupExtendedSingleView) {
|
||||
keyView.dataPopupWithHint.getOrNull(singleView.adjustedIndex) ?: keyView.data
|
||||
} else {
|
||||
keyView.data
|
||||
}
|
||||
} else {
|
||||
keyView.data
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.text
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.inputmethod.*
|
||||
@@ -27,9 +25,7 @@ import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.InputView
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.core.*
|
||||
import dev.patrickgold.florisboard.ime.text.editing.EditingKeyboardView
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
@@ -59,6 +55,8 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
FlorisBoard.EventListener {
|
||||
|
||||
private val florisboard = FlorisBoard.getInstance()
|
||||
private val activeEditorInstance: EditorInstance
|
||||
get() = florisboard.activeEditorInstance
|
||||
|
||||
private var activeKeyboardMode: KeyboardMode? = null
|
||||
private val keyboardViews = EnumMap<KeyboardMode, KeyboardView>(KeyboardMode::class.java)
|
||||
@@ -68,36 +66,24 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
var textViewGroup: LinearLayout? = null
|
||||
|
||||
var keyVariation: KeyVariation = KeyVariation.NORMAL
|
||||
private val layoutManager = LayoutManager(florisboard)
|
||||
lateinit var smartbarManager: SmartbarManager
|
||||
val layoutManager = LayoutManager(florisboard)
|
||||
private lateinit var smartbarManager: SmartbarManager
|
||||
|
||||
// Caps/Space related properties
|
||||
var caps: Boolean = false
|
||||
private set
|
||||
var capsLock: Boolean = false
|
||||
private set
|
||||
private var cursorCapsMode: CapsMode = CapsMode.NONE
|
||||
private var editorCapsMode: CapsMode = CapsMode.NONE
|
||||
private var hasCapsRecentlyChanged: Boolean = false
|
||||
private var hasSpaceRecentlyPressed: Boolean = false
|
||||
|
||||
// Composing text related properties
|
||||
private var composingText: String? = null
|
||||
private var composingTextStart: Int? = null
|
||||
private var cursorPos: Int = 0
|
||||
private var isComposingEnabled: Boolean = false
|
||||
var isManualSelectionMode: Boolean = false
|
||||
private var isManualSelectionModeLeft: Boolean = false
|
||||
private var isManualSelectionModeRight: Boolean = false
|
||||
val isTextSelected: Boolean
|
||||
get() = selectionEnd - selectionStart != 0
|
||||
private var lastCursorAnchorInfo: CursorAnchorInfo? = null
|
||||
private var selectionStart: Int = 0
|
||||
private val selectionStartMin: Int = 0
|
||||
private var selectionEnd: Int = 0
|
||||
private var selectionEndMax: Int = 0
|
||||
|
||||
companion object {
|
||||
private val TAG: String? = TextInputManager::class.simpleName
|
||||
private var instance: TextInputManager? = null
|
||||
|
||||
@Synchronized
|
||||
@@ -118,19 +104,15 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* background).
|
||||
*/
|
||||
override fun onCreate() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreate()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onCreate()")
|
||||
|
||||
for (mode in KeyboardMode.values()) {
|
||||
if (mode == KeyboardMode.CHARACTERS) {
|
||||
var subtypes = florisboard.subtypeManager.subtypes
|
||||
if (subtypes.isEmpty()) {
|
||||
subtypes = listOf(Subtype.DEFAULT)
|
||||
}
|
||||
for (subtype in subtypes) {
|
||||
layoutManager.preloadComputedLayout(mode, subtype)
|
||||
}
|
||||
} else {
|
||||
layoutManager.preloadComputedLayout(mode, florisboard.activeSubtype)
|
||||
var subtypes = florisboard.subtypeManager.subtypes
|
||||
if (subtypes.isEmpty()) {
|
||||
subtypes = listOf(Subtype.DEFAULT)
|
||||
}
|
||||
for (subtype in subtypes) {
|
||||
for (mode in KeyboardMode.values()) {
|
||||
layoutManager.preloadComputedLayout(mode, subtype)
|
||||
}
|
||||
}
|
||||
smartbarManager = SmartbarManager.getInstance()
|
||||
@@ -149,7 +131,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* Sets up the newly registered input view.
|
||||
*/
|
||||
override fun onRegisterInputView(inputView: InputView) {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onRegisterInputView(inputView)")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onRegisterInputView(inputView)")
|
||||
|
||||
launch(Dispatchers.Default) {
|
||||
textViewGroup = inputView.findViewById(R.id.text_input)
|
||||
@@ -162,7 +144,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
setActiveKeyboardMode(activeKeyboardMode)
|
||||
}
|
||||
for (mode in KeyboardMode.values()) {
|
||||
if (mode != activeKeyboardMode) {
|
||||
if (mode != activeKeyboardMode && mode != KeyboardMode.SMARTBAR_NUMBER_ROW) {
|
||||
addKeyboardView(mode)
|
||||
}
|
||||
}
|
||||
@@ -173,7 +155,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* Cancels all coroutines and cleans up.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onDestroy()")
|
||||
if (BuildConfig.DEBUG) Log.i(TAG, "onDestroy()")
|
||||
|
||||
cancel()
|
||||
osHandler.removeCallbacksAndMessages(null)
|
||||
@@ -183,58 +165,60 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the [activeKeyboardMode], [keyVariation] and [isComposingEnabled] property values
|
||||
* when starting to interact with a input editor. Also resets the composing texts and sets the
|
||||
* initial caps mode accordingly.
|
||||
* Evaluates the [activeKeyboardMode], [keyVariation] and [EditorInstance.isComposingEnabled]
|
||||
* property values when starting to interact with a input editor. Also resets the composing
|
||||
* texts and sets the initial caps mode accordingly.
|
||||
*/
|
||||
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
|
||||
val keyboardMode = when (info) {
|
||||
null -> KeyboardMode.CHARACTERS
|
||||
else -> when (info.inputType and InputType.TYPE_MASK_CLASS) {
|
||||
InputType.TYPE_CLASS_NUMBER -> {
|
||||
keyVariation = KeyVariation.NORMAL
|
||||
KeyboardMode.NUMERIC
|
||||
}
|
||||
InputType.TYPE_CLASS_PHONE -> {
|
||||
keyVariation = KeyVariation.NORMAL
|
||||
KeyboardMode.PHONE
|
||||
}
|
||||
InputType.TYPE_CLASS_TEXT -> {
|
||||
keyVariation = when (info.inputType and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS -> {
|
||||
KeyVariation.EMAIL_ADDRESS
|
||||
}
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD -> {
|
||||
KeyVariation.PASSWORD
|
||||
}
|
||||
InputType.TYPE_TEXT_VARIATION_URI -> {
|
||||
KeyVariation.URI
|
||||
}
|
||||
else -> {
|
||||
KeyVariation.NORMAL
|
||||
}
|
||||
override fun onStartInputView(instance: EditorInstance, restarting: Boolean) {
|
||||
val keyboardMode = when (instance.inputAttributes.type) {
|
||||
InputAttributes.Type.NUMBER -> {
|
||||
keyVariation = KeyVariation.NORMAL
|
||||
KeyboardMode.NUMERIC
|
||||
}
|
||||
InputAttributes.Type.PHONE -> {
|
||||
keyVariation = KeyVariation.NORMAL
|
||||
KeyboardMode.PHONE
|
||||
}
|
||||
InputAttributes.Type.TEXT -> {
|
||||
keyVariation = when (instance.inputAttributes.variation) {
|
||||
InputAttributes.Variation.EMAIL_ADDRESS,
|
||||
InputAttributes.Variation.WEB_EMAIL_ADDRESS -> {
|
||||
KeyVariation.EMAIL_ADDRESS
|
||||
}
|
||||
InputAttributes.Variation.PASSWORD,
|
||||
InputAttributes.Variation.VISIBLE_PASSWORD,
|
||||
InputAttributes.Variation.WEB_PASSWORD -> {
|
||||
KeyVariation.PASSWORD
|
||||
}
|
||||
InputAttributes.Variation.URI -> {
|
||||
KeyVariation.URI
|
||||
}
|
||||
else -> {
|
||||
KeyVariation.NORMAL
|
||||
}
|
||||
KeyboardMode.CHARACTERS
|
||||
}
|
||||
else -> {
|
||||
keyVariation = KeyVariation.NORMAL
|
||||
KeyboardMode.CHARACTERS
|
||||
}
|
||||
KeyboardMode.CHARACTERS
|
||||
}
|
||||
else -> {
|
||||
keyVariation = KeyVariation.NORMAL
|
||||
KeyboardMode.CHARACTERS
|
||||
}
|
||||
}
|
||||
isComposingEnabled = when (keyboardMode) {
|
||||
instance.isComposingEnabled = when (keyboardMode) {
|
||||
KeyboardMode.NUMERIC,
|
||||
KeyboardMode.PHONE,
|
||||
KeyboardMode.PHONE2 -> false
|
||||
else -> keyVariation != KeyVariation.PASSWORD && florisboard.prefs.suggestion.enabled
|
||||
else -> keyVariation != KeyVariation.PASSWORD &&
|
||||
florisboard.prefs.suggestion.enabled// &&
|
||||
//!instance.inputAttributes.flagTextAutoComplete &&
|
||||
//!instance.inputAttributes.flagTextNoSuggestions
|
||||
}
|
||||
if (!florisboard.prefs.correction.rememberCapsLockState) {
|
||||
capsLock = false
|
||||
}
|
||||
updateCapsState()
|
||||
resetComposingText()
|
||||
setActiveKeyboardMode(keyboardMode)
|
||||
smartbarManager.onStartInputView(keyboardMode, isComposingEnabled)
|
||||
smartbarManager.onStartInputView(keyboardMode)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,148 +272,31 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* Main logic point for processing cursor updates as well as parsing the current composing word
|
||||
* and passing this info on to the [SmartbarManager] to turn it into candidate suggestions.
|
||||
*/
|
||||
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
||||
cursorAnchorInfo ?: return
|
||||
lastCursorAnchorInfo = cursorAnchorInfo
|
||||
|
||||
val ic = florisboard.currentInputConnection
|
||||
|
||||
val isNewSelectionInBoundsOfOld =
|
||||
cursorAnchorInfo.selectionStart >= (selectionStart - 1) &&
|
||||
cursorAnchorInfo.selectionStart <= (selectionStart + 1) &&
|
||||
cursorAnchorInfo.selectionEnd >= (selectionEnd - 1) &&
|
||||
cursorAnchorInfo.selectionEnd <= (selectionEnd + 1)
|
||||
selectionStart = cursorAnchorInfo.selectionStart
|
||||
selectionEnd = cursorAnchorInfo.selectionEnd
|
||||
val inputText =
|
||||
(ic?.getExtractedText(ExtractedTextRequest(), 0)?.text ?: "").toString()
|
||||
selectionEndMax = inputText.length
|
||||
// TODO: separate composing text from delete swipe word detection
|
||||
//if (isComposingEnabled) {
|
||||
if (!isTextSelected) {
|
||||
val newCursorPos = cursorAnchorInfo.selectionStart
|
||||
val prevComposingText = (cursorAnchorInfo.composingText ?: "").toString()
|
||||
setComposingTextBasedOnInput(inputText, newCursorPos)
|
||||
if ((newCursorPos == cursorPos) && (composingText == prevComposingText)) {
|
||||
// Ignore this, as nothing has changed
|
||||
} else {
|
||||
cursorPos = newCursorPos
|
||||
if (composingText != null && composingTextStart != null) {
|
||||
ic?.setComposingRegion(
|
||||
composingTextStart!!,
|
||||
composingTextStart!! + composingText!!.length
|
||||
)
|
||||
} else {
|
||||
resetComposingText()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resetComposingText()
|
||||
}
|
||||
smartbarManager.generateCandidatesFromComposing(composingText)
|
||||
//}
|
||||
if (!isNewSelectionInBoundsOfOld) {
|
||||
override fun onUpdateSelection() {
|
||||
if (!activeEditorInstance.isNewSelectionInBoundsOfOld) {
|
||||
isManualSelectionMode = false
|
||||
isManualSelectionModeLeft = false
|
||||
isManualSelectionModeRight = false
|
||||
}
|
||||
updateCapsState()
|
||||
smartbarManager.onUpdateCursorAnchorInfo(cursorAnchorInfo)
|
||||
smartbarManager.onUpdateSelection()
|
||||
}
|
||||
|
||||
override fun onPrimaryClipChanged() {
|
||||
smartbarManager.onPrimaryClipChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the [composingText] and [composingTextStart] properties. Does NOT sync with
|
||||
* [SmartbarManager]!
|
||||
*
|
||||
* @param notifyInputConnection If the current input connection should be notified.
|
||||
*/
|
||||
private fun resetComposingText(notifyInputConnection: Boolean = true) {
|
||||
if (notifyInputConnection) {
|
||||
val ic = florisboard.currentInputConnection
|
||||
ic?.finishComposingText()
|
||||
}
|
||||
composingText = null
|
||||
composingTextStart = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse the [composingText] from a given [inputCursorPos] within [inputText].
|
||||
* Sets both [composingText] and [composingTextStart] to null if it fails, else to its
|
||||
* parsed values.
|
||||
*
|
||||
* @param inputText The input text to search in.
|
||||
* @param inputCursorPos The position where to search in [inputText].
|
||||
*/
|
||||
private fun setComposingTextBasedOnInput(inputText: String, inputCursorPos: Int) {
|
||||
val words = inputText.split("[^\\p{L}]".toRegex())
|
||||
var pos = 0
|
||||
resetComposingText(false)
|
||||
for (word in words) {
|
||||
if (inputCursorPos >= pos && inputCursorPos <= pos + word.length && word.isNotEmpty()) {
|
||||
composingText = word
|
||||
composingTextStart = pos
|
||||
break
|
||||
} else {
|
||||
pos += word.length + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should primarily pe used by [SmartbarManager.candidateViewOnClickListener] to commit
|
||||
* a candidate if a user has pressed on it.
|
||||
*/
|
||||
fun commitCandidate(candidateText: String) {
|
||||
val ic = florisboard.currentInputConnection
|
||||
ic?.setComposingText(candidateText, 1)
|
||||
ic?.finishComposingText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the [CapsMode] out of the given [flags].
|
||||
*
|
||||
* @param flags The input flags.
|
||||
* @return A [CapsMode] value.
|
||||
*/
|
||||
private fun parseCapsModeFromFlags(flags: Int): CapsMode {
|
||||
return when {
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS > 0 -> {
|
||||
CapsMode.ALL
|
||||
}
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES > 0 -> {
|
||||
CapsMode.SENTENCES
|
||||
}
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_WORDS > 0 -> {
|
||||
CapsMode.WORDS
|
||||
}
|
||||
else -> {
|
||||
CapsMode.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the current cursor caps mode from the current input connection.
|
||||
*
|
||||
* @return The [CapsMode] according to the returned flags by the current input connection.
|
||||
*/
|
||||
private fun fetchCurrentCursorCapsMode(): CapsMode {
|
||||
val ic = florisboard.currentInputConnection
|
||||
val info = florisboard.currentInputEditorInfo
|
||||
val capsFlags = ic?.getCursorCapsMode(info.inputType) ?: 0
|
||||
return parseCapsModeFromFlags(capsFlags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current caps state according to the [cursorCapsMode], while respecting
|
||||
* [capsLock] property.
|
||||
* Updates the current caps state according to the [EditorInstance.cursorCapsMode], while
|
||||
* respecting [capsLock] property and the correction.autoCapitalization preference.
|
||||
*/
|
||||
private fun updateCapsState() {
|
||||
cursorCapsMode = fetchCurrentCursorCapsMode()
|
||||
editorCapsMode = parseCapsModeFromFlags(florisboard.currentInputEditorInfo.inputType)
|
||||
if (!capsLock) {
|
||||
caps = cursorCapsMode != CapsMode.NONE
|
||||
keyboardViews[activeKeyboardMode]?.invalidateAllKeys()
|
||||
caps = florisboard.prefs.correction.autoCapitalization &&
|
||||
activeEditorInstance.cursorCapsMode != InputAttributes.CapsMode.NONE
|
||||
launch(Dispatchers.Main) {
|
||||
keyboardViews[activeKeyboardMode]?.invalidateAllKeys()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,90 +316,43 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a given [keyCode] as a [KeyEvent.ACTION_DOWN].
|
||||
*
|
||||
* @param ic The input connection on which this operation should be performed.
|
||||
* @param keyCode The key code to send, use a key code defined in Android's [KeyEvent], not in
|
||||
* [KeyCode] or this call may send a weird character, as this key codes do not match!!
|
||||
*/
|
||||
private fun sendSystemKeyEvent(ic: InputConnection?, keyCode: Int) {
|
||||
ic?.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a given [keyCode] as a [KeyEvent.ACTION_DOWN] with ALT pressed.
|
||||
*
|
||||
* @param ic The input connection on which this operation should be performed.
|
||||
* @param keyCode The key code to send, use a key code defined in Android's [KeyEvent], not in
|
||||
* [KeyCode] or this call may send a weird character, as this key codes do not match!!
|
||||
*/
|
||||
private fun sendSystemKeyEventAlt(ic: InputConnection?, keyCode: Int) {
|
||||
ic?.sendKeyEvent(
|
||||
KeyEvent(
|
||||
0,
|
||||
1,
|
||||
KeyEvent.ACTION_DOWN, keyCode,
|
||||
0,
|
||||
KeyEvent.META_ALT_LEFT_ON
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.DELETE] event.
|
||||
*/
|
||||
private fun handleDelete() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
ic?.beginBatchEdit()
|
||||
resetComposingText()
|
||||
isManualSelectionMode = false
|
||||
isManualSelectionModeLeft = false
|
||||
isManualSelectionModeRight = false
|
||||
sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DEL)
|
||||
ic?.endBatchEdit()
|
||||
activeEditorInstance.deleteBackwards()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.DELETE_WORD] event.
|
||||
*/
|
||||
private fun handleDeleteWord() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
ic?.beginBatchEdit()
|
||||
isManualSelectionMode = false
|
||||
isManualSelectionModeLeft = false
|
||||
isManualSelectionModeRight = false
|
||||
ic?.setComposingText("", 1)
|
||||
ic?.finishComposingText()
|
||||
if (ic?.getTextBeforeCursor(1, 0)?.length ?: 0 > 0) {
|
||||
ic?.deleteSurroundingText(1, 0)
|
||||
}
|
||||
composingText = null
|
||||
composingTextStart = null
|
||||
ic?.endBatchEdit()
|
||||
activeEditorInstance.deleteWordsBeforeCursor(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.ENTER] event.
|
||||
*/
|
||||
private fun handleEnter() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
resetComposingText()
|
||||
val action = florisboard.currentInputEditorInfo?.imeOptions ?: 0
|
||||
val actionMasked = action and EditorInfo.IME_MASK_ACTION
|
||||
if (action and EditorInfo.IME_FLAG_NO_ENTER_ACTION > 0) {
|
||||
sendSystemKeyEvent(ic, KeyEvent.KEYCODE_ENTER)
|
||||
if (activeEditorInstance.imeOptions.flagNoEnterAction) {
|
||||
activeEditorInstance.performEnter()
|
||||
} else {
|
||||
when (actionMasked) {
|
||||
EditorInfo.IME_ACTION_DONE,
|
||||
EditorInfo.IME_ACTION_GO,
|
||||
EditorInfo.IME_ACTION_NEXT,
|
||||
EditorInfo.IME_ACTION_PREVIOUS,
|
||||
EditorInfo.IME_ACTION_SEARCH,
|
||||
EditorInfo.IME_ACTION_SEND -> {
|
||||
ic?.performEditorAction(actionMasked)
|
||||
when (activeEditorInstance.imeOptions.action) {
|
||||
ImeOptions.Action.DONE,
|
||||
ImeOptions.Action.GO,
|
||||
ImeOptions.Action.NEXT,
|
||||
ImeOptions.Action.PREVIOUS,
|
||||
ImeOptions.Action.SEARCH,
|
||||
ImeOptions.Action.SEND -> {
|
||||
activeEditorInstance.performEnterAction(activeEditorInstance.imeOptions.action)
|
||||
}
|
||||
else -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_ENTER)
|
||||
else -> activeEditorInstance.performEnter()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,14 +382,13 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* enabled by the user.
|
||||
*/
|
||||
private fun handleSpace() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
if (florisboard.prefs.correction.doubleSpacePeriod) {
|
||||
if (hasSpaceRecentlyPressed) {
|
||||
osHandler.removeCallbacksAndMessages(null)
|
||||
val text = ic?.getTextBeforeCursor(2, 0) ?: ""
|
||||
val text = activeEditorInstance.getTextBeforeCursor(2)
|
||||
if (text.length == 2 && !text.matches("""[.!?‽\s][\s]""".toRegex())) {
|
||||
ic?.deleteSurroundingText(1, 0)
|
||||
ic?.commitText(".", 1)
|
||||
activeEditorInstance.deleteBackwards()
|
||||
activeEditorInstance.commitText(".")
|
||||
}
|
||||
hasSpaceRecentlyPressed = false
|
||||
} else {
|
||||
@@ -579,107 +398,107 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
ic?.commitText(KeyCode.SPACE.toChar().toString(), 1)
|
||||
activeEditorInstance.commitText(KeyCode.SPACE.toChar().toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles [KeyCode] arrow and move events, behaves differently depending on text selection.
|
||||
*/
|
||||
private fun handleArrow(code: Int) {
|
||||
val ic = florisboard.currentInputConnection
|
||||
resetComposingText()
|
||||
if (isTextSelected && isManualSelectionMode) {
|
||||
private fun handleArrow(code: Int) = activeEditorInstance.apply {
|
||||
val selectionStartMin = 0
|
||||
val selectionEndMax = cachedText.length
|
||||
if (selection.isSelectionMode && isManualSelectionMode) {
|
||||
// Text is selected and it is manual selection -> Expand selection depending on started
|
||||
// direction.
|
||||
when (code) {
|
||||
KeyCode.ARROW_DOWN -> {}
|
||||
KeyCode.ARROW_LEFT -> {
|
||||
if (isManualSelectionModeLeft) {
|
||||
ic?.setSelection(
|
||||
(selectionStart - 1).coerceAtLeast(selectionStartMin),
|
||||
selectionEnd
|
||||
setSelection(
|
||||
(selection.start - 1).coerceAtLeast(selectionStartMin),
|
||||
selection.end
|
||||
)
|
||||
} else {
|
||||
ic?.setSelection(selectionStart, selectionEnd - 1)
|
||||
setSelection(selection.start, selection.end - 1)
|
||||
}
|
||||
}
|
||||
KeyCode.ARROW_RIGHT -> {
|
||||
if (isManualSelectionModeRight) {
|
||||
ic?.setSelection(
|
||||
selectionStart,
|
||||
(selectionEnd + 1).coerceAtMost(selectionEndMax)
|
||||
setSelection(
|
||||
selection.start,
|
||||
(selection.end + 1).coerceAtMost(selectionEndMax)
|
||||
)
|
||||
} else {
|
||||
ic?.setSelection(selectionStart + 1, selectionEnd)
|
||||
setSelection(selection.start + 1, selection.end)
|
||||
}
|
||||
}
|
||||
KeyCode.ARROW_UP -> {}
|
||||
KeyCode.MOVE_HOME -> {
|
||||
if (isManualSelectionModeLeft) {
|
||||
ic?.setSelection(selectionStartMin, selectionEnd)
|
||||
setSelection(selectionStartMin, selection.end)
|
||||
} else {
|
||||
ic?.setSelection(selectionStartMin, selectionStart)
|
||||
setSelection(selectionStartMin, selection.start)
|
||||
}
|
||||
}
|
||||
KeyCode.MOVE_END -> {
|
||||
if (isManualSelectionModeRight) {
|
||||
ic?.setSelection(selectionStart, selectionEndMax)
|
||||
setSelection(selection.start, selectionEndMax)
|
||||
} else {
|
||||
ic?.setSelection(selectionEnd, selectionEndMax)
|
||||
setSelection(selection.end, selectionEndMax)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isTextSelected && !isManualSelectionMode) {
|
||||
} else if (selection.isSelectionMode && !isManualSelectionMode) {
|
||||
// Text is selected but no manual selection mode -> arrows behave as if selection was
|
||||
// started in manual left mode
|
||||
when (code) {
|
||||
KeyCode.ARROW_DOWN -> {}
|
||||
KeyCode.ARROW_LEFT -> {
|
||||
ic?.setSelection(selectionStart, selectionEnd - 1)
|
||||
setSelection(selection.start, selection.end - 1)
|
||||
}
|
||||
KeyCode.ARROW_RIGHT -> {
|
||||
ic?.setSelection(
|
||||
selectionStart,
|
||||
(selectionEnd + 1).coerceAtMost(selectionEndMax)
|
||||
setSelection(
|
||||
selection.start,
|
||||
(selection.end + 1).coerceAtMost(selectionEndMax)
|
||||
)
|
||||
}
|
||||
KeyCode.ARROW_UP -> {}
|
||||
KeyCode.MOVE_HOME -> {
|
||||
ic?.setSelection(selectionStartMin, selectionStart)
|
||||
setSelection(selectionStartMin, selection.start)
|
||||
}
|
||||
KeyCode.MOVE_END -> {
|
||||
ic?.setSelection(selectionStart, selectionEndMax)
|
||||
setSelection(selection.start, selectionEndMax)
|
||||
}
|
||||
}
|
||||
} else if (!isTextSelected && isManualSelectionMode) {
|
||||
} else if (!selection.isSelectionMode && isManualSelectionMode) {
|
||||
// No text is selected but manual selection mode is active, user wants to start a new
|
||||
// selection. Must set manual selection direction.
|
||||
when (code) {
|
||||
KeyCode.ARROW_DOWN -> {}
|
||||
KeyCode.ARROW_LEFT -> {
|
||||
ic?.setSelection(
|
||||
(selectionStart - 1).coerceAtLeast(selectionStartMin),
|
||||
selectionStart
|
||||
setSelection(
|
||||
(selection.start - 1).coerceAtLeast(selectionStartMin),
|
||||
selection.start
|
||||
)
|
||||
isManualSelectionModeLeft = true
|
||||
isManualSelectionModeRight = false
|
||||
}
|
||||
KeyCode.ARROW_RIGHT -> {
|
||||
ic?.setSelection(
|
||||
selectionEnd,
|
||||
(selectionEnd + 1).coerceAtMost(selectionEndMax)
|
||||
setSelection(
|
||||
selection.end,
|
||||
(selection.end + 1).coerceAtMost(selectionEndMax)
|
||||
)
|
||||
isManualSelectionModeLeft = false
|
||||
isManualSelectionModeRight = true
|
||||
}
|
||||
KeyCode.ARROW_UP -> {}
|
||||
KeyCode.MOVE_HOME -> {
|
||||
ic?.setSelection(selectionStartMin, selectionStart)
|
||||
setSelection(selectionStartMin, selection.start)
|
||||
isManualSelectionModeLeft = true
|
||||
isManualSelectionModeRight = false
|
||||
}
|
||||
KeyCode.MOVE_END -> {
|
||||
ic?.setSelection(selectionEnd, selectionEndMax)
|
||||
setSelection(selection.end, selectionEndMax)
|
||||
isManualSelectionModeLeft = false
|
||||
isManualSelectionModeRight = true
|
||||
}
|
||||
@@ -687,87 +506,39 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
} else {
|
||||
// No selection and no manual selection mode -> move cursor around
|
||||
when (code) {
|
||||
KeyCode.ARROW_DOWN -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_DOWN)
|
||||
KeyCode.ARROW_LEFT -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_LEFT)
|
||||
KeyCode.ARROW_RIGHT -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_RIGHT)
|
||||
KeyCode.ARROW_UP -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_UP)
|
||||
KeyCode.MOVE_HOME -> sendSystemKeyEventAlt(ic, KeyEvent.KEYCODE_DPAD_UP)
|
||||
KeyCode.MOVE_END -> sendSystemKeyEventAlt(ic, KeyEvent.KEYCODE_DPAD_DOWN)
|
||||
KeyCode.ARROW_DOWN -> activeEditorInstance.sendSystemKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN)
|
||||
KeyCode.ARROW_LEFT -> activeEditorInstance.sendSystemKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT)
|
||||
KeyCode.ARROW_RIGHT -> activeEditorInstance.sendSystemKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
|
||||
KeyCode.ARROW_UP -> activeEditorInstance.sendSystemKeyEvent(KeyEvent.KEYCODE_DPAD_UP)
|
||||
KeyCode.MOVE_HOME -> activeEditorInstance.sendSystemKeyEventAlt(KeyEvent.KEYCODE_DPAD_UP)
|
||||
KeyCode.MOVE_END -> activeEditorInstance.sendSystemKeyEventAlt(KeyEvent.KEYCODE_DPAD_DOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.CLIPBOARD_CUT] event.
|
||||
* TODO: handle other data than text too, e.g. Uri, Intent, ...
|
||||
*/
|
||||
private fun handleClipboardCut() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
val selectedText = ic?.getSelectedText(0)
|
||||
if (selectedText != null) {
|
||||
florisboard.clipboardManager
|
||||
?.setPrimaryClip(ClipData.newPlainText(selectedText, selectedText))
|
||||
}
|
||||
resetComposingText()
|
||||
ic?.commitText("", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.CLIPBOARD_COPY] event.
|
||||
* TODO: handle other data than text too, e.g. Uri, Intent, ...
|
||||
*/
|
||||
private fun handleClipboardCopy() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
val selectedText = ic?.getSelectedText(0)
|
||||
if (selectedText != null) {
|
||||
florisboard.clipboardManager
|
||||
?.setPrimaryClip(ClipData.newPlainText(selectedText, selectedText))
|
||||
}
|
||||
resetComposingText()
|
||||
ic?.setSelection(selectionEnd, selectionEnd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.CLIPBOARD_PASTE] event.
|
||||
* TODO: handle other data than text too, e.g. Uri, Intent, ...
|
||||
*/
|
||||
private fun handleClipboardPaste() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
val item = florisboard.clipboardManager?.primaryClip?.getItemAt(0)
|
||||
val pasteText = item?.text
|
||||
if (pasteText != null) {
|
||||
resetComposingText()
|
||||
ic?.commitText(pasteText, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.CLIPBOARD_SELECT] event.
|
||||
*/
|
||||
private fun handleClipboardSelect() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
resetComposingText()
|
||||
if (isTextSelected) {
|
||||
private fun handleClipboardSelect() = activeEditorInstance.apply {
|
||||
if (selection.isSelectionMode) {
|
||||
if (isManualSelectionMode && isManualSelectionModeLeft) {
|
||||
ic?.setSelection(selectionStart, selectionStart)
|
||||
setSelection(selection.start, selection.start)
|
||||
} else {
|
||||
ic?.setSelection(selectionEnd, selectionEnd)
|
||||
setSelection(selection.end, selection.end)
|
||||
}
|
||||
isManualSelectionMode = false
|
||||
} else {
|
||||
isManualSelectionMode = !isManualSelectionMode
|
||||
// Must recall to update UI properly
|
||||
florisboard.onUpdateCursorAnchorInfo(lastCursorAnchorInfo)
|
||||
// Must call to update UI properly
|
||||
editingKeyboardView?.onUpdateSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.CLIPBOARD_SELECT_ALL] event.
|
||||
*/
|
||||
private fun handleClipboardSelectAll() {
|
||||
val ic = florisboard.currentInputConnection
|
||||
resetComposingText()
|
||||
ic?.setSelection(selectionStartMin, selectionEndMax)
|
||||
activeEditorInstance.setSelection(0, activeEditorInstance.cachedText.length)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -778,8 +549,6 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* @param keyData The [KeyData] object which should be sent.
|
||||
*/
|
||||
fun sendKeyPress(keyData: KeyData) {
|
||||
val ic = florisboard.currentInputConnection
|
||||
|
||||
when (keyData.code) {
|
||||
KeyCode.ARROW_DOWN,
|
||||
KeyCode.ARROW_LEFT,
|
||||
@@ -787,13 +556,22 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
KeyCode.ARROW_UP,
|
||||
KeyCode.MOVE_HOME,
|
||||
KeyCode.MOVE_END -> handleArrow(keyData.code)
|
||||
KeyCode.CLIPBOARD_CUT -> handleClipboardCut()
|
||||
KeyCode.CLIPBOARD_COPY -> handleClipboardCopy()
|
||||
KeyCode.CLIPBOARD_PASTE -> handleClipboardPaste()
|
||||
KeyCode.CLIPBOARD_CUT -> activeEditorInstance.performClipboardCut()
|
||||
KeyCode.CLIPBOARD_COPY -> activeEditorInstance.performClipboardCopy()
|
||||
KeyCode.CLIPBOARD_PASTE -> {
|
||||
activeEditorInstance.performClipboardPaste()
|
||||
smartbarManager.resetClipboardSuggestion()
|
||||
}
|
||||
KeyCode.CLIPBOARD_SELECT -> handleClipboardSelect()
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> handleClipboardSelectAll()
|
||||
KeyCode.DELETE -> handleDelete()
|
||||
KeyCode.ENTER -> handleEnter()
|
||||
KeyCode.DELETE -> {
|
||||
handleDelete()
|
||||
smartbarManager.resetClipboardSuggestion()
|
||||
}
|
||||
KeyCode.ENTER -> {
|
||||
handleEnter()
|
||||
smartbarManager.resetClipboardSuggestion()
|
||||
}
|
||||
KeyCode.LANGUAGE_SWITCH -> florisboard.switchToNextSubtype()
|
||||
KeyCode.SETTINGS -> florisboard.launchSettings()
|
||||
KeyCode.SHIFT -> handleShift()
|
||||
@@ -813,8 +591,6 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
KeyCode.VIEW_SYMBOLS -> setActiveKeyboardMode(KeyboardMode.SYMBOLS)
|
||||
KeyCode.VIEW_SYMBOLS2 -> setActiveKeyboardMode(KeyboardMode.SYMBOLS2)
|
||||
else -> {
|
||||
ic?.beginBatchEdit()
|
||||
resetComposingText()
|
||||
when (activeKeyboardMode) {
|
||||
KeyboardMode.NUMERIC,
|
||||
KeyboardMode.NUMERIC_ADVANCED,
|
||||
@@ -823,13 +599,13 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
KeyType.CHARACTER,
|
||||
KeyType.NUMERIC -> {
|
||||
val text = keyData.code.toChar().toString()
|
||||
ic?.commitText(text, 1)
|
||||
activeEditorInstance.commitText(text)
|
||||
}
|
||||
else -> when (keyData.code) {
|
||||
KeyCode.PHONE_PAUSE,
|
||||
KeyCode.PHONE_WAIT -> {
|
||||
val text = keyData.code.toChar().toString()
|
||||
ic?.commitText(text, 1)
|
||||
activeEditorInstance.commitText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -841,7 +617,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
true -> keyData.label.toUpperCase(Locale.getDefault())
|
||||
false -> keyData.label.toLowerCase(Locale.getDefault())
|
||||
}
|
||||
ic?.commitText(tld, 1)
|
||||
activeEditorInstance.commitText(tld)
|
||||
}
|
||||
else -> {
|
||||
var text = keyData.code.toChar().toString()
|
||||
@@ -849,26 +625,20 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
true -> text.toUpperCase(Locale.getDefault())
|
||||
false -> text.toLowerCase(Locale.getDefault())
|
||||
}
|
||||
ic?.commitText(text, 1)
|
||||
activeEditorInstance.commitText(text)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.e(
|
||||
this::class.simpleName,
|
||||
"sendKeyPress(keyData): Received unknown key: $keyData"
|
||||
)
|
||||
Log.e(TAG,"sendKeyPress(keyData): Received unknown key: $keyData")
|
||||
}
|
||||
}
|
||||
}
|
||||
ic?.endBatchEdit()
|
||||
smartbarManager.resetClipboardSuggestion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class CapsMode {
|
||||
ALL,
|
||||
NONE,
|
||||
SENTENCES,
|
||||
WORDS;
|
||||
if (keyData.code != KeyCode.SHIFT && !capsLock) {
|
||||
updateCapsState()
|
||||
}
|
||||
smartbarManager.updateActiveContainerVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.inputmethod.CursorAnchorInfo
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
@@ -60,8 +59,8 @@ class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener {
|
||||
pasteKey = findViewById(R.id.clipboard_paste)
|
||||
}
|
||||
|
||||
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
||||
val isSelectionActive = florisboard?.textInputManager?.isTextSelected ?: false
|
||||
override fun onUpdateSelection() {
|
||||
val isSelectionActive = florisboard?.activeEditorInstance?.selection?.isSelectionMode ?: false
|
||||
val isSelectionMode = florisboard?.textInputManager?.isManualSelectionMode ?: false
|
||||
arrowUpKey?.isEnabled = !(isSelectionActive || isSelectionMode)
|
||||
arrowDownKey?.isEnabled = !(isSelectionActive || isSelectionMode)
|
||||
|
||||
@@ -23,7 +23,9 @@ import java.util.*
|
||||
*/
|
||||
enum class SwipeAction {
|
||||
NO_ACTION,
|
||||
DELETE_CHARACTERS_PRECISELY,
|
||||
DELETE_WORD,
|
||||
DELETE_WORDS_PRECISELY,
|
||||
HIDE_KEYBOARD,
|
||||
MOVE_CURSOR_UP,
|
||||
MOVE_CURSOR_DOWN,
|
||||
|
||||
@@ -23,7 +23,6 @@ import dev.patrickgold.florisboard.R
|
||||
import java.lang.Exception
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper class which holds all enums, interfaces and classes for detecting a swipe gesture.
|
||||
*/
|
||||
@@ -70,14 +69,15 @@ abstract class SwipeGesture {
|
||||
val diffX = event.x - firstEvent.x
|
||||
val diffY = event.y - firstEvent.y
|
||||
val distanceThresholdNV = numericValue(distanceThreshold)
|
||||
val velocityThresholdNV = numericValue(velocityThreshold)
|
||||
/*val velocityThresholdNV = numericValue(velocityThreshold)
|
||||
val velocity =
|
||||
((convertPixelsToDp(
|
||||
sqrt(diffX.pow(2) + diffY.pow(2)),
|
||||
context
|
||||
) / event.downTime) * 10.0f.pow(8)).toInt()
|
||||
) / event.downTime) * 10.0f.pow(8)).toInt()*/
|
||||
clearEventList()
|
||||
return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) && velocity >= velocityThresholdNV) {
|
||||
// return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) && velocity >= velocityThresholdNV) {
|
||||
return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV)) {
|
||||
val direction = detectDirection(diffX.toDouble(), diffY.toDouble())
|
||||
listener.onSwipe(direction, Type.TOUCH_UP)
|
||||
} else {
|
||||
@@ -173,20 +173,6 @@ abstract class SwipeGesture {
|
||||
VelocityThreshold.VERY_FAST -> context.resources.getInteger(R.integer.gesture_velocity_threshold_very_fast)
|
||||
}.toDouble()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method converts device specific pixels to density independent pixels.
|
||||
*
|
||||
* Source: https://stackoverflow.com/a/9563438/6801193 (by Muhammad Nabeel Arif)
|
||||
*
|
||||
* @param px A value in px (pixels) unit. Which we need to convert into db
|
||||
* @param context Context to get resources and device specific display metrics
|
||||
* @return A float value to represent dp equivalent to px value
|
||||
*/
|
||||
private fun convertPixelsToDp(px: Float, context: Context): Float {
|
||||
return px / (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.text.key
|
||||
|
||||
object KeyCode {
|
||||
|
||||
@@ -1,8 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.text.key
|
||||
|
||||
/**
|
||||
* Data class which describes a single key and its variants.
|
||||
*
|
||||
* @property code The UTF-8 encoded code of the character. The code defined here is used as the
|
||||
* data passed to the system.
|
||||
* @property label The string used to display the key in the UI. Is not used for the actual data
|
||||
* passed to the system. Should normally be the exact same as the [code]. Defaults to an empty
|
||||
* string.
|
||||
* @property hintedNumber The hinted number which will be dynamically inserted into the long-press
|
||||
* [popup]. Leave null to disable the hinted popup for this key. The visibility of the hinted number
|
||||
* is controlled by the preferences. Defaults to null.
|
||||
* @property hintedSymbol The hinted symbol which will be dynamically inserted into the long-press
|
||||
* [popup]. Leave null to disable the hinted popup for this key. The visibility of the hinted symbol
|
||||
* is controlled by the preferences. Defaults to null.
|
||||
* @property popup List of keys which will be accessible while long pressing the key. Defaults to
|
||||
* an empty list (no extended popup).
|
||||
* @property type The type of the key. Some actions require both [code] and [type] to match in order
|
||||
* to be successfully executed. Defaults to [KeyType.CHARACTER].
|
||||
* @property variation Controls if the key should only be shown in some contexts (e.g.: url input)
|
||||
* or if the key should always be visible. Defaults to [KeyVariation.ALL].
|
||||
*/
|
||||
data class KeyData(
|
||||
var code: Int,
|
||||
var label: String = "",
|
||||
var hintedNumber: KeyData? = null,
|
||||
var hintedSymbol: KeyData? = null,
|
||||
var popup: MutableList<KeyData> = mutableListOf(),
|
||||
var type: KeyType = KeyType.CHARACTER,
|
||||
var variation: KeyVariation = KeyVariation.ALL
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.text.key
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.text.key
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package dev.patrickgold.florisboard.ime.text.key
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Handler
|
||||
@@ -25,7 +24,6 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.content.ContextCompat.getDrawable
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
@@ -33,7 +31,9 @@ import androidx.core.view.children
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.ImeOptions
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
@@ -53,7 +53,7 @@ class KeyView(
|
||||
private val keyboardView: KeyboardView,
|
||||
val data: KeyData
|
||||
) : View(keyboardView.context), SwipeGesture.Listener {
|
||||
|
||||
val dataPopupWithHint: MutableList<KeyData>
|
||||
private var isKeyPressed: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -64,6 +64,8 @@ class KeyView(
|
||||
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
|
||||
private var shouldBlockNextKeyCode: Boolean = false
|
||||
|
||||
private var desiredWidth: Int = 0
|
||||
private var desiredHeight: Int = 0
|
||||
private var drawable: Drawable? = null
|
||||
private var drawableColor: Int = 0
|
||||
private var drawablePadding: Int = 0
|
||||
@@ -72,11 +74,22 @@ class KeyView(
|
||||
alpha = 255
|
||||
color = 0
|
||||
isAntiAlias = true
|
||||
isFakeBoldText = true
|
||||
isFakeBoldText = false
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = resources.getDimension(R.dimen.key_textSize)
|
||||
typeface = Typeface.DEFAULT
|
||||
}
|
||||
private var hintedLabel: String? = null
|
||||
private var hintedLabelPaint: Paint = Paint().apply {
|
||||
alpha = 120
|
||||
color = 0
|
||||
isAntiAlias = true
|
||||
isFakeBoldText = false
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = resources.getDimension(R.dimen.key_textHintSize)
|
||||
typeface = Typeface.DEFAULT
|
||||
}
|
||||
private val tempRect: Rect = Rect()
|
||||
|
||||
var florisboard: FlorisBoard? = null
|
||||
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
|
||||
@@ -126,6 +139,23 @@ class KeyView(
|
||||
background = getDrawable(context, R.drawable.shape_rect_rounded)
|
||||
elevation = 4.0f
|
||||
|
||||
var hintKeyData: KeyData? = null
|
||||
val hintedNumber = data.hintedNumber
|
||||
if (prefs.keyboard.hintedNumberRow && hintedNumber != null) {
|
||||
hintKeyData = hintedNumber
|
||||
}
|
||||
val hintedSymbol = data.hintedSymbol
|
||||
if (prefs.keyboard.hintedSymbols && hintedSymbol != null) {
|
||||
hintKeyData = hintedSymbol
|
||||
}
|
||||
dataPopupWithHint = if (hintKeyData == null) {
|
||||
data.popup.toMutableList()
|
||||
} else {
|
||||
val popupList = data.popup.toMutableList()
|
||||
popupList.add(hintKeyData)
|
||||
popupList
|
||||
}
|
||||
|
||||
updateKeyPressedBackground()
|
||||
}
|
||||
|
||||
@@ -174,7 +204,7 @@ class KeyView(
|
||||
* go look at which child the pointer is actually above.
|
||||
*/
|
||||
fun onFlorisTouchEvent(event: MotionEvent?): Boolean {
|
||||
event ?: return false
|
||||
if (event == null || !isEnabled) return false
|
||||
if (swipeGestureDetector.onTouchEvent(event)) {
|
||||
isKeyPressed = false
|
||||
osHandler?.removeCallbacksAndMessages(null)
|
||||
@@ -211,7 +241,7 @@ class KeyView(
|
||||
osHandler = Handler()
|
||||
}
|
||||
osHandler?.postDelayed({
|
||||
if (data.popup.isNotEmpty()) {
|
||||
if (dataPopupWithHint.isNotEmpty()) {
|
||||
keyboardView.popupManager.extend(this)
|
||||
}
|
||||
if (data.code == KeyCode.SPACE) {
|
||||
@@ -270,17 +300,57 @@ class KeyView(
|
||||
*/
|
||||
override fun onSwipe(direction: SwipeGesture.Direction, type: SwipeGesture.Type): Boolean {
|
||||
return when (data.code) {
|
||||
KeyCode.DELETE -> when (type) {
|
||||
SwipeGesture.Type.TOUCH_MOVE -> when (direction) {
|
||||
SwipeGesture.Direction.LEFT -> when (prefs.gestures.deleteKeySwipeLeft) {
|
||||
SwipeAction.DELETE_CHARACTERS_PRECISELY -> {
|
||||
florisboard?.activeEditorInstance?.apply {
|
||||
setSelection(
|
||||
if (selection.start > 0) { selection.start - 1 } else { selection.start },
|
||||
selection.end
|
||||
)
|
||||
}
|
||||
shouldBlockNextKeyCode = true
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
SwipeGesture.Direction.RIGHT -> when (prefs.gestures.deleteKeySwipeLeft) {
|
||||
SwipeAction.DELETE_CHARACTERS_PRECISELY -> {
|
||||
florisboard?.activeEditorInstance?.apply {
|
||||
setSelection(
|
||||
if (selection.start < selection.end) { selection.start + 1 } else { selection.start },
|
||||
selection.end
|
||||
)
|
||||
}
|
||||
shouldBlockNextKeyCode = true
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
SwipeGesture.Type.TOUCH_UP -> when (prefs.gestures.deleteKeySwipeLeft) {
|
||||
SwipeAction.DELETE_CHARACTERS_PRECISELY -> {
|
||||
florisboard?.activeEditorInstance?.apply {
|
||||
if (selection.isSelectionMode) {
|
||||
deleteBackwards()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
KeyCode.SPACE -> when (type) {
|
||||
SwipeGesture.Type.TOUCH_MOVE -> when (direction) {
|
||||
SwipeGesture.Direction.LEFT -> {
|
||||
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
|
||||
osHandler?.removeCallbacksAndMessages(null)
|
||||
shouldBlockNextKeyCode = true
|
||||
true
|
||||
}
|
||||
SwipeGesture.Direction.RIGHT -> {
|
||||
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeRight)
|
||||
osHandler?.removeCallbacksAndMessages(null)
|
||||
shouldBlockNextKeyCode = true
|
||||
true
|
||||
}
|
||||
@@ -299,7 +369,7 @@ class KeyView(
|
||||
* by Devunwired
|
||||
*/
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val desiredWidth = when (keyboardView.computedLayout?.mode) {
|
||||
desiredWidth = when (keyboardView.computedLayout?.mode) {
|
||||
KeyboardMode.NUMERIC,
|
||||
KeyboardMode.PHONE,
|
||||
KeyboardMode.PHONE2 -> (keyboardView.desiredKeyWidth * 2.68f).toInt()
|
||||
@@ -318,7 +388,7 @@ class KeyView(
|
||||
else -> keyboardView.desiredKeyWidth
|
||||
}
|
||||
}
|
||||
val desiredHeight = keyboardView.desiredKeyHeight
|
||||
desiredHeight = keyboardView.desiredKeyHeight
|
||||
|
||||
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||
@@ -373,34 +443,69 @@ class KeyView(
|
||||
outlineProvider = KeyViewOutline(w, h)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the enabled state of a key depending on the [data] and its parameters.
|
||||
*/
|
||||
private fun updateEnabledState() {
|
||||
isEnabled = when (data.code) {
|
||||
KeyCode.CLIPBOARD_COPY,
|
||||
KeyCode.CLIPBOARD_CUT -> {
|
||||
florisboard?.activeEditorInstance?.selection?.isSelectionMode == true &&
|
||||
florisboard?.activeEditorInstance?.isRawInputEditor == false
|
||||
}
|
||||
KeyCode.CLIPBOARD_PASTE -> florisboard?.clipboardManager?.hasPrimaryClip() == true
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> {
|
||||
florisboard?.activeEditorInstance?.isRawInputEditor == false
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
if (!isEnabled) {
|
||||
isKeyPressed = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the background depending on [isKeyPressed] and [data].
|
||||
*/
|
||||
private fun updateKeyPressedBackground() {
|
||||
when (data.code) {
|
||||
KeyCode.ENTER -> {
|
||||
when {
|
||||
keyboardView.isSmartbarKeyboardView -> {
|
||||
elevation = 0.0f
|
||||
setBackgroundTintColor2(
|
||||
this, when {
|
||||
isKeyPressed -> prefs.theme.keyEnterBgColorPressed
|
||||
else -> prefs.theme.keyEnterBgColor
|
||||
}
|
||||
)
|
||||
}
|
||||
KeyCode.SHIFT -> {
|
||||
setBackgroundTintColor2(
|
||||
this, when {
|
||||
isKeyPressed -> prefs.theme.keyShiftBgColorPressed
|
||||
else -> prefs.theme.keyShiftBgColor
|
||||
isKeyPressed && isEnabled -> prefs.theme.smartbarButtonBgColor
|
||||
else -> prefs.theme.smartbarBgColor
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
setBackgroundTintColor2(
|
||||
this, when {
|
||||
isKeyPressed -> prefs.theme.keyBgColorPressed
|
||||
else -> prefs.theme.keyBgColor
|
||||
elevation = 4.0f
|
||||
when (data.code) {
|
||||
KeyCode.ENTER -> {
|
||||
setBackgroundTintColor2(
|
||||
this, when {
|
||||
isKeyPressed && isEnabled -> prefs.theme.keyEnterBgColorPressed
|
||||
else -> prefs.theme.keyEnterBgColor
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
KeyCode.SHIFT -> {
|
||||
setBackgroundTintColor2(
|
||||
this, when {
|
||||
isKeyPressed && isEnabled -> prefs.theme.keyShiftBgColorPressed
|
||||
else -> prefs.theme.keyShiftBgColor
|
||||
}
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
setBackgroundTintColor2(
|
||||
this, when {
|
||||
isKeyPressed && isEnabled -> prefs.theme.keyBgColorPressed
|
||||
else -> prefs.theme.keyBgColor
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,6 +542,7 @@ class KeyView(
|
||||
* TextInputManager.
|
||||
*/
|
||||
fun updateVisibility() {
|
||||
updateEnabledState()
|
||||
when (data.code) {
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT,
|
||||
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
|
||||
@@ -470,6 +576,37 @@ class KeyView(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically sets the text size of [boxPaint] for given [text] so it fits within the given
|
||||
* bounds.
|
||||
*
|
||||
* Implementation based on this blog post by Lucas (SketchingDev), written on Aug 20, 2015
|
||||
* https://sketchingdev.co.uk/blog/resizing-text-to-fit-into-a-container-on-android.html
|
||||
*
|
||||
* @param boxPaint The [Paint] object which the text size should be applied to.
|
||||
* @param boxWidth The max width for the surrounding box of [text].
|
||||
* @param boxHeight The max height for the surrounding box of [text].
|
||||
* @param text The text for which the size should be calculated.
|
||||
*/
|
||||
private fun setTextSizeFor(boxPaint: Paint, boxWidth: Float, boxHeight: Float, text: String) {
|
||||
var stage = 1
|
||||
var textSize = 0.0f
|
||||
while (stage < 3) {
|
||||
if (stage == 1) {
|
||||
textSize += 10.0f
|
||||
} else if (stage == 2) {
|
||||
textSize -= 1.0f
|
||||
}
|
||||
boxPaint.textSize = textSize
|
||||
boxPaint.getTextBounds(text, 0, text.length, tempRect)
|
||||
val fits = tempRect.width() < boxWidth && tempRect.height() < boxHeight
|
||||
if (stage == 1 && !fits || stage == 2 && fits) {
|
||||
stage++
|
||||
}
|
||||
}
|
||||
boxPaint.textSize = textSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the key label / drawable.
|
||||
*/
|
||||
@@ -484,26 +621,59 @@ class KeyView(
|
||||
&& data.code != KeyCode.HALF_SPACE && data.code != KeyCode.KESHIDA || data.type == KeyType.NUMERIC
|
||||
) {
|
||||
label = getComputedLetter()
|
||||
val hintedNumber = data.hintedNumber
|
||||
if (prefs.keyboard.hintedNumberRow && hintedNumber != null) {
|
||||
hintedLabel = getComputedLetter(hintedNumber)
|
||||
}
|
||||
val hintedSymbol = data.hintedSymbol
|
||||
if (prefs.keyboard.hintedSymbols && hintedSymbol != null) {
|
||||
hintedLabel = getComputedLetter(hintedSymbol)
|
||||
}
|
||||
|
||||
} else {
|
||||
when (data.code) {
|
||||
KeyCode.ARROW_LEFT -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_keyboard_arrow_left)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.ARROW_RIGHT -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_keyboard_arrow_right)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.CLIPBOARD_COPY -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_content_copy)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.CLIPBOARD_CUT -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_content_cut)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.CLIPBOARD_PASTE -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_content_paste)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.CLIPBOARD_SELECT_ALL -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_select_all)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.DELETE -> {
|
||||
drawable = getDrawable(context, R.drawable.ic_backspace)
|
||||
drawableColor = prefs.theme.keyFgColor
|
||||
}
|
||||
KeyCode.ENTER -> {
|
||||
val action = florisboard?.currentInputEditorInfo?.imeOptions ?: 0
|
||||
drawable = getDrawable(context, when (action and EditorInfo.IME_MASK_ACTION) {
|
||||
EditorInfo.IME_ACTION_DONE -> R.drawable.ic_done
|
||||
EditorInfo.IME_ACTION_GO -> R.drawable.ic_arrow_right_alt
|
||||
EditorInfo.IME_ACTION_NEXT -> R.drawable.ic_arrow_right_alt
|
||||
EditorInfo.IME_ACTION_NONE -> R.drawable.ic_keyboard_return
|
||||
EditorInfo.IME_ACTION_PREVIOUS -> R.drawable.ic_arrow_right_alt
|
||||
EditorInfo.IME_ACTION_SEARCH -> R.drawable.ic_search
|
||||
EditorInfo.IME_ACTION_SEND -> R.drawable.ic_send
|
||||
else -> R.drawable.ic_arrow_right_alt
|
||||
val imeOptions = florisboard?.activeEditorInstance?.imeOptions ?: ImeOptions.default()
|
||||
drawable = getDrawable(context, when (imeOptions.action) {
|
||||
ImeOptions.Action.DONE -> R.drawable.ic_done
|
||||
ImeOptions.Action.GO -> R.drawable.ic_arrow_right_alt
|
||||
ImeOptions.Action.NEXT -> R.drawable.ic_arrow_right_alt
|
||||
ImeOptions.Action.NONE -> R.drawable.ic_keyboard_return
|
||||
ImeOptions.Action.PREVIOUS -> R.drawable.ic_arrow_right_alt
|
||||
ImeOptions.Action.SEARCH -> R.drawable.ic_search
|
||||
ImeOptions.Action.SEND -> R.drawable.ic_send
|
||||
ImeOptions.Action.UNSPECIFIED -> R.drawable.ic_keyboard_return
|
||||
})
|
||||
drawableColor = prefs.theme.keyEnterFgColor
|
||||
if (action and EditorInfo.IME_FLAG_NO_ENTER_ACTION > 0) {
|
||||
if (imeOptions.flagNoEnterAction) {
|
||||
drawable = getDrawable(context, R.drawable.ic_keyboard_return)
|
||||
}
|
||||
}
|
||||
@@ -580,6 +750,9 @@ class KeyView(
|
||||
// Draw drawable
|
||||
val drawable = drawable
|
||||
if (drawable != null) {
|
||||
if (keyboardView.isSmartbarKeyboardView && !isEnabled) {
|
||||
drawableColor = prefs.theme.smartbarFgColorAlt
|
||||
}
|
||||
var marginV = 0
|
||||
var marginH = 0
|
||||
if (measuredWidth > measuredHeight) {
|
||||
@@ -602,20 +775,40 @@ class KeyView(
|
||||
// Draw label
|
||||
val label = label
|
||||
if (label != null) {
|
||||
if (data.code == KeyCode.VIEW_NUMERIC || data.code == KeyCode.VIEW_NUMERIC_ADVANCED
|
||||
|| data.code == KeyCode.SPACE) {
|
||||
labelPaint.textSize = resources.getDimension(R.dimen.key_numeric_textSize)
|
||||
} else {
|
||||
labelPaint.textSize = resources.getDimension(R.dimen.key_textSize)
|
||||
when (data.code) {
|
||||
KeyCode.VIEW_NUMERIC, KeyCode.VIEW_NUMERIC_ADVANCED -> {
|
||||
labelPaint.textSize = resources.getDimension(R.dimen.key_numeric_textSize)
|
||||
}
|
||||
else -> when {
|
||||
data.type == KeyType.CHARACTER && data.code != KeyCode.SPACE -> {
|
||||
setTextSizeFor(
|
||||
labelPaint,
|
||||
desiredWidth - (2.2f * drawablePadding),
|
||||
desiredHeight - (3.0f * drawablePadding),
|
||||
// Note: taking a "X" here because it is one of the biggest letters and
|
||||
// the keys must have the same base character for calculation, else
|
||||
// they will all look different and weird...
|
||||
"X"
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
setTextSizeFor(
|
||||
labelPaint,
|
||||
measuredWidth - (2.6f * drawablePadding),
|
||||
measuredHeight - (3.6f * drawablePadding),
|
||||
when (data.code) {
|
||||
KeyCode.VIEW_CHARACTERS, KeyCode.VIEW_SYMBOLS, KeyCode.VIEW_SYMBOLS2 -> {
|
||||
resources.getString(R.string.key__view_symbols)
|
||||
}
|
||||
else -> label
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
labelPaint.color = prefs.theme.keyFgColor
|
||||
labelPaint.alpha = if (keyboardView.computedLayout?.mode == KeyboardMode.CHARACTERS &&
|
||||
data.code == KeyCode.SPACE) { 120 } else { 255 }
|
||||
val isPortrait =
|
||||
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
if (prefs.keyboard.oneHandedMode != "off" && isPortrait) {
|
||||
labelPaint.textSize *= 0.9f
|
||||
}
|
||||
val centerX = measuredWidth / 2.0f
|
||||
val centerY = measuredHeight / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
|
||||
if (label.contains("\n")) {
|
||||
@@ -627,6 +820,25 @@ class KeyView(
|
||||
canvas.drawText(label, centerX, centerY, labelPaint)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw hinted label
|
||||
val hintedLabel = hintedLabel
|
||||
if (hintedLabel != null) {
|
||||
setTextSizeFor(
|
||||
hintedLabelPaint,
|
||||
desiredWidth * 1.0f / 6.0f,
|
||||
desiredHeight * 1.0f / 6.0f,
|
||||
// Note: taking a "X" here because it is one of the biggest letters and
|
||||
// the keys must have the same base character for calculation, else
|
||||
// they will all look different and weird...
|
||||
"X"
|
||||
)
|
||||
hintedLabelPaint.color = prefs.theme.keyFgColor
|
||||
hintedLabelPaint.alpha = 120
|
||||
val centerX = measuredWidth * 5.0f / 6.0f
|
||||
val centerY = measuredHeight * 1.0f / 6.0f + (hintedLabelPaint.textSize - hintedLabelPaint.descent()) / 2
|
||||
canvas.drawText(hintedLabel, centerX, centerY, hintedLabelPaint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,5 +24,7 @@ enum class KeyboardMode {
|
||||
NUMERIC,
|
||||
NUMERIC_ADVANCED,
|
||||
PHONE,
|
||||
PHONE2
|
||||
PHONE2,
|
||||
SMARTBAR_CLIPBOARD_CURSOR_ROW,
|
||||
SMARTBAR_NUMBER_ROW
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
|
||||
var florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
|
||||
private var initialKeyCode: Int = 0
|
||||
var isPreviewMode: Boolean = false
|
||||
var isSmartbarKeyboardView: Boolean = false
|
||||
var popupManager = KeyPopupManager<KeyboardView, KeyView>(this)
|
||||
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
|
||||
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
|
||||
@@ -132,7 +133,7 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
|
||||
return false
|
||||
}
|
||||
val eventFloris = MotionEvent.obtainNoHistory(event)
|
||||
if (swipeGestureDetector.onTouchEvent(event)) {
|
||||
if (!isSmartbarKeyboardView && swipeGestureDetector.onTouchEvent(event)) {
|
||||
sendFlorisTouchEvent(eventFloris, MotionEvent.ACTION_CANCEL)
|
||||
activeKeyView = null
|
||||
activePointerId = null
|
||||
@@ -222,7 +223,8 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
|
||||
override fun onSwipe(direction: SwipeGesture.Direction, type: SwipeGesture.Type): Boolean {
|
||||
return when {
|
||||
initialKeyCode == KeyCode.DELETE -> {
|
||||
if (type == SwipeGesture.Type.TOUCH_UP && direction == SwipeGesture.Direction.LEFT) {
|
||||
if (type == SwipeGesture.Type.TOUCH_UP && direction == SwipeGesture.Direction.LEFT &&
|
||||
prefs.gestures.deleteKeySwipeLeft == SwipeAction.DELETE_WORD) {
|
||||
florisboard?.executeSwipeAction(prefs.gestures.deleteKeySwipeLeft)
|
||||
true
|
||||
} else {
|
||||
@@ -287,20 +289,25 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Liste
|
||||
* The desired key heights/widths are being calculated here.
|
||||
*/
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||
|
||||
val keyMarginH = resources.getDimension((R.dimen.key_marginH)).toInt()
|
||||
desiredKeyWidth = (widthSize / 10) - (2 * keyMarginH)
|
||||
|
||||
val keyMarginV = resources.getDimension((R.dimen.key_marginV)).toInt()
|
||||
val keyHeightFactor = when (isPreviewMode) {
|
||||
true -> 0.90f
|
||||
else -> 1.00f
|
||||
}
|
||||
val desiredHeight = keyHeightFactor * (florisboard?.inputView?.desiredTextKeyboardViewHeight ?: resources.getDimension(R.dimen.textKeyboardView_baseHeight).toInt())
|
||||
desiredKeyHeight = (desiredHeight / 4 - 2 * keyMarginV).roundToInt()
|
||||
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(desiredHeight.roundToInt(), MeasureSpec.EXACTLY))
|
||||
val desiredWidth = MeasureSpec.getSize(widthMeasureSpec).toFloat()
|
||||
desiredKeyWidth = if (isSmartbarKeyboardView) {
|
||||
(desiredWidth / 6.0f - 2.0f * keyMarginH).roundToInt()
|
||||
} else {
|
||||
(desiredWidth / 10.0f - 2.0f * keyMarginH).roundToInt()
|
||||
}
|
||||
val desiredHeight = MeasureSpec.getSize(heightMeasureSpec) * if (isPreviewMode) { 0.90f } else { 1.00f }
|
||||
desiredKeyHeight = when {
|
||||
isSmartbarKeyboardView -> desiredHeight - 1.5f * keyMarginV
|
||||
else -> desiredHeight / 4.0f - 2.0f * keyMarginV
|
||||
}.roundToInt()
|
||||
|
||||
super.onMeasure(
|
||||
MeasureSpec.makeMeasureSpec(desiredWidth.roundToInt(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(desiredHeight.roundToInt(), MeasureSpec.EXACTLY)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onApplyThemeAttributes() {
|
||||
|
||||
@@ -22,10 +22,7 @@ import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyTypeAdapter
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyVariationAdapter
|
||||
import dev.patrickgold.florisboard.ime.text.key.*
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
@@ -37,18 +34,19 @@ private typealias KMS = Pair<KeyboardMode, Subtype>
|
||||
* Class which manages layout loading and caching.
|
||||
*/
|
||||
class LayoutManager(private val context: Context) : CoroutineScope by MainScope() {
|
||||
private val layoutCache: HashMap<KMS, Deferred<ComputedLayoutData>> = hashMapOf()
|
||||
private val computedLayoutCache: HashMap<KMS, Deferred<ComputedLayoutData>> = hashMapOf()
|
||||
|
||||
/**
|
||||
* Loads the layout for the specified type and name.
|
||||
*
|
||||
* @returns the [LayoutData] or null.
|
||||
* @return the [LayoutData] or null.
|
||||
*/
|
||||
private fun loadLayout(ltn: LTN?) = loadLayout(ltn?.first, ltn?.second)
|
||||
private fun loadLayout(type: LayoutType?, name: String?): LayoutData? {
|
||||
if (type == null || name == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val rawJsonData: String = try {
|
||||
context.assets.open("ime/text/$type/$name.json").bufferedReader().use { it.readText() }
|
||||
} catch (e: Exception) {
|
||||
@@ -108,9 +106,9 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
* @param main The main layout type and name.
|
||||
* @param modifier The modifier (mod) layout type and name.
|
||||
* @param extension The extension layout type and name.
|
||||
* @returns a [ComputedLayoutData] object, regardless of the specified LTNs or errors.
|
||||
* @return a [ComputedLayoutData] object, regardless of the specified LTNs or errors.
|
||||
*/
|
||||
private fun mergeLayouts(
|
||||
private suspend fun mergeLayoutsAsync(
|
||||
keyboardMode: KeyboardMode,
|
||||
subtype: Subtype,
|
||||
main: LTN? = null,
|
||||
@@ -194,6 +192,28 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
}
|
||||
}
|
||||
|
||||
// Add hints to keys
|
||||
if (keyboardMode == KeyboardMode.CHARACTERS) {
|
||||
val symbolsComputedArrangement = fetchComputedLayoutAsync(KeyboardMode.SYMBOLS, subtype).await().arrangement
|
||||
for ((r, row) in computedArrangement.withIndex()) {
|
||||
if (r >= 3) {
|
||||
break
|
||||
}
|
||||
if (symbolsComputedArrangement.getOrNull(r) != null) {
|
||||
for ((k, key) in row.withIndex()) {
|
||||
if (key.type == KeyType.CHARACTER) {
|
||||
val symbol = symbolsComputedArrangement[r].getOrNull(k)
|
||||
if (r == 0) {
|
||||
key.hintedNumber = symbol
|
||||
} else {
|
||||
key.hintedSymbol = symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ComputedLayoutData(
|
||||
keyboardMode,
|
||||
"computed",
|
||||
@@ -210,7 +230,7 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
* @param keyboardMode The keyboard mode for which the layout should be computed.
|
||||
* @param subtype The subtype which localizes the computed layout.
|
||||
*/
|
||||
private fun computeLayoutFor(
|
||||
private suspend fun computeLayoutFor(
|
||||
keyboardMode: KeyboardMode,
|
||||
subtype: Subtype
|
||||
): ComputedLayoutData {
|
||||
@@ -223,6 +243,9 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
main = LTN(LayoutType.CHARACTERS, subtype.layout)
|
||||
modifier = LTN(LayoutType.CHARACTERS_MOD, "default")
|
||||
}
|
||||
KeyboardMode.EDITING -> {
|
||||
// Layout for this mode is defined in custom layout xml file.
|
||||
}
|
||||
KeyboardMode.NUMERIC -> {
|
||||
main = LTN(LayoutType.NUMERIC, "default")
|
||||
}
|
||||
@@ -244,9 +267,15 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
main = LTN(LayoutType.SYMBOLS2, "western_default")
|
||||
modifier = LTN(LayoutType.SYMBOLS2_MOD, "default")
|
||||
}
|
||||
KeyboardMode.SMARTBAR_CLIPBOARD_CURSOR_ROW -> {
|
||||
extension = LTN(LayoutType.EXTENSION, "clipboard_cursor_row")
|
||||
}
|
||||
KeyboardMode.SMARTBAR_NUMBER_ROW -> {
|
||||
extension = LTN(LayoutType.EXTENSION, "number_row")
|
||||
}
|
||||
}
|
||||
|
||||
return mergeLayouts(keyboardMode, subtype, main, modifier, extension)
|
||||
return mergeLayoutsAsync(keyboardMode, subtype, main, modifier, extension)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,14 +292,14 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
subtype: Subtype
|
||||
): Deferred<ComputedLayoutData> {
|
||||
val kms = KMS(keyboardMode, subtype)
|
||||
val cachedComputedLayout = layoutCache[kms]
|
||||
val cachedComputedLayout = computedLayoutCache[kms]
|
||||
return if (cachedComputedLayout != null) {
|
||||
cachedComputedLayout
|
||||
} else {
|
||||
val computedLayout = async(Dispatchers.IO) {
|
||||
computeLayoutFor(keyboardMode, subtype)
|
||||
}
|
||||
layoutCache[kms] = computedLayout
|
||||
computedLayoutCache[kms] = computedLayout
|
||||
computedLayout
|
||||
}
|
||||
}
|
||||
@@ -289,8 +318,8 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
|
||||
subtype: Subtype
|
||||
) {
|
||||
val kms = KMS(keyboardMode, subtype)
|
||||
if (layoutCache[kms] == null) {
|
||||
layoutCache[kms] = async(Dispatchers.IO) {
|
||||
if (computedLayoutCache[kms] == null) {
|
||||
computedLayoutCache[kms] = async(Dispatchers.IO) {
|
||||
computeLayoutFor(keyboardMode, subtype)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,64 +2,55 @@ package dev.patrickgold.florisboard.ime.text.smartbar
|
||||
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.CursorAnchorInfo
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.children
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.EditorInstance
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
import dev.patrickgold.florisboard.ime.core.Subtype
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputManager
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
// TODO: Implement suggestion creation functionality
|
||||
// TODO: Cleanup and reorganize SmartbarManager
|
||||
class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
class SmartbarManager private constructor() : CoroutineScope by MainScope(),
|
||||
FlorisBoard.EventListener {
|
||||
|
||||
private val florisboard: FlorisBoard = FlorisBoard.getInstance()
|
||||
private var isComposingEnabled: Boolean = false
|
||||
private val activeEditorInstance: EditorInstance
|
||||
get() = florisboard.activeEditorInstance
|
||||
private val prefs: PrefHelper
|
||||
get() = florisboard.prefs
|
||||
|
||||
private val textInputManager: TextInputManager = TextInputManager.getInstance()
|
||||
var smartbarView: SmartbarView? = null
|
||||
private set
|
||||
private var shouldSuggestClipboardContents: Boolean = false
|
||||
private var smartbarView: SmartbarView? = null
|
||||
|
||||
var isQuickActionsVisible: Boolean = false
|
||||
set(value) { field = value; updateActiveContainerVisibility() }
|
||||
|
||||
private val candidateViewOnClickListener = View.OnClickListener { v ->
|
||||
val view = v as Button
|
||||
val text = view.text.toString()
|
||||
if (text.isNotEmpty()) {
|
||||
textInputManager.commitCandidate(text)
|
||||
florisboard.activeEditorInstance.commitCompletion(text)
|
||||
}
|
||||
}
|
||||
private val candidateViewOnLongClickListener = View.OnLongClickListener { v ->
|
||||
true
|
||||
}
|
||||
private val keyButtonOnClickListener = View.OnClickListener { v ->
|
||||
val keyData = when (v.id) {
|
||||
R.id.number_row_0 -> KeyData(48, "0")
|
||||
R.id.number_row_1 -> KeyData(49, "1")
|
||||
R.id.number_row_2 -> KeyData(50, "2")
|
||||
R.id.number_row_3 -> KeyData(51, "3")
|
||||
R.id.number_row_4 -> KeyData(52, "4")
|
||||
R.id.number_row_5 -> KeyData(53, "5")
|
||||
R.id.number_row_6 -> KeyData(54, "6")
|
||||
R.id.number_row_7 -> KeyData(55, "7")
|
||||
R.id.number_row_8 -> KeyData(56, "8")
|
||||
R.id.number_row_9 -> KeyData(57, "9")
|
||||
R.id.cc_select_all -> KeyData(KeyCode.CLIPBOARD_SELECT_ALL)
|
||||
R.id.cc_copy -> KeyData(KeyCode.CLIPBOARD_COPY)
|
||||
R.id.cc_arrow_left -> KeyData(KeyCode.ARROW_LEFT)
|
||||
R.id.cc_arrow_right -> KeyData(KeyCode.ARROW_RIGHT)
|
||||
R.id.cc_cut -> KeyData(KeyCode.CLIPBOARD_CUT)
|
||||
R.id.cc_paste -> KeyData(KeyCode.CLIPBOARD_PASTE)
|
||||
else -> KeyData(0)
|
||||
}
|
||||
florisboard.textInputManager.sendKeyPress(keyData)
|
||||
private val clipboardSuggestionViewOnClickListener = View.OnClickListener {
|
||||
activeEditorInstance.performClipboardPaste()
|
||||
shouldSuggestClipboardContents = false
|
||||
updateActiveContainerVisibility()
|
||||
}
|
||||
private val quickActionOnClickListener = View.OnClickListener { v ->
|
||||
when (v.id) {
|
||||
@@ -82,9 +73,11 @@ class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
else -> return@OnClickListener
|
||||
}
|
||||
isQuickActionsVisible = false
|
||||
updateSmartbarUI()
|
||||
}
|
||||
private val quickActionToggleOnClickListener = View.OnClickListener {
|
||||
isQuickActionsVisible = !isQuickActionsVisible
|
||||
updateSmartbarUI()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -111,16 +104,24 @@ class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
quickAction.setOnClickListener(quickActionOnClickListener)
|
||||
}
|
||||
}
|
||||
val numberRow = smartbarView.findViewById<LinearLayout>(R.id.number_row)
|
||||
for (numberRowButton in numberRow.children) {
|
||||
if (numberRowButton is Button) {
|
||||
numberRowButton.setOnClickListener(keyButtonOnClickListener)
|
||||
launch(Dispatchers.Default) {
|
||||
val numberRow = smartbarView.findViewById<KeyboardView>(R.id.smartbar_variant_number_row)
|
||||
numberRow.isSmartbarKeyboardView = true
|
||||
val layout = textInputManager.layoutManager.fetchComputedLayoutAsync(KeyboardMode.SMARTBAR_NUMBER_ROW, Subtype.DEFAULT).await()
|
||||
launch(Dispatchers.Main) {
|
||||
numberRow.computedLayout = layout
|
||||
numberRow.updateVisibility()
|
||||
}
|
||||
}
|
||||
val clipboardCursorRow = smartbarView.findViewById<ViewGroup>(R.id.clipboard_cursor_row)
|
||||
for (clipboardCursorRowButton in clipboardCursorRow.children) {
|
||||
if (clipboardCursorRowButton is ImageButton) {
|
||||
clipboardCursorRowButton.setOnClickListener(keyButtonOnClickListener)
|
||||
val clipboardSuggestion = smartbarView.findViewById<Button>(R.id.clipboard_suggestion)
|
||||
clipboardSuggestion.setOnClickListener(clipboardSuggestionViewOnClickListener)
|
||||
launch(Dispatchers.Default) {
|
||||
val ccRow = smartbarView.findViewById<KeyboardView>(R.id.clipboard_cursor_row)
|
||||
ccRow.isSmartbarKeyboardView = true
|
||||
val layout = textInputManager.layoutManager.fetchComputedLayoutAsync(KeyboardMode.SMARTBAR_CLIPBOARD_CURSOR_ROW, Subtype.DEFAULT).await()
|
||||
launch(Dispatchers.Main) {
|
||||
ccRow.computedLayout = layout
|
||||
ccRow.updateVisibility()
|
||||
}
|
||||
}
|
||||
val backButton = smartbarView.findViewById<View>(R.id.back_button)
|
||||
@@ -130,11 +131,12 @@ class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
candidateView.setOnLongClickListener(candidateViewOnLongClickListener)
|
||||
}
|
||||
|
||||
smartbarView.setActiveVariant(R.id.smartbar_variant_default)
|
||||
updateSmartbarUI()
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
isQuickActionsVisible = false
|
||||
updateActiveContainerVisibility()
|
||||
}
|
||||
|
||||
// TODO: clean up resources here
|
||||
@@ -144,8 +146,7 @@ class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
instance = null
|
||||
}
|
||||
|
||||
fun onStartInputView(keyboardMode: KeyboardMode, isComposingEnabled: Boolean) {
|
||||
this.isComposingEnabled = isComposingEnabled
|
||||
fun onStartInputView(keyboardMode: KeyboardMode) {
|
||||
when (keyboardMode) {
|
||||
KeyboardMode.NUMERIC, KeyboardMode.PHONE, KeyboardMode.PHONE2 -> {
|
||||
smartbarView?.setActiveVariant(null)
|
||||
@@ -155,32 +156,21 @@ class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
isQuickActionsVisible = false
|
||||
}
|
||||
}
|
||||
updateSmartbarUI()
|
||||
}
|
||||
|
||||
fun onFinishInputView() {
|
||||
//spellCheckerSession?.close()
|
||||
}
|
||||
|
||||
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
||||
val isSelectionActive = florisboard.textInputManager.isTextSelected
|
||||
smartbarView?.findViewById<View>(R.id.cc_cut)?.isEnabled = isSelectionActive
|
||||
smartbarView?.findViewById<View>(R.id.cc_copy)?.isEnabled = isSelectionActive
|
||||
smartbarView?.findViewById<View>(R.id.cc_paste)?.isEnabled =
|
||||
florisboard.clipboardManager?.hasPrimaryClip() ?: false
|
||||
override fun onUpdateSelection() {
|
||||
updateSmartbarUI()
|
||||
}
|
||||
|
||||
fun deleteCandidateFromDictionary(candidate: String) {
|
||||
//
|
||||
}
|
||||
|
||||
fun resetCandidates() {
|
||||
//
|
||||
}
|
||||
|
||||
fun generateCandidatesFromComposing(composingText: String?) {
|
||||
fun generateCandidatesFromComposing(composingText: String) {
|
||||
val smartbarView = smartbarView ?: return
|
||||
|
||||
if (composingText == null) {
|
||||
if (composingText == "") {
|
||||
smartbarView.candidateViewList[0].text = "candidate"
|
||||
smartbarView.candidateViewList[1].text = "suggestions"
|
||||
smartbarView.candidateViewList[2].text = "nyi"
|
||||
@@ -189,43 +179,87 @@ class SmartbarManager private constructor() : FlorisBoard.EventListener {
|
||||
smartbarView.candidateViewList[1].text = composingText + "test"
|
||||
smartbarView.candidateViewList[2].text = ""
|
||||
}
|
||||
//spellCheckerSession?.getSentenceSuggestions(arrayOf(TextInfo(composing)), 3)
|
||||
//android.util.Log.i("SPELL", "GEN")
|
||||
/*val dic: Uri = UserDictionary.Words.CONTENT_URI
|
||||
val resolver: ContentResolver = florisboard.contentResolver
|
||||
val cursor: Cursor = resolver.query(dic, null, null, null, null) ?: return
|
||||
var count = 0
|
||||
while (cursor.moveToNext()) {
|
||||
val word = cursor.getString(cursor.getColumnIndex(UserDictionary.Words.WORD))
|
||||
candidateViewList[count].text = word
|
||||
if (count++ > 2) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrimaryClipChanged() {
|
||||
if (prefs.suggestion.enabled && prefs.suggestion.suggestClipboardContent) {
|
||||
shouldSuggestClipboardContents = true
|
||||
updateActiveContainerVisibility()
|
||||
}
|
||||
cursor.close()*/
|
||||
}
|
||||
|
||||
fun writeCandidate(candidate: String) {
|
||||
//
|
||||
fun resetClipboardSuggestion() {
|
||||
if (prefs.suggestion.enabled && prefs.suggestion.suggestClipboardContent) {
|
||||
shouldSuggestClipboardContents = false
|
||||
updateActiveContainerVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateActiveContainerVisibility() {
|
||||
private fun updateSmartbarUI() {
|
||||
val ei = activeEditorInstance
|
||||
if (ei.selection.isCursorMode && ei.isComposingEnabled) {
|
||||
generateCandidatesFromComposing(ei.currentWord.text)
|
||||
}
|
||||
updateActiveContainerVisibility()
|
||||
val ccRow = smartbarView?.findViewById<KeyboardView>(R.id.clipboard_cursor_row)
|
||||
ccRow?.updateVisibility()
|
||||
}
|
||||
|
||||
fun updateActiveContainerVisibility() {
|
||||
val smartbarView = smartbarView ?: return
|
||||
|
||||
if (isQuickActionsVisible) {
|
||||
smartbarView.setActiveVariant(R.id.smartbar_variant_default)
|
||||
smartbarView.setActiveContainer(R.id.quick_actions)
|
||||
smartbarView.findViewById<View>(R.id.quick_action_toggle)?.rotation = -180.0f
|
||||
} else {
|
||||
if (isComposingEnabled) {
|
||||
smartbarView.setActiveContainer(R.id.candidates)
|
||||
} else if (textInputManager.getActiveKeyboardMode() == KeyboardMode.CHARACTERS) {
|
||||
smartbarView.setActiveContainer(when (florisboard.prefs.suggestion.showInstead) {
|
||||
"number_row" -> R.id.number_row
|
||||
"clipboard_cursor_tools" -> R.id.clipboard_cursor_row
|
||||
else -> null
|
||||
})
|
||||
} else {
|
||||
smartbarView.setActiveContainer(null)
|
||||
when {
|
||||
textInputManager.getActiveKeyboardMode() == KeyboardMode.EDITING -> {
|
||||
smartbarView.setActiveVariant(R.id.smartbar_variant_back_only)
|
||||
smartbarView.setActiveContainer(null)
|
||||
}
|
||||
activeEditorInstance.isComposingEnabled -> {
|
||||
smartbarView.setActiveVariant(R.id.smartbar_variant_default)
|
||||
val containerId = if (shouldSuggestClipboardContents && florisboard.clipboardManager?.hasPrimaryClip() == true) {
|
||||
val clipboardSuggestion = smartbarView.findViewById<Button>(R.id.clipboard_suggestion)
|
||||
val item = florisboard.clipboardManager?.primaryClip?.getItemAt(0)
|
||||
when {
|
||||
item?.text != null -> {
|
||||
clipboardSuggestion?.text = item.text
|
||||
}
|
||||
item?.uri != null -> {
|
||||
clipboardSuggestion?.text = "(Image) " + item.uri.toString()
|
||||
}
|
||||
else -> {
|
||||
clipboardSuggestion?.text = item?.text ?: "(Error while retrieving clipboard data)"
|
||||
}
|
||||
}
|
||||
R.id.clipboard_suggestion_row
|
||||
} else {
|
||||
R.id.candidates
|
||||
}
|
||||
smartbarView.setActiveContainer(containerId)
|
||||
}
|
||||
textInputManager.getActiveKeyboardMode() == KeyboardMode.CHARACTERS -> {
|
||||
when (prefs.suggestion.showInstead) {
|
||||
"number_row" -> {
|
||||
smartbarView.setActiveVariant(R.id.smartbar_variant_number_row)
|
||||
smartbarView.setActiveContainer(null)
|
||||
}
|
||||
"clipboard_cursor_tools" -> {
|
||||
smartbarView.setActiveVariant(R.id.smartbar_variant_default)
|
||||
smartbarView.setActiveContainer(R.id.clipboard_cursor_row)
|
||||
}
|
||||
else -> {
|
||||
smartbarView.setActiveVariant(null)
|
||||
smartbarView.setActiveContainer(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
smartbarView.setActiveVariant(null)
|
||||
smartbarView.setActiveContainer(null)
|
||||
}
|
||||
}
|
||||
smartbarView.findViewById<View>(R.id.quick_action_toggle)?.rotation = 0.0f
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.children
|
||||
@@ -31,8 +30,8 @@ import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
import dev.patrickgold.florisboard.util.setImageTintColor2
|
||||
import kotlinx.android.synthetic.main.florisboard.view.*
|
||||
import dev.patrickgold.florisboard.util.setBackgroundTintColor2
|
||||
import dev.patrickgold.florisboard.util.setDrawableTintColor2
|
||||
|
||||
/**
|
||||
* View class which keeps the references to important children and informs [SmartbarManager] that
|
||||
@@ -61,10 +60,11 @@ class SmartbarView : LinearLayout {
|
||||
|
||||
variants.add(findViewById(R.id.smartbar_variant_default))
|
||||
variants.add(findViewById(R.id.smartbar_variant_back_only))
|
||||
variants.add(findViewById(R.id.smartbar_variant_number_row))
|
||||
|
||||
containers.add(findViewById(R.id.candidates))
|
||||
containers.add(findViewById(R.id.clipboard_suggestion_row))
|
||||
containers.add(findViewById(R.id.clipboard_cursor_row))
|
||||
containers.add(findViewById(R.id.number_row))
|
||||
containers.add(findViewById(R.id.quick_actions))
|
||||
|
||||
candidateViewList.add(findViewById(R.id.candidate0))
|
||||
@@ -131,25 +131,13 @@ class SmartbarView : LinearLayout {
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
setBackgroundColor(prefs.theme.smartbarBgColor)
|
||||
for (container in containers) {
|
||||
for (container in containers + variants) {
|
||||
when (container.id) {
|
||||
R.id.number_row -> {
|
||||
for (button in container.children) {
|
||||
if (button is Button) {
|
||||
button.setTextColor(prefs.theme.smartbarFgColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.clipboard_cursor_row -> {
|
||||
for (button in container.children) {
|
||||
if (button is ImageButton) {
|
||||
if (button.isEnabled) {
|
||||
setImageTintColor2(button, prefs.theme.smartbarFgColor)
|
||||
} else {
|
||||
setImageTintColor2(button, prefs.theme.smartbarFgColorAlt)
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.clipboard_suggestion_row -> {
|
||||
val clipboardSuggestion = findViewById<Button>(R.id.clipboard_suggestion)
|
||||
setBackgroundTintColor2(clipboardSuggestion, prefs.theme.smartbarButtonBgColor)
|
||||
setDrawableTintColor2(clipboardSuggestion, prefs.theme.smartbarButtonFgColor)
|
||||
clipboardSuggestion.setTextColor(prefs.theme.smartbarButtonFgColor)
|
||||
}
|
||||
R.id.candidates -> {
|
||||
for (view in container.children) {
|
||||
|
||||
@@ -88,7 +88,7 @@ data class Theme(
|
||||
* @param context A reference to the current [Context]. Used to request
|
||||
* asset file.
|
||||
* @param path The path to the json theme file in the asset folder.
|
||||
* @returns A parsed [Theme] or null. A null value may indicate that
|
||||
* @return A parsed [Theme] or null. A null value may indicate that
|
||||
* the file does not exist or that an error during the reading
|
||||
* of the file occurred.
|
||||
*/
|
||||
@@ -105,7 +105,7 @@ data class Theme(
|
||||
* Loads a theme from the given [rawData].
|
||||
*
|
||||
* @param rawData The raw json theme file as a string.
|
||||
* @returns A parsed [Theme] or null. A null value may indicate that an error
|
||||
* @return A parsed [Theme] or null. A null value may indicate that an error
|
||||
* during the reading of the [rawData] occurred.
|
||||
*/
|
||||
fun fromJsonString(rawData: String): Theme? {
|
||||
@@ -259,7 +259,7 @@ data class ThemeMetaOnly(
|
||||
* @param context A reference to the current [Context]. Used to request
|
||||
* asset file.
|
||||
* @param path The path to the json theme file in the asset folder.
|
||||
* @returns [ThemeMetaOnly] or null. A null value may indicate that
|
||||
* @return [ThemeMetaOnly] or null. A null value may indicate that
|
||||
* the file does not exist or that an error during the reading
|
||||
* of the file occurred.
|
||||
*/
|
||||
@@ -282,7 +282,7 @@ data class ThemeMetaOnly(
|
||||
* @param context A reference to the current [Context]. Used to request
|
||||
* asset file.
|
||||
* @param path The path to the dir in the asset folder.
|
||||
* @returns [ThemeMetaOnly] or null. A null value may indicate that
|
||||
* @return [ThemeMetaOnly] or null. A null value may indicate that
|
||||
* the file does not exist or that an error during the reading
|
||||
* of the file occurred.
|
||||
*/
|
||||
|
||||
@@ -146,7 +146,7 @@ class DialogSeekBarPreference : Preference {
|
||||
* handle. (Android's SeekBar step is fixed at 1 and min at 0)
|
||||
*
|
||||
* @param actual The actual value.
|
||||
* @returns the internal value which is used to allow different min and step values.
|
||||
* @return the internal value which is used to allow different min and step values.
|
||||
*/
|
||||
private fun actualValueToSeekBarProgress(actual: Int): Int {
|
||||
return (actual - min) / step
|
||||
@@ -156,7 +156,7 @@ class DialogSeekBarPreference : Preference {
|
||||
* Converts the Android SeekBar value to the actual value.
|
||||
*
|
||||
* @param progress The progress value of the SeekBar.
|
||||
* @returns the actual value which is ready to use.
|
||||
* @return the actual value which is ready to use.
|
||||
*/
|
||||
private fun seekBarProgressToActualValue(progress: Int): Int {
|
||||
return (progress * step) + min
|
||||
|
||||
@@ -33,6 +33,7 @@ import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
|
||||
import dev.patrickgold.florisboard.settings.SettingsMainActivity
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ThemeFragment : SettingsMainActivity.SettingsFragment(), CoroutineScope by MainScope(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@@ -54,7 +55,7 @@ class ThemeFragment : SettingsMainActivity.SettingsFragment(), CoroutineScope by
|
||||
keyboardView = KeyboardView(themeContext)
|
||||
keyboardView.layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
resources.getDimension(R.dimen.textKeyboardView_baseHeight).roundToInt()
|
||||
).apply {
|
||||
val m = resources.getDimension(R.dimen.keyboard_preview_margin).toInt()
|
||||
setMargins(m, m, m, m)
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package dev.patrickgold.florisboard.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
|
||||
|
||||
/**
|
||||
* This file has been taken from the Android LatinIME project. Following modifications were done to
|
||||
* the original source code:
|
||||
@@ -71,4 +74,30 @@ object ViewLayoutUtils {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts dp unit to equivalent pixels, depending on device density.
|
||||
*
|
||||
* Source: https://stackoverflow.com/a/9563438/6801193 (by Muhammad Nabeel Arif)
|
||||
*
|
||||
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
|
||||
* @param context Context to get resources and device specific display metrics
|
||||
* @return A float value to represent px equivalent to dp depending on device density
|
||||
*/
|
||||
fun convertDpToPixel(dp: Float, context: Context): Float {
|
||||
return dp * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts device specific pixels to density independent pixels.
|
||||
*
|
||||
* Source: https://stackoverflow.com/a/9563438/6801193 (by Muhammad Nabeel Arif)
|
||||
*
|
||||
* @param px A value in px (pixels) unit. Which we need to convert into db
|
||||
* @param context Context to get resources and device specific display metrics
|
||||
* @return A float value to represent dp equivalent to px value
|
||||
*/
|
||||
fun convertPixelsToDp(px: Float, context: Context): Float {
|
||||
return px / (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.util
|
||||
|
||||
import android.text.InputType
|
||||
import android.text.TextUtils
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
fun EditorInfo.debugSummarize(): String {
|
||||
var summary = this::class.qualifiedName + "\r\n"
|
||||
summary += "imeOptions: " + this.imeOptions.debugSummarize(EditorInfo::class) + "\r\n"
|
||||
summary += "initialCapsMode: " + this.initialCapsMode.debugSummarize(TextUtils::class) + "\r\n"
|
||||
summary += "initialSelStart: " + this.initialSelStart + "\r\n"
|
||||
summary += "initialSelEnd: " + this.initialSelEnd + "\r\n"
|
||||
summary += "inputType: " + this.inputType.debugSummarize(InputType::class) + "\r\n"
|
||||
summary += "packageName: " + this.packageName
|
||||
return summary
|
||||
}
|
||||
|
||||
fun <T: Any> Int.debugSummarize(type: KClass<T>): String {
|
||||
var summary = ""
|
||||
when (type) {
|
||||
EditorInfo::class -> {
|
||||
when (this) {
|
||||
EditorInfo.IME_NULL -> {
|
||||
summary += "IME_NULL"
|
||||
}
|
||||
else -> {
|
||||
val tAction = when (this and EditorInfo.IME_MASK_ACTION) {
|
||||
EditorInfo.IME_ACTION_DONE -> "IME_ACTION_DONE"
|
||||
EditorInfo.IME_ACTION_GO -> "IME_ACTION_GO"
|
||||
EditorInfo.IME_ACTION_NEXT -> "IME_ACTION_NEXT"
|
||||
EditorInfo.IME_ACTION_NONE -> "IME_ACTION_NONE"
|
||||
EditorInfo.IME_ACTION_PREVIOUS -> "IME_ACTION_PREVIOUS"
|
||||
EditorInfo.IME_ACTION_SEARCH -> "IME_ACTION_SEARCH"
|
||||
EditorInfo.IME_ACTION_SEND -> "IME_ACTION_SEND"
|
||||
EditorInfo.IME_ACTION_UNSPECIFIED -> "IME_ACTION_UNSPECIFIED"
|
||||
else -> String.format("0x%08x", this and EditorInfo.IME_MASK_ACTION)
|
||||
}
|
||||
var tFlags = ""
|
||||
if (this and EditorInfo.IME_FLAG_FORCE_ASCII > 0) {
|
||||
tFlags += "IME_FLAG_FORCE_ASCII|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NAVIGATE_NEXT > 0) {
|
||||
tFlags += "IME_FLAG_NAVIGATE_NEXT|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS > 0) {
|
||||
tFlags += "IME_FLAG_NAVIGATE_PREVIOUS|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION > 0) {
|
||||
tFlags += "IME_FLAG_NO_ACCESSORY_ACTION|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NO_ENTER_ACTION > 0) {
|
||||
tFlags += "IME_FLAG_NO_ENTER_ACTION|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NO_EXTRACT_UI > 0) {
|
||||
tFlags += "IME_FLAG_NO_EXTRACT_UI|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NO_FULLSCREEN > 0) {
|
||||
tFlags += "IME_FLAG_NO_FULLSCREEN|"
|
||||
}
|
||||
if (this and EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING > 0) {
|
||||
tFlags += "IME_FLAG_NO_PERSONALIZED_LEARNING|"
|
||||
}
|
||||
if (tFlags.isEmpty()) {
|
||||
tFlags = "(none)"
|
||||
}
|
||||
if (tFlags.endsWith("|")) {
|
||||
tFlags = tFlags.substring(0, tFlags.length - 1)
|
||||
}
|
||||
summary += "action=$tAction flags=$tFlags"
|
||||
}
|
||||
}
|
||||
}
|
||||
InputType::class -> {
|
||||
when (this) {
|
||||
InputType.TYPE_NULL -> {
|
||||
summary += "TYPE_NULL"
|
||||
}
|
||||
else -> {
|
||||
val tClass: String
|
||||
val tVariation: String
|
||||
var tFlags = ""
|
||||
when (this and InputType.TYPE_MASK_CLASS) {
|
||||
InputType.TYPE_CLASS_DATETIME -> {
|
||||
tClass = "TYPE_CLASS_DATETIME"
|
||||
tVariation = when (this and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_DATETIME_VARIATION_DATE -> "TYPE_DATETIME_VARIATION_DATE"
|
||||
InputType.TYPE_DATETIME_VARIATION_NORMAL -> "TYPE_DATETIME_VARIATION_NORMAL"
|
||||
InputType.TYPE_DATETIME_VARIATION_TIME -> "TYPE_DATETIME_VARIATION_TIME"
|
||||
else -> String.format("0x%08x", this and InputType.TYPE_MASK_VARIATION)
|
||||
}
|
||||
}
|
||||
InputType.TYPE_CLASS_NUMBER -> {
|
||||
tClass = "TYPE_CLASS_NUMBER"
|
||||
tVariation = when (this and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_NUMBER_VARIATION_NORMAL -> "TYPE_NUMBER_VARIATION_NORMAL"
|
||||
InputType.TYPE_NUMBER_VARIATION_PASSWORD -> "TYPE_NUMBER_VARIATION_PASSWORD"
|
||||
else -> String.format("0x%08x", this and InputType.TYPE_MASK_VARIATION)
|
||||
}
|
||||
if (this and InputType.TYPE_NUMBER_FLAG_DECIMAL > 0) {
|
||||
tFlags += "TYPE_NUMBER_FLAG_DECIMAL|"
|
||||
}
|
||||
if (this and InputType.TYPE_NUMBER_FLAG_SIGNED > 0) {
|
||||
tFlags += "TYPE_NUMBER_FLAG_SIGNED|"
|
||||
}
|
||||
}
|
||||
InputType.TYPE_CLASS_PHONE -> {
|
||||
tClass = "TYPE_CLASS_PHONE"
|
||||
tVariation = String.format("0x%08x", this and InputType.TYPE_MASK_VARIATION)
|
||||
}
|
||||
InputType.TYPE_CLASS_TEXT -> {
|
||||
tClass = "TYPE_CLASS_TEXT"
|
||||
tVariation = when (this and InputType.TYPE_MASK_VARIATION) {
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS -> "TYPE_TEXT_VARIATION_EMAIL_ADDRESS"
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT -> "TYPE_TEXT_VARIATION_EMAIL_SUBJECT"
|
||||
InputType.TYPE_TEXT_VARIATION_FILTER -> "TYPE_TEXT_VARIATION_FILTER"
|
||||
InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE -> "TYPE_TEXT_VARIATION_LONG_MESSAGE"
|
||||
InputType.TYPE_TEXT_VARIATION_NORMAL -> "TYPE_TEXT_VARIATION_NORMAL"
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD -> "TYPE_TEXT_VARIATION_PASSWORD"
|
||||
InputType.TYPE_TEXT_VARIATION_PERSON_NAME -> "TYPE_TEXT_VARIATION_PERSON_NAME"
|
||||
InputType.TYPE_TEXT_VARIATION_PHONETIC -> "TYPE_TEXT_VARIATION_PHONETIC"
|
||||
InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS -> "TYPE_TEXT_VARIATION_POSTAL_ADDRESS"
|
||||
InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE -> "TYPE_TEXT_VARIATION_SHORT_MESSAGE"
|
||||
InputType.TYPE_TEXT_VARIATION_URI -> "TYPE_TEXT_VARIATION_URI"
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD -> "TYPE_TEXT_VARIATION_VISIBLE_PASSWORD"
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT -> "TYPE_TEXT_VARIATION_WEB_EDIT_TEXT"
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS -> "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD -> "TYPE_TEXT_VARIATION_WEB_PASSWORD"
|
||||
else -> String.format("0x%08x", this and InputType.TYPE_MASK_VARIATION)
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_AUTO_COMPLETE|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_AUTO_CORRECT|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_CAP_CHARACTERS|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_CAP_SENTENCES|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_CAP_WORDS > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_CAP_WORDS|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_IME_MULTI_LINE|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_MULTI_LINE > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_MULTI_LINE|"
|
||||
}
|
||||
if (this and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS > 0) {
|
||||
tFlags += "TYPE_TEXT_FLAG_NO_SUGGESTIONS|"
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
tClass = String.format("0x%08x", this and InputType.TYPE_MASK_CLASS)
|
||||
tVariation = String.format("0x%08x", this and InputType.TYPE_MASK_VARIATION)
|
||||
}
|
||||
}
|
||||
if (tFlags.isEmpty()) {
|
||||
tFlags = "(none)"
|
||||
}
|
||||
if (tFlags.endsWith("|")) {
|
||||
tFlags = tFlags.substring(0, tFlags.length - 1)
|
||||
}
|
||||
summary += "class=$tClass variation=$tVariation flags=$tFlags"
|
||||
}
|
||||
}
|
||||
}
|
||||
TextUtils::class -> {
|
||||
var tFlags = ""
|
||||
if (this and TextUtils.CAP_MODE_CHARACTERS > 0) {
|
||||
tFlags += "CAP_MODE_CHARACTERS|"
|
||||
}
|
||||
if (this and TextUtils.CAP_MODE_SENTENCES > 0) {
|
||||
tFlags += "CAP_MODE_SENTENCES|"
|
||||
}
|
||||
if (this and TextUtils.CAP_MODE_WORDS > 0) {
|
||||
tFlags += "CAP_MODE_WORDS|"
|
||||
}
|
||||
if (this and TextUtils.SAFE_STRING_FLAG_FIRST_LINE > 0) {
|
||||
tFlags += "SAFE_STRING_FLAG_FIRST_LINE|"
|
||||
}
|
||||
if (this and TextUtils.SAFE_STRING_FLAG_SINGLE_LINE > 0) {
|
||||
tFlags += "SAFE_STRING_FLAG_SINGLE_LINE|"
|
||||
}
|
||||
if (this and TextUtils.SAFE_STRING_FLAG_TRIM > 0) {
|
||||
tFlags += "SAFE_STRING_FLAG_TRIM|"
|
||||
}
|
||||
if (tFlags.isEmpty()) {
|
||||
tFlags = "(none)"
|
||||
}
|
||||
if (tFlags.endsWith("|")) {
|
||||
tFlags = tFlags.substring(0, tFlags.length - 1)
|
||||
}
|
||||
summary += "flags=$tFlags"
|
||||
}
|
||||
}
|
||||
return summary
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="false" android:color="?android:colorButtonNormal"/>
|
||||
<item android:color="#FFFFFF"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:drawable="@drawable/ic_content_paste"/>
|
||||
</layer-list>
|
||||
6
app/src/main/res/drawable/shape_rect_rounded_2.xml
Normal file
6
app/src/main/res/drawable/shape_rect_rounded_2.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/black" />
|
||||
<corners android:radius="@dimen/smartbar_radius" />
|
||||
</shape>
|
||||
49
app/src/main/res/layout/crash_dialog.xml
Normal file
49
app/src/main/res/layout/crash_dialog.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:theme="@style/CrashDialogTheme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:text="@string/crash_dialog__description"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/copy_to_clipboard"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:text="@string/crash_dialog__copy_to_clipboard"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/open_bug_report_form"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:text="@string/crash_dialog__open_bug_report_form"/>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stacktrace"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:text="@string/crash_dialog__close"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -39,6 +39,16 @@
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
style="@style/SettingsCardView">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Note: Preferences tagged with [NYI] are not yet implemented and thus won\'t do anything or do some basic placeholder stuff only. Please do not file a bug report for these."/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/repo_url_card"
|
||||
style="@style/SettingsCardView.Clickable">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/smartbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -43,6 +42,18 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/clipboard_suggestion_row"
|
||||
style="@style/SmartbarContainer"
|
||||
android:visibility="gone">
|
||||
|
||||
<Button
|
||||
android:id="@+id/clipboard_suggestion"
|
||||
android:drawableStart="@drawable/ic_content_paste_with_padding"
|
||||
style="@style/SmartbarQuickAction.ClipboardSuggestion"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/quick_actions"
|
||||
style="@style/SmartbarContainer"
|
||||
@@ -76,109 +87,10 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- TODO: integrate a KeyboardView instead of hardcoding these buttons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/number_row"
|
||||
style="@style/SmartbarContainer"
|
||||
android:visibility="gone"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_1"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_2"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="2"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_3"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="3"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_4"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="4"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_5"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="5"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_6"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="6"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_7"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="7"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_8"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="8"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_9"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="9"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/number_row_0"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:text="0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- TODO: integrate a KeyboardView instead of hardcoding these buttons -->
|
||||
<LinearLayout
|
||||
<dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
android:id="@+id/clipboard_cursor_row"
|
||||
style="@style/SmartbarContainer"
|
||||
android:visibility="gone"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cc_select_all"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:src="@drawable/ic_select_all"
|
||||
android:tint="@drawable/button_key_enable_color_selector"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cc_copy"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:src="@drawable/ic_content_copy"
|
||||
android:tint="@drawable/button_key_enable_color_selector"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cc_arrow_left"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:src="@drawable/ic_keyboard_arrow_left"
|
||||
android:tint="@drawable/button_key_enable_color_selector"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cc_arrow_right"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:src="@drawable/ic_keyboard_arrow_right"
|
||||
android:tint="@drawable/button_key_enable_color_selector"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cc_cut"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:src="@drawable/ic_content_cut"
|
||||
android:tint="@drawable/button_key_enable_color_selector"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cc_paste"
|
||||
style="@style/SmartbarCandidate"
|
||||
android:src="@drawable/ic_content_paste"
|
||||
android:tint="@drawable/button_key_enable_color_selector"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Placeholder on the right which reserves the space for a second button -->
|
||||
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
|
||||
@@ -190,6 +102,12 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
android:id="@+id/smartbar_variant_number_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/smartbar_variant_back_only"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
11
app/src/main/res/values-ar/strings.xml
Normal file
11
app/src/main/res/values-ar/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-cs/strings.xml
Normal file
11
app/src/main/res/values-cs/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-da/strings.xml
Normal file
11
app/src/main/res/values-da/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
218
app/src/main/res/values-de/strings.xml
Normal file
218
app/src/main/res/values-de/strings.xml
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Pausieren</string>
|
||||
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Warten</string>
|
||||
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Drei-Punkte-Symbol. Zeigt an, dass durch langes Drücken mehr Zeichen verwendet werden können.</string>
|
||||
<!-- One-handed strings -->
|
||||
<string name="one_handed__close_btn_content_description" comment="Content description for the one-handed close button">Einhandmodus schließen.</string>
|
||||
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Tastatur nach links verschieben.</string>
|
||||
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Tastatur nach rechts verschieben.</string>
|
||||
<!-- Media strings -->
|
||||
<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>
|
||||
<!-- 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">Menschen</string>
|
||||
<string name="emoji__category__animals_nature" comment="Emoji category name">Tiere & Natur</string>
|
||||
<string name="emoji__category__food_drink" comment="Emoji category name">Essen & Trinken</string>
|
||||
<string name="emoji__category__travel_places" comment="Emoji category name">Reisen & Orte</string>
|
||||
<string name="emoji__category__activities" comment="Emoji category name">Aktivitäten</string>
|
||||
<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>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="smartbar__quick_action_toggle__alt" comment="Content description for the quick action toggle button in Smartbar">Schaltet mit einem Klick zwischen Wortvorschlägen und Schnellzugriffsleiste um.</string>
|
||||
<string name="smartbar__quick_action__exit_editing" comment="Content-description for the exit editing layout button in Smartbar">Textbearbeitung verlassen.</string>
|
||||
<string name="smartbar__quick_action__one_handed_mode" comment="Content-description for the one-handed quick action in Smartbar">Einhandmodus umschalten.</string>
|
||||
<string name="smartbar__quick_action__open_settings" comment="Content-description for the settings quick action in Smartbar">Einstellungen öffnen.</string>
|
||||
<string name="smartbar__quick_action__switch_to_editing_context" comment="Content-description for the editing quick action in Smartbar">Zur Textbearbeitung wechseln.</string>
|
||||
<string name="smartbar__quick_action__switch_to_media_context" comment="Content-description for the media quick action in Smartbar">Zur Medieneingabe wechseln.</string>
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title" comment="Title of Settings">Einstellungen</string>
|
||||
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">Weitere Optionen</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Hilfe & Feedback</string>
|
||||
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">Start</string>
|
||||
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">Tastatur</string>
|
||||
<string name="settings__navigation__typing" comment="Long-press hint of bottom nav item Typing in Settings">Schreiben</string>
|
||||
<string name="settings__navigation__theme" comment="Long-press hint of bottom nav item Theme in Settings">Design</string>
|
||||
<string name="settings__navigation__gestures" comment="Long-press hint of bottom nav item Gestures in Settings">Gesten</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 fragment">Willkommen bei %s</string>
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">FlorisBoard ist in Ihrem System nicht aktiviert und kann daher nicht als Eingabemethode ausgewählt werden. Hier klicken, um dieses Problem zu lösen.</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 ist nicht als Standard-Eingabemethode ausgewählt. Hier klicken, um dieses Problem zu lösen.</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">Vielen Dank, dass Sie FlorisBoard ausprobieren! Dieses Projekt befindet sich noch im Alpha-Entwicklungsstadium und es fehlen daher einige Funktionen. Wenn Sie Fehler finden oder Vorschläge zur Verbesserung haben, besuchen Sie unser Repository auf GitHub und erstellen Sie eine Fehlermeldung. Mit Ihrer Hilfe kann FlorisBoard noch besser werden. Vielen Dank!</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">Sprachen & Tastatur-Layout</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">Es scheinen keine benutzerdefinierten Eingabestile konfiguriert zu sein. Als Ausweichlösung wird daher der Eingabestil English/QWERTY benutzt!</string>
|
||||
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">Hinzufügen</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Stil hinzufügen</string>
|
||||
<string name="settings__localization__subtype_apply" comment="Subtype dialog apply button">Übernehmen</string>
|
||||
<string name="settings__localization__subtype_cancel" comment="Subtype dialog cancel button">Abbrechen</string>
|
||||
<string name="settings__localization__subtype_delete" comment="Subtype dialog delete button">Entfernen</string>
|
||||
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Benutzerdefinierten Eingabestil bearbeiten</string>
|
||||
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Gebietsschema</string>
|
||||
<string name="settings__localization__subtype_layout" comment="Label for keyboard layout dropdown in subtype dialog">Tastatur-Layout</string>
|
||||
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">Dieser Eingabestil ist bereits vorhanden!</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme fragment">Tastaturdesign</string>
|
||||
<string name="settings__theme__undefined" comment="General string for an undefined preference value">Nicht definiert</string>
|
||||
<string name="settings__theme__preset_title" comment="Label of the theme preset preference">Design</string>
|
||||
<string name="settings__theme__preset_summary" comment="Summary of the theme preset preference">Benutzerdefiniert (basierend auf %s)</string>
|
||||
<string name="settings__theme__preset_dialog_selected_theme" comment="Label of the selected themes list">Ausgewähltes Design:</string>
|
||||
<string name="settings__theme__preset_dialog_available_themes" comment="Label of the available themes list">Verfügbare Designs:</string>
|
||||
<string name="settings__theme__preset_dialog_alt_arrow_right" comment="Content description of the theme selection button in theme dialog">Pfeil rechts</string>
|
||||
<string name="settings__theme__background" comment="General label for a background preference">Hintergrundfarbe</string>
|
||||
<string name="settings__theme__background_active" comment="General label for an active background preference">Hintergrundfarbe wenn aktiv</string>
|
||||
<string name="settings__theme__background_pressed" comment="General label for a pressed background preference">Hintergrundfarbe wenn gedrückt</string>
|
||||
<string name="settings__theme__foreground" comment="General label for a foreground preference">Vordergrundfarbe</string>
|
||||
<string name="settings__theme__foreground_alt" comment="General label for an alternate foreground preference">Vordergrundfarbe (Alternativ)</string>
|
||||
<string name="settings__theme__foreground_capslock" comment="General label for a capslock foreground preference">Vordergrundfarbe (Umschalttaste festgestellt)</string>
|
||||
<string name="settings__theme__dialog_title" comment="Title of the color selection dialog for a single theme preference">Farbe wählen</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Fenster & System</string>
|
||||
<string name="settings__theme__group_keyboard" comment="Theme group label">Tastatur</string>
|
||||
<string name="settings__theme__group_key" comment="Theme group label">Taste</string>
|
||||
<string name="settings__theme__group_key_enter" comment="Theme group label">Eingabetaste</string>
|
||||
<string name="settings__theme__group_key_popup" comment="Theme group label">Tasten Pop-Up</string>
|
||||
<string name="settings__theme__group_key_shift" comment="Theme group label">Umschalttaste</string>
|
||||
<string name="settings__theme__group_media" comment="Theme group label">Medienkontext</string>
|
||||
<string name="settings__theme__group_one_handed" comment="Theme group label">Einhandmodus</string>
|
||||
<string name="settings__theme__group_one_handed_button" comment="Theme group label">Einhandmodus Schalter</string>
|
||||
<string name="settings__theme__group_smartbar" comment="Theme group label">Schnellzugriffsleiste</string>
|
||||
<string name="settings__theme__group_smartbar_button" comment="Theme group label">Schnellzugriffsleiste Schalter</string>
|
||||
<string name="pref__theme__colorPrimary_title" comment="Title of Color primary theme preference">Hauptfarbe</string>
|
||||
<string name="pref__theme__colorPrimary_summary" comment="Summary of Color primary theme preference">Wird auf Medien-Reiter und aktuelle Auswahl angewandt</string>
|
||||
<string name="pref__theme__colorPrimaryDark_title" comment="Title of Color primary dark theme preference">Hauptfarbe (dunkel)</string>
|
||||
<string name="pref__theme__colorPrimaryDark_summary" comment="Summary of Color primary dark theme preference">Zurzeit nicht in Benutzung, für zukünftige Funktionen reserviert</string>
|
||||
<string name="pref__theme__colorAccent_title" comment="Title of Color accent theme preference">Akzentfarbe</string>
|
||||
<string name="pref__theme__colorAccent_summary" comment="Summary of Color accent theme preference">Wird auf den Emoji-Reiter angewandt</string>
|
||||
<string name="pref__theme__navBarColor_title" comment="Title of Nav bar color theme preference">Farbe der Navigationsleiste</string>
|
||||
<string name="pref__theme__navBarColor_summary" comment="Summary of Nav bar color theme preference">Der Hintergrund der Navigationsleiste.</string>
|
||||
<string name="pref__theme__navBarIsLight_title" comment="Title of Nav bar is light theme preference">Dunkler Vordergrund der Navigationsleiste</string>
|
||||
<string name="pref__theme__navBarIsLight_summary" comment="Summary of Nav bar is light theme preference">EIN für dunklen oder AUS für hellen Vordergrund.</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences fragment">Tastatur-Einstellungen</string>
|
||||
<string name="pref__keyboard__group_keys__label" comment="Preference group title">Tasten</string>
|
||||
<string name="pref__keyboard__hinted_number_row__label" comment="Preference title">Zahlenreihe</string>
|
||||
<string name="pref__keyboard__hinted_number_row__summary" comment="Preference summary">Erste Reihe der Tastatur deutet Zahlenreihe im Hintergrund an</string>
|
||||
<string name="pref__keyboard__hinted_symbols__label" comment="Preference title">Symbole</string>
|
||||
<string name="pref__keyboard__hinted_symbols__summary" comment="Preference summary">Zweite und dritte Reihe der Tastatur deuten Symbole im Hintergrund an</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Layout</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label" comment="Preference value">Einhandmodus</string>
|
||||
<string name="pref__keyboard__one_handed_mode__off" comment="Preference value">Aus</string>
|
||||
<string name="pref__keyboard__one_handed_mode__right" comment="Preference value">Rechtshändermodus</string>
|
||||
<string name="pref__keyboard__one_handed_mode__left" comment="Preference value">Linkshändermodus</string>
|
||||
<string name="pref__keyboard__height_factor__label" comment="Preference title">Tastaturhöhe</string>
|
||||
<string name="pref__keyboard__height_factor__extra_short" comment="Preference value">Sehr klein</string>
|
||||
<string name="pref__keyboard__height_factor__short" comment="Preference value">Klein</string>
|
||||
<string name="pref__keyboard__height_factor__mid_short" comment="Preference value">Etwas kleiner</string>
|
||||
<string name="pref__keyboard__height_factor__normal" comment="Preference value">Normal</string>
|
||||
<string name="pref__keyboard__height_factor__mid_tall" comment="Preference value">Etwas größer</string>
|
||||
<string name="pref__keyboard__height_factor__tall" comment="Preference value">Groß</string>
|
||||
<string name="pref__keyboard__height_factor__extra_tall" comment="Preference value">Sehr groß</string>
|
||||
<string name="pref__keyboard__bottom_offset__label" comment="Preference title">Unteres Ende absetzen (für abgerundete Bildschirme)</string>
|
||||
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Tastendruck</string>
|
||||
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">Ton bei Tastendruck</string>
|
||||
<string name="pref__keyboard__sound_volume__label" comment="Preference title">Lautstärke der Tastendrucktöne</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label" comment="Preference title">Vibration bei Tastendruck</string>
|
||||
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">Vibrationsstärke bei Tastendruck</string>
|
||||
<string name="pref__keyboard__popup_visible__label" comment="Preference title">Pop-Up Sichtbarkeit</string>
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Pop-Up bei Tastendruck anzeigen</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Verzögerung bei langem Tastendruck</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Tipperlebnis</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Vorschläge</string>
|
||||
<string name="pref__suggestion__enabled__label" comment="Preference title">[NYI] Vorschläge während des Tippens anzeigen</string>
|
||||
<string name="pref__suggestion__enabled__summary" comment="Preference summary">Wird über der Tastatur angezeigt</string>
|
||||
<string name="pref__suggestion__show_instead__label" comment="Preference title">Anstatt der Vorschläge anzeigen</string>
|
||||
<string name="pref__suggestion__show_instead__number_row" comment="Preference value">Zahlenreihe</string>
|
||||
<string name="pref__suggestion__show_instead__clipboard_cursor_tools" comment="Preference value">Werkzeuge für die Zwischenablage</string>
|
||||
<string name="pref__suggestion__suggest_clipboard_content__label" comment="Preference title">Inhalt der Zwischenablage</string>
|
||||
<string name="pref__suggestion__suggest_clipboard_content__summary" comment="Preference summary">Inhalte der Zwischenablage einfügen, die zuvor kopiert wurden</string>
|
||||
<string name="pref__suggestion__use_pref_words__label" comment="Preference title">[NYI] Vorschläge für nächstes Wort</string>
|
||||
<string name="pref__suggestion__use_pref_words__summary" comment="Preference summary">Vorschläge anhand der vorherigen Wörter machen</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Korrekturen</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Autom. Groß-/Kleinschreibung</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Automatisches Großschreiben je nach aktuellem Kontext</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__label" comment="Preference title">Status der festgestellten Umschalttaste merken</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Die festgestellte Umschalttaste bleibt auch beim Wechsel in ein anderes Textfeld aktiviert</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Doppeltes Leerzeichen durch Punkt ersetzen</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Doppeltes Tippen auf die Leertaste fügt Punkt und ein Leerzeichen ein</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gesten & Glide Typing</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Glide Typing</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">[NYI] Glide Typing einschalten</string>
|
||||
<string name="pref__glide__enabled__summary" comment="Preference summary">Durch Gleiten über die Buchstaben Wort eingeben</string>
|
||||
<string name="pref__glide__show_trail__label" comment="Preference title">[NYI] Bewegungsspur anzeigen</string>
|
||||
<string name="pref__glide__show_trail__summary" comment="Preference summary">Wird jeweils nach einem Wort ausgeblendet</string>
|
||||
<string name="pref__gestures__title" comment="Preference group title">Gesten</string>
|
||||
<string name="pref__gestures__swipe_action__no_action" comment="Preference value for swipe action">Keine Aktion</string>
|
||||
<string name="pref__gestures__swipe_action__hide_keyboard" comment="Preference value for swipe action">Tastatur verstecken</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_up" comment="Preference value for swipe action">Cursor nach oben bewegen</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_down" comment="Preference value for swipe action">Cursor nach unten bewegen</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_left" comment="Preference value for swipe action">Cursor nach links bewegen</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_right" comment="Preference value for swipe action">Cursor nach rechts bewegen</string>
|
||||
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">Umschalttaste</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_subtype" comment="Preference value for swipe action">Zum vorherigen Eingabestil wechseln</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_next_subtype" comment="Preference value for swipe action">Zum nächsten Eingabestil wechseln</string>
|
||||
<string name="pref__gestures__swipe_up__label" comment="Preference title">Nach oben wischen</string>
|
||||
<string name="pref__gestures__swipe_down__label" comment="Preference title">Nach unten streichen</string>
|
||||
<string name="pref__gestures__swipe_left__label" comment="Preference title">Nach links streichen</string>
|
||||
<string name="pref__gestures__swipe_right__label" comment="Preference title">Nach rechts streichen</string>
|
||||
<string name="pref__gestures__space_bar_swipe_left__label" comment="Preference title">Leertaste nach links streichen</string>
|
||||
<string name="pref__gestures__space_bar_swipe_right__label" comment="Preference title">Leertaste nach rechts streichen</string>
|
||||
<string name="pref__gestures__delete_key_swipe_left__label" comment="Preference title">Löschtaste nach links streichen</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__label" comment="Preference title">Gesten-Geschwindigkeitsschwelle</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_slow" comment="Preference value for swipe velocity threshold">Sehr langsam</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__slow" comment="Preference value for swipe velocity threshold">Langsam</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__normal" comment="Preference value for swipe velocity threshold">Normal</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__fast" comment="Preference value for swipe velocity threshold">Schnell</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_fast" comment="Preference value for swipe velocity threshold">Sehr schnell</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__label" comment="Preference title">Gesten-Distanzschwelle</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_short" comment="Preference value for swipe distance threshold">Sehr kurz</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__short" comment="Preference value for swipe distance threshold">Kurz</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__normal" comment="Preference value for swipe distance threshold">Normal</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__long" comment="Preference value for swipe distance threshold">Lang</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_long" comment="Preference value for swipe distance threshold">Sehr lang</string>
|
||||
<string name="settings__advanced__title" comment="Title of Advanced settings activity">Erweitert</string>
|
||||
<string name="pref__advanced__settings_theme__label" comment="Label of Settings theme preference in Advanced">App-Design</string>
|
||||
<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__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Zeige die App in der Übersicht</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Über</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">App-Icon von FlorisBoard</string>
|
||||
<string name="about__view_licenses" comment="Label of View licenses button in About">Open Source-Lizenzen</string>
|
||||
<string name="about__view_privacy_policy" comment="Label of View privacy policy button in About">Datenschutzrichtlinien</string>
|
||||
<string name="about__view_source_code" comment="Label of View source code button in About">Quellcode</string>
|
||||
<string name="about__license__title" comment="Title of Open-source licenses dialog">Open Source-Lizenzen</string>
|
||||
<!-- Setup UI strings -->
|
||||
<string name="setup__title" comment="Title of Setup">Einrichtung</string>
|
||||
<string name="setup__prev_button" comment="Label of Previous button in Setup (try to find a short translation due to limited space in UI)">Zurück</string>
|
||||
<string name="setup__cancel_button" comment="Label of Cancel button in Setup">Abbrechen</string>
|
||||
<string name="setup__next_button" comment="Label of Next button in Setup (try to find a short translation due to limited space in UI)">Weiter</string>
|
||||
<string name="setup__finish_button" comment="Label of Finish button in Setup">Abschließen</string>
|
||||
<string name="setup__ok_button" comment="Label of OK button in Setup">Okay</string>
|
||||
<string name="setup__welcome__title" comment="Title of Welcome fragment in Setup">Herzlich willkommen!</string>
|
||||
<string name="setup__welcome__intro" comment="Paragraph in Welcome fragment in Setup">Vielen Dank, dass Sie FlorisBoard ausprobieren! Bevor Sie loslegen können, muss FlorisBoard in den Systemeinstellungen aktiviert und Sprache, Design, etc. eingerichtet werden... Aber keine Sorge - Der Einrichtungsassistenz wird sie durch die Konfiguration leiten!</string>
|
||||
<string name="setup__welcome__privacy" comment="Paragraph in Welcome fragment in Setup">FlorisBoard respektiert Ihre Privatsphäre vollständig und sammelt keine Nutzungsdaten. Für mehr Informationen:</string>
|
||||
<string name="setup__welcome__trust" comment="Paragraph in Welcome fragment in Setup">Der Quellcode für FlorisBoard ist für alle öffentlich zugänglich, so können Sie leicht selbst überprüfen, wie FlorisBoard im Hintergrund arbeitet. Besuchen Sie dafür das Repository.</string>
|
||||
<string name="setup__welcome__contribute" comment="Paragraph in Welcome fragment in Setup">Ein letzter Hinweis, bevor die Einrichtung startet - Wenn Sie Fehler finden oder Vorschläge zur Verbesserung haben, besuchen Sie unser Repository auf GitHub und erstellen sie eine Fehlermeldung. Mit Ihrer Hilfe kann FlorisBoard noch besser werden!</string>
|
||||
<string name="setup__welcome__outro" comment="Paragraph in Welcome fragment in Setup">Um die Einrichtung zu starten, klicken sie auf <i>WEITER</i>.</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">FlorisBoard aktivieren</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">Android verlangt, dass Tastaturen manuell aktiviert werden müssen, bevor sie benutzt werden können. Klicken Sie auf die Schaltfläche um in die Einstellungen für <i>Sprache und Eingabe</i> zu gelangen, stellen Sie sicher, dass dort \'<i>FlorisBoard</i>\' aktiviert ist.</string>
|
||||
<string name="setup__enable_ime__text_after_enabled" comment="Description of state in Enable IME fragment after user enabled">FlorisBoard wurde erfolgreich aktiviert. Um fortzufahren klicken sie auf <i>WEITER</i>!</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Öffne Sprachen und Eingabe</string>
|
||||
<string name="setup__make_default__title" comment="Title of Make IME default fragment in Setup">FlorisBoard als Standard einrichten</string>
|
||||
<string name="setup__make_default__text_before_switch" comment="Description of state in Make IME default fragment before user switched">FlorisBoard ist nun auf ihrem System aktiviert. Um es zu benutzen, wählen Sie bei der Standard-Eingabemethode FlorisBoard aus!</string>
|
||||
<string name="setup__make_default__text_after_switch" comment="Description of state in Make IME default fragment after user switched">FlorisBoard wurde erfolgreich als Standard-Tastatur ausgewählt!</string>
|
||||
<string name="setup__make_default__text_switch_button" comment="Label of switch button in Make IME default fragment">Tastatur wechseln</string>
|
||||
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Einrichtung abgeschlossen!</string>
|
||||
<!-- Crash Dialog strings -->
|
||||
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard Fehlermeldung</string>
|
||||
<string name="crash_dialog__description" comment="Description of crash dialog">Wir bitten die Unannehmlichkeiten zu entschuldigen, aber FlorisBoard wurde wegen eines Fehlers unerwartet geschlossen.\n\nWenn Sie diesen Fehler melden möchten, klicken Sie auf \"In die Zwischenablage kopieren\" und danach auf \"Fehlermeldung erstellen\". Füllen Sie die Fehlermeldung aus und fügen Sie das Protokoll ein. Mit Ihrer Hilfe kann FlorisBoard besser und stabiler für alle werden. Vielen Dank!</string>
|
||||
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">In die Zwischenablage kopieren</string>
|
||||
<string name="crash_dialog__open_bug_report_form" comment="Label of Open bug report button in crash dialog">Fehler melden (github.com)</string>
|
||||
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Schließen</string>
|
||||
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard Fehlermeldungen</string>
|
||||
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard funktioniert nicht mehr…</string>
|
||||
<string name="crash_once_notification__body" comment="Body of the notification for a single crash">Tippen, um Details anzuzeigen</string>
|
||||
<string name="crash_multiple_notification__title" comment="Title of the notification for consecutive crashes">FlorisBoard funktioniert zum wiederholten Male nicht…</string>
|
||||
<string name="crash_multiple_notification__body" comment="Body of the notification for consecutive crashes">Um eine endlose Absturzschleife zu verhindern, wurde automatisch auf die zuvor benutzte Tastatur zurückgegriffen. Tippen, um die Fehlermeldung anzuzeigen</string>
|
||||
</resources>
|
||||
11
app/src/main/res/values-el/strings.xml
Normal file
11
app/src/main/res/values-el/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-es/strings.xml
Normal file
11
app/src/main/res/values-es/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-fi/strings.xml
Normal file
11
app/src/main/res/values-fi/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-fr/strings.xml
Normal file
11
app/src/main/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-he/strings.xml
Normal file
11
app/src/main/res/values-he/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-hu/strings.xml
Normal file
11
app/src/main/res/values-hu/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
@@ -1,123 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="key__phone_pause">Pausa</string>
|
||||
<string name="key__phone_wait">Attendi</string>
|
||||
<string name="key_popup__threedots_alt">Icona a tre puntini.Se visibile, indica che è possibile utilizzare più lettere se premuto a lungo.</string>
|
||||
|
||||
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Pausa</string>
|
||||
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Attendi</string>
|
||||
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Icona a tre puntini.Se visibile, indica che è possibile utilizzare più lettere se premuto a lungo.</string>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<string name="media__tab__emojis">Emojis</string>
|
||||
<string name="media__tab__emoticons">Emoticons</string>
|
||||
<string name="media__tab__kaomoji">Kaomoji</string>
|
||||
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion">Smileys & Emotions</string>
|
||||
<string name="emoji__category__people_body">Persone & Corpo</string>
|
||||
<string name="emoji__category__animals_nature">Animali & Natura</string>
|
||||
<string name="emoji__category__food_drink">Cibo & Bevande</string>
|
||||
<string name="emoji__category__travel_places">Viaggi & Luoghi</string>
|
||||
<string name="emoji__category__activities">Attività</string>
|
||||
<string name="emoji__category__objects">Oggetti</string>
|
||||
<string name="emoji__category__symbols">Simboli</string>
|
||||
<string name="emoji__category__flags">Bandiere</string>
|
||||
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Persone & Corpo</string>
|
||||
<string name="emoji__category__animals_nature" comment="Emoji category name">Animali & Natura</string>
|
||||
<string name="emoji__category__food_drink" comment="Emoji category name">Cibo & Bevande</string>
|
||||
<string name="emoji__category__travel_places" comment="Emoji category name">Viaggi & Luoghi</string>
|
||||
<string name="emoji__category__activities" comment="Emoji category name">Attività</string>
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Oggetti</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Simboli</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Bandiere</string>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="smartbar__quick_action_toggle__alt">Attiva / disattiva azione rapida. Se premuto, alterna i suggerimenti di parole ed i pulsanti di azione rapida.</string>
|
||||
<string name="smartbar__quick_action__one_handed_mode">Attiva / disattiva la modalità a una mano.</string>
|
||||
<string name="smartbar__quick_action__open_settings">Apri Impostazioni.</string>
|
||||
<string name="smartbar__quick_action__switch_to_media_context">Passa alla visualizzazione dei media.</string>
|
||||
|
||||
<string name="smartbar__quick_action_toggle__alt" comment="Content description for the quick action toggle button in Smartbar">Attiva / disattiva azione rapida. Se premuto, alterna i suggerimenti di parole ed i pulsanti di azione rapida.</string>
|
||||
<string name="smartbar__quick_action__one_handed_mode" comment="Content-description for the one-handed quick action in Smartbar">Attiva / disattiva la modalità a una mano.</string>
|
||||
<string name="smartbar__quick_action__open_settings" comment="Content-description for the settings quick action in Smartbar">Apri Impostazioni.</string>
|
||||
<string name="smartbar__quick_action__switch_to_media_context" comment="Content-description for the media quick action in Smartbar">Passa alla visualizzazione dei media.</string>
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title">Impostazioni</string>
|
||||
<string name="settings__menu">Altre opzioni</string>
|
||||
<string name="settings__menu_about">Informazioni su</string>
|
||||
<string name="settings__menu_help">Aiuto & feedback</string>
|
||||
<string name="settings__navigation__home">Home</string>
|
||||
<string name="settings__navigation__keyboard">Tastiera</string>
|
||||
<string name="settings__navigation__typing">Digitazione</string>
|
||||
<string name="settings__navigation__theme">Tema</string>
|
||||
<string name="settings__navigation__gestures">Gesti</string>
|
||||
|
||||
<string name="settings__home__title">Benvenuto in %s</string>
|
||||
<string name="settings__home__ime_not_enabled">FlorisBoard non è abilitato nel sistema e quindi non sarà disponibile come metodo di immissione.Clicca quì per risolvere questo problema.</string>
|
||||
<string name="settings__home__ime_not_selected">FlorisBoard non è la tastiera predefinita. Clicca quì per risolvere questo problema.</string>
|
||||
<string name="settings__home__contribute">Grazie per aver provato FlorisBoard! Questo progetto è ancora in fase alfa e quindi manca di alcune funzionalità. Se trovate qualche bug o volete dare un suggerimento, date un\'occhiata al repo su GitHub e segnalate un problema. Questo aiuta a rendere FlorisBoard migliore. Grazie!</string>
|
||||
|
||||
<string name="settings__localization__title">Lingue & Layout della tastiera</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning">Sembra che tu non abbia configurato nessuno stile di input personalizzato. Come ripiego verrà utilizzato lo stile input English/QWERTY!</string>
|
||||
<string name="settings__localization__subtype_add">Aggiungi</string>
|
||||
<string name="settings__localization__subtype_add_title">Aggiungi stile input</string>
|
||||
<string name="settings__localization__subtype_apply">Applica</string>
|
||||
<string name="settings__localization__subtype_cancel">Annulla</string>
|
||||
<string name="settings__localization__subtype_delete">Elimina</string>
|
||||
<string name="settings__localization__subtype_edit_title">Modifica stile di input</string>
|
||||
<string name="settings__localization__subtype_locale">Locale</string>
|
||||
<string name="settings__localization__subtype_layout">Layout della tastiera</string>
|
||||
<string name="settings__localization__subtype_error_already_exists">Questo stile di input esiste già !</string>
|
||||
|
||||
<string name="settings__theme__title">Tema tastiera</string>
|
||||
<string name="pref__theme__name__label">Tema tastiera</string>
|
||||
|
||||
<string name="settings__keyboard__title">Tastiera preferenze</string>
|
||||
<string name="pref__keyboard__group_layout__label">Layout</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label">Modalità ad una mano</string>
|
||||
<string name="pref__keyboard__height_factor__label">Altezza tastiera</string>
|
||||
<string name="pref__keyboard__group_keypress__label">Pressione tasti</string>
|
||||
<string name="pref__keyboard__sound_enabled__label">Suono pressione tasti</string>
|
||||
<string name="pref__keyboard__sound_volume__label">Volume del suono alla pressione dei tasti</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label">Vibrazione alla pressione dei tasti</string>
|
||||
<string name="pref__keyboard__vibration_strength__label">Intensità della vibrazione alla pressione dei tasti</string>
|
||||
<string name="pref__keyboard__popup_visible__label">Visibilità Popup</string>
|
||||
<string name="pref__keyboard__popup_visible__summary">Mostra popup quando si preme un tasto</string>
|
||||
<string name="pref__keyboard__long_press_delay__label">Ritardo lunga pressione tasti</string>
|
||||
|
||||
<string name="settings__typing__title">Esperienza di digitazione</string>
|
||||
<string name="pref__suggestion__title">Suggerimenti</string>
|
||||
<string name="pref__suggestion__enabled__label">Visualizza suggerimenti mentre digiti</string>
|
||||
<string name="pref__suggestion__enabled__summary">Verrà visualizzato nella parte superiore della tastiera</string>
|
||||
<string name="pref__suggestion__use_pref_words__label">Suggerimenti per la parola successiva</string>
|
||||
<string name="pref__suggestion__use_pref_words__summary">Utilizzare le parole precedenti per generare suggerimenti</string>
|
||||
<string name="pref__correction__title">Correzioni</string>
|
||||
<string name="pref__correction__double_space_period__label">Doppio tocco barra spaziatrice</string>
|
||||
<string name="pref__correction__double_space_period__summary">Doppio tocco su barra spaziatrice per mettere il punto (.) seguito da uno spazio</string>
|
||||
|
||||
<string name="settings__gestures__title">Gesti & Digitazione a scorrimento</string>
|
||||
|
||||
<string name="settings__advanced__title">Avanzate</string>
|
||||
<string name="pref__advanced__settings_theme__label">Impostazioni tema</string>
|
||||
<string name="pref__advanced__show_app_icon__label">Mostra icona nel launcher</string>
|
||||
|
||||
<string name="settings__title" comment="Title of Settings">Impostazioni</string>
|
||||
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">Altre opzioni</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Aiuto & feedback</string>
|
||||
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">Tastiera</string>
|
||||
<string name="settings__navigation__typing" comment="Long-press hint of bottom nav item Typing in Settings">Digitazione</string>
|
||||
<string name="settings__navigation__theme" comment="Long-press hint of bottom nav item Theme in Settings">Tema</string>
|
||||
<string name="settings__navigation__gestures" comment="Long-press hint of bottom nav item Gestures in Settings">Gesti</string>
|
||||
<string name="settings__home__title" comment="Title of the Home fragment">Benvenuto in %s</string>
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">FlorisBoard non è abilitato nel sistema e quindi non sarà disponibile come metodo di immissione.Clicca quì per risolvere questo problema.</string>
|
||||
<string name="settings__home__ime_not_selected" comment="Warning message shown in Home fragment when FlorisBoard is not selected as the default keyboard">FlorisBoard non è la tastiera predefinita. Clicca quì per risolvere questo problema.</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">Grazie per aver provato FlorisBoard! Questo progetto è ancora in fase alfa e quindi manca di alcune funzionalità. Se trovate qualche bug o volete dare un suggerimento, date un\'occhiata al repo su GitHub e segnalate un problema. Questo aiuta a rendere FlorisBoard migliore. Grazie!</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">Lingue & Layout della tastiera</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">Sembra che tu non abbia configurato nessuno stile di input personalizzato. Come ripiego verrà utilizzato lo stile input English/QWERTY!</string>
|
||||
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">Aggiungi</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Aggiungi stile input</string>
|
||||
<string name="settings__localization__subtype_apply" comment="Subtype dialog apply button">Applica</string>
|
||||
<string name="settings__localization__subtype_cancel" comment="Subtype dialog cancel button">Annulla</string>
|
||||
<string name="settings__localization__subtype_delete" comment="Subtype dialog delete button">Elimina</string>
|
||||
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Modifica stile di input</string>
|
||||
<string name="settings__localization__subtype_layout" comment="Label for keyboard layout dropdown in subtype dialog">Layout della tastiera</string>
|
||||
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">Questo stile di input esiste già !</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme fragment">Tema tastiera</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences fragment">Tastiera preferenze</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label" comment="Preference value">Modalità ad una mano</string>
|
||||
<string name="pref__keyboard__height_factor__label" comment="Preference title">Altezza tastiera</string>
|
||||
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Pressione tasti</string>
|
||||
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">Suono pressione tasti</string>
|
||||
<string name="pref__keyboard__sound_volume__label" comment="Preference title">Volume del suono alla pressione dei tasti</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label" comment="Preference title">Vibrazione alla pressione dei tasti</string>
|
||||
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">Intensità della vibrazione alla pressione dei tasti</string>
|
||||
<string name="pref__keyboard__popup_visible__label" comment="Preference title">Visibilità Popup</string>
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Mostra popup quando si preme un tasto</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Ritardo lunga pressione tasti</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Esperienza di digitazione</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Suggerimenti</string>
|
||||
<string name="pref__suggestion__enabled__label" comment="Preference title">Visualizza suggerimenti mentre digiti</string>
|
||||
<string name="pref__suggestion__enabled__summary" comment="Preference summary">Verrà visualizzato nella parte superiore della tastiera</string>
|
||||
<string name="pref__suggestion__use_pref_words__label" comment="Preference title">Suggerimenti per la parola successiva</string>
|
||||
<string name="pref__suggestion__use_pref_words__summary" comment="Preference summary">Utilizzare le parole precedenti per generare suggerimenti</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Correzioni</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Doppio tocco barra spaziatrice</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Doppio tocco su barra spaziatrice per mettere il punto (.) seguito da uno spazio</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gesti & Digitazione a scorrimento</string>
|
||||
<string name="settings__advanced__title" comment="Title of Advanced settings activity">Avanzate</string>
|
||||
<string name="pref__advanced__settings_theme__label" comment="Label of Settings theme preference in Advanced">Impostazioni tema</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Mostra icona nel launcher</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title">Informazioni su</string>
|
||||
<string name="about__app_icon_content_description">Icona dell\'app FlorisBoard</string>
|
||||
<string name="about__view_licenses">Licenze open source</string>
|
||||
<string name="about__view_privacy_policy">Norme sulla privacy</string>
|
||||
<string name="about__view_source_code">Codice sorgente</string>
|
||||
|
||||
<string name="about__license__title">Licenze open source</string>
|
||||
|
||||
<string name="about__title" comment="Title of About activity">Informazioni su</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Icona dell\'app FlorisBoard</string>
|
||||
<string name="about__view_licenses" comment="Label of View licenses button in About">Licenze open source</string>
|
||||
<string name="about__view_privacy_policy" comment="Label of View privacy policy button in About">Norme sulla privacy</string>
|
||||
<string name="about__view_source_code" comment="Label of View source code button in About">Codice sorgente</string>
|
||||
<string name="about__license__title" comment="Title of Open-source licenses dialog">Licenze open source</string>
|
||||
<!-- Setup UI strings -->
|
||||
<string name="setup__title">Configurazione</string>
|
||||
<string name="setup__prev_button">Precedente</string>
|
||||
<string name="setup__cancel_button">Annulla</string>
|
||||
<string name="setup__next_button">Avanti</string>
|
||||
<string name="setup__finish_button">Fine</string>
|
||||
<string name="setup__ok_button">OK</string>
|
||||
|
||||
<string name="setup__welcome__title">Benvenuto!</string>
|
||||
<string name="setup__welcome__intro">Grazie per aver provato FlorisBoard! Prima che possiate iniziare ad usarlo, dobbiamo fare le solite cose e abilitarlo nelle impostazioni di sistema, impostare la vostra lingua/ il layout preferito, ecc... Ma non preoccuparti: segui questa procedura guidata </string>
|
||||
<string name="setup__welcome__privacy">[[ TODO: inserisci quì la descrizione della privacy ]]</string>
|
||||
<string name="setup__welcome__trust">Il codice sorgente di FlorisBoard è accessibile pubblicamente a chiunque, quindi puoi facilmente rivedere cosa fa FlorisBoard in background. Controlla il link nel repository in basso.</string>
|
||||
<string name="setup__welcome__contribute">Un\'ultima cosa prima di iniziare l\'installazione - se riscontri errori / arresti anomali / problemi con FlorisBoard o hai una richiesta di funzionalità - vai al repository GitHub collegato di seguito e presenta un problema. Questo aiuta a migliorare l\'esperienza per tutti gli utenti!</string>
|
||||
<string name="setup__welcome__outro">Per avviare l\'installazione, fai clic su <i>AVANTI</i>.</string>
|
||||
|
||||
<string name="setup__enable_ime__title">Abilita FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled">Android richiede che ogni tastiera personalizzata debba essere abilitata manualmente prima di poterla utilizzare. Fai clic sul pulsante in basso per passare a <i>Lingua & Input</i>impostazioni, quindi assicurati di selezionare\'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input">Apri lingue & Impostazioni di input</string>
|
||||
<string name="setup__enable_ime__text_after_enabled">FlorisBoard è stato abilitato con successo. Per continuare, fai clic su <i>AVANTI</i>!</string>
|
||||
|
||||
<string name="setup__make_default__title">Rendi FlorisBoard predefinita</string>
|
||||
<string name="setup__make_default__text_before_switch">FlorisBoard è ora abilitato nel tuo sistema. Per usarlo attivamente, passa a FlorisBoard selezionandolo nella finestra di dialogo del selettore di input!</string>
|
||||
<string name="setup__make_default__text_switch_button">Cambia tastiera</string>
|
||||
<string name="setup__make_default__text_after_switch">Hai cambiato con successo la tastiera predefinita su FlorisBoard!</string>
|
||||
|
||||
<string name="setup__finish__title">Installazione terminata!</string>
|
||||
<string name="setup__title" comment="Title of Setup">Configurazione</string>
|
||||
<string name="setup__prev_button" comment="Label of Previous button in Setup (try to find a short translation due to limited space in UI)">Precedente</string>
|
||||
<string name="setup__cancel_button" comment="Label of Cancel button in Setup">Annulla</string>
|
||||
<string name="setup__next_button" comment="Label of Next button in Setup (try to find a short translation due to limited space in UI)">Avanti</string>
|
||||
<string name="setup__finish_button" comment="Label of Finish button in Setup">Fine</string>
|
||||
<string name="setup__welcome__title" comment="Title of Welcome fragment in Setup">Benvenuto!</string>
|
||||
<string name="setup__welcome__intro" comment="Paragraph in Welcome fragment in Setup">Grazie per aver provato FlorisBoard! Prima che possiate iniziare ad usarlo, dobbiamo fare le solite cose e abilitarlo nelle impostazioni di sistema, impostare la vostra lingua/ il layout preferito, ecc… Ma non preoccuparti: segui questa procedura guidata </string>
|
||||
<string name="setup__welcome__trust" comment="Paragraph in Welcome fragment in Setup">Il codice sorgente di FlorisBoard è accessibile pubblicamente a chiunque, quindi puoi facilmente rivedere cosa fa FlorisBoard in background. Controlla il link nel repository in basso.</string>
|
||||
<string name="setup__welcome__contribute" comment="Paragraph in Welcome fragment in Setup">Un\'ultima cosa prima di iniziare l\'installazione - se riscontri errori / arresti anomali / problemi con FlorisBoard o hai una richiesta di funzionalità - vai al repository GitHub collegato di seguito e presenta un problema. Questo aiuta a migliorare l\'esperienza per tutti gli utenti!</string>
|
||||
<string name="setup__welcome__outro" comment="Paragraph in Welcome fragment in Setup">Per avviare l\'installazione, fai clic su <i>AVANTI</i>.</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">Abilita FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">Android richiede che ogni tastiera personalizzata debba essere abilitata manualmente prima di poterla utilizzare. Fai clic sul pulsante in basso per passare a <i>Lingua & Input</i>impostazioni, quindi assicurati di selezionare\'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_after_enabled" comment="Description of state in Enable IME fragment after user enabled">FlorisBoard è stato abilitato con successo. Per continuare, fai clic su <i>AVANTI</i>!</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Apri lingue & Impostazioni di input</string>
|
||||
<string name="setup__make_default__title" comment="Title of Make IME default fragment in Setup">Rendi FlorisBoard predefinita</string>
|
||||
<string name="setup__make_default__text_before_switch" comment="Description of state in Make IME default fragment before user switched">FlorisBoard è ora abilitato nel tuo sistema. Per usarlo attivamente, passa a FlorisBoard selezionandolo nella finestra di dialogo del selettore di input!</string>
|
||||
<string name="setup__make_default__text_after_switch" comment="Description of state in Make IME default fragment after user switched">Hai cambiato con successo la tastiera predefinita su FlorisBoard!</string>
|
||||
<string name="setup__make_default__text_switch_button" comment="Label of switch button in Make IME default fragment">Cambia tastiera</string>
|
||||
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Installazione terminata!</string>
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="inputView_baseHeight">214dp</dimen>
|
||||
<dimen name="smartbar_baseHeight">34dp</dimen>
|
||||
<dimen name="textKeyboardView_baseHeight">184dp</dimen>
|
||||
<dimen name="mediaKeyboardView_baseHeight">@dimen/inputView_baseHeight</dimen>
|
||||
|
||||
<fraction name="inputView_minHeightFraction">45%p</fraction>
|
||||
<fraction name="inputView_maxHeightFraction">46%p</fraction>
|
||||
|
||||
<dimen name="media_tab_paddingH">10dp</dimen>
|
||||
</resources>
|
||||
|
||||
11
app/src/main/res/values-nl/strings.xml
Normal file
11
app/src/main/res/values-nl/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-no/strings.xml
Normal file
11
app/src/main/res/values-no/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-pl/strings.xml
Normal file
11
app/src/main/res/values-pl/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
218
app/src/main/res/values-pt-rBR/strings.xml
Normal file
218
app/src/main/res/values-pt-rBR/strings.xml
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Pausar</string>
|
||||
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Esperar</string>
|
||||
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Ícone de três pontos. Se visível, indica que mais letras podem ser usadas se pressionadas por mais tempo.</string>
|
||||
<!-- One-handed strings -->
|
||||
<string name="one_handed__close_btn_content_description" comment="Content description for the one-handed close button">Fechar o modo uma mão.</string>
|
||||
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Mover teclado para a esquerda.</string>
|
||||
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Mover teclado para a direita.</string>
|
||||
<!-- Media strings -->
|
||||
<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>
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Sorrisos & Emoções</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">Pessoas & Corpo</string>
|
||||
<string name="emoji__category__animals_nature" comment="Emoji category name">Animais & Natureza</string>
|
||||
<string name="emoji__category__food_drink" comment="Emoji category name">Comida & Bebida</string>
|
||||
<string name="emoji__category__travel_places" comment="Emoji category name">Viagem & Lugares</string>
|
||||
<string name="emoji__category__activities" comment="Emoji category name">Atividades</string>
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Objetos</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Símbolos</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Bandeiras</string>
|
||||
<!-- Smartbar strings -->
|
||||
<string name="smartbar__quick_action_toggle__alt" comment="Content description for the quick action toggle button in Smartbar">Alternar ação rápida. Se pressionado, alterna entre as sugestões de palavra e os botões de ação rápida.</string>
|
||||
<string name="smartbar__quick_action__exit_editing" comment="Content-description for the exit editing layout button in Smartbar">Sair do painel de edição de texto.</string>
|
||||
<string name="smartbar__quick_action__one_handed_mode" comment="Content-description for the one-handed quick action in Smartbar">Alternar estado do modo de uma mão.</string>
|
||||
<string name="smartbar__quick_action__open_settings" comment="Content-description for the settings quick action in Smartbar">Abrir configurações.</string>
|
||||
<string name="smartbar__quick_action__switch_to_editing_context" comment="Content-description for the editing quick action in Smartbar">Mudar para painel de edição de texto.</string>
|
||||
<string name="smartbar__quick_action__switch_to_media_context" comment="Content-description for the media quick action in Smartbar">Mudar para visualização de entrada de mídia.</string>
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title" comment="Title of Settings">Configurações</string>
|
||||
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">Mais opções</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Ajuda & feedback</string>
|
||||
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">Início</string>
|
||||
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">Teclado</string>
|
||||
<string name="settings__navigation__typing" comment="Long-press hint of bottom nav item Typing in Settings">Digitação</string>
|
||||
<string name="settings__navigation__theme" comment="Long-press hint of bottom nav item Theme in Settings">Tema</string>
|
||||
<string name="settings__navigation__gestures" comment="Long-press hint of bottom nav item Gestures in Settings">Gestos</string>
|
||||
<string name="settings__default" comment="General string which is used when a preference has the default value set">Padrão</string>
|
||||
<string name="settings__system_default" comment="General string which is used when a preference has the system default value set">Padrão do sistema</string>
|
||||
<string name="settings__home__title" comment="Title of the Home fragment">Bem-vindo ao %s</string>
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">O FlorisBoard não está ativado no sistema e, portanto, não estará disponível como um método de entrada no alternador de métodos de entrada. Clique aqui para resolver este problema.</string>
|
||||
<string name="settings__home__ime_not_selected" comment="Warning message shown in Home fragment when FlorisBoard is not selected as the default keyboard">O FlorisBoard não foi selecionado como o método de entrada padrão. Clique aqui para resolver este problema.</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">Obrigado por experimentar o FlorisBoard! Este projeto ainda está em alpha e, portanto, faltando recursos. Se você encontrar algum bug ou quiser fazer uma sugestão, por favor, confira o repo no GitHub e crie um problema. Isso ajuda a tornar o FlorisBoard melhor. Obrigado!</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">Idiomas & Formatos do teclado</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">Parece que você não configurou nenhum formato de digitação. Como alternativa, será utilizado o formato Inglês/QWERTY!</string>
|
||||
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">Adicionar</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Adicionar formato de digitação</string>
|
||||
<string name="settings__localization__subtype_apply" comment="Subtype dialog apply button">Aplicar</string>
|
||||
<string name="settings__localization__subtype_cancel" comment="Subtype dialog cancel button">Cancelar</string>
|
||||
<string name="settings__localization__subtype_delete" comment="Subtype dialog delete button">Excluir</string>
|
||||
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Editar formato de digitação</string>
|
||||
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Idioma</string>
|
||||
<string name="settings__localization__subtype_layout" comment="Label for keyboard layout dropdown in subtype dialog">Formato do teclado</string>
|
||||
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">Este formato de digitação já existe!</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme fragment">Tema do teclado</string>
|
||||
<string name="settings__theme__undefined" comment="General string for an undefined preference value">Indefinido</string>
|
||||
<string name="settings__theme__preset_title" comment="Label of the theme preset preference">Tema</string>
|
||||
<string name="settings__theme__preset_summary" comment="Summary of the theme preset preference">Personalizado (baseado no %s)</string>
|
||||
<string name="settings__theme__preset_dialog_selected_theme" comment="Label of the selected themes list">Tema selecionado:</string>
|
||||
<string name="settings__theme__preset_dialog_available_themes" comment="Label of the available themes list">Temas disponíveis:</string>
|
||||
<string name="settings__theme__preset_dialog_alt_arrow_right" comment="Content description of the theme selection button in theme dialog">Seta à direita</string>
|
||||
<string name="settings__theme__background" comment="General label for a background preference">Cor do plano de fundo</string>
|
||||
<string name="settings__theme__background_active" comment="General label for an active background preference">Cor do plano de fundo quando ativa</string>
|
||||
<string name="settings__theme__background_pressed" comment="General label for a pressed background preference">Cor do plano fundo quando pressionada</string>
|
||||
<string name="settings__theme__foreground" comment="General label for a foreground preference">Cor do primeiro plano</string>
|
||||
<string name="settings__theme__foreground_alt" comment="General label for an alternate foreground preference">Cor do primeiro plano (alternativa)</string>
|
||||
<string name="settings__theme__foreground_capslock" comment="General label for a capslock foreground preference">Cor do primeiro plano (caps lock)</string>
|
||||
<string name="settings__theme__dialog_title" comment="Title of the color selection dialog for a single theme preference">Selecionar uma cor</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Janela & Sistema</string>
|
||||
<string name="settings__theme__group_keyboard" comment="Theme group label">Teclado</string>
|
||||
<string name="settings__theme__group_key" comment="Theme group label">Tecla</string>
|
||||
<string name="settings__theme__group_key_enter" comment="Theme group label">Tecla Enter</string>
|
||||
<string name="settings__theme__group_key_popup" comment="Theme group label">Popup da tecla</string>
|
||||
<string name="settings__theme__group_key_shift" comment="Theme group label">Tecla Shift</string>
|
||||
<string name="settings__theme__group_media" comment="Theme group label">Contexto de mídia</string>
|
||||
<string name="settings__theme__group_one_handed" comment="Theme group label">Uma mão</string>
|
||||
<string name="settings__theme__group_one_handed_button" comment="Theme group label">Botões do modo uma mão</string>
|
||||
<string name="settings__theme__group_smartbar" comment="Theme group label">Barra Superior</string>
|
||||
<string name="settings__theme__group_smartbar_button" comment="Theme group label">Botões da barra superior</string>
|
||||
<string name="pref__theme__colorPrimary_title" comment="Title of Color primary theme preference">Cor primária</string>
|
||||
<string name="pref__theme__colorPrimary_summary" comment="Summary of Color primary theme preference">Aplicado à ondulação da aba principal de mídia e destaque de seleção</string>
|
||||
<string name="pref__theme__colorPrimaryDark_title" comment="Title of Color primary dark theme preference">Cor primária (escuro)</string>
|
||||
<string name="pref__theme__colorPrimaryDark_summary" comment="Summary of Color primary dark theme preference">Atualmente não utilizado, reservado para implementação futura</string>
|
||||
<string name="pref__theme__colorAccent_title" comment="Title of Color accent theme preference">Cor de destaque</string>
|
||||
<string name="pref__theme__colorAccent_summary" comment="Summary of Color accent theme preference">Aplicado à ondulação da aba emoji</string>
|
||||
<string name="pref__theme__navBarColor_title" comment="Title of Nav bar color theme preference">Cor da barra de navegação</string>
|
||||
<string name="pref__theme__navBarColor_summary" comment="Summary of Nav bar color theme preference">O plano de fundo da barra de navegação.</string>
|
||||
<string name="pref__theme__navBarIsLight_title" comment="Title of Nav bar is light theme preference">Barra de navegação escura em primeiro plano</string>
|
||||
<string name="pref__theme__navBarIsLight_summary" comment="Summary of Nav bar is light theme preference">Ligue para escurecer ou desligue para clarear o primeiro plano.</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences fragment">Preferências do Teclado</string>
|
||||
<string name="pref__keyboard__group_keys__label" comment="Preference group title">Teclas</string>
|
||||
<string name="pref__keyboard__hinted_number_row__label" comment="Preference title">Linha de números</string>
|
||||
<string name="pref__keyboard__hinted_number_row__summary" comment="Preference summary">A primeira linha do layout de caracteres sugere uma linha de números</string>
|
||||
<string name="pref__keyboard__hinted_symbols__label" comment="Preference title">Símbolos</string>
|
||||
<string name="pref__keyboard__hinted_symbols__summary" comment="Preference summary">A segunda e terceira linha do layout de caracteres sugere símbolos</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Layout</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label" comment="Preference value">Modo uma mão</string>
|
||||
<string name="pref__keyboard__one_handed_mode__off" comment="Preference value">Desligado</string>
|
||||
<string name="pref__keyboard__one_handed_mode__right" comment="Preference value">Modo destro</string>
|
||||
<string name="pref__keyboard__one_handed_mode__left" comment="Preference value">Modo canhoto</string>
|
||||
<string name="pref__keyboard__height_factor__label" comment="Preference title">Altura do teclado</string>
|
||||
<string name="pref__keyboard__height_factor__extra_short" comment="Preference value">Muito baixo</string>
|
||||
<string name="pref__keyboard__height_factor__short" comment="Preference value">Baixo</string>
|
||||
<string name="pref__keyboard__height_factor__mid_short" comment="Preference value">Relativamente baixo</string>
|
||||
<string name="pref__keyboard__height_factor__normal" comment="Preference value">Normal</string>
|
||||
<string name="pref__keyboard__height_factor__mid_tall" comment="Preference value">Relativamente alto</string>
|
||||
<string name="pref__keyboard__height_factor__tall" comment="Preference value">Alto</string>
|
||||
<string name="pref__keyboard__height_factor__extra_tall" comment="Preference value">Muito alto</string>
|
||||
<string name="pref__keyboard__bottom_offset__label" comment="Preference title">Deslocamento inferior (para telas curvas)</string>
|
||||
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Pressionar tecla</string>
|
||||
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">Som ao pressionar uma tecla</string>
|
||||
<string name="pref__keyboard__sound_volume__label" comment="Preference title">Volume do som ao pressionar uma tecla</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label" comment="Preference title">Vibrar ao pressionar uma tecla</string>
|
||||
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">Força da vibração ao pressionar uma tecla</string>
|
||||
<string name="pref__keyboard__popup_visible__label" comment="Preference title">Visibilidade do PopUp</string>
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Mostrar popup quando pressionar uma tecla</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Atraso ao pressionar e segurar uma tecla</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Experiência de digitação</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Sugestões</string>
|
||||
<string name="pref__suggestion__enabled__label" comment="Preference title">[NYI] Mostrar sugestões enquanto você digita</string>
|
||||
<string name="pref__suggestion__enabled__summary" comment="Preference summary">Vai aparecer em cima do teclado</string>
|
||||
<string name="pref__suggestion__show_instead__label" comment="Preference title">O que mostrar em vez de sugestões</string>
|
||||
<string name="pref__suggestion__show_instead__number_row" comment="Preference value">Linha de números</string>
|
||||
<string name="pref__suggestion__show_instead__clipboard_cursor_tools" comment="Preference value">Ferramentas de cursor da área de transferência</string>
|
||||
<string name="pref__suggestion__suggest_clipboard_content__label" comment="Preference title">Sugestões de conteúdo da área de transferência</string>
|
||||
<string name="pref__suggestion__suggest_clipboard_content__summary" comment="Preference summary">Sugerir conteúdo de área de transferência para colar se copiado anteriormente</string>
|
||||
<string name="pref__suggestion__use_pref_words__label" comment="Preference title">[NYI] Sugestões de próxima palavra</string>
|
||||
<string name="pref__suggestion__use_pref_words__summary" comment="Preference summary">Usar palavras anteriores para gerar sugestões</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Correções</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Capitalização automática</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Deixar palavras em maiúsculo com base no contexto de entrada atual</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__label" comment="Preference title">Lembrar do estado do caps lock</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">O caps lock permanecerá ligado ao mudar para outro campo de texto</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Dois espaços para ponto final</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Tocar duas vezes na barra de espaço insere um ponto final seguido por um espaço</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestos & Digitação deslizante</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Digitação deslizante</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">[NYI] Ativar digitação deslizante</string>
|
||||
<string name="pref__glide__enabled__summary" comment="Preference summary">Digitar uma palavra deslizando o dedo através de suas letras</string>
|
||||
<string name="pref__glide__show_trail__label" comment="Preference title">[NYI] Mostrar trilha de deslizamento</string>
|
||||
<string name="pref__glide__show_trail__summary" comment="Preference summary">Desaparecerá após cada palavra</string>
|
||||
<string name="pref__gestures__title" comment="Preference group title">Gestos</string>
|
||||
<string name="pref__gestures__swipe_action__no_action" comment="Preference value for swipe action">Nenhuma ação</string>
|
||||
<string name="pref__gestures__swipe_action__hide_keyboard" comment="Preference value for swipe action">Esconder teclado</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_up" comment="Preference value for swipe action">Mover cursor para cima</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_down" comment="Preference value for swipe action">Mover cursor para baixo</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_left" comment="Preference value for swipe action">Mover cursor para esquerda</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_right" comment="Preference value for swipe action">Mover cursor para direita</string>
|
||||
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">Shift</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_subtype" comment="Preference value for swipe action">Mudar para formato de digitação anterior</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_next_subtype" comment="Preference value for swipe action">Mudar para próximo formato de digitação</string>
|
||||
<string name="pref__gestures__swipe_up__label" comment="Preference title">Deslizar para cima</string>
|
||||
<string name="pref__gestures__swipe_down__label" comment="Preference title">Deslizar para baixo</string>
|
||||
<string name="pref__gestures__swipe_left__label" comment="Preference title">Deslizar para esquerda</string>
|
||||
<string name="pref__gestures__swipe_right__label" comment="Preference title">Deslizar para direita</string>
|
||||
<string name="pref__gestures__space_bar_swipe_left__label" comment="Preference title">Deslizar barra de espaço para esquerda</string>
|
||||
<string name="pref__gestures__space_bar_swipe_right__label" comment="Preference title">Deslizar barra de espaço para direita</string>
|
||||
<string name="pref__gestures__delete_key_swipe_left__label" comment="Preference title">Deslizar tecla excluir para esquerda</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__label" comment="Preference title">Limite de velocidade do deslizamento</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_slow" comment="Preference value for swipe velocity threshold">Muito lento</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__slow" comment="Preference value for swipe velocity threshold">Lento</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__normal" comment="Preference value for swipe velocity threshold">Normal</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__fast" comment="Preference value for swipe velocity threshold">Rápido</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_fast" comment="Preference value for swipe velocity threshold">Muito rápido</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__label" comment="Preference title">Limite de distância do deslizamento</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_short" comment="Preference value for swipe distance threshold">Muito curta</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__short" comment="Preference value for swipe distance threshold">Curta</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__normal" comment="Preference value for swipe distance threshold">Normal</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__long" comment="Preference value for swipe distance threshold">Longa</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_long" comment="Preference value for swipe distance threshold">Muito longa</string>
|
||||
<string name="settings__advanced__title" comment="Title of Advanced settings activity">Avançado</string>
|
||||
<string name="pref__advanced__settings_theme__label" comment="Label of Settings theme preference in Advanced">Configurações de tema</string>
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">Claro</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Escuro</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Mostrar ícone do aplicativo no launcher</string>
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title" comment="Title of About activity">Sobre</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">Ícone de aplicativo do FlorisBoard</string>
|
||||
<string name="about__view_licenses" comment="Label of View licenses button in About">Licenças open source</string>
|
||||
<string name="about__view_privacy_policy" comment="Label of View privacy policy button in About">Política de privacidade</string>
|
||||
<string name="about__view_source_code" comment="Label of View source code button in About">Código fonte</string>
|
||||
<string name="about__license__title" comment="Title of Open-source licenses dialog">Licenças open source</string>
|
||||
<!-- Setup UI strings -->
|
||||
<string name="setup__title" comment="Title of Setup">Configurar</string>
|
||||
<string name="setup__prev_button" comment="Label of Previous button in Setup (try to find a short translation due to limited space in UI)">Anterior</string>
|
||||
<string name="setup__cancel_button" comment="Label of Cancel button in Setup">Cancelar</string>
|
||||
<string name="setup__next_button" comment="Label of Next button in Setup (try to find a short translation due to limited space in UI)">Próximo</string>
|
||||
<string name="setup__finish_button" comment="Label of Finish button in Setup">Concluir</string>
|
||||
<string name="setup__ok_button" comment="Label of OK button in Setup">OK</string>
|
||||
<string name="setup__welcome__title" comment="Title of Welcome fragment in Setup">Bem-vindo!</string>
|
||||
<string name="setup__welcome__intro" comment="Paragraph in Welcome fragment in Setup">Obrigado por experimentar o FlorisBoard! Antes de começar a usá-lo, temos que fazer as coisas usuais e ativá-lo nas configurações do sistema, configurar seu idioma/formato de digitação preferido, etc… Mas não se preocupe - o assistente de configuração vai guiá-lo através disso!</string>
|
||||
<string name="setup__welcome__privacy" comment="Paragraph in Welcome fragment in Setup">O FlorisBoard respeita totalmente sua privacidade e não coleta dados do usuário. Para mais informações, consulte aqui:</string>
|
||||
<string name="setup__welcome__trust" comment="Paragraph in Welcome fragment in Setup">O código-fonte do FlorisBoard é acessível publicamente para qualquer pessoa, para que você possa facilmente rever o que o FlorisBoard faz em segundo plano. Confira o link do repositório abaixo.</string>
|
||||
<string name="setup__welcome__contribute" comment="Paragraph in Welcome fragment in Setup">Uma última coisa antes de iniciar a configuração - se você encontrar alguns bugs/travamentos/problemas com o FlorisBoard ou tiver uma solicitação de recurso - vá até o repositório do GitHub vinculado abaixo e crie um problema. Isso ajuda na melhoria da experiência de todos os usuários!</string>
|
||||
<string name="setup__welcome__outro" comment="Paragraph in Welcome fragment in Setup">Para iniciar a configuração, clique em <i>PRÓXIMO</i>.</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">Ativar FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">O Android exige que todo teclado personalizado tenha que ser ativado manualmente antes de usá-lo. Clique no botão abaixo para ir as configurações de <i>Idioma & Entrada</i>, em seguida, certifique-se de verificar \'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_after_enabled" comment="Description of state in Enable IME fragment after user enabled">FlorisBoard foi ativado com sucesso. Para continuar clique em <i>PRÓXIMO</i>!</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Abrir configurações de Idioma & Entrada</string>
|
||||
<string name="setup__make_default__title" comment="Title of Make IME default fragment in Setup">Selecione o FlorisBoard como teclado padrão</string>
|
||||
<string name="setup__make_default__text_before_switch" comment="Description of state in Make IME default fragment before user switched">O FlorisBoard agora está ativado em seu sistema. Para usá-lo ativamente, mude para FlorisBoard selecionando-o na caixa de diálogo do seletor de entrada!</string>
|
||||
<string name="setup__make_default__text_after_switch" comment="Description of state in Make IME default fragment after user switched">Teclado padrão alterado para o FlorisBoard com sucesso!</string>
|
||||
<string name="setup__make_default__text_switch_button" comment="Label of switch button in Make IME default fragment">Mudar teclado</string>
|
||||
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Configuração concluída!</string>
|
||||
<!-- Crash Dialog strings -->
|
||||
<string name="crash_dialog__title" comment="Title of crash dialog">Relatório de erro do FlorisBoard</string>
|
||||
<string name="crash_dialog__description" comment="Description of crash dialog">Desculpe pelo inconveniente, mas o FlorisBoard travou devido a um erro inesperado.\n\nSe deseja reportar esse erro, clique em \"Copiar para a área de transferência\", em seguida, no botão \"Abrir relatório de bug\". Preencha o relatório do bug e cole o registro. Isso ajuda a tornar o FlorisBoard melhor e mais estável para todos. Obrigado!</string>
|
||||
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copiar para a área de transferência</string>
|
||||
<string name="crash_dialog__open_bug_report_form" comment="Label of Open bug report button in crash dialog">Abrir relatório de bug no (github.com)</string>
|
||||
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Fechar</string>
|
||||
<string name="crash_notification_channel__title" comment="Title of crash notification channel">Relatórios de erros do FlorisBoard</string>
|
||||
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard parou de funcionar…</string>
|
||||
<string name="crash_once_notification__body" comment="Body of the notification for a single crash">Toque para ver os detalhes do erro</string>
|
||||
<string name="crash_multiple_notification__title" comment="Title of the notification for consecutive crashes">FlorisBoard parece parar de funcionar repetidamente…</string>
|
||||
<string name="crash_multiple_notification__body" comment="Body of the notification for consecutive crashes">Recuando para o teclado anterior para parar o loop de travamento infinito. Toque para ver os detalhes do erro</string>
|
||||
</resources>
|
||||
11
app/src/main/res/values-pt/strings.xml
Normal file
11
app/src/main/res/values-pt/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-ru/strings.xml
Normal file
11
app/src/main/res/values-ru/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-sk/strings.xml
Normal file
11
app/src/main/res/values-sk/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-sl/strings.xml
Normal file
11
app/src/main/res/values-sl/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
11
app/src/main/res/values-sv/strings.xml
Normal file
11
app/src/main/res/values-sv/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- One-handed strings -->
|
||||
<!-- Media strings -->
|
||||
<!-- Emoji strings -->
|
||||
<!-- Smartbar strings -->
|
||||
<!-- Settings UI strings -->
|
||||
<!-- About UI strings -->
|
||||
<!-- Setup UI strings -->
|
||||
<!-- Crash Dialog strings -->
|
||||
</resources>
|
||||
5
app/src/main/res/values-sw600dp-land/dimens.xml
Normal file
5
app/src/main/res/values-sw600dp-land/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<fraction name="inputView_minHeightFraction">40%p</fraction>
|
||||
<fraction name="inputView_maxHeightFraction">46%p</fraction>
|
||||
</resources>
|
||||
5
app/src/main/res/values-sw600dp/dimens.xml
Normal file
5
app/src/main/res/values-sw600dp/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<fraction name="inputView_minHeightFraction">35%p</fraction>
|
||||
<fraction name="inputView_maxHeightFraction">46%p</fraction>
|
||||
</resources>
|
||||
5
app/src/main/res/values-sw768dp-land/dimens.xml
Normal file
5
app/src/main/res/values-sw768dp-land/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<fraction name="inputView_minHeightFraction">35%p</fraction>
|
||||
<fraction name="inputView_maxHeightFraction">46%p</fraction>
|
||||
</resources>
|
||||
5
app/src/main/res/values-sw768dp/dimens.xml
Normal file
5
app/src/main/res/values-sw768dp/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<fraction name="inputView_minHeightFraction">35%p</fraction>
|
||||
<fraction name="inputView_maxHeightFraction">46%p</fraction>
|
||||
</resources>
|
||||
@@ -52,7 +52,6 @@
|
||||
|
||||
<string-array name="pref__gestures__swipe_action__entries">
|
||||
<item>@string/pref__gestures__swipe_action__no_action</item>
|
||||
<item>@string/pref__gestures__swipe_action__delete_word</item>
|
||||
<item>@string/pref__gestures__swipe_action__hide_keyboard</item>
|
||||
<item>@string/pref__gestures__swipe_action__move_cursor_up</item>
|
||||
<item>@string/pref__gestures__swipe_action__move_cursor_down</item>
|
||||
@@ -64,7 +63,6 @@
|
||||
</string-array>
|
||||
<string-array name="pref__gestures__swipe_action__values">
|
||||
<item>no_action</item>
|
||||
<item>delete_word</item>
|
||||
<item>hide_keyboard</item>
|
||||
<item>move_cursor_up</item>
|
||||
<item>move_cursor_down</item>
|
||||
@@ -75,6 +73,20 @@
|
||||
<item>switch_to_next_subtype</item>
|
||||
</string-array>
|
||||
|
||||
<!-- TODO: Implement precise word deleting -->
|
||||
<string-array name="pref__gestures__swipe_action_delete__entries">
|
||||
<item>@string/pref__gestures__swipe_action__no_action</item>
|
||||
<item>@string/pref__gestures__swipe_action__delete_characters_precisely</item>
|
||||
<item>@string/pref__gestures__swipe_action__delete_word</item>
|
||||
<!--<item>@string/pref__gestures__swipe_action__delete_words_precisely</item>-->
|
||||
</string-array>
|
||||
<string-array name="pref__gestures__swipe_action_delete__values">
|
||||
<item>no_action</item>
|
||||
<item>delete_characters_precisely</item>
|
||||
<item>delete_word</item>
|
||||
<!--<item>delete_words_precisely</item>-->
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref__gestures__swipe_velocity_threshold__entries">
|
||||
<item>@string/pref__gestures__swipe_velocity_threshold__very_slow</item>
|
||||
<item>@string/pref__gestures__swipe_velocity_threshold__slow</item>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<dimen name="textKeyboardView_baseHeight">208dp</dimen>
|
||||
<dimen name="mediaKeyboardView_baseHeight">@dimen/inputView_baseHeight</dimen>
|
||||
|
||||
<fraction name="inputView_minHeightFraction">38.2%p</fraction>
|
||||
<fraction name="inputView_maxHeightFraction">46%p</fraction>
|
||||
|
||||
<dimen name="key_width">33dp</dimen>
|
||||
<dimen name="key_height">42dp</dimen>
|
||||
<dimen name="emoji_key_width">@dimen/key_height</dimen>
|
||||
@@ -18,6 +21,7 @@
|
||||
<dimen name="key_borderRadius">6dp</dimen>
|
||||
|
||||
<dimen name="key_textSize">18sp</dimen>
|
||||
<dimen name="key_textHintSize">10sp</dimen>
|
||||
<dimen name="key_numeric_textSize">12sp</dimen>
|
||||
<dimen name="key_popup_textSize">21sp</dimen>
|
||||
<dimen name="emoji_key_textSize">22sp</dimen>
|
||||
@@ -32,6 +36,7 @@
|
||||
<dimen name="one_handed_button_height">@dimen/one_handed_width</dimen>
|
||||
|
||||
<dimen name="smartbar_height">40dp</dimen>
|
||||
<dimen name="smartbar_radius">20dp</dimen>
|
||||
<dimen name="smartbar_button_margin">4dp</dimen>
|
||||
<dimen name="smartbar_button_padding">6dp</dimen>
|
||||
|
||||
|
||||
@@ -1,217 +1,242 @@
|
||||
<resources>
|
||||
<string name="key__phone_pause">Pause</string>
|
||||
<string name="key__phone_wait">Wait</string>
|
||||
<string name="key_popup__threedots_alt">Three-dot icon. If visible, indicates that more letters can be used if longer pressed.</string>
|
||||
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Pause</string>
|
||||
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Wait</string>
|
||||
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Three-dot icon. If visible, indicates that more letters can be used if longer pressed.</string>
|
||||
|
||||
<!-- One-handed strings -->
|
||||
<string name="one_handed__close_btn_content_description">Close one-handed mode.</string>
|
||||
<string name="one_handed__move_start_btn_content_description">Move keyboard to the left.</string>
|
||||
<string name="one_handed__move_end_btn_content_description">Move keyboard to the right.</string>
|
||||
<string name="one_handed__close_btn_content_description" comment="Content description for the one-handed close button">Close one-handed mode.</string>
|
||||
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Move keyboard to the left.</string>
|
||||
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Move keyboard to the right.</string>
|
||||
|
||||
<!-- Media strings -->
|
||||
<string name="media__tab__emojis">Emojis</string>
|
||||
<string name="media__tab__emoticons">Emoticons</string>
|
||||
<string name="media__tab__kaomoji">Kaomoji</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">Emoticons</string>
|
||||
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
|
||||
|
||||
<!-- Emoji strings -->
|
||||
<string name="emoji__category__smileys_emotion">Smileys & Emotions</string>
|
||||
<string name="emoji__category__people_body">People & Body</string>
|
||||
<string name="emoji__category__animals_nature">Animals & Nature</string>
|
||||
<string name="emoji__category__food_drink">Food & Drink</string>
|
||||
<string name="emoji__category__travel_places">Travel & Places</string>
|
||||
<string name="emoji__category__activities">Activities</string>
|
||||
<string name="emoji__category__objects">Objects</string>
|
||||
<string name="emoji__category__symbols">Symbols</string>
|
||||
<string name="emoji__category__flags">Flags</string>
|
||||
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Smileys & Emotions</string>
|
||||
<string name="emoji__category__people_body" comment="Emoji category name">People & Body</string>
|
||||
<string name="emoji__category__animals_nature" comment="Emoji category name">Animals & Nature</string>
|
||||
<string name="emoji__category__food_drink" comment="Emoji category name">Food & Drink</string>
|
||||
<string name="emoji__category__travel_places" comment="Emoji category name">Travel & Places</string>
|
||||
<string name="emoji__category__activities" comment="Emoji category name">Activities</string>
|
||||
<string name="emoji__category__objects" comment="Emoji category name">Objects</string>
|
||||
<string name="emoji__category__symbols" comment="Emoji category name">Symbols</string>
|
||||
<string name="emoji__category__flags" comment="Emoji category name">Flags</string>
|
||||
|
||||
<!-- Smartbar strings -->
|
||||
<string name="smartbar__quick_action_toggle__alt">Quick action toggle. If pressed, toggles between the word suggestions and the quick action buttons.</string>
|
||||
<string name="smartbar__quick_action__exit_editing">Exit text editing panel.</string>
|
||||
<string name="smartbar__quick_action__one_handed_mode">Toggle the state of the one-handed mode.</string>
|
||||
<string name="smartbar__quick_action__open_settings">Open settings.</string>
|
||||
<string name="smartbar__quick_action__switch_to_editing_context">Switch to text editing panel.</string>
|
||||
<string name="smartbar__quick_action__switch_to_media_context">Switch to media input view.</string>
|
||||
<string name="smartbar__quick_action_toggle__alt" comment="Content description for the quick action toggle button in Smartbar">Quick action toggle. If pressed, toggles between the word suggestions and the quick action buttons.</string>
|
||||
<string name="smartbar__quick_action__exit_editing" comment="Content-description for the exit editing layout button in Smartbar">Exit text editing panel.</string>
|
||||
<string name="smartbar__quick_action__one_handed_mode" comment="Content-description for the one-handed quick action in Smartbar">Toggle the state of the one-handed mode.</string>
|
||||
<string name="smartbar__quick_action__open_settings" comment="Content-description for the settings quick action in Smartbar">Open settings.</string>
|
||||
<string name="smartbar__quick_action__switch_to_editing_context" comment="Content-description for the editing quick action in Smartbar">Switch to text editing panel.</string>
|
||||
<string name="smartbar__quick_action__switch_to_media_context" comment="Content-description for the media quick action in Smartbar">Switch to media input view.</string>
|
||||
|
||||
<!-- Settings UI strings -->
|
||||
<string name="settings__title">Settings</string>
|
||||
<string name="settings__menu">More options</string>
|
||||
<string name="settings__menu_about">About</string>
|
||||
<string name="settings__menu_advanced">@string/settings__advanced__title</string>
|
||||
<string name="settings__menu_help">Help & feedback</string>
|
||||
<string name="settings__navigation__home">Home</string>
|
||||
<string name="settings__navigation__keyboard">Keyboard</string>
|
||||
<string name="settings__navigation__typing">Typing</string>
|
||||
<string name="settings__navigation__theme">Theme</string>
|
||||
<string name="settings__navigation__gestures">Gestures</string>
|
||||
<string name="settings__default">Default</string>
|
||||
<string name="settings__system_default">System default</string>
|
||||
<string name="settings__title" comment="Title of Settings">Settings</string>
|
||||
<string name="settings__menu" comment="Hint of top-right three-dot icon in Settings">More options</string>
|
||||
<string name="settings__menu_about" translatable="false" comment="Three-dot menu entry for About activity">@string/about__title</string>
|
||||
<string name="settings__menu_advanced" translatable="false" comment="Three-dot menu entry for Advanced activity">@string/settings__advanced__title</string>
|
||||
<string name="settings__menu_help" comment="Three-dot menu entry for Help and Feedback web link">Help & feedback</string>
|
||||
<string name="settings__navigation__home" comment="Long-press hint of bottom nav item Home in Settings">Home</string>
|
||||
<string name="settings__navigation__keyboard" comment="Long-press hint of bottom nav item Keyboard in Settings">Keyboard</string>
|
||||
<string name="settings__navigation__typing" comment="Long-press hint of bottom nav item Typing in Settings">Typing</string>
|
||||
<string name="settings__navigation__theme" comment="Long-press hint of bottom nav item Theme in Settings">Theme</string>
|
||||
<string name="settings__navigation__gestures" comment="Long-press hint of bottom nav item Gestures in Settings">Gestures</string>
|
||||
<string name="settings__default" comment="General string which is used when a preference has the default value set">Default</string>
|
||||
<string name="settings__system_default" comment="General string which is used when a preference has the system default value set">System default</string>
|
||||
|
||||
<string name="settings__home__title">Welcome to %s</string>
|
||||
<string name="settings__home__ime_not_enabled">FlorisBoard is not enabled in the system and thus won\'t be available as an input method in the input picker. Click here to resolve this issue.</string>
|
||||
<string name="settings__home__ime_not_selected">FlorisBoard is not selected as the default input method. Click here to resolve this issue.</string>
|
||||
<string name="settings__home__contribute">Thanks for trying out FlorisBoard! This project is still in alpha and therefore missing features. If you find any bugs or want to make a suggestion, please check out the repo on GitHub and file an issue. This helps making FlorisBoard better. Thank you!</string>
|
||||
<string name="settings__home__title" comment="Title of the Home fragment">Welcome to %s</string>
|
||||
<string name="settings__home__ime_not_enabled" comment="Error message shown in Home fragment when FlorisBoard is not enabled in the system">FlorisBoard is not enabled in the system and thus won\'t be available as an input method in the input picker. Click here to resolve this issue.</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 is not selected as the default input method. Click here to resolve this issue.</string>
|
||||
<string name="settings__home__contribute" comment="Contributing message shown in Home fragment">Thanks for trying out FlorisBoard! This project is still in alpha and therefore missing features. If you find any bugs or want to make a suggestion, please check out the repo on GitHub and file an issue. This helps making FlorisBoard better. Thank you!</string>
|
||||
|
||||
<string name="settings__localization__title">Languages & Keyboard layouts</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning">It seems that you haven\'t configured any subtypes. As a fallback the subtype English/QWERTY will be used!</string>
|
||||
<string name="settings__localization__subtype_add">Add</string>
|
||||
<string name="settings__localization__subtype_add_title">Add subtype</string>
|
||||
<string name="settings__localization__subtype_apply">Apply</string>
|
||||
<string name="settings__localization__subtype_cancel">Cancel</string>
|
||||
<string name="settings__localization__subtype_delete">Delete</string>
|
||||
<string name="settings__localization__subtype_edit_title">Edit subtype</string>
|
||||
<string name="settings__localization__subtype_locale">Locale</string>
|
||||
<string name="settings__localization__subtype_layout">Keyboard layout</string>
|
||||
<string name="settings__localization__subtype_error_already_exists">This subtype already exists!</string>
|
||||
<string name="settings__localization__title" comment="Title of languages and layout box in the Typing fragment">Languages & Keyboard layouts</string>
|
||||
<string name="settings__localization__subtype_no_subtypes_configured_warning" comment="Warning message that no subtype has been defined in the Typing fragment">It seems that you haven\'t configured any subtypes. As a fallback the subtype English/QWERTY will be used!</string>
|
||||
<string name="settings__localization__subtype_add" comment="Subtype dialog add button">Add</string>
|
||||
<string name="settings__localization__subtype_add_title" comment="Title of subtype dialog when adding a new subtype">Add subtype</string>
|
||||
<string name="settings__localization__subtype_apply" comment="Subtype dialog apply button">Apply</string>
|
||||
<string name="settings__localization__subtype_cancel" comment="Subtype dialog cancel button">Cancel</string>
|
||||
<string name="settings__localization__subtype_delete" comment="Subtype dialog delete button">Delete</string>
|
||||
<string name="settings__localization__subtype_edit_title" comment="Title of subtype dialog when editing an existing subtype">Edit subtype</string>
|
||||
<string name="settings__localization__subtype_locale" comment="Label for locale dropdown in subtype dialog">Locale</string>
|
||||
<string name="settings__localization__subtype_layout" comment="Label for keyboard layout dropdown in subtype dialog">Keyboard layout</string>
|
||||
<string name="settings__localization__subtype_error_already_exists" comment="Error message shown in subtype dialog when a subtype to add already exists">This subtype already exists!</string>
|
||||
|
||||
<string name="settings__theme__title">Keyboard theme</string>
|
||||
<string name="settings__theme__undefined">Undefined</string>
|
||||
<string name="settings__theme__preset_title">Theme</string>
|
||||
<string name="settings__theme__preset_summary">Custom (based on %s)</string>
|
||||
<string name="settings__theme__preset_dialog_selected_theme">Selected theme:</string>
|
||||
<string name="settings__theme__preset_dialog_available_themes">Available themes:</string>
|
||||
<string name="settings__theme__preset_dialog_alt_arrow_right">Arrow right</string>
|
||||
<string name="settings__theme__background">Background color</string>
|
||||
<string name="settings__theme__background_active">Background color when active</string>
|
||||
<string name="settings__theme__background_pressed">Background color when pressed</string>
|
||||
<string name="settings__theme__foreground">Foreground color</string>
|
||||
<string name="settings__theme__foreground_alt">Foreground color (alternative)</string>
|
||||
<string name="settings__theme__foreground_capslock">Foreground color (caps lock)</string>
|
||||
<string name="settings__theme__dialog_title">Select a color</string>
|
||||
<string name="settings__theme__group_window">Window & System</string>
|
||||
<string name="settings__theme__group_keyboard">Keyboard</string>
|
||||
<string name="settings__theme__group_key">Key</string>
|
||||
<string name="settings__theme__group_key_enter">Enter key</string>
|
||||
<string name="settings__theme__group_key_popup">Key popup</string>
|
||||
<string name="settings__theme__group_key_shift">Shift key</string>
|
||||
<string name="settings__theme__group_media">Media context</string>
|
||||
<string name="settings__theme__group_one_handed">One-handed</string>
|
||||
<string name="settings__theme__group_one_handed_button">One-handed button</string>
|
||||
<string name="settings__theme__group_smartbar">Smartbar</string>
|
||||
<string name="settings__theme__group_smartbar_button">Smartbar button</string>
|
||||
<string name="pref__theme__name__label">Keyboard Theme</string>
|
||||
<string name="pref__theme__colorPrimary_title">Primary color</string>
|
||||
<string name="pref__theme__colorPrimary_summary">Applied to main media tab ripple and selection highlight</string>
|
||||
<string name="pref__theme__colorPrimaryDark_title">Primary color (dark)</string>
|
||||
<string name="pref__theme__colorPrimaryDark_summary">Currently not used, reserved for future implementation</string>
|
||||
<string name="pref__theme__colorAccent_title">Accent color</string>
|
||||
<string name="pref__theme__colorAccent_summary">Applied to emoji tab ripple</string>
|
||||
<string name="pref__theme__navBarColor_title">Navigation bar color</string>
|
||||
<string name="pref__theme__navBarColor_summary">The background of the navigation bar.</string>
|
||||
<string name="pref__theme__navBarIsLight_title">Navigation bar dark foreground</string>
|
||||
<string name="pref__theme__navBarIsLight_summary">Set to ON for dark or to OFF for light foreground.</string>
|
||||
<string name="settings__theme__title" comment="Title of the Theme fragment">Keyboard theme</string>
|
||||
<string name="settings__theme__undefined" comment="General string for an undefined preference value">Undefined</string>
|
||||
<string name="settings__theme__preset_title" comment="Label of the theme preset preference">Theme</string>
|
||||
<string name="settings__theme__preset_summary" comment="Summary of the theme preset preference">Custom (based on %s)</string>
|
||||
<string name="settings__theme__preset_dialog_selected_theme" comment="Label of the selected themes list">Selected theme:</string>
|
||||
<string name="settings__theme__preset_dialog_available_themes" comment="Label of the available themes list">Available themes:</string>
|
||||
<string name="settings__theme__preset_dialog_alt_arrow_right" comment="Content description of the theme selection button in theme dialog">Arrow right</string>
|
||||
<string name="settings__theme__background" comment="General label for a background preference">Background color</string>
|
||||
<string name="settings__theme__background_active" comment="General label for an active background preference">Background color when active</string>
|
||||
<string name="settings__theme__background_pressed" comment="General label for a pressed background preference">Background color when pressed</string>
|
||||
<string name="settings__theme__foreground" comment="General label for a foreground preference">Foreground color</string>
|
||||
<string name="settings__theme__foreground_alt" comment="General label for an alternate foreground preference">Foreground color (alternative)</string>
|
||||
<string name="settings__theme__foreground_capslock" comment="General label for a capslock foreground preference">Foreground color (caps lock)</string>
|
||||
<string name="settings__theme__dialog_title" comment="Title of the color selection dialog for a single theme preference">Select a color</string>
|
||||
<string name="settings__theme__group_window" comment="Theme group label">Window & System</string>
|
||||
<string name="settings__theme__group_keyboard" comment="Theme group label">Keyboard</string>
|
||||
<string name="settings__theme__group_key" comment="Theme group label">Key</string>
|
||||
<string name="settings__theme__group_key_enter" comment="Theme group label">Enter key</string>
|
||||
<string name="settings__theme__group_key_popup" comment="Theme group label">Key popup</string>
|
||||
<string name="settings__theme__group_key_shift" comment="Theme group label">Shift key</string>
|
||||
<string name="settings__theme__group_media" comment="Theme group label">Media context</string>
|
||||
<string name="settings__theme__group_one_handed" comment="Theme group label">One-handed</string>
|
||||
<string name="settings__theme__group_one_handed_button" comment="Theme group label">One-handed button</string>
|
||||
<string name="settings__theme__group_smartbar" comment="Theme group label">Smartbar</string>
|
||||
<string name="settings__theme__group_smartbar_button" comment="Theme group label">Smartbar button</string>
|
||||
<string name="pref__theme__colorPrimary_title" comment="Title of Color primary theme preference">Primary color</string>
|
||||
<string name="pref__theme__colorPrimary_summary" comment="Summary of Color primary theme preference">Applied to main media tab ripple and selection highlight</string>
|
||||
<string name="pref__theme__colorPrimaryDark_title" comment="Title of Color primary dark theme preference">Primary color (dark)</string>
|
||||
<string name="pref__theme__colorPrimaryDark_summary" comment="Summary of Color primary dark theme preference">Currently not used, reserved for future implementation</string>
|
||||
<string name="pref__theme__colorAccent_title" comment="Title of Color accent theme preference">Accent color</string>
|
||||
<string name="pref__theme__colorAccent_summary" comment="Summary of Color accent theme preference">Applied to emoji tab ripple</string>
|
||||
<string name="pref__theme__navBarColor_title" comment="Title of Nav bar color theme preference">Navigation bar color</string>
|
||||
<string name="pref__theme__navBarColor_summary" comment="Summary of Nav bar color theme preference">The background of the navigation bar.</string>
|
||||
<string name="pref__theme__navBarIsLight_title" comment="Title of Nav bar is light theme preference">Navigation bar dark foreground</string>
|
||||
<string name="pref__theme__navBarIsLight_summary" comment="Summary of Nav bar is light theme preference">Set to ON for dark or to OFF for light foreground.</string>
|
||||
|
||||
<string name="settings__keyboard__title">Keyboard Preferences</string>
|
||||
<string name="pref__keyboard__group_layout__label">Layout</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label">One-handed mode</string>
|
||||
<string name="pref__keyboard__one_handed_mode__off">Off</string>
|
||||
<string name="pref__keyboard__one_handed_mode__right">Right-handed mode</string>
|
||||
<string name="pref__keyboard__one_handed_mode__left">Left-handed mode</string>
|
||||
<string name="pref__keyboard__height_factor__label">Keyboard height</string>
|
||||
<string name="pref__keyboard__height_factor__extra_short">Extra-short</string>
|
||||
<string name="pref__keyboard__height_factor__short">Short</string>
|
||||
<string name="pref__keyboard__height_factor__mid_short">Mid-short</string>
|
||||
<string name="pref__keyboard__height_factor__normal">Normal</string>
|
||||
<string name="pref__keyboard__height_factor__mid_tall">Mid-tall</string>
|
||||
<string name="pref__keyboard__height_factor__tall">Tall</string>
|
||||
<string name="pref__keyboard__height_factor__extra_tall">Extra-tall</string>
|
||||
<string name="pref__keyboard__group_keypress__label">Key press</string>
|
||||
<string name="pref__keyboard__sound_enabled__label">Sound on key press</string>
|
||||
<string name="pref__keyboard__sound_volume__label">Sound volume on key press</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label">Vibrate on key press</string>
|
||||
<string name="pref__keyboard__vibration_strength__label">Vibration strength on key press</string>
|
||||
<string name="pref__keyboard__popup_visible__label">PopUp Visibility</string>
|
||||
<string name="pref__keyboard__popup_visible__summary">Show popup when you press a key</string>
|
||||
<string name="pref__keyboard__long_press_delay__label">Long key press delay</string>
|
||||
<string name="settings__keyboard__title" comment="Title of Keyboard preferences fragment">Keyboard Preferences</string>
|
||||
<string name="pref__keyboard__group_keys__label" comment="Preference group title">Keys</string>
|
||||
<string name="pref__keyboard__hinted_number_row__label" comment="Preference title">Number row</string>
|
||||
<string name="pref__keyboard__hinted_number_row__summary" comment="Preference summary">First row of character layout hints number row</string>
|
||||
<string name="pref__keyboard__hinted_symbols__label" comment="Preference title">Symbols</string>
|
||||
<string name="pref__keyboard__hinted_symbols__summary" comment="Preference summary">Second and third row of character layout hint symbols</string>
|
||||
<string name="pref__keyboard__group_layout__label" comment="Preference group title">Layout</string>
|
||||
<string name="pref__keyboard__one_handed_mode__label" comment="Preference value">One-handed mode</string>
|
||||
<string name="pref__keyboard__one_handed_mode__off" comment="Preference value">Off</string>
|
||||
<string name="pref__keyboard__one_handed_mode__right" comment="Preference value">Right-handed mode</string>
|
||||
<string name="pref__keyboard__one_handed_mode__left" comment="Preference value">Left-handed mode</string>
|
||||
<string name="pref__keyboard__height_factor__label" comment="Preference title">Keyboard height</string>
|
||||
<string name="pref__keyboard__height_factor__extra_short" comment="Preference value">Extra-short</string>
|
||||
<string name="pref__keyboard__height_factor__short" comment="Preference value">Short</string>
|
||||
<string name="pref__keyboard__height_factor__mid_short" comment="Preference value">Mid-short</string>
|
||||
<string name="pref__keyboard__height_factor__normal" comment="Preference value">Normal</string>
|
||||
<string name="pref__keyboard__height_factor__mid_tall" comment="Preference value">Mid-tall</string>
|
||||
<string name="pref__keyboard__height_factor__tall" comment="Preference value">Tall</string>
|
||||
<string name="pref__keyboard__height_factor__extra_tall" comment="Preference value">Extra-tall</string>
|
||||
<string name="pref__keyboard__bottom_offset__label" comment="Preference title">Bottom offset (for curved screens)</string>
|
||||
<string name="pref__keyboard__group_keypress__label" comment="Preference group title">Key press</string>
|
||||
<string name="pref__keyboard__sound_enabled__label" comment="Preference title">Sound on key press</string>
|
||||
<string name="pref__keyboard__sound_volume__label" comment="Preference title">Sound volume on key press</string>
|
||||
<string name="pref__keyboard__vibration_enabled__label" comment="Preference title">Vibrate on key press</string>
|
||||
<string name="pref__keyboard__vibration_strength__label" comment="Preference title">Vibration strength on key press</string>
|
||||
<string name="pref__keyboard__popup_visible__label" comment="Preference title">PopUp Visibility</string>
|
||||
<string name="pref__keyboard__popup_visible__summary" comment="Preference summary">Show popup when you press a key</string>
|
||||
<string name="pref__keyboard__long_press_delay__label" comment="Preference title">Long key press delay</string>
|
||||
|
||||
<string name="settings__typing__title">Typing experience</string>
|
||||
<string name="pref__suggestion__title">Suggestions</string>
|
||||
<string name="pref__suggestion__enabled__label">[NYI] Display suggestions while you type</string>
|
||||
<string name="pref__suggestion__enabled__summary">Will show on top of the keyboard</string>
|
||||
<string name="pref__suggestion__show_instead__label">What to show instead of suggestions</string>
|
||||
<string name="pref__suggestion__show_instead__number_row">Number row</string>
|
||||
<string name="pref__suggestion__show_instead__clipboard_cursor_tools">Clipboard cursor tools</string>
|
||||
<string name="pref__suggestion__use_pref_words__label">[NYI] Next-word suggestions</string>
|
||||
<string name="pref__suggestion__use_pref_words__summary">Use previous words for generating suggestions</string>
|
||||
<string name="pref__correction__title">Corrections</string>
|
||||
<string name="pref__correction__double_space_period__label">Double-space period</string>
|
||||
<string name="pref__correction__double_space_period__summary">Tapping twice on spacebar inserts a period followed by a space</string>
|
||||
<string name="settings__typing__title" comment="Title of Typing experience fragment">Typing experience</string>
|
||||
<string name="pref__suggestion__title" comment="Preference group title">Suggestions</string>
|
||||
<string name="pref__suggestion__enabled__label" comment="Preference title">[NYI] Display suggestions while you type</string>
|
||||
<string name="pref__suggestion__enabled__summary" comment="Preference summary">Will show on top of the keyboard</string>
|
||||
<string name="pref__suggestion__show_instead__label" comment="Preference title">What to show instead of suggestions</string>
|
||||
<string name="pref__suggestion__show_instead__number_row" comment="Preference value">Number row</string>
|
||||
<string name="pref__suggestion__show_instead__clipboard_cursor_tools" comment="Preference value">Clipboard cursor tools</string>
|
||||
<string name="pref__suggestion__suggest_clipboard_content__label" comment="Preference title">Clipboard content suggestions</string>
|
||||
<string name="pref__suggestion__suggest_clipboard_content__summary" comment="Preference summary">Suggest clipboard content to paste if previously copied</string>
|
||||
<string name="pref__suggestion__use_pref_words__label" comment="Preference title">[NYI] Next-word suggestions</string>
|
||||
<string name="pref__suggestion__use_pref_words__summary" comment="Preference summary">Use previous words for generating suggestions</string>
|
||||
<string name="pref__correction__title" comment="Preference group title">Corrections</string>
|
||||
<string name="pref__correction__auto_capitalization__label" comment="Preference title">Auto-capitalization</string>
|
||||
<string name="pref__correction__auto_capitalization__summary" comment="Preference summary">Capitalize words based on the current input context</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__label" comment="Preference title">Remember caps lock state</string>
|
||||
<string name="pref__correction__remember_caps_lock_state__summary" comment="Preference summary">Caps lock will stay on when moving to another text field</string>
|
||||
<string name="pref__correction__double_space_period__label" comment="Preference title">Double-space period</string>
|
||||
<string name="pref__correction__double_space_period__summary" comment="Preference summary">Tapping twice on spacebar inserts a period followed by a space</string>
|
||||
|
||||
<string name="settings__gestures__title">Gestures & Glide typing</string>
|
||||
<string name="pref__glide__title">Glide typing</string>
|
||||
<string name="pref__glide__enabled__label">[NYI] Enable glide typing</string>
|
||||
<string name="pref__glide__enabled__summary">Type in a word by sliding your finger through its letters</string>
|
||||
<string name="pref__glide__show_trail__label">[NYI] Show glide trail</string>
|
||||
<string name="pref__glide__show_trail__summary">Will disappear after each word</string>
|
||||
<string name="pref__gestures__title">Gestures</string>
|
||||
<string name="pref__gestures__swipe_action__no_action">No action</string>
|
||||
<string name="pref__gestures__swipe_action__delete_word">Delete word</string>
|
||||
<string name="pref__gestures__swipe_action__hide_keyboard">Hide keyboard</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_up">Move cursor up</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_down">Move cursor down</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_left">Move cursor left</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_right">Move cursor right</string>
|
||||
<string name="pref__gestures__swipe_action__shift">Shift</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_subtype">Switch to previous subtype</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_next_subtype">Switch to next subtype</string>
|
||||
<string name="pref__gestures__swipe_up__label">Swipe up</string>
|
||||
<string name="pref__gestures__swipe_down__label">Swipe down</string>
|
||||
<string name="pref__gestures__swipe_left__label">Swipe left</string>
|
||||
<string name="pref__gestures__swipe_right__label">Swipe right</string>
|
||||
<string name="pref__gestures__space_bar_swipe_left__label">Space bar swipe left</string>
|
||||
<string name="pref__gestures__space_bar_swipe_right__label">Space bar swipe right</string>
|
||||
<string name="pref__gestures__delete_key_swipe_left__label">Delete key swipe left</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__label">Swipe velocity threshold</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_slow">Very slow</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__slow">Slow</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__normal">Normal</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__fast">Fast</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_fast">Very fast</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__label">Swipe distance threshold</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_short">Very short</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__short">Short</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__normal">Normal</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__long">Long</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_long">Very long</string>
|
||||
<string name="settings__gestures__title" comment="Title of Gestures fragment">Gestures & Glide typing</string>
|
||||
<string name="pref__glide__title" comment="Preference group title">Glide typing</string>
|
||||
<string name="pref__glide__enabled__label" comment="Preference title">[NYI] Enable glide typing</string>
|
||||
<string name="pref__glide__enabled__summary" comment="Preference summary">Type in a word by sliding your finger through its letters</string>
|
||||
<string name="pref__glide__show_trail__label" comment="Preference title">[NYI] Show glide trail</string>
|
||||
<string name="pref__glide__show_trail__summary" comment="Preference summary">Will disappear after each word</string>
|
||||
<string name="pref__gestures__title" comment="Preference group title">Gestures</string>
|
||||
<string name="pref__gestures__swipe_action__no_action" comment="Preference value for swipe action">No action</string>
|
||||
<string name="pref__gestures__swipe_action__delete_characters_precisely" comment="Preference value for swipe action">Delete characters precisely</string>
|
||||
<string name="pref__gestures__swipe_action__delete_word" comment="Preference value for swipe action">Delete current word</string>
|
||||
<string name="pref__gestures__swipe_action__delete_words_precisely" comment="Preference value for swipe action">Delete words precisely</string>
|
||||
<string name="pref__gestures__swipe_action__hide_keyboard" comment="Preference value for swipe action">Hide keyboard</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_up" comment="Preference value for swipe action">Move cursor up</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_down" comment="Preference value for swipe action">Move cursor down</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_left" comment="Preference value for swipe action">Move cursor left</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_right" comment="Preference value for swipe action">Move cursor right</string>
|
||||
<string name="pref__gestures__swipe_action__shift" comment="Preference value for swipe action">Shift</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_subtype" comment="Preference value for swipe action">Switch to previous subtype</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_next_subtype" comment="Preference value for swipe action">Switch to next subtype</string>
|
||||
<string name="pref__gestures__swipe_up__label" comment="Preference title">Swipe up</string>
|
||||
<string name="pref__gestures__swipe_down__label" comment="Preference title">Swipe down</string>
|
||||
<string name="pref__gestures__swipe_left__label" comment="Preference title">Swipe left</string>
|
||||
<string name="pref__gestures__swipe_right__label" comment="Preference title">Swipe right</string>
|
||||
<string name="pref__gestures__space_bar_swipe_left__label" comment="Preference title">Space bar swipe left</string>
|
||||
<string name="pref__gestures__space_bar_swipe_right__label" comment="Preference title">Space bar swipe right</string>
|
||||
<string name="pref__gestures__delete_key_swipe_left__label" comment="Preference title">Delete key swipe left</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__label" comment="Preference title">Swipe velocity threshold</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_slow" comment="Preference value for swipe velocity threshold">Very slow</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__slow" comment="Preference value for swipe velocity threshold">Slow</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__normal" comment="Preference value for swipe velocity threshold">Normal</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__fast" comment="Preference value for swipe velocity threshold">Fast</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_fast" comment="Preference value for swipe velocity threshold">Very fast</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__label" comment="Preference title">Swipe distance threshold</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_short" comment="Preference value for swipe distance threshold">Very short</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__short" comment="Preference value for swipe distance threshold">Short</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__normal" comment="Preference value for swipe distance threshold">Normal</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__long" comment="Preference value for swipe distance threshold">Long</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_long" comment="Preference value for swipe distance threshold">Very long</string>
|
||||
|
||||
<string name="settings__advanced__title">Advanced</string>
|
||||
<string name="pref__advanced__settings_theme__label">Settings theme</string>
|
||||
<string name="pref__advanced__settings_theme__light">Light</string>
|
||||
<string name="pref__advanced__settings_theme__dark">Dark</string>
|
||||
<string name="pref__advanced__show_app_icon__label">Show app icon in launcher</string>
|
||||
<string name="settings__advanced__title" comment="Title of Advanced settings activity">Advanced</string>
|
||||
<string name="pref__advanced__settings_theme__label" comment="Label of Settings theme preference in Advanced">Settings theme</string>
|
||||
<string name="pref__advanced__settings_theme__light" comment="Possible value of Settings theme preference in Advanced">Light</string>
|
||||
<string name="pref__advanced__settings_theme__dark" comment="Possible value of Settings theme preference in Advanced">Dark</string>
|
||||
<string name="pref__advanced__show_app_icon__label" comment="Label of Show app icon preference in Advanced">Show app icon in launcher</string>
|
||||
|
||||
<!-- About UI strings -->
|
||||
<string name="about__title">About</string>
|
||||
<string name="about__app_icon_content_description">App icon of FlorisBoard</string>
|
||||
<string name="about__view_licenses">Open source licenses</string>
|
||||
<string name="about__view_privacy_policy">Privacy policy</string>
|
||||
<string name="about__view_source_code">Source code</string>
|
||||
<string name="about__title" comment="Title of About activity">About</string>
|
||||
<string name="about__app_icon_content_description" comment="Content description of app icon in About">App icon of FlorisBoard</string>
|
||||
<string name="about__view_licenses" comment="Label of View licenses button in About">Open source licenses</string>
|
||||
<string name="about__view_privacy_policy" comment="Label of View privacy policy button in About">Privacy policy</string>
|
||||
<string name="about__view_source_code" comment="Label of View source code button in About">Source code</string>
|
||||
|
||||
<string name="about__license__title">Open-source licenses</string>
|
||||
<string name="about__license__title" comment="Title of Open-source licenses dialog">Open-source licenses</string>
|
||||
|
||||
<!-- Setup UI strings -->
|
||||
<string name="setup__title">Setup</string>
|
||||
<string name="setup__prev_button">Prev</string>
|
||||
<string name="setup__cancel_button">Cancel</string>
|
||||
<string name="setup__next_button">Next</string>
|
||||
<string name="setup__finish_button">Finish</string>
|
||||
<string name="setup__ok_button">OK</string>
|
||||
<string name="setup__title" comment="Title of Setup">Setup</string>
|
||||
<string name="setup__prev_button" comment="Label of Previous button in Setup (try to find a short translation due to limited space in UI)">Prev</string>
|
||||
<string name="setup__cancel_button" comment="Label of Cancel button in Setup">Cancel</string>
|
||||
<string name="setup__next_button" comment="Label of Next button in Setup (try to find a short translation due to limited space in UI)">Next</string>
|
||||
<string name="setup__finish_button" comment="Label of Finish button in Setup">Finish</string>
|
||||
<string name="setup__ok_button" comment="Label of OK button in Setup">OK</string>
|
||||
|
||||
<string name="setup__welcome__title">Welcome!</string>
|
||||
<string name="setup__welcome__intro">Thanks for trying out FlorisBoard! Before you can start using it, we have to do the usual stuff and enable it in the system settings, set up your preferred language/layout, etc… But no worries - the setup wizard will guide you through this!</string>
|
||||
<string name="setup__welcome__privacy">[[ TODO: insert privacy description here ]]</string>
|
||||
<string name="setup__welcome__trust">The source code for FlorisBoard is publicly accessible for anyone, so you can easily review what FlorisBoard does in the background. Check out the repository link below.</string>
|
||||
<string name="setup__welcome__contribute">One last thing before you start the setup - if you encounter any bugs/crashes/issues with FlorisBoard or you have a feature request - head over to the GitHub repository linked below and file an issue. This helps in improving the experience for all users!</string>
|
||||
<string name="setup__welcome__outro">To start the setup, click on <i>NEXT</i>.</string>
|
||||
<string name="setup__welcome__title" comment="Title of Welcome fragment in Setup">Welcome!</string>
|
||||
<string name="setup__welcome__intro" comment="Paragraph in Welcome fragment in Setup">Thanks for trying out FlorisBoard! Before you can start using it, we have to do the usual stuff and enable it in the system settings, set up your preferred language/layout, etc… But no worries - the setup wizard will guide you through this!</string>
|
||||
<string name="setup__welcome__privacy" comment="Paragraph in Welcome fragment in Setup">FlorisBoard does fully respect your privacy and does not collect any user data. For more info see here:</string>
|
||||
<string name="setup__welcome__trust" comment="Paragraph in Welcome fragment in Setup">The source code for FlorisBoard is publicly accessible for anyone, so you can easily review what FlorisBoard does in the background. Check out the repository link below.</string>
|
||||
<string name="setup__welcome__contribute" comment="Paragraph in Welcome fragment in Setup">One last thing before you start the setup - if you encounter any bugs/crashes/issues with FlorisBoard or you have a feature request - head over to the GitHub repository linked below and file an issue. This helps in improving the experience for all users!</string>
|
||||
<string name="setup__welcome__outro" comment="Paragraph in Welcome fragment in Setup">To start the setup, click on <i>NEXT</i>.</string>
|
||||
|
||||
<string name="setup__enable_ime__title">Enable FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled">Android requires that every custom keyboard has to be manually enabled before you can use it. Click the button below to go to the <i>Language & Input</i> settings, then make sure to check \'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input">Open Language & Input settings</string>
|
||||
<string name="setup__enable_ime__text_after_enabled">FlorisBoard has been successfully enabled. To continue click <i>NEXT</i>!</string>
|
||||
<string name="setup__enable_ime__title" comment="Title of Enable IME fragment in Setup">Enable FlorisBoard</string>
|
||||
<string name="setup__enable_ime__text_before_enabled" comment="Description of state in Enable IME fragment before user enabled">Android requires that every custom keyboard has to be manually enabled before you can use it. Click the button below to go to the <i>Language & Input</i> settings, then make sure to check \'<i>FlorisBoard</i>\'.</string>
|
||||
<string name="setup__enable_ime__text_after_enabled" comment="Description of state in Enable IME fragment after user enabled">FlorisBoard has been successfully enabled. To continue click <i>NEXT</i>!</string>
|
||||
<string name="setup__enable_ime__text_button_language_and_input" comment="Label of language and input button in Enable IME fragment">Open Language & Input settings</string>
|
||||
|
||||
<string name="setup__make_default__title">Make FlorisBoard default</string>
|
||||
<string name="setup__make_default__text_before_switch">FlorisBoard is now enabled in your system. To actively use it, switch to FlorisBoard by selecting it in the input selector dialog!</string>
|
||||
<string name="setup__make_default__text_switch_button">Switch keyboard</string>
|
||||
<string name="setup__make_default__text_after_switch">Successfully switched the default keyboard to FlorisBoard!</string>
|
||||
<string name="setup__make_default__title" comment="Title of Make IME default fragment in Setup">Make FlorisBoard default</string>
|
||||
<string name="setup__make_default__text_before_switch" comment="Description of state in Make IME default fragment before user switched">FlorisBoard is now enabled in your system. To actively use it, switch to FlorisBoard by selecting it in the input selector dialog!</string>
|
||||
<string name="setup__make_default__text_after_switch" comment="Description of state in Make IME default fragment after user switched">Successfully switched the default keyboard to FlorisBoard!</string>
|
||||
<string name="setup__make_default__text_switch_button" comment="Label of switch button in Make IME default fragment">Switch keyboard</string>
|
||||
|
||||
<string name="setup__finish__title">Setup finished!</string>
|
||||
<string name="setup__finish__title" comment="Title of Setup finished fragment in Setup">Setup finished!</string>
|
||||
|
||||
<!-- Crash Dialog strings -->
|
||||
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard error report</string>
|
||||
<string name="crash_dialog__description" comment="Description of crash dialog">Sorry for the inconvenience, but FlorisBoard has crashed due to an unexpected error.\n\nIf you wish to report this error, click on "Copy to clipboard", then on the "Open bug report" button. Fill out the bug report and paste the log. This helps in making FlorisBoard better and more stable for everyone. Thank you!</string>
|
||||
<string name="crash_dialog__copy_to_clipboard" comment="Label of Copy to clipboard button in crash dialog">Copy to clipboard</string>
|
||||
<string name="crash_dialog__open_bug_report_form" comment="Label of Open bug report button in crash dialog">Open bug report form (github.com)</string>
|
||||
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Close</string>
|
||||
<string name="crash_notification_channel__title" comment="Title of crash notification channel">FlorisBoard error reports</string>
|
||||
<string name="crash_once_notification__title" comment="Title of the notification for a single crash">FlorisBoard has stopped working…</string>
|
||||
<string name="crash_once_notification__body" comment="Body of the notification for a single crash">Tap to view error details</string>
|
||||
<string name="crash_multiple_notification__title" comment="Title of the notification for consecutive crashes">FlorisBoard seems to stop working repeatedly…</string>
|
||||
<string name="crash_multiple_notification__body" comment="Body of the notification for consecutive crashes">Falling back to previous keyboard to stop infinite crash loop. Tap to view error details</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<string name="app_name" translatable="false">FlorisBoard</string>
|
||||
|
||||
<string name="florisboard__repo_url" translatable="false">https://github.com/florisboard/florisboard</string>
|
||||
<string name="florisboard__issue_tracker_url" translatable="false">https://github.com/florisboard/florisboard/issues</string>
|
||||
<string name="florisboard__issue_tracker_new_issue_url" translatable="false">https://github.com/florisboard/florisboard/issues/new</string>
|
||||
<string name="florisboard__privacy_policy_url" translatable="false">https://gist.github.com/patrickgold/a18f1e47468d72f0868afc69d6faaf0b</string>
|
||||
|
||||
<string name="key__view_characters" translatable="false">ABC</string>
|
||||
|
||||
@@ -58,6 +58,18 @@
|
||||
<item name="android:tint">#000000</item>
|
||||
</style>
|
||||
|
||||
<style name="SmartbarQuickAction.ClipboardSuggestion">
|
||||
<item name="android:layout_width">200dp</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_weight">0</item>
|
||||
<item name="android:background">@drawable/shape_rect_rounded_2</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:ellipsize">marquee</item>
|
||||
<item name="android:fadingEdge">horizontal</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
</style>
|
||||
|
||||
<style name="SmartbarQuickAction.Toggle">
|
||||
<item name="android:layout_weight">0</item>
|
||||
<item name="android:autoMirrored">true</item>
|
||||
|
||||
@@ -55,4 +55,6 @@
|
||||
<item name="android:navigationBarColor">@color/navigationBarColor</item>
|
||||
</style>
|
||||
|
||||
<style name="CrashDialogTheme" parent="Theme.AppCompat.DayNight"/>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -89,21 +89,21 @@
|
||||
|
||||
<ListPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="delete_word"
|
||||
app:entries="@array/pref__gestures__swipe_action__entries"
|
||||
app:entryValues="@array/pref__gestures__swipe_action__values"
|
||||
android:defaultValue="delete_characters_precisely"
|
||||
app:entries="@array/pref__gestures__swipe_action_delete__entries"
|
||||
app:entryValues="@array/pref__gestures__swipe_action_delete__values"
|
||||
app:key="gestures__delete_key_swipe_left"
|
||||
app:title="@string/pref__gestures__delete_key_swipe_left__label"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
<ListPreference
|
||||
<!--<ListPreference
|
||||
app:iconSpaceReserved="false"
|
||||
android:defaultValue="normal"
|
||||
app:entries="@array/pref__gestures__swipe_velocity_threshold__entries"
|
||||
app:entryValues="@array/pref__gestures__swipe_velocity_threshold__values"
|
||||
app:key="gestures__swipe_velocity_threshold"
|
||||
app:title="@string/pref__gestures__swipe_velocity_threshold__label"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
app:useSimpleSummaryProvider="true"/>-->
|
||||
|
||||
<ListPreference
|
||||
app:iconSpaceReserved="false"
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__keyboard__group_keys__label">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="keyboard__hinted_number_row"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__keyboard__hinted_number_row__label"
|
||||
app:summary="@string/pref__keyboard__hinted_number_row__summary"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="keyboard__hinted_symbols"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__keyboard__hinted_symbols__label"
|
||||
app:summary="@string/pref__keyboard__hinted_symbols__summary"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__keyboard__group_layout__label">
|
||||
@@ -24,6 +44,17 @@
|
||||
app:title="@string/pref__keyboard__height_factor__label"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
<dev.patrickgold.florisboard.settings.components.DialogSeekBarPreference
|
||||
app:allowDividerAbove="false"
|
||||
android:defaultValue="0"
|
||||
app:key="keyboard__bottom_offset"
|
||||
app:min="0"
|
||||
app:max="24"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__keyboard__bottom_offset__label"
|
||||
app:seekBarIncrement="1"
|
||||
app:unit=" dp"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
||||
@@ -8,11 +8,28 @@
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
app:enabled="true"
|
||||
app:key="suggestion__enabled"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__suggestion__enabled__label"
|
||||
app:summary="@string/pref__suggestion__enabled__summary"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
app:dependency="suggestion__enabled"
|
||||
app:key="suggestion__use_prev_words"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__suggestion__use_pref_words__label"
|
||||
app:summary="@string/pref__suggestion__use_pref_words__summary"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
app:dependency="suggestion__enabled"
|
||||
app:key="suggestion__suggest_clipboard_content"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__suggestion__suggest_clipboard_content__label"
|
||||
app:summary="@string/pref__suggestion__suggest_clipboard_content__summary"/>
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="clipboard_cursor_tools"
|
||||
app:entries="@array/pref__suggestion__show_instead__entries"
|
||||
@@ -22,20 +39,26 @@
|
||||
app:title="@string/pref__suggestion__show_instead__label"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
app:dependency="suggestion__enabled"
|
||||
app:key="suggestion__use_prev_words"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__suggestion__use_pref_words__label"
|
||||
app:summary="@string/pref__suggestion__use_pref_words__summary"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__correction__title">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
app:key="correction__auto_capitalization"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__correction__auto_capitalization__label"
|
||||
app:summary="@string/pref__correction__auto_capitalization__summary"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
app:key="correction__remember_caps_lock_state"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref__correction__remember_caps_lock_state__label"
|
||||
app:summary="@string/pref__correction__remember_caps_lock_state__summary"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
app:key="correction__double_space_period"
|
||||
|
||||
@@ -8,7 +8,7 @@ buildscript {
|
||||
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
4
crowdin.yml
Normal file
4
crowdin.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
files:
|
||||
- source: /app/src/main/res/values/strings.xml
|
||||
translation: /app/src/main/res/values-%two_letters_code%/%original_file_name%
|
||||
translate_attributes: 0
|
||||
14
fastlane/metadata/android/en-US/changelogs/14.txt
Normal file
14
fastlane/metadata/android/en-US/changelogs/14.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
- Add number row / underlying symbol support in character layouts
|
||||
- First row of each character layout has numbers 1-9 and 0 integrated
|
||||
- Second and third row have symbols according to the symbol layout
|
||||
- Number row / Underlying symbols can be enabled/disabled seperately
|
||||
in the preferences
|
||||
- Add bottom offset option to accommodate for curved screens (#20)
|
||||
- Add option to turn off auto-capitalization (#21)
|
||||
- Fix clipboard/cursor UI not updating in Smartbar when text selection
|
||||
has changed
|
||||
- Improve emoji layout
|
||||
- Scroll orientation is now vertical to better scale to different sizes
|
||||
of the keyboard
|
||||
- Temporarily remove swipe velocity threshold as it causes gestures to be
|
||||
ignored in certain circumstances
|
||||
5
fastlane/metadata/android/en-US/changelogs/15.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/15.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
- Rework core to better implement interface between FlorisBoard and other apps
|
||||
- Shift state should now update after a key press (#35)
|
||||
- Send key should now send the desired action or a newline character (#33)
|
||||
- Adjusting keyboard height also affects font size of keys (#32)
|
||||
- Add option to remember / forget caps lock state throughout different input fields (#30)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user