Compare commits

..

5 Commits

Author SHA1 Message Date
Patrick Goldinger
ddc4f7f1ba Release v0.4.2 2024-10-17 19:12:13 +02:00
Patrick Goldinger
afea8c721f Merge branch 'main' into release/0.4 2024-10-17 19:10:49 +02:00
Patrick Goldinger
9cffcea246 Release v0.4.1 2024-10-10 20:53:19 +02:00
Patrick Goldinger
5acf80db0f Merge branch 'main' into release/0.4 2024-10-10 16:03:17 +02:00
Patrick Goldinger
80fb20885b Release v0.4.0 2024-09-18 18:26:47 +02:00
535 changed files with 10801 additions and 30809 deletions

View File

@@ -1,11 +0,0 @@
**/.*/
!.git/
**/build/
**/dist/
**/out/
**/target/
utils/
!utils/repr_build/scripts/
.env
gradlew.bat
local.properties

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use_flake

View File

@@ -1,4 +1,4 @@
name: 🐛 Bug Report
name: Bug Report
description: Create a report to help FlorisBoard improve
labels:
- "bug"
@@ -44,8 +44,7 @@ body:
label: Install Source
options:
- Google PlayStore
- F-Droid (F-Droid Main)
- F-Droid (IzzyOnDroid)
- F-Droid
- GitHub
validations:
required: true
@@ -63,14 +62,3 @@ body:
placeholder: e.g. 10, Stock
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/florisboard/florisboard/issues) or [closed](https://github.com/florisboard/florisboard/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md)."
required: true
- label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise."
required: true

View File

@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Matrix
url: https://matrix.to/#/#florisboard:matrix.org
about: General chat about FlorisBoard and quick Q/A
- name: ❓ Questions / Feedback
url: https://github.com/florisboard/florisboard/discussions/new/choose
about: Post your questions or feedback in the discussions panel
- name: Ask a question
url: https://github.com/florisboard/florisboard/discussions/new?category=q-a
about: Ask here if you have a question about FlorisBoard or need assistance
- name: General feedback
url: https://github.com/florisboard/florisboard/discussions/new?category=feedback
about: Give general feedback about this project

View File

@@ -1,4 +1,4 @@
name: 💥 Crash report
name: Crash report
description: Create a report with a generated crash log attached to help FlorisBoard improve
labels:
- "bug"
@@ -36,14 +36,3 @@ body:
description: Paste the generated crash log below
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/florisboard/florisboard/issues) or [closed](https://github.com/florisboard/florisboard/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md)."
required: true
- label: "I have taken the time to fill in all the required details. I understand that the crash report will be dismissed otherwise."
required: true

View File

@@ -1,4 +1,4 @@
name: Feature request
name: Feature request / Suggestion
description: Suggest an idea for this project
labels:
- "proposal"
@@ -20,14 +20,3 @@ body:
description: Please explain your idea in a precise way.
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/florisboard/florisboard/issues) or [closed](https://github.com/florisboard/florisboard/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/florisboard/florisboard/blob/main/CONTRIBUTING.md)."
required: true
- label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise."
required: true

View File

@@ -25,14 +25,14 @@ jobs:
with:
submodules: recursive
- uses: gradle/actions/wrapper-validation@v4
- uses: gradle/actions/wrapper-validation@v3
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin
- name: Set up CMake and Ninja
uses: lukka/get-cmake@v4.0.2
uses: lukka/get-cmake@latest
- name: Build with Gradle
run: ./gradlew clean assembleDebug
- uses: actions/upload-artifact@v4

1
.gitignore vendored
View File

@@ -40,7 +40,6 @@ captures/
# Intellij
*.iml
.idea/
!/.idea/copyright/
# Keystore files
*.jks

View File

@@ -1,6 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (C) &amp;#36;originalComment.match(&quot;Copyright \(C\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year The FlorisBoard Contributors&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
<option name="myName" value="copyright" />
</copyright>
</component>

View File

@@ -1,15 +0,0 @@
<component name="CopyrightManager">
<settings default="copyright">
<module2copyright>
<element module="Project Files" copyright="copyright" />
</module2copyright>
<LanguageOptions name="Shell Script">
<option name="fileTypeOverride" value="1" />
</LanguageOptions>
<LanguageOptions name="XML">
<option name="fileTypeOverride" value="1" />
<option name="prefixLines" value="false" />
</LanguageOptions>
<LanguageOptions name="__TEMPLATE__" />
</settings>
</component>

View File

@@ -4,34 +4,32 @@ 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.
The FlorisBoard community is international, as such we require all contributions, including issues, pull requests, and participation in the Matrix chat to be in English and follow the [code of conduct](https://github.com/florisboard/florisboard/blob/main/CODE_OF_CONDUCT.md). Contributions not adhering to these requirements will be dismissed. Thanks for making the FlorisBoard community an inclusive and safe space for everyone!
## General contributions
## Non-code contributions
### Translation
### Translations
To make FlorisBoard accessible in as many languages as possible, the platform [Crowdin](https://crowdin.florisboard.org) is used to crowdsource and manage translations. The list of languages in Crowdin covers a good range of languages, but feel free to email [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a new language.
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. The list of languages in Crowdin covers a good range of languages, but feel free to send an email to [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev) to request a new language.
> [!IMPORTANT]
> This is the only source of translations - **PRs that add/update translations are not accepted.**
### Feedback
You can [give general feedback](https://github.com/florisboard/florisboard/discussions/new?category=feedback) directly here on GitHub. This is the preferred way to give feedback, as it allows not only for me to read and respond to feedback, but for everyone in this community.
### Bug reporting
Allows us to see where FlorisBoard has flaws and should be improved to maximize stability and user experience. To make this process as smooth as possible, please use the pre-made [bug report template](https://github.com/florisboard/florisboard/issues/new?template=bug_report.yml). This makes it easy for us to understand what the bug is and how to solve it.
This kind of contribution is the most important, as it tells where FlorisBoard has flaws and thus should be improved to maximize stability and user experience. To make this process as smooth as possible, please use the pre-made [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 error logs
Logs are captured by FlorisBoard's crash handler, which gives you the ability to copy it to the clipboard and paste it in the crash report [issue template](https://github.com/florisboard/florisboard/issues/new?template=crash_report.yml). This is the preferred way to capture logs.
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.
### Feature proposals
Use the feature proposal [issue template](https://github.com/florisboard/florisboard/issues/new?template=feature_request.yml) to suggest a new idea or improvement for this project.
### Feedback
You can [give general feedback](https://github.com/florisboard/florisboard/discussions/new?category=feedback) directly here on GitHub. This is the preferred way to give feedback, as it allows not only for me to read and respond to feedback, but for everyone in this community.
Use the feature proposal issue template to suggest a new idea or improvement for this project.
## Code contributions
@@ -40,15 +38,15 @@ You are always welcome to contribute new features or work on existing issues, th
> [!NOTE]
> If you intend to implement a bigger feature please coordinate with us so we can prevent that there's a major difference in expected implementation.
If you are overwhelmed by the code don't hesitate to ask for help in the [dev chat](https://matrix.to/#/#florisboard-dev:matrix.org) or the discussions tab! Some issues are also marked as good first issue, which are easy to do tasks.
If you are overwhelmed by the code don't hesistate to ask for help in the [dev chat](https://matrix.to/#/#florisboard-dev:matrix.org) or the discussions tab! Some issues are also marked as good first issue, which are easy to do tasks.
### System requirements for development
- Desktop PC with Linux or WSL2 (Windows)
- MacOS and Windows without WSL2 probably works too however there's no official support
- At least 16GB of RAM (because of Android Studio / IntelliJ)
- At least 16GB of RAM (because of Android Studio)
- The following tools must be installed:
- Android Studio (bundles SDK and NDK) or IntelliJ with Android and Compose plugin
- Android Studio (bundles SDK and NDK)
- Java 17
- CMake 3.22+
- Clang 15+
@@ -66,7 +64,34 @@ If you want to manually build the project without Android Studio you must ensure
and Gradle should take care of every build task.
## Joining the team
If you want to join the core maintainer/moderator team on a volunteer basis and be part of this project's journey that's great to hear!
### Basic Requirements
- A passion for seeing FlorisBoard flourish
- Good English skills for team and public communication
- A GitHub account and a Matrix handle
### Why Join
You'll have the chance to work directly with me and other team members. While the general idea is for us to work on all kinds of different aspects of the project as a team, if you're particularly interested in a specific area (e.g., UI, extensions, text processing), that's totally okay too!
### Available Roles
Currently the following roles are available and need help:
Role Description | Required Dev Experience
---|---
Software Developer (Kotlin) for Core App | Java/Kotlin development experience (on Android)
Software Developer (Rust) for Native Core | Some Rust development experience
GitHub Issues/Discussions Moderator | None
Crowdin Translation Verifier | Language proficiency for the language you want to verify
Interested? Feel free to dm me ([@patrickgold](https://matrix.to/#/@patrickgold:matrix.org)) on Matrix or send an email to [florisboard@patrickgold.dev](mailto:florisboard@patrickgold.dev).
## Donating
You can also show your support by buying me a coffee, so I can stay up all night and chase away bugs or add new cool stuff :)
Alternatively you can also show your support by buying me a coffee, so I can stay up all night and chase away bugs or add new cool stuff :)
See the `Sponsors` button for available options!

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020-2025 The FlorisBoard Contributors
Copyright 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.

View File

@@ -1,11 +1,11 @@
<img align="left" width="80" height="80"
src=".github/repo_icon.png" alt="App icon">
# FlorisBoard [![Crowdin](https://badges.crowdin.net/florisboard/localized.svg)](https://crowdin.florisboard.org) [![Matrix badge](https://img.shields.io/badge/chat-%23florisboard%3amatrix.org-blue)](https://matrix.to/#/#florisboard:matrix.org) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![FlorisBoard CI](https://github.com/florisboard/florisboard/actions/workflows/android.yml/badge.svg?event=push)](https://github.com/florisboard/florisboard/actions/workflows/android.yml)
# FlorisBoard [![Crowdin](https://badges.crowdin.net/florisboard/localized.svg)](https://crowdin.florisboard.patrickgold.dev) [![Matrix badge](https://img.shields.io/badge/chat-%23florisboard%3amatrix.org-blue)](https://matrix.to/#/#florisboard:matrix.org) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![FlorisBoard CI](https://github.com/florisboard/florisboard/actions/workflows/android.yml/badge.svg?event=push)](https://github.com/florisboard/florisboard/actions/workflows/android.yml)
**FlorisBoard** is a free and open-source keyboard for Android 8.0+
**FlorisBoard** is a free and open-source keyboard for Android 7.0+
devices. It aims at being modern, user-friendly and customizable while
fully respecting your privacy. Currently in beta state.
fully respecting your privacy. Currently in early-beta state.
<table>
<tr>
@@ -26,13 +26,10 @@ fully respecting your privacy. Currently in beta state.
</tr>
<tr>
<td valign="top">
<p>
<a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a>
<a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a>
</p>
<p><a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a></p>
<p>
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-closed-beta-test), then visit the [testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard))
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard))
</p>
<p>
@@ -50,7 +47,7 @@ fully respecting your privacy. Currently in beta state.
<p><a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard.beta"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a></p>
<p>
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-closed-beta-test), then visit the [preview testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-public-alpha-test), then visit the [preview testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard.beta). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard.beta))
</p>
<p>
@@ -67,7 +64,7 @@ fully respecting your privacy. Currently in beta state.
</tr>
</table>
Beginning with v0.7 FlorisBoard will enter the public beta on Google Play.
Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
## Highlighted features
- Integrated clipboard manager / history
@@ -77,7 +74,7 @@ Beginning with v0.7 FlorisBoard will enter the public beta on Google Play.
> [!IMPORTANT]
> Word suggestions/spell checking are not included in the current releases
> and are a major goal for the v0.6 milestone.
> and are a major goal for the v0.5 milestone.
Feature roadmap: See [ROADMAP.md](ROADMAP.md)
@@ -99,22 +96,6 @@ Many thanks to Ali ([@4H1R](https://github.com/4H1R)) for implementing the store
Please refer to this [page](https://github.com/florisboard/florisboard/wiki/List-of-permissions-FlorisBoard-requests)
to get more information on this topic.
## APK signing certificate hashes
The package names and SHA-256 hashes of the signature certificate are listed below, so you can verify both FlorisBoard variants with apksigner by using `apksigner verify --print-certs florisboard-<version>-<track>.apk` when you download the APK.
If you have [AppVerifier](https://github.com/soupslurpr/AppVerifier) installed, you can alternatively copy both the package name and the hash of the corresponding track and share them to AppVerifier.
##### Stable track:
dev.patrickgold.florisboard<br>
0B:80:71:64:50:8E:AF:EB:1F:BB:81:5B:E7:A2:3C:77:FE:68:9D:94:B1:43:75:C9:9B:DA:A9:B6:57:7F:D6:D6
##### Preview track:
dev.patrickgold.florisboard.beta<br>
0B:80:71:64:50:8E:AF:EB:1F:BB:81:5B:E7:A2:3C:77:FE:68:9D:94:B1:43:75:C9:9B:DA:A9:B6:57:7F:D6:D6
## Used libraries, components and icons
* [AndroidX libraries](https://github.com/androidx/androidx) by
[Android Jetpack](https://github.com/androidx)
@@ -133,7 +114,7 @@ Many thanks to [Nikolay Anzarov](https://www.behance.net/nikolayanzarov) ([@Bloo
## License
```
Copyright 2020-2025 The FlorisBoard Contributors
Copyright 2020-2024 Patrick Goldinger
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -148,8 +129,6 @@ See the License for the specific language governing permissions and
limitations under the License.
```
Thanks to [The FlorisBoard Contributors](https://github.com/florisboard/florisboard/graphs/contributors) for making this project possible!
<!-- BEGIN SECTION: obtainium_links -->
<!-- auto-generated link templates, do NOT edit by hand -->
<!-- see fastlane/update-readme.sh -->

View File

@@ -1,67 +1,67 @@
# Roadmap
This feature roadmap intents to provide transparency to what is planned to be added to FlorisBoard in the foreseeable future. Note that there are no ETAs for any version milestones down below, experience has shown these won't hold anyway.
This feature roadmap intents to provide transparency to what is planned to be added to FlorisBoard in the foreseeable future. Note that there are no ETAs for any version milestones down below, experience has shown these won't hold anyways.
Each major milestone has associated alpha/beta releases, so if you are interested in previewing features quicker, keep an eye out! Each major 0.x release has also patch releases after the initial major release, which will be published on both the stable and preview tracks.
Each major milestone has associated alpha/beta releases, so if you are interested in previewing features quicker, keep an eye out! Each major 0.x release has also patch releases after the initial major release, which will be published on both the stable and beta tracks.
## 0.5 (currently in development)
## 0.4
> [!NOTE]
> The milestone 0.5 was split, thus the word suggestions now come with version 0.6. The old version 0.6 has been moved down and is now 0.7. The time it takes to implement word suggestions will not change, but we can now release the new theme editor earlier, which would otherwise lie dormant.
**Main focus**: Getting the project back on track, see [this announcement](https://github.com/florisboard/florisboard/discussions/2314) for details. Note that this has also replaced the previous roadmap, however this step is necessary for getting the project back on track again.
- [x] Theme rework part II / Snygg v2
- [x] See https://github.com/florisboard/florisboard/pull/2855
- [x] Spaces in URI bug (See https://github.com/florisboard/florisboard/issues/2898)
- [x] Re-add time based theme switching (See https://github.com/florisboard/florisboard/pull/2977)
- [x] Add support for any remaining new features introduced with Android 13 / 14
- [x] Raise minimum required Android version from Android 7 (SDK level 24) to Android 8 (SDK level 26)
This includes, but is not exclusive to:
- Fixing the most reported bugs/issues
- Merging in the Material You theme PR -> Adds Material You support (v0.4.0-alpha05)
- Merging in other external PRs as best as possible
- Reworking the Settings UI warning boxes and hiding any UI for features related to word suggestions until they are ready
- Remove existing glide/swipe typing (see 0.5 milestone)
- Improvements in clipboard / emoji functionality (v0.4.0-beta01/beta02)
- Prepare project to have native code implemented in [Rust](https://www.rust-lang.org/) (v0.4.0-beta02)
- - Upgrade Settings UI to Material 3 (v0.4.0-beta03)
- Add support for importing extensions via system file handler APIs (relevant for Addons store) (v0.4.0-beta03)
Note that the previous versioning scheme has been dropped in favor of using a major.minor.patch versioning scheme, so versions like `0.3.16` are a thing of the past :)
## 0.5
- Implement predictive text support / spell checking
- Consider adding proximity-based key typo detection
- Add new extension type: Language Pack
- Basically groups all locale-relevant data (predictive base model, emoji suggestion data, ...)
in a dynamically importable extension file
- New keyboard layout engine + file syntax based on the upcoming Unicode Keyboard v3 standard
- RFC document with technical details will be released later
- New text processing logic (maybe moved back to 0.6)
- RFC document with technical details will be released later
- Add Tablet mode / Optimizations for landscape input based on new keyboard layout engine
- Reimplementation of glide typing with the new layout engine and predictive text core
- Add support for any remaining new features introduced with Android 13
## 0.6
- [ ] Implement predictive text support / spell checking
- [ ] Add new extension type: Language Pack
- Basically groups all locale-relevant data (predictive base model, emoji suggestion data, ...)
in a dynamically importable extension file
- [ ] Proper physical keyboard support (See https://github.com/florisboard/florisboard/issues/1972)
- [ ] Rework cache manager (See https://github.com/florisboard/florisboard/issues/2870)
## k3lp
> [!NOTE]
> The development of k3lp is not tied to a florisboard version and takes place on [codeberg.org](https://codeberg.org/k3lp/k3lp) simultaneously.
- [ ] New keyboard layout engine + file syntax based on the upcoming Unicode Keyboard v3 standard
- [ ] Add Tablet mode / Optimizations for landscape input based on new keyboard layout engine
## 0.7+
> [!NOTE]
> From 0.6 onwards we plan to have more stable 0.X releases but with at most one large feature per release, thus having a much quicker iteration of new features on the stable track, which is a benefit for everyone involved.
- [ ] Add floating keyboard mode
- [ ] New text processing logic
- [ ] Complete rework of the Emoji panel
- [ ] Emoji search
- [ ] Fully scrollable emoji list (soft category borders)
- [ ] Side scrollable emoji list (swipe for next category)
- [ ] More granular theming options
- [ ] Layout customization (e.g. placement of category buttons)
- [ ] Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles
- [ ] Reimplementation of glide typing with the new layout engine and predictive text core
- [ ] Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable 0.7)
- [ ] Rework branding images and texts of FlorisBoard for the app stores
- [ ] Focus on stability and experience improvements of the app and keyboard
- [ ] Add support for new features introduced with Android 15 / 16
- Complete rework of the Emoji panel
- Recently used / Emoji history (already implemented with 0.3.14)
- Emoji search
- Emoji suggestions when using :emoji_name: syntax (already implemented with v0.4.0-beta02)
- Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles
- Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable 0.6)
- Rework branding images and texts of FlorisBoard for the app stores
- Focus on stability and experience improvements of the app and keyboard
- Add support for new features introduced with Android 14
- Not finalized, but planned: raise minimum required Android version from Android 7 (SDK level 24) to Android 8 (SDK level 26)
## Backlog / Planned (unassigned)
**Features that MAY be added (even in versions mentioned above) or dismissed**
- Full on-board layout editor which allows users to create their own layouts without writing a JSON file
- Theme rework part II
- Adaptive themes v2
- Voice-to-text with Mozilla's open-source voice service (or any other oss voice provider)
- Text translation
- Floating keyboard
- Stickers/GIFs
- Kaomoji panel implementation
- FlorisBoard landing web page for presentation
- Implementing additional layouts
- Support for Tasker/Automate/MacroDroid plugins
- Support for WearOS/Smartwatches

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2025 The FlorisBoard Contributors
* Copyright (C) 2022 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
import java.io.ByteArrayOutputStream
plugins {
@@ -22,39 +23,35 @@ plugins {
alias(libs.plugins.kotlin.plugin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.mannodermaus.android.junit5)
alias(libs.plugins.mikepenz.aboutlibraries)
}
val projectMinSdk: String by project
val projectTargetSdk: String by project
val projectCompileSdk: String by project
val projectBuildToolsVersion: String by project
val projectNdkVersion: String by project
val projectVersionCode: String by project
val projectVersionName: String by project
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "").let { suffix ->
if (suffix.isNotEmpty()) {
"-$suffix"
} else {
suffix
}
}
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "")
android {
namespace = "dev.patrickgold.florisboard"
compileSdk = projectCompileSdk.toInt()
buildToolsVersion = tools.versions.buildTools.get()
ndkVersion = tools.versions.ndk.get()
buildToolsVersion = projectBuildToolsVersion
ndkVersion = projectNdkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "1.8"
freeCompilerArgs = listOf(
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-Xjvm-default=all-compatibility",
"-Xwhen-guards",
)
}
@@ -152,13 +149,7 @@ android {
}
aboutLibraries {
collect {
configPath = file("src/main/config")
}
}
lint {
baseline = file("lint.xml")
configPath = "app/src/main/config"
}
testOptions {
@@ -171,21 +162,21 @@ android {
}
}
composeCompiler {
// DO NOT ENABLE STRONG SKIPPING! This project currently relies on
// recomposition on parent state change to update the UI correctly.
featureFlags.add(ComposeFeatureFlag.StrongSkipping.disabled())
}
tasks.withType<Test> {
useJUnitPlatform()
}
dependencies {
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
// testImplementation(composeBom)
// androidTestImplementation(composeBom)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.autofill)
implementation(libs.androidx.collection.ktx)
implementation(libs.androidx.compose.material.icons)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.compose.ui)
@@ -195,30 +186,32 @@ dependencies {
implementation(libs.androidx.emoji2)
implementation(libs.androidx.emoji2.views)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.material.icons)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.profileinstaller)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.runtime)
implementation(libs.cache4k)
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.coroutines)
implementation(libs.kotlinx.serialization.json)
implementation(libs.mikepenz.aboutlibraries.core)
implementation(libs.mikepenz.aboutlibraries.compose)
implementation(libs.patrickgold.compose.tooltip)
implementation(libs.patrickgold.jetpref.datastore.model)
ksp(libs.patrickgold.jetpref.datastore.model.processor)
implementation(libs.patrickgold.jetpref.datastore.ui)
implementation(libs.patrickgold.jetpref.material.ui)
implementation(project(":lib:android"))
implementation(project(":lib:color"))
implementation(project(":lib:compose"))
implementation(project(":lib:kotlin"))
implementation(project(":lib:native"))
implementation(project(":lib:snygg"))
testImplementation(libs.kotlin.test.junit5)
testImplementation(libs.equalsverifier)
testImplementation(libs.kotest.assertions.core)
testImplementation(libs.kotest.extensions.roboelectric)
testImplementation(libs.kotest.property)
testImplementation(libs.kotest.runner.junit5)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
}

View File

@@ -1,3 +1,3 @@
<lint>
<issue id="ExtraTranslation" severity="warning"/>
<issue id="UsingMaterialAndMaterial3Libraries" severity="ignore" />
</lint>

View File

@@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "145ca5bf4bff8e98f71ebc70ab3b495b",
"identityHash": "282a1b421e498fd0e21c055b6a4315e0",
"entities": [
{
"tableName": "clipboard_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL DEFAULT 0, `isRemoteDevice` INTEGER NOT NULL DEFAULT 0)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL, `isRemoteDevice` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
@@ -54,15 +54,13 @@
"fieldPath": "isSensitive",
"columnName": "isSensitive",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
"notNull": true
},
{
"fieldPath": "isRemoteDevice",
"columnName": "isRemoteDevice",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
"notNull": true
}
],
"primaryKey": {
@@ -88,7 +86,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '145ca5bf4bff8e98f71ebc70ab3b495b')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '282a1b421e498fd0e21c055b6a4315e0')"
]
}
}

View File

@@ -1,94 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "1dd181d116dcb4530fb5b33451ea9ab5",
"entities": [
{
"tableName": "clipboard_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `is_sensitive` INTEGER NOT NULL DEFAULT 0, `is_remote_device` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "creationTimestampMs",
"columnName": "creationTimestampMs",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isPinned",
"columnName": "isPinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mimeTypes",
"columnName": "mimeTypes",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSensitive",
"columnName": "is_sensitive",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "isRemoteDevice",
"columnName": "is_remote_device",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"_id"
]
},
"indices": [
{
"name": "index_clipboard_history__id",
"unique": false,
"columnNames": [
"_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dd181d116dcb4530fb5b33451ea9ab5')"
]
}
}

View File

@@ -1,19 +1,3 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* 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
import androidx.test.ext.junit.runners.AndroidJUnit4

View File

@@ -1,4 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020-2022 Patrick Goldinger
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
@@ -114,7 +128,6 @@
android:name="dev.patrickgold.florisboard.lib.crashutility.CrashDialogActivity"
android:icon="@mipmap/floris_app_icon"
android:label="@string/crash_dialog__title"
android:configChanges="orientation|screenSize"
android:theme="@style/CrashDialogTheme"/>
<!-- Copy to Clipboard Activity -->

View File

@@ -17,13 +17,6 @@
"direction": "rtl",
"modifier": "org.florisboard.layouts:arabic"
},
{
"id": "armenian_alt_phonetic",
"label": "Armenian Alt Phonetic",
"authors": [ "MikayelB" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:armenian"
},
{
"id": "western_armenian",
"label": "Armenian (Western)",
@@ -56,12 +49,6 @@
"authors": [ "iamrasel" ],
"direction": "ltr"
},
{
"id": "hindi_in",
"label": "हिंदी",
"authors": [ "npnpatidar" ],
"direction": "ltr"
},
{
"id": "bepo",
"label": "BÉPO",
@@ -157,13 +144,6 @@
"direction": "ltr",
"modifier": "org.florisboard.layouts:dvorak"
},
{
"id": "dvorak_se",
"label": "Dvorak (SE)",
"authors": [ "iceaway" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:dvorak_se"
},
{
"id": "esperanto",
"label": "Esperanto",
@@ -517,12 +497,6 @@
"authors": [ "msrd0" ],
"direction": "ltr"
},
{
"id": "dvorak_se",
"label": "Dvorak (SE)",
"authors": [ "iceaway" ],
"direction": "ltr"
},
{
"id": "hebrew",
"label": "עברית",
@@ -621,12 +595,6 @@
"authors": [ "waelwindows" ],
"direction": "ltr"
},
{
"id": "czech",
"label": "Czech",
"authors": [ "bmondream" ],
"direction": "ltr"
},
{
"id": "devanagari",
"label": "Devanagari",

View File

@@ -1,37 +0,0 @@
[
[
{ "$": "auto_text_key", "code": 229, "label": "å" },
{ "$": "auto_text_key", "code": 228, "label": "ä" },
{ "$": "auto_text_key", "code": 246, "label": "ö" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 102, "label": "f" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 99, "label": "c" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
{ "$": "auto_text_key", "code": 108, "label": "l" }
],
[
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 104, "label": "h" },
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 110, "label": "n" },
{ "$": "auto_text_key", "code": 115, "label": "s" }
],
[
{ "$": "auto_text_key", "code": 113, "label": "q" },
{ "$": "auto_text_key", "code": 106, "label": "j" },
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 120, "label": "x" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 109, "label": "m" },
{ "$": "auto_text_key", "code": 119, "label": "w" },
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 122, "label": "z" }
]
]

View File

@@ -1,42 +0,0 @@
[
[
{ "$": "case_selector", "lower": { "code": 2335, "label": "ट" }, "upper": { "code": 2336, "label": "ठ" } },
{ "$": "case_selector", "lower": { "code": 2337, "label": "ड" }, "upper": { "code": 2338, "label": "ढ" } },
{ "$": "case_selector", "lower": { "code": 2375, "label": "े" }, "upper": { "code": 2376, "label": "ै" } },
{ "$": "case_selector", "lower": { "code": 2352, "label": "र" }, "upper": { "code": 2371, "label": "ृ" } },
{ "$": "case_selector", "lower": { "code": 2340, "label": "त" }, "upper": { "code": 2341, "label": "थ" } },
{ "$": "case_selector", "lower": { "code": 2351, "label": "य" }, "upper": { "code": 2399, "label": "य़" } },
{ "$": "case_selector", "lower": { "code": 2369, "label": "ु" }, "upper": { "code": 2370, "label": "ू" } },
{ "$": "case_selector", "lower": { "code": 2367, "label": "ि" }, "upper": { "code": 2368, "label": "ी" } },
{ "$": "case_selector", "lower": { "code": 2379, "label": "ो" }, "upper": { "code": 2380, "label": "ौ" } },
{ "$": "case_selector", "lower": { "code": 2346, "label": "प" }, "upper": { "code": 2398, "label": "फ़" } }
],
[
{ "$": "case_selector", "lower": { "code": 2366, "label": "ा" }, "upper": { "code": 2309, "label": "अ" } },
{ "$": "case_selector", "lower": { "code": 2360, "label": "स" }, "upper": { "code": 2358, "label": "श" } },
{ "$": "case_selector", "lower": { "code": 2342, "label": "द" }, "upper": { "code": 2343, "label": "ध" } },
{ "$": "case_selector", "lower": { "code": 2347, "label": "फ" }, "upper": { "code": 2364, "label": " ़" } },
{ "$": "case_selector", "lower": { "code": 2327, "label": "ग" }, "upper": { "code": 2328, "label": "घ" } },
{ "$": "case_selector", "lower": { "code": 2361, "label": "ह" }, "upper": { "code": 2307, "label": "" } },
{ "$": "case_selector", "lower": { "code": 2332, "label": "ज" }, "upper": { "code": 2333, "label": "झ" } },
{ "$": "case_selector", "lower": { "code": 2325, "label": "क" }, "upper": { "code": 2326, "label": "ख" } },
{ "$": "case_selector", "lower": { "code": 2354, "label": "ल" }, "upper": { "code": 2355, "label": "ळ" } }
],
[
{
"$": "case_selector",
"lower": { "$": "multi_text_key", "codePoints": [2332, 2381, 2334], "label": "ज्ञ" },
"upper": { "code": 2395, "label": "ज़" }
},
{
"$": "case_selector",
"lower": { "$": "multi_text_key", "codePoints": [2325, 2381, 2359], "label": "क्ष" },
"upper": { "code": 2359, "label": "ष" }
},
{ "$": "case_selector", "lower": { "code": 2330, "label": "च" }, "upper": { "code": 2331, "label": "छ" } },
{ "$": "case_selector", "lower": { "code": 2357, "label": "व" }, "upper": { "code": 2381, "label": "्" } },
{ "$": "case_selector", "lower": { "code": 2348, "label": "ब" }, "upper": { "code": 2349, "label": "भ" } },
{ "$": "case_selector", "lower": { "code": 2344, "label": "न" }, "upper": { "code": 2339, "label": "ण" } },
{ "$": "case_selector", "lower": { "code": 2350, "label": "म" }, "upper": { "code": 2306, "label": "ं" } }
]
]

View File

@@ -1,16 +0,0 @@
[
[
{ "code": -11, "label": "shift", "type": "modifier" },
{ "code": 0, "type": "placeholder" },
{ "code": -7, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "$": "auto_text_key", "code": 44, "label": "," },
{ "code": -227, "label": "language_switch", "type": "system_gui" },
{ "code": -212, "label": "ime_ui_mode_media", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "$": "auto_text_key", "code": 46, "label": "." },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -3,52 +3,46 @@
{ "code": 2535, "label": "১", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 2536, "label": "২", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2537, "label": "৩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 2538, "label": "", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 2539, "label": "৫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 2540, "label": "৬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2542, "label": "৮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2543, "label": "৯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2534, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -33,7 +33,7 @@
{ "code": 32902, "label": "肆" },
{ "code": 18825, "label": "䦉" },
{ "code": 20118, "label": "亖" },
{ "code": 65300, "label": "" }
{ "code": 65300, "label": "" }
]
} },
{ "code": 20116, "label": "五", "type": "numeric", "popup": {

View File

@@ -1,119 +0,0 @@
[
[
{ "$": "shift_state_selector",
"default": {
"code": 43, "label": "+", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 282, "label": "Ě", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" }
}
},
"default": {
"code": 283, "label": "ě", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 352, "label": "Š", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" }
}
},
"default": {
"code": 353, "label": "š", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 268, "label": "Č", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" }
}
},
"default": {
"code": 269, "label": "č", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 344, "label": "Ř", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" }
}
},
"default": {
"code": 345, "label": "ř", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 381, "label": "Ž", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" }
}
},
"default": {
"code": 382, "label": "ž", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 221, "label": "Ý", "type": "numeric", "popup": {
"main": { "code": 55, "label": "7" }
}
},
"default": {
"code": 253, "label": "ý", "type": "numeric", "popup": {
"main": { "code": 55, "label": "7" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 193, "label": "Á", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" }
}
},
"default": {
"code": 225, "label": "á", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 205, "label": "Í", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" }
}
},
"default": {
"code": 237, "label": "í", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" }
}
}
},
{ "$": "shift_state_selector",
"shiftedManual": {
"code": 201, "label": "É", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" }
}
},
"default": {
"code": 233, "label": "é", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" }
}
}
}
]
]

