Compare commits
221 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdc8635793 | ||
|
|
0ed3e7bd22 | ||
|
|
18ac2dc0d8 | ||
|
|
c04ca29421 | ||
|
|
b6466daebb | ||
|
|
d9dedc447f | ||
|
|
5dbd98ae9e | ||
|
|
f8db145a63 | ||
|
|
563a24b7d1 | ||
|
|
5dbea21fab | ||
|
|
ce937c3f58 | ||
|
|
439fdade51 | ||
|
|
a77531e483 | ||
|
|
343646f0f8 | ||
|
|
91c2337633 | ||
|
|
000c0f1e30 | ||
|
|
2401b1c776 | ||
|
|
555e329447 | ||
|
|
a2b3033d04 | ||
|
|
1020fff6cd | ||
|
|
6fa4fadf04 | ||
|
|
f386428acd | ||
|
|
f45540eab1 | ||
|
|
0d509f8cfb | ||
|
|
5fd26affc9 | ||
|
|
198ae1fc7c | ||
|
|
1e9ce7ba54 | ||
|
|
702785b7fc | ||
|
|
8c37c6188b | ||
|
|
69ad3e2352 | ||
|
|
9161d1574d | ||
|
|
7ff7309e76 | ||
|
|
6df803c239 | ||
|
|
3385fe7cbd | ||
|
|
320b9e0751 | ||
|
|
9178207653 | ||
|
|
90b0812ae4 | ||
|
|
fb03a82e45 | ||
|
|
f5a7220ba7 | ||
|
|
e413f3918e | ||
|
|
d25bdd8938 | ||
|
|
1f84d08fa9 | ||
|
|
da9d68dd3b | ||
|
|
1bc36ceec7 | ||
|
|
c57b60d00c | ||
|
|
e304fbd120 | ||
|
|
7eb7c21e13 | ||
|
|
710e7ca85e | ||
|
|
207845d46f | ||
|
|
707d54b6f4 | ||
|
|
c01f167d49 | ||
|
|
176ca00f66 | ||
|
|
081cfdb0ee | ||
|
|
d9f94aecac | ||
|
|
6691706aed | ||
|
|
1b77138798 | ||
|
|
07ebd04052 | ||
|
|
40c2bfd819 | ||
|
|
855ad47674 | ||
|
|
e032e4acb8 | ||
|
|
fe6930fb76 | ||
|
|
6a10f0a01a | ||
|
|
30717eeb90 | ||
|
|
a664ab18c9 | ||
|
|
f50983d7ab | ||
|
|
be858802c5 | ||
|
|
1ba690e53a | ||
|
|
e16f81d350 | ||
|
|
0de2039d72 | ||
|
|
50b6a63468 | ||
|
|
8cb644b418 | ||
|
|
f138124670 | ||
|
|
0f76d7f9df | ||
|
|
27b9ec4628 | ||
|
|
ac733ed1dc | ||
|
|
6d15708f95 | ||
|
|
4377f3e41c | ||
|
|
1e690018d7 | ||
|
|
93bb5d2714 | ||
|
|
ad2b08a342 | ||
|
|
9e6508cee4 | ||
|
|
f735c138fb | ||
|
|
d663947fec | ||
|
|
c800617e26 | ||
|
|
f47c7abaf3 | ||
|
|
faf06ee234 | ||
|
|
07c41f9c27 | ||
|
|
80a0d9edab | ||
|
|
cd943a9d4a | ||
|
|
c3d3107b12 | ||
|
|
b91fac8e76 | ||
|
|
e2c784f4cf | ||
|
|
f83bdd8a28 | ||
|
|
dc10a459ca | ||
|
|
4bea68f151 | ||
|
|
daa8ce71ac | ||
|
|
f06f475e89 | ||
|
|
b784d0805c | ||
|
|
c245c6a37c | ||
|
|
264a287171 | ||
|
|
82d82466c6 | ||
|
|
0242d24cd1 | ||
|
|
76e683bfec | ||
|
|
ee1988d98e | ||
|
|
fe5f0d18ac | ||
|
|
41527e4f23 | ||
|
|
66fb1c5873 | ||
|
|
05103214dd | ||
|
|
bf9e2e4438 | ||
|
|
4209bdcfbe | ||
|
|
31db482bb4 | ||
|
|
e33499dab5 | ||
|
|
92b99ff34e | ||
|
|
f991c6479b | ||
|
|
5a45b1600a | ||
|
|
79f884b2a0 | ||
|
|
22330ad67b | ||
|
|
7f50a5aa77 | ||
|
|
de389918be | ||
|
|
4a57829105 | ||
|
|
bc6ca8c7fc | ||
|
|
0ffe0c915e | ||
|
|
392699f333 | ||
|
|
cf801c02fd | ||
|
|
665356f77b | ||
|
|
48c356a569 | ||
|
|
60eb92e92a | ||
|
|
602ffc2a93 | ||
|
|
dbacc0e466 | ||
|
|
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 | ||
|
|
15caf66370 | ||
|
|
ae0a8e551b | ||
|
|
cb4bedfc2c | ||
|
|
7d63a6885c | ||
|
|
841d797b7c | ||
|
|
0c9ba5326a | ||
|
|
7c5a7dc148 | ||
|
|
37fc714729 | ||
|
|
ec7d65ebc0 | ||
|
|
5670af16d6 | ||
|
|
6b39a846e6 | ||
|
|
a25501d63c | ||
|
|
e9a5f2161c | ||
|
|
6f12f22937 | ||
|
|
25054ef679 | ||
|
|
3af17f99fe | ||
|
|
06664ff521 | ||
|
|
fde0749a3b | ||
|
|
c061e15263 | ||
|
|
7256c597c2 | ||
|
|
2f9d32027b | ||
|
|
538912edc2 | ||
|
|
ee5ff81ee8 | ||
|
|
d873dc54c5 | ||
|
|
1e967463de | ||
|
|
5c084a10dc | ||
|
|
f158a9deb3 | ||
|
|
66d328293c | ||
|
|
e33b652bb3 | ||
|
|
65d8c02b95 | ||
|
|
bd090132eb | ||
|
|
0eb5ca318b | ||
|
|
dfa9df6cd6 | ||
|
|
3f5dfbc852 | ||
|
|
59caafbf19 | ||
|
|
037a452baf | ||
|
|
ffa405f289 | ||
|
|
5d7091582f | ||
|
|
b4096f2cfb | ||
|
|
81c62f3e91 | ||
|
|
5c7db2b344 | ||
|
|
30bca99092 | ||
|
|
9a9445dab1 | ||
|
|
1fbfc32429 | ||
|
|
645b682451 |
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
|
||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,21 +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: ''
|
||||
|
||||
---
|
||||
|
||||
#### Short description of your idea
|
||||
A short but clear and concise description of your idea.
|
||||
|
||||
#### Detailed description of your idea
|
||||
A clear and concise description of what you want to be added or changed. If you also have
|
||||
an idea how to implement it, please describe it here.
|
||||
|
||||
#### Alternatives to your idea
|
||||
If you have considered an alternative solution for your idea, describe it here.
|
||||
|
||||
#### Additional context
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!--
|
||||
- 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!
|
||||
-->
|
||||
32
.github/workflows/android.yml
vendored
Normal file
32
.github/workflows/android.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: FlorisBoard CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean assemble
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-debug.apk
|
||||
path: app/build/outputs/apk/debug/app-debug.apk
|
||||
100
CONTRIBUTING.md
100
CONTRIBUTING.md
@@ -2,62 +2,59 @@
|
||||
|
||||
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 [Crowdin](https://crowdin.florisboard.patrickgold.dev) 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.
|
||||
You can now oficially add layouts to FlorisBoard as described below.
|
||||
FlorisBoard's core has stabilized enough that adding new content is
|
||||
safe, although there will be some changes in the future.
|
||||
|
||||
## Translating FlorisBoard
|
||||
Currently you need to modify `app/src/main/assets/ime/config.json` to
|
||||
add the filename of the language/layout to the `characterLayouts`
|
||||
section and the `defaultSubtypes` section, making sure to include
|
||||
the language's IETF BCP 47 code ([ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||
and [ISO 3166-1 region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)).
|
||||
For example, Dutch as spoken in Belgium is `nl-be`. Use a unique value
|
||||
for `id` to avoid possible crahses caused by duplicate ids.
|
||||
|
||||
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.
|
||||
Add the keyboard layout at `app/src/main/assets/ime/text/characters/<preferredLayout_name_here>.json`,
|
||||
with `code` referring to the characters codepoint and `label` being the
|
||||
respective unicode character.
|
||||
|
||||
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.
|
||||
Any accents or diacritics that should be exposed via long press can be
|
||||
added at `assets/ime/text/characters/extended_popups/<languageTag_name_here>.json`.
|
||||
For each key, you can add 1 main and several relevant accents. The main
|
||||
accent should be used for accents which are important for the language
|
||||
you add. The main field is used for determining if a hint or an accent
|
||||
should take priority, so please make sure to leave main empty and just
|
||||
use relevant for accents which are not-so important.
|
||||
|
||||
## Bug reporting
|
||||
|
||||
@@ -68,6 +65,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.
|
||||
|
||||
168
README.md
168
README.md
@@ -1,10 +1,23 @@
|
||||
# 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://github.com/florisboard/florisboard/releases) [](https://crowdin.florisboard.patrickgold.dev) 
|
||||
|
||||
#### Public Alpha Test Programme
|
||||
Wanna try it out on your device? You can join the public alpha test
|
||||
programme on Google Play. To become a tester, follow these steps:
|
||||
**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. Get it on F-Droid_:
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge">](https://f-droid.org/packages/dev.patrickgold.florisboard)
|
||||
|
||||
_B. Google Play Public Alpha Test_:
|
||||
|
||||
You can join the public alpha test programme on Google Play. To become a
|
||||
tester, follow these steps:
|
||||
1. Join the
|
||||
[FlorisBoard Public Alpha Test](https://groups.google.com/g/florisboard-public-alpha-test)
|
||||
Google Group to be able to access the testing programme.
|
||||
@@ -18,92 +31,155 @@ programme on Google Play. To become a tester, follow these steps:
|
||||
4. Finished! You will receive future versions of FlorisBoard via Google
|
||||
Play.
|
||||
|
||||
##### 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)
|
||||
With the v0.4.0 release FlorisBoard will enter the public beta in GPlay, allowing to directly search
|
||||
for and download FlorisBoard without prior joining the alpha group.
|
||||
|
||||
Thank you for contributing to FlorisBoard!
|
||||
_C. Use the APK provided in the release section of this repo_
|
||||
|
||||
##### Note on F-Droid release
|
||||
FlorisBoard is currently only available through Google Play, but it is
|
||||
planned to also release it via F-Droid later on. There is no exact
|
||||
timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
|
||||
### 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).
|
||||
|
||||
---
|
||||
|
||||

|
||||
<img align="right" height="256"
|
||||
src="https://patrickgold.dev/media/previews/florisboard-preview-day.png"
|
||||
alt="Preview image">
|
||||
|
||||
## Feature roadmap
|
||||
## Implemented features
|
||||
This list contains all implemented and fully functional features
|
||||
FlorisBoard currently has to offer. For planned features and its
|
||||
milestones, please refer to the [Feature roadmap](#feature-roadmap).
|
||||
|
||||
### Basics
|
||||
* [x] Implementation of the keyboard core (InputMethodService)
|
||||
* [x] Own implementation of deprecated KeyboardView (base only)
|
||||
* [x] Custom implementation of deprecated KeyboardView (base only)
|
||||
* [x] Caps + Caps Lock
|
||||
* [x] Key popups
|
||||
* [x] Extended key popups (e.g. a -> á, à, ä, ...) (needs tweaks for
|
||||
emojis)
|
||||
* [x] Extended key popups (e.g. a -> á, à, ä, ...)
|
||||
* [x] Key press sound/vibration
|
||||
* [x] Portrait orientation support
|
||||
* [x] Landscape orientation support (needs tweaks)
|
||||
* [ ] Tablet screen support
|
||||
|
||||
### Layouts
|
||||
* [x] Latin character layout (QWERTY)
|
||||
* [x] Other character layouts (both latin and non-latin) (Currently
|
||||
QWERTZ, AZERTY, swiss and spanish are supported besides QWERTY)
|
||||
* [x] Latin character layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish,
|
||||
Norwegian, Swedish/Finnish, Icelandic, Danish, Hungarian,
|
||||
Croatian, Polish, Romanian); more coming in future versions
|
||||
* [x] Non-latin character layouts (Arabic, Persian, Greek, Russian
|
||||
(JCUKEN))
|
||||
* [x] Adapt to situation in app (password, url, text, etc. )
|
||||
* [x] Special character layout(s)
|
||||
* [x] Numeric layout
|
||||
* [x] Numeric layout (advanced)
|
||||
* [x] Phone number layout
|
||||
* [x] Emoji layout (popups buggy atm)
|
||||
* [x] Emoji layout
|
||||
* [x] Emoticon layout
|
||||
* [ ] Kaomoji layout
|
||||
|
||||
### Preferences
|
||||
* [x] Setup wizard
|
||||
* [x] Preferences screen
|
||||
* [x] Customize look and behaviour of keyboard (currently only
|
||||
light/dark theme)
|
||||
* [ ] Theme customization
|
||||
* [ ] Theme import/export (?)
|
||||
* [x] Customize look and behaviour of keyboard
|
||||
* [x] Theme presets (currently only day/night theme + borderless)
|
||||
* [x] Theme customization
|
||||
* [x] Subtype selection (language/layout)
|
||||
* [x] Keyboard behaviour preferences
|
||||
* [ ] Text suggestion / Auto correct preferences
|
||||
* [ ] Gesture preferences
|
||||
|
||||
### Composing suggestions
|
||||
* [ ] Auto suggest words from precompiled dictionary
|
||||
* [ ] Auto suggest words from user dictionary
|
||||
* [ ] Auto suggest contacts
|
||||
* [ ] Multilingual typing
|
||||
* [x] Gesture preferences
|
||||
|
||||
### Other useful features
|
||||
* [x] One-handed mode
|
||||
* [ ] Clipboard manager (?)
|
||||
* [ ] Floating keyboard
|
||||
* [ ] Gesture support
|
||||
* [ ] Glide typing (?)
|
||||
* [x] Clipboard/cursor tools
|
||||
* [x] Integrated number row / symbols in character layouts
|
||||
* [x] Gesture support
|
||||
* [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
|
||||
pairs, only compile-time defined ones)
|
||||
* [ ] Description and settings reference in System Language & Input
|
||||
* [ ] (dev only) Generate well-structured documentation of code
|
||||
* [ ] ...
|
||||
|
||||
Note: (?) = not sure if it will be implemented
|
||||
## Feature roadmap
|
||||
This section describes the features which are planned to be implemented
|
||||
in FlorisBoard for the next major versions, modularized into sections.
|
||||
Please note that the milestone due dates are only raw estimates and will
|
||||
most likely be delayed back, even though I'm eager to stick to these as
|
||||
close as possible.
|
||||
|
||||
## Used libraries and icons
|
||||
### [v0.4.0](https://github.com/florisboard/florisboard/milestone/4)
|
||||
- Module A: Smartbar rework (Implemented with #91)
|
||||
- Ability to enable/disable Smartbar (features below thus only work if
|
||||
Smartbar is enabled)
|
||||
- Dynamic switching between clipboard tools and word suggestions
|
||||
- Ability to show both the number row and word suggestions at once
|
||||
- Better icons in quick actions
|
||||
- Complete rework of the Smartbar code base and the Smartbar layout
|
||||
definition in XML
|
||||
|
||||
- Module B: Composing suggestions
|
||||
- Auto-suggestion of words based of precompiled dictionaries
|
||||
- Management of custom dictionary entries
|
||||
- Opt-in only: Learning of often typed word pais to better predict next
|
||||
words over time. Data collected here is stored locally and never leaves
|
||||
the user's device.
|
||||
|
||||
- Module C: Extension packs (base implementation with #162)
|
||||
- Ability to load dictionaries (and later potentially other cool
|
||||
features too) only if needed to keep the core APK size small
|
||||
- Currently unclear how exactly this will work, but this is definitely
|
||||
a must-have feature
|
||||
|
||||
- Module D: Glide typing
|
||||
- Swiping over the characters will automatically convert this to a word
|
||||
- Possibly also add improvements based on the Flow keyboard
|
||||
|
||||
- Module E: Theme rework (Implemented with #162)
|
||||
- Themes are now based on the Asset schema
|
||||
- Dynamic theme creation
|
||||
- Different theme modes (`Always day`, `Always dark`, `Follow system`
|
||||
and `Follow time`)
|
||||
- Define a separate theme both for day and night theme
|
||||
- Adapt to app theme if possible
|
||||
|
||||
### [v0.5.0](https://github.com/florisboard/florisboard/milestone/5)
|
||||
There's no exact roadmap yet but it is planned that the media part of
|
||||
FlorisBoard (emojis, emoticons, kaomoji) gets a rework. Also as an extension
|
||||
(requires v0.4.0/Module C) GIF support is planned.
|
||||
|
||||
### > v0.5.0
|
||||
This is completely open as of now and will gather planned features as time
|
||||
passes...
|
||||
|
||||
Backlog (currently not assigned to any milestone):
|
||||
|
||||
- Theme import/export
|
||||
- Floating keyboard
|
||||
|
||||
## 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)
|
||||
* [Google Material icons](https://github.com/google/material-design-icons) by
|
||||
[google](https://github.com/google)
|
||||
* [Moshi JSON library](https://github.com/square/moshi) by
|
||||
[square](https://github.com/square)
|
||||
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
|
||||
[Jared Rummler](https://github.com/jaredrummler)
|
||||
* [Timber](https://github.com/JakeWharton/timber) by
|
||||
[JakeWharton](https://github.com/JakeWharton)
|
||||
* [kotlin-result](https://github.com/michaelbull/kotlin-result) by
|
||||
[Michael Bull](https://github.com/michaelbull)
|
||||
* [expandable-fab](https://github.com/nambicompany/expandable-fab) by
|
||||
[Nambi](https://github.com/nambicompany)
|
||||
|
||||
## License
|
||||
```
|
||||
|
||||
@@ -6,12 +6,21 @@ android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "dev.patrickgold.florisboard"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 10
|
||||
versionName "0.1.1"
|
||||
versionCode 22
|
||||
versionName "0.3.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -21,9 +30,20 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_name", "FlorisBoard Debug"
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
resValue "string", "app_name", "FlorisBoard"
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,18 +51,26 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.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'
|
||||
testImplementation 'org.robolectric:robolectric:4.4'
|
||||
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.1.0'
|
||||
implementation 'com.squareup.moshi:moshi-adapters:1.9.2'
|
||||
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'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result:1.1.9"
|
||||
implementation 'com.nambimobile.widgets:expandable-fab:1.0.2'
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -66,6 +67,20 @@
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<!-- Theme Selector Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.settings.ThemeManagerActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/settings__title"
|
||||
android:theme="@style/SettingsTheme"/>
|
||||
|
||||
<!-- Theme Editor Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.settings.ThemeEditorActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/settings__theme_editor__title"
|
||||
android:theme="@style/SettingsTheme"/>
|
||||
|
||||
<!-- About Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.settings.AboutActivity"
|
||||
@@ -74,6 +89,14 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/SettingsTheme"/>
|
||||
|
||||
<!-- Advanced Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.settings.AdvancedActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/settings__advanced__title"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/SettingsTheme"/>
|
||||
|
||||
<!-- Setup Activity -->
|
||||
<activity
|
||||
android:name="dev.patrickgold.florisboard.setup.SetupActivity"
|
||||
@@ -82,6 +105,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>
|
||||
|
||||
@@ -12,176 +12,177 @@
|
||||
"swiss_german": "Swiss German (QWERTZ)",
|
||||
"swiss_french": "Swiss French (QWERTZ)",
|
||||
"swiss_italian": "Swiss Italian (QWERTZ)",
|
||||
"persian": "Persian"
|
||||
"hungarian": "Hungarian (QWERTZ)",
|
||||
"persian": "Persian",
|
||||
"arabic": "Arabic",
|
||||
"esperanto": "Esperanto",
|
||||
"esperanto_with_hx": "Esperanto with 'ĥ'",
|
||||
"colemak": "Colemak",
|
||||
"dvorak": "Dvorak",
|
||||
"jcuken_russian": "Russian (JCUKEN)",
|
||||
"canadian_french": "Canadian French (QWERTY)",
|
||||
"greek": "Ελληνικά"
|
||||
},
|
||||
"defaultSubtypes": [
|
||||
{
|
||||
"id": 101,
|
||||
"languageTag": "en-US",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"languageTag": "en-UK",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"languageTag": "en-CA",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 104,
|
||||
"languageTag": "en-AU",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 201,
|
||||
"languageTag": "de-DE",
|
||||
"preferredLayout": "qwertz",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwertz"
|
||||
},
|
||||
{
|
||||
"id": 202,
|
||||
"languageTag": "de-AT",
|
||||
"preferredLayout": "qwertz",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwertz"
|
||||
},
|
||||
{
|
||||
"id": 203,
|
||||
"languageTag": "de-CH",
|
||||
"preferredLayout": "swiss_german",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "swiss_german"
|
||||
},
|
||||
{
|
||||
"id": 301,
|
||||
"languageTag": "fr-FR",
|
||||
"preferredLayout": "azerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "azerty"
|
||||
},
|
||||
{
|
||||
"id": 302,
|
||||
"languageTag": "fr-CA",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "canadian_french"
|
||||
},
|
||||
{
|
||||
"id": 303,
|
||||
"languageTag": "fr-CH",
|
||||
"preferredLayout": "swiss_french",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "swiss_french"
|
||||
},
|
||||
{
|
||||
"id": 401,
|
||||
"languageTag": "it-IT",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 402,
|
||||
"languageTag": "it-CH",
|
||||
"preferredLayout": "swiss_italian",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "swiss_italian"
|
||||
},
|
||||
{
|
||||
"id": 501,
|
||||
"languageTag": "es-ES",
|
||||
"preferredLayout": "spanish",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "spanish"
|
||||
},
|
||||
{
|
||||
"id": 502,
|
||||
"languageTag": "es-US",
|
||||
"preferredLayout": "spanish",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "spanish"
|
||||
},
|
||||
{
|
||||
"id": 503,
|
||||
"languageTag": "es-419",
|
||||
"preferredLayout": "spanish",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "spanish"
|
||||
},
|
||||
{
|
||||
"id": 601,
|
||||
"languageTag": "pt-PT",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 602,
|
||||
"languageTag": "pt-BR",
|
||||
"preferredLayout": "qwerty",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 701,
|
||||
"languageTag": "nb-NO",
|
||||
"preferredLayout": "norwegian",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "norwegian"
|
||||
},
|
||||
{
|
||||
"id": 702,
|
||||
"languageTag": "nn-NO",
|
||||
"preferredLayout": "norwegian",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "norwegian"
|
||||
},
|
||||
{
|
||||
"id": 711,
|
||||
"languageTag": "sv-SE",
|
||||
"preferredLayout": "swedish_finnish",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "swedish_finnish"
|
||||
},
|
||||
{
|
||||
"id": 721,
|
||||
"languageTag": "fi-FI",
|
||||
"preferredLayout": "swedish_finnish",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "swedish_finnish"
|
||||
},
|
||||
{
|
||||
"id": 731,
|
||||
"languageTag": "da-DK",
|
||||
"preferredLayout": "danish",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "danish"
|
||||
},
|
||||
{
|
||||
"id": 741,
|
||||
"languageTag": "is-IS",
|
||||
"preferredLayout": "icelandic",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "icelandic"
|
||||
},
|
||||
{
|
||||
"id": 800,
|
||||
"id": 801,
|
||||
"languageTag": "fa-FA",
|
||||
"preferredLayout": "persian",
|
||||
"isAsciiCapable": true,
|
||||
"isEmojiCapable": true
|
||||
"preferredLayout": "persian"
|
||||
},
|
||||
{
|
||||
"id": 901,
|
||||
"languageTag": "ar",
|
||||
"preferredLayout": "arabic"
|
||||
},
|
||||
{
|
||||
"id": 1001,
|
||||
"languageTag": "hu",
|
||||
"preferredLayout": "hungarian"
|
||||
},
|
||||
{
|
||||
"id": 1101,
|
||||
"languageTag": "eo",
|
||||
"preferredLayout": "esperanto"
|
||||
},
|
||||
{
|
||||
"id": 1201,
|
||||
"languageTag": "hr",
|
||||
"preferredLayout": "qwertz"
|
||||
},
|
||||
{
|
||||
"id": 1301,
|
||||
"languageTag": "ru",
|
||||
"preferredLayout": "jcuken_russian"
|
||||
},
|
||||
{
|
||||
"id": 1401,
|
||||
"languageTag": "el",
|
||||
"preferredLayout": "greek"
|
||||
},
|
||||
{
|
||||
"id": 1501,
|
||||
"languageTag": "ro",
|
||||
"preferredLayout": "qwerty"
|
||||
},
|
||||
{
|
||||
"id": 1601,
|
||||
"languageTag": "pl",
|
||||
"preferredLayout": "qwerty"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
47
app/src/main/assets/ime/text/characters/arabic.json
Normal file
47
app/src/main/assets/ime/text/characters/arabic.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "arabic",
|
||||
"authors": [ "HeiWiper" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "arabic",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 1590, "label": "ض" },
|
||||
{ "code": 1589, "label": "ص" },
|
||||
{ "code": 1579, "label": "ث" },
|
||||
{ "code": 1602, "label": "ق" },
|
||||
{ "code": 1601, "label": "ف" },
|
||||
{ "code": 1594, "label": "غ" },
|
||||
{ "code": 1593, "label": "ع" },
|
||||
{ "code": 1607, "label": "ه" },
|
||||
{ "code": 1582, "label": "خ" },
|
||||
{ "code": 1581, "label": "ح" },
|
||||
{ "code": 1580, "label": "ج" }
|
||||
],
|
||||
[
|
||||
{ "code": 1588, "label": "ش" },
|
||||
{ "code": 1587, "label": "س" },
|
||||
{ "code": 1610, "label": "ي" },
|
||||
{ "code": 1576, "label": "ب" },
|
||||
{ "code": 1604, "label": "ل" },
|
||||
{ "code": 1575, "label": "ا" },
|
||||
{ "code": 1578, "label": "ت" },
|
||||
{ "code": 1606, "label": "ن" },
|
||||
{ "code": 1605, "label": "م" },
|
||||
{ "code": 1603, "label": "ك" },
|
||||
{ "code": 1591, "label": "ط" }
|
||||
],
|
||||
[
|
||||
{ "code": 1584, "label": "ذ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 65157, "label": "ﺅ" },
|
||||
{ "code": 1585, "label": "ر" },
|
||||
{ "code": 1609, "label": "ى" },
|
||||
{ "code": 1577, "label": "ة" },
|
||||
{ "code": 1608, "label": "و" },
|
||||
{ "code": 1586, "label": "ز" },
|
||||
{ "code": 1592, "label": "ظ" },
|
||||
{ "code": 1583, "label": "د" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "azerty",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,7 +15,8 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -25,20 +27,23 @@
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 109, "label": "m" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 39, "label": "'", "popup": [
|
||||
{ "code": 8218, "label": "‚" },
|
||||
{ "code": 8216, "label": "‘" },
|
||||
{ "code": 8217, "label": "’" },
|
||||
{ "code": 8249, "label": "‹" },
|
||||
{ "code": 8250, "label": "›" }
|
||||
] }
|
||||
{ "code": 39, "label": "'", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 8218, "label": "‚" },
|
||||
{ "code": 8216, "label": "‘" },
|
||||
{ "code": 8217, "label": "’" },
|
||||
{ "code": 8249, "label": "‹" },
|
||||
{ "code": 8250, "label": "›" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
43
app/src/main/assets/ime/text/characters/canadian_french.json
Normal file
43
app/src/main/assets/ime/text/characters/canadian_french.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "canadian_french",
|
||||
"authors": [ "The-Quantum-Alpha" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 232, "label": "è" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 224, "label": "à" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" }
|
||||
]
|
||||
]
|
||||
}
|
||||
45
app/src/main/assets/ime/text/characters/colemak.json
Normal file
45
app/src/main/assets/ime/text/characters/colemak.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "colemak",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 59, "label": ";", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 58, "label": ":" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 109, "label": "m" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "danish",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -15,7 +16,8 @@
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 229, "label": "å" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,7 +29,8 @@
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
64
app/src/main/assets/ime/text/characters/dvorak.json
Normal file
64
app/src/main/assets/ime/text/characters/dvorak.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "dvorak",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"modifier": "dvorak",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 64, "label": "@", "groupId": 101, "variation": "email_address" },
|
||||
{ "code": 39, "label": "'", "groupId": 101, "variation": "normal", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 34, "label": "\"" }
|
||||
]
|
||||
} },
|
||||
{ "code": 39, "label": "'", "groupId": 101, "variation": "password", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 34, "label": "\"" }
|
||||
]
|
||||
} },
|
||||
{ "code": 47, "label": "/", "groupId": 101, "variation": "uri" },
|
||||
{ "code": 44, "label": ",", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
} },
|
||||
{ "code": 46, "label": ".", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 62, "label": ">" }
|
||||
]
|
||||
} },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 108, "label": "l" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 115, "label": "s" }
|
||||
],
|
||||
[
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 118, "label": "v" }
|
||||
]
|
||||
]
|
||||
}
|
||||
49
app/src/main/assets/ime/text/characters/esperanto.json
Normal file
49
app/src/main/assets/ime/text/characters/esperanto.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "esperanto",
|
||||
"authors": [ "jeremiah-miller", "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 349, "label": "ŝ", "popup": {
|
||||
"main": { "code": 113, "label": "q" }
|
||||
} },
|
||||
{ "code": 285, "label": "ĝ", "popup": {
|
||||
"main": { "code": 119, "label": "w" }
|
||||
} },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 365, "label": "ŭ", "popup": {
|
||||
"main": { "code": 121, "label": "y" }
|
||||
} },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 309, "label": "ĵ" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 265, "label": "ĉ", "popup": {
|
||||
"main": { "code": 120, "label": "x" }
|
||||
} },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "esperanto_with_hx",
|
||||
"authors": [ "jeremiah-miller", "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 349, "label": "ŝ", "popup": {
|
||||
"main": { "code": 113, "label": "q" }
|
||||
} },
|
||||
{ "code": 285, "label": "ĝ", "popup": {
|
||||
"main": { "code": 119, "label": "w" }
|
||||
} },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 365, "label": "ŭ", "popup": {
|
||||
"main": { "code": 121, "label": "y" }
|
||||
} },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 309, "label": "ĵ" }
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 265, "label": "ĉ", "popup": {
|
||||
"main": { "code": 120, "label": "x" }
|
||||
} },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 293, "label": "ĥ" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "$default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"~enter": {
|
||||
"main": { "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
|
||||
"relevant": [
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
|
||||
]
|
||||
},
|
||||
"~left": {
|
||||
"main": { "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
"relevant": [
|
||||
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
|
||||
{ "code": -100, "label": "settings", "type": "system_gui" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
148
app/src/main/assets/ime/text/characters/extended_popups/ar.json
Normal file
148
app/src/main/assets/ime/text/characters/extended_popups/ar.json
Normal file
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "ar",
|
||||
"authors": [ "HeiWiper" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"ض": {
|
||||
"relevant": [
|
||||
{ "code": 1633, "label": "١" }
|
||||
]
|
||||
},
|
||||
"ص": {
|
||||
"relevant": [
|
||||
{ "code": 1634, "label": "٢" }
|
||||
]
|
||||
},
|
||||
"ث": {
|
||||
"relevant": [
|
||||
{ "code": 1635, "label": "٣" }
|
||||
]
|
||||
},
|
||||
"ق": {
|
||||
"relevant": [
|
||||
{ "code": 1704, "label": "ڨ" },
|
||||
{ "code": 1636, "label": "٤" }
|
||||
]
|
||||
},
|
||||
"ف": {
|
||||
"relevant": [
|
||||
{ "code": 1701, "label": "ڥ" },
|
||||
{ "code": 1700, "label": "ڤ" },
|
||||
{ "code": 1698, "label": "ڢ" },
|
||||
{ "code": 1637, "label": "٥" }
|
||||
]
|
||||
},
|
||||
"غ": {
|
||||
"relevant": [
|
||||
{ "code": 1638, "label": "٦" }
|
||||
]
|
||||
},
|
||||
"ع": {
|
||||
"relevant": [
|
||||
{ "code": 1639, "label": "٧" }
|
||||
]
|
||||
},
|
||||
"ه": {
|
||||
"relevant": [
|
||||
{ "code": 1726, "label": "ھ" },
|
||||
{ "code": 1640, "label": "٨" }
|
||||
]
|
||||
},
|
||||
"خ": {
|
||||
"relevant": [
|
||||
{ "code": 1641, "label": "٩" }
|
||||
]
|
||||
},
|
||||
"ح": {
|
||||
"relevant": [
|
||||
{ "code": 1632, "label": "٠" }
|
||||
]
|
||||
},
|
||||
"ج": {
|
||||
"relevant": [
|
||||
{ "code": 1670, "label": "چ" }
|
||||
]
|
||||
},
|
||||
"ش": {
|
||||
"relevant": [
|
||||
{ "code": 1692, "label": "ڜ" }
|
||||
]
|
||||
},
|
||||
"ي": {
|
||||
"relevant": [
|
||||
{ "code": 1574, "label": "ئ" },
|
||||
{ "code": 1609, "label": "ى" }
|
||||
]
|
||||
},
|
||||
"ب": {
|
||||
"relevant": [
|
||||
{ "code": 1662, "label": "پ" }
|
||||
]
|
||||
},
|
||||
"ل": {
|
||||
"relevant": [
|
||||
{ "code": 65275, "label": "لا" },
|
||||
{ "code": 65273, "label": "لإ" },
|
||||
{ "code": 65271, "label": "لأ" },
|
||||
{ "code": 65269, "label": "لآ" }
|
||||
]
|
||||
},
|
||||
"ا": {
|
||||
"relevant": [
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 1571, "label": "أ" },
|
||||
{ "code": 1573, "label": "إ" },
|
||||
{ "code": 1649, "label": "ٱ" }
|
||||
]
|
||||
},
|
||||
"ك": {
|
||||
"relevant": [
|
||||
{ "code": 1705, "label": "ک"},
|
||||
{ "code": 1711, "label": "گ" }
|
||||
]
|
||||
},
|
||||
"ى": {
|
||||
"relevant": [
|
||||
{ "code": 1574, "label": "ئ" }
|
||||
]
|
||||
},
|
||||
"ز": {
|
||||
"relevant": [
|
||||
{ "code": 1688, "label": "ژ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 1611, "label": "ً" },
|
||||
"relevant": [
|
||||
{ "code": 1622, "label": "ٖ" },
|
||||
{ "code": 1648, "label": "ٰ" },
|
||||
{ "code": 1619, "label": "ٓ" },
|
||||
{ "code": 1615, "label": "ُ" },
|
||||
{ "code": 1616, "label": "ِ" },
|
||||
{ "code": 1614, "label": "َ" },
|
||||
{ "code": 1600, "label": "ـ" },
|
||||
{ "code": 1621, "label": "ٕ" },
|
||||
{ "code": 1620, "label": "ٔ" },
|
||||
{ "code": 1617, "label": "ّ" },
|
||||
{ "code": 1612, "label": "ٌ" },
|
||||
{ "code": 1613, "label": "ٍ" },
|
||||
{ "code": 1618, "label": "ْ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".ir"},
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +1,133 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
"d": [
|
||||
{ "code": 240, "label": "ð" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "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": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": [
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 353, "label": "š" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
"y": [
|
||||
{ "code": 253, "label": "ý" },
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
"æ": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
"ø": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "da",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"d": {
|
||||
"relevant": [
|
||||
{ "code": 240, "label": "ð" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"l": {
|
||||
"relevant": [
|
||||
{ "code": 322, "label": "ł" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 353, "label": "š" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 253, "label": "ý" },
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
]
|
||||
},
|
||||
"æ": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"ø": {
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".eu" },
|
||||
{ "code": -255, "label": ".dk" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +1,109 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": 347, "label": "ś" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "de",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"main": { "code": 228, "label": "ä" },
|
||||
"relevant": [
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"main": { "code": 246, "label": "ö" },
|
||||
"relevant": [
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"main": { "code": 223, "label": "ß" },
|
||||
"relevant": [
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"main": { "code": 252, "label": "ü" },
|
||||
"relevant": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".ch" },
|
||||
{ "code": -255, "label": ".de" },
|
||||
{ "code": -255, "label": ".at" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "el",
|
||||
"authors": [ "tsiflimagas" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"α": {
|
||||
"relevant": [
|
||||
{ "code": 940, "label": "ά" }
|
||||
]
|
||||
},
|
||||
"ε": {
|
||||
"relevant": [
|
||||
{ "code": 941, "label": "έ" }
|
||||
]
|
||||
},
|
||||
"η": {
|
||||
"relevant": [
|
||||
{ "code": 942, "label": "ή" }
|
||||
]
|
||||
},
|
||||
"ι": {
|
||||
"relevant": [
|
||||
{ "code": 912, "label": "ΐ" },
|
||||
{ "code": 970, "label": "ϊ" },
|
||||
{ "code": 943, "label": "ί" }
|
||||
]
|
||||
},
|
||||
"ο": {
|
||||
"relevant": [
|
||||
{ "code": 972, "label": "ό" }
|
||||
]
|
||||
},
|
||||
"υ": {
|
||||
"relevant": [
|
||||
{ "code": 944, "label": "ΰ" },
|
||||
{ "code": 971, "label": "ϋ" },
|
||||
{ "code": 973, "label": "ύ" }
|
||||
]
|
||||
},
|
||||
"ω": {
|
||||
"relevant": [
|
||||
{ "code": 974, "label": "ώ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".gr" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +1,107 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" }
|
||||
],
|
||||
"n": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": 363, "label": "ū" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "en",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "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": {
|
||||
"relevant": [
|
||||
{ "code": 223, "label": "ß" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "eo",
|
||||
"authors": [ "jeremiah-miller" ],
|
||||
"mapping":{
|
||||
"all": {
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 265, "label": "ĉ" }
|
||||
]
|
||||
},
|
||||
"g": {
|
||||
"relevant": [
|
||||
{ "code": 285, "label": "ĝ" }
|
||||
]
|
||||
},
|
||||
"h": {
|
||||
"relevant": [
|
||||
{ "code": 293, "label": "ĥ" }
|
||||
]
|
||||
},
|
||||
"j": {
|
||||
"relevant": [
|
||||
{ "code": 309, "label": "ĵ" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 349, "label": "ŝ" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 365, "label": "ŭ" }
|
||||
]
|
||||
},
|
||||
"q": {
|
||||
"relevant": [
|
||||
{ "code": 349, "label": "ŝ" }
|
||||
]
|
||||
},
|
||||
"w": {
|
||||
"relevant": [
|
||||
{ "code": 285, "label": "ĝ" }
|
||||
]
|
||||
},
|
||||
"x": {
|
||||
"relevant": [
|
||||
{ "code": 265, "label": "ĉ" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 365, "label": "ŭ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,115 @@
|
||||
{
|
||||
"a": [
|
||||
{ "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": 263, "label": "ć" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "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": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 223, "label": "ß" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 161, "label": "¡" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 191, "label": "¿" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "es",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"main": { "code": 225, "label": "á" },
|
||||
"relevant": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 261, "label": "ą" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"main": { "code": 243, "label": "ó" },
|
||||
"relevant": [
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 242, "label": "ò" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 223, "label": "ß" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".com.es" },
|
||||
{ "code": -255, "label": ".es" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,125 @@
|
||||
{
|
||||
"ض": [
|
||||
{ "code": 1777, "label": "۱" }
|
||||
],
|
||||
"ص": [
|
||||
{ "code": 1778, "label": "۲" }
|
||||
],
|
||||
"ث": [
|
||||
{ "code": 1779, "label": "۳" }
|
||||
],
|
||||
"ق": [
|
||||
{ "code": 1780, "label": "۴" }
|
||||
],
|
||||
"ف": [
|
||||
{ "code": 1781, "label": "۵" }
|
||||
],
|
||||
"غ": [
|
||||
{ "code": 1782, "label": "۶" }
|
||||
],
|
||||
"ع": [
|
||||
{ "code": 1783, "label": "۷" }
|
||||
],
|
||||
"ه": [
|
||||
{ "code": 1784, "label": "۸" }
|
||||
],
|
||||
"خ": [
|
||||
{ "code": 1785, "label": "۹" }
|
||||
],
|
||||
"ح": [
|
||||
{ "code": 1776, "label": "۰" }
|
||||
],
|
||||
"ی": [
|
||||
{ "code": 1574, "label": "ئ" },
|
||||
{ "code": 1610, "label": "ي" }
|
||||
],
|
||||
"ا": [
|
||||
{ "code": 1649, "label": "ٱ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1571, "label": "أ" },
|
||||
{ "code": 1573, "label": "إ" }
|
||||
],
|
||||
"ت": [
|
||||
{ "code": 1577, "label": "ة" }
|
||||
],
|
||||
"ک": [
|
||||
{ "code": 1603, "label": "ك" }
|
||||
],
|
||||
"ز": [
|
||||
{ "code": 1688, "label": "ژ" }
|
||||
],
|
||||
"و": [
|
||||
{ "code": 1572, "label": "ؤ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 1622, "label": "ٖ" },
|
||||
{ "code": 1648, "label": "ٰ" },
|
||||
{ "code": 1619, "label": "ٓ" },
|
||||
{ "code": 1615, "label": "ُ" },
|
||||
{ "code": 1616, "label": "ِ" },
|
||||
{ "code": 1614, "label": "َ" },
|
||||
{ "code": 1600, "label": "ـ" },
|
||||
{ "code": 1621, "label": "ٕ" },
|
||||
{ "code": 1618, "label": "ْ" },
|
||||
{ "code": 1617, "label": "ّ" },
|
||||
{ "code": 1612, "label": "ٌ" },
|
||||
{ "code": 1613, "label": "ٍ" },
|
||||
{ "code": 1611, "label": "ً" },
|
||||
{ "code": 1620, "label": "ٔ" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" },
|
||||
{ "code": -255, "label": ".ir"}
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "fa",
|
||||
"authors": [ "PHELAT" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"ض": {
|
||||
"relevant": [
|
||||
{ "code": 1777, "label": "۱" }
|
||||
]
|
||||
},
|
||||
"ص": {
|
||||
"relevant": [
|
||||
{ "code": 1778, "label": "۲" }
|
||||
]
|
||||
},
|
||||
"ث": {
|
||||
"relevant": [
|
||||
{ "code": 1779, "label": "۳" }
|
||||
]
|
||||
},
|
||||
"ق": {
|
||||
"relevant": [
|
||||
{ "code": 1780, "label": "۴" }
|
||||
]
|
||||
},
|
||||
"ف": {
|
||||
"relevant": [
|
||||
{ "code": 1781, "label": "۵" }
|
||||
]
|
||||
},
|
||||
"غ": {
|
||||
"relevant": [
|
||||
{ "code": 1782, "label": "۶" }
|
||||
]
|
||||
},
|
||||
"ع": {
|
||||
"relevant": [
|
||||
{ "code": 1783, "label": "۷" }
|
||||
]
|
||||
},
|
||||
"ه": {
|
||||
"relevant": [
|
||||
{ "code": 1784, "label": "۸" }
|
||||
]
|
||||
},
|
||||
"خ": {
|
||||
"relevant": [
|
||||
{ "code": 1785, "label": "۹" }
|
||||
]
|
||||
},
|
||||
"ح": {
|
||||
"relevant": [
|
||||
{ "code": 1776, "label": "۰" }
|
||||
]
|
||||
},
|
||||
"ی": {
|
||||
"relevant": [
|
||||
{ "code": 1574, "label": "ئ" },
|
||||
{ "code": 1610, "label": "ي" }
|
||||
]
|
||||
},
|
||||
"ا": {
|
||||
"relevant": [
|
||||
{ "code": 1570, "label": "آ" },
|
||||
{ "code": 1649, "label": "ٱ" },
|
||||
{ "code": 1569, "label": "ء" },
|
||||
{ "code": 1571, "label": "أ" },
|
||||
{ "code": 1573, "label": "إ" }
|
||||
]
|
||||
},
|
||||
"ت": {
|
||||
"relevant": [
|
||||
{ "code": 1577, "label": "ة" }
|
||||
]
|
||||
},
|
||||
"ک": {
|
||||
"relevant": [
|
||||
{ "code": 1706, "label": "ڪ"},
|
||||
{ "code": 1603, "label": "ك" }
|
||||
]
|
||||
},
|
||||
"ز": {
|
||||
"relevant": [
|
||||
{ "code": 1688, "label": "ژ" }
|
||||
]
|
||||
},
|
||||
"و": {
|
||||
"relevant": [
|
||||
{ "code": 1572, "label": "ؤ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 1611, "label": "ً" },
|
||||
"relevant": [
|
||||
{ "code": 1622, "label": "ٖ" },
|
||||
{ "code": 1648, "label": "ٰ" },
|
||||
{ "code": 1619, "label": "ٓ" },
|
||||
{ "code": 1615, "label": "ُ" },
|
||||
{ "code": 1616, "label": "ِ" },
|
||||
{ "code": 1614, "label": "َ" },
|
||||
{ "code": 1600, "label": "ـ" },
|
||||
{ "code": 1621, "label": "ٕ" },
|
||||
{ "code": 1618, "label": "ْ" },
|
||||
{ "code": 1617, "label": "ّ" },
|
||||
{ "code": 1612, "label": "ٌ" },
|
||||
{ "code": 1613, "label": "ٍ" },
|
||||
{ "code": 1620, "label": "ٔ" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".ir"},
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,118 @@
|
||||
{
|
||||
"a": [
|
||||
{ "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": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": [
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
"z": [
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 382, "label": "ž" },
|
||||
{ "code": 378, "label": "ź" }
|
||||
],
|
||||
"ä": [
|
||||
{ "code": 230, "label": "æ" }
|
||||
],
|
||||
"ö": [
|
||||
{ "code": 248, "label": "ø" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "fi",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 382, "label": "ž" },
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 378, "label": "ź" }
|
||||
]
|
||||
},
|
||||
"ä": {
|
||||
"relevant": [
|
||||
{ "code": 230, "label": "æ" }
|
||||
]
|
||||
},
|
||||
"ö": {
|
||||
"relevant": [
|
||||
{ "code": 248, "label": "ø" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +1,121 @@
|
||||
{
|
||||
"a": [
|
||||
{ "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": 263, "label": "ć" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "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": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 339, "label": "œ" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
"y": [
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "fr",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 339, "label": "œ" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 223, "label": "ß" },
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 347, "label": "ś" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "hr",
|
||||
"authors": [ "hedidnothingwrong" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"d": {
|
||||
"relevant": [
|
||||
{ "code": 273, "label": "đ" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 353, "label": "š" }
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 382, "label": "ž" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".eu" },
|
||||
{ "code": -255, "label": ".hr" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "hu",
|
||||
"authors": [ "zoli111" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 225, "label": "á" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 237, "label": "í" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 337, "label": "ő" }
|
||||
]
|
||||
},
|
||||
"ö": {
|
||||
"relevant": [
|
||||
{ "code": 337, "label": "ő" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 369, "label": "ű" }
|
||||
]
|
||||
},
|
||||
"ü": {
|
||||
"relevant": [
|
||||
{ "code": 369, "label": "ű" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".hu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,110 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 229, "label": "å" }
|
||||
],
|
||||
"d": [
|
||||
{ "code": 240, "label": "ð" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
],
|
||||
"y": [
|
||||
{ "code": 253, "label": "ý" },
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "is",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 229, "label": "å" }
|
||||
]
|
||||
},
|
||||
"d": {
|
||||
"relevant": [
|
||||
{ "code": 240, "label": "ð" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 234, "label": "ê" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 239, "label": "ï" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"t": {
|
||||
"relevant": [
|
||||
{ "code": 254, "label": "þ" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 249, "label": "ù" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 253, "label": "ý" },
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,103 @@
|
||||
{
|
||||
"a": [
|
||||
{ "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": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "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": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 243, "label": "ó" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "it",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 170, "label": "ª" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 230, "label": "æ" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 237, "label": "í" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".it" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,99 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
"æ": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
"ø": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "nb",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
]
|
||||
},
|
||||
"æ": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"ø": {
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,109 @@
|
||||
{
|
||||
"a": [
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
],
|
||||
"c": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
],
|
||||
"i": [
|
||||
{ "code": 236, "label": "ì" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
],
|
||||
"y": [
|
||||
{ "code": 7923, "label": "ỳ" }
|
||||
],
|
||||
"æ": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
],
|
||||
"ø": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "nn",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 225, "label": "á" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 257, "label": "ā" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 281, "label": "ę" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 236, "label": "ì" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 251, "label": "û" },
|
||||
{ "code": 250, "label": "ú" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 7923, "label": "ỳ" }
|
||||
]
|
||||
},
|
||||
"æ": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
},
|
||||
"ø": {
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "pl",
|
||||
"authors": [ "Mikołaj Biel" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 261, "label": "ą" },
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 281, "label": "ę" }
|
||||
]
|
||||
},
|
||||
"l": {
|
||||
"relevant": [
|
||||
{ "code": 322, "label": "ł" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 243, "label": "ó" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 347, "label": "ś" }
|
||||
]
|
||||
},
|
||||
"x": {
|
||||
"relevant": [
|
||||
{ "code": 378, "label": "ź" }
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 378, "label": "ź" },
|
||||
{ "code": 380, "label": "ż" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".pl" },
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,109 @@
|
||||
{
|
||||
"a": [
|
||||
{ "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": 269, "label": "č" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 281, "label": "ę" }
|
||||
],
|
||||
"i": [
|
||||
{ "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": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "pt",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "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": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 263, "label": "ć" },
|
||||
{ "code": 269, "label": "č" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 279, "label": "ė" },
|
||||
{ "code": 275, "label": "ē" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 281, "label": "ę" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 236, "label": "ì" },
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 238, "label": "î" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 241, "label": "ñ" },
|
||||
{ "code": 324, "label": "ń" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 186, "label": "º" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 245, "label": "õ" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 251, "label": "û" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".pt" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "ro",
|
||||
"authors": [ "bertin0" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"main": { "code": 259, "label": "ă" },
|
||||
"relevant": [
|
||||
{ "code": 226, "label": "â" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"main": { "code": 238, "label": "î" }
|
||||
},
|
||||
"s": {
|
||||
"main": {"code": 537, "label": "ș"}
|
||||
},
|
||||
"t": {
|
||||
"main": {"code": 539, "label": "ț"}
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".ro" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" },
|
||||
{ "code": -255, "label": ".edu" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"type": "characters/extended_popups",
|
||||
"name": "ru",
|
||||
"authors": [ "williamtheaker" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"е": {
|
||||
"relevant": [
|
||||
{ "code": 1105, "label": "ё" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".ru" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +1,160 @@
|
||||
{
|
||||
"a": [
|
||||
{ "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": 263, "label": "ć" }
|
||||
],
|
||||
"d": [
|
||||
{ "code": 240, "label": "ð" },
|
||||
{ "code": 271, "label": "ď" }
|
||||
],
|
||||
"e": [
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 281, "label": "ę" }
|
||||
],
|
||||
"i": [
|
||||
{ "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": 241, "label": "ñ" }
|
||||
],
|
||||
"o": [
|
||||
{ "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": [
|
||||
{ "code": 345, "label": "ř" }
|
||||
],
|
||||
"s": [
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 351, "label": "ş" },
|
||||
{ "code": 223, "label": "ß" }
|
||||
],
|
||||
"t": [
|
||||
{ "code": 357, "label": "ť" },
|
||||
{ "code": 254, "label": "þ" }
|
||||
],
|
||||
"u": [
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 251, "label": "û" }
|
||||
],
|
||||
"y": [
|
||||
{ "code": 253, "label": "ý" },
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
],
|
||||
"z": [
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 378, "label": "ź" },
|
||||
{ "code": 382, "label": "ž" }
|
||||
],
|
||||
"ä": [
|
||||
{ "code": 230, "label": "æ" }
|
||||
],
|
||||
"ö": [
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
],
|
||||
".~normal": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 63, "label": "?" }
|
||||
],
|
||||
".~uri": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".com" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
"type": "characters/extended_popups",
|
||||
"name": "sv",
|
||||
"authors": [ "patrickgold" ],
|
||||
"mapping": {
|
||||
"all": {
|
||||
"a": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" },
|
||||
{ "code": 224, "label": "à" },
|
||||
{ "code": 226, "label": "â" },
|
||||
{ "code": 261, "label": "ą" },
|
||||
{ "code": 227, "label": "ã" },
|
||||
{ "code": 229, "label": "å" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 225, "label": "á" }
|
||||
]
|
||||
},
|
||||
"c": {
|
||||
"relevant": [
|
||||
{ "code": 231, "label": "ç" },
|
||||
{ "code": 269, "label": "č" },
|
||||
{ "code": 263, "label": "ć" }
|
||||
]
|
||||
},
|
||||
"d": {
|
||||
"relevant": [
|
||||
{ "code": 240, "label": "ð" },
|
||||
{ "code": 271, "label": "ď" }
|
||||
]
|
||||
},
|
||||
"e": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 234, "label": "ê" },
|
||||
{ "code": 232, "label": "è" },
|
||||
{ "code": 235, "label": "ë" },
|
||||
{ "code": 281, "label": "ę" }
|
||||
]
|
||||
},
|
||||
"i": {
|
||||
"relevant": [
|
||||
{ "code": 237, "label": "í" },
|
||||
{ "code": 239, "label": "ï" },
|
||||
{ "code": 299, "label": "ī" },
|
||||
{ "code": 303, "label": "į" },
|
||||
{ "code": 238, "label": "î" },
|
||||
{ "code": 236, "label": "ì" }
|
||||
]
|
||||
},
|
||||
"l": {
|
||||
"relevant": [
|
||||
{ "code": 322, "label": "ł" }
|
||||
]
|
||||
},
|
||||
"n": {
|
||||
"relevant": [
|
||||
{ "code": 324, "label": "ń" },
|
||||
{ "code": 328, "label": "ň" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
]
|
||||
},
|
||||
"o": {
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 333, "label": "ō" },
|
||||
{ "code": 245, "label": "õ" },
|
||||
{ "code": 242, "label": "ò" },
|
||||
{ "code": 244, "label": "ô" },
|
||||
{ "code": 243, "label": "ó" },
|
||||
{ "code": 339, "label": "œ" },
|
||||
{ "code": 248, "label": "ø" }
|
||||
]
|
||||
},
|
||||
"r": {
|
||||
"relevant": [
|
||||
{ "code": 345, "label": "ř" }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"relevant": [
|
||||
{ "code": 347, "label": "ś" },
|
||||
{ "code": 353, "label": "š" },
|
||||
{ "code": 351, "label": "ş" },
|
||||
{ "code": 223, "label": "ß" }
|
||||
]
|
||||
},
|
||||
"t": {
|
||||
"relevant": [
|
||||
{ "code": 357, "label": "ť" },
|
||||
{ "code": 254, "label": "þ" }
|
||||
]
|
||||
},
|
||||
"u": {
|
||||
"relevant": [
|
||||
{ "code": 252, "label": "ü" },
|
||||
{ "code": 363, "label": "ū" },
|
||||
{ "code": 249, "label": "ù" },
|
||||
{ "code": 250, "label": "ú" },
|
||||
{ "code": 251, "label": "û" }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"relevant": [
|
||||
{ "code": 253, "label": "ý" },
|
||||
{ "code": 255, "label": "ÿ" }
|
||||
]
|
||||
},
|
||||
"z": {
|
||||
"relevant": [
|
||||
{ "code": 378, "label": "ź" },
|
||||
{ "code": 380, "label": "ż" },
|
||||
{ "code": 382, "label": "ž" }
|
||||
]
|
||||
},
|
||||
"ä": {
|
||||
"relevant": [
|
||||
{ "code": 230, "label": "æ" }
|
||||
]
|
||||
},
|
||||
"ö": {
|
||||
"relevant": [
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 339, "label": "œ" }
|
||||
]
|
||||
},
|
||||
"~right": {
|
||||
"main": { "code": 44, "label": "," },
|
||||
"relevant": [
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 37, "label": "%" },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 34, "label": "\"" },
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 58, "label": ":" },
|
||||
{ "code": 39, "label": "'" },
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": 33, "label": "!" },
|
||||
{ "code": 63, "label": "?" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"uri": {
|
||||
"~right": {
|
||||
"main": { "code": -255, "label": ".com" },
|
||||
"relevant": [
|
||||
{ "code": -255, "label": ".gov" },
|
||||
{ "code": -255, "label": ".edu" },
|
||||
{ "code": -255, "label": ".org" },
|
||||
{ "code": -255, "label": ".net" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
app/src/main/assets/ime/text/characters/greek.json
Normal file
40
app/src/main/assets/ime/text/characters/greek.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "greek",
|
||||
"authors": [ "tsiflimagas" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 894, "label": ";" },
|
||||
{ "code": 962, "label": "ς" },
|
||||
{ "code": 949, "label": "ε" },
|
||||
{ "code": 961, "label": "ρ" },
|
||||
{ "code": 964, "label": "τ" },
|
||||
{ "code": 965, "label": "υ" },
|
||||
{ "code": 952, "label": "θ" },
|
||||
{ "code": 953, "label": "ι" },
|
||||
{ "code": 959, "label": "ο" },
|
||||
{ "code": 960, "label": "π" }
|
||||
],
|
||||
[
|
||||
{ "code": 945, "label": "α" },
|
||||
{ "code": 963, "label": "σ" },
|
||||
{ "code": 948, "label": "δ" },
|
||||
{ "code": 966, "label": "φ" },
|
||||
{ "code": 947, "label": "γ" },
|
||||
{ "code": 951, "label": "η" },
|
||||
{ "code": 958, "label": "ξ" },
|
||||
{ "code": 954, "label": "κ" },
|
||||
{ "code": 955, "label": "λ" }
|
||||
],
|
||||
[
|
||||
{ "code": 950, "label": "ζ" },
|
||||
{ "code": 967, "label": "χ" },
|
||||
{ "code": 968, "label": "ψ" },
|
||||
{ "code": 969, "label": "ω" },
|
||||
{ "code": 946, "label": "β" },
|
||||
{ "code": 957, "label": "ν" },
|
||||
{ "code": 956, "label": "μ" }
|
||||
]
|
||||
]
|
||||
}
|
||||
44
app/src/main/assets/ime/text/characters/hungarian.json
Normal file
44
app/src/main/assets/ime/text/characters/hungarian.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "hungarian",
|
||||
"authors": [ "zoli111" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 113, "label": "q" },
|
||||
{ "code": 119, "label": "w" },
|
||||
{ "code": 101, "label": "e" },
|
||||
{ "code": 114, "label": "r" },
|
||||
{ "code": 116, "label": "t" },
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 117, "label": "u" },
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
{ "code": 102, "label": "f" },
|
||||
{ "code": 103, "label": "g" },
|
||||
{ "code": 104, "label": "h" },
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 233, "label": "é" },
|
||||
{ "code": 225, "label": "á" }
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
{ "code": 118, "label": "v" },
|
||||
{ "code": 98, "label": "b" },
|
||||
{ "code": 110, "label": "n" },
|
||||
{ "code": 109, "label": "m" },
|
||||
{ "code": 252, "label": "ü" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "icelandic",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -15,7 +16,8 @@
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 240, "label": "ð" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,7 +29,8 @@
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 230, "label": "æ" },
|
||||
{ "code": 246, "label": "ö" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
46
app/src/main/assets/ime/text/characters/jcuken_russian.json
Normal file
46
app/src/main/assets/ime/text/characters/jcuken_russian.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "jcuken_russian",
|
||||
"authors": [ "williamtheaker" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 1081, "label": "й" },
|
||||
{ "code": 1094, "label": "ц" },
|
||||
{ "code": 1091, "label": "у" },
|
||||
{ "code": 1082, "label": "к" },
|
||||
{ "code": 1077, "label": "е" },
|
||||
{ "code": 1085, "label": "н" },
|
||||
{ "code": 1075, "label": "г" },
|
||||
{ "code": 1096, "label": "ш" },
|
||||
{ "code": 1097, "label": "щ" },
|
||||
{ "code": 1079, "label": "з" },
|
||||
{ "code": 1093, "label": "х" },
|
||||
{ "code": 1098, "label": "ъ" }
|
||||
],
|
||||
[
|
||||
{ "code": 1092 , "label": "ф" },
|
||||
{ "code": 1099 , "label": "ы" },
|
||||
{ "code": 1074 , "label": "в" },
|
||||
{ "code": 1072 , "label": "а" },
|
||||
{ "code": 1087 , "label": "п" },
|
||||
{ "code": 1088 , "label": "р" },
|
||||
{ "code": 1086 , "label": "о" },
|
||||
{ "code": 1083 , "label": "л" },
|
||||
{ "code": 1076 , "label": "д" },
|
||||
{ "code": 1078 , "label": "ж" },
|
||||
{ "code": 1101 , "label": "э" }
|
||||
],
|
||||
[
|
||||
{ "code": 1103 , "label": "я" },
|
||||
{ "code": 1095 , "label": "ч" },
|
||||
{ "code": 1089 , "label": "с" },
|
||||
{ "code": 1084 , "label": "м" },
|
||||
{ "code": 1080 , "label": "и" },
|
||||
{ "code": 1090 , "label": "т" },
|
||||
{ "code": 1100 , "label": "ь" },
|
||||
{ "code": 1073 , "label": "б" },
|
||||
{ "code": 1102 , "label": "ю" }
|
||||
]
|
||||
]
|
||||
}
|
||||
27
app/src/main/assets/ime/text/characters/mod/arabic.json
Normal file
27
app/src/main/assets/ime/text/characters/mod/arabic.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "arabic",
|
||||
"authors": [ "HeiWiper" ],
|
||||
"direction": "rtl",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
|
||||
{ "code": 1548, "label": "،", "groupId": 1, "variation": "normal" },
|
||||
{ "code": 1548, "label": "،", "groupId": 1, "variation": "password" },
|
||||
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,32 +1,28 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -1, "label": "shift", "type": "modifier" },
|
||||
{ "code": 0 },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": 64, "label": "@", "variation": "email_address" },
|
||||
{ "code": 44, "label": ",", "variation": "normal" },
|
||||
{ "code": 47, "label": "/", "variation": "uri" },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": -100, "label": "settings", "type": "system_gui" }
|
||||
] },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
|
||||
{ "code": -100, "label": "settings", "type": "system_gui" }
|
||||
] },
|
||||
{ "code": 32, "label": " " },
|
||||
{ "code": 46, "label": ".", "variation": "email_address" },
|
||||
{ "code": 46, "label": ".", "variation": "normal" },
|
||||
{ "code": 46, "label": ".", "variation": "uri" },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
|
||||
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
|
||||
] }
|
||||
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
|
||||
{ "code": 44, "label": ",", "groupId": 1, "variation": "normal" },
|
||||
{ "code": 44, "label": ",", "groupId": 1, "variation": "password" },
|
||||
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
25
app/src/main/assets/ime/text/characters/mod/dvorak.json
Normal file
25
app/src/main/assets/ime/text/characters/mod/dvorak.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "dvorak",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -1, "label": "shift", "type": "modifier" },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": 113, "label": "q", "groupId": 1 },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 122, "label": "z", "groupId": 2, "variation": "email_address" },
|
||||
{ "code": 122, "label": "z", "groupId": 2, "variation": "normal" },
|
||||
{ "code": 122, "label": "z", "groupId": 2, "variation": "password" },
|
||||
{ "code": 122, "label": "z", "groupId": 2, "variation": "uri" },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,33 +1,31 @@
|
||||
{
|
||||
"type": "characters/mod",
|
||||
"name": "persian",
|
||||
"authors": [ "PHELAT" ],
|
||||
"direction": "rtl",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 0 },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
],
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": 64, "label": "@", "variation": "email_address" },
|
||||
{ "code": 1548, "label": "،", "variation": "normal" },
|
||||
{ "code": 47, "label": "/", "variation": "uri" },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": -100, "label": "settings", "type": "system_gui" }
|
||||
] },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
|
||||
{ "code": -100, "label": "settings", "type": "system_gui" }
|
||||
] },
|
||||
{ "code": 32, "label": " " },
|
||||
{ "code": 64, "label": "@", "groupId": 1, "variation": "email_address" },
|
||||
{ "code": 1548, "label": "،", "groupId": 1, "variation": "normal" },
|
||||
{ "code": 1548, "label": "،", "groupId": 1, "variation": "password" },
|
||||
{ "code": 47, "label": "/", "groupId": 1, "variation": "uri" },
|
||||
{ "code": -210, "label": "language_switch", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 8204, "label": "half_space", "variation": "normal" },
|
||||
{ "code": 46, "label": ".", "variation": "email_address" },
|
||||
{ "code": 46, "label": ".", "variation": "normal" },
|
||||
{ "code": 46, "label": ".", "variation": "uri" },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
|
||||
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
|
||||
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
|
||||
] }
|
||||
{ "code": 8204, "label": "half_space", "variation": "password" },
|
||||
{ "code": 1600, "label": "kashida", "variation": "normal" },
|
||||
{ "code": 1600, "label": "kashida", "variation": "password" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "email_address" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "normal" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "password" },
|
||||
{ "code": 46, "label": ".", "groupId": 2, "variation": "uri" },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "norwegian",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -15,7 +16,8 @@
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 229, "label": "å" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,7 +29,8 @@
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 248, "label": "ø" },
|
||||
{ "code": 230, "label": "æ" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "persian",
|
||||
"authors": [ "PHELAT" ],
|
||||
"direction": "rtl",
|
||||
"modifier": "persian",
|
||||
"arrangement": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "qwerty",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,7 +15,8 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -24,7 +26,8 @@
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "qwertz",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,7 +15,8 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -24,7 +26,8 @@
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "spanish",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,7 +15,8 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -25,7 +27,8 @@
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 241, "label": "ñ" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swedish_finnish",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -15,7 +16,8 @@
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 229, "label": "å" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,7 +29,8 @@
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 246, "label": "ö" },
|
||||
{ "code": 228, "label": "ä" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": 122, "label": "z" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swiss_french",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,10 +15,13 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 232, "label": "è", "popup": [
|
||||
{ "code": 252, "label": "ü" }
|
||||
] }
|
||||
], [
|
||||
{ "code": 232, "label": "è", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 252, "label": "ü" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,13 +31,18 @@
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 233, "label": "é", "popup": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
] },
|
||||
{ "code": 224, "label": "à", "popup": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
] }
|
||||
], [
|
||||
{ "code": 233, "label": "é", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 246, "label": "ö" }
|
||||
]
|
||||
} },
|
||||
{ "code": 224, "label": "à", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 228, "label": "ä" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swiss_german",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,10 +15,13 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 252, "label": "ü", "popup": [
|
||||
{ "code": 232, "label": "è" }
|
||||
] }
|
||||
], [
|
||||
{ "code": 252, "label": "ü", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 232, "label": "è" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,13 +31,18 @@
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 246, "label": "ö", "popup": [
|
||||
{ "code": 233, "label": "é" }
|
||||
] },
|
||||
{ "code": 228, "label": "ä", "popup": [
|
||||
{ "code": 224, "label": "à" }
|
||||
] }
|
||||
], [
|
||||
{ "code": 246, "label": "ö", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" }
|
||||
]
|
||||
} },
|
||||
{ "code": 228, "label": "ä", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"type": "characters",
|
||||
"name": "swiss_italian",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
@@ -14,10 +15,13 @@
|
||||
{ "code": 105, "label": "i" },
|
||||
{ "code": 111, "label": "o" },
|
||||
{ "code": 112, "label": "p" },
|
||||
{ "code": 252, "label": "ü", "popup": [
|
||||
{ "code": 232, "label": "è" }
|
||||
] }
|
||||
], [
|
||||
{ "code": 252, "label": "ü", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 232, "label": "è" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 97, "label": "a" },
|
||||
{ "code": 115, "label": "s" },
|
||||
{ "code": 100, "label": "d" },
|
||||
@@ -27,13 +31,18 @@
|
||||
{ "code": 106, "label": "j" },
|
||||
{ "code": 107, "label": "k" },
|
||||
{ "code": 108, "label": "l" },
|
||||
{ "code": 246, "label": "ö", "popup": [
|
||||
{ "code": 233, "label": "é" }
|
||||
] },
|
||||
{ "code": 228, "label": "ä", "popup": [
|
||||
{ "code": 224, "label": "à" }
|
||||
] }
|
||||
], [
|
||||
{ "code": 246, "label": "ö", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 233, "label": "é" }
|
||||
]
|
||||
} },
|
||||
{ "code": 228, "label": "ä", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 224, "label": "à" }
|
||||
]
|
||||
} }
|
||||
],
|
||||
[
|
||||
{ "code": 121, "label": "y" },
|
||||
{ "code": 120, "label": "x" },
|
||||
{ "code": 99, "label": "c" },
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "extension",
|
||||
"name": "clipboard_cursor_row",
|
||||
"authors": [ "patrickgold" ],
|
||||
"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" }
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,41 +1,74 @@
|
||||
{
|
||||
"type": "extension",
|
||||
"name": "number_row",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 49, "label": "1", "popup": [
|
||||
{ "code": 185, "label": "¹" },
|
||||
{ "code": 189, "label": "½" },
|
||||
{ "code": 8531, "label": "⅓" },
|
||||
{ "code": 188, "label": "¼" },
|
||||
{ "code": 8539, "label": "⅛" }
|
||||
] },
|
||||
{ "code": 50, "label": "2", "popup": [
|
||||
{ "code": 178, "label": "²" },
|
||||
{ "code": 8532, "label": "⅔" }
|
||||
] },
|
||||
{ "code": 51, "label": "3", "popup": [
|
||||
{ "code": 8540, "label": "⅜" },
|
||||
{ "code": 179, "label": "³" },
|
||||
{ "code": 190, "label": "¾" }
|
||||
] },
|
||||
{ "code": 52, "label": "4", "popup": [
|
||||
{ "code": 8308, "label": "⁴" }
|
||||
] },
|
||||
{ "code": 53, "label": "5", "popup": [
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
] },
|
||||
{ "code": 54, "label": "6", "popup": [] },
|
||||
{ "code": 55, "label": "7", "popup": [
|
||||
{ "code": 8542, "label": "⅞" }
|
||||
] },
|
||||
{ "code": 56, "label": "8", "popup": [] },
|
||||
{ "code": 57, "label": "9", "popup": [] },
|
||||
{ "code": 48, "label": "0", "popup": [
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8319, "label": "ⁿ" }
|
||||
] }
|
||||
{ "code": 49, "label": "1", "type": "numeric", "popup": {
|
||||
"main": { "code": 185, "label": "¹" },
|
||||
"relevant": [
|
||||
{ "code": 8537, "label": "⅙" },
|
||||
{ "code": 8528, "label": "⅐" },
|
||||
{ "code": 8539, "label": "⅛" },
|
||||
{ "code": 8529, "label": "⅑" },
|
||||
{ "code": 8530, "label": "⅒" },
|
||||
{ "code": 189, "label": "½" },
|
||||
{ "code": 8531, "label": "⅓" },
|
||||
{ "code": 188, "label": "¼" },
|
||||
{ "code": 8533, "label": "⅕" }
|
||||
]
|
||||
} },
|
||||
{ "code": 50, "label": "2", "type": "numeric", "popup": {
|
||||
"main": { "code": 178, "label": "²" },
|
||||
"relevant": [
|
||||
{ "code": 8532, "label": "⅔" },
|
||||
{ "code": 8534, "label": "⅖" }
|
||||
]
|
||||
} },
|
||||
{ "code": 51, "label": "3", "type": "numeric", "popup": {
|
||||
"main": { "code": 179, "label": "³" },
|
||||
"relevant": [
|
||||
{ "code": 8535, "label": "⅗" },
|
||||
{ "code": 190, "label": "¾" },
|
||||
{ "code": 8540, "label": "⅜" }
|
||||
]
|
||||
} },
|
||||
{ "code": 52, "label": "4", "type": "numeric", "popup": {
|
||||
"main": { "code": 8308, "label": "⁴" },
|
||||
"relevant": [
|
||||
{ "code": 8536, "label": "⅘" }
|
||||
]
|
||||
} },
|
||||
{ "code": 53, "label": "5", "type": "numeric", "popup": {
|
||||
"main": { "code": 8309, "label": "⁵" },
|
||||
"relevant": [
|
||||
{ "code": 8538, "label": "⅚" },
|
||||
{ "code": 8541, "label": "⅝" }
|
||||
]
|
||||
} },
|
||||
{ "code": 54, "label": "6", "type": "numeric", "popup": {
|
||||
"main": { "code": 8310, "label": "⁶" }
|
||||
} },
|
||||
{ "code": 55, "label": "7", "type": "numeric", "popup": {
|
||||
"main": { "code": 8311, "label": "⁷" },
|
||||
"relevant": [
|
||||
{ "code": 8542, "label": "⅞" }
|
||||
]
|
||||
} },
|
||||
{ "code": 56, "label": "8", "type": "numeric", "popup": {
|
||||
"main": { "code": 8312, "label": "⁸" }
|
||||
} },
|
||||
{ "code": 57, "label": "9", "type": "numeric", "popup": {
|
||||
"main": { "code": 8313, "label": "⁹" }
|
||||
} },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": {
|
||||
"main": { "code": 8304, "label": "⁰" },
|
||||
"relevant": [
|
||||
{ "code": 8709, "label": "∅" },
|
||||
{ "code": 8319, "label": "ⁿ" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,43 +1,53 @@
|
||||
{
|
||||
"type": "numeric_advanced",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 43, "label": "+", "popup": [
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 42, "label": "*" },
|
||||
{ "code": 47, "label": "/" }
|
||||
] },
|
||||
{ "code": 43, "label": "+", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 45, "label": "-" },
|
||||
{ "code": 42, "label": "*" },
|
||||
{ "code": 47, "label": "/" }
|
||||
]
|
||||
} },
|
||||
{ "code": 49, "label": "1", "type": "numeric" },
|
||||
{ "code": 50, "label": "2", "type": "numeric" },
|
||||
{ "code": 51, "label": "3", "type": "numeric" },
|
||||
{ "code": 37, "label": "%", "popup": [] }
|
||||
], [
|
||||
{ "code": 40, "label": "(", "popup": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 123, "label": "{" }
|
||||
] },
|
||||
{ "code": 37, "label": "%" }
|
||||
],
|
||||
[
|
||||
{ "code": 40, "label": "(", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 123, "label": "{" }
|
||||
]
|
||||
} },
|
||||
{ "code": 52, "label": "4", "type": "numeric" },
|
||||
{ "code": 53, "label": "5", "type": "numeric" },
|
||||
{ "code": 54, "label": "6", "type": "numeric" },
|
||||
{ "code": 32, "label": " ", "popup": [] }
|
||||
], [
|
||||
{ "code": 41, "label": ")", "popup": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 125, "label": "}" }
|
||||
] },
|
||||
{ "code": 32, "label": "space" }
|
||||
],
|
||||
[
|
||||
{ "code": 41, "label": ")", "popup": {
|
||||
"relevant": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 125, "label": "}" }
|
||||
]
|
||||
} },
|
||||
{ "code": 55, "label": "7", "type": "numeric" },
|
||||
{ "code": 56, "label": "8", "type": "numeric" },
|
||||
{ "code": 57, "label": "9", "type": "numeric" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": -201, "label": "view_characters", "type": "system_gui" },
|
||||
{ "code": 44, "label": ",", "popup": [] },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": 48, "label": "0", "type": "numeric" },
|
||||
{ "code": 61, "label": "=", "popup": [] },
|
||||
{ "code": 46, "label": ".", "popup": [] },
|
||||
{ "code": 61, "label": "=" },
|
||||
{ "code": 46, "label": "." },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
{
|
||||
"type": "numeric",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 49, "label": "1", "type": "numeric" },
|
||||
{ "code": 50, "label": "2", "type": "numeric" },
|
||||
{ "code": 51, "label": "3", "type": "numeric" },
|
||||
{ "code": 45, "label": "-", "popup": [] }
|
||||
], [
|
||||
{ "code": 45, "label": "-" }
|
||||
],
|
||||
[
|
||||
{ "code": 52, "label": "4", "type": "numeric" },
|
||||
{ "code": 53, "label": "5", "type": "numeric" },
|
||||
{ "code": 54, "label": "6", "type": "numeric" },
|
||||
{ "code": 32, "label": " ", "popup": [] }
|
||||
], [
|
||||
{ "code": 32, "label": "space" }
|
||||
],
|
||||
[
|
||||
{ "code": 55, "label": "7", "type": "numeric" },
|
||||
{ "code": 56, "label": "8", "type": "numeric" },
|
||||
{ "code": 57, "label": "9", "type": "numeric" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
{ "code": 44, "label": ",", "popup": [] },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": [
|
||||
{ "code": 43, "label": "+" }
|
||||
] },
|
||||
{ "code": 46, "label": ".", "popup": [] },
|
||||
],
|
||||
[
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": {
|
||||
"main": { "code": 43, "label": "+" }
|
||||
} },
|
||||
{ "code": 46, "label": "." },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
{
|
||||
"type": "phone",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 49, "label": "1", "type": "numeric" },
|
||||
{ "code": 50, "label": "2", "type": "numeric" },
|
||||
{ "code": 51, "label": "3", "type": "numeric" },
|
||||
{ "code": 45, "label": "-", "popup": [] }
|
||||
], [
|
||||
{ "code": 45, "label": "-" }
|
||||
],
|
||||
[
|
||||
{ "code": 52, "label": "4", "type": "numeric" },
|
||||
{ "code": 53, "label": "5", "type": "numeric" },
|
||||
{ "code": 54, "label": "6", "type": "numeric" },
|
||||
{ "code": 32, "label": " ", "popup": [] }
|
||||
], [
|
||||
{ "code": 32, "label": "space" }
|
||||
],
|
||||
[
|
||||
{ "code": 55, "label": "7", "type": "numeric" },
|
||||
{ "code": 56, "label": "8", "type": "numeric" },
|
||||
{ "code": 57, "label": "9", "type": "numeric" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": -207, "label": "view_phone2", "type": "system_gui" },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": [
|
||||
{ "code": 43, "label": "+" }
|
||||
] },
|
||||
{ "code": 46, "label": ".", "popup": [] },
|
||||
{ "code": 48, "label": "0", "type": "numeric", "popup": {
|
||||
"main": { "code": 43, "label": "+" }
|
||||
} },
|
||||
{ "code": 46, "label": "." },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
{
|
||||
"type": "phone2",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 40, "label": "(", "popup": [] },
|
||||
{ "code": 47, "label": "/", "popup": [] },
|
||||
{ "code": 41, "label": ")", "popup": [] },
|
||||
{ "code": 45, "label": "-", "popup": [] }
|
||||
], [
|
||||
{ "code": 78, "label": "N", "popup": [] },
|
||||
{ "code": 40, "label": "(" },
|
||||
{ "code": 47, "label": "/" },
|
||||
{ "code": 41, "label": ")" },
|
||||
{ "code": 45, "label": "-" }
|
||||
],
|
||||
[
|
||||
{ "code": 78, "label": "N" },
|
||||
{ "code": 44, "label": "pause", "type": "system_gui" },
|
||||
{ "code": 44, "label": ",", "popup": [] },
|
||||
{ "code": 32, "label": " ", "popup": [] }
|
||||
], [
|
||||
{ "code": 42, "label": "*", "popup": [] },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": 32, "label": "space" }
|
||||
],
|
||||
[
|
||||
{ "code": 42, "label": "*" },
|
||||
{ "code": 59, "label": "wait", "type": "system_gui" },
|
||||
{ "code": 35, "label": "#", "popup": [] },
|
||||
{ "code": 35, "label": "#" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": -206, "label": "view_phone", "type": "system_gui" },
|
||||
{ "code": 43, "label": "+", "popup": [] },
|
||||
{ "code": 46, "label": ".", "popup": [] },
|
||||
{ "code": 43, "label": "+" },
|
||||
{ "code": 46, "label": "." },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
{
|
||||
"type": "symbols/mod",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -203, "label": "view_symbols2", "type": "system_gui" },
|
||||
{ "code": 0 },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": -201, "label": "view_characters", "type": "system_gui" },
|
||||
{ "code": 44, "label": ",", "popup": [] },
|
||||
{ "code": 44, "label": "," },
|
||||
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
|
||||
{ "code": 32, "label": " ", "popup": [] },
|
||||
{ "code": 46, "label": ".", "popup": [] },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 46, "label": ".", "popup": {
|
||||
"main": { "code": 8230, "label": "…" }
|
||||
} },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,71 +1,96 @@
|
||||
{
|
||||
"type": "symbols",
|
||||
"name": "western_default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 64, "label": "@", "popup": [] },
|
||||
{ "code": 35, "label": "#", "popup": [
|
||||
{ "code": 8470, "label": "№" }
|
||||
] },
|
||||
{ "code": 36, "label": "$", "popup": [
|
||||
{ "code": 8369, "label": "₱" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
] },
|
||||
{ "code": 95, "label": "_", "popup": [] },
|
||||
{ "code": 38, "label": "&", "popup": [] },
|
||||
{ "code": 45, "label": "-", "popup": [
|
||||
{ "code": 8212, "label": "—" },
|
||||
{ "code": 95, "label": "_" },
|
||||
{ "code": 8211, "label": "–" },
|
||||
{ "code": 183, "label": "·" }
|
||||
] },
|
||||
{ "code": 43, "label": "+", "popup": [
|
||||
{ "code": 177, "label": "±" }
|
||||
] },
|
||||
{ "code": 40, "label": "(", "popup": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": 123, "label": "{" }
|
||||
] },
|
||||
{ "code": 41, "label": ")", "popup": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 62, "label": ">" },
|
||||
{ "code": 125, "label": "}" }
|
||||
] },
|
||||
{ "code": 47, "label": "/", "popup": [] }
|
||||
], [
|
||||
{ "code": 42, "label": "*", "popup": [
|
||||
{ "code": 9733, "label": "★" },
|
||||
{ "code": 8224, "label": "†" },
|
||||
{ "code": 8225, "label": "‡" }
|
||||
] },
|
||||
{ "code": 34, "label": "\"", "popup": [
|
||||
{ "code": 8222, "label": "„" },
|
||||
{ "code": 8220, "label": "“" },
|
||||
{ "code": 8221, "label": "”" },
|
||||
{ "code": 171, "label": "«" },
|
||||
{ "code": 187, "label": "»" }
|
||||
] },
|
||||
{ "code": 39, "label": "'", "popup": [
|
||||
{ "code": 8218, "label": "‚" },
|
||||
{ "code": 8216, "label": "‘" },
|
||||
{ "code": 8217, "label": "’" },
|
||||
{ "code": 8249, "label": "‹" },
|
||||
{ "code": 8250, "label": "›" }
|
||||
] },
|
||||
{ "code": 58, "label": ":", "popup": [] },
|
||||
{ "code": 59, "label": ";", "popup": [] },
|
||||
{ "code": 33, "label": "!", "popup": [
|
||||
{ "code": 161, "label": "¡" }
|
||||
] },
|
||||
{ "code": 63, "label": "?", "popup": [
|
||||
{ "code": 191, "label": "¿" },
|
||||
{ "code": 8253, "label": "‽" }
|
||||
] }
|
||||
{ "code": 64, "label": "@" },
|
||||
{ "code": 35, "label": "#", "popup": {
|
||||
"main": { "code": 8470, "label": "№" }
|
||||
} },
|
||||
{ "code": 36, "label": "$", "popup": {
|
||||
"main": { "code": 8364, "label": "€" },
|
||||
"relevant": [
|
||||
{ "code": 8369, "label": "₱" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 165, "label": "¥" }
|
||||
]
|
||||
} },
|
||||
{ "code": 37, "label": "%", "popup": {
|
||||
"main": { "code": 8240, "label": "‰" },
|
||||
"relevant": [
|
||||
{ "code": 8453, "label": "℅" }
|
||||
]
|
||||
} },
|
||||
{ "code": 38, "label": "&" },
|
||||
{ "code": 45, "label": "-", "popup": {
|
||||
"main": { "code": 95, "label": "_" },
|
||||
"relevant": [
|
||||
{ "code": 8212, "label": "—" },
|
||||
{ "code": 8211, "label": "–" },
|
||||
{ "code": 183, "label": "·" }
|
||||
]
|
||||
} },
|
||||
{ "code": 43, "label": "+", "popup": {
|
||||
"main": { "code": 177, "label": "±" }
|
||||
} },
|
||||
{ "code": 40, "label": "(", "popup": {
|
||||
"main": { "code": 60, "label": "<" },
|
||||
"relevant": [
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 123, "label": "{" }
|
||||
]
|
||||
} },
|
||||
{ "code": 41, "label": ")", "popup": {
|
||||
"main": { "code": 62, "label": ">" },
|
||||
"relevant": [
|
||||
{ "code": 93, "label": "]" },
|
||||
{ "code": 125, "label": "}" }
|
||||
]
|
||||
} },
|
||||
{ "code": 47, "label": "/" }
|
||||
],
|
||||
[
|
||||
{ "code": 42, "label": "*", "popup": {
|
||||
"main": { "code": 8224, "label": "†" },
|
||||
"relevant": [
|
||||
{ "code": 9733, "label": "★" },
|
||||
{ "code": 8225, "label": "‡" }
|
||||
]
|
||||
} },
|
||||
{ "code": 34, "label": "\"", "popup": {
|
||||
"main": { "code": 8221, "label": "”" },
|
||||
"relevant": [
|
||||
{ "code": 8222, "label": "„" },
|
||||
{ "code": 8220, "label": "“" },
|
||||
{ "code": 171, "label": "«" },
|
||||
{ "code": 187, "label": "»" }
|
||||
]
|
||||
} },
|
||||
{ "code": 39, "label": "'", "popup": {
|
||||
"main": { "code": 8217, "label": "’" },
|
||||
"relevant": [
|
||||
{ "code": 8218, "label": "‚" },
|
||||
{ "code": 8216, "label": "‘" },
|
||||
{ "code": 8249, "label": "‹" },
|
||||
{ "code": 8250, "label": "›" }
|
||||
]
|
||||
} },
|
||||
{ "code": 58, "label": ":", "popup": {
|
||||
"main": { "code": 8942, "label": "⋮" }
|
||||
} },
|
||||
{ "code": 59, "label": ";" },
|
||||
{ "code": 33, "label": "!", "popup": {
|
||||
"main": { "code": 161, "label": "¡" }
|
||||
} },
|
||||
{ "code": 63, "label": "?", "popup": {
|
||||
"main": { "code": 191, "label": "¿" },
|
||||
"relevant": [
|
||||
{ "code": 8253, "label": "‽" }
|
||||
]
|
||||
} }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"type": "symbols2/mod",
|
||||
"name": "default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
|
||||
{ "code": 0 },
|
||||
{ "code": 0, "type": "placeholder" },
|
||||
{ "code": -5, "label": "delete", "type": "enter_editing" }
|
||||
], [
|
||||
],
|
||||
[
|
||||
{ "code": -201, "label": "view_characters", "type": "system_gui" },
|
||||
{ "code": 60, "label": "<", "popup": [] },
|
||||
{ "code": 60, "label": "<" },
|
||||
{ "code": -205, "label": "view_numeric_advanced", "type": "system_gui" },
|
||||
{ "code": 32, "label": " ", "popup": [] },
|
||||
{ "code": 62, "label": ">", "popup": [] },
|
||||
{ "code": 10, "label": "enter", "type": "enter_editing" }
|
||||
{ "code": 32, "label": "space" },
|
||||
{ "code": 62, "label": ">" },
|
||||
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,69 +1,79 @@
|
||||
{
|
||||
"type": "symbols2",
|
||||
"name": "western_default",
|
||||
"authors": [ "patrickgold" ],
|
||||
"direction": "ltr",
|
||||
"arrangement": [
|
||||
[
|
||||
{ "code": 126, "label": "~", "popup": [] },
|
||||
{ "code": 96, "label": "`", "popup": [] },
|
||||
{ "code": 124, "label": "|", "popup": [] },
|
||||
{ "code": 8226, "label": "•", "popup": [
|
||||
{ "code": 9827, "label": "♣" },
|
||||
{ "code": 9824, "label": "♠" },
|
||||
{ "code": 9834, "label": "♪" },
|
||||
{ "code": 9829, "label": "♥" },
|
||||
{ "code": 9830, "label": "♦" }
|
||||
] },
|
||||
{ "code": 8730, "label": "√", "popup": [] },
|
||||
{ "code": 960, "label": "π", "popup": [
|
||||
{ "code": 937, "label": "Ω" },
|
||||
{ "code": 928, "label": "Π" },
|
||||
{ "code": 956, "label": "μ" }
|
||||
] },
|
||||
{ "code": 247, "label": "÷", "popup": [] },
|
||||
{ "code": 215, "label": "×", "popup": [] },
|
||||
{ "code": 182, "label": "¶", "popup": [
|
||||
{ "code": 167, "label": "§" }
|
||||
] },
|
||||
{ "code": 8710, "label": "∆", "popup": [] }
|
||||
], [
|
||||
{ "code": 163, "label": "£", "popup": [] },
|
||||
{ "code": 162, "label": "¢", "popup": [] },
|
||||
{ "code": 8364, "label": "€", "popup": [] },
|
||||
{ "code": 165, "label": "¥", "popup": [] },
|
||||
{ "code": 94, "label": "^", "popup": [
|
||||
{ "code": 8592, "label": "←" },
|
||||
{ "code": 8593, "label": "↑" },
|
||||
{ "code": 8595, "label": "↓" },
|
||||
{ "code": 8594, "label": "→" }
|
||||
] },
|
||||
{ "code": 176, "label": "°", "popup": [
|
||||
{ "code": 8242, "label": "′" },
|
||||
{ "code": 8243, "label": "″" }
|
||||
] },
|
||||
{ "code": 61, "label": "=", "popup": [
|
||||
{ "code": 8734, "label": "∞" },
|
||||
{ "code": 8800, "label": "≠" },
|
||||
{ "code": 8776, "label": "≈" }
|
||||
] },
|
||||
{ "code": 123, "label": "{", "popup": [
|
||||
{ "code": 40, "label": "(" }
|
||||
] },
|
||||
{ "code": 125, "label": "}", "popup": [
|
||||
{ "code": 41, "label": ")" }
|
||||
] },
|
||||
{ "code": 92, "label": "\\", "popup": [] }
|
||||
], [
|
||||
{ "code": 37, "label": "%", "popup": [
|
||||
{ "code": 8240, "label": "‰" },
|
||||
{ "code": 8453, "label": "℅" }
|
||||
] },
|
||||
{ "code": 169, "label": "©", "popup": [] },
|
||||
{ "code": 174, "label": "®", "popup": [] },
|
||||
{ "code": 8482, "label": "™", "popup": [] },
|
||||
{ "code": 10003, "label": "✓", "popup": [] },
|
||||
{ "code": 91, "label": "[", "popup": [] },
|
||||
{ "code": 93, "label": "]", "popup": [] }
|
||||
{ "code": 126, "label": "~" },
|
||||
{ "code": 96, "label": "`" },
|
||||
{ "code": 124, "label": "|" },
|
||||
{ "code": 8226, "label": "•", "popup": {
|
||||
"main": { "code": 9834, "label": "♪" },
|
||||
"relevant": [
|
||||
{ "code": 9827, "label": "♣" },
|
||||
{ "code": 9824, "label": "♠" },
|
||||
{ "code": 9829, "label": "♥" },
|
||||
{ "code": 9830, "label": "♦" }
|
||||
]
|
||||
} },
|
||||
{ "code": 8730, "label": "√" },
|
||||
{ "code": 960, "label": "π", "popup": {
|
||||
"main": { "code": 928, "label": "Π" },
|
||||
"relevant": [
|
||||
{ "code": 937, "label": "Ω" },
|
||||
{ "code": 956, "label": "μ" }
|
||||
]
|
||||
} },
|
||||
{ "code": 247, "label": "÷" },
|
||||
{ "code": 215, "label": "×" },
|
||||
{ "code": 182, "label": "¶", "popup": {
|
||||
"main": { "code": 167, "label": "§" }
|
||||
} },
|
||||
{ "code": 8710, "label": "∆" }
|
||||
],
|
||||
[
|
||||
{ "code": 163, "label": "£" },
|
||||
{ "code": 162, "label": "¢" },
|
||||
{ "code": 8364, "label": "€" },
|
||||
{ "code": 165, "label": "¥" },
|
||||
{ "code": 94, "label": "^", "popup": {
|
||||
"main": { "code": 8593, "label": "↑" },
|
||||
"relevant": [
|
||||
{ "code": 8592, "label": "←" },
|
||||
{ "code": 8595, "label": "↓" },
|
||||
{ "code": 8594, "label": "→" }
|
||||
]
|
||||
} },
|
||||
{ "code": 176, "label": "°", "popup": {
|
||||
"main": { "code": 8242, "label": "′" },
|
||||
"relevant": [
|
||||
{ "code": 8243, "label": "″" }
|
||||
]
|
||||
} },
|
||||
{ "code": 61, "label": "=", "popup": {
|
||||
"main": { "code": 8800, "label": "≠" },
|
||||
"relevant": [
|
||||
{ "code": 8734, "label": "∞" },
|
||||
{ "code": 8776, "label": "≈" }
|
||||
]
|
||||
} },
|
||||
{ "code": 123, "label": "{", "popup": {
|
||||
"main": { "code": 40, "label": "(" }
|
||||
} },
|
||||
{ "code": 125, "label": "}", "popup": {
|
||||
"main": { "code": 41, "label": ")" }
|
||||
} },
|
||||
{ "code": 92, "label": "\\" }
|
||||
],
|
||||
[
|
||||
{ "code": 95, "label": "_" },
|
||||
{ "code": 169, "label": "©" },
|
||||
{ "code": 174, "label": "®" },
|
||||
{ "code": 8482, "label": "™" },
|
||||
{ "code": 10003, "label": "✓" },
|
||||
{ "code": 91, "label": "[" },
|
||||
{ "code": 93, "label": "]" }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
64
app/src/main/assets/ime/theme/floris_day.json
Normal file
64
app/src/main/assets/ime/theme/floris_day.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "floris_day",
|
||||
"label": "Floris Day",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNightTheme": false,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#4CAF50",
|
||||
"colorPrimaryDark": "#388E3C",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "true",
|
||||
"semiTransparentColor": "#20000000",
|
||||
"textColor": "#000000"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#E0E0E0"
|
||||
},
|
||||
"key": {
|
||||
"background": "#FFFFFF",
|
||||
"backgroundPressed": "#F5F5F5",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "true"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#757575"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "#E8F5E9",
|
||||
"foreground": "#424242"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#EEEEEE",
|
||||
"backgroundActive": "#BDBDBD",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "transparent",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#8A8A8A"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "@key/background",
|
||||
"foreground": "@key/foreground"
|
||||
}
|
||||
}
|
||||
}
|
||||
68
app/src/main/assets/ime/theme/floris_day_borderless.json
Normal file
68
app/src/main/assets/ime/theme/floris_day_borderless.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "floris_day_borderless",
|
||||
"label": "Floris Day Borderless",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNightTheme": false,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#4CAF50",
|
||||
"colorPrimaryDark": "#388E3C",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "true",
|
||||
"semiTransparentColor": "#20000000",
|
||||
"textColor": "#000000"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#E0E0E0"
|
||||
},
|
||||
"key": {
|
||||
"background": "transparent",
|
||||
"backgroundPressed": "#7FF5F5F5",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "false"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"key:space": {
|
||||
"background": "#7FF5F5F5",
|
||||
"backgroundPressed": "#FFF5F5F5"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#757575"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "#E8F5E9",
|
||||
"foreground": "#424242"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#EEEEEE",
|
||||
"backgroundActive": "#BDBDBD",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "transparent",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#8A8A8A"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "#FFFFFF",
|
||||
"foreground": "@window/textColor"
|
||||
}
|
||||
}
|
||||
}
|
||||
64
app/src/main/assets/ime/theme/floris_night.json
Normal file
64
app/src/main/assets/ime/theme/floris_night.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "floris_night",
|
||||
"label": "Floris Night",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNightTheme": true,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#4CAF50",
|
||||
"colorPrimaryDark": "#388E3C",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "false",
|
||||
"semiTransparentColor": "#20FFFFFF",
|
||||
"textColor": "#FFFFFF"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#212121"
|
||||
},
|
||||
"key": {
|
||||
"background": "#424242",
|
||||
"backgroundPressed": "#616161",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "true"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#BDBDBD"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "#1B5E20",
|
||||
"foreground": "#EEEEEE"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#757575",
|
||||
"backgroundActive": "#BDBDBD",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "transparent",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#73FFFFFF"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "@key/background",
|
||||
"foreground": "@key/foreground"
|
||||
}
|
||||
}
|
||||
}
|
||||
68
app/src/main/assets/ime/theme/floris_night_borderless.json
Normal file
68
app/src/main/assets/ime/theme/floris_night_borderless.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
|
||||
"name": "floris_night_borderless",
|
||||
"label": "Floris Night Borderless",
|
||||
"authors": [ "patrickgold" ],
|
||||
"isNightTheme": true,
|
||||
"attributes": {
|
||||
"window": {
|
||||
"colorPrimary": "#4CAF50",
|
||||
"colorPrimaryDark": "#388E3C",
|
||||
"colorAccent": "#FF9800",
|
||||
"navigationBarColor": "@keyboard/background",
|
||||
"navigationBarLight": "false",
|
||||
"semiTransparentColor": "#20FFFFFF",
|
||||
"textColor": "#FFFFFF"
|
||||
},
|
||||
"keyboard": {
|
||||
"background": "#212121"
|
||||
},
|
||||
"key": {
|
||||
"background": "transparent",
|
||||
"backgroundPressed": "#7F616161",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundPressed": "@window/textColor",
|
||||
"showBorder": "false"
|
||||
},
|
||||
"key:enter": {
|
||||
"background": "@window/colorPrimary",
|
||||
"backgroundPressed": "@window/colorPrimaryDark",
|
||||
"foreground": "#FFFFFF",
|
||||
"foregroundPressed": "#FFFFFF"
|
||||
},
|
||||
"key:shift:capslock": {
|
||||
"foreground": "@window/colorAccent",
|
||||
"foregroundPressed": "@window/colorAccent"
|
||||
},
|
||||
"key:space": {
|
||||
"background": "#2F616161",
|
||||
"backgroundPressed": "#7F616161"
|
||||
},
|
||||
"media": {
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#BDBDBD"
|
||||
},
|
||||
"oneHanded": {
|
||||
"background": "#1B5E20",
|
||||
"foreground": "#EEEEEE"
|
||||
},
|
||||
"popup": {
|
||||
"background": "#757575",
|
||||
"backgroundActive": "#BDBDBD",
|
||||
"foreground": "@window/textColor"
|
||||
},
|
||||
"privateMode": {
|
||||
"background": "#A000FF",
|
||||
"foreground": "#FFFFFF"
|
||||
},
|
||||
"smartbar": {
|
||||
"background": "transparent",
|
||||
"foreground": "@window/textColor",
|
||||
"foregroundAlt": "#73FFFFFF"
|
||||
},
|
||||
"smartbarButton": {
|
||||
"background": "#424242",
|
||||
"foreground": "@window/textColor"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,48 @@ limitations under the License.
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>ColorPicker preference</h3>
|
||||
<span>Copyright 2016 Jared Rummler / Copyright 2015 Daniel Nilsson</span>
|
||||
<pre>
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
</pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>ExpandableFab</h3>
|
||||
<span>Copyright (c) 2020 Kelvin Abumere and The Nambi Company</span>
|
||||
<pre>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
</pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>FlexboxLayout</h3>
|
||||
<span>Copyright 2018 Google LLC</span>
|
||||
<pre>
|
||||
@@ -461,6 +503,24 @@ limitations under the License.
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>kotlin-result</h3>
|
||||
<span>Copyright (c) 2017-2020 Michael Bull (https://www.michael-bull.com)</span>
|
||||
<pre>
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Material Icons</h3>
|
||||
<span>Copyright 2018 Google LLC</span>
|
||||
<pre>
|
||||
@@ -678,6 +738,24 @@ You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
</pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Timber</h3>
|
||||
<span>Copyright 2013 Jake Wharton</span>
|
||||
<pre>
|
||||
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.
|
||||
|
||||
@@ -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,401 @@
|
||||
/*
|
||||
* 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 timber.log.Timber
|
||||
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 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) {
|
||||
Timber.e(
|
||||
"install($context): Can't install crash handler with a null Context object, doing nothing!"
|
||||
)
|
||||
return false
|
||||
}
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
if (oldHandler is UncaughtExceptionHandler) {
|
||||
Timber.i("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
|
||||
)
|
||||
)
|
||||
Timber.i(
|
||||
"install($context): Successfully installed crash handler for this application!"
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Timber.e(
|
||||
"install($context): Failed to install crash handler, probably due to missing runtime permission 'setDefaultUncaughtExceptionHandler':\n$e"
|
||||
)
|
||||
return false
|
||||
} catch (e: Exception) {
|
||||
Timber.e(
|
||||
"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)
|
||||
}
|
||||
Timber.i(
|
||||
"install($context): Successfully created crash handler notification channel!"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(
|
||||
"install($context): Failed to create crash handler notification channel due to an unspecified error:\n$e"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.e(
|
||||
"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()
|
||||
Timber.i("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?) {
|
||||
Timber.e("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)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.BuildConfig
|
||||
import dev.patrickgold.florisboard.crashutility.CrashUtility
|
||||
import dev.patrickgold.florisboard.ime.extension.AssetManager
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import timber.log.Timber
|
||||
|
||||
class FlorisApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
CrashUtility.install(this)
|
||||
val prefHelper = PrefHelper.getDefaultInstance(this)
|
||||
val assetManager = AssetManager.init(this)
|
||||
ThemeManager.init(this, assetManager, prefHelper)
|
||||
prefHelper.initDefaultPreferences()
|
||||
}
|
||||
}
|
||||
@@ -17,31 +17,38 @@
|
||||
package dev.patrickgold.florisboard.ime.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
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.*
|
||||
import android.provider.Settings
|
||||
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
|
||||
import android.widget.LinearLayout
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import com.squareup.moshi.Json
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.media.MediaInputManager
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupLayerView
|
||||
import dev.patrickgold.florisboard.ime.text.TextInputManager
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
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.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.settings.SettingsMainActivity
|
||||
import dev.patrickgold.florisboard.util.*
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Variable which holds the current [FlorisBoard] instance. To get this instance from another
|
||||
@@ -53,21 +60,34 @@ 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,
|
||||
ThemeManager.OnThemeUpdatedListener {
|
||||
lateinit var prefs: PrefHelper
|
||||
private set
|
||||
|
||||
val context: Context
|
||||
get() = inputView?.context ?: this
|
||||
private var inputView: InputView? = null
|
||||
get() = inputWindowView?.context ?: this
|
||||
var inputView: InputView? = null
|
||||
private set
|
||||
var popupLayerView: PopupLayerView? = null
|
||||
private set
|
||||
private var inputWindowView: InputWindowView? = null
|
||||
private var eventListeners: MutableList<WeakReference<EventListener?>?> = mutableListOf()
|
||||
|
||||
private var audioManager: AudioManager? = null
|
||||
private var imeManager:InputMethodManager? = null
|
||||
var clipboardManager: ClipboardManager? = null
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
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
|
||||
private var currentThemeResId: Int = 0
|
||||
private var isNumberRowVisible: Boolean = false
|
||||
|
||||
val textInputManager: TextInputManager
|
||||
val mediaInputManager: MediaInputManager
|
||||
@@ -81,14 +101,22 @@ class FlorisBoard : InputMethodService() {
|
||||
|
||||
companion object {
|
||||
private const val IME_ID: String = "dev.patrickgold.florisboard/.ime.core.FlorisBoard"
|
||||
private const val IME_ID_DEBUG: String = "dev.patrickgold.florisboard.debug/dev.patrickgold.florisboard.ime.core.FlorisBoard"
|
||||
|
||||
fun checkIfImeIsEnabled(context: Context): Boolean {
|
||||
val activeImeIds = Settings.Secure.getString(
|
||||
context.contentResolver,
|
||||
Settings.Secure.ENABLED_INPUT_METHODS
|
||||
)
|
||||
if (BuildConfig.DEBUG) Log.i(FlorisBoard::class.simpleName, "List of active IMEs: $activeImeIds")
|
||||
return activeImeIds.split(":").contains(IME_ID)
|
||||
Timber.i("List of active IMEs: $activeImeIds")
|
||||
return when {
|
||||
BuildConfig.DEBUG -> {
|
||||
activeImeIds.split(":").contains(IME_ID_DEBUG)
|
||||
}
|
||||
else -> {
|
||||
activeImeIds.split(":").contains(IME_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkIfImeIsSelected(context: Context): Boolean {
|
||||
@@ -96,14 +124,33 @@ class FlorisBoard : InputMethodService() {
|
||||
context.contentResolver,
|
||||
Settings.Secure.DEFAULT_INPUT_METHOD
|
||||
)
|
||||
if (BuildConfig.DEBUG) Log.i(FlorisBoard::class.simpleName, "Selected IME: $selectedImeId")
|
||||
return selectedImeId == IME_ID
|
||||
Timber.i("Selected IME: $selectedImeId")
|
||||
return when {
|
||||
BuildConfig.DEBUG -> {
|
||||
selectedImeId == IME_ID_DEBUG
|
||||
}
|
||||
else -> {
|
||||
selectedImeId == IME_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getInstance(): FlorisBoard {
|
||||
return florisboardInstance!!
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getInstanceOrNull(): FlorisBoard? {
|
||||
return florisboardInstance
|
||||
}
|
||||
|
||||
fun getDayNightBaseThemeId(isNightTheme: Boolean): Int {
|
||||
return when (isNightTheme) {
|
||||
true -> R.style.KeyboardThemeBase_Night
|
||||
else -> R.style.KeyboardThemeBase_Day
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -125,187 +172,223 @@ class FlorisBoard : InputMethodService() {
|
||||
.build()
|
||||
)
|
||||
}
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreate()")
|
||||
Timber.i("onCreate()")
|
||||
|
||||
imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
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(this)
|
||||
prefs = PrefHelper.getDefaultInstance(this)
|
||||
prefs.initDefaultPreferences()
|
||||
prefs.sync()
|
||||
subtypeManager = SubtypeManager(this, prefs)
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
|
||||
currentThemeResId = prefs.theme.getSelectedThemeResId()
|
||||
currentThemeIsNight = themeManager.activeTheme.isNightTheme
|
||||
currentThemeResId = getDayNightBaseThemeId(currentThemeIsNight)
|
||||
isNumberRowVisible = prefs.keyboard.numberRow
|
||||
setTheme(currentThemeResId)
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
|
||||
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
|
||||
|
||||
super.onCreate()
|
||||
textInputManager.onCreate()
|
||||
mediaInputManager.onCreate()
|
||||
eventListeners.toList().forEach { it?.get()?.onCreate() }
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateInputView(): View? {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreateInputView()")
|
||||
Timber.i("onCreateInputView()")
|
||||
|
||||
baseContext.setTheme(currentThemeResId)
|
||||
|
||||
inputView = layoutInflater.inflate(R.layout.florisboard, null) as InputView
|
||||
inputWindowView = layoutInflater.inflate(R.layout.florisboard, null) as InputWindowView
|
||||
popupLayerView = inputWindowView?.findViewById(R.id.popup_layer)
|
||||
|
||||
textInputManager.onCreateInputView()
|
||||
mediaInputManager.onCreateInputView()
|
||||
eventListeners.toList().forEach { it?.get()?.onCreateInputView() }
|
||||
|
||||
return inputView
|
||||
return inputWindowView
|
||||
}
|
||||
|
||||
fun registerInputView(inputView: InputView) {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "registerInputView(inputView)")
|
||||
Timber.i("registerInputView($inputView)")
|
||||
|
||||
this.inputView = inputView
|
||||
initializeOneHandedEnvironment()
|
||||
updateSoftInputWindowLayoutParameters()
|
||||
updateOneHandedPanelVisibility()
|
||||
themeManager.notifyCallbackReceivers()
|
||||
setActiveInput(R.id.text_input)
|
||||
|
||||
textInputManager.onRegisterInputView(inputView)
|
||||
mediaInputManager.onRegisterInputView(inputView)
|
||||
eventListeners.toList().forEach { it?.get()?.onRegisterInputView(inputView) }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onDestroy()")
|
||||
Timber.i("onDestroy()")
|
||||
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
clipboardManager?.removePrimaryClipChangedListener(this)
|
||||
osHandler.removeCallbacksAndMessages(null)
|
||||
florisboardInstance = null
|
||||
|
||||
eventListeners.toList().forEach { it?.get()?.onDestroy() }
|
||||
eventListeners.clear()
|
||||
super.onDestroy()
|
||||
textInputManager.onDestroy()
|
||||
mediaInputManager.onDestroy()
|
||||
}
|
||||
|
||||
override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
|
||||
Timber.i("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)
|
||||
Timber.i("onStartInputView($info, $restarting)")
|
||||
Timber.i("onStartInputView: ${info?.debugSummarize()}")
|
||||
|
||||
super.onStartInputView(info, restarting)
|
||||
textInputManager.onStartInputView(info, restarting)
|
||||
mediaInputManager.onStartInputView(info, restarting)
|
||||
activeEditorInstance = EditorInstance.from(info, this)
|
||||
themeManager.updateRemoteColorValues(activeEditorInstance.packageName)
|
||||
eventListeners.toList().forEach {
|
||||
it?.get()?.onStartInputView(activeEditorInstance, restarting)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInputView(finishingInput: Boolean) {
|
||||
currentInputConnection?.requestCursorUpdates(0)
|
||||
Timber.i( "onFinishInputView($finishingInput)")
|
||||
|
||||
if (finishingInput) {
|
||||
activeEditorInstance = EditorInstance.default()
|
||||
}
|
||||
|
||||
super.onFinishInputView(finishingInput)
|
||||
textInputManager.onFinishInputView(finishingInput)
|
||||
mediaInputManager.onFinishInputView(finishingInput)
|
||||
eventListeners.toList().forEach { it?.get()?.onFinishInputView(finishingInput) }
|
||||
}
|
||||
|
||||
override fun onFinishInput() {
|
||||
Timber.i("onFinishInput()")
|
||||
|
||||
super.onFinishInput()
|
||||
currentInputConnection?.requestCursorUpdates(0)
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowShown()")
|
||||
Timber.i("onWindowShown()")
|
||||
|
||||
prefs.sync()
|
||||
updateThemeIfNecessary()
|
||||
val newIsNumberRowVisible = prefs.keyboard.numberRow
|
||||
if (isNumberRowVisible != newIsNumberRowVisible) {
|
||||
textInputManager.layoutManager.clearLayoutCache(KeyboardMode.CHARACTERS)
|
||||
isNumberRowVisible = newIsNumberRowVisible
|
||||
}
|
||||
themeManager.update()
|
||||
updateOneHandedPanelVisibility()
|
||||
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
|
||||
onSubtypeChanged(activeSubtype)
|
||||
setActiveInput(R.id.text_input)
|
||||
|
||||
super.onWindowShown()
|
||||
textInputManager.onWindowShown()
|
||||
mediaInputManager.onWindowShown()
|
||||
eventListeners.toList().forEach { it?.get()?.onWindowShown() }
|
||||
}
|
||||
|
||||
override fun onWindowHidden() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowHidden()")
|
||||
Timber.i("onWindowHidden()")
|
||||
|
||||
super.onWindowHidden()
|
||||
textInputManager.onWindowHidden()
|
||||
mediaInputManager.onWindowHidden()
|
||||
eventListeners.toList().forEach { it?.get()?.onWindowHidden() }
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
Timber.i("onConfigurationChanged($newConfig)")
|
||||
if (isInputViewShown) {
|
||||
updateOneHandedPanelVisibility()
|
||||
}
|
||||
|
||||
super.onConfigurationChanged(newConfig)
|
||||
textInputManager.onConfigurationChanged(newConfig)
|
||||
mediaInputManager.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
||||
super.onUpdateCursorAnchorInfo(cursorAnchorInfo)
|
||||
textInputManager.onUpdateCursorAnchorInfo(cursorAnchorInfo)
|
||||
mediaInputManager.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
|
||||
) {
|
||||
Timber.i("onUpdateSelection($oldSelStart, $oldSelEnd, $newSelStart, $newSelEnd, $candidatesStart, $candidatesEnd)")
|
||||
|
||||
super.onUpdateSelection(
|
||||
oldSelStart,
|
||||
oldSelEnd,
|
||||
newSelStart,
|
||||
newSelEnd,
|
||||
candidatesStart,
|
||||
candidatesEnd
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd,
|
||||
candidatesStart, candidatesEnd
|
||||
)
|
||||
textInputManager.onUpdateSelection(
|
||||
oldSelStart,
|
||||
oldSelEnd,
|
||||
newSelStart,
|
||||
newSelEnd,
|
||||
candidatesStart,
|
||||
candidatesEnd
|
||||
)
|
||||
mediaInputManager.onUpdateSelection(
|
||||
oldSelStart,
|
||||
oldSelEnd,
|
||||
newSelStart,
|
||||
newSelEnd,
|
||||
candidatesStart,
|
||||
candidatesEnd
|
||||
activeEditorInstance.onUpdateSelection(
|
||||
oldSelStart, oldSelEnd,
|
||||
newSelStart, newSelEnd
|
||||
)
|
||||
eventListeners.toList().forEach { it?.get()?.onUpdateSelection() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the preferences if the selected theme res id has changed and updates the theme only
|
||||
* then by rebuilding the UI and setting the navigation bar theme manually.
|
||||
*/
|
||||
private fun updateThemeIfNecessary() {
|
||||
val newThemeResId = prefs.theme.getSelectedThemeResId()
|
||||
if (newThemeResId != currentThemeResId) {
|
||||
currentThemeResId = newThemeResId
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
// 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 = theme.isNightTheme
|
||||
if (currentThemeIsNight != newThemeIsNightMode) {
|
||||
currentThemeResId = getDayNightBaseThemeId(newThemeIsNightMode)
|
||||
currentThemeIsNight = newThemeIsNightMode
|
||||
setInputView(onCreateInputView())
|
||||
val w = window?.window ?: return
|
||||
w.navigationBarColor = getColorFromAttr(baseContext, android.R.attr.navigationBarColor)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
var flags = w.decorView.systemUiVisibility
|
||||
flags = if (getBooleanFromAttr(baseContext, android.R.attr.windowLightNavigationBar)) {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
} else {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||
}
|
||||
w.decorView.systemUiVisibility = flags
|
||||
return
|
||||
}
|
||||
|
||||
// Get Window and the flags of the DecorView
|
||||
val w = window?.window ?: return
|
||||
var flags = w.decorView.systemUiVisibility
|
||||
|
||||
// Update navigation bar theme
|
||||
w.navigationBarColor = theme.getAttr(Theme.Attr.WINDOW_NAVIGATION_BAR_COLOR).toSolidColor().color
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
flags = if (theme.getAttr(Theme.Attr.WINDOW_NAVIGATION_BAR_LIGHT).toOnOff().state) {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
} else {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||
}
|
||||
}
|
||||
|
||||
// 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(theme.getAttr(Theme.Attr.KEYBOARD_BACKGROUND).toSolidColor().color)
|
||||
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(theme.getAttr(Theme.Attr.ONE_HANDED_BACKGROUND).toSolidColor().color)
|
||||
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(theme.getAttr(Theme.Attr.ONE_HANDED_BACKGROUND).toSolidColor().color)
|
||||
ColorStateList.valueOf(theme.getAttr(Theme.Attr.ONE_HANDED_FOREGROUND).toSolidColor().color).also {
|
||||
inputView?.oneHandedCtrlMoveStart?.imageTintList = it
|
||||
inputView?.oneHandedCtrlMoveEnd?.imageTintList = it
|
||||
inputView?.oneHandedCtrlCloseStart?.imageTintList = it
|
||||
inputView?.oneHandedCtrlCloseEnd?.imageTintList = it
|
||||
}
|
||||
eventListeners.toList().forEach { it?.get()?.onApplyThemeAttributes() }
|
||||
}
|
||||
|
||||
override fun onComputeInsets(outInsets: Insets?) {
|
||||
super.onComputeInsets(outInsets)
|
||||
val inputView = this.inputView ?: return
|
||||
val inputWindowView = this.inputWindowView ?: return
|
||||
// TODO: Check also if the keyboard is currently suppressed by a hardware keyboard
|
||||
if (!isInputViewShown) {
|
||||
outInsets?.contentTopInsets = inputView.height
|
||||
outInsets?.visibleTopInsets = inputView.height
|
||||
outInsets?.contentTopInsets = inputWindowView.height
|
||||
outInsets?.visibleTopInsets = inputWindowView.height
|
||||
return
|
||||
}
|
||||
val innerInputViewContainer =
|
||||
inputView.findViewById<LinearLayout>(R.id.inner_input_view_container) ?: return
|
||||
val visibleTopY = inputView.height - innerInputViewContainer.measuredHeight
|
||||
val visibleTopY = inputWindowView.height - inputView.measuredHeight
|
||||
outInsets?.contentTopInsets = visibleTopY
|
||||
outInsets?.visibleTopInsets = visibleTopY
|
||||
}
|
||||
@@ -321,8 +404,8 @@ class FlorisBoard : InputMethodService() {
|
||||
private fun updateSoftInputWindowLayoutParameters() {
|
||||
val w = window?.window ?: return
|
||||
ViewLayoutUtils.updateLayoutHeightOf(w, WindowManager.LayoutParams.MATCH_PARENT)
|
||||
val inputView = this.inputView
|
||||
if (inputView != null) {
|
||||
val inputWindowView = this.inputWindowView
|
||||
if (inputWindowView != null) {
|
||||
val layoutHeight = if (isFullscreenMode) {
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
} else {
|
||||
@@ -331,7 +414,7 @@ class FlorisBoard : InputMethodService() {
|
||||
val inputArea = w.findViewById<View>(android.R.id.inputArea)
|
||||
ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight)
|
||||
ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM)
|
||||
ViewLayoutUtils.updateLayoutHeightOf(inputView, layoutHeight)
|
||||
ViewLayoutUtils.updateLayoutHeightOf(inputWindowView, layoutHeight)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,9 +422,9 @@ class FlorisBoard : InputMethodService() {
|
||||
* Makes a key press vibration if the user has this feature enabled in the preferences.
|
||||
*/
|
||||
fun keyPressVibrate() {
|
||||
if (prefs.looknfeel.vibrationEnabled) {
|
||||
var vibrationStrength = prefs.looknfeel.vibrationStrength
|
||||
if (vibrationStrength == 0 && prefs.looknfeel.vibrationEnabledSystem) {
|
||||
if (prefs.keyboard.vibrationEnabled) {
|
||||
var vibrationStrength = prefs.keyboard.vibrationStrength
|
||||
if (vibrationStrength == -1 && prefs.keyboard.vibrationEnabledSystem) {
|
||||
vibrationStrength = 36
|
||||
}
|
||||
if (vibrationStrength > 0) {
|
||||
@@ -363,15 +446,15 @@ class FlorisBoard : InputMethodService() {
|
||||
* Makes a key press sound if the user has this feature enabled in the preferences.
|
||||
*/
|
||||
fun keyPressSound(keyData: KeyData? = null) {
|
||||
if (prefs.looknfeel.soundEnabled) {
|
||||
val soundVolume = prefs.looknfeel.soundVolume
|
||||
if (prefs.keyboard.soundEnabled) {
|
||||
val soundVolume = prefs.keyboard.soundVolume
|
||||
val effect = when (keyData?.code) {
|
||||
KeyCode.SPACE -> AudioManager.FX_KEYPRESS_SPACEBAR
|
||||
KeyCode.DELETE -> AudioManager.FX_KEYPRESS_DELETE
|
||||
KeyCode.ENTER -> AudioManager.FX_KEYPRESS_RETURN
|
||||
else -> AudioManager.FX_KEYPRESS_STANDARD
|
||||
}
|
||||
if (soundVolume == 0 && prefs.looknfeel.soundEnabledSystem) {
|
||||
if (soundVolume == -1 && prefs.keyboard.soundEnabledSystem) {
|
||||
audioManager!!.playSoundEffect(effect)
|
||||
} else if (soundVolume > 0) {
|
||||
audioManager!!.playSoundEffect(effect, soundVolume / 100f)
|
||||
@@ -379,6 +462,20 @@ class FlorisBoard : InputMethodService() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a given [SwipeAction]. Ignores any [SwipeAction] but the ones relevant for this
|
||||
* class.
|
||||
*/
|
||||
fun executeSwipeAction(swipeAction: SwipeAction) {
|
||||
when (swipeAction) {
|
||||
SwipeAction.HIDE_KEYBOARD -> requestHideSelf(0)
|
||||
SwipeAction.SWITCH_TO_PREV_SUBTYPE -> switchToPrevSubtype()
|
||||
SwipeAction.SWITCH_TO_NEXT_SUBTYPE -> switchToNextSubtype()
|
||||
SwipeAction.SWITCH_TO_PREV_KEYBOARD -> switchToPrevKeyboard()
|
||||
else -> textInputManager.executeSwipeAction(swipeAction)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the IME and launches [SettingsMainActivity].
|
||||
*/
|
||||
@@ -398,6 +495,26 @@ class FlorisBoard : InputMethodService() {
|
||||
return subtypeManager.subtypes.size > 1
|
||||
}
|
||||
|
||||
fun switchToPrevKeyboard(){
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
switchToPreviousInputMethod()
|
||||
} else {
|
||||
window.window?.let { window ->
|
||||
imeManager?.switchToLastInputMethod(window.attributes.token)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e,"Unable to switch to the previous IME")
|
||||
imeManager?.showInputMethodPicker()
|
||||
}
|
||||
}
|
||||
|
||||
fun switchToPrevSubtype() {
|
||||
activeSubtype = subtypeManager.switchToPrevSubtype() ?: Subtype.DEFAULT
|
||||
onSubtypeChanged(activeSubtype)
|
||||
}
|
||||
|
||||
fun switchToNextSubtype() {
|
||||
activeSubtype = subtypeManager.switchToNextSubtype() ?: Subtype.DEFAULT
|
||||
onSubtypeChanged(activeSubtype)
|
||||
@@ -411,50 +528,46 @@ class FlorisBoard : InputMethodService() {
|
||||
fun setActiveInput(type: Int) {
|
||||
when (type) {
|
||||
R.id.text_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild =
|
||||
inputView?.mainViewFlipper?.indexOfChild(textInputManager.textViewGroup) ?: 0
|
||||
inputView?.mainViewFlipper?.displayedChild = 0
|
||||
}
|
||||
R.id.media_input -> {
|
||||
inputView?.mainViewFlipper?.displayedChild =
|
||||
inputView?.mainViewFlipper?.indexOfChild(mediaInputManager.mediaViewGroup) ?: 0
|
||||
inputView?.mainViewFlipper?.displayedChild = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeOneHandedEnvironment() {
|
||||
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_start)
|
||||
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
|
||||
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_end)
|
||||
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
|
||||
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_start)
|
||||
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
|
||||
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_end)
|
||||
?.setOnClickListener { v -> onOneHandedPanelButtonClick(v) }
|
||||
{ v:View -> onOneHandedPanelButtonClick(v) }.also {
|
||||
inputView?.oneHandedCtrlMoveStart?.setOnClickListener(it)
|
||||
inputView?.oneHandedCtrlMoveEnd?.setOnClickListener(it)
|
||||
inputView?.oneHandedCtrlCloseStart?.setOnClickListener(it)
|
||||
inputView?.oneHandedCtrlCloseEnd?.setOnClickListener(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onOneHandedPanelButtonClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.one_handed_ctrl_move_start -> {
|
||||
prefs.looknfeel.oneHandedMode = "start"
|
||||
prefs.keyboard.oneHandedMode = "start"
|
||||
}
|
||||
R.id.one_handed_ctrl_move_end -> {
|
||||
prefs.looknfeel.oneHandedMode = "end"
|
||||
prefs.keyboard.oneHandedMode = "end"
|
||||
}
|
||||
R.id.one_handed_ctrl_close_start,
|
||||
R.id.one_handed_ctrl_close_end -> {
|
||||
prefs.looknfeel.oneHandedMode = "off"
|
||||
prefs.keyboard.oneHandedMode = "off"
|
||||
}
|
||||
}
|
||||
updateOneHandedPanelVisibility()
|
||||
}
|
||||
|
||||
fun toggleOneHandedMode() {
|
||||
when (prefs.looknfeel.oneHandedMode) {
|
||||
when (prefs.keyboard.oneHandedMode) {
|
||||
"off" -> {
|
||||
prefs.looknfeel.oneHandedMode = "end"
|
||||
prefs.keyboard.oneHandedMode = "end"
|
||||
}
|
||||
else -> {
|
||||
prefs.looknfeel.oneHandedMode = "off"
|
||||
prefs.keyboard.oneHandedMode = "off"
|
||||
}
|
||||
}
|
||||
updateOneHandedPanelVisibility()
|
||||
@@ -465,7 +578,7 @@ class FlorisBoard : InputMethodService() {
|
||||
inputView?.oneHandedCtrlPanelStart?.visibility = View.GONE
|
||||
inputView?.oneHandedCtrlPanelEnd?.visibility = View.GONE
|
||||
} else {
|
||||
when (prefs.looknfeel.oneHandedMode) {
|
||||
when (prefs.keyboard.oneHandedMode) {
|
||||
"off" -> {
|
||||
inputView?.oneHandedCtrlPanelStart?.visibility = View.GONE
|
||||
inputView?.oneHandedCtrlPanelEnd?.visibility = View.GONE
|
||||
@@ -486,30 +599,54 @@ 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.
|
||||
* @return True if the listener has been added successfully, false otherwise.
|
||||
*/
|
||||
fun addEventListener(listener: EventListener): Boolean {
|
||||
return eventListeners.add(WeakReference(listener))
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given [listener] from the list which will receive FlorisBoard events.
|
||||
*
|
||||
* TODO: implement this function with a proper iterator
|
||||
*
|
||||
* @param listener The same listener object which was used in [addEventListener].
|
||||
* @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 {
|
||||
eventListeners.toList().forEach {
|
||||
if (it?.get() == listener) {
|
||||
return eventListeners.remove(it)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
interface EventListener {
|
||||
fun onCreate() {}
|
||||
fun onCreateInputView() {}
|
||||
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 onConfigurationChanged(newConfig: Configuration) {}
|
||||
|
||||
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) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ViewFlipper
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
/**
|
||||
* Custom ViewFlipper class used to prevent an unnecessary exception to be thrown when it is
|
||||
* detached from a window.
|
||||
*
|
||||
* Based on the solution of this SO answer: https://stackoverflow.com/a/8208874/6801193
|
||||
*/
|
||||
class FlorisViewFlipper : ViewFlipper {
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
try {
|
||||
super.onDetachedFromWindow()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
stopFlipping()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,36 @@
|
||||
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.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyVariation
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
import timber.log.Timber
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Root view of the keyboard. Notifies [FlorisBoard] when it has been attached to a window.
|
||||
*/
|
||||
class InputView : FrameLayout {
|
||||
|
||||
class InputView : LinearLayout {
|
||||
private var florisboard: FlorisBoard = FlorisBoard.getInstance()
|
||||
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
|
||||
|
||||
var desiredInputViewHeight: Float = resources.getDimension(R.dimen.inputView_baseHeight)
|
||||
private set
|
||||
var desiredSmartbarHeight: Float = resources.getDimension(R.dimen.smartbar_baseHeight)
|
||||
private set
|
||||
var desiredTextKeyboardViewHeight: Float = resources.getDimension(R.dimen.textKeyboardView_baseHeight)
|
||||
private set
|
||||
var desiredMediaKeyboardViewHeight: Float = resources.getDimension(R.dimen.mediaKeyboardView_baseHeight)
|
||||
private set
|
||||
|
||||
var mainViewFlipper: ViewFlipper? = null
|
||||
private set
|
||||
@@ -38,20 +54,120 @@ class InputView : FrameLayout {
|
||||
private set
|
||||
var oneHandedCtrlPanelEnd: LinearLayout? = null
|
||||
private set
|
||||
var oneHandedCtrlMoveStart: ImageButton? = null
|
||||
private set
|
||||
var oneHandedCtrlMoveEnd: ImageButton? = null
|
||||
private set
|
||||
var oneHandedCtrlCloseStart: ImageButton? = null
|
||||
private set
|
||||
var oneHandedCtrlCloseEnd: ImageButton? = null
|
||||
private set
|
||||
|
||||
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()")
|
||||
Timber.i("onAttachedToWindow()")
|
||||
|
||||
super.onAttachedToWindow()
|
||||
|
||||
mainViewFlipper = findViewById(R.id.main_view_flipper)
|
||||
oneHandedCtrlPanelStart = findViewById(R.id.one_handed_ctrl_panel_start)
|
||||
oneHandedCtrlPanelEnd = findViewById(R.id.one_handed_ctrl_panel_end)
|
||||
oneHandedCtrlMoveStart = findViewById(R.id.one_handed_ctrl_move_start)
|
||||
oneHandedCtrlMoveEnd = findViewById(R.id.one_handed_ctrl_move_end)
|
||||
oneHandedCtrlCloseStart = findViewById(R.id.one_handed_ctrl_close_start)
|
||||
oneHandedCtrlCloseEnd = findViewById(R.id.one_handed_ctrl_close_end)
|
||||
|
||||
florisboard.registerInputView(this)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val heightFactor = when (resources.configuration.orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> 1.0f
|
||||
else -> if (prefs.keyboard.oneHandedMode != "off") {
|
||||
0.9f
|
||||
} else {
|
||||
1.0f
|
||||
}
|
||||
} * when (prefs.keyboard.heightFactor) {
|
||||
"extra_short" -> 0.85f
|
||||
"short" -> 0.90f
|
||||
"mid_short" -> 0.95f
|
||||
"normal" -> 1.00f
|
||||
"mid_tall" -> 1.05f
|
||||
"tall" -> 1.10f
|
||||
"extra_tall" -> 1.15f
|
||||
"custom" -> prefs.keyboard.heightFactorCustom.toFloat() / 100.0f
|
||||
else -> 1.00f
|
||||
}
|
||||
var baseHeight = calcInputViewHeight() * heightFactor
|
||||
var baseSmartbarHeight = 0.16129f * baseHeight
|
||||
var baseTextInputHeight = baseHeight - baseSmartbarHeight
|
||||
val tim = florisboard.textInputManager
|
||||
val shouldGiveAdditionalSpace = prefs.keyboard.numberRow &&
|
||||
!(tim.getActiveKeyboardMode() == KeyboardMode.NUMERIC ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE2)
|
||||
if (shouldGiveAdditionalSpace) {
|
||||
val additionalHeight = desiredTextKeyboardViewHeight * 0.18f
|
||||
baseHeight += additionalHeight
|
||||
baseTextInputHeight += additionalHeight
|
||||
}
|
||||
val smartbarDisabled = !prefs.smartbar.enabled ||
|
||||
tim.keyVariation == KeyVariation.PASSWORD && prefs.keyboard.numberRow ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.NUMERIC ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE ||
|
||||
tim.getActiveKeyboardMode() == KeyboardMode.PHONE2
|
||||
if (smartbarDisabled) {
|
||||
baseHeight = baseTextInputHeight
|
||||
baseSmartbarHeight = 0.0f
|
||||
}
|
||||
desiredInputViewHeight = baseHeight
|
||||
desiredSmartbarHeight = baseSmartbarHeight
|
||||
desiredTextKeyboardViewHeight = baseTextInputHeight
|
||||
desiredMediaKeyboardViewHeight = baseHeight
|
||||
// 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).
|
||||
baseHeight += ViewLayoutUtils.convertDpToPixel(
|
||||
florisboard.prefs.keyboard.bottomOffset.toFloat(),
|
||||
context
|
||||
)
|
||||
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(baseHeight.roundToInt(), 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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ViewFlipper
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.R
|
||||
|
||||
/**
|
||||
* Root view of the keyboard.
|
||||
*/
|
||||
class InputWindowView : FrameLayout {
|
||||
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)
|
||||
}
|
||||
@@ -21,6 +21,12 @@ import android.content.SharedPreferences
|
||||
import android.provider.Settings
|
||||
import androidx.preference.PreferenceManager
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.DistanceThreshold
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
|
||||
import dev.patrickgold.florisboard.ime.text.gestures.VelocityThreshold
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeMode
|
||||
import dev.patrickgold.florisboard.util.TimeUtil
|
||||
import dev.patrickgold.florisboard.util.VersionName
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
@@ -37,9 +43,12 @@ class PrefHelper(
|
||||
|
||||
val advanced = Advanced(this)
|
||||
val correction = Correction(this)
|
||||
val gestures = Gestures(this)
|
||||
val glide = Glide(this)
|
||||
val internal = Internal(this)
|
||||
val keyboard = Keyboard(this)
|
||||
val looknfeel = Looknfeel(this)
|
||||
val localization = Localization(this)
|
||||
val smartbar = Smartbar(this)
|
||||
val suggestion = Suggestion(this)
|
||||
val theme = Theme(this)
|
||||
|
||||
@@ -110,15 +119,30 @@ class PrefHelper(
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var defaultInstance: PrefHelper? = null
|
||||
|
||||
@Synchronized
|
||||
fun getDefaultInstance(context: Context): PrefHelper {
|
||||
if (defaultInstance == null) {
|
||||
defaultInstance = PrefHelper(context)
|
||||
}
|
||||
return defaultInstance!!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the [PreferenceManager] to set the defined preferences to their default values, if
|
||||
* they have not been initialized yet.
|
||||
*/
|
||||
fun initDefaultPreferences() {
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_advanced, true)
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_gestures, true)
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_keyboard, true)
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_looknfeel, true)
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_theme, true)
|
||||
PreferenceManager.setDefaultValues(context, R.xml.prefs_typing, true)
|
||||
//theme.dayThemeRef = "assets:ime/theme/floris_day.json"
|
||||
//theme.nightThemeRef = "assets:ime/theme/floris_night.json"
|
||||
//setPref(Keyboard.SUBTYPES, "")
|
||||
//setPref(Internal.IS_IME_SET_UP, false)
|
||||
}
|
||||
@@ -128,10 +152,10 @@ class PrefHelper(
|
||||
*/
|
||||
fun sync() {
|
||||
val contentResolver = context.contentResolver
|
||||
looknfeel.soundEnabledSystem = Settings.System.getInt(
|
||||
keyboard.soundEnabledSystem = Settings.System.getInt(
|
||||
contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 0
|
||||
) != 0
|
||||
looknfeel.vibrationEnabledSystem = Settings.System.getInt(
|
||||
keyboard.vibrationEnabledSystem = Settings.System.getInt(
|
||||
contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0
|
||||
) != 0
|
||||
|
||||
@@ -147,6 +171,7 @@ class PrefHelper(
|
||||
companion object {
|
||||
const val SETTINGS_THEME = "advanced__settings_theme"
|
||||
const val SHOW_APP_ICON = "advanced__show_app_icon"
|
||||
const val FORCE_PRIVATE_MODE = "advanced__force_private_mode"
|
||||
}
|
||||
|
||||
var settingsTheme: String = ""
|
||||
@@ -155,6 +180,9 @@ class PrefHelper(
|
||||
var showAppIcon: Boolean = false
|
||||
get() = prefHelper.getPref(SHOW_APP_ICON, true)
|
||||
private set
|
||||
var forcePrivateMode: Boolean
|
||||
get() = prefHelper.getPref(FORCE_PRIVATE_MODE, false)
|
||||
set(v) = prefHelper.setPref(FORCE_PRIVATE_MODE, v)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,37 +190,112 @@ 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for internal preferences.
|
||||
* Wrapper class for gestures preferences.
|
||||
*/
|
||||
class Gestures(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val SWIPE_UP = "gestures__swipe_up"
|
||||
const val SWIPE_DOWN = "gestures__swipe_down"
|
||||
const val SWIPE_LEFT = "gestures__swipe_left"
|
||||
const val SWIPE_RIGHT = "gestures__swipe_right"
|
||||
const val SPACE_BAR_SWIPE_LEFT = "gestures__space_bar_swipe_left"
|
||||
const val SPACE_BAR_SWIPE_RIGHT = "gestures__space_bar_swipe_right"
|
||||
const val SPACE_BAR_SWIPE_UP = "gestures__space_bar_swipe_up"
|
||||
const val DELETE_KEY_SWIPE_LEFT = "gestures__delete_key_swipe_left"
|
||||
const val SWIPE_VELOCITY_THRESHOLD = "gestures__swipe_velocity_threshold"
|
||||
const val SWIPE_DISTANCE_THRESHOLD = "gestures__swipe_distance_threshold"
|
||||
}
|
||||
|
||||
var swipeUp: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_UP, "no_action"))
|
||||
set(v) = prefHelper.setPref(SWIPE_UP, v)
|
||||
var swipeDown: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_DOWN, "no_action"))
|
||||
set(v) = prefHelper.setPref(SWIPE_DOWN, v)
|
||||
var swipeLeft: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_LEFT, "no_action"))
|
||||
set(v) = prefHelper.setPref(SWIPE_LEFT, v)
|
||||
var swipeRight: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_RIGHT, "no_action"))
|
||||
set(v) = prefHelper.setPref(SWIPE_RIGHT, v)
|
||||
var spaceBarSwipeUp: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_UP, "no_action"))
|
||||
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_UP, v)
|
||||
var spaceBarSwipeLeft: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_LEFT, "no_action"))
|
||||
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_LEFT, v)
|
||||
var spaceBarSwipeRight: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_RIGHT, "no_action"))
|
||||
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_RIGHT, v)
|
||||
var deleteKeySwipeLeft: SwipeAction
|
||||
get() = SwipeAction.fromString(prefHelper.getPref(DELETE_KEY_SWIPE_LEFT, "no_action"))
|
||||
set(v) = prefHelper.setPref(DELETE_KEY_SWIPE_LEFT, v)
|
||||
var swipeVelocityThreshold: VelocityThreshold
|
||||
get() = VelocityThreshold.fromString(prefHelper.getPref(SWIPE_VELOCITY_THRESHOLD, "normal"))
|
||||
set(v) = prefHelper.setPref(SWIPE_VELOCITY_THRESHOLD, v)
|
||||
var swipeDistanceThreshold: DistanceThreshold
|
||||
get() = DistanceThreshold.fromString(prefHelper.getPref(SWIPE_DISTANCE_THRESHOLD, "normal"))
|
||||
set(v) = prefHelper.setPref(SWIPE_DISTANCE_THRESHOLD, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for glide preferences.
|
||||
*/
|
||||
class Glide(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ENABLED = "glide__enabled"
|
||||
const val SHOW_TRAIL = "glide__show_trail"
|
||||
}
|
||||
|
||||
var enabled: Boolean
|
||||
get() = prefHelper.getPref(ENABLED, false)
|
||||
set(v) = prefHelper.setPref(ENABLED, v)
|
||||
var showTrail: Boolean
|
||||
get() = prefHelper.getPref(SHOW_TRAIL, false)
|
||||
set(v) = prefHelper.setPref(SHOW_TRAIL, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for internal preferences. A preference qualifies as an internal pref if the
|
||||
* user has no ability to control this preference's value directly (via a UI pref view).
|
||||
*/
|
||||
class Internal(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val IS_IME_SET_UP = "internal__is_ime_set_up"
|
||||
const val VERSION_ON_INSTALL = "internal__version_on_install"
|
||||
const val VERSION_LAST_USE = "internal__version_last_use"
|
||||
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
|
||||
const val IS_IME_SET_UP = "internal__is_ime_set_up"
|
||||
const val VERSION_ON_INSTALL = "internal__version_on_install"
|
||||
const val VERSION_LAST_USE = "internal__version_last_use"
|
||||
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
|
||||
}
|
||||
|
||||
var isImeSetUp: Boolean
|
||||
get() = prefHelper.getPref(IS_IME_SET_UP, false)
|
||||
set(value) = prefHelper.setPref(IS_IME_SET_UP, value)
|
||||
get() = prefHelper.getPref(IS_IME_SET_UP, false)
|
||||
set(v) = prefHelper.setPref(IS_IME_SET_UP, v)
|
||||
var versionOnInstall: String
|
||||
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
|
||||
set(value) = prefHelper.setPref(VERSION_ON_INSTALL, value)
|
||||
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
|
||||
set(v) = prefHelper.setPref(VERSION_ON_INSTALL, v)
|
||||
var versionLastUse: String
|
||||
get() = prefHelper.getPref(VERSION_LAST_USE, VersionName.DEFAULT_RAW)
|
||||
set(value) = prefHelper.setPref(VERSION_LAST_USE, value)
|
||||
get() = prefHelper.getPref(VERSION_LAST_USE, VersionName.DEFAULT_RAW)
|
||||
set(v) = prefHelper.setPref(VERSION_LAST_USE, v)
|
||||
var versionLastChangelog: String
|
||||
get() = prefHelper.getPref(VERSION_LAST_CHANGELOG, VersionName.DEFAULT_RAW)
|
||||
set(value) = prefHelper.setPref(VERSION_LAST_CHANGELOG, value)
|
||||
get() = prefHelper.getPref(VERSION_LAST_CHANGELOG, VersionName.DEFAULT_RAW)
|
||||
set(v) = prefHelper.setPref(VERSION_LAST_CHANGELOG, v)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,72 +303,121 @@ class PrefHelper(
|
||||
*/
|
||||
class Keyboard(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ACTIVE_SUBTYPE_ID = "keyboard__active_subtype_id"
|
||||
const val SUBTYPES = "keyboard__subtypes"
|
||||
}
|
||||
|
||||
var activeSubtypeId: Int
|
||||
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, -1)
|
||||
set(v) = prefHelper.setPref(ACTIVE_SUBTYPE_ID, v)
|
||||
var subtypes: String
|
||||
get() = prefHelper.getPref(SUBTYPES, "")
|
||||
set(v) = prefHelper.setPref(SUBTYPES, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for looknfeel preferences.
|
||||
*/
|
||||
class Looknfeel(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val HEIGHT_FACTOR = "looknfeel__height_factor"
|
||||
const val LONG_PRESS_DELAY = "looknfeel__long_press_delay"
|
||||
const val ONE_HANDED_MODE = "looknfeel__one_handed_mode"
|
||||
const val SOUND_ENABLED = "looknfeel__sound_enabled"
|
||||
const val SOUND_VOLUME = "looknfeel__sound_volume"
|
||||
const val VIBRATION_ENABLED = "looknfeel__vibration_enabled"
|
||||
const val VIBRATION_STRENGTH = "looknfeel__vibration_strength"
|
||||
const val BOTTOM_OFFSET = "keyboard__bottom_offset"
|
||||
const val FONT_SIZE_MULTIPLIER_PORTRAIT = "keyboard__font_size_multiplier_portrait"
|
||||
const val FONT_SIZE_MULTIPLIER_LANDSCAPE = "keyboard__font_size_multiplier_landscape"
|
||||
const val HEIGHT_FACTOR = "keyboard__height_factor"
|
||||
const val HEIGHT_FACTOR_CUSTOM = "keyboard__height_factor_custom"
|
||||
const val HINTED_NUMBER_ROW_MODE = "keyboard__hinted_number_row_mode"
|
||||
const val HINTED_SYMBOLS_MODE = "keyboard__hinted_symbols_mode"
|
||||
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
|
||||
const val NUMBER_ROW = "keyboard__number_row"
|
||||
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
|
||||
const val POPUP_ENABLED = "keyboard__popup_enabled"
|
||||
const val SOUND_ENABLED = "keyboard__sound_enabled"
|
||||
const val SOUND_VOLUME = "keyboard__sound_volume"
|
||||
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
|
||||
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
|
||||
}
|
||||
|
||||
var bottomOffset: Int = 0
|
||||
get() = prefHelper.getPref(BOTTOM_OFFSET, 0)
|
||||
private set
|
||||
var fontSizeMultiplierPortrait: Int
|
||||
get() = prefHelper.getPref(FONT_SIZE_MULTIPLIER_PORTRAIT, 100)
|
||||
set(v) = prefHelper.setPref(FONT_SIZE_MULTIPLIER_PORTRAIT, v)
|
||||
var fontSizeMultiplierLandscape: Int
|
||||
get() = prefHelper.getPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, 100)
|
||||
set(v) = prefHelper.setPref(FONT_SIZE_MULTIPLIER_LANDSCAPE, v)
|
||||
var heightFactor: String = ""
|
||||
get() = prefHelper.getPref(HEIGHT_FACTOR, "normal")
|
||||
private set
|
||||
var heightFactorCustom: Int
|
||||
get() = prefHelper.getPref(HEIGHT_FACTOR_CUSTOM, 100)
|
||||
set(v) = prefHelper.setPref(HEIGHT_FACTOR_CUSTOM, v)
|
||||
var hintedNumberRowMode: KeyHintMode
|
||||
get() = KeyHintMode.fromString(prefHelper.getPref(HINTED_NUMBER_ROW_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
|
||||
set(v) = prefHelper.setPref(HINTED_NUMBER_ROW_MODE, v)
|
||||
var hintedSymbolsMode: KeyHintMode
|
||||
get() = KeyHintMode.fromString(prefHelper.getPref(HINTED_SYMBOLS_MODE, KeyHintMode.ENABLED_ACCENT_PRIORITY.toString()))
|
||||
set(v) = prefHelper.setPref(HINTED_SYMBOLS_MODE, v)
|
||||
var longPressDelay: Int = 0
|
||||
get() = prefHelper.getPref(LONG_PRESS_DELAY, 300)
|
||||
private set
|
||||
var numberRow: Boolean
|
||||
get() = prefHelper.getPref(NUMBER_ROW, false)
|
||||
set(v) = prefHelper.setPref(NUMBER_ROW, v)
|
||||
var oneHandedMode: String
|
||||
get() = prefHelper.getPref(ONE_HANDED_MODE, "off")
|
||||
set(value) = prefHelper.setPref(ONE_HANDED_MODE, value)
|
||||
var popupEnabled: Boolean = false
|
||||
get() = prefHelper.getPref(POPUP_ENABLED, true)
|
||||
private set
|
||||
var soundEnabled: Boolean = false
|
||||
get() = prefHelper.getPref(SOUND_ENABLED, true)
|
||||
private set
|
||||
var soundEnabledSystem: Boolean = false
|
||||
var soundVolume: Int = 0
|
||||
get() = prefHelper.getPref(SOUND_VOLUME, 0)
|
||||
get() = prefHelper.getPref(SOUND_VOLUME, -1)
|
||||
private set
|
||||
var vibrationEnabled: Boolean = false
|
||||
get() = prefHelper.getPref(VIBRATION_ENABLED, true)
|
||||
private set
|
||||
var vibrationEnabledSystem: Boolean = false
|
||||
var vibrationStrength: Int = 0
|
||||
get() = prefHelper.getPref(VIBRATION_STRENGTH, 0)
|
||||
get() = prefHelper.getPref(VIBRATION_STRENGTH, -1)
|
||||
private set
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for localization preferences.
|
||||
*/
|
||||
class Localization(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ACTIVE_SUBTYPE_ID = "localization__active_subtype_id"
|
||||
const val SUBTYPES = "localization__subtypes"
|
||||
}
|
||||
|
||||
var activeSubtypeId: Int
|
||||
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, Subtype.DEFAULT.id)
|
||||
set(v) = prefHelper.setPref(ACTIVE_SUBTYPE_ID, v)
|
||||
var subtypes: String
|
||||
get() = prefHelper.getPref(SUBTYPES, "")
|
||||
set(v) = prefHelper.setPref(SUBTYPES, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for Smartbar preferences.
|
||||
*/
|
||||
class Smartbar(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ENABLED = "smartbar__enabled"
|
||||
}
|
||||
|
||||
var enabled: Boolean
|
||||
get() = prefHelper.getPref(ENABLED, true)
|
||||
set(v) = prefHelper.setPref(ENABLED, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for suggestion preferences.
|
||||
*/
|
||||
class Suggestion(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val ENABLED = "suggestion__enabled"
|
||||
const val USE_PREV_WORDS = "suggestion__use_prev_words"
|
||||
const val ENABLED = "suggestion__enabled"
|
||||
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 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 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)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,18 +425,35 @@ class PrefHelper(
|
||||
*/
|
||||
class Theme(private val prefHelper: PrefHelper) {
|
||||
companion object {
|
||||
const val NAME = "theme__name"
|
||||
const val MODE = "theme__mode"
|
||||
const val DAY_THEME_REF = "theme__day_theme_ref"
|
||||
const val DAY_THEME_ADAPT_TO_APP = "theme__day_theme_adapt_to_app"
|
||||
const val NIGHT_THEME_REF = "theme__night_theme_ref"
|
||||
const val NIGHT_THEME_ADAPT_TO_APP = "theme__night_theme_adapt_to_app"
|
||||
const val SUNRISE_TIME = "theme__sunrise_time"
|
||||
const val SUNSET_TIME = "theme__sunset_time"
|
||||
}
|
||||
|
||||
var name: String = ""
|
||||
get() = prefHelper.getPref(NAME, "floris_light")
|
||||
private set
|
||||
fun getSelectedThemeResId(): Int {
|
||||
return when (name) {
|
||||
"floris_light" -> R.style.KeyboardTheme_FlorisLight
|
||||
"floris_dark" -> R.style.KeyboardTheme_FlorisDark
|
||||
else -> R.style.KeyboardTheme_FlorisLight
|
||||
}
|
||||
}
|
||||
var mode: ThemeMode
|
||||
get() = ThemeMode.fromString(prefHelper.getPref(MODE, ThemeMode.FOLLOW_SYSTEM.toString()))
|
||||
set(v) = prefHelper.setPref(MODE, v)
|
||||
var dayThemeRef: String
|
||||
get() = prefHelper.getPref(DAY_THEME_REF, "assets:ime/theme/floris_day.json")
|
||||
set(v) = prefHelper.setPref(DAY_THEME_REF, v)
|
||||
var dayThemeAdaptToApp: Boolean
|
||||
get() = prefHelper.getPref(DAY_THEME_ADAPT_TO_APP, false)
|
||||
set(v) = prefHelper.setPref(DAY_THEME_ADAPT_TO_APP, v)
|
||||
var nightThemeRef: String
|
||||
get() = prefHelper.getPref(NIGHT_THEME_REF, "assets:ime/theme/floris_night.json")
|
||||
set(v) = prefHelper.setPref(NIGHT_THEME_REF, v)
|
||||
var nightThemeAdaptToApp: Boolean
|
||||
get() = prefHelper.getPref(NIGHT_THEME_ADAPT_TO_APP, false)
|
||||
set(v) = prefHelper.setPref(NIGHT_THEME_ADAPT_TO_APP, v)
|
||||
var sunriseTime: Int
|
||||
get() = prefHelper.getPref(SUNRISE_TIME, TimeUtil.encode(6, 0))
|
||||
set(v) = prefHelper.setPref(SUNRISE_TIME, v)
|
||||
var sunsetTime: Int
|
||||
get() = prefHelper.getPref(SUNSET_TIME, TimeUtil.encode(18, 0))
|
||||
set(v) = prefHelper.setPref(SUNSET_TIME, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,16 +87,10 @@ data class Subtype(
|
||||
* Must be a string which also exists in [FlorisBoard.ImeConfig.characterLayouts]. If the value is
|
||||
* not included within this list, no layout will be shown to the user if the user selects the
|
||||
* predefined layout value.
|
||||
* @property isAsciiCapable Legacy attribute for Android's InputMethodSubtype. Currently no real
|
||||
* use within this project.
|
||||
* @property isEmojiCapable Legacy attribute for Android's InputMethodSubtype. Currently no real
|
||||
* use within this project.
|
||||
*/
|
||||
data class DefaultSubtype(
|
||||
var id: Int,
|
||||
@Json(name = "languageTag")
|
||||
var locale: Locale,
|
||||
var preferredLayout: String,
|
||||
var isAsciiCapable: Boolean,
|
||||
var isEmojiCapable: Boolean
|
||||
var preferredLayout: String
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ class SubtypeManager(
|
||||
var imeConfig: FlorisBoard.ImeConfig = FlorisBoard.ImeConfig(context.packageName)
|
||||
var subtypes: List<Subtype>
|
||||
get() {
|
||||
val listRaw = prefs.keyboard.subtypes
|
||||
val listRaw = prefs.localization.subtypes
|
||||
return if (listRaw.isBlank()) {
|
||||
listOf()
|
||||
} else {
|
||||
@@ -58,7 +58,7 @@ class SubtypeManager(
|
||||
}
|
||||
}
|
||||
set(v) {
|
||||
prefs.keyboard.subtypes = v.joinToString(SUBTYPE_LIST_STR_DELIMITER)
|
||||
prefs.localization.subtypes = v.joinToString(SUBTYPE_LIST_STR_DELIMITER)
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -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,21 +129,21 @@ 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? {
|
||||
for (subtype in subtypes) {
|
||||
if (subtype.id == prefs.keyboard.activeSubtypeId) {
|
||||
if (subtype.id == prefs.localization.activeSubtypeId) {
|
||||
return subtype
|
||||
}
|
||||
}
|
||||
val subtypeList = subtypes
|
||||
return if (subtypeList.isNotEmpty()) {
|
||||
prefs.keyboard.activeSubtypeId = subtypeList[0].id
|
||||
prefs.localization.activeSubtypeId = subtypeList[0].id
|
||||
subtypeList[0]
|
||||
} else {
|
||||
prefs.keyboard.activeSubtypeId = -1
|
||||
prefs.localization.activeSubtypeId = Subtype.DEFAULT.id
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -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? {
|
||||
@@ -212,15 +212,43 @@ class SubtypeManager(
|
||||
}
|
||||
}
|
||||
subtypes = subtypeList
|
||||
if (subtypeToRemove.id == prefs.keyboard.activeSubtypeId) {
|
||||
if (subtypeToRemove.id == prefs.localization.activeSubtypeId) {
|
||||
getActiveSubtype()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the previous subtype in the subtype list if possible.
|
||||
*
|
||||
* @return The new active subtype or null if the determination process failed.
|
||||
*/
|
||||
fun switchToPrevSubtype(): Subtype? {
|
||||
val subtypeList = subtypes
|
||||
val activeSubtype = getActiveSubtype() ?: return null
|
||||
var triggerNextSubtype = false
|
||||
var newActiveSubtype: Subtype? = null
|
||||
for (subtype in subtypeList.reversed()) {
|
||||
if (triggerNextSubtype) {
|
||||
triggerNextSubtype = false
|
||||
newActiveSubtype = subtype
|
||||
} else if (subtype == activeSubtype) {
|
||||
triggerNextSubtype = true
|
||||
}
|
||||
}
|
||||
if (triggerNextSubtype) {
|
||||
newActiveSubtype = subtypeList.last()
|
||||
}
|
||||
prefs.localization.activeSubtypeId = when (newActiveSubtype) {
|
||||
null -> Subtype.DEFAULT.id
|
||||
else -> newActiveSubtype.id
|
||||
}
|
||||
return newActiveSubtype
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -236,10 +264,10 @@ class SubtypeManager(
|
||||
}
|
||||
}
|
||||
if (triggerNextSubtype) {
|
||||
newActiveSubtype = subtypeList[0]
|
||||
newActiveSubtype = subtypeList.first()
|
||||
}
|
||||
prefs.keyboard.activeSubtypeId = when (newActiveSubtype) {
|
||||
null -> -1
|
||||
prefs.localization.activeSubtypeId = when (newActiveSubtype) {
|
||||
null -> Subtype.DEFAULT.id
|
||||
else -> newActiveSubtype.id
|
||||
}
|
||||
return newActiveSubtype
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.extension
|
||||
|
||||
import android.content.Context
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Result
|
||||
|
||||
/**
|
||||
* Interface for an Asset to use within FlorisBoard. An asset is everything from a dictionary to a
|
||||
* keyboard layout to a extended popup mapping, etc. Assets are very important for the splitting
|
||||
* FlorisBoard's resources into assets.
|
||||
*
|
||||
* NOTE: At the current state, this is only a simple implementation idea and only PopupMappingAsset
|
||||
* partly uses it. This package and it's classes are expected to grow and gain more importance over
|
||||
* time.
|
||||
*/
|
||||
interface Asset {
|
||||
/**
|
||||
* The name of the Asset, must be unique throughout all Assets. Is used to internally identify
|
||||
* and sort the Asset. This name is non-translatable and thus is a static string.
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* The display name of the Asset. This is the label which will be shown to the user in the
|
||||
* Settings UI. Currently also a static string.
|
||||
* TODO: make this string localize-able
|
||||
*/
|
||||
val label: String
|
||||
|
||||
/**
|
||||
* A list of authors who actively worked on the content of this Asset. Any content of string is
|
||||
* valid, but the best practice is to use the GitHub username.
|
||||
*/
|
||||
val authors: List<String>
|
||||
|
||||
/**
|
||||
* "Static" functions which every Asset should provide.
|
||||
*/
|
||||
interface Companion<T> {
|
||||
/**
|
||||
* Creates an empty Asset of type [T].
|
||||
*/
|
||||
fun empty(): T
|
||||
|
||||
/**
|
||||
* Loads an Asset of type [T] from the specified path.
|
||||
*/
|
||||
fun fromFile(context: Context, path: String): Result<T, Throwable> = Err(NotImplementedError())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.extension
|
||||
|
||||
import android.content.Context
|
||||
import com.github.michaelbull.result.*
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupExtension
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyTypeAdapter
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyVariationAdapter
|
||||
import dev.patrickgold.florisboard.ime.text.layout.LayoutTypeAdapter
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
class AssetManager private constructor(private val applicationContext: Context) {
|
||||
private val moshi: Moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
/*.add(PolymorphicJsonAdapterFactory.of(Asset::class.java, "\$type")
|
||||
.withSubtype(PopupExtension::class.java, PopupExtension::class.qualifiedName)
|
||||
.withSubtype(Theme::class.java, Theme::class.qualifiedName)
|
||||
)*/
|
||||
.add(LayoutTypeAdapter())
|
||||
.add(KeyTypeAdapter())
|
||||
.add(KeyVariationAdapter())
|
||||
.build()
|
||||
|
||||
companion object {
|
||||
private var defaultInstance: AssetManager? = null
|
||||
|
||||
fun init(applicationContext: Context): AssetManager {
|
||||
val instance = AssetManager(applicationContext)
|
||||
defaultInstance = instance
|
||||
return instance
|
||||
}
|
||||
|
||||
fun default(): AssetManager {
|
||||
val instance = defaultInstance
|
||||
if (instance != null) {
|
||||
return instance
|
||||
} else {
|
||||
throw UninitializedPropertyAccessException(
|
||||
"${this::class.simpleName} has not been initialized previously. Make sure to call init(applicationContext) before using default()."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAsset(ref: AssetRef): Result<Nothing?, Throwable> {
|
||||
return when (ref.source) {
|
||||
AssetSource.Internal -> {
|
||||
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
|
||||
if (file.isFile) {
|
||||
val success = file.delete()
|
||||
if (success) {
|
||||
Ok(null)
|
||||
} else {
|
||||
Err(Exception("Could not delete file."))
|
||||
}
|
||||
} else {
|
||||
Err(Exception("Provided reference is not a file."))
|
||||
}
|
||||
}
|
||||
else -> Err(Exception("Can not delete an asset in source '${ref.source}'"))
|
||||
}
|
||||
}
|
||||
|
||||
fun hasAsset(ref: AssetRef): Boolean {
|
||||
return when (ref.source) {
|
||||
AssetSource.Assets -> {
|
||||
try {
|
||||
val file = File(ref.path)
|
||||
val list = applicationContext.assets.list(file.parent?.toString() ?: "")
|
||||
list?.contains(file.name) == true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
AssetSource.Internal -> {
|
||||
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
|
||||
file.exists() && file.isFile
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun <T: Asset> listAssets(ref: AssetRef, assetClass: Class<T>): Result<Map<AssetRef, T>, Throwable> {
|
||||
val retMap = mutableMapOf<AssetRef, T>()
|
||||
return when (ref.source) {
|
||||
AssetSource.Assets -> {
|
||||
try {
|
||||
val list = applicationContext.assets.list(ref.path)
|
||||
if (list != null) {
|
||||
for (file in list) {
|
||||
val fileRef = ref.copy(path = ref.path + "/" + file)
|
||||
val assetResult = loadAsset(fileRef, assetClass)
|
||||
assetResult.onSuccess { asset ->
|
||||
retMap[fileRef.copy()] = asset
|
||||
}.onFailure { error ->
|
||||
Timber.e(error.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(retMap.toMap())
|
||||
} catch (e: Exception) {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
AssetSource.Internal -> {
|
||||
val dir = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
|
||||
if (dir.isDirectory) {
|
||||
dir.listFiles()?.let {
|
||||
it.forEach { file ->
|
||||
if (file.isFile) {
|
||||
val fileRef = ref.copy(path = ref.path + "/" + file.name)
|
||||
val assetResult = loadAsset(fileRef, assetClass)
|
||||
assetResult.onSuccess { asset ->
|
||||
retMap[fileRef.copy()] = asset
|
||||
}.onFailure { error ->
|
||||
Timber.e(error.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(retMap.toMap())
|
||||
}
|
||||
else -> Ok(retMap.toMap())
|
||||
}
|
||||
}
|
||||
|
||||
fun <T: Asset> loadAsset(ref: AssetRef, assetClass: Class<T>): Result<T, Throwable> {
|
||||
val rawJsonData = when (ref.source) {
|
||||
is AssetSource.Assets -> {
|
||||
try {
|
||||
applicationContext.assets.open(ref.path).bufferedReader().use { it.readText() }
|
||||
} catch (e: Exception) {
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
is AssetSource.Internal -> {
|
||||
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
|
||||
val contents = readFile(file)
|
||||
if (contents.isBlank()) {
|
||||
"{}"
|
||||
} else {
|
||||
contents
|
||||
}
|
||||
}
|
||||
else -> "{}"
|
||||
}
|
||||
return try {
|
||||
val adapter = moshi.adapter(assetClass)
|
||||
val asset = adapter.fromJson(rawJsonData)
|
||||
if (asset != null) {
|
||||
Ok(asset)
|
||||
} else {
|
||||
Err(NullPointerException("Asset failed to load!"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T: Asset> writeAsset(ref: AssetRef, assetClass: Class<T>, asset: T): Result<Boolean, Throwable> {
|
||||
return when (ref.source) {
|
||||
AssetSource.Internal -> {
|
||||
val adapter = moshi.adapter(assetClass)
|
||||
val rawJson = adapter.toJson(asset)
|
||||
val file = File(applicationContext.filesDir.absolutePath + "/" + ref.path)
|
||||
writeToFile(file, rawJson)
|
||||
Ok(true)
|
||||
}
|
||||
else -> Err(Exception("Can not write an asset in source '${ref.source}'"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.parent?.let {
|
||||
val dir = File(it)
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs()
|
||||
}
|
||||
}
|
||||
file.writeText(text)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.extension
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
|
||||
/**
|
||||
* Data class which is a reference to an asset file. It indicates in which storage medium the asset
|
||||
* is as well as the relative path to it.
|
||||
*
|
||||
* @property source The source in which the asset is (APK assets, internal storage, external)
|
||||
* @property path The relative path to the asset within [source]. Must not begin and end with a
|
||||
* forward slash.
|
||||
*/
|
||||
data class AssetRef(
|
||||
val source: AssetSource,
|
||||
val path: String
|
||||
) {
|
||||
companion object {
|
||||
private const val DELIMITER: String = ":"
|
||||
|
||||
fun fromString(str: String): Result<AssetRef, String> {
|
||||
val items = str.split(DELIMITER)
|
||||
if (items.size != 2) {
|
||||
return Err("Unexpected length of given asset ref. Make sure that the asset ref string contains exactly 2 items separated by '$DELIMITER'!")
|
||||
}
|
||||
val retSource = AssetSource.fromString(items[0]).getOrElse {
|
||||
return Err(it)
|
||||
}
|
||||
return Ok(AssetRef(retSource, items[1]))
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val retString: StringBuilder = StringBuilder().apply {
|
||||
append(source.toString())
|
||||
append(DELIMITER)
|
||||
append(path)
|
||||
}
|
||||
return retString.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.extension
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Sealed class which specifies where an asset comes from. There are 3 different types, all of which
|
||||
* require a different approach on how to access the actual asset.
|
||||
*/
|
||||
sealed class AssetSource {
|
||||
/**
|
||||
* The asset comes pre-built with the application, thus all paths must be relative to the asset
|
||||
* directory of FlorisBoard.
|
||||
*/
|
||||
object Assets : AssetSource()
|
||||
|
||||
/**
|
||||
* The asset is saved in the internal storage of FlorisBoard, all relative paths must therefore
|
||||
* be treated as such.
|
||||
*/
|
||||
object Internal : AssetSource()
|
||||
|
||||
/**
|
||||
* Asset source is an external extension, which requires the package name and possibly other
|
||||
* data. Currently NYI.
|
||||
* TODO: Implement external extensions
|
||||
*/
|
||||
data class External(val packageName: String) : AssetSource() {
|
||||
override fun toString(): String {
|
||||
return super.toString()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val externalRegex: Regex = """^external\\(([a-z]+\\.)*[a-z]+\\)\$""".toRegex()
|
||||
|
||||
fun fromString(str: String): Result<AssetSource, String> {
|
||||
return when (val string = str.toLowerCase(Locale.ENGLISH)) {
|
||||
"assets" -> Ok(Assets)
|
||||
"internal" -> Ok(Internal)
|
||||
else -> {
|
||||
if (string.matches(externalRegex)) {
|
||||
val packageName = string.substring(9, string.length - 1)
|
||||
Ok(External(packageName))
|
||||
} else {
|
||||
Err("'$str' is not a valid AssetSource.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
is Assets -> "assets"
|
||||
is Internal -> "internal"
|
||||
is External -> "external($packageName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,13 @@
|
||||
package dev.patrickgold.florisboard.ime.media
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.os.Handler
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
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
|
||||
@@ -33,7 +33,10 @@ import dev.patrickgold.florisboard.ime.media.emoticon.EmoticonKeyboardView
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyType
|
||||
import dev.patrickgold.florisboard.util.cancelAll
|
||||
import dev.patrickgold.florisboard.util.postAtScheduledRate
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -50,10 +53,12 @@ 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
|
||||
private var osTimer: Timer? = null
|
||||
private var repeatedKeyPressHandler: Handler? = null
|
||||
private var tabLayout: TabLayout? = null
|
||||
private val tabViews = EnumMap<Tab, LinearLayout>(Tab::class.java)
|
||||
|
||||
@@ -71,6 +76,15 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
florisboard.addEventListener(this)
|
||||
}
|
||||
|
||||
override fun onCreateInputView() {
|
||||
super.onCreateInputView()
|
||||
repeatedKeyPressHandler = Handler(florisboard.context.mainLooper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new input view has been registered. Used to initialize all media-relevant
|
||||
* views and layouts.
|
||||
@@ -78,7 +92,7 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onRegisterInputView(inputView: InputView) {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onRegisterInputView(inputView)")
|
||||
Timber.i("onRegisterInputView(inputView)")
|
||||
|
||||
launch(Dispatchers.Default) {
|
||||
mediaViewGroup = inputView.findViewById(R.id.media_input)
|
||||
@@ -104,15 +118,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))
|
||||
}
|
||||
}
|
||||
@@ -122,7 +133,7 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
* Clean-up of resources and stopping all coroutines.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onDestroy()")
|
||||
Timber.i("onDestroy()")
|
||||
|
||||
cancel()
|
||||
instance = null
|
||||
@@ -135,10 +146,10 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
event ?: return false
|
||||
val data = when (view.id) {
|
||||
R.id.media_input_switch_to_text_input_button -> {
|
||||
KeyData(KeyCode.SWITCH_TO_TEXT_CONTEXT)
|
||||
KeyData(code = KeyCode.SWITCH_TO_TEXT_CONTEXT)
|
||||
}
|
||||
R.id.media_input_backspace_button -> {
|
||||
KeyData(KeyCode.DELETE, type = KeyType.ENTER_EDITING)
|
||||
KeyData(code = KeyCode.DELETE, type = KeyType.ENTER_EDITING)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@@ -147,17 +158,14 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound(data)
|
||||
if (data?.code == KeyCode.DELETE && data.type == KeyType.ENTER_EDITING) {
|
||||
osTimer = Timer()
|
||||
osTimer?.scheduleAtFixedRate(object : TimerTask() {
|
||||
override fun run() {
|
||||
florisboard.textInputManager.sendKeyPress(data)
|
||||
}
|
||||
}, 500, 50)
|
||||
val delayMillis = florisboard.prefs.keyboard.longPressDelay.toLong()
|
||||
repeatedKeyPressHandler?.postAtScheduledRate(delayMillis, 25) {
|
||||
florisboard.textInputManager.sendKeyPress(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
osTimer?.cancel()
|
||||
osTimer = null
|
||||
repeatedKeyPressHandler?.cancelAll()
|
||||
if (event.actionMasked != MotionEvent.ACTION_CANCEL && data != null) {
|
||||
florisboard.textInputManager.sendKeyPress(data)
|
||||
}
|
||||
@@ -195,18 +203,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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.media
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class MediaInputView : LinearLayout, FlorisBoard.EventListener,
|
||||
ThemeManager.OnThemeUpdatedListener {
|
||||
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
|
||||
var tabLayout: TabLayout? = null
|
||||
private set
|
||||
var switchToTextInputButton: Button? = null
|
||||
private set
|
||||
var backspaceButton: ImageButton? = null
|
||||
private set
|
||||
|
||||
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) {
|
||||
florisboard?.addEventListener(this)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
tabLayout = findViewById(R.id.media_input_tabs)
|
||||
switchToTextInputButton = findViewById(R.id.media_input_switch_to_text_input_button)
|
||||
backspaceButton = findViewById(R.id.media_input_backspace_button)
|
||||
onApplyThemeAttributes()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
val fgColor = theme.getAttr(Theme.Attr.MEDIA_FOREGROUND).toSolidColor().color
|
||||
val colorPrimary = theme.getAttr(Theme.Attr.WINDOW_COLOR_PRIMARY).toSolidColor().color
|
||||
tabLayout?.setTabTextColors(fgColor, fgColor)
|
||||
tabLayout?.tabIconTint = ColorStateList.valueOf(fgColor)
|
||||
tabLayout?.setSelectedTabIndicatorColor(colorPrimary)
|
||||
switchToTextInputButton?.setTextColor(fgColor)
|
||||
backspaceButton?.imageTintList = ColorStateList.valueOf(fgColor)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val height = florisboard?.inputView?.desiredMediaKeyboardViewHeight ?: 0.0f
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height.roundToInt(), MeasureSpec.EXACTLY))
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,16 @@ 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
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.core.FlorisBoard
|
||||
import dev.patrickgold.florisboard.util.getColorFromAttr
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
|
||||
/**
|
||||
* View class for managing the rendering and the events of a single emoji keyboard key.
|
||||
@@ -40,10 +44,12 @@ import dev.patrickgold.florisboard.util.getColorFromAttr
|
||||
*/
|
||||
@SuppressLint("ViewConstructor")
|
||||
class EmojiKeyView(
|
||||
private val florisboard: FlorisBoard,
|
||||
private val emojiKeyboardView: EmojiKeyboardView,
|
||||
val data: EmojiKeyData
|
||||
) : androidx.appcompat.widget.AppCompatTextView(florisboard.context) {
|
||||
) : androidx.appcompat.widget.AppCompatTextView(emojiKeyboardView.context),
|
||||
FlorisBoard.EventListener, ThemeManager.OnThemeUpdatedListener {
|
||||
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
|
||||
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
|
||||
|
||||
private var isCancelled: Boolean = false
|
||||
private var osHandler: Handler? = null
|
||||
@@ -55,14 +61,16 @@ class EmojiKeyView(
|
||||
setPadding(0, 0, 0, 0)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.emoji_key_textSize))
|
||||
|
||||
triangleDrawable = resources.getDrawable(
|
||||
R.drawable.triangle_bottom_right, context.theme
|
||||
)
|
||||
triangleDrawable?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
|
||||
getColorFromAttr(context, R.attr.emoji_key_fgColor), BlendModeCompat.SRC_ATOP
|
||||
)
|
||||
triangleDrawable = ContextCompat.getDrawable(context, R.drawable.triangle_bottom_right)
|
||||
|
||||
text = data.getCodePointsAsString()
|
||||
|
||||
florisboard?.addEventListener(this)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
onApplyThemeAttributes()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,18 +87,18 @@ class EmojiKeyView(
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
isCancelled = false
|
||||
val delayMillis = florisboard.prefs.looknfeel.longPressDelay
|
||||
val delayMillis = prefs.keyboard.longPressDelay
|
||||
if (osHandler == null) {
|
||||
osHandler = Handler()
|
||||
}
|
||||
osHandler?.postDelayed({
|
||||
(parent.parent as HorizontalScrollView)
|
||||
(parent.parent as ScrollView)
|
||||
.requestDisallowInterceptTouchEvent(true)
|
||||
emojiKeyboardView.isScrollBlocked = true
|
||||
emojiKeyboardView.popupManager.show(this)
|
||||
emojiKeyboardView.popupManager.extend(this)
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound()
|
||||
emojiKeyboardView.popupManager.show(this, KeyHintMode.DISABLED)
|
||||
emojiKeyboardView.popupManager.extend(this, KeyHintMode.DISABLED)
|
||||
florisboard?.keyPressVibrate()
|
||||
florisboard?.keyPressSound()
|
||||
}, delayMillis.toLong())
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
@@ -117,10 +125,10 @@ class EmojiKeyView(
|
||||
if (event.actionMasked != MotionEvent.ACTION_CANCEL &&
|
||||
retData != null && !isCancelled) {
|
||||
if (!emojiKeyboardView.isScrollBlocked) {
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound()
|
||||
florisboard?.keyPressVibrate()
|
||||
florisboard?.keyPressSound()
|
||||
}
|
||||
florisboard.mediaInputManager.sendEmojiKeyPress(retData)
|
||||
florisboard?.mediaInputManager?.sendEmojiKeyPress(retData)
|
||||
performClick()
|
||||
}
|
||||
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||
@@ -131,18 +139,29 @@ class EmojiKeyView(
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
triangleDrawable?.setBounds(
|
||||
(measuredWidth * 0.75f).toInt(),
|
||||
(measuredHeight * 0.75f).toInt(),
|
||||
(measuredWidth * 0.85f).toInt(),
|
||||
(measuredHeight * 0.85f).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
triangleDrawable?.colorFilter =
|
||||
BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
|
||||
theme.getAttr(Theme.Attr.MEDIA_FOREGROUND_ALT).toSolidColor().color, BlendModeCompat.SRC_ATOP
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
canvas ?: return
|
||||
|
||||
if (data.popup.isNotEmpty()) {
|
||||
triangleDrawable?.setBounds(
|
||||
(measuredWidth * 0.75f).toInt(),
|
||||
(measuredHeight * 0.75f).toInt(),
|
||||
(measuredWidth * 0.85f).toInt(),
|
||||
(measuredHeight * 0.85f).toInt()
|
||||
)
|
||||
triangleDrawable?.draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,22 @@ package dev.patrickgold.florisboard.ime.media.emoji
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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
|
||||
import dev.patrickgold.florisboard.ime.popup.KeyPopupManager
|
||||
import dev.patrickgold.florisboard.ime.popup.PopupManager
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
|
||||
@@ -42,19 +44,22 @@ import java.util.*
|
||||
*
|
||||
* @property florisboard Reference to instance of core class [FlorisBoard].
|
||||
*/
|
||||
class EmojiKeyboardView : LinearLayout {
|
||||
class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener,
|
||||
ThemeManager.OnThemeUpdatedListener {
|
||||
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
|
||||
private var activeCategory: EmojiCategory = EmojiCategory.SMILEYS_EMOTION
|
||||
private var emojiViewFlipper: ViewFlipper
|
||||
private val emojiKeyWidth = resources.getDimension(R.dimen.emoji_key_width).toInt()
|
||||
private val emojiKeyHeight = resources.getDimension(R.dimen.emoji_key_height).toInt()
|
||||
private val florisboard: FlorisBoard = FlorisBoard.getInstance()
|
||||
private var layouts: Deferred<EmojiLayoutDataMap>
|
||||
private val mainScope = MainScope()
|
||||
private val uiLayouts = EnumMap<EmojiCategory, HorizontalScrollView>(EmojiCategory::class.java)
|
||||
private val tabLayout: TabLayout
|
||||
private val uiLayouts = EnumMap<EmojiCategory, ScrollView>(EmojiCategory::class.java)
|
||||
|
||||
var isScrollBlocked: Boolean = false
|
||||
var popupManager = KeyPopupManager<EmojiKeyboardView, EmojiKeyView>(this)
|
||||
var popupManager = PopupManager<EmojiKeyboardView, EmojiKeyView>(this, florisboard?.popupLayerView)
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
@@ -62,18 +67,21 @@ class EmojiKeyboardView : LinearLayout {
|
||||
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)
|
||||
|
||||
val tabs =
|
||||
tabLayout =
|
||||
ViewGroup.inflate(context, R.layout.media_input_emoji_tabs, null) as TabLayout
|
||||
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
setActiveCategory(when (tab?.position) {
|
||||
0 -> EmojiCategory.SMILEYS_EMOTION
|
||||
@@ -92,21 +100,24 @@ class EmojiKeyboardView : LinearLayout {
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
})
|
||||
addView(tabs)
|
||||
addView(tabLayout)
|
||||
florisboard?.addEventListener(this)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
mainScope.launch {
|
||||
layouts.await()
|
||||
buildLayout()
|
||||
setActiveCategory(EmojiCategory.SMILEYS_EMOTION)
|
||||
themeManager.requestThemeUpdate(this@EmojiKeyboardView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
mainScope.cancel()
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,10 +127,10 @@ class EmojiKeyboardView : LinearLayout {
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,32 +140,52 @@ class EmojiKeyboardView : LinearLayout {
|
||||
* 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 =
|
||||
EmojiKeyView(florisboard, this@EmojiKeyboardView, emojiKeyData)
|
||||
EmojiKeyView(this@EmojiKeyboardView, emojiKeyData)
|
||||
emojiKeyView.layoutParams = FlexboxLayout.LayoutParams(
|
||||
emojiKeyWidth, emojiKeyHeight
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,4 +223,11 @@ class EmojiKeyboardView : LinearLayout {
|
||||
))
|
||||
isScrollBlocked = true
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
val fgColor = theme.getAttr(Theme.Attr.MEDIA_FOREGROUND).toSolidColor().color
|
||||
val colorAccent = theme.getAttr(Theme.Attr.WINDOW_COLOR_ACCENT).toSolidColor().color
|
||||
tabLayout.tabIconTint = ColorStateList.valueOf(fgColor)
|
||||
tabLayout.setSelectedTabIndicatorColor(colorAccent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.util.Log
|
||||
import androidx.core.graphics.PaintCompat
|
||||
import timber.log.Timber
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
@@ -171,13 +172,13 @@ fun parseRawEmojiSpecsFile(
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("EmojiLayoutDataMap", "parseRawEmojiSpecsFile(): $e")
|
||||
Timber.e("parseRawEmojiSpecsFile(): $e")
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close()
|
||||
} catch (e: IOException) {
|
||||
Log.e("EmojiLayoutDataMap", "parseRawEmojiSpecsFile(): $e")
|
||||
Timber.e("parseRawEmojiSpecsFile(): $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class EmoticonKeyView : androidx.appcompat.widget.AppCompatTextView {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
setBackgroundColor(getColorFromAttr(context, R.attr.semiTransparentColor))
|
||||
florisboard.keyPressVibrate()
|
||||
florisboard.keyPressSound(KeyData(0))
|
||||
florisboard.keyPressSound(KeyData())
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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.popup
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.core.content.ContextCompat.getDrawable
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.util.*
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class KeyPopupExtendedSingleView(
|
||||
context: Context, var isActive: Boolean = false
|
||||
) : androidx.appcompat.widget.AppCompatTextView(
|
||||
context, null, 0
|
||||
) {
|
||||
|
||||
var iconDrawable: Drawable? = null
|
||||
|
||||
init {
|
||||
background = getDrawable(context, R.drawable.shape_rect_rounded)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
canvas ?: return
|
||||
|
||||
setBackgroundTintColor(this, when {
|
||||
isActive -> R.attr.key_popup_extended_bgColorActive
|
||||
else -> R.attr.key_popup_extended_bgColor
|
||||
})
|
||||
|
||||
val drawable = iconDrawable
|
||||
val drawablePadding = (0.2f * measuredHeight).toInt()
|
||||
if (drawable != null) {
|
||||
var marginV = 0
|
||||
var marginH = 0
|
||||
if (measuredWidth > measuredHeight) {
|
||||
marginH = (measuredWidth - measuredHeight) / 2
|
||||
} else {
|
||||
marginV = (measuredHeight - measuredWidth) / 2
|
||||
}
|
||||
drawable.setBounds(
|
||||
marginH + drawablePadding,
|
||||
marginV + drawablePadding,
|
||||
measuredWidth - marginH - drawablePadding,
|
||||
measuredHeight - marginV - drawablePadding)
|
||||
drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
|
||||
getColorFromAttr(context, R.attr.key_popup_fgColor),
|
||||
BlendModeCompat.SRC_ATOP
|
||||
)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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.popup
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.PaintDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
import kotlin.math.min
|
||||
|
||||
class PopupExtendedView : View, ThemeManager.OnThemeUpdatedListener {
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
|
||||
private val activeBackgroundDrawable: PaintDrawable = PaintDrawable()
|
||||
private var backgroundDrawable: PaintDrawable = PaintDrawable()
|
||||
private val labelPaint: Paint = Paint().apply {
|
||||
alpha = 255
|
||||
color = 0
|
||||
isAntiAlias = true
|
||||
isFakeBoldText = false
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = resources.getDimension(R.dimen.key_textSize)
|
||||
typeface = Typeface.DEFAULT
|
||||
}
|
||||
private val tldPaint: Paint = Paint().apply {
|
||||
alpha = 255
|
||||
color = 0
|
||||
isAntiAlias = true
|
||||
isFakeBoldText = false
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = resources.getDimension(R.dimen.key_textSize)
|
||||
typeface = Typeface.DEFAULT
|
||||
}
|
||||
|
||||
val properties: Properties = Properties(
|
||||
width = resources.getDimension(R.dimen.key_width).toInt(),
|
||||
height = resources.getDimension(R.dimen.key_height).toInt(),
|
||||
xOffset = 0,
|
||||
yOffset = 0,
|
||||
gravity = Gravity.START,
|
||||
elements = mutableListOf(),
|
||||
activeElementIndex = -1,
|
||||
labelTextSize = resources.getDimension(R.dimen.key_popup_textSize),
|
||||
)
|
||||
val isShowing: Boolean
|
||||
get() = visibility == VISIBLE
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
init {
|
||||
visibility = GONE
|
||||
background = backgroundDrawable
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
activeBackgroundDrawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND_ACTIVE).toSolidColor().color)
|
||||
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
|
||||
}
|
||||
backgroundDrawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
|
||||
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
|
||||
}
|
||||
elevation = ViewLayoutUtils.convertDpToPixel(4.0f, context)
|
||||
labelPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
|
||||
tldPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
|
||||
if (isShowing) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyProperties(anchor: View) {
|
||||
val anchorCoords = IntArray(2)
|
||||
anchor.getLocationInWindow(anchorCoords)
|
||||
val anchorX = anchorCoords[0]
|
||||
val anchorY = anchorCoords[1] + anchor.measuredHeight
|
||||
when (val lp = layoutParams) {
|
||||
is FrameLayout.LayoutParams -> lp.apply {
|
||||
width = properties.width
|
||||
height = properties.height
|
||||
setMargins(
|
||||
anchorX + properties.xOffset,
|
||||
anchorY + properties.yOffset,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
|
||||
setMargins(
|
||||
anchorX + properties.xOffset,
|
||||
anchorY + properties.yOffset,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
labelPaint.textSize = properties.labelTextSize
|
||||
tldPaint.textSize = properties.labelTextSize * 0.6f
|
||||
if (isShowing) {
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(anchor: View) {
|
||||
applyProperties(anchor)
|
||||
visibility = VISIBLE
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
visibility = GONE
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
canvas ?: return
|
||||
|
||||
if (properties.elements.isEmpty() || properties.elements.first().isEmpty()) {
|
||||
return
|
||||
}
|
||||
val baseSize = properties.elements.first().size
|
||||
val elementWidth = measuredWidth / baseSize
|
||||
val elementHeight = measuredHeight / properties.elements.size
|
||||
|
||||
var currentElementIndex = 0
|
||||
for ((r, row) in properties.elements.reversed().withIndex()) {
|
||||
val leftOffset = when (properties.gravity) {
|
||||
Gravity.END -> baseSize - row.size
|
||||
else -> 0
|
||||
}
|
||||
for ((e, element) in row.withIndex()) {
|
||||
val left = (e + leftOffset) * elementWidth
|
||||
val top = r * elementHeight
|
||||
if (properties.activeElementIndex == currentElementIndex) {
|
||||
activeBackgroundDrawable.setBounds(
|
||||
left, top, left + elementWidth, top + elementHeight
|
||||
)
|
||||
activeBackgroundDrawable.draw(canvas)
|
||||
}
|
||||
when (element) {
|
||||
is Element.Label -> {
|
||||
val label = element.label
|
||||
if (label.isNotEmpty()) {
|
||||
val centerX = left + elementWidth / 2.0f
|
||||
val centerY = top + elementHeight / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
|
||||
canvas.drawText(label, centerX, centerY, labelPaint)
|
||||
}
|
||||
}
|
||||
is Element.Tld -> {
|
||||
val tld = element.tld
|
||||
if (tld.isNotEmpty()) {
|
||||
val centerX = left + elementWidth / 2.0f
|
||||
val centerY = top + elementHeight / 2.0f + (tldPaint.textSize - tldPaint.descent()) / 2
|
||||
canvas.drawText(tld, centerX, centerY, tldPaint)
|
||||
}
|
||||
}
|
||||
is Element.Icon -> {
|
||||
val drawable = element.icon
|
||||
drawable.setTint(labelPaint.color)
|
||||
val drawableSize = (min(elementWidth, elementHeight) * 0.6f).toInt()
|
||||
val drawablePaddingLeft = ((elementWidth - drawableSize) / 2.0f).toInt()
|
||||
val drawablePaddingTop = ((elementHeight - drawableSize) / 2.0f).toInt()
|
||||
drawable.setBounds(
|
||||
left + drawablePaddingLeft,
|
||||
top + drawablePaddingTop,
|
||||
left + drawablePaddingLeft + drawableSize,
|
||||
top + drawablePaddingTop + drawableSize
|
||||
)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
currentElementIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Properties(
|
||||
var width: Int,
|
||||
var height: Int,
|
||||
var xOffset: Int,
|
||||
var yOffset: Int,
|
||||
var gravity: Int,
|
||||
var elements: MutableList<MutableList<Element>>,
|
||||
var activeElementIndex: Int,
|
||||
var labelTextSize: Float
|
||||
) {
|
||||
fun getElementOrNull(index: Int = activeElementIndex): Element? {
|
||||
if (index < 0) {
|
||||
return null
|
||||
}
|
||||
var cachedIndex = index
|
||||
elements.reversed().forEach { row ->
|
||||
if (cachedIndex >= row.size) {
|
||||
cachedIndex -= row.size
|
||||
} else {
|
||||
return row[cachedIndex]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Element(val adjustedIndex: Int) {
|
||||
class Label(val label: String, adjustedIndex: Int) : Element(adjustedIndex)
|
||||
class Tld(val tld: String, adjustedIndex: Int) : Element(adjustedIndex)
|
||||
class Icon(val icon: Drawable, adjustedIndex: Int) : Element(adjustedIndex)
|
||||
object Undefined : Element(-1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.popup
|
||||
|
||||
import android.content.Context
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import dev.patrickgold.florisboard.ime.extension.Asset
|
||||
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
|
||||
|
||||
/**
|
||||
* An object which maps each base key to its extended popups. This can be done for each
|
||||
* key variation. [KeyVariation.ALL] is always the fallback for each key.
|
||||
*/
|
||||
typealias PopupMapping = Map<KeyVariation, Map<String, PopupSet<KeyData>>>
|
||||
|
||||
/**
|
||||
* Class which contains an extended popup mapping to use for adding popups subtype based on the
|
||||
* keyboard layout.
|
||||
*
|
||||
* @property mapping The mapping of the base keys to their popups. See [PopupMapping] for more info.
|
||||
*/
|
||||
class PopupExtension(
|
||||
override val name: String,
|
||||
override val label: String = name,
|
||||
override val authors: List<String>,
|
||||
val mapping: PopupMapping
|
||||
) : Asset {
|
||||
companion object : Asset.Companion<PopupExtension> {
|
||||
override fun empty() = PopupExtension("", "", listOf(), mapOf())
|
||||
|
||||
override fun fromFile(context: Context, path: String): Result<PopupExtension, Throwable> {
|
||||
return try {
|
||||
val raw = context.assets.open(path).bufferedReader().use { it.readText() }
|
||||
val asset = fromJsonString(raw)
|
||||
if (asset != null) {
|
||||
Ok(asset)
|
||||
} else {
|
||||
Err(NullPointerException())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJsonString(json: String): PopupExtension? {
|
||||
val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.add(KeyTypeAdapter())
|
||||
.add(KeyVariationAdapter())
|
||||
.build()
|
||||
val layoutAdapter = moshi.adapter(PopupExtension::class.java)
|
||||
return layoutAdapter.fromJson(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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.popup
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.widget.FrameLayout
|
||||
import dev.patrickgold.florisboard.ime.core.PrefHelper
|
||||
|
||||
class PopupLayerView : FrameLayout {
|
||||
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
|
||||
|
||||
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)
|
||||
|
||||
init {
|
||||
background = null
|
||||
isClickable = false
|
||||
isFocusable = false
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -17,24 +17,17 @@
|
||||
package dev.patrickgold.florisboard.ime.popup
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.view.*
|
||||
import androidx.core.content.ContextCompat.getDrawable
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyData
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyView
|
||||
import dev.patrickgold.florisboard.ime.media.emoji.EmojiKeyboardView
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyData
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyView
|
||||
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
|
||||
import dev.patrickgold.florisboard.util.setTextTintColor
|
||||
|
||||
/**
|
||||
* Manages the creation and dismissal of key popups as well as the checks if the pointer moved
|
||||
@@ -42,12 +35,13 @@ import dev.patrickgold.florisboard.util.setTextTintColor
|
||||
*
|
||||
* @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) {
|
||||
|
||||
class PopupManager<T_KBD: View, T_KV: View>(
|
||||
private val keyboardView: T_KBD,
|
||||
private val popupLayerView: PopupLayerView?
|
||||
) {
|
||||
private var anchorLeft: Boolean = false
|
||||
private var anchorRight: Boolean = false
|
||||
private var anchorOffset: Int = 0
|
||||
private var activeExtIndex: Int? = null
|
||||
private val exceptionsForKeyCodes = listOf(
|
||||
KeyCode.ENTER,
|
||||
KeyCode.LANGUAGE_SWITCH,
|
||||
@@ -56,142 +50,110 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
)
|
||||
private var keyPopupWidth: Int
|
||||
private var keyPopupHeight: Int
|
||||
var keyPopupTextSize: Float = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
|
||||
private var keyPopupDiffX: Int = 0
|
||||
private val popupView: LinearLayout
|
||||
private val popupViewExt: FlexboxLayout
|
||||
private val popupView: PopupView
|
||||
private val popupViewExt: PopupExtendedView
|
||||
private var row0count: Int = 0
|
||||
private var row1count: Int = 0
|
||||
private var window: PopupWindow
|
||||
private var windowExt: PopupWindow
|
||||
|
||||
/** Is true if the preview popup is visible to the user, else false */
|
||||
val isShowingPopup: Boolean
|
||||
get() = popupView.visibility == View.VISIBLE
|
||||
get() = popupView.isShowing
|
||||
/** Is true if the extended popup is visible to the user, else false */
|
||||
val isShowingExtendedPopup: Boolean
|
||||
get() = windowExt.isShowing
|
||||
get() = popupViewExt.isShowing
|
||||
|
||||
init {
|
||||
keyPopupWidth = keyboardView.resources.getDimension(R.dimen.key_width).toInt()
|
||||
keyPopupHeight = keyboardView.resources.getDimension(R.dimen.key_height).toInt()
|
||||
popupView = View.inflate(
|
||||
keyboardView.context,
|
||||
R.layout.key_popup, null
|
||||
) as LinearLayout
|
||||
popupView.visibility = View.INVISIBLE
|
||||
popupViewExt = View.inflate(
|
||||
keyboardView.context,
|
||||
R.layout.key_popup_extended, null
|
||||
) as FlexboxLayout
|
||||
window = createPopupWindow(popupView)
|
||||
windowExt = createPopupWindow(popupViewExt)
|
||||
popupView = PopupView(keyboardView.context)
|
||||
popupViewExt = PopupExtendedView(keyboardView.context)
|
||||
popupLayerView?.addView(popupView)
|
||||
popupLayerView?.addView(popupViewExt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a [KeyPopupExtendedSingleView] and preconfigure it.
|
||||
* Helper function to create a element for the extended popup and preconfigure it.
|
||||
*
|
||||
* @param keyView Reference to the keyView currently controlling the popup.
|
||||
* @param k The index of the key in the key data popup array.
|
||||
* @param isInitActive If it should initially be marked as active.
|
||||
* @param isWrapBefore If the [FlexboxLayout] should wrap before this view.
|
||||
* @return A preconfigured [KeyPopupExtendedSingleView].
|
||||
* @param adjustedIndex The index of the key in the key data popup array.
|
||||
* @return A preconfigured extended popup element.
|
||||
*/
|
||||
private fun createTextView(
|
||||
private fun createElement(
|
||||
keyView: T_KV,
|
||||
k: Int,
|
||||
isInitActive: Boolean = false,
|
||||
isWrapBefore: Boolean = false
|
||||
): KeyPopupExtendedSingleView? {
|
||||
val textView = KeyPopupExtendedSingleView(keyView.context, isInitActive)
|
||||
val lp = FlexboxLayout.LayoutParams(keyPopupWidth, keyView.measuredHeight)
|
||||
lp.isWrapBefore = isWrapBefore
|
||||
textView.layoutParams = lp
|
||||
textView.gravity = Gravity.CENTER
|
||||
setTextTintColor(
|
||||
textView,
|
||||
R.attr.key_popup_fgColor
|
||||
)
|
||||
val textSize = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
|
||||
if (keyView is KeyView) {
|
||||
when (keyView.data.popup[k].code) {
|
||||
KeyCode.SETTINGS -> {
|
||||
textView.iconDrawable = getDrawable(
|
||||
keyView.context, R.drawable.ic_settings
|
||||
)
|
||||
}
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT -> {
|
||||
textView.text = keyView.resources.getString(R.string.key__view_characters)
|
||||
}
|
||||
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
|
||||
textView.iconDrawable = getDrawable(
|
||||
keyView.context, R.drawable.ic_sentiment_satisfied
|
||||
)
|
||||
}
|
||||
KeyCode.TOGGLE_ONE_HANDED_MODE -> {
|
||||
textView.iconDrawable = getDrawable(
|
||||
keyView.context, R.drawable.ic_keyboard_arrow_right
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
textView.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX, when (keyView.data.popup[k].code) {
|
||||
KeyCode.URI_COMPONENT_TLD,
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT -> textSize * 0.6f
|
||||
else -> textSize
|
||||
}
|
||||
)
|
||||
textView.text = keyView.getComputedLetter(keyView.data.popup[k])
|
||||
adjustedIndex: Int
|
||||
): PopupExtendedView.Element {
|
||||
return when (keyView) {
|
||||
is KeyView -> {
|
||||
when (keyView.data.popup[adjustedIndex].code) {
|
||||
KeyCode.SETTINGS -> {
|
||||
getDrawable(keyView.context, R.drawable.ic_settings)?.let {
|
||||
PopupExtendedView.Element.Icon(it, adjustedIndex)
|
||||
} ?: PopupExtendedView.Element.Undefined
|
||||
}
|
||||
KeyCode.SWITCH_TO_TEXT_CONTEXT -> {
|
||||
PopupExtendedView.Element.Label(
|
||||
keyView.resources.getString(R.string.key__view_characters), adjustedIndex
|
||||
)
|
||||
}
|
||||
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
|
||||
getDrawable(keyView.context, R.drawable.ic_sentiment_satisfied)?.let {
|
||||
PopupExtendedView.Element.Icon(it, adjustedIndex)
|
||||
} ?: PopupExtendedView.Element.Undefined
|
||||
}
|
||||
KeyCode.URI_COMPONENT_TLD -> {
|
||||
PopupExtendedView.Element.Tld(
|
||||
keyView.data.popup[adjustedIndex].label, adjustedIndex
|
||||
)
|
||||
}
|
||||
KeyCode.TOGGLE_ONE_HANDED_MODE -> {
|
||||
getDrawable(keyView.context, R.drawable.ic_smartphone)?.let {
|
||||
PopupExtendedView.Element.Icon(it, adjustedIndex)
|
||||
} ?: PopupExtendedView.Element.Undefined
|
||||
}
|
||||
else -> {
|
||||
PopupExtendedView.Element.Label(
|
||||
keyView.getComputedLetter(keyView.data.popup[adjustedIndex]), adjustedIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (keyView is EmojiKeyView) {
|
||||
textView.text = keyView.data.popup[k].getCodePointsAsString()
|
||||
}
|
||||
return textView
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for a convenient way of creating a [PopupWindow].
|
||||
*
|
||||
* @param view The view to set as content view of the [PopupWindow].
|
||||
* @return A new [PopupWindow] already preconfigured and ready-to-go.
|
||||
*/
|
||||
private fun createPopupWindow(view: View): PopupWindow {
|
||||
return PopupWindow(keyboardView.context).apply {
|
||||
animationStyle = 0
|
||||
contentView = view
|
||||
enterTransition = null
|
||||
exitTransition = null
|
||||
isClippingEnabled = false
|
||||
isFocusable = false
|
||||
isTouchable = false
|
||||
setBackgroundDrawable(null)
|
||||
is EmojiKeyView -> {
|
||||
PopupExtendedView.Element.Label(
|
||||
keyView.data.popup[adjustedIndex].getCodePointsAsString(), adjustedIndex
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
PopupExtendedView.Element.Undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a preview popup for the passed [keyView]. Ignores show requests for key views which
|
||||
* key code is equal to or less than [KeyCode.SPACE]. KeyViews with a code defined in
|
||||
* [exceptionsForKeyCodes] will only shadow-calculating the size of the key popup, as these
|
||||
* sizes are needed for the extended popup. No popup will be shown to the user in this case.
|
||||
*
|
||||
* @param keyView Reference to the keyView currently controlling the popup.
|
||||
* Calculates all attributes required by both the normal and the extended popup, regardless of
|
||||
* the passed [keyView]'s code.
|
||||
*/
|
||||
fun show(keyView: T_KV) {
|
||||
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE
|
||||
&& !exceptionsForKeyCodes.contains(keyView.data.code)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update keyPopupWidth and keyPopupHeight
|
||||
private fun calc(keyView: T_KV) {
|
||||
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) {
|
||||
@@ -199,34 +161,40 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
keyPopupHeight = (keyView.measuredHeight * 2.5f).toInt()
|
||||
}
|
||||
keyPopupDiffX = (keyView.measuredWidth - keyPopupWidth) / 2
|
||||
// Calculating is done, so exit show() here if this key view is a special one.
|
||||
if (keyView is KeyView && exceptionsForKeyCodes.contains(keyView.data.code)) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a preview popup for the passed [keyView]. Ignores show requests for key views which
|
||||
* key code is equal to or less than [KeyCode.SPACE].
|
||||
*
|
||||
* @param keyView Reference to the keyView currently controlling the popup.
|
||||
*/
|
||||
fun show(keyView: T_KV, keyHintMode: KeyHintMode) {
|
||||
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE) {
|
||||
return
|
||||
}
|
||||
|
||||
val keyPopupX = keyPopupDiffX
|
||||
val keyPopupY = -keyPopupHeight
|
||||
if (window.isShowing) {
|
||||
window.update(keyView, keyPopupX, keyPopupY, keyPopupWidth, keyPopupHeight)
|
||||
} else {
|
||||
window.width = keyPopupWidth
|
||||
window.height = keyPopupHeight
|
||||
window.showAsDropDown(keyView, keyPopupX, keyPopupY, Gravity.NO_GRAVITY)
|
||||
}
|
||||
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
|
||||
else -> View.VISIBLE
|
||||
calc(keyView)
|
||||
|
||||
popupView.properties.apply {
|
||||
width = keyPopupWidth
|
||||
height = keyPopupHeight
|
||||
xOffset = keyPopupDiffX
|
||||
yOffset = -keyPopupHeight
|
||||
innerLabelFactor = 0.4f
|
||||
label = when (keyView) {
|
||||
is KeyView -> keyView.getComputedLetter()
|
||||
is EmojiKeyView -> keyView.data.getCodePointsAsString()
|
||||
else -> ""
|
||||
}
|
||||
} else if (keyView is EmojiKeyView) {
|
||||
popupView.findViewById<TextView>(R.id.key_popup_text)?.text = keyView.data.getCodePointsAsString()
|
||||
popupView.findViewById<ImageView>(R.id.key_popup_threedots)?.visibility = when {
|
||||
keyView.data.popup.isEmpty() -> View.INVISIBLE
|
||||
else -> View.VISIBLE
|
||||
labelTextSize = keyPopupTextSize
|
||||
shouldIndicateExtendedPopups = when (keyView) {
|
||||
is KeyView -> keyView.data.popup.size(keyHintMode) > 0
|
||||
is EmojiKeyView -> keyView.data.popup.isNotEmpty()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
popupView.visibility = View.VISIBLE
|
||||
popupView.show(keyView)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,28 +218,28 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
*
|
||||
* @param keyView Reference to the keyView currently controlling the popup.
|
||||
*/
|
||||
fun extend(keyView: T_KV) {
|
||||
fun extend(keyView: T_KV, keyHintMode: KeyHintMode) {
|
||||
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE
|
||||
&& !exceptionsForKeyCodes.contains(keyView.data.code)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!isShowingPopup) {
|
||||
calc(keyView)
|
||||
}
|
||||
|
||||
// Anchor left if keyView is in left half of keyboardView, else anchor right
|
||||
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.data.popup.size(keyHintMode)
|
||||
is EmojiKeyView -> keyView.data.popup.size
|
||||
else -> 0
|
||||
}
|
||||
when {
|
||||
n <= 0 -> return
|
||||
n <= 5 -> {
|
||||
row1count = 0
|
||||
row0count = n
|
||||
@@ -314,64 +282,106 @@ 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 EmojiKeyView -> keyView.data.popup.indices
|
||||
else -> IntRange(0, 0)
|
||||
popupViewExt.properties.elements.clear()
|
||||
val initUiIndex = when {
|
||||
anchorLeft -> anchorOffset + row1count
|
||||
anchorRight -> row0count - 1 - anchorOffset + row1count
|
||||
else -> 0
|
||||
}
|
||||
for (k in indices) {
|
||||
val isInitActive =
|
||||
anchorLeft && (k - row1count == anchorOffset) ||
|
||||
anchorRight && (k - row1count == row0count - 1 - anchorOffset)
|
||||
popupViewExt.addView(
|
||||
createTextView(
|
||||
keyView, k, isInitActive, (row1count > 0) && (k - row1count == 0)
|
||||
)
|
||||
)
|
||||
if (isInitActive) {
|
||||
activeExtIndex = k
|
||||
val popupIndices: IntArray
|
||||
val uiIndices = IntRange(0, (n - 1).coerceAtLeast(0))
|
||||
if (keyView is KeyView) {
|
||||
popupIndices = IntArray(n) { 0 }
|
||||
when (keyHintMode) {
|
||||
KeyHintMode.ENABLED_ACCENT_PRIORITY -> when {
|
||||
keyView.data.popup.main != null -> {
|
||||
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
if (keyView.data.popup.hint != null) when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
|
||||
}
|
||||
}
|
||||
keyView.data.popup.hint != null -> when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
|
||||
else -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
|
||||
}
|
||||
}
|
||||
KeyHintMode.ENABLED_HINT_PRIORITY -> when {
|
||||
keyView.data.popup.hint != null -> {
|
||||
popupIndices[initUiIndex] = PopupSet.HINT_INDEX
|
||||
if (keyView.data.popup.main != null) when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.MAIN_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.MAIN_INDEX
|
||||
}
|
||||
}
|
||||
keyView.data.popup.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
}
|
||||
KeyHintMode.ENABLED_SMART_PRIORITY -> when {
|
||||
keyView.data.popup.main != null -> {
|
||||
popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
if (keyView.data.popup.hint != null) when {
|
||||
initUiIndex + 1 < n -> popupIndices[initUiIndex + 1] = PopupSet.HINT_INDEX
|
||||
initUiIndex - 1 >= 0 -> popupIndices[initUiIndex - 1] = PopupSet.HINT_INDEX
|
||||
}
|
||||
}
|
||||
keyView.data.popup.hint != null -> popupIndices[initUiIndex] = PopupSet.HINT_INDEX
|
||||
}
|
||||
KeyHintMode.DISABLED -> when {
|
||||
keyView.data.popup.main != null -> popupIndices[initUiIndex] = PopupSet.MAIN_INDEX
|
||||
}
|
||||
}
|
||||
var offset = 0
|
||||
for (uiIndex in uiIndices) {
|
||||
if (popupIndices[uiIndex] < 0) {
|
||||
offset++
|
||||
} else {
|
||||
popupIndices[uiIndex] = uiIndex - offset
|
||||
}
|
||||
}
|
||||
} else {
|
||||
popupIndices = IntArray(n) { it }
|
||||
}
|
||||
if (row1count > 0) {
|
||||
popupViewExt.properties.elements.add(mutableListOf())
|
||||
}
|
||||
popupViewExt.properties.elements.add(mutableListOf())
|
||||
for (uiIndex in uiIndices) {
|
||||
val rowIndex = if (uiIndex < row1count && row1count > 0) { 1 } else { 0 }
|
||||
popupViewExt.properties.elements[rowIndex].add(
|
||||
createElement(keyView, popupIndices[uiIndex])
|
||||
)
|
||||
}
|
||||
popupView.findViewById<ImageView>(R.id.key_popup_threedots)?.visibility = View.INVISIBLE
|
||||
|
||||
// Calculate layout params
|
||||
val extWidth = row0count * keyPopupWidth
|
||||
val extHeight = when {
|
||||
row1count > 0 -> keyView.measuredHeight * 2
|
||||
else -> keyView.measuredHeight
|
||||
}
|
||||
popupViewExt.justifyContent = if (anchorLeft) {
|
||||
JustifyContent.FLEX_START
|
||||
} else {
|
||||
JustifyContent.FLEX_END
|
||||
}
|
||||
if (popupViewExt.layoutParams == null) {
|
||||
popupViewExt.layoutParams = ViewGroup.LayoutParams(extWidth, extHeight)
|
||||
} else {
|
||||
popupViewExt.layoutParams.apply {
|
||||
width = extWidth
|
||||
height = extHeight
|
||||
}
|
||||
}
|
||||
row1count > 0 -> keyPopupHeight * 0.4f * 2.0f
|
||||
else -> keyPopupHeight * 0.4f
|
||||
}.toInt()
|
||||
val x = ((keyView.measuredWidth - keyPopupWidth) / 2) + when {
|
||||
anchorLeft -> -anchorOffset * keyPopupWidth
|
||||
anchorRight -> -extWidth + keyPopupWidth + anchorOffset * keyPopupWidth
|
||||
else -> 0
|
||||
}
|
||||
val y = -keyPopupHeight - when {
|
||||
row1count > 0 -> keyView.measuredHeight
|
||||
row1count > 0 -> (keyPopupHeight * 0.4f).toInt()
|
||||
else -> 0
|
||||
}
|
||||
|
||||
// Position and show popup window
|
||||
if (windowExt.isShowing) {
|
||||
windowExt.update(keyView, x, y, extWidth, extHeight)
|
||||
} else {
|
||||
windowExt.width = extWidth
|
||||
windowExt.height = extHeight
|
||||
windowExt.showAsDropDown(keyView, x, y, Gravity.NO_GRAVITY)
|
||||
popupViewExt.properties.apply {
|
||||
width = extWidth
|
||||
height = extHeight
|
||||
xOffset = x
|
||||
yOffset = y
|
||||
gravity = if (anchorLeft) { Gravity.START } else { Gravity.END }
|
||||
labelTextSize = keyPopupTextSize
|
||||
activeElementIndex = initUiIndex
|
||||
}
|
||||
popupViewExt.show(keyView)
|
||||
|
||||
popupView.properties.shouldIndicateExtendedPopups = false
|
||||
popupView.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,7 +404,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
return false
|
||||
}
|
||||
|
||||
activeExtIndex = when {
|
||||
popupViewExt.properties.activeElementIndex = when {
|
||||
anchorLeft -> when {
|
||||
// check if out of boundary on x-axis
|
||||
event.x < keyPopupDiffX - (anchorOffset + 1) * keyPopupWidth ||
|
||||
@@ -440,24 +450,7 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
}
|
||||
else -> -1
|
||||
}
|
||||
|
||||
if (keyView is KeyView) {
|
||||
for (k in keyView.data.popup.indices) {
|
||||
val view = popupViewExt.getChildAt(k)
|
||||
if (view != null) {
|
||||
val textView = view as KeyPopupExtendedSingleView
|
||||
textView.isActive = k == activeExtIndex
|
||||
}
|
||||
}
|
||||
} else if (keyView is EmojiKeyView) {
|
||||
for (k in keyView.data.popup.indices) {
|
||||
val view = popupViewExt.getChildAt(k)
|
||||
if (view != null) {
|
||||
val textView = view as KeyPopupExtendedSingleView
|
||||
textView.isActive = k == activeExtIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
popupViewExt.invalidate()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -472,7 +465,12 @@ 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 element = popupViewExt.properties.getElementOrNull()
|
||||
if (element != null) {
|
||||
keyView.data.popup.getOrNull(element.adjustedIndex) ?: keyView.data
|
||||
} else {
|
||||
keyView.data
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -488,7 +486,12 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
*/
|
||||
fun getActiveEmojiKeyData(keyView: T_KV): EmojiKeyData? {
|
||||
return if (keyView is EmojiKeyView) {
|
||||
keyView.data.popup.getOrNull(activeExtIndex ?: -1) ?: keyView.data
|
||||
val element = popupViewExt.properties.getElementOrNull()
|
||||
if (element != null) {
|
||||
keyView.data.popup.getOrNull(element.adjustedIndex) ?: keyView.data
|
||||
} else {
|
||||
keyView.data
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -498,12 +501,9 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
* Hides the key preview popup as well as the extended popup.
|
||||
*/
|
||||
fun hide() {
|
||||
popupView.visibility = View.INVISIBLE
|
||||
if (windowExt.isShowing) {
|
||||
windowExt.dismiss()
|
||||
}
|
||||
|
||||
activeExtIndex = null
|
||||
popupView.hide()
|
||||
popupViewExt.hide()
|
||||
popupViewExt.properties.activeElementIndex = -1
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -511,13 +511,10 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
|
||||
* is closing.
|
||||
*/
|
||||
fun dismissAllPopups() {
|
||||
if (window.isShowing) {
|
||||
window.dismiss()
|
||||
}
|
||||
if (windowExt.isShowing) {
|
||||
windowExt.dismiss()
|
||||
}
|
||||
|
||||
activeExtIndex = null
|
||||
popupView.hide()
|
||||
popupLayerView?.removeView(popupView)
|
||||
popupViewExt.hide()
|
||||
popupViewExt.properties.activeElementIndex = -1
|
||||
popupLayerView?.removeView(popupViewExt)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.popup
|
||||
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
|
||||
|
||||
/**
|
||||
* A popup set for a single key. This set describes, if the key has a [hint] character,
|
||||
* a [main] character and other [relevant] popups.
|
||||
*
|
||||
* Note, that a hint character should **never** be set in a json extended popup file, rather it
|
||||
* should only be dynamically set by the LayoutManager.
|
||||
*
|
||||
* The order in which these defined popups will be shown depends on the current [KeyHintMode],
|
||||
* al well as the calculations made by the KeyPopupManager.
|
||||
*
|
||||
* The popup set can be accessed like an array with the addition that negative indexes defined
|
||||
* within this companion object are allowed (as long as the corresponding [hint] or [main]
|
||||
* character is *not* null).
|
||||
*/
|
||||
class PopupSet<T> (
|
||||
var hint: T? = null,
|
||||
var main: T? = null,
|
||||
var relevant: List<T> = listOf()
|
||||
) : Collection<T> {
|
||||
companion object {
|
||||
const val HINT_INDEX: Int = -2
|
||||
const val MAIN_INDEX: Int = -1
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = if (hint != null) { 1 } else { 0 } + if (main != null) { 1 } else { 0 } +
|
||||
relevant.size
|
||||
|
||||
fun size(keyHintMode: KeyHintMode): Int {
|
||||
return if (keyHintMode == KeyHintMode.DISABLED && hint != null) {
|
||||
size - 1
|
||||
} else {
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(index: Int): T {
|
||||
val item = getOrNull(index)
|
||||
if (item == null) {
|
||||
throw IndexOutOfBoundsException(
|
||||
"Specified index $index is not an valid entry in this PopupSet!"
|
||||
)
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrNull(index: Int): T? {
|
||||
if (index >= relevant.size || index < HINT_INDEX) {
|
||||
return null
|
||||
}
|
||||
return when (index) {
|
||||
HINT_INDEX -> hint
|
||||
MAIN_INDEX -> main
|
||||
else -> relevant.getOrNull(index)
|
||||
}
|
||||
}
|
||||
|
||||
override fun contains(element: T): Boolean {
|
||||
return hint == element || main == element || relevant.contains(element)
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<T>): Boolean {
|
||||
for (element in elements) {
|
||||
if (!contains(element)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return size == 0
|
||||
}
|
||||
|
||||
fun merge(other: PopupSet<T>) {
|
||||
val tempRelevant = relevant.toMutableList()
|
||||
tempRelevant.addAll(other.relevant)
|
||||
other.hint?.let {
|
||||
if (hint == null) {
|
||||
hint = it
|
||||
} else {
|
||||
tempRelevant.add(it)
|
||||
}
|
||||
}
|
||||
other.main?.let {
|
||||
if (main == null) {
|
||||
main = it
|
||||
} else {
|
||||
tempRelevant.add(it)
|
||||
}
|
||||
}
|
||||
relevant = tempRelevant.toList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.popup
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.PaintDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import dev.patrickgold.florisboard.R
|
||||
import dev.patrickgold.florisboard.ime.theme.Theme
|
||||
import dev.patrickgold.florisboard.ime.theme.ThemeManager
|
||||
import dev.patrickgold.florisboard.util.ViewLayoutUtils
|
||||
|
||||
class PopupView : View, ThemeManager.OnThemeUpdatedListener {
|
||||
private val themeManager: ThemeManager = ThemeManager.default()
|
||||
|
||||
private var backgroundDrawable: PaintDrawable = PaintDrawable()
|
||||
private val labelPaint: Paint = Paint().apply {
|
||||
alpha = 255
|
||||
color = 0
|
||||
isAntiAlias = true
|
||||
isFakeBoldText = false
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = resources.getDimension(R.dimen.key_textSize)
|
||||
typeface = Typeface.DEFAULT
|
||||
}
|
||||
private val threeDotsDrawable: Drawable? =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_more_horiz)
|
||||
|
||||
val properties: Properties = Properties(
|
||||
width = resources.getDimension(R.dimen.key_width).toInt(),
|
||||
height = resources.getDimension(R.dimen.key_height).toInt(),
|
||||
xOffset = 0,
|
||||
yOffset = 0,
|
||||
innerLabelFactor = 0.4f,
|
||||
label = "",
|
||||
labelTextSize = resources.getDimension(R.dimen.key_popup_textSize),
|
||||
shouldIndicateExtendedPopups = false
|
||||
)
|
||||
val isShowing: Boolean
|
||||
get() = visibility == VISIBLE
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
init {
|
||||
visibility = GONE
|
||||
background = backgroundDrawable
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
themeManager.registerOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
themeManager.unregisterOnThemeUpdatedListener(this)
|
||||
}
|
||||
|
||||
override fun onThemeUpdated(theme: Theme) {
|
||||
backgroundDrawable.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.POPUP_BACKGROUND).toSolidColor().color)
|
||||
setCornerRadius(ViewLayoutUtils.convertDpToPixel(6.0f, context))
|
||||
}
|
||||
elevation = ViewLayoutUtils.convertDpToPixel(4.0f, context)
|
||||
threeDotsDrawable?.apply {
|
||||
setTint(theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color)
|
||||
}
|
||||
labelPaint.color = theme.getAttr(Theme.Attr.POPUP_FOREGROUND).toSolidColor().color
|
||||
if (isShowing) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyProperties(anchor: View) {
|
||||
val anchorCoords = IntArray(2)
|
||||
anchor.getLocationInWindow(anchorCoords)
|
||||
val anchorX = anchorCoords[0]
|
||||
val anchorY = anchorCoords[1] + anchor.measuredHeight
|
||||
when (val lp = layoutParams) {
|
||||
is FrameLayout.LayoutParams -> lp.apply {
|
||||
width = properties.width
|
||||
height = properties.height
|
||||
setMargins(
|
||||
anchorX + properties.xOffset,
|
||||
anchorY + properties.yOffset,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
layoutParams = FrameLayout.LayoutParams(properties.width, properties.height).apply {
|
||||
setMargins(
|
||||
anchorX + properties.xOffset,
|
||||
anchorY + properties.yOffset,
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
labelPaint.textSize = properties.labelTextSize
|
||||
if (isShowing) {
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(anchor: View) {
|
||||
applyProperties(anchor)
|
||||
visibility = VISIBLE
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
visibility = GONE
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
canvas ?: return
|
||||
|
||||
// Draw label
|
||||
val label = properties.label
|
||||
if (label.isNotEmpty()) {
|
||||
val centerX = measuredWidth / 2.0f
|
||||
val centerY =
|
||||
(measuredHeight * properties.innerLabelFactor) / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
|
||||
canvas.drawText(label, centerX, centerY, labelPaint)
|
||||
}
|
||||
|
||||
// Draw drawable
|
||||
val drawable = threeDotsDrawable
|
||||
if (drawable != null && properties.shouldIndicateExtendedPopups) {
|
||||
val marginTop = measuredHeight * properties.innerLabelFactor
|
||||
val drawableSize = marginTop * 0.25f
|
||||
drawable.setBounds(
|
||||
(measuredWidth * 0.95f - drawableSize).toInt(),
|
||||
marginTop.toInt(),
|
||||
(measuredWidth * 0.95f).toInt(),
|
||||
(marginTop + drawableSize).toInt()
|
||||
)
|
||||
drawable.draw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
data class Properties(
|
||||
var width: Int,
|
||||
var height: Int,
|
||||
var xOffset: Int,
|
||||
var yOffset: Int,
|
||||
var innerLabelFactor: Float,
|
||||
var label: String,
|
||||
var labelTextSize: Float,
|
||||
var shouldIndicateExtendedPopups: Boolean
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user