View File

@@ -3,52 +3,46 @@
{ "code": 2407, "label": "१", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 2408, "label": "२", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2409, "label": "३", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 2410, "label": "४", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 2411, "label": "५", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 2412, "label": "६", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2414, "label": "८", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2415, "label": "९", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2406, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 1633, "label": "١", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 1634, "label": "٢", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 1635, "label": "٣", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 1636, "label": "٤", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 1637, "label": "٥", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 1638, "label": "٦", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 1640, "label": "٨", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 1641, "label": "٩", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 1632, "label": "٠", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 2791, "label": "૧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 2792, "label": "૨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2793, "label": "૩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 2794, "label": "૪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 2795, "label": "૫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 2796, "label": "૬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2798, "label": "૮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2799, "label": "૯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2790, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 2663, "label": "", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 2664, "label": "੨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2665, "label": "੩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 2666, "label": "", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 2667, "label": "੫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 2668, "label": "੬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2670, "label": "੮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2671, "label": "੯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2662, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 3303, "label": "೧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 3304, "label": "೨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3305, "label": "೩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 3306, "label": "೪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 3307, "label": "೫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 3308, "label": "೬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3310, "label": "೮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3311, "label": "೯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3302, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 3431, "label": "൧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 3432, "label": "൨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3433, "label": "൩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 3434, "label": "൪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 3435, "label": "൫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 3436, "label": "൬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3438, "label": "൮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3439, "label": "൯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3430, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 2919, "label": "୧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 2920, "label": "", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 2921, "label": "୩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 2922, "label": "୪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 2923, "label": "୫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 2924, "label": "୬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 2926, "label": "୮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 2927, "label": "୯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 2918, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 1777, "label": "۱", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 1778, "label": "۲", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 1779, "label": "۳", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 1780, "label": "۴", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 1781, "label": "۵", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 1782, "label": "۶", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 1784, "label": "۸", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 1785, "label": "۹", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 1776, "label": "۰", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 3047, "label": "௧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 3048, "label": "௨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3049, "label": "௩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 3050, "label": "௪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 3051, "label": "௫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 3052, "label": "௬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3054, "label": "௮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3055, "label": "௯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3046, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 3175, "label": "౧", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 3176, "label": "౨", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 3177, "label": "౩", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 3178, "label": "౪", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 3179, "label": "౫", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 3180, "label": "౬", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 3182, "label": "౮", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 3183, "label": "౯", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 3174, "label": "", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -3,23 +3,20 @@
{ "code": 3665, "label": "๑", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 3666, "label": "๒", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
@@ -27,59 +24,44 @@
{ "code": 3667, "label": "๓", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8540, "label": "" },
{ "code": 8323, "label": "₃"},
{ "code": 8535, "label": "" },
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "" }
{ "code": 8540, "label": "" }
]
} },
{ "code": 3668, "label": "๔", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "⁴" },
"relevant": [
{ "code": 8324, "label": "₄" },
{ "code": 8536, "label": "⅘" }
]
} },
{ "code": 3669, "label": "๕", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8325, "label": "₅" },
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
} },
{ "code": 3670, "label": "๖", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" },
"relevant": [
{ "code": 8326, "label": "₆" }
]
"main": { "code": 8310, "label": "⁶" }
} },
{ "code": 3671, "label": "๗", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8327, "label": "₇" },
{ "code": 8542, "label": "⅞" }
]
} },
{ "code": 3672, "label": "๘", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" },
"relevant": [
{ "code": 8328, "label": "₈" }
]
"main": { "code": 8312, "label": "⁸" }
} },
{ "code": 3673, "label": "๙", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" },
"relevant": [
{ "code": 8329, "label": "₉" }
]
"main": { "code": 8313, "label": "⁹" }
} },
{ "code": 3664, "label": "", "type": "numeric", "popup": {
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
{ "code": 8319, "label": "ⁿ" }
]
} }
]

View File

@@ -3,52 +3,46 @@
{ "code": 71905, "label": "𑣡", "type": "numeric", "popup": {
"main": { "code": 49, "label": "1" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 185, "label": "¹" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
} },
{ "code": 71906, "label": "𑣢", "type": "numeric", "popup": {
"main": { "code": 50, "label": "2" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 178, "label": "²" },
{ "code": 8532, "label": "⅔" },
{ "code": 178, "label": "²" },
{ "code": 8534, "label": "⅖" }
]
} },
{ "code": 71907, "label": "𑣣", "type": "numeric", "popup": {
"main": { "code": 51, "label": "3" },
"relevant": [
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "⅗" },
{ "code": 8540, "label": "" },
{ "code": 8323, "label": ""},
{ "code": 179, "label": "³" }
{ "code": 190, "label": "¾" },
{ "code": 179, "label": "³" },
{ "code": 8540, "label": "" }
]
} },
{ "code": 71908, "label": "𑣤", "type": "numeric", "popup": {
"main": { "code": 52, "label": "4" },
"relevant": [
{ "code": 8324, "label": "" },
{ "code": 8308, "label": "⁴" },
{ "code": 8536, "label": "⅘" }
{ "code": 8536, "label": "" },
{ "code": 8308, "label": "⁴" }
]
} },
{ "code": 71909, "label": "𑣥", "type": "numeric", "popup": {
"main": { "code": 53, "label": "5" },
"relevant": [
{ "code": 8538, "label": "⅚" },
{ "code": 8325, "label": "₅" },
{ "code": 8309, "label": "⁵" },
{ "code": 8541, "label": "⅝" }
]
@@ -56,7 +50,6 @@
{ "code": 71910, "label": "𑣦", "type": "numeric", "popup": {
"main": { "code": 54, "label": "6" },
"relevant": [
{ "code": 8326, "label": "₆" },
{ "code": 8310, "label": "⁶" }
]
} },
@@ -64,32 +57,27 @@
"main": { "code": 55, "label": "7" },
"relevant": [
{ "code": 8542, "label": "⅞" },
{ "code": 8327, "label": "₇" },
{ "code": 8311, "label": "⁷" }
]
} },
{ "code": 71912, "label": "𑣨", "type": "numeric", "popup": {
"main": { "code": 56, "label": "8" },
"relevant": [
{ "code": 8328, "label": "₈" },
{ "code": 8312, "label": "⁸" }
]
} },
{ "code": 71913, "label": "𑣩", "type": "numeric", "popup": {
"main": { "code": 57, "label": "9" },
"relevant": [
{ "code": 8329, "label": "₉" },
{ "code": 8313, "label": "⁹" }
]
} },
{ "code": 71904, "label": "𑣠", "type": "numeric", "popup": {
"main": { "code": 48, "label": "0" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8304, "label": "" },
{ "code": 8585, "label": "" },
{ "code": 8320, "label": "₀" }
{ "code": 8709, "label": "" },
{ "code": 8304, "label": "" }
]
} }
]

View File

@@ -10,17 +10,15 @@
"code": 49, "label": "1", "type": "numeric", "popup": {
"main": { "code": 185, "label": "¹" },
"relevant": [
{ "code": 8321, "label": "" },
{ "code": 8537, "label": "" },
{ "code": 8528, "label": "⅐" },
{ "code": 8539, "label": "⅛" },
{ "code": 8529, "label": "⅑" },
{ "code": 8530, "label": "⅒" },
{ "code": 8543, "label": "⅟" },
{ "code": 189, "label": "½" },
{ "code": 8531, "label": "⅓" },
{ "code": 188, "label": "¼" },
{ "code": 8533, "label": "⅕" },
{ "code": 8537, "label": "⅙" }
{ "code": 8533, "label": "⅕" }
]
}
}
@@ -31,7 +29,6 @@
"code": 50, "label": "2", "type": "numeric", "popup": {
"main": { "code": 178, "label": "²" },
"relevant": [
{ "code": 8322, "label": "₂"},
{ "code": 8532, "label": "⅔" },
{ "code": 8534, "label": "⅖" }
]
@@ -48,10 +45,9 @@
"code": 51, "label": "3", "type": "numeric", "popup": {
"main": { "code": 179, "label": "³" },
"relevant": [
{ "code": 8540, "label": "" },
{ "code": 8323, "label": "₃"},
{ "code": 8535, "label": "" },
{ "code": 190, "label": "¾" },
{ "code": 8535, "label": "" }
{ "code": 8540, "label": "" }
]
}
}
@@ -72,7 +68,6 @@
"code": 52, "label": "4", "type": "numeric", "popup": {
"main": { "code": 8308, "label": "⁴" },
"relevant": [
{ "code": 8324, "label": "₄" },
{ "code": 8536, "label": "⅘" }
]
}
@@ -91,7 +86,6 @@
"code": 53, "label": "5", "type": "numeric", "popup": {
"main": { "code": 8309, "label": "⁵" },
"relevant": [
{ "code": 8325, "label": "₅" },
{ "code": 8538, "label": "⅚" },
{ "code": 8541, "label": "⅝" }
]
@@ -111,10 +105,7 @@
},
"default": {
"code": 54, "label": "6", "type": "numeric", "popup": {
"main": { "code": 8310, "label": "⁶" },
"relevant": [
{ "code": 8326, "label": "₆" }
]
"main": { "code": 8310, "label": "⁶" }
}
}
},
@@ -124,7 +115,6 @@
"code": 55, "label": "7", "type": "numeric", "popup": {
"main": { "code": 8311, "label": "⁷" },
"relevant": [
{ "code": 8327, "label": "₇" },
{ "code": 8542, "label": "⅞" }
]
}
@@ -142,10 +132,7 @@
},
"default": {
"code": 56, "label": "8", "type": "numeric", "popup": {
"main": { "code": 8312, "label": "⁸" },
"relevant": [
{ "code": 8328, "label": "₈" }
]
"main": { "code": 8312, "label": "⁸" }
}
}
},
@@ -173,10 +160,7 @@
},
"default": {
"code": 57, "label": "9", "type": "numeric", "popup": {
"main": { "code": 8313, "label": "⁹" },
"relevant": [
{ "code": 8329, "label": "₉" }
]
"main": { "code": 8313, "label": "⁹" }
}
}
},
@@ -207,9 +191,7 @@
"main": { "code": 8304, "label": "⁰" },
"relevant": [
{ "code": 8709, "label": "∅" },
{ "code": 8319, "label": "ⁿ" },
{ "code": 8585, "label": "↉" },
{ "code": 8320, "label": "₀" }
{ "code": 8319, "label": "ⁿ" }
]
}
}

View File

@@ -64,7 +64,7 @@
{ "code": 11816, "label": "⸨" },
{ "code": 10214, "label": "⟦" },
{ "code": 10216, "label": "⟨" },
{ "code": 10218, "label": "" },
{ "code": 10218, "label": "" },
{ "code": 123, "label": "{" }
]
} },
@@ -72,7 +72,7 @@
"relevant": [
{ "code": 41, "label": ")" },
{ "code": 11817, "label": "⸩" },
{ "code": 10215, "label": "" },
{ "code": 10215, "label": "" },
{ "code": 10217, "label": "⟩" },
{ "code": 10219, "label": "⟫" },
{ "code": 125, "label": "}" }

View File

@@ -23,17 +23,13 @@
{ "code": 45, "label": "-", "popup": {
"main": { "code": 95, "label": "_" },
"relevant": [
{ "code": 8315, "label": "⁻" },
{ "code": 8212, "label": "—" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
} },
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" },
"relevant": [
{ "code": 8314, "label": "⁺" }
]
"main": { "code": 177, "label": "±" }
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(", "popup": {

View File

@@ -48,7 +48,6 @@
{ "id": "fi", "authors": [ "patrickgold" ] },
{ "id": "fo", "authors": [ "BinFlush" ] },
{ "id": "fr", "authors": [ "patrickgold" ] },
{ "id": "hi-IN", "authors": [ "npnpatidar" ] },
{ "id": "hr", "authors": [ "hedidnothingwrong" ] },
{ "id": "hu", "authors": [ "zoli111, gabik65" ] },
{ "id": "hy", "authors": [ "PJTSearch" ] },
@@ -401,16 +400,6 @@
"characters": "org.florisboard.layouts:qwertz"
}
},
{
"languageTag": "hy",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:armenian_dram",
"popupMapping": "org.florisboard.localization:hy",
"preferred": {
"characters": "org.florisboard.layouts:armenian_alt_phonetic",
"symbols": "org.florisboard.layouts:armenian"
}
},
{
"languageTag": "hy",
"composer": "org.florisboard.composers:appender",
@@ -706,16 +695,6 @@
"numericAdvanced": "org.florisboard.layouts:bengali"
}
},
{
"languageTag": "hi-IN",
"composer": "org.florisboard.composers:appender",
"currencySet": "org.florisboard.currencysets:indian_rupee",
"popupMapping": "org.florisboard.localization:hi-IN",
"preferred": {
"characters": "org.florisboard.layouts:hindi_in",
"numericRow": "org.florisboard.layouts:devanagari"
}
},
{
"languageTag": "ast",
"composer": "org.florisboard.composers:appender",

View File

@@ -70,9 +70,8 @@
]
},
"~right": {
"main": { "code": 1567, "label": "؟" },
"main": { "code": 1611, "label": "ً" },
"relevant": [
{ "code": 1611, "label": "ً" },
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },

View File

@@ -34,7 +34,9 @@
]
},
"h": {
"main": { "$": "auto_text_key", "code": 7717, "label": "ḥ" }
"relevant": [
{ "$": "auto_text_key", "code": 7717, "label": "ḥ" }
]
},
"i": {
"main": { "$": "auto_text_key", "code": 237, "label": "í" },
@@ -47,11 +49,13 @@
]
},
"l": {
"main": { "$": "auto_text_key", "code": 7735, "label": "ḷ" }
"relevant": [
{ "$": "auto_text_key", "code": 7735, "label": "ḷ" }
]
},
"n": {
"main": { "$": "auto_text_key", "code": 241, "label": "ñ" },
"relevant": [
{ "$": "auto_text_key", "code": 241, "label": "ñ" },
{ "$": "auto_text_key", "code": 324, "label": "ń" }
]
},
@@ -69,7 +73,9 @@
]
},
"r": {
"main": { "$": "auto_text_key", "code": 691, "label": "ʳ" }
"relevant": [
{ "$": "auto_text_key", "code": 691, "label": "ʳ" }
]
},
"s": {
"relevant": [
@@ -106,4 +112,4 @@
}
}
}

View File

@@ -72,7 +72,7 @@
"র": {
"main": { "$": "auto_text_key", "code": 2482, "label": "ল" },
"relevant": [
{ "code": -255, "label": "র্য" }
{ "code": -255, "label": "র্য" }
]
},
"ন": {

View File

@@ -1,173 +0,0 @@
{
"all": {
"क": {
"main": { "$": "auto_text_key", "code": 2392, "label": "क़" },
"relevant": [{ "$": "auto_text_key", "code": 2393, "label": "ख़" }]
},
"ग": {
"main": { "$": "auto_text_key", "code": 2394, "label": "ग़" },
"relevant": [{ "$": "auto_text_key", "code": 2427, "label": "ॻ" }]
},
"च": {
"relevant": [
{ "$": "auto_text_key", "code": 2385, "label": " ॑" },
{ "$": "auto_text_key", "code": 2386, "label": " ॒" }
]
},
"ज": {
"main": { "$": "auto_text_key", "code": 2395, "label": "ज़" },
"relevant": [
{ "$": "auto_text_key", "code": 2428, "label": "ॼ" },
{ "$": "auto_text_key", "code": 2425, "label": "ॹ" }
]
},
"ड": {
"main": { "$": "auto_text_key", "code": 2396, "label": "ड़" },
"relevant": [{ "$": "auto_text_key", "code": 2397, "label": "ढ़" }]
},
"त": {
"main": { "$": "multi_text_key", "codePoints": [2340, 2381, 2352], "label": "त्र" }
},
"द": {
"main": { "$": "auto_text_key", "code": 2396, "label": "ड़" },
"relevant": [
{ "$": "auto_text_key", "code": 2430, "label": "ॾ" },
{ "$": "auto_text_key", "code": 2397, "label": "ढ़" },
{ "$": "auto_text_key", "code": 2424, "label": "ॸ" }
]
},
"न": {
"main": { "$": "auto_text_key", "code": 2329, "label": "ङ" },
"relevant": [
{ "$": "auto_text_key", "code": 2345, "label": "ऩ" },
{ "$": "auto_text_key", "code": 2334, "label": "ञ" }
]
},
"फ": {
"main": { "$": "auto_text_key", "code": 2398, "label": "फ़" }
},
"ब": {
"relevant": [
{ "$": "auto_text_key", "code": 2431, "label": "ॿ" },
{ "$": "auto_text_key", "code": 2365, "label": "ऽ" },
{ "$": "auto_text_key", "code": 2416, "label": "॰" }
]
},
"म": {
"main": { "$": "auto_text_key", "code": 2305, "label": "ँ" },
"relevant": [{ "$": "auto_text_key", "code": 2304, "label": "ऀ" }]
},
"य": {
"main": { "$": "auto_text_key", "code": 2426, "label": "ॺ" }
},
"र": {
"main": { "$": "auto_text_key", "code": 2315, "label": "ऋ" },
"relevant": [
{ "$": "auto_text_key", "code": 2400, "label": "ॠ" },
{ "$": "auto_text_key", "code": 2372, "label": "ॄ" },
{ "$": "auto_text_key", "code": 2353, "label": "ऱ" }
]
},
"ल": {
"relevant": [
{ "$": "auto_text_key", "code": 2402, "label": "ॢ" },
{ "$": "auto_text_key", "code": 2403, "label": "ॣ" },
{ "$": "auto_text_key", "code": 2316, "label": "ऌ" },
{ "$": "auto_text_key", "code": 2401, "label": "ॡ" },
{ "$": "auto_text_key", "code": 2356, "label": "ऴ" }
]
},
"व": {
"relevant": [
{ "$": "auto_text_key", "code": 2387, "label": " ॓" },
{ "$": "auto_text_key", "code": 2388, "label": " ॔" }
]
},
"स": {
"main": { "$": "multi_text_key", "codePoints": [2358, 2381, 2352], "label": "श्र" },
"relevant": [{ "$": "auto_text_key", "code": 2359, "label": "ष" }]
},
"ा": {
"main": { "$": "auto_text_key", "code": 2310, "label": "आ" },
"relevant": [
{ "$": "auto_text_key", "code": 2373, "label": "ॅ" },
{ "$": "auto_text_key", "code": 2418, "label": "ॲ" },
{ "$": "auto_text_key", "code": 2308, "label": "ऄ" }
]
},
"ि": {
"main": { "$": "auto_text_key", "code": 2311, "label": "इ" },
"relevant": [{ "$": "auto_text_key", "code": 2312, "label": "ई" }]
},
"ु": {
"main": { "$": "auto_text_key", "code": 2313, "label": "उ" },
"relevant": [
{ "$": "auto_text_key", "code": 2422, "label": "ॶ" },
{ "$": "auto_text_key", "code": 2423, "label": "ॷ" },
{ "$": "auto_text_key", "code": 2390, "label": " ॖ" },
{ "$": "auto_text_key", "code": 2314, "label": "ऊ" },
{ "$": "auto_text_key", "code": 2391, "label": " ॗ" }
]
},
"े": {
"main": { "$": "auto_text_key", "code": 2319, "label": "ए" },
"relevant": [
{ "$": "auto_text_key", "code": 2317, "label": "ऍ" },
{ "$": "auto_text_key", "code": 2318, "label": "ऎ" },
{ "$": "auto_text_key", "code": 2374, "label": " ॆ" },
{ "$": "auto_text_key", "code": 2320, "label": "ऐ" },
{ "$": "auto_text_key", "code": 2382, "label": " ॎ" },
{ "$": "auto_text_key", "code": 2389, "label": " ॕ" }
]
},
"ो": {
"main": { "$": "auto_text_key", "code": 2323, "label": "ओ" },
"relevant": [
{ "$": "auto_text_key", "code": 2322, "label": "ऒ" },
{ "$": "auto_text_key", "code": 2378, "label": " ॊ" },
{ "$": "auto_text_key", "code": 2383, "label": " ॏ" },
{ "$": "auto_text_key", "code": 2421, "label": "ॵ" },
{ "$": "auto_text_key", "code": 2384, "label": "ॐ" },
{ "$": "auto_text_key", "code": 2377, "label": "ॉ" },
{ "$": "auto_text_key", "code": 2419, "label": "ॳ" },
{ "$": "auto_text_key", "code": 2420, "label": "ॴ" },
{ "$": "auto_text_key", "code": 2362, "label": " ऺ" },
{ "$": "auto_text_key", "code": 2363, "label": " ऻ" },
{ "$": "auto_text_key", "code": 2324, "label": "औ" },
{ "$": "auto_text_key", "code": 2321, "label": "ऑ" }
]
},
"~right": {
"main": { "code": 2404, "label": "।" },
"relevant": [
{ "code": 37, "label": "%" },
{ "code": 43, "label": "+" },
{ "code": 45, "label": "-" },
{ "code": 58, "label": ":" },
{ "code": 59, "label": ";" },
{ "code": 47, "label": "/" },
{ "$": "layout_direction_selector", "ltr": { "code": 40, "label": "(" }, "rtl": { "code": 41, "label": "(" } },
{ "$": "layout_direction_selector", "ltr": { "code": 41, "label": ")" }, "rtl": { "code": 40, "label": ")" } },
{ "code": 8205, "label": ">⁞<" },
{ "code": 8204, "label": "<⁞>" },
{ "code": 2417, "label": "ॱ" },
{ "code": 2429, "label": "" },
{ "code": 33, "label": "!" },
{ "code": 63, "label": "?" },
{ "code": 2405, "label": "॥" }
]
}
},
"uri": {
"~right": {
"main": { "code": -255, "label": ".com" },
"relevant": [
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".net" },
{ "code": -255, "label": ".in" },
{ "code": -255, "label": ".co.in" }
]
}
}
}

View File

@@ -10,9 +10,9 @@
"main": { "$": "auto_text_key" ,"code" : 237, "label": "í" }
},
"o": {
"main": { "$": "auto_text_key", "code" : 243, "label": "ó" },
"main": { "$": "auto_text_key", "code" : 246, "label": "ö" },
"relevant": [
{ "$": "auto_text_key", "code" : 246, "label": "ö" },
{ "$": "auto_text_key", "code" : 243, "label": "ó" },
{ "$": "auto_text_key", "code" : 337, "label": "ő" }
]
},
@@ -22,9 +22,9 @@
]
},
"u": {
"main": { "$": "auto_text_key", "code" : 250, "label": "ú" },
"main": { "$": "auto_text_key", "code" : 252, "label": "ü" },
"relevant": [
{ "$": "auto_text_key", "code" : 252, "label": "ü" },
{ "$": "auto_text_key", "code" : 250, "label": "ú" },
{ "$": "auto_text_key", "code" : 369, "label": "ű" }
]
},

View File

@@ -0,0 +1,65 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "gboard_day",
"label": "Gboard Day",
"authors": [ "patrickgold", "itskareem" ],
"isNightTheme": false,
"attributes": {
"window": {
"colorPrimary": "#0479ed",
"colorPrimaryDark": "#0467c9",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "true",
"semiTransparentColor": "#20000000",
"textColor": "#000000"
},
"keyboard": {
"background": "#D1D6DC"
},
"key": {
"background": "#FCFFFF",
"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": "@keyboard/background",
"foreground": "#424242"
},
"popup": {
"background": "#EEEEEE",
"backgroundActive": "#BDBDBD",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"background": "@keyboard/background",
"foreground": "@window/textColor",
"foregroundAlt": "#8A8A8A"
},
"smartbarButton": {
"background": "@key/background",
"foreground": "@key/foreground"
},
"glideTrail": {"foreground": "#200479ed"}
}
}

View File

@@ -0,0 +1,65 @@
{
"$type": "dev.patrickgold.florisboard.ime.theme.Theme",
"name": "gboard_night",
"label": "Gboard Night",
"authors": [ "Netscaping" ],
"isNightTheme": true,
"attributes": {
"window": {
"colorPrimary": "#5e97f6",
"colorPrimaryDark": "#4285f4",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/background",
"navigationBarLight": "false",
"semiTransparentColor": "#20FFFFFF",
"textColor": "#FFFFFF"
},
"keyboard": {
"background": "#292e33"
},
"key": {
"background": "#484c4f",
"backgroundPressed": "#5e5e60",
"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": "#373c41",
"foreground": "#9b9da0"
},
"popup": {
"background": "#373c41",
"backgroundActive": "#5a5e60",
"foreground": "@window/textColor"
},
"privateMode": {
"background": "#A000FF",
"foreground": "#FFFFFF"
},
"smartbar": {
"background": "transparent",
"foreground": "#d4d5d6",
"foregroundAlt": "#73FFFFFF"
},
"smartbarButton": {
"background": "#FFFFFF",
"foreground": "#686868"
},
"glideTrail": {"foreground": "#205e97f6"}
}
}

View File

@@ -2,10 +2,10 @@
"$": "ime.extension.theme",
"meta": {
"id": "org.florisboard.themes",
"version": "0.2.0",
"version": "0.1.0",
"title": "FlorisBoard default themes",
"description": "Default themes (both day and night) for the keyboard UI",
"maintainers": [ "patrickgold <patrick@patrickgold.dev>", "lm41 <lm41@lm41.xyz>" ],
"maintainers": [ "patrickgold <patrick@patrickgold.dev>" ],
"license": "apache-2.0"
},
"themes": [
@@ -13,37 +13,49 @@
"id": "floris_day",
"label": "Floris Day",
"authors": [ "patrickgold" ],
"isNight": false
"isNight": false,
"isBorderless": false,
"isMaterialYouAware": false
},
{
"id": "floris_day_borderless",
"label": "Floris Day (Borderless)",
"authors": [ "patrickgold" ],
"isNight": false
"isNight": false,
"isBorderless": true,
"isMaterialYouAware": false
},
{
"id": "floris_night",
"label": "Floris Night",
"authors": [ "patrickgold" ],
"isNight": true
"isNight": true,
"isBorderless": false,
"isMaterialYouAware": false
},
{
"id": "floris_night_borderless",
"label": "Floris Night (Borderless)",
"authors": [ "patrickgold" ],
"isNight": true
"isNight": true,
"isBorderless": true,
"isMaterialYouAware": false
},
{
"id": "floris_pure_night",
"label": "Floris Pure Night",
"authors": [ "serebit" ],
"isNight": true
"isNight": true,
"isBorderless": false,
"isMaterialYouAware": false
},
{
"id": "floris_pure_night_borderless",
"label": "Floris Pure Night (Borderless)",
"authors": [ "serebit" ],
"isNight": true
"isNight": true,
"isBorderless": true,
"isMaterialYouAware": false
}
]
}

View File

@@ -1,40 +1,23 @@
{
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
"@defines": {
"--primary": "#4caf50",
"--primary-variant": "#388e3c",
"--secondary": "#ff9800",
"--secondary-variant": "#e65100",
"--background": "#e0e0e0",
"--background-variant": "#d0d0d0",
"--surface": "#ffffff",
"--surface-variant": "#f5f5f5",
"--popup-surface": "#eeeeee",
"--focused-popup-surface": "#bdbdbd",
"--drag-marker": "rgb(255,0,0)",
"--spacer-color": "rgba(0 ,0, 0, 0.25)",
"--one-hand-background": "#e8f5e9",
"--one-hand-foreground": "#424242",
"--incognito-icon-color": "#00000011",
"--on-primary": "#f0f0f0",
"--on-background": "#121212",
"--on-background-disabled": "rgb(175,175,175)",
"--on-surface": "#000000",
"--on-surface-variant": "#5f5f5f",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)"
"keyboard": {
"background": "var(--background)"
},
"key": {
@@ -42,322 +25,170 @@
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp",
"text-max-lines": "1"
"shadow-elevation": "2dp"
},
"key:pressed": {
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)"
},
"key[code=10]": {
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "var(--on-surface)"
},
"key[code=10]:pressed": {
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "var(--on-surface)"
},
"key[code=32]": {
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"text-overflow": "ellipsis"
},
"key[code=-201,-202,-203]": {
"font-size": "18sp"
},
"key[code=-204,-205]": {
"font-size": "12sp"
},
"key[code=-205]": {
"text-max-lines": "2"
},
"key[code=-11][shiftstate=`caps_lock`]": {
"foreground": "var(--secondary)"
},
"key-hint": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"font-family": "monospace",
"font-size": "12sp",
"padding": "0dp 1dp 1dp 0dp",
"text-max-lines": "1"
"font-size": "12sp"
},
"key-popup-box": {
"background": "var(--popup-surface)",
"key-popup": {
"background": "#eeeeee",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"key-popup-extended-indicator": {
"font-size": "16sp"
"key-popup:focus": {
"background": "#bdbdbd",
"foreground": "var(--on-surface)"
},
"smartbar": {
"font-size": "18sp"
},
"smartbar-shared-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "6dp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
"background": "transparent",
"foreground": "#12121248"
},
"smartbar-action-tile": {
"background": "var(--background-variant)",
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
"background": "transparent",
"foreground": "#12121248"
},
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"smartbar-actions-editor-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "16sp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-header-button": {
"margin": "4dp",
"shape": "circle()"
"font-size": "16sp"
},
"smartbar-actions-editor-subheader": {
"foreground": "var(--secondary)",
"font-size": "16sp",
"font-weight": "bold",
"padding": "12dp 16dp 12dp 8dp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"smartbar-actions-editor-tile-grid": {
"margin": "4dp 0dp"
},
"smartbar-actions-editor-tile": {
"margin": "4dp",
"padding": "8dp",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-tile[code=-999]": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-editor-tile[code=-991]": {
"foreground": "var(--drag-marker)"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rectangle()",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
"foreground": "var(--on-background)"
},
"smartbar-candidate-spacer": {
"foreground": "var(--spacer-color)"
"foreground": "var(--surface)"
},
"clipboard-header": {
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
"margin": "4dp",
"shape": "circle()"
},
"clipboard-header-button:disabled": {
"foreground": "var(--on-background-disabled)"
},
"clipboard-header-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"background": "transparent",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
"font-size": "16sp"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)",
"shadow-elevation": "1dp"
},
"clipboard-clear-all-dialog-message": {
"padding": "16dp"
},
"clipboard-clear-all-dialog-buttons": {
"padding": "4dp"
},
"clipboard-clear-all-dialog-button": {
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#eeeeee",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)"
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"clipboard-history-disabled-title": {
"font-weight": "bold"
"emoji-tab": {
"foreground": "var(--on-background)"
},
"clipboard-history-disabled-message": {
"padding": "0dp 4dp 0dp 8dp"
},
"clipboard-history-disabled-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
},
"clipboard-history-locked-title": {
"font-weight": "bold",
"text-align": "center"
},
"clipboard-history-locked-message": {
"padding": "0dp 4dp 0dp 0dp",
"text-align": "center"
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"extracted-landscape-input-layout": {
@@ -382,82 +213,15 @@
},
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
},
"media-emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"media-emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-key-popup-box": {
"background": "var(--popup-surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"media-emoji-key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"media-emoji-tab": {
"foreground": "var(--on-background)"
},
"media-emoji-tab:focus": {
"foreground": "var(--primary)"
},
"media-bottom-row-button": {
"padding": "16dp 0dp",
"shape": "var(--shape)"
},
"media-emoji-key-popup-extended-indicator": {
"foreground": "inherit"
"foreground": "#00000011"
},
"one-handed-panel": {
"background": "var(--one-hand-background)",
"foreground": "var(--one-hand-foreground)"
"background": "#e8f5e9",
"foreground": "#424242"
},
"subtype-panel": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"subtype-panel-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "18sp",
"padding": "12dp",
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
"system-nav-bar": {
"background": "var(--background)"
}
}

View File

@@ -1,124 +1,83 @@
{
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
"@defines": {
"--primary": "#4caf50",
"--primary-variant": "#388e3c",
"--secondary": "#ff9800",
"--secondary-variant": "#e65100",
"--background": "#e0e0e0",
"--background-variant": "#d0d0d0",
"--surface": "#f0f0f0",
"--surface-variant": "#ffffff",
"--popup-surface": "#eeeeee",
"--focused-popup-surface": "#bdbdbd",
"--drag-marker": "rgb(255,0,0)",
"--spacer-color": "rgba(0 ,0, 0, 0.25)",
"--one-hand-background": "#e8f5e9",
"--one-hand-foreground": "#424242",
"--incognito-icon-color": "#00000011",
"--on-primary": "#f0f0f0",
"--on-background": "#121212",
"--on-background-disabled": "#12121248",
"--on-surface": "#000000",
"--on-surface-variant": "#5f5f5f",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)"
"keyboard": {
"background": "var(--background)"
},
"key": {
"background": "transparent",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"text-max-lines": "1"
"shape": "var(--shape)"
},
"key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"key[code=10]": {
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "var(--on-surface)",
"margin": "0dp 6dp"
"foreground": "var(--on-surface)"
},
"key[code=10]:pressed": {
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "var(--on-surface)"
},
"key[code=32]": {
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"margin": "0dp 6dp",
"text-overflow": "ellipsis"
},
"key[code=-201,-202,-203]": {
"font-size": "18sp"
},
"key[code=-204,-205]": {
"font-size": "12sp"
},
"key[code=-205]": {
"text-max-lines": "2"
},
"key[code=-11][shiftstate=`caps_lock`]": {
"foreground": "var(--secondary)"
},
"key-hint": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"font-family": "monospace",
"padding": "0dp 1dp 1dp 0dp",
"text-max-lines": "1"
"font-size": "12sp"
},
"key-popup-box": {
"background": "var(--popup-surface)",
"key-popup": {
"background": "#eeeeee",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape)"
},
"key-popup-extended-indicator": {
"font-size": "16sp"
"key-popup:focus": {
"background": "#bdbdbd",
"foreground": "var(--on-surface)"
},
"smartbar": {
"font-size": "18sp"
},
"smartbar-shared-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "6dp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-key:pressed": {
@@ -126,244 +85,109 @@
"foreground": "var(--on-surface)"
},
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
"background": "transparent",
"foreground": "#12121248"
},
"smartbar-action-tile": {
"background": "var(--background-variant)",
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
"background": "transparent",
"foreground": "#12121248"
},
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"smartbar-actions-editor-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "16sp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-header-button": {
"margin": "4dp",
"shape": "circle()"
"font-size": "16sp"
},
"smartbar-actions-editor-subheader": {
"foreground": "var(--on-background)",
"font-size": "16sp",
"font-weight": "bold",
"padding": "12dp 16dp 12dp 8dp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"font-size": "16sp"
},
"smartbar-actions-editor-tile-grid": {
"margin": "4dp 0dp"
},
"smartbar-actions-editor-tile": {
"margin": "4dp",
"padding": "8dp",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-tile[code=-999]": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-editor-tile[code=-991]": {
"foreground": "var(--drag-marker)"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rectangle()",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
"margin": "4dp",
"shape": "circle()"
},
"clipboard-header-button:disabled": {
"foreground": "var(--on-background-disabled)"
},
"clipboard-header-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"background": "transparent",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
"font-size": "16sp"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)",
"shadow-elevation": "1dp"
},
"clipboard-clear-all-dialog-message": {
"padding": "16dp"
},
"clipboard-clear-all-dialog-buttons": {
"padding": "4dp"
},
"clipboard-clear-all-dialog-button": {
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#eeeeee",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)"
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"clipboard-history-disabled-title": {
"font-weight": "bold"
"emoji-tab": {
"foreground": "var(--on-background)"
},
"clipboard-history-disabled-message": {
"padding": "0dp 4dp 0dp 8dp"
},
"clipboard-history-disabled-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
},
"clipboard-history-locked-title": {
"font-weight": "bold",
"text-align": "center"
},
"clipboard-history-locked-message": {
"padding": "0dp 4dp 0dp 0dp",
"text-align": "center"
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"extracted-landscape-input-layout": {
@@ -388,82 +212,15 @@
},
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
},
"media-emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"media-emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-key-popup-box": {
"background": "var(--popup-surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"media-emoji-key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"media-emoji-tab": {
"foreground": "var(--on-background)"
},
"media-emoji-tab:focus": {
"foreground": "var(--primary)"
},
"media-bottom-row-button": {
"padding": "16dp 0dp",
"shape": "var(--shape)"
},
"media-emoji-key-popup-extended-indicator": {
"foreground": "inherit"
"foreground": "#00000011"
},
"one-handed-panel": {
"background": "var(--one-hand-background)",
"foreground": "var(--one-hand-foreground)"
"background": "#e8f5e9",
"foreground": "#424242"
},
"subtype-panel": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"subtype-panel-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "18sp",
"padding": "12dp",
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
"system-nav-bar": {
"background": "var(--background)"
}
}

View File

@@ -1,35 +1,23 @@
{
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
"@defines": {
"--primary": "#4caf50",
"--primary-variant": "#388e3c",
"--secondary": "#f57c00",
"--secondary-variant": "#e65100",
"--background": "#212121",
"--background-variant": "#313131",
"--surface": "#424242",
"--surface-variant": "#616161",
"--popup-surface": "#757575",
"--focused-popup-surface": "#bdbdbd",
"--drag-marker": "rgb(255,0,0)",
"--spacer-color": "rgba(255, 255, 255, 0.25)",
"--one-hand-background": "#1b5e20",
"--one-hand-foreground": "#eeeeee",
"--incognito-icon-color": "#ffffff11",
"--on-primary": "#f0f0f0",
"--on-background": "#dcdcdc",
"--on-background-disabled": "#dcdcdc48",
"--on-surface": "#ffffff",
"--on-surface-variant": "#a0a0a0",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)"
"keyboard": {
"background": "var(--background)"
},
"key": {
@@ -37,322 +25,170 @@
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp",
"text-max-lines": "1"
"shadow-elevation": "2dp"
},
"key:pressed": {
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)"
},
"key[code=10]": {
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "var(--on-surface)"
},
"key[code=10]:pressed": {
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "var(--on-surface)"
},
"key[code=32]": {
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"text-overflow": "ellipsis"
},
"key[code=-201,-202,-203]": {
"font-size": "18sp"
},
"key[code=-204,-205]": {
"font-size": "12sp"
},
"key[code=-205]": {
"text-max-lines": "2"
},
"key[code=-11][shiftstate=`caps_lock`]": {
"foreground": "var(--secondary)"
},
"key-hint": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"font-family": "monospace",
"font-size": "12sp",
"padding": "0dp 1dp 1dp 0dp",
"text-max-lines": "1"
"font-size": "12sp"
},
"key-popup-box": {
"background": "var(--popup-surface)",
"key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"key-popup-extended-indicator": {
"font-size": "16sp"
"key-popup:focus": {
"background": "#bdbdbd",
"foreground": "var(--on-surface)"
},
"smartbar": {
"font-size": "18sp"
},
"smartbar-shared-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "6dp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile": {
"background": "var(--background-variant)",
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"smartbar-actions-editor-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "16sp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-header-button": {
"margin": "4dp",
"shape": "circle()"
"font-size": "16sp"
},
"smartbar-actions-editor-subheader": {
"foreground": "var(--secondary)",
"font-size": "16sp",
"font-weight": "bold",
"padding": "12dp 16dp 12dp 8dp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"smartbar-actions-editor-tile-grid": {
"margin": "4dp 0dp"
},
"smartbar-actions-editor-tile": {
"margin": "4dp",
"padding": "8dp",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-tile[code=-999]": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-editor-tile[code=-991]": {
"foreground": "var(--drag-marker)"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rectangle()",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
"foreground": "var(--on-background)"
},
"smartbar-candidate-spacer": {
"foreground": "var(--spacer-color)"
"foreground": "var(--surface)"
},
"clipboard-header": {
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
"margin": "4dp",
"shape": "circle()"
},
"clipboard-header-button:disabled": {
"foreground": "var(--on-background-disabled)"
},
"clipboard-header-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"background": "transparent",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
"font-size": "16sp"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)",
"shadow-elevation": "1dp"
},
"clipboard-clear-all-dialog-message": {
"padding": "16dp"
},
"clipboard-clear-all-dialog-buttons": {
"padding": "4dp"
},
"clipboard-clear-all-dialog-button": {
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)"
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"clipboard-history-disabled-title": {
"font-weight": "bold"
"emoji-tab": {
"foreground": "var(--on-background)"
},
"clipboard-history-disabled-message": {
"padding": "0dp 4dp 0dp 8dp"
},
"clipboard-history-disabled-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
},
"clipboard-history-locked-title": {
"font-weight": "bold",
"text-align": "center"
},
"clipboard-history-locked-message": {
"padding": "0dp 4dp 0dp 0dp",
"text-align": "center"
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"extracted-landscape-input-layout": {
@@ -377,81 +213,15 @@
},
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
},
"media-emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"media-emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-key-popup-box": {
"background": "var(--popup-surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"media-emoji-key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"media-emoji-tab": {
"foreground": "var(--on-background)"
},
"media-emoji-tab:focus": {
"foreground": "var(--primary)"
},
"media-bottom-row-button": {
"padding": "16dp 0dp",
"shape": "var(--shape)"
},
"media-emoji-key-popup-extended-indicator": {
"foreground": "inherit"
"foreground": "#ffffff11"
},
"one-handed-panel": {
"background": "var(--one-hand-background)",
"foreground": "var(--one-hand-foreground)"
"background": "#1b5e20",
"foreground": "#eeeeee"
},
"subtype-panel": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"subtype-panel-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "18sp",
"padding": "12dp",
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
"system-nav-bar": {
"background": "var(--background)"
}
}

View File

@@ -1,119 +1,83 @@
{
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
"@defines": {
"--primary": "#4caf50",
"--primary-variant": "#388e3c",
"--secondary": "#f57c00",
"--secondary-variant": "#e65100",
"--background": "#212121",
"--background-variant": "#313131",
"--surface": "#424242",
"--surface-variant": "#616161",
"--popup-surface": "#757575",
"--focused-popup-surface": "#bdbdbd",
"--drag-marker": "rgb(255,0,0)",
"--spacer-color": "rgba(255, 255, 255, 0.25)",
"--one-hand-background": "#1b5e20",
"--one-hand-foreground": "#eeeeee",
"--incognito-icon-color": "#ffffff11",
"--on-primary": "#f0f0f0",
"--on-background": "#dcdcdc",
"--on-background-disabled": "#dcdcdc48",
"--on-surface": "#ffffff",
"--on-surface-variant": "#a0a0a0",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)"
"keyboard": {
"background": "var(--background)"
},
"key": {
"background": "transparent",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"text-max-lines": "1"
"shape": "var(--shape)"
},
"key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"key[code=10]": {
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "var(--on-surface)",
"margin": "0dp 6dp"
"foreground": "var(--on-surface)"
},
"key[code=10]:pressed": {
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "var(--on-surface)"
},
"key[code=32]": {
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"margin": "0dp 6dp",
"text-overflow": "ellipsis"
},
"key[code=-201,-202,-203]": {
"font-size": "18sp"
},
"key[code=-204,-205]": {
"font-size": "12sp"
},
"key[code=-205]": {
"text-max-lines": "2"
},
"key[code=-11][shiftstate=`caps_lock`]": {
"foreground": "var(--secondary)"
},
"key-hint": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"font-family": "monospace",
"padding": "0dp 1dp 1dp 0dp",
"text-max-lines": "1"
"font-size": "12sp"
},
"key-popup-box": {
"background": "var(--popup-surface)",
"key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape)"
},
"key-popup-extended-indicator": {
"font-size": "16sp"
"key-popup:focus": {
"background": "#bdbdbd",
"foreground": "var(--on-surface)"
},
"smartbar": {
"font-size": "18sp"
},
"smartbar-shared-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "6dp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-key:pressed": {
@@ -121,244 +85,109 @@
"foreground": "var(--on-surface)"
},
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile": {
"background": "var(--background-variant)",
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"smartbar-actions-editor-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "16sp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-header-button": {
"margin": "4dp",
"shape": "circle()"
"font-size": "16sp"
},
"smartbar-actions-editor-subheader": {
"foreground": "var(--on-background)",
"font-size": "16sp",
"font-weight": "bold",
"padding": "12dp 16dp 12dp 8dp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"font-size": "16sp"
},
"smartbar-actions-editor-tile-grid": {
"margin": "4dp 0dp"
},
"smartbar-actions-editor-tile": {
"margin": "4dp",
"padding": "8dp",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-tile[code=-999]": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-editor-tile[code=-991]": {
"foreground": "var(--drag-marker)"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rectangle()",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
"margin": "4dp",
"shape": "circle()"
},
"clipboard-header-button:disabled": {
"foreground": "var(--on-background-disabled)"
},
"clipboard-header-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"background": "transparent",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
"font-size": "16sp"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)",
"shadow-elevation": "1dp"
},
"clipboard-clear-all-dialog-message": {
"padding": "16dp"
},
"clipboard-clear-all-dialog-buttons": {
"padding": "4dp"
},
"clipboard-clear-all-dialog-button": {
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)"
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"clipboard-history-disabled-title": {
"font-weight": "bold"
"emoji-tab": {
"foreground": "var(--on-background)"
},
"clipboard-history-disabled-message": {
"padding": "0dp 4dp 0dp 8dp"
},
"clipboard-history-disabled-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
},
"clipboard-history-locked-title": {
"font-weight": "bold",
"text-align": "center"
},
"clipboard-history-locked-message": {
"padding": "0dp 4dp 0dp 0dp",
"text-align": "center"
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"extracted-landscape-input-layout": {
@@ -383,81 +212,15 @@
},
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
},
"media-emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"media-emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-key-popup-box": {
"background": "var(--popup-surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"media-emoji-key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"media-emoji-tab": {
"foreground": "var(--on-background)"
},
"media-emoji-tab:focus": {
"foreground": "var(--primary)"
},
"media-bottom-row-button": {
"padding": "16dp 0dp",
"shape": "var(--shape)"
},
"media-emoji-key-popup-extended-indicator": {
"foreground": "inherit"
"foreground": "#ffffff11"
},
"one-handed-panel": {
"background": "var(--one-hand-background)",
"foreground": "var(--one-hand-foreground)"
"background": "#1b5e20",
"foreground": "#eeeeee"
},
"subtype-panel": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"subtype-panel-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "18sp",
"padding": "12dp",
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
"system-nav-bar": {
"background": "var(--background)"
}
}

View File

@@ -1,35 +1,23 @@
{
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
"@defines": {
"--primary": "#388e3c",
"--primary-variant": "#306d32",
"--secondary": "#ff9800",
"--secondary-variant": "#804c00",
"--background": "#000000",
"--background-variant": "#111111",
"--surface": "#212121",
"--surface-variant": "#3d3d3d",
"--popup-surface": "#424242",
"--focused-popup-surface": "#707070",
"--drag-marker": "rgb(255,0,0)",
"--spacer-color": "rgba(255, 255, 255, 0.25)",
"--one-hand-background": "#1b5e20",
"--one-hand-foreground": "#eeeeee",
"--incognito-icon-color": "#ffffff11",
"--on-primary": "#f0f0f0",
"--on-background-disabled": "#dcdcdc48",
"--on-background": "#eeeeee",
"--on-surface": "#eeeeee",
"--on-surface-variant": "#ffffff73",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)"
"keyboard": {
"background": "var(--background)"
},
"key": {
@@ -37,81 +25,60 @@
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp",
"text-max-lines": "1"
"shadow-elevation": "2dp"
},
"key:pressed": {
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)"
},
"key[code=10]": {
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "var(--on-surface)"
},
"key[code=10]:pressed": {
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "var(--on-surface)"
},
"key[code=32]": {
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "var(--surface)",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"text-overflow": "ellipsis"
},
"key[code=-201,-202,-203]": {
"font-size": "18sp"
},
"key[code=-204,-205]": {
"font-size": "12sp"
},
"key[code=-205]": {
"text-max-lines": "2"
},
"key[code=-11][shiftstate=`caps_lock`]": {
"foreground": "var(--secondary)"
},
"key-hint": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"font-family": "monospace",
"font-size": "12sp",
"padding": "0dp 1dp 1dp 0dp",
"text-max-lines": "1"
"font-size": "12sp"
},
"key-popup-box": {
"background": "var(--popup-surface)",
"key-popup": {
"background": "#424242",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"key-popup-extended-indicator": {
"font-size": "16sp"
"key-popup:focus": {
"background": "#707070",
"foreground": "var(--on-surface)"
},
"smartbar": {
"font-size": "18sp"
},
"smartbar-shared-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "6dp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-key:pressed": {
@@ -119,244 +86,109 @@
"foreground": "var(--on-surface)"
},
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile": {
"background": "var(--background-variant)",
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"smartbar-actions-editor-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "16sp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-header-button": {
"margin": "4dp",
"shape": "circle()"
"font-size": "16sp"
},
"smartbar-actions-editor-subheader": {
"foreground": "var(--secondary)",
"font-size": "16sp",
"font-weight": "bold",
"padding": "12dp 16dp 12dp 8dp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"smartbar-actions-editor-tile-grid": {
"margin": "4dp 0dp"
},
"smartbar-actions-editor-tile": {
"margin": "4dp",
"padding": "8dp",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-tile[code=-999]": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-editor-tile[code=-991]": {
"foreground": "var(--drag-marker)"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rectangle()",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
"foreground": "var(--on-background)"
},
"smartbar-candidate-spacer": {
"foreground": "var(--spacer-color)"
"foreground": "var(--surface)"
},
"clipboard-header": {
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
"margin": "4dp",
"shape": "circle()"
},
"clipboard-header-button:disabled": {
"foreground": "var(--on-background-disabled)"
},
"clipboard-header-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"background": "transparent",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
"font-size": "16sp"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)",
"shadow-elevation": "1dp"
},
"clipboard-clear-all-dialog-message": {
"padding": "16dp"
},
"clipboard-clear-all-dialog-buttons": {
"padding": "4dp"
},
"clipboard-clear-all-dialog-button": {
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)"
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"clipboard-history-disabled-title": {
"font-weight": "bold"
"emoji-tab": {
"foreground": "var(--on-background)"
},
"clipboard-history-disabled-message": {
"padding": "0dp 4dp 0dp 8dp"
},
"clipboard-history-disabled-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
},
"clipboard-history-locked-title": {
"font-weight": "bold",
"text-align": "center"
},
"clipboard-history-locked-message": {
"padding": "0dp 4dp 0dp 0dp",
"text-align": "center"
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"extracted-landscape-input-layout": {
@@ -367,95 +199,29 @@
"foreground": "var(--on-background)",
"font-size": "16sp",
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"border-color": "var(--secondary)",
"border-width": "2dp"
"border-color": "var(--secondary-variant)",
"border-width": "1dp"
},
"extracted-landscape-input-action": {
"background": "var(--primary)",
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "rounded-corner(4dp, 4dp, 4dp, 4dp)"
},
"glide-trail": {
"foreground": "var(--primary)"
"foreground": "var(--primary-variant)"
},
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
},
"media-emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"media-emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-key-popup-box": {
"background": "var(--popup-surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"media-emoji-key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"media-emoji-tab": {
"foreground": "var(--on-background)"
},
"media-emoji-tab:focus": {
"foreground": "var(--primary)"
},
"media-bottom-row-button": {
"padding": "16dp 0dp",
"shape": "var(--shape)"
},
"media-emoji-key-popup-extended-indicator": {
"foreground": "inherit"
"foreground": "#ffffff11"
},
"one-handed-panel": {
"background": "var(--one-hand-background)",
"foreground": "var(--one-hand-foreground)"
"background": "#000000",
"foreground": "#eeeeee"
},
"subtype-panel": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"subtype-panel-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "18sp",
"padding": "12dp",
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
"system-nav-bar": {
"background": "var(--background)"
}
}

View File

@@ -1,119 +1,83 @@
{
"$schema": "https://schemas.florisboard.org/snygg/v2/stylesheet",
"@defines": {
"--primary": "#388e3c",
"--primary-variant": "#306d32",
"--secondary": "#ff9800",
"--secondary-variant": "#804c00",
"--background": "#000000",
"--background-variant": "#111111",
"--surface": "#212121",
"--surface-variant": "#3d3d3d",
"--popup-surface": "#424242",
"--focused-popup-surface": "#707070",
"--drag-marker": "rgb(255,0,0)",
"--spacer-color": "rgba(255, 255, 255, 0.25)",
"--one-hand-background": "#1b5e20",
"--one-hand-foreground": "#eeeeee",
"--incognito-icon-color": "#ffffff11",
"--on-primary": "#f0f0f0",
"--on-background-disabled": "#dcdcdc48",
"--on-background": "#eeeeee",
"--on-surface": "#eeeeee",
"--on-surface-variant": "#ffffff73",
"--shape": "rounded-corner(8dp, 8dp, 8dp, 8dp)",
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"--shape-chip": "rounded-corner(50%, 50%, 50%, 50%)"
"--shape-variant": "rounded-corner(12dp, 12dp, 12dp, 12dp)"
},
"window": {
"background": "var(--background)",
"foreground": "var(--on-background)"
"keyboard": {
"background": "var(--background)"
},
"key": {
"background": "transparent",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"text-max-lines": "1"
"shape": "var(--shape)"
},
"key:pressed": {
"background": "var(--surface)",
"background": "#6161617f",
"foreground": "var(--on-surface)"
},
"key[code=10]": {
"key[code={c:enter}]": {
"background": "var(--primary)",
"foreground": "var(--on-surface)",
"margin": "0dp 6dp"
"foreground": "var(--on-surface)"
},
"key[code=10]:pressed": {
"key[code={c:enter}]:pressed": {
"background": "var(--primary-variant)",
"foreground": "var(--on-surface)"
},
"key[code=32]": {
"background": "var(--surface)",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"margin": "0dp 6dp",
"text-overflow": "ellipsis"
},
"key[code=-201,-202,-203]": {
"font-size": "18sp"
},
"key[code=-204,-205]": {
"font-size": "12sp"
},
"key[code=-205]": {
"text-max-lines": "2"
},
"key[code=-11][shiftstate=`caps_lock`]": {
"key[code={c:shift}][shiftstate={sh:caps_lock}]": {
"foreground": "var(--secondary)"
},
"key[code={c:space}]": {
"background": "#61616146",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp"
},
"key-hint": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"font-size": "12sp",
"font-family": "monospace",
"padding": "0dp 1dp 1dp 0dp",
"text-max-lines": "1"
"font-size": "12sp"
},
"key-popup-box": {
"background": "var(--popup-surface)",
"key-popup": {
"background": "#363636",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape)"
},
"key-popup-extended-indicator": {
"font-size": "16sp"
"key-popup:focus": {
"background": "#5F5F5F",
"foreground": "var(--on-surface)"
},
"smartbar": {
"font-size": "18sp"
},
"smartbar-shared-actions-toggle": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "6dp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-extended-actions-toggle": {
"background": "transparent",
"foreground": "var(--on-surface-variant)",
"margin": "6dp",
"shape": "circle()"
},
"smartbar-action-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-key:pressed": {
@@ -121,244 +85,109 @@
"foreground": "var(--on-surface)"
},
"smartbar-action-key:disabled": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-overflow": {
"margin": "4dp"
},
"smartbar-actions-overflow-customize-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"font-size": "14sp",
"margin": "0dp 8dp 0dp 0dp",
"shape": "rounded-corner(24dp, 24dp, 24dp, 24dp)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile": {
"background": "var(--background-variant)",
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "4dp",
"shape": "rounded-corner(20%, 20%, 20%, 20%)",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
"font-size": "18sp",
"shape": "var(--shape)"
},
"smartbar-action-tile:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"smartbar-action-tile:disabled": {
"foreground": "var(--on-background-disabled)"
"background": "transparent",
"foreground": "#dcdcdc48"
},
"smartbar-action-tile-icon": {
"font-size": "24sp",
"margin": "0dp 0dp 0dp 8dp"
"smartbar-actions-overflow-customize-button": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"shape": "circle()",
"shadow-elevation": "2dp"
},
"smartbar-actions-editor": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"smartbar-actions-editor-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "16sp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-header-button": {
"margin": "4dp",
"shape": "circle()"
"font-size": "16sp"
},
"smartbar-actions-editor-subheader": {
"foreground": "var(--on-background)",
"font-size": "16sp",
"font-weight": "bold",
"padding": "12dp 16dp 12dp 8dp",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"font-size": "16sp"
},
"smartbar-actions-editor-tile-grid": {
"margin": "4dp 0dp"
},
"smartbar-actions-editor-tile": {
"margin": "4dp",
"padding": "8dp",
"text-align": "center",
"text-max-lines": "2",
"text-overflow": "ellipsis"
},
"smartbar-actions-editor-tile[code=-999]": {
"foreground": "var(--on-background-disabled)"
},
"smartbar-actions-editor-tile[code=-991]": {
"foreground": "var(--drag-marker)"
},
"smartbar-candidate-word": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rectangle()",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rectangle()"
},
"smartbar-candidate-word:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-word-secondary-text": {
"font-size": "8sp",
"margin": "0dp 2dp 0dp 0dp"
},
"smartbar-candidate-clip": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "14sp",
"margin": "4dp",
"padding": "8dp 0dp",
"shape": "rounded-corner(8%, 8%, 8%, 8%)",
"text-max-lines": "1",
"text-overflow": "ellipsis"
"shape": "rounded-corner(8%, 8%, 8%, 8%)"
},
"smartbar-candidate-clip:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-background)"
},
"smartbar-candidate-clip-icon": {
"margin": "0dp 0dp 4dp 0dp"
},
"smartbar-candidate-spacer": {
"foreground": "var(--surface)"
},
"clipboard-header": {
"foreground": "var(--on-background)",
"font-size": "16sp"
},
"clipboard-header-button": {
"margin": "4dp",
"shape": "circle()"
},
"clipboard-header-button:disabled": {
"foreground": "var(--on-background-disabled)"
},
"clipboard-header-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"clipboard-subheader": {
"font-size": "14sp",
"margin": "6dp 10dp 6dp 6dp"
},
"clipboard-content": {
"padding": "10dp 0dp"
},
"clipboard-filter-row": {
"background": "var(--background-variant)",
"foreground": "var(--on-background)",
"padding": "0dp 0dp 4dp 0dp",
"shape": "var(--shape-variant)"
},
"clipboard-filter-chip": {
"background": "var(--surface)",
"background": "transparent",
"foreground": "var(--on-surface)",
"margin": "4dp 4dp 0dp 4dp",
"padding": "8dp 4dp",
"shape": "var(--shape-chip)"
},
"clipboard-filter-chip[state=`active`]": {
"background": "var(--primary)",
"foreground": "var(--on-primary)"
},
"clipboard-filter-chip-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-grid": {
"shape": "var(--shape-variant)"
"font-size": "16sp"
},
"clipboard-item": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp",
"text-max-lines": "10",
"text-overflow": "ellipsis"
},
"clipboard-item[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-description": {
"font-size": "12sp",
"font-style": "italic"
"shadow-elevation": "2dp"
},
"clipboard-item-popup": {
"background": "var(--surface)",
"background": "var(--surface-variant)",
"foreground": "var(--on-surface)",
"font-size": "14sp",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-popup[type=`text`]": {
"padding": "12dp 8dp"
},
"clipboard-item-timestamp": {
"font-size": "11sp",
"padding": "16dp 8dp"
},
"clipboard-item-actions": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"margin": "4dp",
"shape": "var(--shape-variant)",
"shadow-elevation": "2dp"
},
"clipboard-item-action": {
"font-size": "16sp",
"padding": "12dp"
},
"clipboard-item-action-text": {
"margin": "4dp 0dp 0dp 0dp"
},
"clipboard-clear-all-dialog": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)",
"shadow-elevation": "1dp"
},
"clipboard-clear-all-dialog-message": {
"padding": "16dp"
},
"clipboard-clear-all-dialog-buttons": {
"padding": "4dp"
},
"clipboard-clear-all-dialog-button": {
"emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"emoji-key-popup": {
"background": "#757575",
"foreground": "var(--on-surface)",
"shape": "var(--shape-variant)"
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"clipboard-history-disabled-title": {
"font-weight": "bold"
"emoji-tab": {
"foreground": "var(--on-background)"
},
"clipboard-history-disabled-message": {
"padding": "0dp 4dp 0dp 8dp"
},
"clipboard-history-disabled-button": {
"background": "var(--primary)",
"foreground": "var(--on-primary)",
"shape": "rounded-corner(24dp,24dp,24dp,24dp)"
},
"clipboard-history-locked-title": {
"font-weight": "bold",
"text-align": "center"
},
"clipboard-history-locked-message": {
"padding": "0dp 4dp 0dp 0dp",
"text-align": "center"
"emoji-tab:focus": {
"foreground": "var(--primary)"
},
"extracted-landscape-input-layout": {
@@ -369,95 +198,29 @@
"foreground": "var(--on-background)",
"font-size": "16sp",
"shape": "rounded-corner(12dp, 12dp, 12dp, 12dp)",
"border-color": "var(--secondary)",
"border-width": "2dp"
"border-color": "var(--secondary-variant)",
"border-width": "1dp"
},
"extracted-landscape-input-action": {
"background": "var(--primary)",
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"shape": "rounded-corner(4dp, 4dp, 4dp, 4dp)"
},
"glide-trail": {
"foreground": "var(--primary)"
"foreground": "var(--primary-variant)"
},
"incognito-mode-indicator": {
"foreground": "var(--incognito-icon-color)"
},
"inline-autofill-chip": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-subheader": {
"font-weight": "bold",
"margin": "4dp"
},
"media-emoji-key": {
"background": "transparent",
"foreground": "var(--on-background)",
"font-size": "22sp",
"shape": "var(--shape)"
},
"media-emoji-key:pressed": {
"background": "var(--surface)",
"foreground": "var(--on-surface)"
},
"media-emoji-key-popup-box": {
"background": "var(--popup-surface)",
"foreground": "var(--on-surface)",
"font-size": "22sp",
"shape": "var(--shape)",
"shadow-elevation": "2dp"
},
"media-emoji-key-popup-element:focus": {
"background": "var(--focused-popup-surface)",
"shape": "var(--shape)"
},
"media-emoji-tab": {
"foreground": "var(--on-background)"
},
"media-emoji-tab:focus": {
"foreground": "var(--primary)"
},
"media-bottom-row-button": {
"padding": "16dp 0dp",
"shape": "var(--shape)"
},
"media-emoji-key-popup-extended-indicator": {
"foreground": "inherit"
"foreground": "#ffffff11"
},
"one-handed-panel": {
"background": "var(--one-hand-background)",
"foreground": "var(--one-hand-foreground)"
"background": "#000000",
"foreground": "#eeeeee"
},
"subtype-panel": {
"background": "var(--background)",
"foreground": "var(--on-background)",
"shape": "rounded-corner(24dp, 24dp, 0dp, 0dp)"
},
"subtype-panel-header": {
"background": "var(--surface)",
"foreground": "var(--on-surface)",
"font-size": "18sp",
"padding": "12dp",
"text-align": "center",
"text-max-lines": "1",
"text-overflow": "ellipsis"
},
"subtype-panel-list-item": {
"font-size": "16sp",
"padding": "16dp"
},
"subtype-panel-list-item-icon-leading": {
"font-size": "24sp",
"padding": "0dp 0dp 16dp 0dp"
},
"subtype-panel-list-item-text": {
"text-max-lines": "1",
"text-overflow": "ellipsis"
"system-nav-bar": {
"background": "var(--background)"
}
}

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020-2025 The FlorisBoard Contributors
Copyright 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,10 +23,8 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.util.Log
import androidx.core.os.UserManagerCompat
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
@@ -35,6 +33,7 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyboardManager
import dev.patrickgold.florisboard.ime.media.emoji.FlorisEmojiCompat
import dev.patrickgold.florisboard.ime.nlp.NlpManager
import dev.patrickgold.florisboard.ime.text.gestures.GlideTypingManager
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.crashutility.CrashUtility
@@ -42,11 +41,7 @@ import dev.patrickgold.florisboard.lib.devtools.Flog
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.jetpref.datastore.runtime.initAndroid
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import dev.patrickgold.jetpref.datastore.JetPref
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.tryOrNull
import org.florisboard.libnative.dummyAdd
@@ -66,12 +61,12 @@ class FlorisApplication : Application() {
System.loadLibrary("fl_native")
} catch (_: Exception) {
}
FlorisImeTheme.init()
}
}
private val prefs by florisPreferenceModel()
private val mainHandler by lazy { Handler(mainLooper) }
private val scope = CoroutineScope(Dispatchers.Default)
val preferenceStoreLoaded = MutableStateFlow(false)
val cacheManager = lazy { CacheManager(this) }
val clipboardManager = lazy { ClipboardManager(this) }
@@ -87,6 +82,7 @@ class FlorisApplication : Application() {
super.onCreate()
FlorisApplicationReference = WeakReference(this)
try {
JetPref.configure(saveIntervalMs = 500)
Flog.install(
context = this,
isFloggingEnabled = BuildConfig.DEBUG,
@@ -114,14 +110,7 @@ class FlorisApplication : Application() {
fun init() {
cacheDir?.deleteContentsRecursively()
scope.launch {
val result = FlorisPreferenceStore.initAndroid(
context = this@FlorisApplication,
datastoreName = FlorisPreferenceModel.NAME,
)
Log.i("PREFS", result.toString())
preferenceStoreLoaded.value = true
}
prefs.initializeBlocking(this)
extensionManager.value.init()
clipboardManager.value.initializeForContext(this)
DictionaryManager.init(this)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package dev.patrickgold.florisboard
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.inputmethodservice.ExtractEditText
import android.os.Build
@@ -47,11 +46,11 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -75,12 +74,10 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import dev.patrickgold.florisboard.app.FlorisAppActivity
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.devtools.DevtoolsOverlay
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.ClipboardInputLayout
import dev.patrickgold.florisboard.ime.core.SelectSubtypePanel
import dev.patrickgold.florisboard.ime.core.isSubtypeSelectionShowing
import dev.patrickgold.florisboard.ime.editor.EditorRange
import dev.patrickgold.florisboard.ime.editor.FlorisEditorInfo
import dev.patrickgold.florisboard.ime.input.InputFeedbackController
@@ -101,33 +98,33 @@ import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionsEditorPa
import dev.patrickgold.florisboard.ime.text.TextInputLayout
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.ime.theme.WallpaperChangeReceiver
import dev.patrickgold.florisboard.lib.compose.FlorisButton
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
import dev.patrickgold.florisboard.lib.compose.SystemUiIme
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.devtools.flogInfo
import dev.patrickgold.florisboard.lib.devtools.flogWarning
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import org.florisboard.lib.snygg.ui.SnyggSurface
import org.florisboard.lib.snygg.ui.shape
import org.florisboard.lib.snygg.ui.snyggBackground
import org.florisboard.lib.snygg.ui.snyggBorder
import org.florisboard.lib.snygg.ui.snyggShadow
import org.florisboard.lib.snygg.ui.solidColor
import org.florisboard.lib.snygg.ui.spSize
import dev.patrickgold.florisboard.lib.util.ViewUtils
import dev.patrickgold.florisboard.lib.util.debugSummarize
import dev.patrickgold.florisboard.lib.util.launchActivity
import dev.patrickgold.jetpref.datastore.model.observeAsState
import java.lang.ref.WeakReference
import kotlinx.coroutines.flow.update
import org.florisboard.lib.android.AndroidInternalR
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.isOrientationLandscape
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.systemServiceOrNull
import org.florisboard.lib.compose.ProvideLocalizedResources
import org.florisboard.lib.kotlin.collectIn
import org.florisboard.lib.snygg.ui.SnyggBox
import org.florisboard.lib.snygg.ui.SnyggButton
import org.florisboard.lib.snygg.ui.SnyggRow
import org.florisboard.lib.snygg.ui.SnyggSurfaceView
import org.florisboard.lib.snygg.ui.SnyggText
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
import org.florisboard.lib.kotlin.collectLatestIn
import java.lang.ref.WeakReference
/**
* Global weak reference for the [FlorisImeService] class. This is needed as certain actions (request hide, switch to
@@ -234,10 +231,10 @@ class FlorisImeService : LifecycleInputMethodService() {
val imm = ims.systemServiceOrNull(InputMethodManager::class) ?: return false
val list: List<InputMethodInfo> = imm.enabledInputMethodList
for (el in list) {
for (i in 0 until el.subtypeCount) {
for (i in 0 until el.subtypeCount){
if (el.getSubtypeAt(i).mode != "voice") continue
if (AndroidVersion.ATLEAST_API28_P) {
ims.switchInputMethod(el.id, el.getSubtypeAt(i))
ims.switchInputMethod(el.id)
return true
} else {
ims.window.window?.let { window ->
@@ -248,12 +245,12 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
}
ims.showShortToastSync("Failed to find voice IME, do you have one installed?")
ims.showShortToast("Failed to find voice IME, do you have one installed?")
return false
}
}
private val prefs by FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val editorInstance by editorInstance()
private val keyboardManager by keyboardManager()
private val nlpManager by nlpManager()
@@ -269,8 +266,6 @@ class FlorisImeService : LifecycleInputMethodService() {
private var isExtractUiShown by mutableStateOf(false)
private var resourcesContext by mutableStateOf(this as Context)
private val wallpaperChangeReceiver = WallpaperChangeReceiver()
init {
setTheme(R.style.FlorisImeTheme)
}
@@ -279,25 +274,11 @@ class FlorisImeService : LifecycleInputMethodService() {
super.onCreate()
FlorisImeServiceReference = WeakReference(this)
WindowCompat.setDecorFitsSystemWindows(window.window!!, false)
subtypeManager.activeSubtypeFlow.collectIn(lifecycleScope) { subtype ->
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
val config = Configuration(resources.configuration)
if (prefs.localization.displayKeyboardLabelsInSubtypeLanguage.get()) {
config.setLocale(subtype.primaryLocale.base)
}
config.setLocale(subtype.primaryLocale.base)
resourcesContext = createConfigurationContext(config)
}
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.asFlow().collectIn(lifecycleScope) { shouldSync ->
val config = Configuration(resources.configuration)
if (shouldSync) {
config.setLocale(subtypeManager.activeSubtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.physicalKeyboard.showOnScreenKeyboard.asFlow().collectIn(lifecycleScope) {
updateInputViewShown()
}
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
registerReceiver(wallpaperChangeReceiver, IntentFilter(Intent.ACTION_WALLPAPER_CHANGED))
}
override fun onCreateInputView(): View {
@@ -334,14 +315,8 @@ class FlorisImeService : LifecycleInputMethodService() {
return defaultExtractView
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
themeManager.configurationChangeCounter.update { it + 1 }
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(wallpaperChangeReceiver)
FlorisImeServiceReference = WeakReference(null)
inputWindowView = null
}
@@ -366,13 +341,6 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
override fun onEvaluateInputViewShown(): Boolean {
val config = resources.configuration
return super.onEvaluateInputViewShown()
|| config.keyboard == Configuration.KEYBOARD_NOKEYS
|| prefs.physicalKeyboard.showOnScreenKeyboard.get()
}
override fun onUpdateSelection(
oldSelStart: Int,
oldSelEnd: Int,
@@ -415,6 +383,7 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
inputFeedbackController.updateSystemPrefsState()
}
@@ -478,10 +447,6 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS) { "Creating inline suggestions request" }
val stylesBundle = themeManager.createInlineSuggestionUiStyleBundle(this)
if (stylesBundle == null) {
flogWarning(LogTopic.IMS_EVENTS) { "Failed to retrieve inline suggestions style bundle" }
return null
}
val spec = InlinePresentationSpec.Builder(
InlineSuggestionUiSmallestSize,
InlineSuggestionUiBiggestSize,
@@ -529,9 +494,7 @@ class FlorisImeService : LifecycleInputMethodService() {
outInsets.visibleTopInsets = visibleTopY
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION
val left = 0
val top = if (keyboardManager.activeState.isBottomSheetShowing() || keyboardManager.activeState.isSubtypeSelectionShowing()) {
0
} else {
val top = if (keyboardManager.activeState.isBottomSheetShowing()) { 0 } else {
visibleTopY - if (needAdditionalOverlay) FlorisImeSizing.Static.smartbarHeightPx else 0
}
val right = inputViewSize.width
@@ -544,8 +507,7 @@ class FlorisImeService : LifecycleInputMethodService() {
*/
private fun updateSoftInputWindowLayoutParameters() {
val w = window?.window ?: return
// TODO: Verify that this doesn't give us a padding problem
WindowCompat.setDecorFitsSystemWindows(w, false)
WindowCompat.setDecorFitsSystemWindows(w, true)
ViewUtils.updateLayoutHeightOf(w, WindowManager.LayoutParams.MATCH_PARENT)
val layoutHeight = if (isFullscreenUiMode) {
WindowManager.LayoutParams.WRAP_CONTENT
@@ -578,25 +540,23 @@ class FlorisImeService : LifecycleInputMethodService() {
@Composable
private fun ImeUiWrapper() {
ProvideLocalizedResources(
resourcesContext,
appName = R.string.app_name,
) {
ProvideLocalizedResources(resourcesContext) {
ProvideKeyboardRowBaseHeight {
CompositionLocalProvider(LocalInputFeedbackController provides inputFeedbackController) {
FlorisImeTheme {
// Do not apply system bar padding here yet, we want to draw it ourselves
Column(modifier = Modifier.fillMaxWidth()) {
if (!(isFullscreenUiMode && isExtractUiShown)) {
DevtoolsOverlay(
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
)
) {
DevtoolsUi()
}
}
ImeUi()
SystemUiIme()
}
SystemUiIme()
}
}
}
@@ -607,38 +567,26 @@ class FlorisImeService : LifecycleInputMethodService() {
@Composable
private fun ImeUi() {
val state by keyboardManager.activeState.collectAsState()
val attributes = mapOf(
FlorisImeUi.Attr.Mode to state.keyboardMode.toString(),
FlorisImeUi.Attr.ShiftState to state.inputShiftState.toString(),
val keyboardStyle = FlorisImeTheme.style.get(
element = FlorisImeUi.Keyboard,
mode = state.inputShiftState.value,
)
val layoutDirection = LocalLayoutDirection.current
LaunchedEffect(layoutDirection) {
keyboardManager.activeState.layoutDirection = layoutDirection
SideEffect {
if (keyboardManager.activeState.layoutDirection != layoutDirection) {
keyboardManager.activeState.layoutDirection = layoutDirection
}
}
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
SnyggBox(
elementName = FlorisImeUi.Window.elementName,
attributes = attributes,
SnyggSurface(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.onGloballyPositioned { coords -> inputViewSize = coords.size },
clickAndSemanticsModifier = Modifier
.onGloballyPositioned { coords -> inputViewSize = coords.size }
// Do not remove below line or touch input may get stuck
.pointerInteropFilter { false },
supportsBackgroundImage = !AndroidVersion.ATLEAST_API30_R,
allowClip = false,
style = keyboardStyle,
) {
// The SurfaceView is used to render the background image under inline-autofill chips. These are only
// available on Android >=11, and SurfaceView causes trouble on Android 8/9, thus we render the image
// in the SurfaceView for Android >=11, and in the Compose View Tree for Android <=10.
if (AndroidVersion.ATLEAST_API30_R) {
SnyggSurfaceView(
elementName = FlorisImeUi.Window.elementName,
attributes = attributes,
modifier = Modifier.matchParentSize(),
)
}
val configuration = LocalConfiguration.current
val bottomOffset by if (configuration.isOrientationPortrait()) {
prefs.keyboard.bottomOffsetPortrait
@@ -649,18 +597,17 @@ class FlorisImeService : LifecycleInputMethodService() {
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
// Apply system bars padding here (we already drew our keyboard background)
.safeDrawingPadding()
// FIXME: removing this fixes the Smartbar sizing but breaks one-handed-mode
//.height(IntrinsicSize.Min)
.padding(bottom = bottomOffset),
) {
val oneHandedMode by prefs.keyboard.oneHandedMode.observeAsState()
val oneHandedModeEnabled by prefs.keyboard.oneHandedModeEnabled.observeAsState()
val oneHandedModeScaleFactor by prefs.keyboard.oneHandedModeScaleFactor.observeAsState()
val keyboardWeight = when {
!oneHandedModeEnabled || configuration.isOrientationLandscape() -> 1f
oneHandedMode == OneHandedMode.OFF || configuration.isOrientationLandscape() -> 1f
else -> oneHandedModeScaleFactor / 100f
}
if (oneHandedModeEnabled && oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
if (oneHandedMode == OneHandedMode.END && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.START,
weight = 1f - keyboardWeight,
@@ -679,7 +626,7 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
}
if (oneHandedModeEnabled && oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
if (oneHandedMode == OneHandedMode.START && configuration.isOrientationPortrait()) {
OneHandedPanel(
panelSide = OneHandedMode.END,
weight = 1f - keyboardWeight,
@@ -690,13 +637,18 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return keyboardManager.onHardwareKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)
@Composable
private fun DevtoolsUi() {
val devtoolsEnabled by prefs.devtools.enabled.observeAsState()
if (devtoolsEnabled) {
DevtoolsOverlay(modifier = Modifier.fillMaxSize())
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
return keyboardManager.onHardwareKeyUp(keyCode, event) || super.onKeyUp(keyCode, event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean =
if (keyboardManager.onHardwareKeyDown(keyCode, event)) true
else super.onKeyDown(keyCode, event)
private inner class ComposeInputView : AbstractComposeView(this) {
init {
@@ -731,29 +683,15 @@ class FlorisImeService : LifecycleInputMethodService() {
val keyboardManager by context.keyboardManager()
val state by keyboardManager.activeState.collectAsState()
ProvideLocalizedResources(
resourcesContext,
appName = R.string.app_name,
forceLayoutDirection = LayoutDirection.Ltr,
) {
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
FlorisImeTheme {
BottomSheetHostUi(
isShowing = state.isBottomSheetShowing() || state.isSubtypeSelectionShowing(),
isShowing = state.isBottomSheetShowing(),
onHide = {
if (state.isBottomSheetShowing()) {
keyboardManager.activeState.isActionsEditorVisible = false
}
if (state.isSubtypeSelectionShowing()) {
keyboardManager.activeState.isSubtypeSelectionVisible = false
}
keyboardManager.activeState.isActionsEditorVisible = false
},
) {
if (state.isBottomSheetShowing()) {
QuickActionsEditorPanel()
}
if (state.isSubtypeSelectionShowing()) {
SelectSubtypePanel()
}
QuickActionsEditorPanel()
}
}
}
@@ -787,43 +725,44 @@ class FlorisImeService : LifecycleInputMethodService() {
@Composable
fun Content() {
ProvideLocalizedResources(
resourcesContext,
appName = R.string.app_name,
forceLayoutDirection = LayoutDirection.Ltr,
) {
val context = LocalContext.current
ProvideLocalizedResources(resourcesContext, forceLayoutDirection = LayoutDirection.Ltr) {
FlorisImeTheme {
val layoutStyle = FlorisImeTheme.style.get(FlorisImeUi.ExtractedLandscapeInputLayout)
val fieldStyle = FlorisImeTheme.style.get(FlorisImeUi.ExtractedLandscapeInputField)
val actionStyle = FlorisImeTheme.style.get(FlorisImeUi.ExtractedLandscapeInputAction)
val activeEditorInfo by editorInstance.activeInfoFlow.collectAsState()
SnyggBox(FlorisImeUi.ExtractedLandscapeInputLayout.elementName) {
SnyggRow(
Box(
modifier = Modifier
.snyggBackground(context, layoutStyle, FlorisImeTheme.fallbackSurfaceColor()),
) {
Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
SnyggBox(
elementName = FlorisImeUi.ExtractedLandscapeInputLayout.elementName,
val fieldColor = fieldStyle.foreground.solidColor(context, FlorisImeTheme.fallbackContentColor())
AndroidView(
modifier = Modifier
.padding(8.dp)
.fillMaxHeight()
.weight(1f),
) {
val fieldStyle = rememberSnyggThemeQuery(FlorisImeUi.ExtractedLandscapeInputField.elementName)
val foreground = fieldStyle.foreground()
AndroidView(
factory = { extractEditText },
update = { view ->
view.background = null
view.backgroundTintList = null
view.foregroundTintList = null
view.setTextColor(foreground.toArgb())
view.setHintTextColor(foreground.copy(foreground.alpha * 0.6f).toArgb())
view.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
fieldStyle.fontSize(default = 16.sp).value,
)
},
)
}
SnyggButton(
FlorisImeUi.ExtractedLandscapeInputAction.elementName,
.weight(1f)
.snyggShadow(fieldStyle)
.snyggBorder(context, fieldStyle)
.snyggBackground(context, fieldStyle),
factory = { extractEditText },
update = { view ->
view.background = null
view.backgroundTintList = null
view.foregroundTintList = null
view.setTextColor(fieldColor.toArgb())
view.setHintTextColor(fieldColor.copy(fieldColor.alpha * 0.6f).toArgb())
view.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
fieldStyle.fontSize.spSize(default = 16.sp).value,
)
},
)
FlorisButton(
onClick = {
if (activeEditorInfo.extractedActionId != 0) {
currentInputConnection?.performEditorAction(activeEditorInfo.extractedActionId)
@@ -832,13 +771,15 @@ class FlorisImeService : LifecycleInputMethodService() {
}
},
modifier = Modifier.padding(horizontal = 8.dp),
) {
SnyggText(
text = activeEditorInfo.extractedActionLabel
?: getTextForImeAction(activeEditorInfo.imeOptions.action.toInt())
?: "ACTION",
)
}
text = activeEditorInfo.extractedActionLabel
?: getTextForImeAction(activeEditorInfo.imeOptions.action.toInt())
?: "ACTION",
shape = actionStyle.shape.shape(),
colors = ButtonDefaults.buttonColors(
containerColor = actionStyle.background.solidColor(context, FlorisImeTheme.fallbackContentColor()),
contentColor = actionStyle.foreground.solidColor(context, FlorisImeTheme.fallbackSurfaceColor()),
),
)
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import android.service.textservice.SpellCheckerService
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextInfo
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
@@ -33,7 +33,7 @@ import kotlinx.coroutines.runBlocking
import org.florisboard.lib.kotlin.map
class FlorisSpellCheckerService : SpellCheckerService() {
private val prefs by FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val dictionaryManager get() = DictionaryManager.default()
private val nlpManager by nlpManager()
private val subtypeManager by subtypeManager()

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021-2024 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,10 @@ package dev.patrickgold.florisboard.app
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import dev.patrickgold.florisboard.app.settings.theme.ColorPreferenceSerializer
import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
import dev.patrickgold.florisboard.ime.clipboard.CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
@@ -43,39 +40,56 @@ import dev.patrickgold.florisboard.ime.smartbar.CandidatesDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickAction
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionJsonConfig
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.ime.theme.extCoreTheme
import org.florisboard.lib.android.isOrientationPortrait
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import org.florisboard.lib.snygg.SnyggLevel
import dev.patrickgold.florisboard.lib.util.VersionName
import dev.patrickgold.jetpref.datastore.annotations.Preferences
import dev.patrickgold.jetpref.datastore.jetprefDataStoreOf
import dev.patrickgold.jetpref.datastore.model.LocalTime
import dev.patrickgold.jetpref.datastore.model.PreferenceData
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
import dev.patrickgold.jetpref.datastore.model.PreferenceType
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.color.DEFAULT_GREEN
val FlorisPreferenceStore = jetprefDataStoreOf(FlorisPreferenceModel::class)
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
@Preferences
abstract class FlorisPreferenceModel : PreferenceModel() {
companion object {
const val NAME = "florisboard-app-prefs"
class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val advanced = Advanced()
inner class Advanced {
val settingsTheme = enum(
key = "advanced__settings_theme",
default = AppTheme.AUTO,
)
val useMaterialYou = boolean(
key = "advanced__use_material_you",
default = true,
)
val settingsLanguage = string(
key = "advanced__settings_language",
default = "auto",
)
val showAppIcon = boolean(
key = "advanced__show_app_icon",
default = true,
)
val incognitoMode = enum(
key = "advanced__incognito_mode",
default = IncognitoMode.DYNAMIC_ON_OFF,
)
// Internal pref
val forceIncognitoModeFromDynamic = boolean(
key = "advanced__force_incognito_mode_from_dynamic",
default = false,
)
}
val clipboard = Clipboard()
@@ -96,23 +110,6 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "clipboard__history_enabled",
default = false,
)
val numHistoryGridColumnsPortrait = int(
key = "clipboard__num_history_grid_columns_portrait",
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
)
val numHistoryGridColumnsLandscape = int(
key = "clipboard__num_history_grid_columns_landscape",
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
)
@Composable
fun numHistoryGridColumns(): PreferenceData<Int> {
val configuration = LocalConfiguration.current
return if (configuration.isOrientationPortrait()) {
numHistoryGridColumnsPortrait
} else {
numHistoryGridColumnsLandscape
}
}
val cleanUpOld = boolean(
key = "clipboard__clean_up_old",
default = false,
@@ -121,14 +118,6 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "clipboard__clean_up_after",
default = 20,
)
val autoCleanSensitive = boolean(
key = "clipboard__auto_clean_sensitive",
default = false,
)
val autoCleanSensitiveAfter = int(
key = "clipboard__auto_clean_sensitive_after",
default = 20,
)
val limitHistorySize = boolean(
key = "clipboard__limit_history_size",
default = true,
@@ -141,14 +130,6 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "clipboard__clear_primary_clip_deletes_last_item",
default = true,
)
val suggestionEnabled = boolean(
key = "clipboard__suggestion_enabled",
default = true,
)
val suggestionTimeout = int(
key = "clipboard__suggestion_timeout",
default = 60,
)
}
val correction = Correction()
@@ -177,6 +158,10 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "devtools__enabled",
default = false,
)
val showHeapMemoryStats = boolean(
key = "devtools__show_heap_memory_stats",
default = false,
)
val showPrimaryClip = boolean(
key = "devtools__show_primary_clip",
default = false,
@@ -509,11 +494,7 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
)
val oneHandedMode = enum(
key = "keyboard__one_handed_mode",
default = OneHandedMode.END,
)
val oneHandedModeEnabled = boolean(
key = "keyboard__one_handed_mode_enabled",
default = false,
default = OneHandedMode.OFF,
)
val oneHandedModeScaleFactor = int(
key = "keyboard__one_handed_mode_scale_factor",
@@ -585,14 +566,14 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
@Composable
fun fontSizeMultiplier(): Float {
val configuration = LocalConfiguration.current
val oneHandedModeEnabled by oneHandedModeEnabled.observeAsState()
val oneHandedMode by oneHandedMode.observeAsState()
val oneHandedModeFactor by oneHandedModeScaleFactor.observeAsTransformingState { it / 100.0f }
val fontSizeMultiplierBase by if (configuration.isOrientationPortrait()) {
fontSizeMultiplierPortrait
} else {
fontSizeMultiplierLandscape
}.observeAsTransformingState { it / 100.0f }
val fontSizeMultiplier = fontSizeMultiplierBase * if (oneHandedModeEnabled && configuration.isOrientationPortrait()) {
val fontSizeMultiplier = fontSizeMultiplierBase * if (oneHandedMode != OneHandedMode.OFF && configuration.isOrientationPortrait()) {
oneHandedModeFactor
} else {
1.0f
@@ -607,10 +588,6 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "localization__display_language_names_in",
default = DisplayLanguageNamesIn.SYSTEM_LOCALE,
)
val displayKeyboardLabelsInSubtypeLanguage = boolean(
key = "localization__display_keyboard_labels_in_subtype_language",
default = false,
)
val activeSubtypeId = long(
key = "localization__active_subtype_id",
default = Subtype.DEFAULT.id,
@@ -621,38 +598,6 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
)
}
val other = Other()
inner class Other {
val settingsTheme = enum(
key = "other__settings_theme",
default = AppTheme.AUTO,
)
val accentColor = custom(
key = "other__accent_color",
default = when (AndroidVersion.ATLEAST_API31_S) {
true -> Color.Unspecified
false -> DEFAULT_GREEN
},
serializer = ColorPreferenceSerializer,
)
val settingsLanguage = string(
key = "other__settings_language",
default = "auto",
)
val showAppIcon = boolean(
key = "other__show_app_icon",
default = true,
)
}
val physicalKeyboard = PhysicalKeyboard()
inner class PhysicalKeyboard {
val showOnScreenKeyboard = boolean(
key = "physical_keyboard__show_on_screen_keyboard",
default = false,
)
}
val smartbar = Smartbar()
inner class Smartbar {
val enabled = boolean(
@@ -729,14 +674,13 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "suggestion__block_possibly_offensive",
default = true,
)
val incognitoMode = enum(
key = "suggestion__incognito_mode",
default = IncognitoMode.DYNAMIC_ON_OFF,
val clipboardContentEnabled = boolean(
key = "suggestion__clipboard_content_enabled",
default = true,
)
// Internal pref
val forceIncognitoModeFromDynamic = boolean(
key = "suggestion__force_incognito_mode_from_dynamic",
default = false,
val clipboardContentTimeout = int(
key = "suggestion__clipboard_content_timeout",
default = 60,
)
}
@@ -756,25 +700,17 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
default = extCoreTheme("floris_night"),
serializer = ExtensionComponentName.Serializer,
)
val accentColor = custom(
key = "theme__accent_color",
default = when (AndroidVersion.ATLEAST_API31_S) {
true -> Color.Unspecified
false -> DEFAULT_GREEN
},
serializer = ColorPreferenceSerializer,
)
val sunriseTime = localTime(
key = "theme__sunrise_time",
default = LocalTime(6, 0),
)
val sunsetTime = localTime(
key = "theme__sunset_time",
default = LocalTime(18, 0),
)
val editorColorRepresentation = enum(
key = "theme__editor_color_representation",
default = ColorRepresentation.HEX,
//val sunriseTime = localTime(
// key = "theme__sunrise_time",
// default = LocalTime.of(6, 0),
//)
//val sunsetTime = localTime(
// key = "theme__sunset_time",
// default = LocalTime.of(18, 0),
//)
val editorDisplayColorsAs = enum(
key = "theme__editor_display_colors_as",
default = DisplayColorsAs.HEX8,
)
val editorDisplayKbdAfterDialogs = enum(
key = "theme__editor_display_kbd_after_dialogs",
@@ -788,6 +724,34 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
override fun migrate(entry: PreferenceMigrationEntry): PreferenceMigrationEntry {
return when (entry.key) {
// Migrate enums from their lowercase to uppercase representation
// Keep migration rule until: 0.5 dev cycle
"advanced__settings_theme", "gestures__swipe_up", "gestures__swipe_down", "gestures__swipe_left",
"gestures__swipe_right", "gestures__space_bar_swipe_up", "gestures__space_bar_swipe_left",
"gestures__space_bar_swipe_right", "gestures__space_bar_long_press", "gestures__delete_key_swipe_left",
"gestures__delete_key_long_press", "keyboard__hinted_number_row_mode", "keyboard__hinted_symbols_mode",
"keyboard__utility_key_action", "keyboard__one_handed_mode", "keyboard__landscape_input_ui_mode",
"localization__display_language_names_in", "smartbar__primary_actions_row_type",
"smartbar__secondary_actions_placement", "smartbar__secondary_actions_row_type", "spelling__language_mode",
"suggestion__display_mode", "theme__mode", "theme__editor_display_colors_as",
"theme__editor_display_kbd_after_dialogs", "theme__editor_level",
-> {
entry.transform(rawValue = entry.rawValue.uppercase())
}
// Migrate old private mode force flag as this is a sensitive preference
// Keep migration rule until: 0.5 dev cycle
"advanced__force_private_mode" -> {
if (entry.rawValue.toBoolean()) {
entry.transform(
type = PreferenceType.string(),
key = "advanced__incognito_mode",
rawValue = IncognitoMode.FORCE_ON.toString(),
)
} else {
entry.reset()
}
}
// Migrate media prefs to emoji prefs
// Keep migration rule until: 0.6 dev cycle
@@ -802,86 +766,18 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
"media__emoji_recently_used_max_size" -> {
entry.transform(key = "emoji__history_recent_max_size")
}
// Migrate advanced prefs to other prefs
// Keep migration rules until: 0.7 dev cycle
"advanced__settings_theme" -> {
entry.transform(key = "other__settings_theme")
}
"advanced__accent_color" -> {
entry.transform(key = "other__accent_color")
}
"advanced__settings_language" -> {
entry.transform(key = "other__settings_language")
}
"advanced__show_app_icon" -> {
entry.transform(key = "other__show_app_icon")
}
"advanced__incognito_mode" -> {
entry.transform(key = "suggestion__incognito_mode")
}
"advanced__force_incognito_mode_from_dynamic" -> {
entry.transform(key = "suggestion__force_incognito_mode_from_dynamic")
}
// Migrate clipboard suggestion prefs to clipboard
// Keep migration rules until: 0.7 dev cycle
"suggestion__clipboard_content_enabled" -> {
entry.transform(key = "clipboard__suggestion_enabled")
}
"suggestion__clipboard_content_timeout" -> {
entry.transform(key = "clipboard__suggestion_timeout")
}
//Migrate one hand mode prefs keep until: 0.7 dev cycle
"keyboard__one_handed_mode" -> {
if (entry.rawValue == "OFF") {
entry.reset()
} else {
entry.keepAsIs()
}
}
"smartbar__action_arrangement" -> {
fun migrateAction(action: QuickAction): QuickAction {
return if (action is QuickAction.InsertKey && action.data.code == KeyCode.COMPACT_LAYOUT_TO_RIGHT) {
action.copy(TextKeyData.TOGGLE_COMPACT_LAYOUT)
} else {
action
}
}
val arrangement = QuickActionJsonConfig.decodeFromString<QuickActionArrangement>(entry.rawValue)
var newArrangement = arrangement.copy(
stickyAction = arrangement.stickyAction?.let{ migrateAction(it) },
dynamicActions = arrangement.dynamicActions.map { migrateAction(it) },
hiddenActions = arrangement.hiddenActions.map { migrateAction(it) },
)
if (QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH) !in newArrangement) {
newArrangement = newArrangement.copy(
dynamicActions = newArrangement.dynamicActions.plus(QuickAction.InsertKey(TextKeyData.LANGUAGE_SWITCH))
)
}
if (QuickAction.InsertKey(TextKeyData.FORWARD_DELETE) !in newArrangement) {
newArrangement = newArrangement.copy(
dynamicActions = newArrangement.dynamicActions.plus(QuickAction.InsertKey(TextKeyData.FORWARD_DELETE))
)
}
val json = QuickActionJsonConfig.encodeToString(newArrangement.distinct())
entry.transform(rawValue = json)
}
// Migrate theme editor fine-tuning
// Keep migration rule until: 0.6 dev cycle
"theme__editor_display_colors_as" -> {
val colorRepresentation = when (entry.rawValue) {
"RGBA" -> ColorRepresentation.RGB
else -> ColorRepresentation.HEX
}
"media__emoji_preferred_skin_tone" -> {
entry.transform(
key = "theme__editor_color_representation",
rawValue = colorRepresentation.name,
key = "emoji__preferred_skin_tone",
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
)
}
"media__emoji_preferred_hair_style" -> {
entry.transform(
key = "emoji__preferred_hair_style",
rawValue = entry.rawValue.uppercase(), // keep until: 0.5 dev cycle
)
}
// Default: keep entry
else -> entry.keepAsIs()

View File

@@ -1,32 +1,14 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.settings.theme.DisplayColorsAs
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
import dev.patrickgold.florisboard.ime.input.InputShiftState
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
import dev.patrickgold.florisboard.ime.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.keyboard.SpaceBarMode
import dev.patrickgold.florisboard.ime.landscapeinput.LandscapeInputUiMode
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
@@ -42,10 +24,10 @@ import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyHintMode
import dev.patrickgold.florisboard.ime.text.key.UtilityKeyAction
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.lib.compose.stringRes
import org.florisboard.lib.snygg.SnyggLevel
import dev.patrickgold.jetpref.datastore.ui.ListPreferenceEntry
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.kotlin.curlyFormat
import kotlin.reflect.KClass
@@ -60,19 +42,19 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
)
entry(
key = AppTheme.AUTO_AMOLED,
label = stringRes(R.string.pref__other__settings_theme__auto_amoled),
label = stringRes(R.string.pref__advanced__settings_theme__auto_amoled),
)
entry(
key = AppTheme.LIGHT,
label = stringRes(R.string.pref__other__settings_theme__light),
label = stringRes(R.string.pref__advanced__settings_theme__light),
)
entry(
key = AppTheme.DARK,
label = stringRes(R.string.pref__other__settings_theme__dark),
label = stringRes(R.string.pref__advanced__settings_theme__dark),
)
entry(
key = AppTheme.AMOLED_DARK,
label = stringRes(R.string.pref__other__settings_theme__amoled_dark),
label = stringRes(R.string.pref__advanced__settings_theme__amoled_dark),
)
}
},
@@ -104,24 +86,18 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
)
}
},
ColorRepresentation::class to DEFAULT to {
DisplayColorsAs::class to DEFAULT to {
listPrefEntries {
entry(
key = ColorRepresentation.HEX,
label = stringRes(R.string.enum__color_representation__hex),
key = DisplayColorsAs.HEX8,
label = stringRes(R.string.enum__display_colors_as__hex8),
description = stringRes(R.string.general__example_given).curlyFormat("example" to "#4caf50ff"),
showDescriptionOnlyIfSelected = true,
)
entry(
key = ColorRepresentation.RGB,
label = stringRes(R.string.enum__color_representation__rgb),
description = stringRes(R.string.general__example_given).curlyFormat("example" to "rgba(76, 175, 80, 1.0)"),
showDescriptionOnlyIfSelected = true,
)
entry(
key = ColorRepresentation.HSV,
label = stringRes(R.string.enum__color_representation__hsv),
description = stringRes(R.string.general__example_given).curlyFormat("example" to "hsva(122, 56, 68, 1.0)"),
key = DisplayColorsAs.RGBA,
label = stringRes(R.string.enum__display_colors_as__rgba),
description = stringRes(R.string.general__example_given).curlyFormat("example" to "rgba(76,175,80,1.0)"),
showDescriptionOnlyIfSelected = true,
)
}
@@ -366,58 +342,6 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
)
}
},
InputShiftState::class to DEFAULT to {
listPrefEntries {
entry(
key = InputShiftState.UNSHIFTED,
label = stringRes(R.string.enum__input_shift_state__unshifted),
)
entry(
key = InputShiftState.SHIFTED_MANUAL,
label = stringRes(R.string.enum__input_shift_state__shifted_manual),
)
entry(
key = InputShiftState.SHIFTED_AUTOMATIC,
label = stringRes(R.string.enum__input_shift_state__shifted_automatic),
)
entry(
key = InputShiftState.CAPS_LOCK,
label = stringRes(R.string.enum__input_shift_state__caps_lock),
)
}
},
KeyboardMode::class to DEFAULT to {
listPrefEntries {
entry(
key = KeyboardMode.CHARACTERS,
label = stringRes(R.string.enum__keyboard_mode__characters),
)
entry(
key = KeyboardMode.SYMBOLS,
label = stringRes(R.string.enum__keyboard_mode__symbols),
)
entry(
key = KeyboardMode.SYMBOLS2,
label = stringRes(R.string.enum__keyboard_mode__symbols2),
)
entry(
key = KeyboardMode.NUMERIC,
label = stringRes(R.string.enum__keyboard_mode__numeric),
)
entry(
key = KeyboardMode.NUMERIC_ADVANCED,
label = stringRes(R.string.enum__keyboard_mode__numeric_advanced),
)
entry(
key = KeyboardMode.PHONE,
label = stringRes(R.string.enum__keyboard_mode__phone),
)
entry(
key = KeyboardMode.PHONE2,
label = stringRes(R.string.enum__keyboard_mode__phone2),
)
}
},
LandscapeInputUiMode::class to DEFAULT to {
listPrefEntries {
entry(
@@ -436,6 +360,10 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
},
OneHandedMode::class to DEFAULT to {
listPrefEntries {
entry(
key = OneHandedMode.OFF,
label = stringRes(R.string.enum__one_handed_mode__off),
)
entry(
key = OneHandedMode.START,
label = stringRes(R.string.enum__one_handed_mode__start),
@@ -598,10 +526,6 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
key = SwipeAction.SHOW_INPUT_METHOD_PICKER,
label = stringRes(R.string.enum__swipe_action__show_input_method_picker),
)
entry(
key = SwipeAction.SHOW_SUBTYPE_PICKER,
label = "Show subtype picker"
)
entry(
key = SwipeAction.SWITCH_TO_PREV_SUBTYPE,
label = stringRes(R.string.enum__swipe_action__switch_to_prev_subtype),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -39,30 +40,26 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.compose.LocalPreviewFieldController
import dev.patrickgold.florisboard.lib.compose.PreviewKeyboardField
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
import dev.patrickgold.florisboard.lib.compose.conditional
import dev.patrickgold.florisboard.lib.compose.rememberPreviewFieldController
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.util.AppVersionUtils
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.ProvideDefaultDialogPrefStrings
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.hideAppIcon
import org.florisboard.lib.android.showAppIcon
import org.florisboard.lib.compose.ProvideLocalizedResources
import org.florisboard.lib.compose.conditional
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.kotlin.collectIn
import java.util.concurrent.atomic.AtomicBoolean
enum class AppTheme(val id: String) {
AUTO("auto"),
@@ -77,8 +74,7 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
}
class FlorisAppActivity : ComponentActivity() {
private val prefs by FlorisPreferenceStore
private val appContext by appContext()
private val prefs by florisPreferenceModel()
private val cacheManager by cacheManager()
private var appTheme by mutableStateOf(AppTheme.AUTO)
private var showAppIcon = true
@@ -88,44 +84,42 @@ class FlorisAppActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Splash screen should be installed before calling super.onCreate()
installSplashScreen().apply {
setKeepOnScreenCondition { !appContext.preferenceStoreLoaded.value }
setKeepOnScreenCondition { !prefs.datastoreReadyStatus.get() }
}
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
prefs.other.settingsTheme.asFlow().collectIn(lifecycleScope) {
prefs.advanced.settingsTheme.observe(this) {
appTheme = it
}
prefs.other.settingsLanguage.asFlow().collectIn(lifecycleScope) {
prefs.advanced.settingsLanguage.observe(this) {
val config = Configuration(resources.configuration)
val locale = if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it)
config.setLocale(locale.base)
resourcesContext = createConfigurationContext(config)
}
if (AndroidVersion.ATMOST_API28_P) {
prefs.other.showAppIcon.asFlow().collectIn(lifecycleScope) {
prefs.advanced.showAppIcon.observe(this) {
showAppIcon = it
}
}
//Check if android 13+ is running and the NotificationPermission is not set
if (AndroidVersion.ATLEAST_API33_T &&
prefs.internal.notificationPermissionState.get() == NotificationPermissionState.NOT_SET
) {
// update pref value to show the setup screen again again
prefs.internal.isImeSetUp.set(false)
}
// We defer the setContent call until the datastore model is loaded, until then the splash screen stays drawn
val isModelLoaded = AtomicBoolean(false)
appContext.preferenceStoreLoaded.collectIn(lifecycleScope) { loaded ->
if (!loaded || isModelLoaded.getAndSet(true)) return@collectIn
// Check if android 13+ is running and the NotificationPermission is not set
if (AndroidVersion.ATLEAST_API33_T &&
prefs.internal.notificationPermissionState.get() == NotificationPermissionState.NOT_SET
) {
// update pref value to show the setup screen again
prefs.internal.isImeSetUp.set(false)
}
prefs.datastoreReadyStatus.observe(this) { isModelLoaded ->
if (!isModelLoaded) return@observe
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
setContent {
ProvideLocalizedResources(
resourcesContext,
appName = R.string.app_name,
) {
FlorisAppTheme(theme = appTheme) {
ProvideLocalizedResources(resourcesContext) {
val useMaterialYou by prefs.advanced.useMaterialYou.observeAsState()
FlorisAppTheme(theme = appTheme, isMaterialYouAware = useMaterialYou) {
Surface(color = MaterialTheme.colorScheme.background) {
AppContent()
}
@@ -197,7 +191,7 @@ class FlorisAppActivity : ComponentActivity() {
Routes.AppNavHost(
modifier = Modifier.weight(1.0f),
navController = navController,
startDestination = if (isImeSetUp) Routes.Settings.Home::class else Routes.Setup.Screen::class,
startDestination = if (isImeSetUp) Routes.Settings.Home else Routes.Setup.Screen,
)
PreviewKeyboardField(previewFieldController)
}
@@ -221,5 +215,9 @@ class FlorisAppActivity : ComponentActivity() {
}
intentToBeHandled = null
}
SideEffect {
navController.setOnBackPressedDispatcher(this.onBackPressedDispatcher)
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,17 +17,12 @@
package dev.patrickgold.florisboard.app
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.unit.IntOffset
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraphBuilder
@@ -35,7 +30,6 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import androidx.navigation.toRoute
import dev.patrickgold.florisboard.app.devtools.AndroidLocalesScreen
import dev.patrickgold.florisboard.app.devtools.AndroidSettingsScreen
import dev.patrickgold.florisboard.app.devtools.DevtoolsScreen
@@ -53,9 +47,8 @@ import dev.patrickgold.florisboard.app.settings.HomeScreen
import dev.patrickgold.florisboard.app.settings.about.AboutScreen
import dev.patrickgold.florisboard.app.settings.about.ProjectLicenseScreen
import dev.patrickgold.florisboard.app.settings.about.ThirdPartyLicensesScreen
import dev.patrickgold.florisboard.app.settings.advanced.AdvancedScreen
import dev.patrickgold.florisboard.app.settings.advanced.BackupScreen
import dev.patrickgold.florisboard.app.settings.advanced.OtherScreen
import dev.patrickgold.florisboard.app.settings.advanced.PhysicalKeyboardScreen
import dev.patrickgold.florisboard.app.settings.advanced.RestoreScreen
import dev.patrickgold.florisboard.app.settings.clipboard.ClipboardScreen
import dev.patrickgold.florisboard.app.settings.dictionary.DictionaryScreen
@@ -76,185 +69,112 @@ import dev.patrickgold.florisboard.app.settings.theme.ThemeManagerScreenAction
import dev.patrickgold.florisboard.app.settings.theme.ThemeScreen
import dev.patrickgold.florisboard.app.settings.typing.TypingScreen
import dev.patrickgold.florisboard.app.setup.SetupScreen
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.reflect.KClass
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Deeplink(val path: String)
inline fun <reified T : Any> NavGraphBuilder.composableWithDeepLink(
kClass: KClass<T>,
noinline content: @Composable (AnimatedContentScope.(NavBackStackEntry) -> Unit),
) {
val deeplink = requireNotNull(kClass.annotations.firstOrNull { it is Deeplink } as? Deeplink) {
"faulty class: $kClass with annotations ${kClass.annotations}"
}
composable<T>(
deepLinks = listOf(navDeepLink<T>(basePath = "ui://florisboard/${deeplink.path}")),
content = content,
)
}
import org.florisboard.lib.kotlin.curlyFormat
@Suppress("FunctionName", "ConstPropertyName")
object Routes {
object Setup {
@Serializable
object Screen
const val Screen = "setup"
}
object Settings {
@Serializable
@Deeplink("settings/home")
object Home
const val Home = "settings"
@Serializable
@Deeplink("settings/localization")
object Localization
const val Localization = "settings/localization"
const val SelectLocale = "settings/localization/select-locale"
const val LanguagePackManager = "settings/localization/language-pack-manage/{action}"
fun LanguagePackManager(action: LanguagePackManagerScreenAction) =
LanguagePackManager.curlyFormat("action" to action.id)
const val SubtypeAdd = "settings/localization/subtype/add"
const val SubtypeEdit = "settings/localization/subtype/edit/{id}"
fun SubtypeEdit(id: Long) = SubtypeEdit.curlyFormat("id" to id)
@Serializable
@Deeplink("settings/localization/select-locale")
object SelectLocale
const val Theme = "settings/theme"
const val ThemeManager = "settings/theme/manage/{action}"
fun ThemeManager(action: ThemeManagerScreenAction) = ThemeManager.curlyFormat("action" to action.id)
@Serializable
@Deeplink("settings/localization/language-pack-manage")
data class LanguagePackManager(val action: LanguagePackManagerScreenAction)
const val Keyboard = "settings/keyboard"
const val InputFeedback = "settings/keyboard/input-feedback"
@Serializable
@Deeplink("settings/localization/subtype/add")
object SubtypeAdd
const val Smartbar = "settings/smartbar"
@Serializable
@Deeplink("settings/localization/subtype/edit")
data class SubtypeEdit(val id: Long)
const val Typing = "settings/typing"
@Serializable
@Deeplink("settings/theme")
object Theme
const val Dictionary = "settings/dictionary"
const val UserDictionary = "settings/dictionary/user-dictionary/{type}"
fun UserDictionary(type: UserDictionaryType) = UserDictionary.curlyFormat("type" to type.id)
@Serializable
@Deeplink("settings/theme/manage")
data class ThemeManager(val action: ThemeManagerScreenAction)
const val Gestures = "settings/gestures"
@Serializable
@Deeplink("settings/keyboard")
object Keyboard
const val Clipboard = "settings/clipboard"
@Serializable
@Deeplink("settings/keyboard/input-feedback")
object InputFeedback
const val Media = "settings/media"
@Serializable
@Deeplink("settings/smartbar")
object Smartbar
const val Advanced = "settings/advanced"
const val Backup = "settings/advanced/backup"
const val Restore = "settings/advanced/restore"
@Serializable
@Deeplink("settings/typing")
object Typing
@Serializable
@Deeplink("settings/dictionary")
object Dictionary
@Serializable
@Deeplink("settings/dictionary/user-dictionary")
data class UserDictionary(val type: UserDictionaryType)
@Serializable
@Deeplink("settings/gestures")
object Gestures
@Serializable
@Deeplink("settings/clipboard")
object Clipboard
@Serializable
@Deeplink("settings/media")
object Media
@Serializable
@Deeplink("settings/other")
object Other
@Serializable
@Deeplink("settings/other/physical-keyboard")
object PhysicalKeyboard
@Serializable
@Deeplink("settings/other/backup")
object Backup
@Serializable
@Deeplink("settings/other/restore")
object Restore
@Serializable
@Deeplink("settings/about")
object About
@Serializable
@Deeplink("settings/about/project-license")
object ProjectLicense
@Serializable
@Deeplink("settings/about/third-party-licenses")
object ThirdPartyLicenses
const val About = "settings/about"
const val ProjectLicense = "settings/about/project-license"
const val ThirdPartyLicenses = "settings/about/third-party-licenses"
}
object Devtools {
@Serializable
@Deeplink("devtools")
object Home
const val Home = "devtools"
@Serializable
@Deeplink("devtools/android/locales")
object AndroidLocales
const val AndroidLocales = "devtools/android/locales"
const val AndroidSettings = "devtools/android/settings/{name}"
fun AndroidSettings(name: String) = AndroidSettings.curlyFormat("name" to name)
@Serializable
@Deeplink("devtools/android/settings")
data class AndroidSettings(val name: String)
@Serializable
@Deeplink("export-debug-log")
object ExportDebugLog
const val ExportDebugLog = "export-debug-log"
}
object Ext {
@Serializable
@Deeplink("ext")
object Home
const val Home = "ext"
@Serializable
@Deeplink("ext/list")
data class List(val type: ExtensionListScreenType, val showUpdate: Boolean? = null)
const val List = "ext/list/{type}?showUpdate={showUpdate}"
fun List(
type: ExtensionListScreenType,
showUpdate: Boolean
) = List.curlyFormat("type" to type.id, "showUpdate" to showUpdate)
@Serializable
@Deeplink("ext/edit")
data class Edit(val id: String, @SerialName("create") val serialType: String? = null)
const val Edit = "ext/edit/{id}?create={serial_type}"
fun Edit(id: String, serialType: String? = null): String {
return Edit.curlyFormat("id" to id, "serial_type" to (serialType ?: ""))
}
@Serializable
@Deeplink("ext/export")
data class Export(val id: String)
const val Export = "ext/export/{id}"
fun Export(id: String) = Export.curlyFormat("id" to id)
@Serializable
@Deeplink("ext/import")
data class Import(val type: ExtensionImportScreenType, val uuid: String? = null)
const val Import = "ext/import/{type}?uuid={uuid}"
fun Import(
type: ExtensionImportScreenType,
uuid: String?,
) = Import.curlyFormat("type" to type.id, "uuid" to uuid.toString())
@Serializable
@Deeplink("ext/view")
data class View(val id: String)
const val View = "ext/view/{id}"
fun View(id: String) = View.curlyFormat("id" to id)
@Serializable
@Deeplink("ext/check-updates")
object CheckUpdates
const val CheckUpdates = "ext/check-updates"
}
@Composable
fun AppNavHost(
modifier: Modifier,
navController: NavHostController,
startDestination: KClass<*>,
startDestination: String,
) {
fun NavGraphBuilder.composableWithDeepLink(
route: String,
content: @Composable (AnimatedContentScope.(NavBackStackEntry) -> Unit),
) {
composable(
route = route,
deepLinks = listOf(navDeepLink { uriPattern = "ui://florisboard/$route" }),
content = content,
)
}
NavHost(
modifier = modifier,
navController = navController,
@@ -265,103 +185,108 @@ object Routes {
exitTransition = {
slideOut { IntOffset(-it.width, 0) } + fadeOut()
},
popEnterTransition = { EnterTransition.None },
popExitTransition = {
scaleOut(
targetScale = 0.85F,
transformOrigin = TransformOrigin(pivotFractionX = 0.8f, pivotFractionY = 0.5f)
) + fadeOut(spring(stiffness = Spring.StiffnessMedium))
popEnterTransition = {
slideIn { IntOffset(-it.width, 0) } + fadeIn()
},
popExitTransition = {
slideOut { IntOffset(it.width, 0) } + fadeOut()
}
) {
composable<Setup.Screen> { SetupScreen() }
composable(Setup.Screen) { SetupScreen() }
composableWithDeepLink(Settings.Home::class) { HomeScreen() }
composableWithDeepLink(Settings.Home) { HomeScreen() }
composableWithDeepLink(Settings.Localization::class) { LocalizationScreen() }
composableWithDeepLink(Settings.SelectLocale::class) { SelectLocaleScreen() }
composableWithDeepLink(Settings.LanguagePackManager::class) { navBackStack ->
val payload = navBackStack.toRoute<Settings.LanguagePackManager>()
LanguagePackManagerScreen(payload.action)
composableWithDeepLink(Settings.Localization) { LocalizationScreen() }
composableWithDeepLink(Settings.SelectLocale) { SelectLocaleScreen() }
composableWithDeepLink(Settings.LanguagePackManager) { navBackStack ->
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
LanguagePackManagerScreenAction.entries.firstOrNull { it.id == actionId }
}
LanguagePackManagerScreen(action)
}
composableWithDeepLink(Settings.SubtypeAdd::class) { SubtypeEditorScreen(null) }
composableWithDeepLink(Settings.SubtypeEdit::class) { navBackStack ->
val payload = navBackStack.toRoute<Settings.SubtypeEdit>()
SubtypeEditorScreen(payload.id)
composableWithDeepLink(Settings.SubtypeAdd) { SubtypeEditorScreen(null) }
composableWithDeepLink(Settings.SubtypeEdit) { navBackStack ->
val id = navBackStack.arguments?.getString("id")?.toLongOrNull()
SubtypeEditorScreen(id)
}
composableWithDeepLink(Settings.Theme::class) { ThemeScreen() }
composableWithDeepLink(Settings.ThemeManager::class) { navBackStack ->
val payload = navBackStack.toRoute<Settings.ThemeManager>()
ThemeManagerScreen(payload.action)
composableWithDeepLink(Settings.Theme) { ThemeScreen() }
composableWithDeepLink(Settings.ThemeManager) { navBackStack ->
val action = navBackStack.arguments?.getString("action")?.let { actionId ->
ThemeManagerScreenAction.entries.firstOrNull { it.id == actionId }
}
ThemeManagerScreen(action)
}
composableWithDeepLink(Settings.Keyboard::class) { KeyboardScreen() }
composableWithDeepLink(Settings.InputFeedback::class) { InputFeedbackScreen() }
composableWithDeepLink(Settings.Keyboard) { KeyboardScreen() }
composableWithDeepLink(Settings.InputFeedback) { InputFeedbackScreen() }
composableWithDeepLink(Settings.Smartbar::class) { SmartbarScreen() }
composableWithDeepLink(Settings.Smartbar) { SmartbarScreen() }
composableWithDeepLink(Settings.Typing::class) { TypingScreen() }
composableWithDeepLink(Settings.Typing) { TypingScreen() }
composableWithDeepLink(Settings.Dictionary::class) { DictionaryScreen() }
composableWithDeepLink(Settings.UserDictionary::class) { navBackStack ->
val payload = navBackStack.toRoute<Settings.UserDictionary>()
UserDictionaryScreen(payload.type)
composableWithDeepLink(Settings.Dictionary) { DictionaryScreen() }
composableWithDeepLink(Settings.UserDictionary) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
UserDictionaryType.entries.firstOrNull { it.id == typeId }
}
UserDictionaryScreen(type!!)
}
composableWithDeepLink(Settings.Gestures::class) { GesturesScreen() }
composableWithDeepLink(Settings.Gestures) { GesturesScreen() }
composableWithDeepLink(Settings.Clipboard::class) { ClipboardScreen() }
composableWithDeepLink(Settings.Clipboard) { ClipboardScreen() }
composableWithDeepLink(Settings.Media::class) { MediaScreen() }
composableWithDeepLink(Settings.Media) { MediaScreen() }
composableWithDeepLink(Settings.Other::class) { OtherScreen() }
composableWithDeepLink(Settings.PhysicalKeyboard::class) { PhysicalKeyboardScreen() }
composableWithDeepLink(Settings.Backup::class) { BackupScreen() }
composableWithDeepLink(Settings.Restore::class) { RestoreScreen() }
composableWithDeepLink(Settings.Advanced) { AdvancedScreen() }
composableWithDeepLink(Settings.Backup) { BackupScreen() }
composableWithDeepLink(Settings.Restore) { RestoreScreen() }
composableWithDeepLink(Settings.About::class) { AboutScreen() }
composableWithDeepLink(Settings.ProjectLicense::class) { ProjectLicenseScreen() }
composableWithDeepLink(Settings.ThirdPartyLicenses::class) { ThirdPartyLicensesScreen() }
composableWithDeepLink(Settings.About) { AboutScreen() }
composableWithDeepLink(Settings.ProjectLicense) { ProjectLicenseScreen() }
composableWithDeepLink(Settings.ThirdPartyLicenses) { ThirdPartyLicensesScreen() }
composableWithDeepLink(Devtools.Home::class) { DevtoolsScreen() }
composableWithDeepLink(Devtools.AndroidLocales::class) { AndroidLocalesScreen() }
composableWithDeepLink(Devtools.AndroidSettings::class) { navBackStack ->
val payload = navBackStack.toRoute<Devtools.AndroidSettings>()
AndroidSettingsScreen(payload.name)
composableWithDeepLink(Devtools.Home) { DevtoolsScreen() }
composableWithDeepLink(Devtools.AndroidLocales) { AndroidLocalesScreen() }
composableWithDeepLink(Devtools.AndroidSettings) { navBackStack ->
val name = navBackStack.arguments?.getString("name")
AndroidSettingsScreen(name)
}
composableWithDeepLink(Devtools.ExportDebugLog::class) { ExportDebugLogScreen() }
composableWithDeepLink(Devtools.ExportDebugLog) { ExportDebugLogScreen() }
composableWithDeepLink(Ext.Home::class) { ExtensionHomeScreen() }
composableWithDeepLink(Ext.List::class) { navBackStack ->
val payload = navBackStack.toRoute<Ext.List>()
val showUpdate = payload.showUpdate != null && payload.showUpdate
ExtensionListScreen(payload.type, showUpdate)
composableWithDeepLink(Ext.Home) { ExtensionHomeScreen() }
composableWithDeepLink(Ext.List) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
ExtensionListScreenType.entries.firstOrNull { it.id == typeId }
} ?: error("unknown type")
val showUpdate = navBackStack.arguments?.getString("showUpdate")
ExtensionListScreen(type, showUpdate == "true")
}
composableWithDeepLink(Ext.Edit::class) { navBackStack ->
val payload = navBackStack.toRoute<Ext.Edit>()
val extensionId = payload.id
val serialType = payload.serialType
composableWithDeepLink(Ext.Edit) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
val serialType = navBackStack.arguments?.getString("serial_type")
ExtensionEditScreen(
id = extensionId,
id = extensionId.toString(),
createSerialType = serialType.takeIf { !it.isNullOrBlank() },
)
}
composableWithDeepLink(Ext.Export::class) { navBackStack ->
val payload = navBackStack.toRoute<Ext.Export>()
val extensionId = payload.id
ExtensionExportScreen(id = extensionId)
composableWithDeepLink(Ext.Export) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
ExtensionExportScreen(id = extensionId.toString())
}
composableWithDeepLink(Ext.Import::class) { navBackStack ->
val payload = navBackStack.toRoute<Ext.Import>()
val uuid = payload.uuid
ExtensionImportScreen(payload.type, uuid)
composableWithDeepLink(Ext.Import) { navBackStack ->
val type = navBackStack.arguments?.getString("type")?.let { typeId ->
ExtensionImportScreenType.entries.firstOrNull { it.id == typeId }
} ?: ExtensionImportScreenType.EXT_ANY
val uuid = navBackStack.arguments?.getString("uuid")?.takeIf { it != "null" }
ExtensionImportScreen(type, uuid)
}
composableWithDeepLink(Ext.View::class) { navBackStack ->
val payload = navBackStack.toRoute<Ext.View>()
val extensionId = payload.id
ExtensionViewScreen(id = extensionId)
composableWithDeepLink(Ext.View) { navBackStack ->
val extensionId = navBackStack.arguments?.getString("id")
ExtensionViewScreen(id = extensionId.toString())
}
composableWithDeepLink(Ext.CheckUpdates::class) {
composableWithDeepLink(Ext.CheckUpdates) {
CheckUpdatesScreen()
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,21 +17,19 @@
package dev.patrickgold.florisboard.app.apptheme
import android.app.Activity
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.color.ColorMappings
import org.florisboard.lib.android.AndroidVersion
/*private val AmoledDarkColorPalette = darkColorScheme(
primary = Green500,
@@ -72,66 +70,170 @@ private val LightColorPalette = lightColorScheme(
*/
)*/
private val lightScheme = lightColorScheme(
primary = primaryLight,
onPrimary = onPrimaryLight,
primaryContainer = primaryContainerLight,
onPrimaryContainer = onPrimaryContainerLight,
secondary = secondaryLight,
onSecondary = onSecondaryLight,
secondaryContainer = secondaryContainerLight,
onSecondaryContainer = onSecondaryContainerLight,
tertiary = tertiaryLight,
onTertiary = onTertiaryLight,
tertiaryContainer = tertiaryContainerLight,
onTertiaryContainer = onTertiaryContainerLight,
error = errorLight,
onError = onErrorLight,
errorContainer = errorContainerLight,
onErrorContainer = onErrorContainerLight,
background = backgroundLight,
onBackground = onBackgroundLight,
surface = surfaceLight,
onSurface = onSurfaceLight,
surfaceVariant = surfaceVariantLight,
onSurfaceVariant = onSurfaceVariantLight,
outline = outlineLight,
outlineVariant = outlineVariantLight,
scrim = scrimLight,
inverseSurface = inverseSurfaceLight,
inverseOnSurface = inverseOnSurfaceLight,
inversePrimary = inversePrimaryLight,
surfaceDim = surfaceDimLight,
surfaceBright = surfaceBrightLight,
surfaceContainerLowest = surfaceContainerLowestLight,
surfaceContainerLow = surfaceContainerLowLight,
surfaceContainer = surfaceContainerLight,
surfaceContainerHigh = surfaceContainerHighLight,
surfaceContainerHighest = surfaceContainerHighestLight,
)
@Composable
fun getColorScheme(
context: Context,
theme: AppTheme,
): ColorScheme {
val prefs by FlorisPreferenceStore
val accentColor by prefs.other.accentColor.observeAsState()
val isDark = isSystemInDarkTheme()
private val darkScheme = darkColorScheme(
primary = primaryDark,
onPrimary = onPrimaryDark,
primaryContainer = primaryContainerDark,
onPrimaryContainer = onPrimaryContainerDark,
secondary = secondaryDark,
onSecondary = onSecondaryDark,
secondaryContainer = secondaryContainerDark,
onSecondaryContainer = onSecondaryContainerDark,
tertiary = tertiaryDark,
onTertiary = onTertiaryDark,
tertiaryContainer = tertiaryContainerDark,
onTertiaryContainer = onTertiaryContainerDark,
error = errorDark,
onError = onErrorDark,
errorContainer = errorContainerDark,
onErrorContainer = onErrorContainerDark,
background = backgroundDark,
onBackground = onBackgroundDark,
surface = surfaceDark,
onSurface = onSurfaceDark,
surfaceVariant = surfaceVariantDark,
onSurfaceVariant = onSurfaceVariantDark,
outline = outlineDark,
outlineVariant = outlineVariantDark,
scrim = scrimDark,
inverseSurface = inverseSurfaceDark,
inverseOnSurface = inverseOnSurfaceDark,
inversePrimary = inversePrimaryDark,
surfaceDim = surfaceDimDark,
surfaceBright = surfaceBrightDark,
surfaceContainerLowest = surfaceContainerLowestDark,
surfaceContainerLow = surfaceContainerLowDark,
surfaceContainer = surfaceContainerDark,
surfaceContainerHigh = surfaceContainerHighDark,
surfaceContainerHighest = surfaceContainerHighestDark,
)
return when (theme) {
AppTheme.AUTO -> {
if (isDark) {
ColorMappings.dynamicDarkColorScheme(context, accentColor)
} else {
ColorMappings.dynamicLightColorScheme(context, accentColor)
}
}
AppTheme.DARK -> {
ColorMappings.dynamicDarkColorScheme(context, accentColor)
}
AppTheme.LIGHT -> {
ColorMappings.dynamicLightColorScheme(context, accentColor)
}
AppTheme.AMOLED_DARK -> {
ColorMappings.dynamicDarkColorScheme(context, accentColor).amoled()
}
AppTheme.AUTO_AMOLED -> {
if (isDark) {
ColorMappings.dynamicDarkColorScheme(context, accentColor).amoled()
} else {
ColorMappings.dynamicLightColorScheme(context, accentColor)
}
}
}
}
fun ColorScheme.amoled(): ColorScheme {
return this.copy(background = Color.Black, surface = Color.Black)
}
private val amoledScheme = darkScheme.copy(
background = amoledDark,
surface = amoledDark
)
@Composable
fun FlorisAppTheme(
theme: AppTheme,
content: @Composable () -> Unit,
isMaterialYouAware: Boolean,
content: @Composable () -> Unit
) {
val colors = getColorScheme(
context = LocalContext.current,
theme = theme,
)
val colors = if (AndroidVersion.ATLEAST_API31_S) {
when (theme) {
AppTheme.AUTO -> when {
isMaterialYouAware -> when {
isSystemInDarkTheme() -> dynamicDarkColorScheme(LocalContext.current)
else -> dynamicLightColorScheme(LocalContext.current)
}
else -> {
when {
isSystemInDarkTheme() -> darkScheme
else -> lightScheme
}
}
}
AppTheme.AUTO_AMOLED -> when {
isMaterialYouAware -> when {
isSystemInDarkTheme() -> dynamicDarkColorScheme(LocalContext.current).copy(
background = amoledDark,
surface = amoledDark,
)
else -> dynamicLightColorScheme(LocalContext.current)
}
else -> {
when {
isSystemInDarkTheme() -> amoledScheme
else -> lightScheme
}
}
}
AppTheme.LIGHT -> when {
isMaterialYouAware -> dynamicLightColorScheme(LocalContext.current)
else -> lightScheme
}
AppTheme.DARK -> when {
isMaterialYouAware -> dynamicDarkColorScheme(LocalContext.current)
else -> darkScheme
}
AppTheme.AMOLED_DARK -> when {
isMaterialYouAware -> dynamicDarkColorScheme(LocalContext.current).copy(
background = amoledDark,
surface = amoledDark,
)
else -> amoledScheme
}
}
} else {
when (theme) {
AppTheme.AUTO -> when {
isSystemInDarkTheme() -> darkScheme
else -> lightScheme
}
AppTheme.AUTO_AMOLED -> when {
isSystemInDarkTheme() -> darkScheme
else -> lightScheme
}
AppTheme.LIGHT -> lightScheme
AppTheme.DARK -> darkScheme
AppTheme.AMOLED_DARK -> amoledScheme
}
}
val darkTheme =
theme == AppTheme.DARK
|| theme == AppTheme.AMOLED_DARK
|| (theme == AppTheme.AUTO && isSystemInDarkTheme())
|| (theme == AppTheme.AUTO_AMOLED && isSystemInDarkTheme())
|| theme == AppTheme.AMOLED_DARK
|| (theme == AppTheme.AUTO && isSystemInDarkTheme())
|| (theme == AppTheme.AUTO_AMOLED && isSystemInDarkTheme())
val view = LocalView.current
if (!view.isInEditMode) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,11 +32,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.compose.FlorisIconButton
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import java.util.Locale
@@ -66,9 +66,9 @@ fun AndroidLocalesScreen() = FlorisScreen {
out.appendLine()
}
}
context.showLongToastSync("Exported available system locales to \"${txtFile.path}\"")
context.showLongToast("Exported available system locales to \"${txtFile.path}\"")
} catch (e: Exception) {
context.showLongToastSync(
context.showLongToast(
R.string.error__snackbar_message_template,
"error_message" to e.message.toString(),
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,11 +27,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import org.florisboard.lib.android.AndroidSettings
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import org.florisboard.lib.android.AndroidSettings
import org.florisboard.lib.compose.stringRes
@Composable
fun AndroidSettingsScreen(name: String?) = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,70 +39,47 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.keyboard.CachedLayout
import dev.patrickgold.florisboard.ime.keyboard.DebugLayoutComputationResult
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.observeAsNonNullState
import dev.patrickgold.florisboard.nlpManager
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.AndroidVersion
import java.text.SimpleDateFormat
import java.util.*
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.snygg.SnyggMissingSchemaException
private val CardBackground = Color.Black.copy(0.6f)
private val DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", FlorisLocale.default().base)
@Composable
fun DevtoolsOverlay(modifier: Modifier = Modifier) {
val context = LocalContext.current
val prefs by FlorisPreferenceStore
val appContext by context.appContext()
val keyboardManager by context.keyboardManager()
val themeManager by context.themeManager()
val prefs by florisPreferenceModel()
val devtoolsEnabled by prefs.devtools.enabled.observeAsState()
val showPrimaryClip by prefs.devtools.showPrimaryClip.observeAsState()
val showInputStateOverlay by prefs.devtools.showInputStateOverlay.observeAsState()
val showSpellingOverlay by prefs.devtools.showSpellingOverlay.observeAsState()
val showInlineAutofillOverlay by prefs.devtools.showInlineAutofillOverlay.observeAsState()
val prefsLoaded by appContext.preferenceStoreLoaded.collectAsState()
val debugLayoutResult by keyboardManager.layoutManager.debugLayoutComputationResultFlow.collectAsState()
val themeInfo by themeManager.activeThemeInfo.collectAsState()
CompositionLocalProvider(
LocalContentColor provides Color.White,
LocalLayoutDirection provides LayoutDirection.Ltr,
) {
Column(modifier = modifier) {
if (devtoolsEnabled && showPrimaryClip) {
if (showPrimaryClip) {
DevtoolsClipboardOverlay()
}
if (devtoolsEnabled && showInputStateOverlay) {
if (showInputStateOverlay) {
DevtoolsInputStateOverlay()
}
if (debugLayoutResult?.allLayoutsSuccess() == false) {
DevtoolsLastLayoutComputationOverlay(debugLayoutResult)
}
if (devtoolsEnabled && showSpellingOverlay) {
if (showSpellingOverlay) {
DevtoolsSpellingOverlay()
}
if (devtoolsEnabled && showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
if (showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
DevtoolsInlineAutofillOverlay()
}
val loadFailure = themeInfo.loadFailure
if (loadFailure != null && prefsLoaded) {
DevtoolsStylesheetFailedToLoadOverlay(loadFailure)
}
}
}
}
@@ -148,34 +125,6 @@ private fun DevtoolsInputStateOverlay() {
}
}
@Composable
private fun DevtoolsLastLayoutComputationOverlay(debugLayoutResult: DebugLayoutComputationResult?) {
@Composable
fun PrintResult(result: Result<CachedLayout?>) {
if (result.isSuccess) {
DevtoolsText(text = "loaded: ${result.getOrNull()?.name}")
} else {
DevtoolsText(text = "error: ${result.exceptionOrNull()}")
}
}
DevtoolsOverlayBox(title = "Last layout computation") {
if (debugLayoutResult == null) {
DevtoolsText(text = "No layout computation result available.")
return@DevtoolsOverlayBox
}
DevtoolsSubGroup(title = "main") {
PrintResult(debugLayoutResult.main)
}
DevtoolsSubGroup(title = "mod") {
PrintResult(debugLayoutResult.mod)
}
DevtoolsSubGroup(title = "ext") {
PrintResult(debugLayoutResult.ext)
}
}
}
@Composable
private fun DevtoolsSpellingOverlay() {
val context = LocalContext.current
@@ -237,39 +186,6 @@ private fun DevtoolsInlineAutofillOverlay() {
}
}
@Composable
private fun DevtoolsStylesheetFailedToLoadOverlay(loadFailure: ThemeManager.LoadFailure) {
DevtoolsOverlayBox(title = "Failed to load stylesheet, fell back to base style") {
DevtoolsSubGroup(title = "Extension") {
DevtoolsText(text = "id: ${loadFailure.extension.id}")
DevtoolsText(text = "title: ${loadFailure.extension.title}")
DevtoolsText(text = "version: ${loadFailure.extension.version}")
}
DevtoolsSubGroup(title = "Component") {
DevtoolsText(text = "id: ${loadFailure.component.id}")
DevtoolsText(text = "label: ${loadFailure.component.label}")
DevtoolsText(text = "path: ${loadFailure.component.stylesheetPath()}")
}
val cause = loadFailure.cause
DevtoolsSubGroup(title = "Cause") {
DevtoolsText(text = "${cause.message}")
}
if (cause is SnyggMissingSchemaException) {
DevtoolsSubGroup(title = "Explanation") {
DevtoolsText(
text = """
It appears youre trying to load a theme designed for FlorisBoard v0.4 (Snygg v1), which isnt compatible with the latest release using Snygg v2.
If you are the theme author, please update your theme to support Snygg v2.
If youre a user, please update your theme via the Addons Store. If an updated version isnt available yet, please select one of the built-in themes during this transition period.
""".trimIndent()
)
}
}
}
}
@Composable
private fun DevtoolsOverlayBox(
title: String,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
@@ -28,19 +27,16 @@ import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.dictionary.FlorisUserDictionaryDatabase
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionArrangement
import org.florisboard.lib.android.AndroidSettings
import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.android.AndroidSettings
import kotlinx.coroutines.launch
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.android.showLongToastSync
class DebugOnPurposeCrashException : Exception(
"Success! The app crashed purposely to display this beautiful screen we all love :)"
@@ -54,7 +50,6 @@ fun DevtoolsScreen() = FlorisScreen {
val context = LocalContext.current
val navController = LocalNavController.current
val extensionManager by context.extensionManager()
val scope = rememberCoroutineScope()
val (showDialog, setShowDialog) = remember { mutableStateOf(false) }
@@ -66,6 +61,12 @@ fun DevtoolsScreen() = FlorisScreen {
)
PreferenceGroup(title = stringRes(R.string.devtools__title)) {
SwitchPreference(
prefs.devtools.showHeapMemoryStats,
title = stringRes(R.string.devtools__show_heap_memory_stats__label),
summary = stringRes(R.string.devtools__show_heap_memory_stats__summary),
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
SwitchPreference(
prefs.devtools.showPrimaryClip,
title = stringRes(R.string.devtools__show_primary_clip__label),
@@ -109,21 +110,10 @@ fun DevtoolsScreen() = FlorisScreen {
onClick = { setShowDialog(true) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.devtools__reset_quick_actions_to_default__label),
summary = stringRes(R.string.devtools__reset_quick_actions_to_default__summary),
onClick = {
scope.launch {
prefs.smartbar.actionArrangement.set(QuickActionArrangement.Default)
}
context.showLongToastSync(R.string.devtools__reset_quick_actions_to_default__toast_success)
},
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.devtools__reset_flag__label, "flag_name" to "isImeSetUp"),
summary = stringRes(R.string.devtools__reset_flag_is_ime_set_up__summary),
onClick = { scope.launch { prefs.internal.isImeSetUp.set(false) } },
onClick = { prefs.internal.isImeSetUp.set(false) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
@@ -138,13 +128,6 @@ fun DevtoolsScreen() = FlorisScreen {
onClick = { navController.navigate(Routes.Devtools.ExportDebugLog) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
SwitchPreference(
prefs.glide.enabled,
title = "prefs.glide.enabled (debug)",
summaryOn = "This impacts your performance and may trigger the all keys invisible bug!",
summaryOff = "Recommended to keep this off!",
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.devtools__group_android__title)) {
@@ -205,14 +188,14 @@ fun DevtoolsScreen() = FlorisScreen {
title = "keyboardExtensions",
summary = extensionManager.keyboardExtensions.internalModuleDir.absolutePath,
onClick = {
context.showLongToastSync(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
context.showLongToast(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
},
)
Preference(
title = "themes",
summary = extensionManager.themes.internalModuleDir.absolutePath,
onClick = {
context.showLongToastSync(extensionManager.themes.internalModuleDir.absolutePath)
context.showLongToast(extensionManager.themes.internalModuleDir.absolutePath)
},
)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2025 The FlorisBoard Contributors
* Copyright (C) 2022 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,16 +38,15 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.lib.compose.FlorisButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.devtools.Devtools
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.compose.FlorisButton
import org.florisboard.lib.compose.florisHorizontalScroll
import org.florisboard.lib.compose.florisScrollbar
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.android.showShortToastSync
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI
@Composable
@@ -55,7 +54,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
title = stringRes(R.string.devtools__debuglog__title)
scrollable = false
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val clipboardManager by context.clipboardManager()
@@ -75,7 +74,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
modifier = Modifier,
text = stringRes(R.string.devtools__debuglog__copy_log),
@@ -84,7 +83,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(formattedDebugLog!!.joinToString("\n"))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
text = stringRes(R.string.devtools__debuglog__copy_for_github),
enabled = debugLog != null,

View File

@@ -1,19 +1,3 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.layout.Row
@@ -21,7 +5,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Input
import androidx.compose.material.icons.filled.Shop
import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material3.MaterialTheme
@@ -30,55 +13,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.generateUpdateUrl
import dev.patrickgold.florisboard.lib.util.launchUrl
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.FlorisTextButton
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.kotlin.curlyFormat
@Composable
fun ImportExtensionBox(navController: NavController) {
val context = LocalContext.current
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
Text(
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 4.dp),
text = stringRes(id = R.string.ext__home__info),
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
FlorisTextButton(
onClick = {
context.launchUrl("https://${BuildConfig.FLADDONS_STORE_URL}/")
},
icon = Icons.Default.Shop,
text = stringRes(id = R.string.ext__home__visit_store),
)
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, null))
},
icon = Icons.AutoMirrored.Filled.Input,
text = stringRes(R.string.action__import),
)
}
}
}
@Composable
fun UpdateBox(extensionIndex: List<Extension>) {
val context = LocalContext.current

View File

@@ -1,19 +1,3 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import androidx.compose.runtime.Composable
@@ -21,7 +5,7 @@ import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import org.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.compose.stringRes
@Composable
fun CheckUpdatesScreen() = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2025 The FlorisBoard Contributors
* Copyright (C) 2022 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,13 +40,13 @@ import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.nlp.LanguagePackComponent
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.ExtensionComponent
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
import org.florisboard.lib.compose.FlorisIconButton
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.FlorisTextButton
import org.florisboard.lib.compose.stringRes
@Composable
fun ExtensionComponentNoneFoundView() {
@@ -81,11 +81,14 @@ fun ExtensionComponentView(
when (component) {
is ThemeExtensionComponent -> {
val text = remember(
component.authors, component.isNightTheme, component.stylesheetPath(),
component.authors, component.isNightTheme, component.isBorderless,
component.isMaterialYouAware, component.stylesheetPath(),
) {
buildString {
appendLine("authors = ${component.authors}")
appendLine("isNightTheme = ${component.isNightTheme}")
appendLine("isBorderless = ${component.isBorderless}")
appendLine("isMaterialYouAware = ${component.isMaterialYouAware}")
append("stylesheetPath = ${component.stylesheetPath()}")
}
}

View File

@@ -1,292 +0,0 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import android.provider.OpenableColumns
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Photo
import androidx.compose.material.icons.filled.TextFields
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import java.io.File
import java.util.*
import org.florisboard.lib.android.query
import org.florisboard.lib.android.readToFile
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.compose.FlorisIconButton
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.kotlin.io.parentDir
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.kotlin.mimeTypeFilterOf
const val FONTS = "fonts"
const val IMAGES = "images"
val MIME_TYPES = mapOf(
FONTS to mimeTypeFilterOf(
// Source: https://www.alienfactory.co.uk/articles/mime-types-for-web-fonts-in-bedsheet#mimeTypes
"font/*",
"application/font-*",
"application/x-font-*",
"application/vnd.ms-fontobject",
),
IMAGES to mimeTypeFilterOf(
"image/*",
),
)
@Composable
fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = FlorisScreen {
title = stringRes(R.string.ext__editor__files__title)
fun handleBackPress() {
workspace.currentAction = null
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = Icons.Default.Close,
)
}
content {
val context = LocalContext.current
var version by rememberSaveable { mutableIntStateOf(0) }
val fontFiles = remember(version) {
workspace.extDir.subDir(FONTS).listFiles { it.isFile }.orEmpty().asList()
}
val imageFiles = remember(version) {
workspace.extDir.subDir(IMAGES).listFiles { it.isFile }.orEmpty().asList()
}
var currentImportDest by remember { mutableStateOf<String?>(null) }
var currentImportResult by remember { mutableStateOf<Result<Pair<File, String>>?>(null) }
val importLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri ->
currentImportResult = runCatching {
checkNotNull(uri) { "" }
val tempFile = context.cacheDir.subFile("temp_${UUID.randomUUID()}")
context.contentResolver.readToFile(uri, tempFile)
val mimeType = context.contentResolver.getType(uri)
val filter = MIME_TYPES[currentImportDest!!]!!
check(filter.matches(mimeType)) {
"Given file mime type was '$mimeType', expected one of ${filter.types}"
}
val fileName = context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME)).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return@use null
val name = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.getString(name)
}
tempFile to fileName.orEmpty()
}
},
)
LaunchedEffect(currentImportResult) {
val message = currentImportResult?.exceptionOrNull()?.message
if (!message.isNullOrBlank()) {
context.showLongToast(message)
}
}
BackHandler {
handleBackPress()
}
@Composable
fun FileList(title: String, icon: ImageVector, files: List<File>, onAdd: () -> Unit) {
var dialogFile by remember { mutableStateOf<File?>(null) }
ListItem(
headlineContent = {
Text(
text = title,
color = MaterialTheme.colorScheme.secondary,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
leadingContent = {
Spacer(modifier = Modifier.width(24.dp))
},
trailingContent = {
IconButton(
onClick = onAdd,
) {
Icon(Icons.Default.Add, null)
}
},
)
for (file in files) {
Preference(
onClick = {
dialogFile = file
},
icon = icon,
title = file.name,
)
}
dialogFile?.let { file ->
var fileNameInput by rememberSaveable { mutableStateOf(file.name) }
JetPrefAlertDialog(
title = stringRes(R.string.general__properties),
confirmLabel = stringRes(R.string.action__apply),
dismissLabel = stringRes(R.string.action__cancel),
neutralLabel = stringRes(R.string.action__delete),
allowOutsideDismissal = true,
onNeutral = {
if (file.delete()) {
context.showShortToastSync("Successfully deleted")
} else {
context.showShortToastSync("Failed to delete")
}
dialogFile = null
version++
},
onConfirm = {
val newFile = file.parentFile!!.subFile(fileNameInput).canonicalFile
if (newFile.parentFile != file.canonicalFile.parentFile) {
context.showLongToastSync("Invalid file name!")
return@JetPrefAlertDialog
}
if (newFile.exists()) {
context.showShortToastSync("Filename already exists.")
return@JetPrefAlertDialog
}
val success = file.renameTo(newFile)
if (success) {
context.showShortToastSync("Successfully renamed")
} else {
context.showShortToastSync("Failed to rename the file.")
}
dialogFile = null
version++
},
onDismiss = {
dialogFile = null
},
) {
JetPrefTextField(
labelText = stringRes(R.string.general__file_name),
value = fileNameInput,
onValueChange = { fileNameInput = it },
singleLine = true,
)
}
}
}
FileList(
title = stringRes(R.string.ext__editor__files__type_fonts),
icon = Icons.Default.TextFields,
files = fontFiles,
) {
currentImportDest = FONTS
importLauncher.launch("*/*")
}
FileList(
title = stringRes(R.string.ext__editor__files__type_images),
icon = Icons.Default.Photo,
files = imageFiles,
) {
currentImportDest = IMAGES
importLauncher.launch("*/*")
}
val dest = currentImportDest
val result = currentImportResult?.getOrNull()
if (dest != null && result != null) {
var fileNameInput by rememberSaveable { mutableStateOf(result.second) }
JetPrefAlertDialog(
title = stringRes(R.string.action__import_file),
confirmLabel = stringRes(R.string.action__add),
onConfirm = {
val fileName = fileNameInput.trim()
val dir = workspace.extDir.subDir(dest)
dir.mkdirs()
val file = dir.subFile(fileName)
if (file.parentDir != workspace.extDir.subDir(dest)) {
context.showShortToastSync("Invalid file name")
} else if (file.exists()) {
context.showShortToastSync("File already exists")
} else {
val tempFile = result.first
if (!tempFile.renameTo(file)) {
context.showShortToastSync("Failed to rename file")
tempFile.delete()
}
currentImportDest = null
currentImportResult = null
version++
}
},
dismissLabel = stringRes(R.string.action__cancel),
onDismiss = {
val tempFile = result.first
tempFile.delete()
currentImportDest = null
currentImportResult = null
},
) {
JetPrefTextField(
value = fileNameInput,
onValueChange = { fileNameInput = it },
singleLine = true,
)
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import androidx.compose.material.icons.automirrored.outlined.LibraryBooks
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Code
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -42,15 +43,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.settings.advanced.RadioListItem
import dev.patrickgold.florisboard.app.settings.theme.DialogProperty
import dev.patrickgold.florisboard.app.settings.theme.PrettyPrintConfig
import dev.patrickgold.florisboard.app.settings.theme.ThemeEditorScreen
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.extensionManager
@@ -62,9 +60,15 @@ import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentImpl
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionEditor
import dev.patrickgold.florisboard.lib.ValidationResult
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
import dev.patrickgold.florisboard.lib.compose.FlorisInfoCard
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisUnsavedChangesDialog
import dev.patrickgold.florisboard.lib.compose.Validation
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.ExtensionComponent
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
@@ -79,22 +83,15 @@ import dev.patrickgold.florisboard.lib.ext.validate
import dev.patrickgold.florisboard.lib.io.FlorisRef
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.florisboard.lib.rememberValidationResult
import org.florisboard.lib.snygg.SnyggStylesheetJsonConfig
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.vectorResource
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import java.util.*
import org.florisboard.lib.compose.FlorisButtonBar
import org.florisboard.lib.compose.FlorisIconButton
import org.florisboard.lib.compose.FlorisInfoCard
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.kotlin.io.writeJson
import java.util.UUID
import kotlin.reflect.KClass
private val TextFieldVerticalPadding = 8.dp
@@ -200,7 +197,7 @@ private fun ExtensionEditScreenSheetSwitcher(
ManageDependenciesScreen(workspace)
}
is EditorAction.ManageFiles -> {
ExtensionEditFilesScreen(workspace)
ManageFilesScreen(workspace)
}
is EditorAction.CreateComponent<*> -> {
CreateComponentScreen(workspace, action.type)
@@ -264,33 +261,17 @@ private fun EditScreen(
return
}
val manifest = extEditor.build()
workspace.saverDir.deleteContentsRecursively()
val manifestFile = workspace.saverDir.subFile(ExtensionDefaults.MANIFEST_FILE_NAME)
manifestFile.writeJson(manifest, ExtensionJsonConfig)
when (extEditor) {
is ThemeExtensionEditor -> {
// TODO: this is hacky
val fonts = workspace.extDir.subDir("fonts")
if (fonts.exists()) {
fonts.copyRecursively(workspace.saverDir.subDir("fonts"), overwrite = true)
}
val images = workspace.extDir.subDir("images")
if (images.exists()) {
images.copyRecursively(workspace.saverDir.subDir("images"), overwrite = true)
}
for (theme in extEditor.themes) {
val stylesheetFile = workspace.saverDir.subFile(theme.stylesheetPath())
stylesheetFile.parentFile?.mkdirs()
val stylesheetEditor = theme.stylesheetEditor
if (stylesheetEditor != null) {
runCatching {
val stylesheet = stylesheetEditor.build().toJson(PrettyPrintConfig).getOrThrow()
stylesheetFile.writeText(stylesheet)
}.onFailure {
// TODO: better error handling
context.showLongToastSync(it.message.toString())
return
}
val stylesheet = stylesheetEditor.build()
stylesheetFile.writeJson(stylesheet, SnyggStylesheetJsonConfig)
} else {
val unmodifiedStylesheetFile = workspace.extDir.subFile(theme.stylesheetPath())
if (unmodifiedStylesheetFile.exists()) {
@@ -353,7 +334,7 @@ private fun EditScreen(
)
Preference(
onClick = { workspace.currentAction = EditorAction.ManageFiles },
icon = ImageVector.vectorResource(R.drawable.ic_file_blank),
icon = vectorResource(R.drawable.ic_file_blank),
title = stringRes(R.string.ext__editor__files__title),
)
}
@@ -601,6 +582,35 @@ private fun ManageDependenciesScreen(workspace: CacheManager.ExtEditorWorkspace<
}
}
@Composable
private fun ManageFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = FlorisScreen {
title = stringRes(R.string.ext__editor__files__title)
fun handleBackPress() {
workspace.currentAction = null
}
navigationIcon {
FlorisIconButton(
onClick = { handleBackPress() },
icon = Icons.Default.Close,
)
}
content {
BackHandler {
handleBackPress()
}
FlorisInfoCard(
modifier = Modifier.padding(all = 8.dp),
text = """
Managing archive files is currently not supported.
""".trimIndent().replace('\n', ' '),
)
}
}
private enum class CreateFrom {
EMPTY,
EXISTING;
@@ -628,7 +638,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
for (theme in editor.themes) {
put(ExtensionComponentName(extId, theme.id), theme)
}
for ((componentName, theme) in themeManager.indexedThemeConfigs.value.first) {
for ((componentName, theme) in themeManager.indexedThemeConfigs.value ?: emptyMap()) {
if (componentName.extensionId != extId) {
put(componentName, theme)
}
@@ -666,7 +676,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
when (createFrom) {
CreateFrom.EMPTY -> {
if (editor.themes.any { it.id == newId.trim() }) {
context.showLongToastSync("A theme with this ID already exists!")
context.showLongToast("A theme with this ID already exists!")
} else {
val componentEditor = ThemeExtensionComponentEditor(
id = newId.trim(),
@@ -693,14 +703,15 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
val component = editor.themes.find { it.id == componentName.componentId } ?: return
val componentEditor = component.let { c ->
ThemeExtensionComponentEditor(
componentId, c.label, c.authors, c.isNightTheme, stylesheetPath = "",
componentId, c.label, c.authors, c.isNightTheme, c.isBorderless,
c.isMaterialYouAware, stylesheetPath = "",
).also { it.stylesheetEditor = c.stylesheetEditor }
}
if (componentEditor.stylesheetEditor != null) {
val stylesheet = componentEditor.stylesheetEditor!!.build()
val stylesheetFile = workspace.extDir.subFile(componentEditor.stylesheetPath())
stylesheetFile.parentFile?.mkdirs()
val stylesheet = componentEditor.stylesheetEditor!!.build().toJson(PrettyPrintConfig).getOrThrow()
stylesheetFile.writeText(stylesheet)
stylesheetFile.writeJson(stylesheet, SnyggStylesheetJsonConfig)
componentEditor.stylesheetEditor = null
} else {
val srcStylesheetFile = workspace.extDir.subFile(component.stylesheetPath())
@@ -710,7 +721,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
}
editor.themes.add(componentEditor)
} else {
val component = themeManager.indexedThemeConfigs.value.first.get(componentName) ?: return
val component = themeManager.indexedThemeConfigs.value?.get(componentName) ?: return
val componentEditor = (component as? ThemeExtensionComponentImpl)?.edit() ?: return
componentEditor.id = componentId
componentEditor.stylesheetPath = ""
@@ -802,37 +813,36 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__meta__id),
) {
JetPrefTextField(
modifier = Modifier.fillMaxWidth(),
FlorisOutlinedTextField(
value = newId,
onValueChange = { newId = it },
singleLine = true,
showValidationError = showValidationErrors,
validationResult = newIdValidation,
)
Validation(showValidationErrors, newIdValidation)
}
DialogProperty(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__meta__label),
) {
JetPrefTextField(
modifier = Modifier.fillMaxWidth(),
FlorisOutlinedTextField(
value = newLabel,
onValueChange = { newLabel = it },
singleLine = true,
showValidationError = showValidationErrors,
validationResult = newLabelValidation,
)
Validation(showValidationErrors, newLabelValidation)
}
DialogProperty(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringRes(R.string.ext__meta__authors),
) {
JetPrefTextField(
modifier = Modifier.fillMaxWidth(),
FlorisOutlinedTextField(
value = newAuthors,
onValueChange = { newAuthors = it },
showValidationError = showValidationErrors,
validationResult = newAuthorsValidation,
)
Validation(showValidationErrors, newAuthorsValidation)
}
}
}
@@ -850,6 +860,7 @@ private fun EditorSheetTextField(
showValidationError: Boolean = false,
validationResult: ValidationResult? = null,
) {
val borderColor = MaterialTheme.colorScheme.outline
Column(modifier = Modifier.padding(vertical = TextFieldVerticalPadding)) {
Row(
modifier = Modifier
@@ -869,13 +880,18 @@ private fun EditorSheetTextField(
)
}
}
JetPrefTextField(
FlorisOutlinedTextField(
modifier = modifier.fillMaxWidth(),
enabled = enabled,
value = value,
onValueChange = onValueChange,
singleLine = singleLine,
showValidationError = showValidationError,
validationResult = validationResult,
colors = OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = borderColor,
disabledBorderColor = borderColor,
)
)
Validation(showValidationError, validationResult)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.ExtensionDefaults
import org.florisboard.lib.android.showLongToastSync
@Composable
fun ExtensionExportScreen(id: String) {
@@ -62,9 +61,9 @@ private fun ExportScreen(ext: Extension) = FlorisScreen {
return@rememberLauncherForActivityResult
}
runCatching { extensionManager.export(ext, uri) }.onSuccess {
context.showLongToastSync(R.string.ext__export__success)
context.showLongToast(R.string.ext__export__success)
}.onFailure { error ->
context.showLongToastSync(R.string.ext__export__failure, "error_message" to error.localizedMessage)
context.showLongToast(R.string.ext__export__failure, "error_message" to error.localizedMessage)
}
navController.popBackStack()
},

View File

@@ -1,34 +1,33 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.ext
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Input
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Shop
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.util.launchUrl
import dev.patrickgold.jetpref.datastore.ui.Preference
import org.florisboard.lib.compose.stringRes
@Composable
fun ExtensionHomeScreen() = FlorisScreen {
@@ -41,7 +40,36 @@ fun ExtensionHomeScreen() = FlorisScreen {
val extensionIndex = extensionManager.combinedExtensionList()
content {
ImportExtensionBox(navController)
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
) {
Text(
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 4.dp),
text = stringRes(id = R.string.ext__home__info),
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
FlorisTextButton(
onClick = {
context.launchUrl("https://${BuildConfig.FLADDONS_STORE_URL}/")
},
icon = Icons.Default.Shop,
text = stringRes(id = R.string.ext__home__visit_store),
)
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.Import(ExtensionImportScreenType.EXT_ANY, null))
},
icon = Icons.AutoMirrored.Filled.Input,
text = stringRes(R.string.action__import),
)
}
}
UpdateBox(extensionIndex = extensionIndex)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,17 +50,17 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyboardExtension
import dev.patrickgold.florisboard.ime.nlp.LanguagePackExtension
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
import dev.patrickgold.florisboard.lib.NATIVE_NULLPTR
import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisBulletSpacer
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.io.FileRegistry
import org.florisboard.lib.compose.FlorisBulletSpacer
import org.florisboard.lib.compose.FlorisButtonBar
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.FlorisOutlinedButton
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.florisHorizontalScroll
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.resultOk
enum class ExtensionImportScreenType(
@@ -188,10 +188,10 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
}
}.onSuccess {
workspace.close()
context.showLongToastSync(R.string.ext__import__success)
context.showLongToast(R.string.ext__import__success)
navController.popBackStack()
}.onFailure { error ->
context.showLongToastSync(R.string.ext__import__failure, "error_message" to error.localizedMessage)
context.showLongToast(R.string.ext__import__failure, "error_message" to error.localizedMessage)
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.florisboard.lib.compose.FlorisChip
import dev.patrickgold.florisboard.lib.compose.FlorisChip
@Composable
fun ExtensionKeywordChip(

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024-2025 The FlorisBoard Contributors
* Copyright (C) 2024 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,10 @@
package dev.patrickgold.florisboard.app.ext
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
@@ -38,13 +33,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import dev.patrickgold.florisboard.R
@@ -52,14 +42,13 @@ import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisTextButton
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.observeAsNonNullState
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.FlorisTextButton
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.florisScrollbar
import org.florisboard.lib.compose.stringRes
enum class ExtensionListScreenType(
val id: String,
@@ -91,69 +80,49 @@ enum class ExtensionListScreenType(
fun ExtensionListScreen(type: ExtensionListScreenType, showUpdate: Boolean) = FlorisScreen {
title = stringRes(type.titleResId)
previewFieldVisible = false
scrollable = false
val context = LocalContext.current
val navController = LocalNavController.current
val extensionManager by context.extensionManager()
val extensionIndex by type.getExtensionIndex(extensionManager).observeAsNonNullState()
var fabHeight by remember {
mutableStateOf(0)
}
val fabHeightDp = with(LocalDensity.current) { fabHeight.toDp()+16.dp }
val listState = rememberLazyListState()
content {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.florisScrollbar(state = listState, isVertical = true),
state = listState,
contentPadding = PaddingValues(bottom = fabHeightDp),
) {
if (showUpdate) {
item {
ImportExtensionBox(navController)
}
item {
UpdateBox(extensionIndex = extensionIndex)
}
}
items(extensionIndex) { ext ->
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
title = ext.meta.title,
subtitle = ext.meta.id,
if (showUpdate) {
UpdateBox(extensionIndex = extensionIndex)
}
for (ext in extensionIndex) {
FlorisOutlinedBox(
modifier = Modifier.defaultFlorisOutlinedBox(),
title = ext.meta.title,
subtitle = ext.meta.id,
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
text = ext.meta.description ?: "",
style = MaterialTheme.typography.bodySmall,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
text = ext.meta.description ?: "",
style = MaterialTheme.typography.bodySmall,
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.View(ext.meta.id))
},
icon = Icons.Outlined.Info,
text = stringRes(id = R.string.ext__list__view_details),//stringRes(R.string.action__add),
colors = ButtonDefaults.textButtonColors(),
)
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.Edit(ext.meta.id))
},
icon = Icons.Default.Edit,
text = stringRes(R.string.action__edit),
enabled = extensionManager.canDelete(ext),
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp),
) {
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.View(ext.meta.id))
},
icon = Icons.Outlined.Info,
text = stringRes(id = R.string.ext__list__view_details),//stringRes(R.string.action__add),
colors = ButtonDefaults.textButtonColors(),
)
Spacer(modifier = Modifier.weight(1f))
FlorisTextButton(
onClick = {
navController.navigate(Routes.Ext.Edit(ext.meta.id))
},
icon = Icons.Default.Edit,
text = stringRes(R.string.action__edit),
enabled = extensionManager.canDelete(ext),
)
}
}
}
}
@@ -173,9 +142,6 @@ fun ExtensionListScreen(type: ExtensionListScreenType, showUpdate: Boolean) = Fl
text = stringRes(id = R.string.ext__editor__title_create_any),
)
},
modifier = Modifier.onGloballyPositioned {
fabHeight = it.size.height
},
shape = FloatingActionButtonDefaults.extendedFabShape,
onClick = { type.launchExtensionCreate.invoke(navController) },
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,10 +30,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.lib.compose.FlorisChip
import dev.patrickgold.florisboard.lib.ext.ExtensionMaintainer
import dev.patrickgold.florisboard.lib.util.launchUrl
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import org.florisboard.lib.compose.FlorisChip
@Composable
fun ExtensionMaintainerChip(

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import org.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.compose.stringRes
@Composable
internal fun ExtensionNotFoundScreen(id: String) = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,17 +51,17 @@ import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.nlp.LanguagePackExtension
import dev.patrickgold.florisboard.ime.theme.ThemeExtension
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentImpl
import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisConfirmDeleteDialog
import dev.patrickgold.florisboard.lib.compose.FlorisHyperlinkText
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.ExtensionMaintainer
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
import dev.patrickgold.florisboard.lib.io.FlorisRef
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.compose.FlorisOutlinedButton
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.stringRes
@Composable
fun ExtensionViewScreen(id: String) {
@@ -202,7 +202,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
}.onSuccess {
navController.popBackStack()
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
R.string.error__snackbar_message,
"error_message" to error.localizedMessage,
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,13 +37,13 @@ import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.lib.compose.FlorisErrorCard
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisWarningCard
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.Preference
import org.florisboard.lib.compose.FlorisErrorCard
import org.florisboard.lib.compose.FlorisWarningCard
import org.florisboard.lib.compose.stringRes
@Composable
fun HomeScreen() = FlorisScreen {
@@ -152,8 +152,8 @@ fun HomeScreen() = FlorisScreen {
)
Preference(
icon = Icons.Outlined.Build,
title = stringRes(R.string.settings__other__title),
onClick = { navController.navigate(Routes.Settings.Other) },
title = stringRes(R.string.settings__advanced__title),
onClick = { navController.navigate(Routes.Settings.Advanced) },
)
Preference(
icon = Icons.Outlined.Info,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,12 +41,12 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.util.launchUrl
import dev.patrickgold.jetpref.datastore.ui.Preference
import org.florisboard.lib.android.stringRes
import org.florisboard.lib.compose.FlorisCanvasIcon
import org.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.compose.FlorisCanvasIcon
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.Preference
@Composable
fun AboutScreen() = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,11 +29,11 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
import dev.patrickgold.florisboard.lib.compose.florisVerticalScroll
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.io.FlorisRef
import dev.patrickgold.florisboard.lib.io.loadTextAsset
import org.florisboard.lib.compose.florisHorizontalScroll
import org.florisboard.lib.compose.florisVerticalScroll
import org.florisboard.lib.compose.stringRes
@Composable
fun ProjectLicenseScreen() = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,13 +21,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors
import com.mikepenz.aboutlibraries.ui.compose.m3.LibraryDefaults
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import org.florisboard.lib.compose.florisScrollbar
import org.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.compose.florisScrollbar
import dev.patrickgold.florisboard.lib.compose.stringRes
@Composable
fun ThirdPartyLicensesScreen() = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,73 +19,58 @@ package dev.patrickgold.florisboard.app.settings.advanced
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Adb
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.FormatColorFill
import androidx.compose.material.icons.filled.FormatPaint
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Preview
import androidx.compose.material.icons.filled.SettingsBackupRestore
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.keyboard.IncognitoMode
import dev.patrickgold.florisboard.lib.FlorisLocale
import org.florisboard.lib.android.AndroidVersion
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.ColorPickerPreference
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
import org.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.vectorResource
@Composable
fun OtherScreen() = FlorisScreen {
title = stringRes(R.string.settings__other__title)
fun AdvancedScreen() = FlorisScreen {
title = stringRes(R.string.settings__advanced__title)
previewFieldVisible = false
val navController = LocalNavController.current
val context = LocalContext.current
content {
ListPreference(
prefs.other.settingsTheme,
prefs.advanced.settingsTheme,
icon = Icons.Default.Palette,
title = stringRes(R.string.pref__other__settings_theme__label),
title = stringRes(R.string.pref__advanced__settings_theme__label),
entries = enumDisplayEntriesOf(AppTheme::class),
)
ColorPickerPreference(
pref = prefs.other.accentColor,
title = stringRes(R.string.pref__other__settings_accent_color__label),
defaultValueLabel = stringRes(R.string.action__default),
icon = Icons.Default.FormatColorFill,
defaultColors = ColorMappings.colors,
showAlphaSlider = false,
enableAdvancedLayout = false,
colorOverride = {
if (it.isMaterialYou(context)) {
Color.Unspecified
} else {
it
}
}
SwitchPreference(
pref = prefs.advanced.useMaterialYou,
icon = Icons.Default.FormatPaint,
title = stringRes(R.string.pref__advanced__settings_material_you__label),
visibleIf = {
AndroidVersion.ATLEAST_API31_S
},
)
ListPreference(
prefs.other.settingsLanguage,
prefs.advanced.settingsLanguage,
icon = Icons.Default.Language,
title = stringRes(R.string.pref__other__settings_language__label),
title = stringRes(R.string.pref__advanced__settings_language__label),
entries = listPrefEntries {
listOf(
"auto",
@@ -147,19 +132,20 @@ fun OtherScreen() = FlorisScreen {
}
)
SwitchPreference(
prefs.other.showAppIcon,
prefs.advanced.showAppIcon,
icon = Icons.Default.Preview,
title = stringRes(R.string.pref__other__show_app_icon__label),
title = stringRes(R.string.pref__advanced__show_app_icon__label),
summary = when {
AndroidVersion.ATLEAST_API29_Q -> stringRes(R.string.pref__other__show_app_icon__summary_atleast_q)
AndroidVersion.ATLEAST_API29_Q -> stringRes(R.string.pref__advanced__show_app_icon__summary_atleast_q)
else -> null
},
enabledIf = { AndroidVersion.ATMOST_API28_P },
)
Preference(
icon = ImageVector.vectorResource(R.drawable.ic_keyboard_keys),
title = stringRes(R.string.physical_keyboard__title),
onClick = { navController.navigate(Routes.Settings.PhysicalKeyboard) },
ListPreference(
prefs.advanced.incognitoMode,
icon = vectorResource(id = R.drawable.ic_incognito),
title = stringRes(R.string.pref__advanced__incognito_mode__label),
entries = enumDisplayEntriesOf(IncognitoMode::class),
)
Preference(
icon = Icons.Default.Adb,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,6 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -42,33 +41,28 @@ import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.rippleClickable
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.io.FileRegistry
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.jetpref.datastore.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.datastore.jetprefDatastoreDir
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.writeFromFile
import org.florisboard.lib.compose.FlorisButtonBar
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.rippleClickable
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.kotlin.io.writeJson
@@ -142,7 +136,6 @@ fun BackupScreen() = FlorisScreen {
val navController = LocalNavController.current
val context = LocalContext.current
val cacheManager by context.cacheManager()
val scope = rememberCoroutineScope()
var backupDestination by remember { mutableStateOf(Backup.Destination.FILE_SYS) }
val backupFilesSelector = remember { Backup.FilesSelector() }
@@ -162,24 +155,22 @@ fun BackupScreen() = FlorisScreen {
context.contentResolver.writeFromFile(uri, backupWorkspace!!.zipFile)
backupWorkspace!!.close()
}.onSuccess {
context.showLongToastSync(R.string.backup_and_restore__back_up__success)
context.showLongToast(R.string.backup_and_restore__back_up__success)
navController.popBackStack()
}.onFailure { error ->
flogError { error.stackTraceToString() }
context.showLongToastSync(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
backupWorkspace = null
}
},
)
suspend fun prepareBackupWorkspace() {
fun prepareBackupWorkspace() {
val workspace = cacheManager.backupAndRestore.new()
if (backupFilesSelector.jetprefDatastore) {
val fileBasedStorage = workspace.inputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
.let { FileBasedStorage(it.path) }
FlorisPreferenceStore.export(fileBasedStorage).getOrThrow()
context.jetprefDatastoreDir.let { dir ->
dir.copyRecursively(workspace.inputDir.subDir(dir.name))
}
}
val workspaceFilesDir = workspace.inputDir.subDir("files")
if (backupFilesSelector.imeKeyboard) {
@@ -234,7 +225,7 @@ fun BackupScreen() = FlorisScreen {
backupWorkspace = workspace
}
suspend fun prepareAndPerformBackup() {
fun prepareAndPerformBackup() {
runCatching {
if (backupWorkspace == null || backupWorkspace!!.isClosed()) {
prepareBackupWorkspace()
@@ -274,7 +265,7 @@ fun BackupScreen() = FlorisScreen {
)
ButtonBarButton(
onClick = {
scope.launch { prepareAndPerformBackup() }
prepareAndPerformBackup()
},
text = stringRes(R.string.action__back_up),
enabled = backupFilesSelector.atLeastOneSelected(),

View File

@@ -1,69 +0,0 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.settings.advanced
import android.content.Intent
import android.content.res.Configuration
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.compose.stringRes
@Composable
fun PhysicalKeyboardScreen() = FlorisScreen {
title = stringRes(R.string.physical_keyboard__title)
val context = LocalContext.current
val physicalKeyboardAttached by remember {
mutableStateOf(context.resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS)
}
val activityForResult = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { }
content {
if (physicalKeyboardAttached) {
Preference(
title = stringRes(R.string.physical_keyboard__system_settings__title),
summary = stringRes(R.string.physical_keyboard__system_settings__summary),
onClick = {
activityForResult.launch(Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS))
}
)
} else {
Preference(
title = stringRes(R.string.physical_keyboard__system_settings__title),
summary = stringRes(R.string.physical_keyboard__system_settings__summary_not_attached),
)
}
SwitchPreference(
pref = prefs.physicalKeyboard.showOnScreenKeyboard,
title = stringRes(R.string.physical_keyboard__show_on_screen_keyboard__title),
summary = stringRes(R.string.physical_keyboard__show_on_screen_keyboard__summary),
)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,46 +43,47 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
import dev.patrickgold.florisboard.lib.cache.CacheManager
import dev.patrickgold.florisboard.lib.compose.FlorisButtonBar
import dev.patrickgold.florisboard.lib.compose.FlorisCardDefaults
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.jetpref.datastore.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.datastore.runtime.ImportStrategy
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.ui.Preference
import java.io.FileNotFoundException
import java.text.DateFormat
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.florisboard.lib.android.readToFile
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.compose.FlorisButtonBar
import org.florisboard.lib.compose.FlorisCardDefaults
import org.florisboard.lib.compose.FlorisOutlinedBox
import org.florisboard.lib.compose.FlorisOutlinedButton
import org.florisboard.lib.compose.defaultFlorisOutlinedBox
import org.florisboard.lib.compose.stringRes
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.io.readJson
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import java.io.FileNotFoundException
import java.text.DateFormat
import java.util.*
object Restore {
const val MIN_VERSION_CODE = 64
const val PACKAGE_NAME = "dev.patrickgold.florisboard"
const val BACKUP_ARCHIVE_FILE_NAME = "backup.zip"
enum class Mode {
MERGE,
ERASE_AND_OVERWRITE;
}
}
@Composable
@@ -90,12 +91,13 @@ fun RestoreScreen() = FlorisScreen {
title = stringRes(R.string.backup_and_restore__restore__title)
previewFieldVisible = false
val prefs by florisPreferenceModel()
val navController = LocalNavController.current
val context = LocalContext.current
val cacheManager by context.cacheManager()
val restoreFilesSelector = remember { Backup.FilesSelector() }
var importStrategy by remember { mutableStateOf(ImportStrategy.Merge) }
var restoreMode by remember { mutableStateOf(Restore.Mode.MERGE) }
// TODO: rememberCoroutineScope() is unusable because it provides the scope in a cancelled state, which does
// not make sense at all. I suspect that this is a bug and once it is resolved we can use it here again.
val restoreScope = remember { CoroutineScope(Dispatchers.Main) }
@@ -136,7 +138,7 @@ fun RestoreScreen() = FlorisScreen {
}
restoreWorkspace = workspace
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)
@@ -146,14 +148,14 @@ fun RestoreScreen() = FlorisScreen {
suspend fun performRestore() {
val workspace = restoreWorkspace!!
val shouldReset = importStrategy == ImportStrategy.Erase
val shouldReset = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE
if (restoreFilesSelector.jetprefDatastore) {
val file = workspace.outputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
if (file.exists()) {
val fileBasedStorage = FileBasedStorage(file.path)
FlorisPreferenceStore.import(importStrategy, fileBasedStorage).getOrThrow()
val datastoreFile = workspace.outputDir
.subDir(JetPref.JETPREF_DIR_NAME)
.subFile("${prefs.name}.${JetPref.JETPREF_FILE_EXT}")
if (datastoreFile.exists()) {
prefs.datastorePersistenceHandler?.loadPrefs(datastoreFile, shouldReset)
prefs.datastorePersistenceHandler?.persistPrefs()
}
}
val workspaceFilesDir = workspace.outputDir.subDir("files")
@@ -273,16 +275,16 @@ fun RestoreScreen() = FlorisScreen {
) {
RadioListItem(
onClick = {
importStrategy = ImportStrategy.Merge
restoreMode = Restore.Mode.MERGE
},
selected = importStrategy == ImportStrategy.Merge,
selected = restoreMode == Restore.Mode.MERGE,
text = stringRes(R.string.backup_and_restore__restore__mode_merge),
)
RadioListItem(
onClick = {
importStrategy = ImportStrategy.Erase
restoreMode = Restore.Mode.ERASE_AND_OVERWRITE
},
selected = importStrategy == ImportStrategy.Erase,
selected = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE,
text = stringRes(R.string.backup_and_restore__restore__mode_erase_and_overwrite),
)
}
@@ -291,7 +293,7 @@ fun RestoreScreen() = FlorisScreen {
runCatching {
restoreDataFromFileSystemLauncher.launch("*/*")
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,15 +18,13 @@ package dev.patrickgold.florisboard.app.settings.clipboard
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.clipboard.CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.pluralsRes
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.compose.pluralsRes
import org.florisboard.lib.compose.stringRes
@OptIn(ExperimentalJetPrefDatastoreUi::class)
@Composable
@@ -53,47 +51,12 @@ fun ClipboardScreen() = FlorisScreen {
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
)
PreferenceGroup(title = stringRes(R.string.pref__clipboard__group_clipboard_suggestion__label)) {
SwitchPreference(
prefs.clipboard.suggestionEnabled,
title = stringRes(R.string.pref__clipboard__suggestion_enabled__label),
summary = stringRes(R.string.pref__clipboard__suggestion_enabled__summary),
)
DialogSliderPreference(
prefs.clipboard.suggestionTimeout,
title = stringRes(R.string.pref__clipboard__suggestion_timeout__label),
valueLabel = { stringRes(R.string.pref__clipboard__suggestion_timeout__summary, "v" to it) },
min = 30,
max = 300,
stepIncrement = 5,
enabledIf = { prefs.clipboard.suggestionEnabled isEqualTo true },
)
}
PreferenceGroup(title = stringRes(R.string.pref__clipboard__group_clipboard_history__label)) {
SwitchPreference(
prefs.clipboard.historyEnabled,
title = stringRes(R.string.pref__clipboard__enable_clipboard_history__label),
summary = stringRes(R.string.pref__clipboard__enable_clipboard_history__summary),
)
DialogSliderPreference(
primaryPref = prefs.clipboard.numHistoryGridColumnsPortrait,
secondaryPref = prefs.clipboard.numHistoryGridColumnsLandscape,
title = stringRes(R.string.pref__clipboard__num_history_grid_columns__label),
primaryLabel = stringRes(R.string.screen_orientation__portrait),
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
valueLabel = { numGridColumns ->
if (numGridColumns == CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO) {
stringRes(R.string.general__auto)
} else {
numGridColumns.toString()
}
},
min = 0,
max = 10,
stepIncrement = 1,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
)
SwitchPreference(
prefs.clipboard.cleanUpOld,
title = stringRes(R.string.pref__clipboard__clean_up_old__label),
@@ -108,22 +71,6 @@ fun ClipboardScreen() = FlorisScreen {
stepIncrement = 5,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.cleanUpOld isEqualTo true },
)
SwitchPreference(
prefs.clipboard.autoCleanSensitive,
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive__label),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
visibleIf = { AndroidVersion.ATLEAST_API33_T },
)
DialogSliderPreference(
prefs.clipboard.autoCleanSensitiveAfter,
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive_after__label),
valueLabel = { pluralsRes(R.plurals.unit__seconds__written, it, "v" to it) },
min = 0,
max = 300,
stepIncrement = 10,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.autoCleanSensitive isEqualTo true },
visibleIf = { AndroidVersion.ATLEAST_API33_T },
)
SwitchPreference(
prefs.clipboard.limitHistorySize,
title = stringRes(R.string.pref__clipboard__limit_history_size__label),

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,9 +21,9 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.compose.stringRes
@Composable
fun DictionaryScreen() = FlorisScreen {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,21 +55,19 @@ import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryDao
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryEntry
import dev.patrickgold.florisboard.ime.dictionary.UserDictionaryValidation
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.compose.FlorisIconButton
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedTextField
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.Validation
import dev.patrickgold.florisboard.lib.compose.rippleClickable
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.rememberValidationResult
import dev.patrickgold.florisboard.lib.util.launchActivity
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.stringRes
import org.florisboard.lib.compose.FlorisIconButton
import org.florisboard.lib.compose.rippleClickable
import org.florisboard.lib.compose.stringRes
private val AllLanguagesLocale = FlorisLocale.from(language = "zz")
private val UserDictionaryEntryToAdd = UserDictionaryEntry(id = 0, "", 255, null, null)
@@ -144,16 +142,16 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToastSync("Database handle is null, failed to import")
context.showLongToast("Database handle is null, failed to import")
return@rememberLauncherForActivityResult
}
runCatching {
db.importCombinedList(context, uri)
}.onSuccess {
buildUi()
context.showLongToastSync(R.string.settings__udm__dictionary_import_success)
context.showLongToast(R.string.settings__udm__dictionary_import_success)
}.onFailure { error ->
context.showLongToastSync("Error: ${error.localizedMessage}")
context.showLongToast("Error: ${error.localizedMessage}")
}
},
)
@@ -169,15 +167,15 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToastSync("Database handle is null, failed to export")
context.showLongToast("Database handle is null, failed to export")
return@rememberLauncherForActivityResult
}
runCatching {
db.exportCombinedList(context, uri)
}.onSuccess {
context.showLongToastSync(R.string.settings__udm__dictionary_export_success)
context.showLongToast(R.string.settings__udm__dictionary_export_success)
}.onFailure { error ->
context.showLongToastSync("Error: ${error.localizedMessage}")
context.showLongToast("Error: ${error.localizedMessage}")
}
},
)
@@ -368,35 +366,39 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
) {
Column {
DialogProperty(text = stringRes(R.string.settings__udm__dialog__word_label)) {
JetPrefTextField(
FlorisOutlinedTextField(
value = word,
onValueChange = { word = it },
showValidationError = showValidationErrors,
validationResult = wordValidation,
)
Validation(showValidationErrors, wordValidation)
}
DialogProperty(text = stringRes(
R.string.settings__udm__dialog__freq_label,
"f_min" to FREQUENCY_MIN, "f_max" to FREQUENCY_MAX,
)) {
JetPrefTextField(
FlorisOutlinedTextField(
value = freq,
onValueChange = { freq = it },
showValidationError = showValidationErrors,
validationResult = freqValidation,
)
Validation(showValidationErrors, freqValidation)
}
DialogProperty(text = stringRes(R.string.settings__udm__dialog__shortcut_label)) {
JetPrefTextField(
FlorisOutlinedTextField(
value = shortcut,
onValueChange = { shortcut = it },
showValidationError = showValidationErrors,
validationResult = shortcutValidation,
)
Validation(showValidationErrors, shortcutValidation)
}
DialogProperty(text = stringRes(R.string.settings__udm__dialog__locale_label)) {
JetPrefTextField(
FlorisOutlinedTextField(
value = locale,
onValueChange = { locale = it },
showValidationError = showValidationErrors,
validationResult = localeValidation,
)
Validation(showValidationErrors, localeValidation)
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2025 The FlorisBoard Contributors
* Copyright (C) 2021 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,13 +23,14 @@ import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.lib.compose.FlorisInfoCard
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import org.florisboard.lib.compose.FlorisInfoCard
import org.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
@OptIn(ExperimentalJetPrefDatastoreUi::class)
@Composable

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