Compare commits

..

49 Commits

Author SHA1 Message Date
Patrick Goldinger
a25501d63c Release v0.2.0 2020-10-05 22:33:15 +02:00
Patrick Goldinger
e9a5f2161c Fix bugs in setup and settings / Change values for suggestion prefs 2020-10-05 22:25:17 +02:00
Patrick Goldinger
6f12f22937 Fix #17
Fix bug where the action defined for the enter key did not behave as
intended when the supplied action has flags set. Some apps ignored these
and worked flawlessly (Reddit, Chrome, Firefox, ...) while other
apps didn't behave that well (Twitter, F-Droid, ...).
2020-10-05 21:05:28 +02:00
Patrick Goldinger
25054ef679 Improve layout measurement and height factor calculation
- Height factor is now being used in the root InputView rather than
  repeatedly recalculating it on a per KeyboardView basis.
- Keyboard views had an empty space when in one-handed mode.
- Icon size in Smartbar is still messed up, but will be resolved
  when overhauling the Smartbar.
- Media layout may contain empty spaces, will be addressed when
  overhauling the media context UI.
2020-09-22 19:37:51 +02:00
Patrick Goldinger
3af17f99fe Add back Advanced Settings / Fix back button logic in Settings 2020-09-17 20:10:49 +02:00
Patrick Goldinger
06664ff521 Merge pull request #15 from florisboard/feat-theme-customization
Add theme customization feature
2020-09-16 20:04:30 +02:00
Patrick Goldinger
fde0749a3b Fix unhandled exceptions / Improve core layout view
- When detaching an InputView from the window, it will throw an
  exception when it is flipping while being detached. This has been
  fixed by extending the class and catching the exception.
- The core layout now has InputWindowView as the root and InputView
  as the real input view. While technically nothing has changed here,
  due to the better naming scheme it is more clearer now.
- Fix theme colors in both Floris Day and Night.
- Move ime.editing package into ime.text
2020-09-16 19:40:01 +02:00
Patrick Goldinger
c061e15263 Change used color picker pref / Fix bugs 2020-09-13 19:15:04 +02:00
Patrick Goldinger
7256c597c2 Add theme preset selector dialog / Add Floris Night theme
- Theme preset can now be selected and will be applied.
- Floris Night theme re-added (was previously defined in
  res/values/theme.xml)
- Various bug fixes / feature enhancement regarding themes
  and preferences.
2020-09-11 20:23:04 +02:00
Patrick Goldinger
2f9d32027b Add own fragment for theme prefs
- Advanced settings fragment is currently not accessible, will change
  at a later stage
- Partial support for one-handed colors
- Add method to write a Theme class to prefs
- Base theme now only sets the absolute minimum values and fallbacks
- Base Theme comes in Day and Night variant, is dependent on
  isNightTheme flag in <theme>.json
2020-09-09 21:01:24 +02:00
Patrick Goldinger
538912edc2 Add media context theme support / Fix various bugs 2020-09-04 19:45:54 +02:00
Patrick Goldinger
ee5ff81ee8 Improve applying pref color to View 2020-09-02 18:38:45 +02:00
Patrick Goldinger
d873dc54c5 Add theme prefs for KeyPopup, Smartbar, EditingLayout and NavBar 2020-09-01 20:10:44 +02:00
Patrick Goldinger
1e967463de Add theme prefs for core KeyboardView and KeyView 2020-08-31 21:09:24 +02:00
Patrick Goldinger
5c084a10dc Add Theme core class and sample theme file
- Theme class is responsible for parsing / packing a theme.
- The sample theme file will be the keyboards default and fallback theme
  in the future, with slight modifications.
2020-08-31 18:22:01 +02:00
Patrick Goldinger
f158a9deb3 Update Kotlin to 1.4.0 / Update other packages as well 2020-08-27 23:24:11 +02:00
Patrick Goldinger
66d328293c Merge pull request #14 from florisboard/feat-new-settings-screen
Revamp settings screen
2020-08-27 22:23:59 +02:00
Patrick Goldinger
e33b652bb3 Add localization and theme prefs to fragments / Improve layout
- Improve layout of list_item.xml (adheres more to predefined standard
  values now)
- Add localization card to typing fragment.
- Add theme card to keyboard fragment (will get revision and own sub-fragment)
  with next feature (theme customization).
2020-08-27 21:51:47 +02:00
Patrick Goldinger
65d8c02b95 Restructure settings naming scheme and structure (frontend)
- Home, Localization and Theme need a better UI, only temporary for now.
2020-08-26 23:49:10 +02:00
Patrick Goldinger
bd090132eb Restructure settings naming scheme and structure (backend)
- No setting added or removed, only renamed and possibly moved within the
  settings structure.
- Strings have also been updated (en, it-IT)
2020-08-26 22:47:43 +02:00
Patrick Goldinger
0eb5ca318b Release v0.1.2 2020-08-16 22:41:06 +02:00
Patrick Goldinger
dfa9df6cd6 Merge pull request #13 from florisboard/feat-clipboard-cursor-tools
Add clipboard cursor related tools
2020-08-16 22:13:19 +02:00
Patrick Goldinger
3f5dfbc852 Add number row and clipboard cursor tools to Smartbar (#9, #3)
- Smartbar now supports showing a number row or a clipboard/cursor toolbar
  if candidate suggestions are disabled. What toolbar will be shown is
  controlled by prefs.suggestion.showInstead
- Smartbar now shows a back button when the active keyboard mode is
  KeyboardMode.EDITING
- Improvements in backend of Smartbar
2020-08-16 17:07:52 +02:00
Patrick Goldinger
59caafbf19 Implement cursor movement and clipboard functionality
- Add backend handling for editing layout.
- Improve FlorisBoard event listener implementation, allow different
  objects than Text-/MediaInputManager to receive events.
- Add function to send a key event to the system (allows to write this
  in a single line, which is more readable)
2020-08-15 20:26:06 +02:00
Patrick Goldinger
037a452baf Add SmartbarQuickAction to access editing layout 2020-08-14 19:17:13 +02:00
Patrick Goldinger
ffa405f289 Add clipboard and cursor editing layout (UI) 2020-08-14 18:54:35 +02:00
Patrick Goldinger
5d7091582f Modify prefs_looknfeel.xml to use new seek bar pref 2020-08-11 23:12:15 +02:00
Patrick Goldinger
b4096f2cfb Add DialogSeekBarPreference in .settings.components
- This SeekBar implementation allows for better control of min/max/step.
- The current value of the preference is shown in the summary, to change it
  the user has to click on it, where a dialog window with a SeekBar opens.
2020-08-11 23:06:51 +02:00
Patrick Goldinger
81c62f3e91 Improve list item layout
- List item now uses Android's predefined ids for title and summary
- Needed for custom preference implementation
- Layout font size in list_item.xml is now the same as the other list items
2020-08-11 22:58:49 +02:00
Patrick Goldinger
5c7db2b344 Simplify feature_request.md issue template 2020-08-10 23:26:17 +02:00
Patrick Goldinger
30bca99092 Fix extended key popup not aligning correctly
when FlorisBoard initializes with prefs.popup.enabled=false. This was
due to the fact that the show() method of KeyPopupManager did some required
calculations which were not done if the popups were disabled.
2020-08-10 22:59:06 +02:00
Patrick Goldinger
9a9445dab1 Merge pull request #8 from hamedsj/master
Add Disable Checkbox for "Preview Popup" feature
2020-08-10 21:54:45 +02:00
hamedsj
1fbfc32429 Add checkbox for disable "Preview PopUp" in keyboard settings 2020-08-10 23:45:17 +04:30
hamedsj
645b682451 Add Keshida character to untranslatable strings/keys 2020-08-10 22:56:42 +04:30
Patrick Goldinger
63ed46ccf4 Release v0.1.1 2020-08-09 20:43:01 +02:00
Patrick Goldinger
236f682622 Fix LayoutManager incorrectly merging main and mod layout
- Related to issue mentioned in #6
2020-08-09 20:23:27 +02:00
Patrick Goldinger
bef69b3187 Merge pull request #6 from PHELAT/master
Add support for persian keyboard layout
2020-08-09 20:14:03 +02:00
Mahdi Nouri
235224aefd Move the delete key back to the modifier layout 2020-08-09 16:54:09 +04:30
qw123wh
2489872589 Add Italian PlayStore translation by Qw123wh (#5)
- Update full_description.txt
- Add files via upload
- Update full_description.txt
2020-08-09 09:35:53 +02:00
Mahdi Nouri
33a9504707 Add support for persian layout 2020-08-08 18:46:45 +04:30
Mahdi Nouri
0fb7bbb034 Add support for half-space key with an appropriate icon 2020-08-08 17:53:37 +04:30
Mahdi Nouri
beef54941f Add the ability to load a customized layout modifier for each layout 2020-08-08 16:58:49 +04:30
Patrick Goldinger
634ff4972d Show number row in Smartbar if composing word suggestions is disabled 2020-08-08 11:27:22 +02:00
Patrick Goldinger
52f3477e24 Merge branch 'master' of https://github.com/florisboard/florisboard 2020-08-07 21:24:29 +02:00
Patrick Goldinger
1086464b09 Add string resources for preferences items / Fix apostrophe issues 2020-08-07 21:24:10 +02:00
Patrick Goldinger
2e4267aad4 Update full_description.txt
- Add supported keyboard layouts to the list
2020-08-07 19:07:48 +02:00
Patrick Goldinger
b14fe8ad03 Merge pull request #4 from qw123wh/qw123wh--italian-translate
Add Italian translation
2020-08-07 17:42:00 +02:00
qw123wh
82d6141fb2 Italian translation
Add Italian translation
2020-08-07 14:48:41 +02:00
Patrick Goldinger
e0e7bcc08a Split strings.xml into translatable and non-translatable file
- Done to easily identify which strings should be localized and which not
- Add translating guide in CONTRIBUTING.md
2020-08-06 23:08:27 +02:00
111 changed files with 4272 additions and 1243 deletions

View File

@@ -7,15 +7,5 @@ assignees: ''
---
#### Short description of your idea
A short but clear and concise description of your idea.
#### Detailed description of your idea
A clear and concise description of what you want to be added or changed. If you also have
an idea how to implement it, please describe it here.
#### Alternatives to your idea
If you have considered an alternative solution for your idea, describe it here.
#### Additional context
Add any other context or screenshots about the feature request here.
Describe your idea in a short but concise way. If you have multiple ideas which are not directly connected to each other, file an issue per idea. This makes it easy to implement one feature proposal at a time. If you have any examples, e.g. screenshots or other keyboards which have the proposed feature implemented, link them here.
Thank you for your help in making FlorisBoard better!

View File

@@ -20,13 +20,53 @@ configs because some features and structures may change, please do not add this
kind of content yet. As FlorisBoard's state progresses and its core stabilizes,
you will be able to add keyboard layouts.
## Translating FlorisBoard
Before starting to translate, when adding a new translation please file
an issue stating that you want to translate FlorisBoard into a language.
Once this gets approved you can start translating. When updating an
already existing translation file you can just send a PR directly.
If you are not familiar with PRs, check out this guide:
[https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request](https://www.gun.io/blog/how-to-github-fork-branch-and-pull-request)
Notes for tips below:
- Replace `<language>` with the language you want to add
- Replace `<code>` with the ISO 639-1 code of the language you want to
add
([List of codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes))
### Tips when adding a new translation
- To add the new translation file, navigate to `app/src/main/res/values`
and copy the file `strings.xml` into the folder
`app/src/main/res/values-<code>` (you have to create this folder)
- Translate only the phrases inside the brackets, leave the name
attribute as it is
E.g.: `<string name="hello_string">Hello World!</string>`
`<string name="hello_string">Ciao mondo!</string>`
- When finished translating, commit your changes locally, as the commit
message use `Add <language> translation`
- Push your change(s) and create the PR. When everything checks out, it
will get accepted.
### Tips when updating a translation
- To update a translation, check the `strings.xml` in
`app/src/main/res/values` for newly added strings and add them to the
translation file in `app/src/main/res/values-<code>`
- When finished translating, commit your changes locally, as the commit
message use `Update <language> translation`
- Push your change(s) and create the PR. When everything checks out, it
will get accepted.
## Bug reporting
This kind of contribution is the most important, as it tells where FlorisBoard
has flaws and thus should be imroved to maximize stability and user experience.
To make this process as smooth as possible, please use the premade [issue
template](.github/ISSUE_TEMPLATE/bug_report.md) for bug reporting. This makes it
easy for us to understand what the bug is and how to solve it.
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 premade [issue template](.github/ISSUE_TEMPLATE/bug_report.md)
for bug reporting. This makes it easy for us to understand what the bug
is and how to solve it.
### Capturing ADB debug logs

View File

@@ -52,9 +52,9 @@ timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
* [ ] Tablet screen support
### Layouts
* [x] Latin character layout (QWERTY)
* [x] Other character layouts (both latin and non-latin) (Currently
QWERTZ, AZERTY, swiss and spanish are supported besides QWERTY)
* [x] Latin character layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish,
Norwegian, Swedish/Finnish, Icelandic, Danish)
* [x] Non-latin character layouts (Persian)
* [x] Adapt to situation in app (password, url, text, etc. )
* [x] Special character layout(s)
* [x] Numeric layout
@@ -69,7 +69,7 @@ timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
* [x] Preferences screen
* [x] Customize look and behaviour of keyboard (currently only
light/dark theme)
* [ ] Theme customization
* [x] Theme customization
* [ ] Theme import/export (?)
* [x] Subtype selection (language/layout)
* [x] Keyboard behaviour preferences
@@ -84,7 +84,7 @@ timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
### Other useful features
* [x] One-handed mode
* [ ] Clipboard manager (?)
* [x] Clipboard/cursor tools
* [ ] Floating keyboard
* [ ] Gesture support
* [ ] Glide typing (?)
@@ -97,13 +97,15 @@ timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
Note: (?) = not sure if it will be implemented
## Used libraries and icons
## Used libraries, components and icons
* [Google Flexbox Layout for Android](https://github.com/google/flexbox-layout)
by [google](https://github.com/google)
* [Google Material icons](https://github.com/google/material-design-icons) by
[google](https://github.com/google)
* [Moshi JSON library](https://github.com/square/moshi) by
[square](https://github.com/square)
* [ColorPicker preference](https://github.com/jaredrummler/ColorPicker) by
[Jared Rummler](https://github.com/jaredrummler)
## License
```

View File

@@ -10,8 +10,8 @@ android {
applicationId "dev.patrickgold.florisboard"
minSdkVersion 23
targetSdkVersion 29
versionCode 9
versionName "0.1.0"
versionCode 12
versionName "0.2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -31,9 +31,11 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
testImplementation 'junit:junit:4.12'
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:1.10.19'
@@ -42,7 +44,8 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android:flexbox:2.0.1'
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.google.android.material:material:1.2.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
implementation 'com.jaredrummler:colorpicker:1.1.0'
}

View File

@@ -74,6 +74,14 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/SettingsTheme"/>
<!-- Advanced Activity -->
<activity
android:name="dev.patrickgold.florisboard.settings.AdvancedActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/settings__advanced__title"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/SettingsTheme"/>
<!-- Setup Activity -->
<activity
android:name="dev.patrickgold.florisboard.setup.SetupActivity"

View File

@@ -11,7 +11,8 @@
"icelandic": "Icelandic (QWERTY)",
"swiss_german": "Swiss German (QWERTZ)",
"swiss_french": "Swiss French (QWERTZ)",
"swiss_italian": "Swiss Italian (QWERTZ)"
"swiss_italian": "Swiss Italian (QWERTZ)",
"persian": "Persian"
},
"defaultSubtypes": [
{
@@ -174,6 +175,13 @@
"preferredLayout": "icelandic",
"isAsciiCapable": true,
"isEmojiCapable": true
},
{
"id": 800,
"languageTag": "fa-FA",
"preferredLayout": "persian",
"isAsciiCapable": true,
"isEmojiCapable": true
}
]
}

View File

@@ -0,0 +1,80 @@
{
"ض": [
{ "code": 1777, "label": "۱" }
],
"ص": [
{ "code": 1778, "label": "۲" }
],
"ث": [
{ "code": 1779, "label": "۳" }
],
"ق": [
{ "code": 1780, "label": "۴" }
],
"ف": [
{ "code": 1781, "label": "۵" }
],
"غ": [
{ "code": 1782, "label": "۶" }
],
"ع": [
{ "code": 1783, "label": "۷" }
],
"ه": [
{ "code": 1784, "label": "۸" }
],
"خ": [
{ "code": 1785, "label": "۹" }
],
"ح": [
{ "code": 1776, "label": "۰" }
],
"ی": [
{ "code": 1574, "label": "ئ" },
{ "code": 1610, "label": "ي" }
],
"ا": [
{ "code": 1649, "label": "ٱ" },
{ "code": 1569, "label": "ء" },
{ "code": 1570, "label": "آ" },
{ "code": 1571, "label": "أ" },
{ "code": 1573, "label": "إ" }
],
"ت": [
{ "code": 1577, "label": "ة" }
],
"ک": [
{ "code": 1603, "label": "ك" },
{ "code": 1706, "label": "ڪ"}
],
"ز": [
{ "code": 1688, "label": "ژ" }
],
"و": [
{ "code": 1572, "label": "ؤ" }
],
".~normal": [
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },
{ "code": 1615, "label": "ُ" },
{ "code": 1616, "label": "ِ" },
{ "code": 1614, "label": "َ" },
{ "code": 1600, "label": "ـ" },
{ "code": 1621, "label": "ٕ" },
{ "code": 1618, "label": "ْ" },
{ "code": 1617, "label": "ّ" },
{ "code": 1612, "label": "ٌ" },
{ "code": 1613, "label": "ٍ" },
{ "code": 1611, "label": "ً" },
{ "code": 1620, "label": "ٔ" }
],
".~uri": [
{ "code": -255, "label": ".gov" },
{ "code": -255, "label": ".edu" },
{ "code": -255, "label": ".org" },
{ "code": -255, "label": ".com" },
{ "code": -255, "label": ".net" },
{ "code": -255, "label": ".ir"}
]
}

View File

@@ -0,0 +1,34 @@
{
"type": "characters/mod",
"name": "persian",
"direction": "rtl",
"arrangement": [
[
{ "code": 0 },
{ "code": -5, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "code": 64, "label": "@", "variation": "email_address" },
{ "code": 1548, "label": "،", "variation": "normal" },
{ "code": 47, "label": "/", "variation": "uri" },
{ "code": -210, "label": "language_switch", "type": "system_gui", "popup": [
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" },
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui", "popup": [
{ "code": -100, "label": "settings", "type": "system_gui" }
] },
{ "code": 32, "label": " " },
{ "code": 8204, "label": "half_space", "variation": "normal" },
{ "code": 1600, "label": "kashida", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "email_address" },
{ "code": 46, "label": ".", "variation": "normal" },
{ "code": 46, "label": ".", "variation": "uri" },
{ "code": 10, "label": "enter", "type": "enter_editing", "popup": [
{ "code": -215, "label": "toggle_one_handed_mode", "type": "system_gui" },
{ "code": -213, "label": "switch_to_media_context", "type": "system_gui" }
] }
]
]
}

View File

@@ -0,0 +1,45 @@
{
"type": "characters",
"name": "persian",
"direction": "rtl",
"modifier": "persian",
"arrangement": [
[
{ "code": 1590, "label": "ض" },
{ "code": 1589, "label": "ص" },
{ "code": 1579, "label": "ث" },
{ "code": 1602, "label": "ق" },
{ "code": 1601, "label": "ف" },
{ "code": 1594, "label": "غ" },
{ "code": 1593, "label": "ع" },
{ "code": 1607, "label": "ه" },
{ "code": 1582, "label": "خ" },
{ "code": 1581, "label": "ح" },
{ "code": 1580, "label": "ج" }
],
[
{ "code": 1588, "label": "ش" },
{ "code": 1587, "label": "س" },
{ "code": 1740, "label": "ی" },
{ "code": 1576, "label": "ب" },
{ "code": 1604, "label": "ل" },
{ "code": 1575, "label": "ا" },
{ "code": 1578, "label": "ت" },
{ "code": 1606, "label": "ن" },
{ "code": 1605, "label": "م" },
{ "code": 1705, "label": "ک" },
{ "code": 1711, "label": "گ" }
],
[
{ "code": 1592, "label": "ظ" },
{ "code": 1591, "label": "ط" },
{ "code": 1586, "label": "ز" },
{ "code": 1585, "label": "ر" },
{ "code": 1584, "label": "ذ" },
{ "code": 1583, "label": "د" },
{ "code": 1662, "label": "پ" },
{ "code": 1608, "label": "و" },
{ "code": 1670, "label": "چ" }
]
]
}

View File

@@ -0,0 +1,60 @@
{
"name": "floris_day",
"displayName": "Floris Day",
"author": "patrickgold",
"isNightTheme": false,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/bgColor",
"navigationBarLight": "true",
"semiTransparentColor": "#20000000",
"textColor": "#000000"
},
"keyboard": {
"bgColor": "#E0E0E0"
},
"key": {
"bgColor": "#FFFFFF",
"bgColorPressed": "#F5F5F5",
"fgColor": "@window/textColor"
},
"keyEnter": {
"bgColor": "@window/colorPrimary",
"bgColorPressed": "@window/colorPrimaryDark",
"fgColor": "#FFFFFF"
},
"keyPopup": {
"bgColor": "#EEEEEE",
"bgColorActive": "#BDBDBD",
"fgColor": "@window/textColor"
},
"keyShift": {
"bgColor": "@key/bgColor",
"bgColorPressed": "@key/bgColorPressed",
"fgColor": "@window/textColor",
"fgColorCapsLock": "@window/colorAccent"
},
"media": {
"fgColor": "@window/textColor",
"fgColorAlt": "#757575"
},
"oneHanded": {
"bgColor": "#E8F5E9"
},
"oneHandedButton": {
"fgColor": "#424242"
},
"smartbar": {
"bgColor": "transparent",
"fgColor": "@window/textColor",
"fgColorAlt": "#8A8A8A"
},
"smartbarButton": {
"bgColor": "@key/bgColor",
"fgColor": "@key/fgColor"
}
}
}

View File

@@ -0,0 +1,60 @@
{
"name": "floris_night",
"displayName": "Floris Night",
"author": "patrickgold",
"isNightTheme": true,
"attributes": {
"window": {
"colorPrimary": "#4CAF50",
"colorPrimaryDark": "#388E3C",
"colorAccent": "#FF9800",
"navigationBarColor": "@keyboard/bgColor",
"navigationBarLight": "false",
"semiTransparentColor": "#20FFFFFF",
"textColor": "#FFFFFF"
},
"keyboard": {
"bgColor": "#212121"
},
"key": {
"bgColor": "#424242",
"bgColorPressed": "#616161",
"fgColor": "@window/textColor"
},
"keyEnter": {
"bgColor": "@window/colorPrimary",
"bgColorPressed": "@window/colorPrimaryDark",
"fgColor": "#FFFFFF"
},
"keyPopup": {
"bgColor": "#757575",
"bgColorActive": "#BDBDBD",
"fgColor": "@window/textColor"
},
"keyShift": {
"bgColor": "@key/bgColor",
"bgColorPressed": "@key/bgColorPressed",
"fgColor": "@window/textColor",
"fgColorCapsLock": "@window/colorAccent"
},
"media": {
"fgColor": "@window/textColor",
"fgColorAlt": "#BDBDBD"
},
"oneHanded": {
"bgColor": "#1B5E20"
},
"oneHandedButton": {
"fgColor": "#EEEEEE"
},
"smartbar": {
"bgColor": "transparent",
"fgColor": "@window/textColor",
"fgColorAlt": "#73FFFFFF"
},
"smartbarButton": {
"bgColor": "@key/bgColor",
"fgColor": "@key/fgColor"
}
}
}

View File

@@ -34,6 +34,24 @@ You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</pre>
<hr>
<h3>ColorPicker preference</h3>
<span>Copyright 2016 Jared Rummler / Copyright 2015 Daniel Nilsson</span>
<pre>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@@ -17,8 +17,10 @@
package dev.patrickgold.florisboard.ime.core
import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.inputmethodservice.InputMethodService
import android.media.AudioManager
@@ -58,15 +60,20 @@ class FlorisBoard : InputMethodService() {
private set
val context: Context
get() = inputView?.context ?: this
private var inputView: InputView? = null
get() = inputWindowView?.context ?: this
var inputView: InputView? = null
private set
private var inputWindowView: InputWindowView? = null
private var eventListeners: MutableList<EventListener> = mutableListOf()
private var audioManager: AudioManager? = null
var clipboardManager: ClipboardManager? = null
private var vibrator: Vibrator? = null
private val osHandler = Handler()
lateinit var subtypeManager: SubtypeManager
lateinit var activeSubtype: Subtype
private var currentThemeIsNight: Boolean = false
private var currentThemeResId: Int = 0
val textInputManager: TextInputManager
@@ -104,6 +111,18 @@ class FlorisBoard : InputMethodService() {
fun getInstance(): FlorisBoard {
return florisboardInstance!!
}
@Synchronized
fun getInstanceOrNull(): FlorisBoard? {
return florisboardInstance
}
fun getDayNightBaseThemeId(isNightTheme: Boolean): Int {
return when (isNightTheme) {
true -> R.style.KeyboardThemeBase_Night
else -> R.style.KeyboardThemeBase_Day
}
}
}
override fun onCreate() {
@@ -128,21 +147,23 @@ class FlorisBoard : InputMethodService() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onCreate()")
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
prefs = PrefHelper(this)
prefs = PrefHelper.getDefaultInstance(this)
prefs.initDefaultPreferences()
prefs.sync()
subtypeManager = SubtypeManager(this, prefs)
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
currentThemeResId = prefs.theme.getSelectedThemeResId()
currentThemeIsNight = prefs.internal.themeCurrentIsNight
currentThemeResId = getDayNightBaseThemeId(currentThemeIsNight)
setTheme(currentThemeResId)
updateTheme()
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
super.onCreate()
textInputManager.onCreate()
mediaInputManager.onCreate()
eventListeners.toList().forEach { it.onCreate() }
}
@SuppressLint("InflateParams")
@@ -151,12 +172,11 @@ class FlorisBoard : InputMethodService() {
baseContext.setTheme(currentThemeResId)
inputView = layoutInflater.inflate(R.layout.florisboard, null) as InputView
inputWindowView = layoutInflater.inflate(R.layout.florisboard, null) as InputWindowView
textInputManager.onCreateInputView()
mediaInputManager.onCreateInputView()
eventListeners.toList().forEach { it.onCreateInputView() }
return inputView
return inputWindowView
}
fun registerInputView(inputView: InputView) {
@@ -164,11 +184,11 @@ class FlorisBoard : InputMethodService() {
this.inputView = inputView
initializeOneHandedEnvironment()
updateTheme()
updateSoftInputWindowLayoutParameters()
updateOneHandedPanelVisibility()
textInputManager.onRegisterInputView(inputView)
mediaInputManager.onRegisterInputView(inputView)
eventListeners.toList().forEach { it.onRegisterInputView(inputView) }
}
override fun onDestroy() {
@@ -177,48 +197,44 @@ class FlorisBoard : InputMethodService() {
osHandler.removeCallbacksAndMessages(null)
florisboardInstance = null
eventListeners.toList().forEach { it.onDestroy() }
eventListeners.clear()
super.onDestroy()
textInputManager.onDestroy()
mediaInputManager.onDestroy()
}
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
super.onStartInputView(info, restarting)
textInputManager.onStartInputView(info, restarting)
mediaInputManager.onStartInputView(info, restarting)
eventListeners.toList().forEach { it.onStartInputView(info, restarting) }
}
override fun onFinishInputView(finishingInput: Boolean) {
currentInputConnection?.requestCursorUpdates(0)
super.onFinishInputView(finishingInput)
textInputManager.onFinishInputView(finishingInput)
mediaInputManager.onFinishInputView(finishingInput)
eventListeners.toList().forEach { it.onFinishInputView(finishingInput) }
}
override fun onWindowShown() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowShown()")
prefs.sync()
updateThemeIfNecessary()
updateTheme()
updateOneHandedPanelVisibility()
activeSubtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
setActiveInput(R.id.text_input)
super.onWindowShown()
textInputManager.onWindowShown()
mediaInputManager.onWindowShown()
eventListeners.toList().forEach { it.onWindowShown() }
}
override fun onWindowHidden() {
if (BuildConfig.DEBUG) Log.i(this::class.simpleName, "onWindowHidden()")
super.onWindowHidden()
textInputManager.onWindowHidden()
mediaInputManager.onWindowHidden()
eventListeners.toList().forEach { it.onWindowHidden() }
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -227,14 +243,11 @@ class FlorisBoard : InputMethodService() {
}
super.onConfigurationChanged(newConfig)
textInputManager.onConfigurationChanged(newConfig)
mediaInputManager.onConfigurationChanged(newConfig)
}
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
super.onUpdateCursorAnchorInfo(cursorAnchorInfo)
textInputManager.onUpdateCursorAnchorInfo(cursorAnchorInfo)
mediaInputManager.onUpdateCursorAnchorInfo(cursorAnchorInfo)
eventListeners.toList().forEach { it.onUpdateCursorAnchorInfo(cursorAnchorInfo) }
}
override fun onUpdateSelection(
@@ -253,59 +266,65 @@ class FlorisBoard : InputMethodService() {
candidatesStart,
candidatesEnd
)
textInputManager.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
)
mediaInputManager.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
)
eventListeners.toList().forEach {
it.onUpdateSelection(
oldSelStart,
oldSelEnd,
newSelStart,
newSelEnd,
candidatesStart,
candidatesEnd
)
}
}
/**
* Checks the preferences if the selected theme res id has changed and updates the theme only
* then by rebuilding the UI and setting the navigation bar theme manually.
* Reapplies the supplies colors and settings from prefs to navigation bar.
*/
private fun updateThemeIfNecessary() {
val newThemeResId = prefs.theme.getSelectedThemeResId()
if (newThemeResId != currentThemeResId) {
currentThemeResId = newThemeResId
private fun updateTheme() {
val newThemeIsNightMode = prefs.internal.themeCurrentIsNight
if (currentThemeIsNight != newThemeIsNightMode) {
currentThemeResId = getDayNightBaseThemeId(newThemeIsNightMode)
currentThemeIsNight = newThemeIsNightMode
setInputView(onCreateInputView())
val w = window?.window ?: return
w.navigationBarColor = getColorFromAttr(baseContext, android.R.attr.navigationBarColor)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
var flags = w.decorView.systemUiVisibility
flags = if (getBooleanFromAttr(baseContext, android.R.attr.windowLightNavigationBar)) {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
}
w.decorView.systemUiVisibility = flags
}
return
}
val w = window?.window ?: return
inputView?.setBackgroundColor(prefs.theme.keyboardBgColor)
w.navigationBarColor = prefs.theme.navBarColor
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
var flags = w.decorView.systemUiVisibility
flags = if (prefs.theme.navBarIsLight) {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
}
w.decorView.systemUiVisibility = flags
}
inputView?.oneHandedCtrlPanelStart?.setBackgroundColor(prefs.theme.oneHandedBgColor)
inputView?.oneHandedCtrlPanelEnd?.setBackgroundColor(prefs.theme.oneHandedBgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_start)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_move_end)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_start)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
inputView?.findViewById<ImageButton>(R.id.one_handed_ctrl_close_end)
?.imageTintList = ColorStateList.valueOf(prefs.theme.oneHandedButtonFgColor)
eventListeners.toList().forEach { it.onApplyThemeAttributes() }
}
override fun onComputeInsets(outInsets: Insets?) {
super.onComputeInsets(outInsets)
val inputView = this.inputView ?: return
val inputWindowView = this.inputWindowView ?: return
// TODO: Check also if the keyboard is currently suppressed by a hardware keyboard
if (!isInputViewShown) {
outInsets?.contentTopInsets = inputView.height
outInsets?.visibleTopInsets = inputView.height
outInsets?.contentTopInsets = inputWindowView.height
outInsets?.visibleTopInsets = inputWindowView.height
return
}
val innerInputViewContainer =
inputView.findViewById<LinearLayout>(R.id.inner_input_view_container) ?: return
val visibleTopY = inputView.height - innerInputViewContainer.measuredHeight
val visibleTopY = inputWindowView.height - inputView.measuredHeight
outInsets?.contentTopInsets = visibleTopY
outInsets?.visibleTopInsets = visibleTopY
}
@@ -321,8 +340,8 @@ class FlorisBoard : InputMethodService() {
private fun updateSoftInputWindowLayoutParameters() {
val w = window?.window ?: return
ViewLayoutUtils.updateLayoutHeightOf(w, WindowManager.LayoutParams.MATCH_PARENT)
val inputView = this.inputView
if (inputView != null) {
val inputWindowView = this.inputWindowView
if (inputWindowView != null) {
val layoutHeight = if (isFullscreenMode) {
WindowManager.LayoutParams.WRAP_CONTENT
} else {
@@ -331,7 +350,7 @@ class FlorisBoard : InputMethodService() {
val inputArea = w.findViewById<View>(android.R.id.inputArea)
ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight)
ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM)
ViewLayoutUtils.updateLayoutHeightOf(inputView, layoutHeight)
ViewLayoutUtils.updateLayoutHeightOf(inputWindowView, layoutHeight)
}
}
@@ -339,9 +358,9 @@ class FlorisBoard : InputMethodService() {
* Makes a key press vibration if the user has this feature enabled in the preferences.
*/
fun keyPressVibrate() {
if (prefs.looknfeel.vibrationEnabled) {
var vibrationStrength = prefs.looknfeel.vibrationStrength
if (vibrationStrength == 0 && prefs.looknfeel.vibrationEnabledSystem) {
if (prefs.keyboard.vibrationEnabled) {
var vibrationStrength = prefs.keyboard.vibrationStrength
if (vibrationStrength == -1 && prefs.keyboard.vibrationEnabledSystem) {
vibrationStrength = 36
}
if (vibrationStrength > 0) {
@@ -363,15 +382,15 @@ class FlorisBoard : InputMethodService() {
* Makes a key press sound if the user has this feature enabled in the preferences.
*/
fun keyPressSound(keyData: KeyData? = null) {
if (prefs.looknfeel.soundEnabled) {
val soundVolume = prefs.looknfeel.soundVolume
if (prefs.keyboard.soundEnabled) {
val soundVolume = prefs.keyboard.soundVolume
val effect = when (keyData?.code) {
KeyCode.SPACE -> AudioManager.FX_KEYPRESS_SPACEBAR
KeyCode.DELETE -> AudioManager.FX_KEYPRESS_DELETE
KeyCode.ENTER -> AudioManager.FX_KEYPRESS_RETURN
else -> AudioManager.FX_KEYPRESS_STANDARD
}
if (soundVolume == 0 && prefs.looknfeel.soundEnabledSystem) {
if (soundVolume == -1 && prefs.keyboard.soundEnabledSystem) {
audioManager!!.playSoundEffect(effect)
} else if (soundVolume > 0) {
audioManager!!.playSoundEffect(effect, soundVolume / 100f)
@@ -435,26 +454,26 @@ class FlorisBoard : InputMethodService() {
private fun onOneHandedPanelButtonClick(v: View) {
when (v.id) {
R.id.one_handed_ctrl_move_start -> {
prefs.looknfeel.oneHandedMode = "start"
prefs.keyboard.oneHandedMode = "start"
}
R.id.one_handed_ctrl_move_end -> {
prefs.looknfeel.oneHandedMode = "end"
prefs.keyboard.oneHandedMode = "end"
}
R.id.one_handed_ctrl_close_start,
R.id.one_handed_ctrl_close_end -> {
prefs.looknfeel.oneHandedMode = "off"
prefs.keyboard.oneHandedMode = "off"
}
}
updateOneHandedPanelVisibility()
}
fun toggleOneHandedMode() {
when (prefs.looknfeel.oneHandedMode) {
when (prefs.keyboard.oneHandedMode) {
"off" -> {
prefs.looknfeel.oneHandedMode = "end"
prefs.keyboard.oneHandedMode = "end"
}
else -> {
prefs.looknfeel.oneHandedMode = "off"
prefs.keyboard.oneHandedMode = "off"
}
}
updateOneHandedPanelVisibility()
@@ -465,7 +484,7 @@ class FlorisBoard : InputMethodService() {
inputView?.oneHandedCtrlPanelStart?.visibility = View.GONE
inputView?.oneHandedCtrlPanelEnd?.visibility = View.GONE
} else {
when (prefs.looknfeel.oneHandedMode) {
when (prefs.keyboard.oneHandedMode) {
"off" -> {
inputView?.oneHandedCtrlPanelStart?.visibility = View.GONE
inputView?.oneHandedCtrlPanelEnd?.visibility = View.GONE
@@ -486,6 +505,27 @@ class FlorisBoard : InputMethodService() {
}, 0)
}
/**
* Adds a given [listener] to the list which will receive FlorisBoard events.
*
* @param listener The listener object which receives the events.
* @returns True if the listener has been added successfully, false otherwise.
*/
fun addEventListener(listener: EventListener): Boolean {
return eventListeners.add(listener)
}
/**
* Removes a given [listener] from the list which will receive FlorisBoard events.
*
* @param listener The same listener object which was used in [addEventListener].
* @returns True if the listener has been removed successfully, false otherwise. A false return
* value may also indicate that the [listener] was not added previously.
*/
fun removeEventListener(listener: EventListener): Boolean {
return eventListeners.remove(listener)
}
interface EventListener {
fun onCreate() {}
fun onCreateInputView() {}
@@ -498,8 +538,6 @@ class FlorisBoard : InputMethodService() {
fun onWindowShown() {}
fun onWindowHidden() {}
fun onConfigurationChanged(newConfig: Configuration) {}
fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {}
fun onUpdateSelection(
oldSelStart: Int,
@@ -510,6 +548,7 @@ class FlorisBoard : InputMethodService() {
candidatesEnd: Int
) {}
fun onApplyThemeAttributes() {}
fun onSubtypeChanged(newSubtype: Subtype) {}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.core
import android.content.Context
import android.util.AttributeSet
import android.widget.ViewFlipper
import java.lang.IllegalArgumentException
/**
* Custom ViewFlipper class used to prevent an unnecessary exception to be thrown when it is
* detached from a window.
*
* Based on the solution of this SO answer: https://stackoverflow.com/a/8208874/6801193
*/
class FlorisViewFlipper : ViewFlipper {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
override fun onDetachedFromWindow() {
try {
super.onDetachedFromWindow()
} catch (e: IllegalArgumentException) {
stopFlipping()
}
}
}

View File

@@ -17,20 +17,30 @@
package dev.patrickgold.florisboard.ime.core
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.util.Log
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ViewFlipper
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import kotlin.math.roundToInt
/**
* Root view of the keyboard. Notifies [FlorisBoard] when it has been attached to a window.
*/
class InputView : FrameLayout {
class InputView : LinearLayout {
private var florisboard: FlorisBoard = FlorisBoard.getInstance()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
var desiredInputViewHeight: Int = resources.getDimension(R.dimen.inputView_baseHeight).roundToInt()
private set
var desiredSmartbarHeight: Int = resources.getDimension(R.dimen.smartbar_baseHeight).roundToInt()
private set
var desiredTextKeyboardViewHeight: Int = resources.getDimension(R.dimen.textKeyboardView_baseHeight).roundToInt()
private set
var desiredMediaKeyboardViewHeight: Int = resources.getDimension(R.dimen.mediaKeyboardView_baseHeight).roundToInt()
private set
var mainViewFlipper: ViewFlipper? = null
private set
@@ -54,4 +64,31 @@ class InputView : FrameLayout {
florisboard.registerInputView(this)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightFactor = when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> 0.85f
else -> if (prefs.keyboard.oneHandedMode != "off") {
0.9f
} else {
1.0f
}
} * when (prefs.keyboard.heightFactor) {
"extra_short" -> 0.85f
"short" -> 0.90f
"mid_short" -> 0.95f
"normal" -> 1.00f
"mid_tall" -> 1.05f
"tall" -> 1.10f
"extra_tall" -> 1.15f
else -> 1.00f
}
val height = (resources.getDimension(R.dimen.inputView_baseHeight) * heightFactor).roundToInt()
desiredInputViewHeight = height
desiredSmartbarHeight = (0.16129 * height).roundToInt()
desiredTextKeyboardViewHeight = height - desiredSmartbarHeight
desiredMediaKeyboardViewHeight = height
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.core
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ViewFlipper
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
/**
* Root view of the keyboard.
*/
class InputWindowView : FrameLayout {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

View File

@@ -39,7 +39,7 @@ class PrefHelper(
val correction = Correction(this)
val internal = Internal(this)
val keyboard = Keyboard(this)
val looknfeel = Looknfeel(this)
val localization = Localization(this)
val suggestion = Suggestion(this)
val theme = Theme(this)
@@ -110,15 +110,28 @@ class PrefHelper(
}
}
companion object {
private var defaultInstance: PrefHelper? = null
@Synchronized
fun getDefaultInstance(context: Context): PrefHelper {
if (defaultInstance == null) {
defaultInstance = PrefHelper(context)
}
return defaultInstance!!
}
}
/**
* Tells the [PreferenceManager] to set the defined preferences to their default values, if
* they have not been initialized yet.
*/
fun initDefaultPreferences() {
PreferenceManager.setDefaultValues(context, R.xml.prefs_advanced, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_gestures, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_keyboard, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_looknfeel, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_theme, true)
PreferenceManager.setDefaultValues(context, R.xml.prefs_typing, true)
//setPref(Keyboard.SUBTYPES, "")
//setPref(Internal.IS_IME_SET_UP, false)
}
@@ -128,10 +141,10 @@ class PrefHelper(
*/
fun sync() {
val contentResolver = context.contentResolver
looknfeel.soundEnabledSystem = Settings.System.getInt(
keyboard.soundEnabledSystem = Settings.System.getInt(
contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 0
) != 0
looknfeel.vibrationEnabledSystem = Settings.System.getInt(
keyboard.vibrationEnabledSystem = Settings.System.getInt(
contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0
) != 0
@@ -171,28 +184,41 @@ class PrefHelper(
}
/**
* Wrapper class for internal preferences.
* Wrapper class for internal preferences. A preference qualifies as an internal pref if the
* user has no ability to control this preference's value directly (via a UI pref view).
*/
class Internal(private val prefHelper: PrefHelper) {
companion object {
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val VERSION_ON_INSTALL = "internal__version_on_install"
const val VERSION_LAST_USE = "internal__version_last_use"
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val THEME_CURRENT_BASED_ON = "internal__theme_current_based_on"
const val THEME_CURRENT_IS_MODIFIED = "internal__theme_current_is_modified"
const val THEME_CURRENT_IS_NIGHT = "internal__theme_current_is_night"
const val VERSION_ON_INSTALL = "internal__version_on_install"
const val VERSION_LAST_USE = "internal__version_last_use"
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
}
var isImeSetUp: Boolean
get() = prefHelper.getPref(IS_IME_SET_UP, false)
set(value) = prefHelper.setPref(IS_IME_SET_UP, value)
get() = prefHelper.getPref(IS_IME_SET_UP, false)
set(v) = prefHelper.setPref(IS_IME_SET_UP, v)
var themeCurrentBasedOn: String
get() = prefHelper.getPref(THEME_CURRENT_BASED_ON, "undefined")
set(v) = prefHelper.setPref(THEME_CURRENT_BASED_ON, v)
var themeCurrentIsModified: Boolean
get() = prefHelper.getPref(THEME_CURRENT_IS_MODIFIED, false)
set(v) = prefHelper.setPref(THEME_CURRENT_IS_MODIFIED, v)
var themeCurrentIsNight: Boolean
get() = prefHelper.getPref(THEME_CURRENT_IS_NIGHT, false)
set(v) = prefHelper.setPref(THEME_CURRENT_IS_NIGHT, v)
var versionOnInstall: String
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
set(value) = prefHelper.setPref(VERSION_ON_INSTALL, value)
get() = prefHelper.getPref(VERSION_ON_INSTALL, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_ON_INSTALL, v)
var versionLastUse: String
get() = prefHelper.getPref(VERSION_LAST_USE, VersionName.DEFAULT_RAW)
set(value) = prefHelper.setPref(VERSION_LAST_USE, value)
get() = prefHelper.getPref(VERSION_LAST_USE, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_LAST_USE, v)
var versionLastChangelog: String
get() = prefHelper.getPref(VERSION_LAST_CHANGELOG, VersionName.DEFAULT_RAW)
set(value) = prefHelper.setPref(VERSION_LAST_CHANGELOG, value)
get() = prefHelper.getPref(VERSION_LAST_CHANGELOG, VersionName.DEFAULT_RAW)
set(v) = prefHelper.setPref(VERSION_LAST_CHANGELOG, v)
}
/**
@@ -200,30 +226,14 @@ class PrefHelper(
*/
class Keyboard(private val prefHelper: PrefHelper) {
companion object {
const val ACTIVE_SUBTYPE_ID = "keyboard__active_subtype_id"
const val SUBTYPES = "keyboard__subtypes"
}
var activeSubtypeId: Int
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, -1)
set(v) = prefHelper.setPref(ACTIVE_SUBTYPE_ID, v)
var subtypes: String
get() = prefHelper.getPref(SUBTYPES, "")
set(v) = prefHelper.setPref(SUBTYPES, v)
}
/**
* Wrapper class for looknfeel preferences.
*/
class Looknfeel(private val prefHelper: PrefHelper) {
companion object {
const val HEIGHT_FACTOR = "looknfeel__height_factor"
const val LONG_PRESS_DELAY = "looknfeel__long_press_delay"
const val ONE_HANDED_MODE = "looknfeel__one_handed_mode"
const val SOUND_ENABLED = "looknfeel__sound_enabled"
const val SOUND_VOLUME = "looknfeel__sound_volume"
const val VIBRATION_ENABLED = "looknfeel__vibration_enabled"
const val VIBRATION_STRENGTH = "looknfeel__vibration_strength"
const val HEIGHT_FACTOR = "keyboard__height_factor"
const val LONG_PRESS_DELAY = "keyboard__long_press_delay"
const val ONE_HANDED_MODE = "keyboard__one_handed_mode"
const val POPUP_ENABLED = "keyboard__popup_enabled"
const val SOUND_ENABLED = "keyboard__sound_enabled"
const val SOUND_VOLUME = "keyboard__sound_volume"
const val VIBRATION_ENABLED = "keyboard__vibration_enabled"
const val VIBRATION_STRENGTH = "keyboard__vibration_strength"
}
var heightFactor: String = ""
@@ -235,34 +245,58 @@ class PrefHelper(
var oneHandedMode: String
get() = prefHelper.getPref(ONE_HANDED_MODE, "off")
set(value) = prefHelper.setPref(ONE_HANDED_MODE, value)
var popupEnabled: Boolean = false
get() = prefHelper.getPref(POPUP_ENABLED, true)
private set
var soundEnabled: Boolean = false
get() = prefHelper.getPref(SOUND_ENABLED, true)
private set
var soundEnabledSystem: Boolean = false
var soundVolume: Int = 0
get() = prefHelper.getPref(SOUND_VOLUME, 0)
get() = prefHelper.getPref(SOUND_VOLUME, -1)
private set
var vibrationEnabled: Boolean = false
get() = prefHelper.getPref(VIBRATION_ENABLED, true)
private set
var vibrationEnabledSystem: Boolean = false
var vibrationStrength: Int = 0
get() = prefHelper.getPref(VIBRATION_STRENGTH, 0)
get() = prefHelper.getPref(VIBRATION_STRENGTH, -1)
private set
}
/**
* Wrapper class for localization preferences.
*/
class Localization(private val prefHelper: PrefHelper) {
companion object {
const val ACTIVE_SUBTYPE_ID = "localization__active_subtype_id"
const val SUBTYPES = "localization__subtypes"
}
var activeSubtypeId: Int
get() = prefHelper.getPref(ACTIVE_SUBTYPE_ID, Subtype.DEFAULT.id)
set(v) = prefHelper.setPref(ACTIVE_SUBTYPE_ID, v)
var subtypes: String
get() = prefHelper.getPref(SUBTYPES, "")
set(v) = prefHelper.setPref(SUBTYPES, v)
}
/**
* Wrapper class for suggestion preferences.
*/
class Suggestion(private val prefHelper: PrefHelper) {
companion object {
const val ENABLED = "suggestion__enabled"
const val SHOW_INSTEAD = "suggestion__show_instead"
const val USE_PREV_WORDS = "suggestion__use_prev_words"
}
var enabled: Boolean = false
get() = prefHelper.getPref(ENABLED, true)
private set
var showInstead: String = ""
get() = prefHelper.getPref(SHOW_INSTEAD, "number_row")
private set
var usePrevWords: Boolean = false
get() = prefHelper.getPref(USE_PREV_WORDS, true)
private set
@@ -273,18 +307,119 @@ class PrefHelper(
*/
class Theme(private val prefHelper: PrefHelper) {
companion object {
const val NAME = "theme__name"
const val COLOR_PRIMARY = "theme__colorPrimary"
const val COLOR_PRIMARY_DARK = "theme__colorPrimaryDark"
const val COLOR_ACCENT = "theme__colorAccent"
const val NAV_BAR_COLOR = "theme__navBarColor"
const val NAV_BAR_IS_LIGHT = "theme__navBarIsLight"
const val KEYBOARD_BG_COLOR = "theme__keyboard_bgColor"
const val KEY_BG_COLOR = "theme__key_bgColor"
const val KEY_BG_COLOR_PRESSED = "theme__key_bgColorPressed"
const val KEY_FG_COLOR = "theme__key_fgColor"
const val KEY_ENTER_BG_COLOR = "theme__keyEnter_bgColor"
const val KEY_ENTER_BG_COLOR_PRESSED = "theme__keyEnter_bgColorPressed"
const val KEY_ENTER_FG_COLOR = "theme__keyEnter_fgColor"
const val KEY_SHIFT_BG_COLOR = "theme__keyShift_bgColor"
const val KEY_SHIFT_BG_COLOR_PRESSED = "theme__keyShift_bgColorPressed"
const val KEY_SHIFT_FG_COLOR = "theme__keyShift_fgColor"
const val KEY_SHIFT_FG_COLOR_CAPSLOCK = "theme__keyShift_fgColorCapsLock"
const val KEY_POPUP_BG_COLOR = "theme__keyPopup_bgColor"
const val KEY_POPUP_BG_COLOR_ACTIVE = "theme__keyPopup_bgColorActive"
const val KEY_POPUP_FG_COLOR = "theme__keyPopup_fgColor"
const val MEDIA_FG_COLOR = "theme__media_fgColor"
const val MEDIA_FG_COLOR_ALT = "theme__media_fgColorAlt"
const val ONE_HANDED_BG_COLOR = "theme__oneHanded_bgColor"
const val ONE_HANDED_BUTTON_FG_COLOR = "theme__oneHandedButton_fgColor"
const val SMARTBAR_BG_COLOR = "theme__smartbar_bgColor"
const val SMARTBAR_FG_COLOR = "theme__smartbar_fgColor"
const val SMARTBAR_FG_COLOR_ALT = "theme__smartbar_fgColorAlt"
const val SMARTBAR_BUTTON_BG_COLOR = "theme__smartbarButton_bgColor"
const val SMARTBAR_BUTTON_FG_COLOR = "theme__smartbarButton_fgColor"
}
var name: String = ""
get() = prefHelper.getPref(NAME, "floris_light")
private set
fun getSelectedThemeResId(): Int {
return when (name) {
"floris_light" -> R.style.KeyboardTheme_FlorisLight
"floris_dark" -> R.style.KeyboardTheme_FlorisDark
else -> R.style.KeyboardTheme_FlorisLight
}
}
var colorPrimary: Int
get() = prefHelper.getPref(COLOR_PRIMARY, 0)
set(v) = prefHelper.setPref(COLOR_PRIMARY, v)
var colorPrimaryDark: Int
get() = prefHelper.getPref(COLOR_PRIMARY_DARK, 0)
set(v) = prefHelper.setPref(COLOR_PRIMARY_DARK, v)
var colorAccent: Int
get() = prefHelper.getPref(COLOR_ACCENT, 0)
set(v) = prefHelper.setPref(COLOR_ACCENT, v)
var navBarColor: Int
get() = prefHelper.getPref(NAV_BAR_COLOR, 0)
set(v) = prefHelper.setPref(NAV_BAR_COLOR, v)
var navBarIsLight: Boolean
get() = prefHelper.getPref(NAV_BAR_IS_LIGHT, false)
set(v) = prefHelper.setPref(NAV_BAR_IS_LIGHT, v)
var keyboardBgColor: Int
get() = prefHelper.getPref(KEYBOARD_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEYBOARD_BG_COLOR, v)
var keyBgColor: Int
get() = prefHelper.getPref(KEY_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_BG_COLOR, v)
var keyBgColorPressed: Int
get() = prefHelper.getPref(KEY_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_BG_COLOR_PRESSED, v)
var keyFgColor: Int
get() = prefHelper.getPref(KEY_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_FG_COLOR, v)
var keyEnterBgColor: Int
get() = prefHelper.getPref(KEY_ENTER_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_ENTER_BG_COLOR, v)
var keyEnterBgColorPressed: Int
get() = prefHelper.getPref(KEY_ENTER_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_ENTER_BG_COLOR_PRESSED, v)
var keyEnterFgColor: Int
get() = prefHelper.getPref(KEY_ENTER_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_ENTER_FG_COLOR, v)
var keyShiftBgColor: Int
get() = prefHelper.getPref(KEY_SHIFT_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_BG_COLOR, v)
var keyShiftBgColorPressed: Int
get() = prefHelper.getPref(KEY_SHIFT_BG_COLOR_PRESSED, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_BG_COLOR_PRESSED, v)
var keyShiftFgColor: Int
get() = prefHelper.getPref(KEY_SHIFT_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_FG_COLOR, v)
var keyShiftFgColorCapsLock: Int
get() = prefHelper.getPref(KEY_SHIFT_FG_COLOR_CAPSLOCK, 0)
set(v) = prefHelper.setPref(KEY_SHIFT_FG_COLOR_CAPSLOCK, v)
var keyPopupBgColor: Int
get() = prefHelper.getPref(KEY_POPUP_BG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_POPUP_BG_COLOR, v)
var keyPopupBgColorActive: Int
get() = prefHelper.getPref(KEY_POPUP_BG_COLOR_ACTIVE, 0)
set(v) = prefHelper.setPref(KEY_POPUP_BG_COLOR_ACTIVE, v)
var keyPopupFgColor: Int
get() = prefHelper.getPref(KEY_POPUP_FG_COLOR, 0)
set(v) = prefHelper.setPref(KEY_POPUP_FG_COLOR, v)
var mediaFgColor: Int
get() = prefHelper.getPref(MEDIA_FG_COLOR, 0)
set(v) = prefHelper.setPref(MEDIA_FG_COLOR, v)
var mediaFgColorAlt: Int
get() = prefHelper.getPref(MEDIA_FG_COLOR_ALT, 0)
set(v) = prefHelper.setPref(MEDIA_FG_COLOR_ALT, v)
var oneHandedBgColor: Int
get() = prefHelper.getPref(ONE_HANDED_BG_COLOR, 0)
set(v) = prefHelper.setPref(ONE_HANDED_BG_COLOR, v)
var oneHandedButtonFgColor: Int
get() = prefHelper.getPref(ONE_HANDED_BUTTON_FG_COLOR, 0)
set(v) = prefHelper.setPref(ONE_HANDED_BUTTON_FG_COLOR, v)
var smartbarBgColor: Int
get() = prefHelper.getPref(SMARTBAR_BG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BG_COLOR, v)
var smartbarFgColor: Int
get() = prefHelper.getPref(SMARTBAR_FG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_FG_COLOR, v)
var smartbarFgColorAlt: Int
get() = prefHelper.getPref(SMARTBAR_FG_COLOR_ALT, 0)
set(v) = prefHelper.setPref(SMARTBAR_FG_COLOR_ALT, v)
var smartbarButtonBgColor: Int
get() = prefHelper.getPref(SMARTBAR_BUTTON_BG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BUTTON_BG_COLOR, v)
var smartbarButtonFgColor: Int
get() = prefHelper.getPref(SMARTBAR_BUTTON_FG_COLOR, 0)
set(v) = prefHelper.setPref(SMARTBAR_BUTTON_FG_COLOR, v)
}
}

View File

@@ -48,7 +48,7 @@ class SubtypeManager(
var imeConfig: FlorisBoard.ImeConfig = FlorisBoard.ImeConfig(context.packageName)
var subtypes: List<Subtype>
get() {
val listRaw = prefs.keyboard.subtypes
val listRaw = prefs.localization.subtypes
return if (listRaw.isBlank()) {
listOf()
} else {
@@ -58,7 +58,7 @@ class SubtypeManager(
}
}
set(v) {
prefs.keyboard.subtypes = v.joinToString(SUBTYPE_LIST_STR_DELIMITER)
prefs.localization.subtypes = v.joinToString(SUBTYPE_LIST_STR_DELIMITER)
}
init {
@@ -134,16 +134,16 @@ class SubtypeManager(
*/
fun getActiveSubtype(): Subtype? {
for (subtype in subtypes) {
if (subtype.id == prefs.keyboard.activeSubtypeId) {
if (subtype.id == prefs.localization.activeSubtypeId) {
return subtype
}
}
val subtypeList = subtypes
return if (subtypeList.isNotEmpty()) {
prefs.keyboard.activeSubtypeId = subtypeList[0].id
prefs.localization.activeSubtypeId = subtypeList[0].id
subtypeList[0]
} else {
prefs.keyboard.activeSubtypeId = -1
prefs.localization.activeSubtypeId = Subtype.DEFAULT.id
null
}
}
@@ -212,7 +212,7 @@ class SubtypeManager(
}
}
subtypes = subtypeList
if (subtypeToRemove.id == prefs.keyboard.activeSubtypeId) {
if (subtypeToRemove.id == prefs.localization.activeSubtypeId) {
getActiveSubtype()
}
}
@@ -238,7 +238,7 @@ class SubtypeManager(
if (triggerNextSubtype) {
newActiveSubtype = subtypeList[0]
}
prefs.keyboard.activeSubtypeId = when (newActiveSubtype) {
prefs.localization.activeSubtypeId = when (newActiveSubtype) {
null -> -1
else -> newActiveSubtype.id
}

View File

@@ -71,6 +71,10 @@ class MediaInputManager private constructor() : CoroutineScope by MainScope(),
}
}
init {
florisboard.addEventListener(this)
}
/**
* Called when a new input view has been registered. Used to initialize all media-relevant
* views and layouts.

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.media
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
class MediaInputView : LinearLayout, FlorisBoard.EventListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
var tabLayout: TabLayout? = null
private set
var switchToTextInputButton: Button? = null
private set
var backspaceButton: ImageButton? = null
private set
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
florisboard?.addEventListener(this)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
tabLayout = findViewById(R.id.media_input_tabs)
switchToTextInputButton = findViewById(R.id.media_input_switch_to_text_input_button)
backspaceButton = findViewById(R.id.media_input_backspace_button)
onApplyThemeAttributes()
}
override fun onApplyThemeAttributes() {
tabLayout?.setTabTextColors(prefs.theme.mediaFgColor, prefs.theme.mediaFgColor)
tabLayout?.tabIconTint = ColorStateList.valueOf(prefs.theme.mediaFgColor)
tabLayout?.setSelectedTabIndicatorColor(prefs.theme.colorPrimary)
switchToTextInputButton?.setTextColor(prefs.theme.mediaFgColor)
backspaceButton?.imageTintList = ColorStateList.valueOf(prefs.theme.mediaFgColor)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val height = florisboard?.inputView?.desiredInputViewHeight ?: 0
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
}
}

View File

@@ -18,17 +18,20 @@ package dev.patrickgold.florisboard.ime.media.emoji
import android.annotation.SuppressLint
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.drawable.Drawable
import android.os.Handler
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.widget.HorizontalScrollView
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.util.getColorFromAttr
import dev.patrickgold.florisboard.ime.core.PrefHelper
/**
* View class for managing the rendering and the events of a single emoji keyboard key.
@@ -40,10 +43,12 @@ import dev.patrickgold.florisboard.util.getColorFromAttr
*/
@SuppressLint("ViewConstructor")
class EmojiKeyView(
private val florisboard: FlorisBoard,
private val emojiKeyboardView: EmojiKeyboardView,
val data: EmojiKeyData
) : androidx.appcompat.widget.AppCompatTextView(florisboard.context) {
) : androidx.appcompat.widget.AppCompatTextView(emojiKeyboardView.context),
FlorisBoard.EventListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private var isCancelled: Boolean = false
private var osHandler: Handler? = null
@@ -55,14 +60,16 @@ class EmojiKeyView(
setPadding(0, 0, 0, 0)
setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.emoji_key_textSize))
triangleDrawable = resources.getDrawable(
R.drawable.triangle_bottom_right, context.theme
)
triangleDrawable?.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
getColorFromAttr(context, R.attr.emoji_key_fgColor), BlendModeCompat.SRC_ATOP
)
triangleDrawable = ContextCompat.getDrawable(context, R.drawable.triangle_bottom_right)
text = data.getCodePointsAsString()
florisboard?.addEventListener(this)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
onApplyThemeAttributes()
}
/**
@@ -79,7 +86,7 @@ class EmojiKeyView(
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
isCancelled = false
val delayMillis = florisboard.prefs.looknfeel.longPressDelay
val delayMillis = prefs.keyboard.longPressDelay
if (osHandler == null) {
osHandler = Handler()
}
@@ -89,8 +96,8 @@ class EmojiKeyView(
emojiKeyboardView.isScrollBlocked = true
emojiKeyboardView.popupManager.show(this)
emojiKeyboardView.popupManager.extend(this)
florisboard.keyPressVibrate()
florisboard.keyPressSound()
florisboard?.keyPressVibrate()
florisboard?.keyPressSound()
}, delayMillis.toLong())
}
MotionEvent.ACTION_MOVE -> {
@@ -117,10 +124,10 @@ class EmojiKeyView(
if (event.actionMasked != MotionEvent.ACTION_CANCEL &&
retData != null && !isCancelled) {
if (!emojiKeyboardView.isScrollBlocked) {
florisboard.keyPressVibrate()
florisboard.keyPressSound()
florisboard?.keyPressVibrate()
florisboard?.keyPressSound()
}
florisboard.mediaInputManager.sendEmojiKeyPress(retData)
florisboard?.mediaInputManager?.sendEmojiKeyPress(retData)
performClick()
}
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
@@ -131,18 +138,29 @@ class EmojiKeyView(
return true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
triangleDrawable?.setBounds(
(measuredWidth * 0.75f).toInt(),
(measuredHeight * 0.75f).toInt(),
(measuredWidth * 0.85f).toInt(),
(measuredHeight * 0.85f).toInt()
)
}
override fun onApplyThemeAttributes() {
triangleDrawable?.colorFilter =
BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
prefs.theme.mediaFgColorAlt, BlendModeCompat.SRC_ATOP
)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
if (data.popup.isNotEmpty()) {
triangleDrawable?.setBounds(
(measuredWidth * 0.75f).toInt(),
(measuredHeight * 0.75f).toInt(),
(measuredWidth * 0.85f).toInt(),
(measuredHeight * 0.85f).toInt()
)
triangleDrawable?.draw(canvas)
}
}

View File

@@ -18,6 +18,7 @@ package dev.patrickgold.florisboard.ime.media.emoji
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewGroup
@@ -31,6 +32,7 @@ import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.tabs.TabLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.popup.KeyPopupManager
import kotlinx.coroutines.*
import java.util.*
@@ -42,15 +44,17 @@ import java.util.*
*
* @property florisboard Reference to instance of core class [FlorisBoard].
*/
class EmojiKeyboardView : LinearLayout {
class EmojiKeyboardView : LinearLayout, FlorisBoard.EventListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private var activeCategory: EmojiCategory = EmojiCategory.SMILEYS_EMOTION
private var emojiViewFlipper: ViewFlipper
private val emojiKeyWidth = resources.getDimension(R.dimen.emoji_key_width).toInt()
private val emojiKeyHeight = resources.getDimension(R.dimen.emoji_key_height).toInt()
private val florisboard: FlorisBoard = FlorisBoard.getInstance()
private var layouts: Deferred<EmojiLayoutDataMap>
private val mainScope = MainScope()
private val tabLayout: TabLayout
private val uiLayouts = EnumMap<EmojiCategory, HorizontalScrollView>(EmojiCategory::class.java)
var isScrollBlocked: Boolean = false
@@ -71,9 +75,9 @@ class EmojiKeyboardView : LinearLayout {
emojiViewFlipper.measureAllChildren = false
addView(emojiViewFlipper)
val tabs =
tabLayout =
ViewGroup.inflate(context, R.layout.media_input_emoji_tabs, null) as TabLayout
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
setActiveCategory(when (tab?.position) {
0 -> EmojiCategory.SMILEYS_EMOTION
@@ -92,7 +96,8 @@ class EmojiKeyboardView : LinearLayout {
override fun onTabReselected(tab: TabLayout.Tab?) {}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
})
addView(tabs)
addView(tabLayout)
florisboard?.addEventListener(this)
}
override fun onAttachedToWindow() {
@@ -102,11 +107,7 @@ class EmojiKeyboardView : LinearLayout {
buildLayout()
setActiveCategory(EmojiCategory.SMILEYS_EMOTION)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mainScope.cancel()
onApplyThemeAttributes()
}
/**
@@ -144,7 +145,7 @@ class EmojiKeyboardView : LinearLayout {
flexboxLayout.flexWrap = FlexWrap.WRAP
for (emojiKeyData in layouts.await()[category].orEmpty()) {
val emojiKeyView =
EmojiKeyView(florisboard, this@EmojiKeyboardView, emojiKeyData)
EmojiKeyView(this@EmojiKeyboardView, emojiKeyData)
emojiKeyView.layoutParams = FlexboxLayout.LayoutParams(
emojiKeyWidth, emojiKeyHeight
)
@@ -192,4 +193,9 @@ class EmojiKeyboardView : LinearLayout {
))
isScrollBlocked = true
}
override fun onApplyThemeAttributes() {
tabLayout.tabIconTint = ColorStateList.valueOf(prefs.theme.mediaFgColor)
tabLayout.setSelectedTabIndicatorColor(prefs.theme.colorAccent)
}
}

View File

@@ -19,11 +19,13 @@ package dev.patrickgold.florisboard.ime.popup
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat.getDrawable
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
@SuppressLint("ViewConstructor")
@@ -32,7 +34,7 @@ class KeyPopupExtendedSingleView(
) : androidx.appcompat.widget.AppCompatTextView(
context, null, 0
) {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
var iconDrawable: Drawable? = null
init {
@@ -40,15 +42,16 @@ class KeyPopupExtendedSingleView(
}
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, when {
isActive -> prefs.theme.keyPopupBgColorActive
else -> Color.TRANSPARENT
})
setTextColor(prefs.theme.keyPopupFgColor)
super.onDraw(canvas)
canvas ?: return
setBackgroundTintColor(this, when {
isActive -> R.attr.key_popup_extended_bgColorActive
else -> R.attr.key_popup_extended_bgColor
})
val drawable = iconDrawable
val drawablePadding = (0.2f * measuredHeight).toInt()
if (drawable != null) {
@@ -65,7 +68,7 @@ class KeyPopupExtendedSingleView(
measuredWidth - marginH - drawablePadding,
measuredHeight - marginV - drawablePadding)
drawable.colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
getColorFromAttr(context, R.attr.key_popup_fgColor),
prefs.theme.keyPopupFgColor,
BlendModeCompat.SRC_ATOP
)
drawable.draw(canvas)

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import com.google.android.flexbox.FlexboxLayout
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
class KeyPopupExtendedView : FlexboxLayout {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, prefs.theme.keyPopupBgColor)
super.onDraw(canvas)
}
}

View File

@@ -34,7 +34,6 @@ import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyView
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
import dev.patrickgold.florisboard.util.setTextTintColor
/**
* Manages the creation and dismissal of key popups as well as the checks if the pointer moved
@@ -107,10 +106,6 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
lp.isWrapBefore = isWrapBefore
textView.layoutParams = lp
textView.gravity = Gravity.CENTER
setTextTintColor(
textView,
R.attr.key_popup_fgColor
)
val textSize = keyboardView.resources.getDimension(R.dimen.key_popup_textSize)
if (keyView is KeyView) {
when (keyView.data.popup[k].code) {
@@ -169,20 +164,10 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
}
/**
* Shows a preview popup for the passed [keyView]. Ignores show requests for key views which
* key code is equal to or less than [KeyCode.SPACE]. KeyViews with a code defined in
* [exceptionsForKeyCodes] will only shadow-calculating the size of the key popup, as these
* sizes are needed for the extended popup. No popup will be shown to the user in this case.
*
* @param keyView Reference to the keyView currently controlling the popup.
* Calculates all attributes required by both the normal and the extended popup, regardless of
* the passed [keyView]'s code.
*/
fun show(keyView: T_KV) {
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE
&& !exceptionsForKeyCodes.contains(keyView.data.code)) {
return
}
// Update keyPopupWidth and keyPopupHeight
private fun calc(keyView: T_KV) {
if (keyboardView is KeyboardView) {
when (keyboardView.resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
@@ -199,11 +184,21 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
keyPopupHeight = (keyView.measuredHeight * 2.5f).toInt()
}
keyPopupDiffX = (keyView.measuredWidth - keyPopupWidth) / 2
// Calculating is done, so exit show() here if this key view is a special one.
if (keyView is KeyView && exceptionsForKeyCodes.contains(keyView.data.code)) {
}
/**
* Shows a preview popup for the passed [keyView]. Ignores show requests for key views which
* key code is equal to or less than [KeyCode.SPACE].
*
* @param keyView Reference to the keyView currently controlling the popup.
*/
fun show(keyView: T_KV) {
if (keyView is KeyView && keyView.data.code <= KeyCode.SPACE) {
return
}
calc(keyView)
val keyPopupX = keyPopupDiffX
val keyPopupY = -keyPopupHeight
if (window.isShowing) {
@@ -256,6 +251,10 @@ class KeyPopupManager<T_KBD: View, T_KV: View>(private val keyboardView: T_KBD)
return
}
if (!isShowingPopup) {
calc(keyView)
}
// Anchor left if keyView is in left half of keyboardView, else anchor right
if (keyView is KeyView) {
anchorLeft = keyView.x < keyboardView.measuredWidth / 2

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.popup
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.*
class KeyPopupView : LinearLayout {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private lateinit var text: TextView
private lateinit var threedots: ImageView
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
text = findViewById(R.id.key_popup_text)
threedots = findViewById(R.id.key_popup_threedots)
}
override fun onDraw(canvas: Canvas?) {
setBackgroundTintColor2(this, prefs.theme.keyPopupBgColor)
text.setTextColor(prefs.theme.keyPopupFgColor)
setImageTintColor2(threedots, prefs.theme.keyPopupFgColor)
super.onDraw(canvas)
}
}

View File

@@ -16,15 +16,13 @@
package dev.patrickgold.florisboard.ime.text
import android.content.ClipData
import android.content.Context
import android.os.Handler
import android.text.InputType
import android.util.Log
import android.view.KeyEvent
import android.view.inputmethod.CursorAnchorInfo
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.ExtractedTextRequest
import android.view.inputmethod.InputMethodManager
import android.view.inputmethod.*
import android.widget.LinearLayout
import android.widget.ViewFlipper
import dev.patrickgold.florisboard.BuildConfig
@@ -32,6 +30,7 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.InputView
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.text.editing.EditingKeyboardView
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyType
@@ -62,6 +61,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
private var activeKeyboardMode: KeyboardMode? = null
private val keyboardViews = EnumMap<KeyboardMode, KeyboardView>(KeyboardMode::class.java)
private var editingKeyboardView: EditingKeyboardView? = null
private val osHandler = Handler()
private var textViewFlipper: ViewFlipper? = null
var textViewGroup: LinearLayout? = null
@@ -85,7 +85,16 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
private var composingTextStart: Int? = null
private var cursorPos: Int = 0
private var isComposingEnabled: Boolean = false
private var isTextSelected: Boolean = false
var isManualSelectionMode: Boolean = false
private var isManualSelectionModeLeft: Boolean = false
private var isManualSelectionModeRight: Boolean = false
val isTextSelected: Boolean
get() = selectionEnd - selectionStart != 0
private var lastCursorAnchorInfo: CursorAnchorInfo? = null
private var selectionStart: Int = 0
private val selectionStartMin: Int = 0
private var selectionEnd: Int = 0
private var selectionEndMax: Int = 0
companion object {
private var instance: TextInputManager? = null
@@ -99,6 +108,10 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
}
}
init {
florisboard.addEventListener(this)
}
/**
* Non-UI-related setup + preloading of all required computed layouts (asynchronous in the
* background).
@@ -124,8 +137,6 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
private suspend fun addKeyboardView(mode: KeyboardMode) {
val keyboardView = KeyboardView(florisboard.context)
keyboardView.florisboard = florisboard
keyboardView.prefs = florisboard.prefs
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(mode, florisboard.activeSubtype).await()
keyboardViews[mode] = keyboardView
withContext(Dispatchers.Main) {
@@ -142,6 +153,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
launch(Dispatchers.Default) {
textViewGroup = inputView.findViewById(R.id.text_input)
textViewFlipper = inputView.findViewById(R.id.text_input_view_flipper)
editingKeyboardView = inputView.findViewById(R.id.editing)
val activeKeyboardMode = getActiveKeyboardMode()
addKeyboardView(activeKeyboardMode)
@@ -216,7 +228,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
KeyboardMode.NUMERIC,
KeyboardMode.PHONE,
KeyboardMode.PHONE2 -> false
else -> keyVariation != KeyVariation.PASSWORD
else -> keyVariation != KeyVariation.PASSWORD && florisboard.prefs.suggestion.enabled
}
updateCapsState()
resetComposingText()
@@ -233,6 +245,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
override fun onWindowShown() {
keyboardViews[KeyboardMode.CHARACTERS]?.updateVisibility()
smartbarManager.onWindowShown()
}
/**
@@ -245,16 +258,21 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
}
/**
* Sets [activeKeyboardMode] and updates the [SmartbarManager.activeContainerId].
* Sets [activeKeyboardMode] and updates the [SmartbarManager.isQuickActionsVisible].
*/
private fun setActiveKeyboardMode(mode: KeyboardMode) {
textViewFlipper?.displayedChild =
textViewFlipper?.indexOfChild(keyboardViews[mode]) ?: 0
fun setActiveKeyboardMode(mode: KeyboardMode) {
textViewFlipper?.displayedChild = textViewFlipper?.indexOfChild(when (mode) {
KeyboardMode.EDITING -> editingKeyboardView
else -> keyboardViews[mode]
}) ?: 0
keyboardViews[mode]?.updateVisibility()
keyboardViews[mode]?.requestLayout()
keyboardViews[mode]?.requestLayoutAllKeys()
activeKeyboardMode = mode
smartbarManager.activeContainerId = smartbarManager.getPreferredContainerId()
smartbarManager.isQuickActionsVisible = false
isManualSelectionMode = false
isManualSelectionModeLeft = false
isManualSelectionModeRight = false
}
override fun onSubtypeChanged(newSubtype: Subtype) {
@@ -271,15 +289,24 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
*/
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
cursorAnchorInfo ?: return
lastCursorAnchorInfo = cursorAnchorInfo
val ic = florisboard.currentInputConnection
val isNewSelectionInBoundsOfOld =
cursorAnchorInfo.selectionStart >= (selectionStart - 1) &&
cursorAnchorInfo.selectionStart <= (selectionStart + 1) &&
cursorAnchorInfo.selectionEnd >= (selectionEnd - 1) &&
cursorAnchorInfo.selectionEnd <= (selectionEnd + 1)
selectionStart = cursorAnchorInfo.selectionStart
selectionEnd = cursorAnchorInfo.selectionEnd
val inputText =
(ic?.getExtractedText(ExtractedTextRequest(), 0)?.text ?: "").toString()
selectionEndMax = inputText.length
if (isComposingEnabled) {
if (cursorAnchorInfo.selectionEnd - cursorAnchorInfo.selectionStart == 0) {
if (!isTextSelected) {
val newCursorPos = cursorAnchorInfo.selectionStart
val prevComposingText = (cursorAnchorInfo.composingText ?: "").toString()
val inputText =
(ic?.getExtractedText(ExtractedTextRequest(), 0)?.text ?: "").toString()
setComposingTextBasedOnInput(inputText, newCursorPos)
if ((newCursorPos == cursorPos) && (composingText == prevComposingText)) {
// Ignore this, as nothing has changed
@@ -299,8 +326,13 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
}
smartbarManager.generateCandidatesFromComposing(composingText)
}
isTextSelected = cursorAnchorInfo.selectionEnd - cursorAnchorInfo.selectionStart != 0
if (!isNewSelectionInBoundsOfOld) {
isManualSelectionMode = false
isManualSelectionModeLeft = false
isManualSelectionModeRight = false
}
updateCapsState()
smartbarManager.onUpdateCursorAnchorInfo(cursorAnchorInfo)
}
/**
@@ -399,6 +431,36 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
}
}
/**
* Sends a given [keyCode] as a [KeyEvent.ACTION_DOWN].
*
* @param ic The input connection on which this operation should be performed.
* @param keyCode The key code to send, use a key code defined in Android's [KeyEvent], not in
* [KeyCode] or this call may send a weird character, as this key codes do not match!!
*/
private fun sendSystemKeyEvent(ic: InputConnection?, keyCode: Int) {
ic?.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
}
/**
* Sends a given [keyCode] as a [KeyEvent.ACTION_DOWN] with ALT pressed.
*
* @param ic The input connection on which this operation should be performed.
* @param keyCode The key code to send, use a key code defined in Android's [KeyEvent], not in
* [KeyCode] or this call may send a weird character, as this key codes do not match!!
*/
private fun sendSystemKeyEventAlt(ic: InputConnection?, keyCode: Int) {
ic?.sendKeyEvent(
KeyEvent(
0,
1,
KeyEvent.ACTION_DOWN, keyCode,
0,
KeyEvent.META_ALT_LEFT_ON
)
)
}
/**
* Handles a [KeyCode.DELETE] event.
*/
@@ -406,12 +468,10 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
val ic = florisboard.currentInputConnection
ic?.beginBatchEdit()
resetComposingText()
ic?.sendKeyEvent(
KeyEvent(
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DEL
)
)
isManualSelectionMode = false
isManualSelectionModeLeft = false
isManualSelectionModeRight = false
sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DEL)
ic?.endBatchEdit()
}
@@ -420,37 +480,24 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
*/
private fun handleEnter() {
val ic = florisboard.currentInputConnection
ic?.beginBatchEdit()
resetComposingText()
val action = florisboard.currentInputEditorInfo?.imeOptions ?: 0
val actionMasked = action and EditorInfo.IME_MASK_ACTION
if (action and EditorInfo.IME_FLAG_NO_ENTER_ACTION > 0) {
ic?.sendKeyEvent(
KeyEvent(
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_ENTER
)
)
sendSystemKeyEvent(ic, KeyEvent.KEYCODE_ENTER)
} else {
when (action and EditorInfo.IME_MASK_ACTION) {
when (actionMasked) {
EditorInfo.IME_ACTION_DONE,
EditorInfo.IME_ACTION_GO,
EditorInfo.IME_ACTION_NEXT,
EditorInfo.IME_ACTION_PREVIOUS,
EditorInfo.IME_ACTION_SEARCH,
EditorInfo.IME_ACTION_SEND -> {
ic?.performEditorAction(action)
}
else -> {
ic?.sendKeyEvent(
KeyEvent(
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_ENTER
)
)
ic?.performEditorAction(actionMasked)
}
else -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_ENTER)
}
}
ic?.endBatchEdit()
}
/**
@@ -498,6 +545,194 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
ic?.commitText(KeyCode.SPACE.toChar().toString(), 1)
}
/**
* Handles [KeyCode] arrow and move events, behaves differently depending on text selection.
*/
private fun handleArrow(code: Int) {
val ic = florisboard.currentInputConnection
resetComposingText()
if (isTextSelected && isManualSelectionMode) {
// Text is selected and it is manual selection -> Expand selection depending on started
// direction.
when (code) {
KeyCode.ARROW_DOWN -> {}
KeyCode.ARROW_LEFT -> {
if (isManualSelectionModeLeft) {
ic?.setSelection(
(selectionStart - 1).coerceAtLeast(selectionStartMin),
selectionEnd
)
} else {
ic?.setSelection(selectionStart, selectionEnd - 1)
}
}
KeyCode.ARROW_RIGHT -> {
if (isManualSelectionModeRight) {
ic?.setSelection(
selectionStart,
(selectionEnd + 1).coerceAtMost(selectionEndMax)
)
} else {
ic?.setSelection(selectionStart + 1, selectionEnd)
}
}
KeyCode.ARROW_UP -> {}
KeyCode.MOVE_HOME -> {
if (isManualSelectionModeLeft) {
ic?.setSelection(selectionStartMin, selectionEnd)
} else {
ic?.setSelection(selectionStartMin, selectionStart)
}
}
KeyCode.MOVE_END -> {
if (isManualSelectionModeRight) {
ic?.setSelection(selectionStart, selectionEndMax)
} else {
ic?.setSelection(selectionEnd, selectionEndMax)
}
}
}
} else if (isTextSelected && !isManualSelectionMode) {
// Text is selected but no manual selection mode -> arrows behave as if selection was
// started in manual left mode
when (code) {
KeyCode.ARROW_DOWN -> {}
KeyCode.ARROW_LEFT -> {
ic?.setSelection(selectionStart, selectionEnd - 1)
}
KeyCode.ARROW_RIGHT -> {
ic?.setSelection(
selectionStart,
(selectionEnd + 1).coerceAtMost(selectionEndMax)
)
}
KeyCode.ARROW_UP -> {}
KeyCode.MOVE_HOME -> {
ic?.setSelection(selectionStartMin, selectionStart)
}
KeyCode.MOVE_END -> {
ic?.setSelection(selectionStart, selectionEndMax)
}
}
} else if (!isTextSelected && isManualSelectionMode) {
// No text is selected but manual selection mode is active, user wants to start a new
// selection. Must set manual selection direction.
when (code) {
KeyCode.ARROW_DOWN -> {}
KeyCode.ARROW_LEFT -> {
ic?.setSelection(
(selectionStart - 1).coerceAtLeast(selectionStartMin),
selectionStart
)
isManualSelectionModeLeft = true
isManualSelectionModeRight = false
}
KeyCode.ARROW_RIGHT -> {
ic?.setSelection(
selectionEnd,
(selectionEnd + 1).coerceAtMost(selectionEndMax)
)
isManualSelectionModeLeft = false
isManualSelectionModeRight = true
}
KeyCode.ARROW_UP -> {}
KeyCode.MOVE_HOME -> {
ic?.setSelection(selectionStartMin, selectionStart)
isManualSelectionModeLeft = true
isManualSelectionModeRight = false
}
KeyCode.MOVE_END -> {
ic?.setSelection(selectionEnd, selectionEndMax)
isManualSelectionModeLeft = false
isManualSelectionModeRight = true
}
}
} else {
// No selection and no manual selection mode -> move cursor around
when (code) {
KeyCode.ARROW_DOWN -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_DOWN)
KeyCode.ARROW_LEFT -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_LEFT)
KeyCode.ARROW_RIGHT -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_RIGHT)
KeyCode.ARROW_UP -> sendSystemKeyEvent(ic, KeyEvent.KEYCODE_DPAD_UP)
KeyCode.MOVE_HOME -> sendSystemKeyEventAlt(ic, KeyEvent.KEYCODE_DPAD_UP)
KeyCode.MOVE_END -> sendSystemKeyEventAlt(ic, KeyEvent.KEYCODE_DPAD_DOWN)
}
}
}
/**
* Handles a [KeyCode.CLIPBOARD_CUT] event.
* TODO: handle other data than text too, e.g. Uri, Intent, ...
*/
private fun handleClipboardCut() {
val ic = florisboard.currentInputConnection
val selectedText = ic?.getSelectedText(0)
if (selectedText != null) {
florisboard.clipboardManager
?.setPrimaryClip(ClipData.newPlainText(selectedText, selectedText))
}
resetComposingText()
ic?.commitText("", 1)
}
/**
* Handles a [KeyCode.CLIPBOARD_COPY] event.
* TODO: handle other data than text too, e.g. Uri, Intent, ...
*/
private fun handleClipboardCopy() {
val ic = florisboard.currentInputConnection
val selectedText = ic?.getSelectedText(0)
if (selectedText != null) {
florisboard.clipboardManager
?.setPrimaryClip(ClipData.newPlainText(selectedText, selectedText))
}
resetComposingText()
ic?.setSelection(selectionEnd, selectionEnd)
}
/**
* Handles a [KeyCode.CLIPBOARD_PASTE] event.
* TODO: handle other data than text too, e.g. Uri, Intent, ...
*/
private fun handleClipboardPaste() {
val ic = florisboard.currentInputConnection
val item = florisboard.clipboardManager?.primaryClip?.getItemAt(0)
val pasteText = item?.text
if (pasteText != null) {
resetComposingText()
ic?.commitText(pasteText, 1)
}
}
/**
* Handles a [KeyCode.CLIPBOARD_SELECT] event.
*/
private fun handleClipboardSelect() {
val ic = florisboard.currentInputConnection
resetComposingText()
if (isTextSelected) {
if (isManualSelectionMode && isManualSelectionModeLeft) {
ic?.setSelection(selectionStart, selectionStart)
} else {
ic?.setSelection(selectionEnd, selectionEnd)
}
isManualSelectionMode = false
} else {
isManualSelectionMode = !isManualSelectionMode
// Must recall to update UI properly
florisboard.onUpdateCursorAnchorInfo(lastCursorAnchorInfo)
}
}
/**
* Handles a [KeyCode.CLIPBOARD_SELECT_ALL] event.
*/
private fun handleClipboardSelectAll() {
val ic = florisboard.currentInputConnection
resetComposingText()
ic?.setSelection(selectionStartMin, selectionEndMax)
}
/**
* Main logic point for sending a key press. Different actions may occur depending on the given
* [KeyData]. This method handles all key press send events, which are text based. For media
@@ -509,6 +744,17 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
val ic = florisboard.currentInputConnection
when (keyData.code) {
KeyCode.ARROW_DOWN,
KeyCode.ARROW_LEFT,
KeyCode.ARROW_RIGHT,
KeyCode.ARROW_UP,
KeyCode.MOVE_HOME,
KeyCode.MOVE_END -> handleArrow(keyData.code)
KeyCode.CLIPBOARD_CUT -> handleClipboardCut()
KeyCode.CLIPBOARD_COPY -> handleClipboardCopy()
KeyCode.CLIPBOARD_PASTE -> handleClipboardPaste()
KeyCode.CLIPBOARD_SELECT -> handleClipboardSelect()
KeyCode.CLIPBOARD_SELECT_ALL -> handleClipboardSelectAll()
KeyCode.DELETE -> handleDelete()
KeyCode.ENTER -> handleEnter()
KeyCode.LANGUAGE_SWITCH -> florisboard.switchToNextSubtype()

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.text.editing
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.Button
import androidx.appcompat.widget.AppCompatImageButton
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import java.util.*
/**
* View class for managing and rendering an editing key.
*/
class EditingKeyView : AppCompatImageButton {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val data: KeyData
private var isKeyPressed: Boolean = false
private var osTimer: Timer? = null
private var label: String? = null
private var labelPaint: Paint = Paint().apply {
alpha = 255
color = 0
isAntiAlias = true
isFakeBoldText = false
textAlign = Paint.Align.CENTER
textSize = Button(context).textSize
typeface = Typeface.DEFAULT
}
var isHighlighted: Boolean = false
set(value) { field = value; invalidate() }
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.style.TextEditingButton)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
val code = when (id) {
R.id.arrow_down -> KeyCode.ARROW_DOWN
R.id.arrow_left -> KeyCode.ARROW_LEFT
R.id.arrow_right -> KeyCode.ARROW_RIGHT
R.id.arrow_up -> KeyCode.ARROW_UP
R.id.backspace -> KeyCode.DELETE
R.id.clipboard_copy -> KeyCode.CLIPBOARD_COPY
R.id.clipboard_cut -> KeyCode.CLIPBOARD_CUT
R.id.clipboard_paste -> KeyCode.CLIPBOARD_PASTE
R.id.move_home -> KeyCode.MOVE_HOME
R.id.move_end -> KeyCode.MOVE_END
R.id.select -> KeyCode.CLIPBOARD_SELECT
R.id.select_all -> KeyCode.CLIPBOARD_SELECT_ALL
else -> 0
}
data = KeyData(code)
context.obtainStyledAttributes(attrs, R.styleable.EditingKeyView).apply {
label = getString(R.styleable.EditingKeyView_android_text)
recycle()
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (!isEnabled || event == null) {
return false
}
super.onTouchEvent(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
isKeyPressed = true
florisboard?.keyPressVibrate()
florisboard?.keyPressSound(data)
when (data.code) {
KeyCode.ARROW_DOWN,
KeyCode.ARROW_LEFT,
KeyCode.ARROW_RIGHT,
KeyCode.ARROW_UP,
KeyCode.DELETE -> {
osTimer = Timer()
osTimer?.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
florisboard?.textInputManager?.sendKeyPress(data)
if (!isKeyPressed) {
osTimer?.cancel()
osTimer = null
}
}
}, 500, 50)
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isKeyPressed = false
osTimer?.cancel()
osTimer = null
if (event.actionMasked != MotionEvent.ACTION_CANCEL) {
florisboard?.textInputManager?.sendKeyPress(data)
}
}
else -> return false
}
return true
}
/**
* Draw the key label / drawable.
*/
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
imageTintList = ColorStateList.valueOf(when {
isEnabled -> prefs.theme.smartbarFgColor
else -> prefs.theme.smartbarFgColorAlt
})
// Draw label
val label = label
if (label != null) {
labelPaint.color = if (isHighlighted && isEnabled) {
prefs.theme.colorPrimary
} else if (!isEnabled) {
prefs.theme.smartbarFgColorAlt
} else {
prefs.theme.smartbarFgColor
}
val isPortrait =
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
if (!isPortrait) {
labelPaint.textSize *= 0.9f
}
val centerX = measuredWidth / 2.0f
val centerY = measuredHeight / 2.0f + (labelPaint.textSize - labelPaint.descent()) / 2
if (label.contains("\n")) {
// Even if more lines may be existing only the first 2 are shown
val labelLines = label.split("\n")
canvas.drawText(labelLines[0], centerX, centerY * 0.70f, labelPaint)
canvas.drawText(labelLines[1], centerX, centerY * 1.30f, labelPaint)
} else {
canvas.drawText(label, centerX, centerY, labelPaint)
}
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.text.editing
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
import android.view.inputmethod.CursorAnchorInfo
import androidx.constraintlayout.widget.ConstraintLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.setBackgroundTintColor2
/**
* View class for updating the key views depending on the current selection and clipboard state.
*/
class EditingKeyboardView : ConstraintLayout, FlorisBoard.EventListener {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private var arrowUpKey: EditingKeyView? = null
private var arrowDownKey: EditingKeyView? = null
private var selectKey: EditingKeyView? = null
private var selectAllKey: EditingKeyView? = null
private var cutKey: EditingKeyView? = null
private var copyKey: EditingKeyView? = null
private var pasteKey: EditingKeyView? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
florisboard?.addEventListener(this)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
arrowUpKey = findViewById(R.id.arrow_up)
arrowDownKey = findViewById(R.id.arrow_down)
selectKey = findViewById(R.id.select)
selectAllKey = findViewById(R.id.select_all)
cutKey = findViewById(R.id.clipboard_cut)
copyKey = findViewById(R.id.clipboard_copy)
pasteKey = findViewById(R.id.clipboard_paste)
}
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
val isSelectionActive = florisboard?.textInputManager?.isTextSelected ?: false
val isSelectionMode = florisboard?.textInputManager?.isManualSelectionMode ?: false
arrowUpKey?.isEnabled = !(isSelectionActive || isSelectionMode)
arrowDownKey?.isEnabled = !(isSelectionActive || isSelectionMode)
selectKey?.isHighlighted = isSelectionActive || isSelectionMode
selectAllKey?.visibility = when {
isSelectionActive -> View.GONE
else -> View.VISIBLE
}
cutKey?.visibility = when {
isSelectionActive -> View.VISIBLE
else -> View.GONE
}
copyKey?.isEnabled = isSelectionActive
pasteKey?.isEnabled = florisboard?.clipboardManager?.hasPrimaryClip() ?: false
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val height = when (heightMode) {
MeasureSpec.EXACTLY -> {
// Must be this size
heightSize
}
MeasureSpec.AT_MOST -> {
// Can't be bigger than...
(florisboard?.inputView?.desiredTextKeyboardViewHeight ?: 0).coerceAtMost(heightSize)
}
else -> {
// Be whatever you want
florisboard?.inputView?.desiredTextKeyboardViewHeight ?: 0
}
}
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setBackgroundTintColor2(this, prefs.theme.smartbarBgColor)
}
}

View File

@@ -69,4 +69,7 @@ object KeyCode {
const val SWITCH_TO_CLIPBOARD_CONTEXT = -214
const val TOGGLE_ONE_HANDED_MODE = -215
const val URI_COMPONENT_TLD = -255
const val KESHIDA = 1600
const val HALF_SPACE = 8204
}

View File

@@ -33,10 +33,10 @@ import androidx.core.view.children
import com.google.android.flexbox.FlexboxLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
import dev.patrickgold.florisboard.util.getColorFromAttr
import dev.patrickgold.florisboard.util.setBackgroundTintColor
import dev.patrickgold.florisboard.util.setBackgroundTintColor2
import java.util.*
/**
@@ -60,6 +60,7 @@ class KeyView(
}
private var osHandler: Handler? = null
private var osTimer: Timer? = null
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private var shouldBlockNextKeyCode: Boolean = false
private var drawable: Drawable? = null
@@ -174,7 +175,11 @@ class KeyView(
event ?: return false
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
keyboardView.popupManager.show(this)
florisboard?.prefs?.keyboard?.let {
if (it.popupEnabled){
keyboardView.popupManager.show(this)
}
}
isKeyPressed = true
florisboard?.keyPressVibrate()
florisboard?.keyPressSound(data)
@@ -190,7 +195,7 @@ class KeyView(
}
}, 500, 50)
}
val delayMillis = keyboardView.prefs.looknfeel.longPressDelay
val delayMillis = prefs.keyboard.longPressDelay
if (osHandler == null) {
osHandler = Handler()
}
@@ -333,20 +338,31 @@ class KeyView(
* Updates the background depending on [isKeyPressed] and [data].
*/
private fun updateKeyPressedBackground() {
if (data.code == KeyCode.ENTER) {
setBackgroundTintColor(
this, when {
isKeyPressed -> R.attr.colorPrimaryDark
else -> R.attr.colorPrimary
}
)
} else {
setBackgroundTintColor(
this, when {
isKeyPressed -> R.attr.key_bgColorPressed
else -> R.attr.key_bgColor
}
)
when (data.code) {
KeyCode.ENTER -> {
setBackgroundTintColor2(
this, when {
isKeyPressed -> prefs.theme.keyEnterBgColorPressed
else -> prefs.theme.keyEnterBgColor
}
)
}
KeyCode.SHIFT -> {
setBackgroundTintColor2(
this, when {
isKeyPressed -> prefs.theme.keyShiftBgColorPressed
else -> prefs.theme.keyShiftBgColor
}
)
}
else -> {
setBackgroundTintColor2(
this, when {
isKeyPressed -> prefs.theme.keyBgColorPressed
else -> prefs.theme.keyBgColor
}
)
}
}
}
@@ -426,14 +442,14 @@ class KeyView(
updateKeyPressedBackground()
if (data.type == KeyType.CHARACTER && data.code != KeyCode.SPACE
|| data.type == KeyType.NUMERIC
&& data.code != KeyCode.HALF_SPACE && data.code != KeyCode.KESHIDA || data.type == KeyType.NUMERIC
) {
label = getComputedLetter()
} else {
when (data.code) {
KeyCode.DELETE -> {
drawable = getDrawable(context, R.drawable.ic_backspace)
drawableColor = getColorFromAttr(context, R.attr.key_fgColor)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.ENTER -> {
val action = florisboard?.currentInputEditorInfo?.imeOptions ?: 0
@@ -447,29 +463,29 @@ class KeyView(
EditorInfo.IME_ACTION_SEND -> R.drawable.ic_send
else -> R.drawable.ic_arrow_right_alt
})
drawableColor = getColorFromAttr(context, R.attr.key_enter_fgColor)
drawableColor = prefs.theme.keyEnterFgColor
if (action and EditorInfo.IME_FLAG_NO_ENTER_ACTION > 0) {
drawable = getDrawable(context, R.drawable.ic_keyboard_return)
}
}
KeyCode.LANGUAGE_SWITCH -> {
drawable = getDrawable(context, R.drawable.ic_language)
drawableColor = getColorFromAttr(context, R.attr.key_fgColor)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.PHONE_PAUSE -> label = resources.getString(R.string.key__phone_pause)
KeyCode.PHONE_WAIT -> label = resources.getString(R.string.key__phone_wait)
KeyCode.SHIFT -> {
drawable = getDrawable(context, when {
florisboard?.textInputManager?.caps ?: false && florisboard?.textInputManager?.capsLock ?: false -> {
drawableColor = getColorFromAttr(context, R.attr.colorAccent)
drawableColor = prefs.theme.keyShiftFgColorCapsLock
R.drawable.ic_keyboard_capslock
}
florisboard?.textInputManager?.caps ?: false && !(florisboard?.textInputManager?.capsLock ?: false) -> {
drawableColor = getColorFromAttr(context, R.attr.key_fgColor)
drawableColor = prefs.theme.keyShiftFgColor
R.drawable.ic_keyboard_capslock
}
else -> {
drawableColor = getColorFromAttr(context, R.attr.key_fgColor)
drawableColor = prefs.theme.keyShiftFgColor
R.drawable.ic_keyboard_arrow_up
}
})
@@ -481,7 +497,7 @@ class KeyView(
KeyboardMode.PHONE,
KeyboardMode.PHONE2 -> {
drawable = getDrawable(context, R.drawable.ic_space_bar)
drawableColor = getColorFromAttr(context, R.attr.key_fgColor)
drawableColor = prefs.theme.keyFgColor
}
KeyboardMode.CHARACTERS -> {
label = florisboard?.activeSubtype?.locale?.displayName
@@ -491,7 +507,7 @@ class KeyView(
}
KeyCode.SWITCH_TO_MEDIA_CONTEXT -> {
drawable = getDrawable(context, R.drawable.ic_sentiment_satisfied)
drawableColor = getColorFromAttr(context, R.attr.key_fgColor)
drawableColor = prefs.theme.keyFgColor
}
KeyCode.SWITCH_TO_TEXT_CONTEXT,
KeyCode.VIEW_CHARACTERS -> {
@@ -513,6 +529,12 @@ class KeyView(
KeyCode.VIEW_SYMBOLS2 -> {
label = resources.getString(R.string.key__view_symbols2)
}
KeyCode.HALF_SPACE -> {
label = resources.getString(R.string.key__view_half_space)
}
KeyCode.KESHIDA -> {
label = resources.getString(R.string.key__view_keshida)
}
}
}
@@ -547,12 +569,12 @@ class KeyView(
} else {
labelPaint.textSize = resources.getDimension(R.dimen.key_textSize)
}
labelPaint.color = getColorFromAttr(context, R.attr.key_fgColor)
labelPaint.color = prefs.theme.keyFgColor
labelPaint.alpha = if (keyboardView.computedLayout?.mode == KeyboardMode.CHARACTERS &&
data.code == KeyCode.SPACE) { 120 } else { 255 }
val isPortrait =
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
if (keyboardView.prefs.looknfeel.oneHandedMode != "off" && isPortrait) {
if (prefs.keyboard.oneHandedMode != "off" && isPortrait) {
labelPaint.textSize *= 0.9f
}
val centerX = measuredWidth / 2.0f

View File

@@ -18,6 +18,7 @@ package dev.patrickgold.florisboard.ime.text.keyboard
enum class KeyboardMode {
CHARACTERS,
EDITING,
SYMBOLS,
SYMBOLS2,
NUMERIC,

View File

@@ -18,9 +18,6 @@ package dev.patrickgold.florisboard.ime.text.keyboard
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewGroup
@@ -34,23 +31,24 @@ import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.popup.KeyPopupManager
import dev.patrickgold.florisboard.ime.text.key.KeyView
import dev.patrickgold.florisboard.ime.text.layout.ComputedLayoutData
import dev.patrickgold.florisboard.util.getColorFromAttr
import kotlin.math.roundToInt
/**
* Manages the layout of the keyboard, key measurement, key selection and all touch events.
* Supports multi touch events.
* Supports multi touch events. Note that the keyboard's background is transparent. The 'real'
* background of this keyboard is the background of the underlying mainViewFlipper. This prevents
* rendering issues when a keyboard is being loaded for the first time.
*
* TODO: Implement swipe gesture support
*
* @property florisboard Reference to instance of core class [FlorisBoard].
*/
class KeyboardView : LinearLayout {
class KeyboardView : LinearLayout, FlorisBoard.EventListener {
private var activeKeyView: KeyView? = null
private var activePointerId: Int? = null
private var activeX: Float = 0.0f
private var activeY: Float = 0.0f
private var colorDrawable: ColorDrawable
var computedLayout: ComputedLayoutData? = null
set(v) {
field = v
@@ -58,21 +56,20 @@ class KeyboardView : LinearLayout {
}
var desiredKeyWidth: Int = resources.getDimension(R.dimen.key_width).toInt()
var desiredKeyHeight: Int = resources.getDimension(R.dimen.key_height).toInt()
var florisboard: FlorisBoard? = null
var florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
var isPreviewMode: Boolean = false
var popupManager = KeyPopupManager<KeyboardView, KeyView>(this)
lateinit var prefs: PrefHelper
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
colorDrawable = ColorDrawable(getColorFromAttr(context, R.attr.keyboard_bgColor))
background = colorDrawable
orientation = VERTICAL
layoutParams = layoutParams ?: FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
florisboard?.addEventListener(this)
}
/**
@@ -237,32 +234,21 @@ class KeyboardView : LinearLayout {
val keyMarginH = resources.getDimension((R.dimen.key_marginH)).toInt()
desiredKeyWidth = (widthSize / 10) - (2 * keyMarginH)
val factor = prefs.looknfeel.heightFactor
val keyHeightFactor = when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> 0.85f
else -> if (prefs.looknfeel.oneHandedMode == "start" ||
prefs.looknfeel.oneHandedMode == "end") {
0.9f
} else {
1.0f
}
} * when (factor) {
"extra_short" -> 0.85f
"short" -> 0.90f
"mid_short" -> 0.95f
"normal" -> 1.00f
"mid_tall" -> 1.05f
"tall" -> 1.10f
"extra_tall" -> 1.15f
else -> 1.00f
} * when (isPreviewMode) {
val keyMarginV = resources.getDimension((R.dimen.key_marginV)).toInt()
val keyHeightFactor = when (isPreviewMode) {
true -> 0.90f
else -> 1.00f
}
desiredKeyHeight = (resources.getDimension(R.dimen.key_height) * keyHeightFactor).toInt()
florisboard?.textInputManager?.smartbarManager?.smartbarView?.setHeightFactor(keyHeightFactor)
val desiredHeight = keyHeightFactor * (florisboard?.inputView?.desiredTextKeyboardViewHeight ?: resources.getDimension(R.dimen.textKeyboardView_baseHeight).toInt())
desiredKeyHeight = (desiredHeight / 4 - 2 * keyMarginV).roundToInt()
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(desiredHeight.roundToInt(), MeasureSpec.EXACTLY))
}
override fun onApplyThemeAttributes() {
if (isPreviewMode) {
setBackgroundColor(prefs.theme.keyboardBgColor)
}
}
/**
@@ -309,10 +295,4 @@ class KeyboardView : LinearLayout {
}
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
colorDrawable.color = getColorFromAttr(context, R.attr.keyboard_bgColor)
}
}

View File

@@ -24,6 +24,7 @@ data class LayoutData(
val type: LayoutType,
val name: String,
val direction: String,
val modifier: String?,
val arrangement: LayoutDataArrangement = listOf()
) {
private fun getComputedLayoutDataArrangement(): ComputedLayoutDataArrangement {

View File

@@ -120,7 +120,12 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
val computedArrangement: ComputedLayoutDataArrangement = mutableListOf()
val mainLayout = loadLayout(main)
val modifierLayout = loadLayout(modifier)
val modifierToLoad = if (mainLayout?.modifier != null) {
LTN(LayoutType.CHARACTERS_MOD, mainLayout.modifier)
} else {
modifier
}
val modifierLayout = loadLayout(modifierToLoad)
val extensionLayout = loadLayout(extension)
if (extensionLayout != null) {
@@ -139,14 +144,12 @@ class LayoutManager(private val context: Context) : CoroutineScope by MainScope(
// merge main and mod here
val mergedRow = mutableListOf<KeyData>()
val firstModRow = modifierLayout.arrangement.firstOrNull()
val firstModKey = firstModRow?.firstOrNull()
if (firstModKey != null) {
mergedRow.add(firstModKey)
}
mergedRow.addAll(mainRow)
val lastModKey = firstModRow?.lastOrNull()
if (lastModKey != null && firstModKey != lastModKey) {
mergedRow.add(lastModKey)
for (modKey in (firstModRow ?: listOf())) {
if (modKey.code == 0) {
mergedRow.addAll(mainRow)
} else {
mergedRow.add(modKey)
}
}
computedArrangement.add(mergedRow)
}

View File

@@ -1,12 +1,9 @@
package dev.patrickgold.florisboard.ime.text.smartbar
import android.content.Context
import android.util.Log
import android.view.View
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SpellCheckerSession
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextServicesManager
import android.view.ViewGroup
import android.view.inputmethod.CursorAnchorInfo
import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
@@ -15,22 +12,21 @@ import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.text.TextInputManager
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
// TODO: Implement suggestion creation functionality
// TODO: Cleanup and reorganize SmartbarManager
class SmartbarManager private constructor() :
SpellCheckerSession.SpellCheckerSessionListener, FlorisBoard.EventListener {
class SmartbarManager private constructor() : FlorisBoard.EventListener {
private val florisboard: FlorisBoard = FlorisBoard.getInstance()
private var isComposingEnabled: Boolean = false
private var spellCheckerSession: SpellCheckerSession? = null
private val textInputManager: TextInputManager = TextInputManager.getInstance()
var smartbarView: SmartbarView? = null
private set
var activeContainerId: Int = R.id.candidates
var isQuickActionsVisible: Boolean = false
set(value) { field = value; updateActiveContainerVisibility() }
private val candidateViewOnClickListener = View.OnClickListener { v ->
@@ -43,7 +39,7 @@ class SmartbarManager private constructor() :
private val candidateViewOnLongClickListener = View.OnLongClickListener { v ->
true
}
private val numberRowButtonOnClickListener = View.OnClickListener { v ->
private val keyButtonOnClickListener = View.OnClickListener { v ->
val keyData = when (v.id) {
R.id.number_row_0 -> KeyData(48, "0")
R.id.number_row_1 -> KeyData(49, "1")
@@ -55,26 +51,40 @@ class SmartbarManager private constructor() :
R.id.number_row_7 -> KeyData(55, "7")
R.id.number_row_8 -> KeyData(56, "8")
R.id.number_row_9 -> KeyData(57, "9")
R.id.cc_select_all -> KeyData(KeyCode.CLIPBOARD_SELECT_ALL)
R.id.cc_copy -> KeyData(KeyCode.CLIPBOARD_COPY)
R.id.cc_arrow_left -> KeyData(KeyCode.ARROW_LEFT)
R.id.cc_arrow_right -> KeyData(KeyCode.ARROW_RIGHT)
R.id.cc_cut -> KeyData(KeyCode.CLIPBOARD_CUT)
R.id.cc_paste -> KeyData(KeyCode.CLIPBOARD_PASTE)
else -> KeyData(0)
}
florisboard.textInputManager.sendKeyPress(keyData)
}
private val quickActionOnClickListener = View.OnClickListener { v ->
when (v.id) {
R.id.quick_action_switch_to_media_context -> {
activeContainerId = getPreferredContainerId()
florisboard.setActiveInput(R.id.media_input)
R.id.back_button -> {
florisboard.textInputManager.setActiveKeyboardMode(KeyboardMode.CHARACTERS)
smartbarView?.setActiveVariant(R.id.smartbar_variant_default)
}
R.id.quick_action_switch_to_editing_context -> {
if (florisboard.textInputManager.getActiveKeyboardMode() == KeyboardMode.EDITING) {
florisboard.textInputManager.setActiveKeyboardMode(KeyboardMode.CHARACTERS)
smartbarView?.setActiveVariant(R.id.smartbar_variant_default)
} else {
florisboard.textInputManager.setActiveKeyboardMode(KeyboardMode.EDITING)
smartbarView?.setActiveVariant(R.id.smartbar_variant_back_only)
}
}
R.id.quick_action_switch_to_media_context -> florisboard.setActiveInput(R.id.media_input)
R.id.quick_action_open_settings -> florisboard.launchSettings()
R.id.quick_action_one_handed_toggle -> florisboard.toggleOneHandedMode()
else -> return@OnClickListener
}
isQuickActionsVisible = false
}
private val quickActionToggleOnClickListener = View.OnClickListener {
activeContainerId = when (activeContainerId) {
R.id.quick_actions -> getPreferredContainerId()
else -> R.id.quick_actions
}
isQuickActionsVisible = !isQuickActionsVisible
}
companion object {
@@ -94,7 +104,7 @@ class SmartbarManager private constructor() :
this.smartbarView = smartbarView
smartbarView.quickActionToggle?.setOnClickListener(quickActionToggleOnClickListener)
smartbarView.findViewById<View>(R.id.quick_action_toggle)?.setOnClickListener(quickActionToggleOnClickListener)
val quickActions = smartbarView.findViewById<LinearLayout>(R.id.quick_actions)
for (quickAction in quickActions.children) {
if (quickAction is ImageButton) {
@@ -104,13 +114,27 @@ class SmartbarManager private constructor() :
val numberRow = smartbarView.findViewById<LinearLayout>(R.id.number_row)
for (numberRowButton in numberRow.children) {
if (numberRowButton is Button) {
numberRowButton.setOnClickListener(numberRowButtonOnClickListener)
numberRowButton.setOnClickListener(keyButtonOnClickListener)
}
}
val clipboardCursorRow = smartbarView.findViewById<ViewGroup>(R.id.clipboard_cursor_row)
for (clipboardCursorRowButton in clipboardCursorRow.children) {
if (clipboardCursorRowButton is ImageButton) {
clipboardCursorRowButton.setOnClickListener(keyButtonOnClickListener)
}
}
val backButton = smartbarView.findViewById<View>(R.id.back_button)
backButton.setOnClickListener(quickActionOnClickListener)
for (candidateView in smartbarView.candidateViewList) {
candidateView.setOnClickListener(candidateViewOnClickListener)
candidateView.setOnLongClickListener(candidateViewOnLongClickListener)
}
smartbarView.setActiveVariant(R.id.smartbar_variant_default)
}
override fun onWindowShown() {
isQuickActionsVisible = false
}
// TODO: clean up resources here
@@ -120,49 +144,15 @@ class SmartbarManager private constructor() :
instance = null
}
override fun onGetSuggestions(arr: Array<out SuggestionsInfo>?) {
if (arr == null || arr.isEmpty()) {
return
}
/*val suggestions = arr[0]
for (i in 0 until suggestions.suggestionsCount) {
candidateViewList[i].text = suggestions.getSuggestionAt(i)
if (i == 2) {
break
}
}*/
}
override fun onGetSentenceSuggestions(arr: Array<out SentenceSuggestionsInfo>?) {
if (arr == null || arr.isEmpty()) {
return
}
/*val suggestions = arr[0].getSuggestionsInfoAt(0)
for (i in 0 until suggestions.suggestionsCount) {
candidateViewList[i].text = suggestions.getSuggestionAt(i)
if (i == 2) {
break
}
}*/
}
fun onStartInputView(keyboardMode: KeyboardMode, isComposingEnabled: Boolean) {
this.isComposingEnabled = isComposingEnabled
when {
keyboardMode == KeyboardMode.NUMERIC ||
keyboardMode == KeyboardMode.PHONE ||
keyboardMode == KeyboardMode.PHONE2 -> {
smartbarView?.visibility = View.GONE
}
!isComposingEnabled -> {
smartbarView?.visibility = View.VISIBLE
activeContainerId = R.id.number_row
when (keyboardMode) {
KeyboardMode.NUMERIC, KeyboardMode.PHONE, KeyboardMode.PHONE2 -> {
smartbarView?.setActiveVariant(null)
}
else -> {
smartbarView?.visibility = View.VISIBLE
activeContainerId = R.id.candidates
//val tsm = florisboard.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) as TextServicesManager
//spellCheckerSession = tsm.newSpellCheckerSession(null, null, this, true)
smartbarView?.setActiveVariant(R.id.smartbar_variant_default)
isQuickActionsVisible = false
}
}
}
@@ -171,6 +161,14 @@ class SmartbarManager private constructor() :
//spellCheckerSession?.close()
}
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
val isSelectionActive = florisboard.textInputManager.isTextSelected
smartbarView?.findViewById<View>(R.id.cc_cut)?.isEnabled = isSelectionActive
smartbarView?.findViewById<View>(R.id.cc_copy)?.isEnabled = isSelectionActive
smartbarView?.findViewById<View>(R.id.cc_paste)?.isEnabled =
florisboard.clipboardManager?.hasPrimaryClip() ?: false
}
fun deleteCandidateFromDictionary(candidate: String) {
//
}
@@ -187,8 +185,6 @@ class SmartbarManager private constructor() :
smartbarView.candidateViewList[1].text = "suggestions"
smartbarView.candidateViewList[2].text = "nyi"
} else {
activeContainerId = R.id.candidates
updateActiveContainerVisibility()
smartbarView.candidateViewList[0].text = ""
smartbarView.candidateViewList[1].text = composingText + "test"
smartbarView.candidateViewList[2].text = ""
@@ -213,44 +209,25 @@ class SmartbarManager private constructor() :
//
}
fun getPreferredContainerId(): Int {
return when {
!isComposingEnabled -> when(textInputManager.getActiveKeyboardMode()) {
KeyboardMode.CHARACTERS -> R.id.number_row
else -> 0
}
else -> R.id.candidates
}
}
private fun updateActiveContainerVisibility() {
val smartbarView = smartbarView ?: return
when (activeContainerId) {
R.id.quick_actions -> {
smartbarView.candidatesView?.visibility = View.GONE
smartbarView.numberRowView?.visibility = View.GONE
smartbarView.quickActionsView?.visibility = View.VISIBLE
smartbarView.quickActionToggle?.rotation = -180.0f
}
R.id.number_row -> {
smartbarView.candidatesView?.visibility = View.GONE
smartbarView.numberRowView?.visibility = View.VISIBLE
smartbarView.quickActionsView?.visibility = View.GONE
smartbarView.quickActionToggle?.rotation = 0.0f
}
R.id.candidates -> {
smartbarView.candidatesView?.visibility = View.VISIBLE
smartbarView.numberRowView?.visibility = View.GONE
smartbarView.quickActionsView?.visibility = View.GONE
smartbarView.quickActionToggle?.rotation = 0.0f
}
else -> {
smartbarView.candidatesView?.visibility = View.GONE
smartbarView.numberRowView?.visibility = View.GONE
smartbarView.quickActionsView?.visibility = View.GONE
smartbarView.quickActionToggle?.rotation = 0.0f
if (isQuickActionsVisible) {
smartbarView.setActiveContainer(R.id.quick_actions)
smartbarView.findViewById<View>(R.id.quick_action_toggle)?.rotation = -180.0f
} else {
if (isComposingEnabled) {
smartbarView.setActiveContainer(R.id.candidates)
} else if (textInputManager.getActiveKeyboardMode() == KeyboardMode.CHARACTERS) {
smartbarView.setActiveContainer(when (florisboard.prefs.suggestion.showInstead) {
"number_row" -> R.id.number_row
"clipboard_cursor_tools" -> R.id.clipboard_cursor_row
else -> null
})
} else {
smartbarView.setActiveContainer(null)
}
smartbarView.findViewById<View>(R.id.quick_action_toggle)?.rotation = 0.0f
}
}
}

View File

@@ -17,13 +17,17 @@
package dev.patrickgold.florisboard.ime.text.smartbar
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.setBackgroundTintColor2
/**
* Basically the same as an ImageButton.
* @see [onMeasure] why this view class exists.
*/
class SmartbarQuickActionButton : androidx.appcompat.widget.AppCompatImageButton {
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@@ -37,4 +41,10 @@ class SmartbarQuickActionButton : androidx.appcompat.widget.AppCompatImageButton
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(heightMeasureSpec, heightMeasureSpec)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setBackgroundTintColor2(this, prefs.theme.smartbarButtonBgColor)
setColorFilter(prefs.theme.smartbarButtonFgColor)
}
}

View File

@@ -17,13 +17,22 @@
package dev.patrickgold.florisboard.ime.text.smartbar
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
import androidx.annotation.IdRes
import androidx.core.view.children
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.setImageTintColor2
import kotlinx.android.synthetic.main.florisboard.view.*
/**
* View class which keeps the references to important children and informs [SmartbarManager] that
@@ -31,19 +40,15 @@ import dev.patrickgold.florisboard.R
* a theme change).
*/
class SmartbarView : LinearLayout {
private val florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val smartbarManager = SmartbarManager.getInstance()
var candidatesView: LinearLayout? = null
private set
private var variants: MutableList<ViewGroup> = mutableListOf()
private var containers: MutableList<ViewGroup> = mutableListOf()
var candidateViewList: MutableList<Button> = mutableListOf()
private set
var numberRowView: LinearLayout? = null
private set
var quickActionsView: LinearLayout? = null
private set
var quickActionToggle: ImageButton? = null
private set
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@@ -54,24 +59,106 @@ class SmartbarView : LinearLayout {
super.onAttachedToWindow()
candidatesView = findViewById(R.id.candidates)
variants.add(findViewById(R.id.smartbar_variant_default))
variants.add(findViewById(R.id.smartbar_variant_back_only))
containers.add(findViewById(R.id.candidates))
containers.add(findViewById(R.id.clipboard_cursor_row))
containers.add(findViewById(R.id.number_row))
containers.add(findViewById(R.id.quick_actions))
candidateViewList.add(findViewById(R.id.candidate0))
candidateViewList.add(findViewById(R.id.candidate1))
candidateViewList.add(findViewById(R.id.candidate2))
numberRowView = findViewById(R.id.number_row)
quickActionsView = findViewById(R.id.quick_actions)
quickActionToggle = findViewById(R.id.quick_action_toggle)
smartbarManager.registerSmartbarView(this)
}
/**
* Multiplies the default smartbar height with the given [factor] and sets it.
* Sets the active Smartbar variant based on the given id. Pass null to hide all variants and
* show an empty Smartbar.
*
* @param which Which variant to show. Pass null to hide all.
*/
fun setHeightFactor(factor: Float) {
val baseSize = resources.getDimension(R.dimen.smartbar_height)
val size = (baseSize * factor).toInt()
layoutParams?.height = size
fun setActiveVariant(@IdRes which: Int?) {
for (variant in variants) {
if (variant.id == which) {
variant.visibility = View.VISIBLE
} else {
variant.visibility = View.GONE
}
}
}
/**
* Sets the active Smartbar container based on the given id. Does only work if the currently
* shown Smartbar variant is [R.id.smartbar_variant_default]. Pass null to hide all containers
* and show only the quick action toggle.
*
* @param which Which container to show. Pass null to hide all.
*/
fun setActiveContainer(@IdRes which: Int?) {
for (container in containers) {
if (container.id == which) {
container.visibility = View.VISIBLE
} else {
container.visibility = View.GONE
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val height = when (heightMode) {
MeasureSpec.EXACTLY -> {
// Must be this size
heightSize
}
MeasureSpec.AT_MOST -> {
// Can't be bigger than...
(florisboard?.inputView?.desiredSmartbarHeight ?: 0).coerceAtMost(heightSize)
}
else -> {
// Be whatever you want
florisboard?.inputView?.desiredSmartbarHeight ?: 0
}
}
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
setBackgroundColor(prefs.theme.smartbarBgColor)
for (container in containers) {
when (container.id) {
R.id.number_row -> {
for (button in container.children) {
if (button is Button) {
button.setTextColor(prefs.theme.smartbarFgColor)
}
}
}
R.id.clipboard_cursor_row -> {
for (button in container.children) {
if (button is ImageButton) {
if (button.isEnabled) {
setImageTintColor2(button, prefs.theme.smartbarFgColor)
} else {
setImageTintColor2(button, prefs.theme.smartbarFgColorAlt)
}
}
}
}
R.id.candidates -> {
for (view in container.children) {
if (view is Button) {
view.setTextColor(prefs.theme.smartbarFgColor)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,310 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.theme
import android.content.Context
import android.graphics.Color
import com.squareup.moshi.Json
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dev.patrickgold.florisboard.ime.core.PrefHelper
/**
* Data class which holds a parsed theme json file. Used for loading a theme
* preset in Settings.
* Note: this implementation is generic and allows for any group/attr names.
* FlorisBoard itself expects certain groups and attrs to be able to
* color the controls accordingly. See 'ime/themes/floris_day.json'
* for a good example of which attributes FlorisBoard needs!
*
* @property name A unique id/name for this theme. Must only contain certain
* characters: upper/lower case letters, numbers (not at the beginning!) or
* an underline (_).
* @property displayName The name of this theme when shown to the user. Can
* contain any valid Unicode character.
* @property author The name of the author of this theme. Should be your
* username on GitHub/GitLab/BitBucket/... or your full name.
* @property isNightTheme If this theme is meant for display at day (false)
* or night (true). This property is only used to auto-assign this theme to
* either the day or night theme list in Settings, which is used when the
* user wants to auto-set his theme based on the current time.
* @property rawAttrs Map which holds the raw attributes of this theme. Note
* that the name of this property is 'attributes' within the json file!
* Attributes are always grouped together. This ensures a better structure
* and easier storage. The group- as well as the attr-name has the same
* limitations as the theme [name].
* Attribute values can be of different format:
* 1. A color
* Either #RRGGBB or #AARRGGBB (case-insensitive) -> e.g. #A034FF23
* 2. A static word
* - transparent (=0x00000000)
* - true (=0x1)
* - false (=0x0)
* 3. A reference to another attribute within the SAME theme, as follows:
* @group/attrName -> e.g. @window/textColor
* Note that referencing attributes has its limitations:
* a. Recursive references will cause an exception.
* b. Referencing an previously defined attribute is fine.
* c. Referencing an attribute not-yet defined is also ok, as long as
* the reference can be resolved at the next iteration.
* d. If the next iteration cannot resolve a value, an exception is
* thrown.
* 4. If the value is of any other format, an exception will be thrown.
*
* @throws IllegalArgumentException either at an invalid value or when a
* reference cannot be resolved.
*/
data class Theme(
val name: String,
val displayName: String,
val author: String,
val isNightTheme: Boolean = false,
@Json(name = "attributes")
private val rawAttrs: Map<String, Map<String, String>>
) {
/**
* Holds the parsed attributes after init.
*/
val parsedAttrs: MutableMap<String, MutableMap<String, Int>> = mutableMapOf()
companion object {
/**
* Loads a theme from the specified [path].
*
* @param context A reference to the current [Context]. Used to request
* asset file.
* @param path The path to the json theme file in the asset folder.
* @returns A parsed [Theme] or null. A null value may indicate that
* the file does not exist or that an error during the reading
* of the file occurred.
*/
fun fromJsonFile(context: Context, path: String): Theme? {
val rawJsonData: String = try {
context.assets.open(path).bufferedReader().use { it.readText() }
} catch (e: Exception) {
null
} ?: return null
return fromJsonString(rawJsonData)
}
/**
* Loads a theme from the given [rawData].
*
* @param rawData The raw json theme file as a string.
* @returns A parsed [Theme] or null. A null value may indicate that an error
* during the reading of the [rawData] occurred.
*/
fun fromJsonString(rawData: String): Theme? {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val layoutAdapter = moshi.adapter(Theme::class.java)
return layoutAdapter.fromJson(rawData)
}
/**
* Writes a given [theme] to the [prefs]. The default color values are based off the
* Floris Day theme and are not intended to be modified. Instead, themes should be defined
* in assets/ime/theme/<theme_id>.json
*
* @param theme The theme data.
* @param prefs The preference object to write the theme to.
*/
fun writeThemeToPrefs(prefs: PrefHelper, theme: Theme) {
// Internal prefs part I
prefs.internal.themeCurrentBasedOn = theme.name
prefs.internal.themeCurrentIsNight = theme.isNightTheme
// Theme attributes
prefs.theme.colorPrimary = theme.getAttr("window/colorPrimary", "#4CAF50")
prefs.theme.colorPrimaryDark = theme.getAttr("window/colorPrimaryDark", "#388E3C")
prefs.theme.colorAccent = theme.getAttr("window/colorAccent", "#FF9800")
prefs.theme.navBarColor = theme.getAttr("window/navigationBarColor", "#E0E0E0")
prefs.theme.navBarIsLight = (theme.getAttrOrNull("window/navigationBarLight") ?: 0) > 0
prefs.theme.keyboardBgColor = theme.getAttr("keyboard/bgColor", "#E0E0E0")
prefs.theme.keyBgColor = theme.getAttr("key/bgColor", "#FFFFFF")
prefs.theme.keyBgColorPressed = theme.getAttr("key/bgColorPressed", "#F5F5F5")
prefs.theme.keyFgColor = theme.getAttr("key/fgColor", "#000000")
prefs.theme.keyEnterBgColor = theme.getAttr("keyEnter/bgColor", "#4CAF50")
prefs.theme.keyEnterBgColorPressed = theme.getAttr("keyEnter/bgColorPressed", "#388E3C")
prefs.theme.keyEnterFgColor = theme.getAttr("keyEnter/fgColor", "#FFFFFF")
prefs.theme.keyPopupBgColor = theme.getAttr("keyPopup/bgColor", "#EEEEEE")
prefs.theme.keyPopupBgColorActive = theme.getAttr("keyPopup/bgColorActive", "#BDBDBD")
prefs.theme.keyPopupFgColor = theme.getAttr("keyPopup/fgColor", "#000000")
prefs.theme.keyShiftBgColor = theme.getAttr("keyShift/bgColor", "#FFFFFF")
prefs.theme.keyShiftBgColorPressed = theme.getAttr("keyShift/bgColorPressed", "#F5F5F5")
prefs.theme.keyShiftFgColor = theme.getAttr("keyShift/fgColor", "#000000")
prefs.theme.keyShiftFgColorCapsLock = theme.getAttr("keyShift/fgColorCapsLock", "#FF9800")
prefs.theme.mediaFgColor = theme.getAttr("media/fgColor", "#000000")
prefs.theme.mediaFgColorAlt = theme.getAttr("media/fgColorAlt", "#757575")
prefs.theme.oneHandedBgColor = theme.getAttr("oneHanded/bgColor", "#E8F5E9")
prefs.theme.oneHandedButtonFgColor = theme.getAttr("oneHandedButton/fgColor", "#424242")
prefs.theme.smartbarBgColor = theme.getAttr("smartbar/bgColor", "#E0E0E0")
prefs.theme.smartbarFgColor = theme.getAttr("smartbar/fgColor", "#000000")
prefs.theme.smartbarFgColorAlt = theme.getAttr("smartbar/fgColorAlt", "#4A000000")
prefs.theme.smartbarButtonBgColor = theme.getAttr("smartbarButton/bgColor", "#FFFFFF")
prefs.theme.smartbarButtonFgColor = theme.getAttr("smartbarButton/fgColor", "#000000")
// Internal prefs part II (must be written at the end!!)
prefs.internal.themeCurrentIsModified = false
}
}
init {
val listOfAttrsToReevaluate = mutableListOf<Triple<String, String, String>>()
for (group in rawAttrs) {
val groupMap = mutableMapOf<String, Int>()
parsedAttrs[group.key] = groupMap
for (attr in group.value) {
val colorRegex = """[#]([0-9a-fA-F]{8}|[0-9a-fA-F]{6})""".toRegex()
val refRegex = """[@]([a-zA-Z_][a-zA-Z0-9_]*)[/]([a-zA-Z_][a-zA-Z0-9_]*)""".toRegex()
when {
attr.value.matches(colorRegex) -> {
groupMap[attr.key] = Color.parseColor(attr.value)
}
attr.value == "transparent" -> {
groupMap[attr.key] = Color.TRANSPARENT
}
attr.value == "true" -> {
groupMap[attr.key] = 0x1
}
attr.value == "false" -> {
groupMap[attr.key] = 0x0
}
attr.value.matches(refRegex) -> {
val attrValue = getAttrOrNull(attr.value.substring(1))
if (attrValue != null) {
groupMap[attr.key] = attrValue
} else {
listOfAttrsToReevaluate.add(Triple(group.key, attr.key, attr.value))
}
}
else -> {
throw IllegalArgumentException("The specified attr '${attr.key}' = '${attr.value}' is not valid!")
}
}
}
}
for (attrToReevaluate in listOfAttrsToReevaluate) {
val attrValue = getAttrOrNull(attrToReevaluate.third.substring(1))
if (attrValue != null) {
parsedAttrs[attrToReevaluate.first]?.put(attrToReevaluate.second, attrValue)
} else {
throw IllegalArgumentException("The specified attr '${attrToReevaluate.second}' = '${attrToReevaluate.third}' is not valid!")
}
}
}
fun getAttr(key: String, defaultColor: String): Int {
return getAttrOrNull(key) ?: Color.parseColor(defaultColor)
}
fun getAttr(group: String, attr: String, defaultColor: String): Int {
return getAttrOrNull(group, attr) ?: Color.parseColor(defaultColor)
}
fun getAttrOrNull(key: String): Int? {
val regex = """([a-zA-Z_][a-zA-Z0-9_]*)[/]([a-zA-Z_][a-zA-Z0-9_]*)""".toRegex()
return if (key.matches(regex)) {
val split = key.split("/")
getAttrOrNull(split[0], split[1])
} else {
null
}
}
fun getAttrOrNull(group: String, attr: String): Int? {
return parsedAttrs[group]?.get(attr)
}
}
/**
* Data class which is used to quickly parse only the relevant meta data to
* display a theme in a selection list.
*
* @see [Theme] for details regarding the attributes and the theme structure.
*/
data class ThemeMetaOnly(
val name: String,
val displayName: String,
val author: String,
val isNightTheme: Boolean = false
) {
companion object {
/**
* Loads the theme meta data from the specified [path].
*
* @param context A reference to the current [Context]. Used to request
* asset file.
* @param path The path to the json theme file in the asset folder.
* @returns [ThemeMetaOnly] or null. A null value may indicate that
* the file does not exist or that an error during the reading
* of the file occurred.
*/
fun loadFromJsonFile(context: Context, path: String): ThemeMetaOnly? {
val rawJsonData: String = try {
context.assets.open(path).bufferedReader().use { it.readText() }
} catch (e: Exception) {
null
} ?: return null
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val layoutAdapter = moshi.adapter(ThemeMetaOnly::class.java)
return layoutAdapter.fromJson(rawJsonData)
}
/**
* Loads all theme meta data from the specified [path].
*
* @param context A reference to the current [Context]. Used to request
* asset file.
* @param path The path to the dir in the asset folder.
* @returns [ThemeMetaOnly] or null. A null value may indicate that
* the file does not exist or that an error during the reading
* of the file occurred.
*/
fun loadAllFromDir(context: Context, path: String): List<ThemeMetaOnly> {
val ret = mutableListOf<ThemeMetaOnly>()
try {
val list = context.assets.list(path)
if (list != null && list.isNotEmpty()) {
// Is a folder
for (file in list) {
val subList = context.assets.list("$path/$file")
if (subList?.isEmpty() == true) {
// Is file
val metaData = loadFromJsonFile(context, "$path/$file")
if (metaData != null) {
ret.add(metaData)
}
}
}
}
} catch (e: java.lang.Exception) {}
return ret
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings
import android.content.SharedPreferences
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.AdvancedActivityBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.util.PackageManagerUtils
class AdvancedActivity : AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var binding: AdvancedActivityBinding
private lateinit var prefs: PrefHelper
companion object {
const val RESULT_APPLY_THEME = 0x322D
}
override fun onCreate(savedInstanceState: Bundle?) {
prefs = PrefHelper.getDefaultInstance(this)
super.onCreate(savedInstanceState)
binding = AdvancedActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setTitle(R.string.settings__advanced__title)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
prefs.sync()
if (key == PrefHelper.Advanced.SETTINGS_THEME) {
setResult(RESULT_APPLY_THEME)
finish()
}
}
override fun onResume() {
prefs.shared.registerOnSharedPreferenceChangeListener(this)
super.onResume()
}
override fun onPause() {
prefs.shared.unregisterOnSharedPreferenceChangeListener(this)
updateLauncherIconStatus()
super.onPause()
}
private fun updateLauncherIconStatus() {
// Set LauncherAlias enabled/disabled state just before destroying/pausing this activity
if (prefs.advanced.showAppIcon) {
PackageManagerUtils.showAppIcon(this)
} else {
PackageManagerUtils.hideAppIcon(this)
}
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsFragmentAdvancedBinding
class AdvancedFragment : SettingsMainActivity.SettingsFragment() {
private lateinit var binding: SettingsFragmentAdvancedBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = SettingsFragmentAdvancedBinding.inflate(inflater, container, false)
val transaction = childFragmentManager.beginTransaction()
transaction.replace(
binding.prefsAdvancedFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_advanced)
)
transaction.commit()
return binding.root
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsFragmentLooknfeelBinding
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
import kotlinx.coroutines.*
class LooknfeelFragment : SettingsMainActivity.SettingsFragment(), CoroutineScope by MainScope() {
private lateinit var binding: SettingsFragmentLooknfeelBinding
private lateinit var keyboardView: KeyboardView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = SettingsFragmentLooknfeelBinding.inflate(inflater, container, false)
launch(Dispatchers.Default) {
val themeContext = ContextThemeWrapper(context, prefs.theme.getSelectedThemeResId())
val layoutManager = LayoutManager(themeContext)
keyboardView = KeyboardView(themeContext)
keyboardView.prefs = prefs
keyboardView.isPreviewMode = true
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(KeyboardMode.CHARACTERS, Subtype.DEFAULT).await()
keyboardView.updateVisibility()
withContext(Dispatchers.Main) {
binding.themeLinearLayout.addView(keyboardView, 0)
}
}
val transaction = childFragmentManager.beginTransaction()
transaction.replace(
binding.prefsLooknfeelFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_looknfeel)
)
transaction.replace(
binding.prefsThemeFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_theme)
)
transaction.commit()
return binding.root
}
override fun onDestroy() {
cancel()
super.onDestroy()
}
}

View File

@@ -28,32 +28,33 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.bottomnavigation.BottomNavigationView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsActivityBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.settings.fragments.*
import dev.patrickgold.florisboard.util.AppVersionUtils
import dev.patrickgold.florisboard.util.PackageManagerUtils
private const val FRAGMENT_TAG = "FRAGMENT_TAG"
internal const val FRAGMENT_TAG = "FRAGMENT_TAG"
private const val PREF_RES_ID = "PREF_RES_ID"
private const val SELECTED_ITEM_ID = "SELECTED_ITEM_ID"
private const val ADVANCED_REQ_CODE = 0x145F
class SettingsMainActivity : AppCompatActivity(),
BottomNavigationView.OnNavigationItemSelectedListener,
SharedPreferences.OnSharedPreferenceChangeListener {
lateinit var binding: SettingsActivityBinding
lateinit var prefs: PrefHelper
private lateinit var prefs: PrefHelper
lateinit var subtypeManager: SubtypeManager
override fun onCreate(savedInstanceState: Bundle?) {
prefs = PrefHelper(this, PreferenceManager.getDefaultSharedPreferences(this))
prefs = PrefHelper.getDefaultInstance(this)
prefs.initDefaultPreferences()
subtypeManager =
SubtypeManager(this, prefs)
prefs.sync()
subtypeManager = SubtypeManager(this, prefs)
val mode = when (prefs.advanced.settingsTheme) {
"light" -> AppCompatDelegate.MODE_NIGHT_NO
@@ -99,9 +100,14 @@ class SettingsMainActivity : AppCompatActivity(),
loadFragment(KeyboardFragment())
true
}
R.id.settings__navigation__looknfeel -> {
supportActionBar?.setTitle(R.string.settings__looknfeel__title)
loadFragment(LooknfeelFragment())
R.id.settings__navigation__typing -> {
supportActionBar?.setTitle(R.string.settings__typing__title)
loadFragment(TypingFragment())
true
}
R.id.settings__navigation__theme -> {
supportActionBar?.setTitle(R.string.settings__theme__title)
loadFragment(ThemeFragment())
true
}
R.id.settings__navigation__gestures -> {
@@ -109,20 +115,15 @@ class SettingsMainActivity : AppCompatActivity(),
loadFragment(GesturesFragment())
true
}
R.id.settings__navigation__advanced -> {
supportActionBar?.setTitle(R.string.settings__advanced__title)
loadFragment(AdvancedFragment())
true
}
else -> false
}
}
private fun loadFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(binding.pageFrame.id, fragment, FRAGMENT_TAG)
//transaction.addToBackStack(null)
transaction.commit()
supportFragmentManager
.beginTransaction()
.replace(binding.pageFrame.id, fragment, FRAGMENT_TAG)
.commit()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@@ -130,6 +131,14 @@ class SettingsMainActivity : AppCompatActivity(),
return true
}
override fun onBackPressed() {
if (binding.bottomNavigation.selectedItemId != R.id.settings__navigation__home) {
binding.bottomNavigation.selectedItemId = R.id.settings__navigation__home
} else {
super.onBackPressed()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
@@ -144,6 +153,10 @@ class SettingsMainActivity : AppCompatActivity(),
startActivity(browserIntent)
true
}
R.id.settings__menu_advanced -> {
startActivityForResult(Intent(this, AdvancedActivity::class.java), ADVANCED_REQ_CODE)
true
}
R.id.settings__menu_about -> {
startActivity(Intent(this, AboutActivity::class.java))
true
@@ -152,22 +165,18 @@ class SettingsMainActivity : AppCompatActivity(),
}
}
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
if (key == PrefHelper.Advanced.SETTINGS_THEME) {
recreate()
}
val fragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
if (fragment != null && fragment.isVisible) {
if (fragment is LooknfeelFragment) {
if (key == PrefHelper.Theme.NAME) {
// TODO: recreate() is only a lazy solution, better would be to only recreate
// the keyboard view
recreate()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == ADVANCED_REQ_CODE) {
if (resultCode == AdvancedActivity.RESULT_APPLY_THEME) {
recreate()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {}
private fun updateLauncherIconStatus() {
// Set LauncherAlias enabled/disabled state just before destroying/pausing this activity
if (prefs.advanced.showAppIcon) {
@@ -195,7 +204,6 @@ class SettingsMainActivity : AppCompatActivity(),
}
abstract class SettingsFragment : Fragment() {
protected lateinit var prefs: PrefHelper
protected lateinit var settingsMainActivity: SettingsMainActivity
protected lateinit var subtypeManager: SubtypeManager
@@ -203,7 +211,6 @@ class SettingsMainActivity : AppCompatActivity(),
super.onCreate(savedInstanceState)
settingsMainActivity = activity as SettingsMainActivity
prefs = settingsMainActivity.prefs
subtypeManager = settingsMainActivity.subtypeManager
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings.components
import android.app.AlertDialog
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.SeekBar
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SeekBarDialogBinding
/**
* Custom preference which represents a seek bar which shows the current value in the summary. The
* value can be changed by clicking on the preference, which brings up a dialog which a seek bar.
* This implementation also allows for a min / max step value, while being backwards compatible.
*
* @see R.styleable.DialogSeekBarPreferenceAttrs for which xml attributes this preference accepts
* besides the default Preference attributes.
*
* @property defaultValue The default value of this preference.
* @property systemDefaultValue At this exact value [systemDefaultValueText] should be shown instead
* of the actual value.
* @property systemDefaultValueText The text to show if this preference's value or seek bar is
* [systemDefaultValue]. Set to null to disable the system default text feature.
* @property min The minimum value of the seek bar. Must not be greater or equal than [max].
* @property max The maximum value of the seek bar. Must not be lesser or equal than [min].
* @property step The step in which the seek bar increases per move. If the provided value is less
* than 1, 1 will be used as step. Note that the xml attribute's name for this property is
* [R.styleable.DialogSeekBarPreferenceAttrs_seekBarIncrement].
* @property unit The unit to show after the value. Set to an empty string to disable this feature.
*/
class DialogSeekBarPreference : Preference {
private var defaultValue: Int = 0
private var systemDefaultValue: Int = -1
private var systemDefaultValueText: String? = null
private var min: Int = 0
private var max: Int = 100
private var step: Int = 1
private var unit: String = ""
@Suppress("unused")
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
layoutResource = R.layout.list_item
context.obtainStyledAttributes(attrs, R.styleable.DialogSeekBarPreferenceAttrs).apply {
min = getInt(R.styleable.DialogSeekBarPreferenceAttrs_min, min)
max = getInt(R.styleable.DialogSeekBarPreferenceAttrs_max, max)
step = getInt(R.styleable.DialogSeekBarPreferenceAttrs_seekBarIncrement, step)
if (step < 1) {
step = 1
}
defaultValue = getInt(R.styleable.DialogSeekBarPreferenceAttrs_android_defaultValue, defaultValue)
systemDefaultValue = getInt(R.styleable.DialogSeekBarPreferenceAttrs_systemDefaultValue, min - 1)
systemDefaultValueText = getString(R.styleable.DialogSeekBarPreferenceAttrs_systemDefaultValueText)
unit = getString(R.styleable.DialogSeekBarPreferenceAttrs_unit) ?: unit
recycle()
}
onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
summary = getTextForValue(newValue.toString())
true
}
onPreferenceClickListener = OnPreferenceClickListener {
showSeekBarDialog()
true
}
}
override fun onAttachedToHierarchy(preferenceManager: PreferenceManager?) {
super.onAttachedToHierarchy(preferenceManager)
summary = getTextForValue(sharedPreferences.getInt(key, defaultValue))
}
/**
* Generates the text for the given [value] and adds the defined [unit] at the end.
* If [systemDefaultValueText] is not null this method tries to match the given [value] with
* [systemDefaultValue] and returns [systemDefaultValueText] upon matching.
*/
private fun getTextForValue(value: Any): String {
if (value !is Int) {
return "??$unit"
}
val systemDefValText = systemDefaultValueText
return if (value == systemDefaultValue && systemDefValText != null) {
systemDefValText
} else {
value.toString() + unit
}
}
/**
* Shows the seek bar dialog.
*/
private fun showSeekBarDialog() {
val inflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val dialogView = SeekBarDialogBinding.inflate(inflater)
val initValue = sharedPreferences.getInt(key, defaultValue)
dialogView.seekBar.max = actualValueToSeekBarProgress(max)
dialogView.seekBar.progress = actualValueToSeekBarProgress(initValue)
dialogView.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
dialogView.seekBarValue.text = getTextForValue(seekBarProgressToActualValue(progress))
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
dialogView.seekBarValue.text = getTextForValue(initValue)
AlertDialog.Builder(context).apply {
setTitle(this@DialogSeekBarPreference.title)
setCancelable(true)
setView(dialogView.root)
setPositiveButton(android.R.string.ok) { _, _ ->
val actualValue = seekBarProgressToActualValue(dialogView.seekBar.progress)
sharedPreferences.edit().putInt(key, actualValue).apply()
}
setNeutralButton(R.string.settings__default) { _, _ ->
sharedPreferences.edit().putInt(key, defaultValue).apply()
}
setNegativeButton(android.R.string.cancel, null)
setOnDismissListener { summary = getTextForValue(sharedPreferences.getInt(key, defaultValue)) }
create()
show()
}
}
/**
* Converts the actual value to a progress value which the Android SeekBar implementation can
* handle. (Android's SeekBar step is fixed at 1 and min at 0)
*
* @param actual The actual value.
* @returns the internal value which is used to allow different min and step values.
*/
private fun actualValueToSeekBarProgress(actual: Int): Int {
return (actual - min) / step
}
/**
* Converts the Android SeekBar value to the actual value.
*
* @param progress The progress value of the SeekBar.
* @returns the actual value which is ready to use.
*/
private fun seekBarProgressToActualValue(progress: Int): Int {
return (progress * step) + min
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings.components
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.ThemeSelectorDialogBinding
import dev.patrickgold.florisboard.databinding.ThemeSelectorListItemBinding
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.theme.Theme
import dev.patrickgold.florisboard.ime.theme.ThemeMetaOnly
/**
* Custom preference which handles the theme preset selection dialog and shows a summary in the
* list.
*/
class ThemePresetSelectorPreference : Preference, SharedPreferences.OnSharedPreferenceChangeListener {
private var dialog: AlertDialog? = null
private val metaDataCache: MutableMap<String, ThemeMetaOnly> = mutableMapOf()
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
@Suppress("unused")
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
layoutResource = R.layout.list_item
onPreferenceClickListener = OnPreferenceClickListener {
showThemeSelectorDialog()
true
}
}
override fun onAttachedToHierarchy(preferenceManager: PreferenceManager?) {
super.onAttachedToHierarchy(preferenceManager)
summary = generateSummaryText()
prefs.shared.registerOnSharedPreferenceChangeListener(this)
}
override fun onDetached() {
if (dialog?.isShowing == true) {
dialog?.dismiss()
}
prefs.shared.unregisterOnSharedPreferenceChangeListener(this)
super.onDetached()
}
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
if (key == PrefHelper.Internal.THEME_CURRENT_IS_MODIFIED) {
summary = generateSummaryText()
}
}
/**
* Generates the summary text to display and returns it. Based on the prefs.internal.theme*
* values and the theme meta cache.
*/
private fun generateSummaryText(): String {
val themeKey = prefs.internal.themeCurrentBasedOn
val isModified = prefs.internal.themeCurrentIsModified
var metaOnly: ThemeMetaOnly? = metaDataCache[themeKey]
if (metaOnly == null) {
try {
metaOnly = ThemeMetaOnly.loadFromJsonFile(context, "ime/theme/$themeKey.json")
} catch (e: Exception) {
return context.resources.getString(R.string.settings__theme__undefined)
}
}
metaOnly ?: return context.resources.getString(R.string.settings__theme__undefined)
return if (isModified) {
String.format(context.resources.getString(R.string.settings__theme__preset_summary), metaOnly.displayName)
} else {
metaOnly.displayName
}
}
/**
* Shows the theme selector dialog.
*/
private fun showThemeSelectorDialog() {
val inflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val dialogView = ThemeSelectorDialogBinding.inflate(inflater)
val selectedThemeView = ThemeSelectorListItemBinding.inflate(inflater)
selectedThemeView.title.text = generateSummaryText()
dialogView.content.addView(selectedThemeView.root, 1)
metaDataCache.clear()
ThemeMetaOnly.loadAllFromDir(context, "ime/theme").forEach { metaData ->
metaDataCache[metaData.name] = metaData
}
for ((themeKey, metaData) in metaDataCache) {
if (themeKey == prefs.internal.themeCurrentBasedOn && !prefs.internal.themeCurrentIsModified) {
continue
}
val availableThemeView = ThemeSelectorListItemBinding.inflate(inflater)
availableThemeView.title.text = metaData.displayName
availableThemeView.root.setOnClickListener {
applyThemePreset(metaData.name)
dialog?.dismiss()
}
dialogView.content.addView(availableThemeView.root)
}
AlertDialog.Builder(context).apply {
setTitle(this@ThemePresetSelectorPreference.title)
setCancelable(true)
setView(dialogView.root)
setPositiveButton(android.R.string.ok) { _, _ ->
//
}
setNeutralButton(R.string.settings__default) { _, _ ->
//
}
setNegativeButton(android.R.string.cancel, null)
setOnDismissListener { summary = generateSummaryText() }
create()
dialog = show()
dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = false
}
}
/**
* Applies the Theme for given [themeKey] to the preferences. Overrides any custom user-defined
* theme in the shared prefs, if existent.
*
* @param themeKey The key of the Theme preset to be applied.
*/
private fun applyThemePreset(themeKey: String) {
val theme = Theme.fromJsonFile(context, "ime/theme/$themeKey.json") ?: return
Theme.writeThemeToPrefs(prefs, theme)
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings.fragments
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
class AdvancedFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_advanced)
}
}

View File

@@ -14,25 +14,14 @@
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings
package dev.patrickgold.florisboard.settings.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsFragmentGesturesBinding
class GesturesFragment : SettingsMainActivity.SettingsFragment() {
private lateinit var binding: SettingsFragmentGesturesBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = SettingsFragmentGesturesBinding.inflate(inflater, container, false)
return binding.root
class GesturesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_gestures)
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings
package dev.patrickgold.florisboard.settings.fragments
import android.content.Intent
import android.net.Uri
@@ -25,6 +25,7 @@ import android.view.ViewGroup
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsFragmentHomeBinding
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.settings.SettingsMainActivity
import dev.patrickgold.florisboard.setup.SetupActivity
class HomeFragment : SettingsMainActivity.SettingsFragment() {
@@ -56,6 +57,12 @@ class HomeFragment : SettingsMainActivity.SettingsFragment() {
startActivity(this)
}
}
binding.localizationCard.setOnClickListener {
settingsMainActivity.binding.bottomNavigation.selectedItemId = R.id.settings__navigation__typing
}
binding.themeCard.setOnClickListener {
settingsMainActivity.binding.bottomNavigation.selectedItemId = R.id.settings__navigation__theme
}
return binding.root
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings.fragments
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
class KeyboardFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_keyboard)
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings.fragments
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import dev.patrickgold.florisboard.R
class ThemeCustomizeFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.prefs_theme)
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2020 Patrick Goldinger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings.fragments
import android.content.SharedPreferences
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsFragmentThemeBinding
import dev.patrickgold.florisboard.ime.core.FlorisBoard
import dev.patrickgold.florisboard.ime.core.PrefHelper
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardMode
import dev.patrickgold.florisboard.ime.text.keyboard.KeyboardView
import dev.patrickgold.florisboard.ime.text.layout.LayoutManager
import dev.patrickgold.florisboard.settings.SettingsMainActivity
import kotlinx.coroutines.*
class ThemeFragment : SettingsMainActivity.SettingsFragment(), CoroutineScope by MainScope(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var binding: SettingsFragmentThemeBinding
private lateinit var keyboardView: KeyboardView
private lateinit var prefs: PrefHelper
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
prefs = PrefHelper.getDefaultInstance(requireContext())
binding = SettingsFragmentThemeBinding.inflate(inflater, container, false)
launch(Dispatchers.Default) {
val themeContext = ContextThemeWrapper(context, FlorisBoard.getDayNightBaseThemeId(prefs.internal.themeCurrentIsNight))
val layoutManager = LayoutManager(themeContext)
keyboardView = KeyboardView(themeContext)
keyboardView.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
val m = resources.getDimension(R.dimen.keyboard_preview_margin).toInt()
setMargins(m, m, m, m)
}
prefs.sync()
keyboardView.isPreviewMode = true
val subtype = subtypeManager.getActiveSubtype() ?: Subtype.DEFAULT
keyboardView.computedLayout = layoutManager.fetchComputedLayoutAsync(KeyboardMode.CHARACTERS, subtype).await()
keyboardView.updateVisibility()
keyboardView.onApplyThemeAttributes()
withContext(Dispatchers.Main) {
binding.root.addView(keyboardView, 0)
}
}
loadThemePrefFragment()
return binding.root
}
private fun loadThemePrefFragment() {
childFragmentManager
.beginTransaction()
.replace(
binding.prefsFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_theme)
)
.commit()
}
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
prefs.sync()
key ?: return
if (key == PrefHelper.Internal.THEME_CURRENT_BASED_ON ||
key == PrefHelper.Internal.THEME_CURRENT_IS_MODIFIED && !prefs.internal.themeCurrentIsModified) {
loadThemePrefFragment()
}
if (key.startsWith("theme__")) {
prefs.internal.themeCurrentIsModified = true
keyboardView.onApplyThemeAttributes()
keyboardView.invalidate()
keyboardView.invalidateAllKeys()
}
}
override fun onResume() {
prefs.shared.registerOnSharedPreferenceChangeListener(this)
super.onResume()
}
override fun onPause() {
prefs.shared.unregisterOnSharedPreferenceChangeListener(this)
super.onPause()
}
override fun onDestroy() {
cancel()
super.onDestroy()
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package dev.patrickgold.florisboard.settings
package dev.patrickgold.florisboard.settings.fragments
import android.app.AlertDialog
import android.os.Bundle
@@ -23,16 +23,15 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import com.google.android.material.snackbar.Snackbar
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.databinding.SettingsFragmentKeyboardBinding
import dev.patrickgold.florisboard.databinding.SettingsFragmentKeyboardSubtypeDialogBinding
import dev.patrickgold.florisboard.databinding.SettingsFragmentKeyboardSubtypeListItemBinding
import dev.patrickgold.florisboard.databinding.ListItemBinding
import dev.patrickgold.florisboard.databinding.SettingsFragmentTypingBinding
import dev.patrickgold.florisboard.databinding.SettingsFragmentTypingSubtypeDialogBinding
import dev.patrickgold.florisboard.settings.SettingsMainActivity
import dev.patrickgold.florisboard.util.LocaleUtils
class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
private lateinit var binding: SettingsFragmentKeyboardBinding
class TypingFragment : SettingsMainActivity.SettingsFragment() {
private lateinit var binding: SettingsFragmentTypingBinding
/**
* Must always have a reference to the open AlertDialog to dismiss the AlertDialog in the event
* of onDestroy(), if this is not done a memory leak will most likely happen!
@@ -44,17 +43,18 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = SettingsFragmentKeyboardBinding.inflate(inflater, container, false)
binding = SettingsFragmentTypingBinding.inflate(inflater, container, false)
binding.subtypeAddBtn.setOnClickListener { showAddSubtypeDialog() }
updateSubtypeListView()
val transaction = childFragmentManager.beginTransaction()
transaction.replace(
binding.prefsKeyboardFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_keyboard)
)
transaction.commit()
childFragmentManager
.beginTransaction()
.replace(
binding.prefsFrame.id,
SettingsMainActivity.PrefFragment.createFromResource(R.xml.prefs_typing)
)
.commit()
return binding.root
}
@@ -66,7 +66,7 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
private fun showAddSubtypeDialog() {
val dialogView =
SettingsFragmentKeyboardSubtypeDialogBinding.inflate(layoutInflater)
SettingsFragmentTypingSubtypeDialogBinding.inflate(layoutInflater)
val languageAdapter: ArrayAdapter<String> = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
@@ -95,11 +95,11 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
)
dialogView.layoutSpinner.adapter = layoutAdapter
AlertDialog.Builder(context).apply {
setTitle(R.string.settings__keyboard__subtype_add_title)
setTitle(R.string.settings__localization__subtype_add_title)
setCancelable(true)
setView(dialogView.root)
setPositiveButton(R.string.settings__keyboard__subtype_add, null)
setNegativeButton(R.string.settings__keyboard__subtype_cancel) { _, _ -> }
setPositiveButton(R.string.settings__localization__subtype_add, null)
setNegativeButton(R.string.settings__localization__subtype_cancel) { _, _ -> }
setOnDismissListener { activeDialogWindow = null }
create()
activeDialogWindow = show()
@@ -110,7 +110,7 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
val layoutName = subtypeManager.imeConfig.characterLayouts.keys.toList()[dialogView.layoutSpinner.selectedItemPosition]
val success = subtypeManager.addSubtype(LocaleUtils.stringToLocale(languageCode), layoutName)
if (!success) {
dialogView.errorBox.setText(R.string.settings__keyboard__subtype_error_already_exists)
dialogView.errorBox.setText(R.string.settings__localization__subtype_error_already_exists)
dialogView.errorBox.visibility = View.VISIBLE
} else {
updateSubtypeListView()
@@ -123,7 +123,7 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
private fun showEditSubtypeDialog(id: Int) {
val subtype = subtypeManager.getSubtypeById(id) ?: return
val dialogView =
SettingsFragmentKeyboardSubtypeDialogBinding.inflate(layoutInflater)
SettingsFragmentTypingSubtypeDialogBinding.inflate(layoutInflater)
val languageAdapter: ArrayAdapter<String> = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
@@ -143,10 +143,10 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
subtypeManager.imeConfig.characterLayouts.keys.toList().indexOf(subtype.layout)
)
AlertDialog.Builder(context).apply {
setTitle(R.string.settings__keyboard__subtype_edit_title)
setTitle(R.string.settings__localization__subtype_edit_title)
setCancelable(true)
setView(dialogView.root)
setPositiveButton(R.string.settings__keyboard__subtype_apply) { _, _ ->
setPositiveButton(R.string.settings__localization__subtype_apply) { _, _ ->
val languageCode = subtypeManager.imeConfig.defaultSubtypesLanguageCodes[dialogView.languageSpinner.selectedItemPosition]
val layoutName = subtypeManager.imeConfig.characterLayouts.keys.toList()[dialogView.layoutSpinner.selectedItemPosition]
subtype.locale = LocaleUtils.stringToLocale(languageCode)
@@ -154,11 +154,11 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
subtypeManager.modifySubtypeWithSameId(subtype)
updateSubtypeListView()
}
setNeutralButton(R.string.settings__keyboard__subtype_delete) { _, _ ->
setNeutralButton(R.string.settings__localization__subtype_delete) { _, _ ->
subtypeManager.removeSubtype(subtype)
updateSubtypeListView()
}
setNegativeButton(R.string.settings__keyboard__subtype_cancel) { _, _ -> }
setNegativeButton(R.string.settings__localization__subtype_cancel) { _, _ -> }
setOnDismissListener { activeDialogWindow = null }
create()
activeDialogWindow = show()
@@ -174,9 +174,9 @@ class KeyboardFragment : SettingsMainActivity.SettingsFragment() {
binding.subtypeNotConfWarning.visibility = View.GONE
for (subtype in subtypes) {
val itemView =
SettingsFragmentKeyboardSubtypeListItemBinding.inflate(layoutInflater)
ListItemBinding.inflate(layoutInflater)
itemView.title.text = subtype.locale.displayName
itemView.caption.text = subtypeManager.imeConfig.characterLayouts[subtype.layout]
itemView.summary.text = subtypeManager.imeConfig.characterLayouts[subtype.layout]
itemView.root.setOnClickListener { showEditSubtypeDialog(subtype.id) }
binding.subtypeListView.addView(itemView.root)
}

View File

@@ -22,6 +22,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import dev.patrickgold.florisboard.databinding.SetupFragmentFinishBinding
import dev.patrickgold.florisboard.ime.theme.Theme
class FinishFragment : Fragment() {
private lateinit var binding: SetupFragmentFinishBinding
@@ -33,6 +34,12 @@ class FinishFragment : Fragment() {
): View? {
binding = SetupFragmentFinishBinding.inflate(inflater, container, false)
// Set theme to floris_day
Theme.writeThemeToPrefs(
(activity as SetupActivity).prefs,
Theme.fromJsonFile(requireContext(), "ime/theme/floris_day.json")!!
)
return binding.root
}

View File

@@ -17,6 +17,7 @@
package dev.patrickgold.florisboard.setup
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -26,6 +27,7 @@ import dev.patrickgold.florisboard.ime.core.FlorisBoard
class MakeDefaultFragment : Fragment(), SetupActivity.EventListener {
private lateinit var binding: SetupFragmentMakeDefaultBinding
private var osHandler: Handler? = null
override fun onCreateView(
inflater: LayoutInflater,
@@ -60,6 +62,11 @@ class MakeDefaultFragment : Fragment(), SetupActivity.EventListener {
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus && context != null) {
updateState()
if (osHandler == null) {
osHandler = Handler()
}
osHandler?.postDelayed({ updateState() }, 250)
osHandler?.postDelayed({ updateState() }, 500)
}
}
}

View File

@@ -40,7 +40,7 @@ class SetupActivity : AppCompatActivity() {
private lateinit var adapter: ViewPagerAdapter
private lateinit var binding: SetupActivityBinding
lateinit var imm: InputMethodManager
private lateinit var prefs: PrefHelper
lateinit var prefs: PrefHelper
private var shouldFinish: Boolean = false
private var shouldLaunchSettings: Boolean = true

View File

@@ -6,6 +6,7 @@ import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import androidx.core.view.children
fun getColorFromAttr(
@@ -33,16 +34,28 @@ fun setBackgroundTintColor(view: View, colorId: Int) {
getColorFromAttr(view.context, colorId)
)
}
fun setBackgroundTintColor2(view: View, colorInt: Int) {
view.backgroundTintList = ColorStateList.valueOf(colorInt)
}
fun setDrawableTintColor(view: Button, colorId: Int) {
view.compoundDrawableTintList = ColorStateList.valueOf(
getColorFromAttr(view.context, colorId)
)
}
fun setDrawableTintColor2(view: Button, colorInt: Int) {
view.compoundDrawableTintList = ColorStateList.valueOf(colorInt)
}
fun setImageTintColor2(view: ImageView, colorInt: Int) {
view.imageTintList = ColorStateList.valueOf(colorInt)
}
fun setTextTintColor(view: View, colorId: Int) {
view.foregroundTintList = ColorStateList.valueOf(
getColorFromAttr(view.context, colorId)
)
}
fun setTextTintColor2(view: View, colorInt: Int) {
view.foregroundTintList = ColorStateList.valueOf(colorInt)
}
fun refreshLayoutOf(view: View?) {
if (view is ViewGroup) {

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="?android:colorButtonNormal"/>
<item android:color="#FFFFFF"/>
</selector>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="?semiTransparentColor"/>
<stroke android:width="0.5dp" android:color="?semiTransparentColor"/>
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="?semiTransparentColor"/>
<stroke android:width="0.5dp" android:color="?semiTransparentColor"/>
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/transparent"/>
<stroke android:width="0.5dp" android:color="?semiTransparentColor"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M9.64,7.64c0.23,-0.5 0.36,-1.05 0.36,-1.64 0,-2.21 -1.79,-4 -4,-4S2,3.79 2,6s1.79,4 4,4c0.59,0 1.14,-0.13 1.64,-0.36L10,12l-2.36,2.36C7.14,14.13 6.59,14 6,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4c0,-0.59 -0.13,-1.14 -0.36,-1.64L12,14l7,7h3v-1L9.64,7.64zM6,8c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM6,20c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM12,12.5c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5 0.5,0.22 0.5,0.5 -0.22,0.5 -0.5,0.5zM19,3l-6,6 2,2 7,-7L22,3z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2L5,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20L5,20L5,4h2v3h10L17,4h2v16z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M18.41,16.59L13.82,12l4.59,-4.59L17,6l-6,6 6,6zM6,6h2v12H6z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M5.59,7.41L10.18,12l-4.59,4.59L7,18l6,-6 -6,-6zM16,6h2v12h-2z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12.45,16h2.09L9.43,3L7.57,3L2.46,16h2.09l1.12,-3h5.64l1.14,3zM6.43,11L8.5,5.48 10.57,11L6.43,11zM21.59,11.59l-8.09,8.09L9.83,16l-1.41,1.41 5.09,5.09L23,13l-1.41,-1.41z"/>
</vector>

View File

@@ -2,16 +2,16 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp" />
<solid android:color="?key_popup_extended_shadowColor" />
<corners android:radius="@dimen/key_borderRadius" />
<padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp"/>
<solid android:color="#CDAFAFAF"/>
<corners android:radius="@dimen/key_borderRadius"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="?key_popup_bgColor" />
<corners android:radius="@dimen/key_borderRadius" />
<solid android:color="@android:color/white"/>
<corners android:radius="@dimen/key_borderRadius"/>
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,16 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar"/>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/advanced_fragment_frame"
android:name="dev.patrickgold.florisboard.settings.fragments.AdvancedFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>

View File

@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyboardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/editing"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:backgroundTintMode="multiply">
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/arrow_left"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.2"
app:layout_constraintHeight_percent="0.75"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="@+id/move_home"
android:src="@drawable/ic_keyboard_arrow_left"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/arrow_up"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@+id/arrow_left"
android:src="@drawable/ic_keyboard_arrow_up"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/select"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toBottomOf="@+id/arrow_up"
app:layout_constraintLeft_toRightOf="@+id/arrow_left"
android:text="@android:string/selectTextMode"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/arrow_down"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toBottomOf="@+id/select"
app:layout_constraintLeft_toRightOf="@+id/arrow_left"
android:src="@drawable/ic_keyboard_arrow_down"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/arrow_right"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.2"
app:layout_constraintHeight_percent="0.75"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/move_end"
app:layout_constraintLeft_toRightOf="@+id/select"
android:src="@drawable/ic_keyboard_arrow_right"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/move_home"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.35"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_first_page"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/move_end"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.35"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintLeft_toRightOf="@+id/move_home"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_last_page"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/select_all"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@+id/arrow_right"
app:layout_constraintRight_toRightOf="parent"
android:text="@android:string/selectAll"
android:visibility="gone"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/clipboard_cut"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toTopOf="@+id/select_all"
app:layout_constraintLeft_toRightOf="@+id/arrow_right"
app:layout_constraintRight_toRightOf="parent"
android:text="@android:string/cut"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:barrierDirection="bottom"
app:constraint_referenced_ids="select_all,clipboard_cut"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/clipboard_copy"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toBottomOf="@+id/barrier1"
app:layout_constraintBottom_toTopOf="@+id/clipboard_paste"
app:layout_constraintLeft_toRightOf="@+id/arrow_right"
app:layout_constraintRight_toRightOf="parent"
android:text="@android:string/copy"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/clipboard_paste"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toBottomOf="@+id/clipboard_copy"
app:layout_constraintBottom_toTopOf="@+id/backspace"
app:layout_constraintLeft_toRightOf="@+id/arrow_right"
app:layout_constraintRight_toRightOf="parent"
android:text="@android:string/paste"/>
<dev.patrickgold.florisboard.ime.text.editing.EditingKeyView
android:id="@+id/backspace"
style="@style/TextEditingButton"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintHeight_percent="0.25"
app:layout_constraintTop_toBottomOf="@+id/clipboard_paste"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/move_end"
app:layout_constraintRight_toRightOf="parent"
android:src="@drawable/ic_backspace"/>
</dev.patrickgold.florisboard.ime.text.editing.EditingKeyboardView>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<dev.patrickgold.florisboard.ime.core.InputView
<dev.patrickgold.florisboard.ime.core.InputWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/florisboard"
android:layout_width="match_parent"
@@ -8,13 +8,13 @@
android:gravity="bottom"
android:orientation="vertical">
<LinearLayout
<dev.patrickgold.florisboard.ime.core.InputView
android:id="@+id/inner_input_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:background="?keyboard_bgColor"
android:background="?inputView_bgColorFallback"
android:baselineAligned="false">
<LinearLayout
@@ -25,32 +25,34 @@
<ImageButton
android:id="@+id/one_handed_ctrl_close_start"
style="@style/OneHandedPanelButton"
android:src="@drawable/ic_zoom_out_map"/>
android:src="@drawable/ic_zoom_out_map"
android:contentDescription="@string/one_handed__close_btn_content_description"/>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/one_handed_button_height"
android:visibility="invisible" />
android:visibility="invisible"/>
<ImageButton
android:id="@+id/one_handed_ctrl_move_start"
style="@style/OneHandedPanelButton"
android:src="@drawable/ic_keyboard_arrow_left"/>
android:src="@drawable/ic_keyboard_arrow_left"
android:contentDescription="@string/one_handed__move_start_btn_content_description"/>
</LinearLayout>
<ViewFlipper
<dev.patrickgold.florisboard.ime.core.FlorisViewFlipper
android:id="@+id/main_view_flipper"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:measureAllChildren="false">
<include layout="@layout/text_input_layout" />
<include layout="@layout/text_input_layout"/>
<include layout="@layout/media_input_layout" />
<include layout="@layout/media_input_layout"/>
</ViewFlipper>
</dev.patrickgold.florisboard.ime.core.FlorisViewFlipper>
<LinearLayout
android:id="@+id/one_handed_ctrl_panel_end"
@@ -60,20 +62,22 @@
<ImageButton
android:id="@+id/one_handed_ctrl_close_end"
style="@style/OneHandedPanelButton"
android:src="@drawable/ic_zoom_out_map"/>
android:src="@drawable/ic_zoom_out_map"
android:contentDescription="@string/one_handed__close_btn_content_description"/>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/one_handed_button_height"
android:visibility="invisible" />
android:visibility="invisible"/>
<ImageButton
android:id="@+id/one_handed_ctrl_move_end"
style="@style/OneHandedPanelButton"
android:src="@drawable/ic_keyboard_arrow_right"/>
android:src="@drawable/ic_keyboard_arrow_right"
android:contentDescription="@string/one_handed__move_end_btn_content_description"/>
</LinearLayout>
</LinearLayout>
</dev.patrickgold.florisboard.ime.core.InputView>
</dev.patrickgold.florisboard.ime.core.InputView>
</dev.patrickgold.florisboard.ime.core.InputWindowView>

View File

@@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<dev.patrickgold.florisboard.ime.popup.KeyPopupView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/key_popup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/key_popup_bgshape">
android:background="@drawable/key_popup_bgshape"
android:backgroundTintMode="multiply">
<TextView
android:id="@+id/key_popup_text"
android:layout_width="match_parent"
android:layout_height="@dimen/key_height"
android:gravity="center"
android:textColor="?attr/key_popup_fgColor"
android:textSize="@dimen/key_popup_textSize"/>
<ImageView
@@ -23,7 +24,6 @@
android:layout_gravity="end"
android:padding="0dp"
android:src="@drawable/ic_more_horiz"
android:tint="?attr/key_popup_fgColor"
android:visibility="visible" />
android:visibility="visible"/>
</LinearLayout>
</dev.patrickgold.florisboard.ime.popup.KeyPopupView>

View File

@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.flexbox.FlexboxLayout
<dev.patrickgold.florisboard.ime.popup.KeyPopupExtendedView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/key_popup_extended"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/key_popup_bgshape"
android:backgroundTintMode="multiply"
android:padding="0dp"
android:textSize="@dimen/key_popup_textSize"
app:flexDirection="row"
app:flexWrap="wrap"
app:showDivider="none" />
app:showDivider="none"/>

View File

@@ -0,0 +1,37 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:listPreferredItemHeightSmall"
android:orientation="vertical"
android:gravity="center_vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:clickable="true"
android:focusable="true"
android:background="?selectableItemBackground">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
tools:text="Title"/>
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4"
tools:text="Summary"/>
</LinearLayout>

View File

@@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<dev.patrickgold.florisboard.ime.media.MediaInputView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/media_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ViewFlipper
<dev.patrickgold.florisboard.ime.core.FlorisViewFlipper
android:id="@+id/media_input_view_flipper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:measureAllChildren="true"/>
android:layout_height="0dp"
android:layout_weight="1.0"
android:measureAllChildren="false"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.0"
android:orientation="horizontal">
<Button
@@ -87,4 +90,4 @@
</LinearLayout>
</LinearLayout>
</dev.patrickgold.florisboard.ime.media.MediaInputView>

View File

@@ -0,0 +1,22 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/seek_bar_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
tools:text="0%"/>
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GESTURES | not yet implemented..."/>
</LinearLayout>

View File

@@ -50,4 +50,28 @@
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/localization_card"
style="@style/SettingsCardView.Clickable">
<!-- TODO: create lang preview and make nice feature graphic for this card -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Localization prefs -->"/>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/theme_card"
style="@style/SettingsCardView.Clickable">
<!-- TODO: create theme preview and make nice feature graphic for this card -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Theme prefs -->"/>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -1,28 +0,0 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="64dp"
android:orientation="vertical"
android:padding="16dp"
android:clickable="true"
android:focusable="true"
android:background="?selectableItemBackground">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
tools:text="Title"/>
<TextView
android:id="@+id/caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="normal"
tools:text="Caption"/>
</LinearLayout>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
style="@style/SettingsCardView"
android:layout_marginTop="16dp"
android:layout_marginBottom="0dp">
<LinearLayout
android:id="@+id/themeLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- KeyboardView preview will be inserted here programmatically -->
<!-- Must be RelativeLayout, because it blocks RecyclerView to create a second scrollbar -->
<RelativeLayout
android:id="@+id/prefs_theme_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Must be RelativeLayout, because it blocks RecyclerView to create a second scrollbar -->
<RelativeLayout
android:id="@+id/prefs_looknfeel_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@@ -4,9 +4,11 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- KeyboardView preview will be inserted here programmatically -->
<!-- Must be RelativeLayout, because it blocks RecyclerView to create a second scrollbar -->
<RelativeLayout
android:id="@+id/prefs_advanced_frame"
android:id="@+id/prefs_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

View File

@@ -12,20 +12,31 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:theme="@style/PreferenceThemeOverlay">
<TextView
android:id="@+id/subtype_heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:text="@string/settings__localization__title"
android:textAppearance="?preferenceCategoryTitleTextAppearance"
android:textColor="?colorAccent"/>
<TextView
android:id="@+id/subtype_not_conf_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_margin="8dp"
android:padding="8dp"
android:background="@drawable/shape_rect_rounded"
android:backgroundTint="?colorWarning"
android:text="@string/settings__keyboard__subtype_no_subtypes_configured_warning"
android:text="@string/settings__localization__subtype_no_subtypes_configured_warning"
android:textColor="?textColorWarning"/>
<LinearLayout
@@ -40,7 +51,7 @@
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="false"
android:text="@string/settings__keyboard__subtype_add_title"
android:text="@string/settings__localization__subtype_add_title"
android:drawableStart="@drawable/ic_add"
android:drawablePadding="8dp"
android:drawableTint="?colorAccent"/>
@@ -51,7 +62,7 @@
<!-- Must be RelativeLayout, because it blocks RecyclerView to create a second scrollbar -->
<RelativeLayout
android:id="@+id/prefs_keyboard_frame"
android:id="@+id/prefs_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

View File

@@ -12,7 +12,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorError"
tools:text="@string/settings__keyboard__subtype_error_already_exists"
tools:text="@string/settings__localization__subtype_error_already_exists"
android:textColor="@color/textColorError"
android:layout_marginBottom="8dp"
android:padding="8dp"
@@ -27,7 +27,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings__keyboard__subtype_locale"
android:text="@string/settings__localization__subtype_locale"
android:paddingHorizontal="8dp"/>
<androidx.appcompat.widget.AppCompatSpinner
@@ -46,7 +46,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings__keyboard__subtype_layout"
android:text="@string/settings__localization__subtype_layout"
android:paddingHorizontal="8dp"/>
<androidx.appcompat.widget.AppCompatSpinner

View File

@@ -4,129 +4,205 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/smartbar"
android:layout_width="match_parent"
android:layout_height="@dimen/smartbar_height"
android:background="?smartbar_bgColor">
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_toggle"
style="@style/SmartbarQuickAction.Toggle"
android:contentDescription="@string/smartbar__quick_action_toggle__alt"
android:src="@drawable/ic_keyboard_arrow_right" />
android:layout_height="wrap_content"
android:background="@android:color/transparent">
<LinearLayout
android:id="@+id/candidates"
style="@style/SmartbarContainer"
android:visibility="gone">
<Button
android:id="@+id/candidate0"
style="@style/SmartbarCandidate" />
<View style="@style/SmartbarDivider" />
<Button
android:id="@+id/candidate1"
style="@style/SmartbarCandidate" />
<View style="@style/SmartbarDivider" />
<Button
android:id="@+id/candidate2"
style="@style/SmartbarCandidate" />
</LinearLayout>
<LinearLayout
android:id="@+id/quick_actions"
style="@style/SmartbarContainer"
android:id="@+id/smartbar_variant_default"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="gone">
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_switch_to_media_context"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__switch_to_media_context"
android:src="@drawable/ic_sentiment_satisfied" />
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_open_settings"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__open_settings"
android:src="@drawable/ic_settings" />
<!-- TODO: find better icon for one-handed mode -->
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_one_handed_toggle"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__one_handed_mode"
android:id="@+id/quick_action_toggle"
style="@style/SmartbarQuickAction.Toggle"
android:contentDescription="@string/smartbar__quick_action_toggle__alt"
android:src="@drawable/ic_keyboard_arrow_right" />
<LinearLayout
android:id="@+id/candidates"
style="@style/SmartbarContainer"
android:visibility="gone">
<Button
android:id="@+id/candidate0"
style="@style/SmartbarCandidate"/>
<View style="@style/SmartbarDivider"/>
<Button
android:id="@+id/candidate1"
style="@style/SmartbarCandidate"/>
<View style="@style/SmartbarDivider"/>
<Button
android:id="@+id/candidate2"
style="@style/SmartbarCandidate"/>
</LinearLayout>
<LinearLayout
android:id="@+id/quick_actions"
style="@style/SmartbarContainer"
android:visibility="gone">
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_switch_to_media_context"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__switch_to_media_context"
android:src="@drawable/ic_sentiment_satisfied"/>
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_open_settings"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__open_settings"
android:src="@drawable/ic_settings"/>
<!-- TODO: find better icon for one-handed mode -->
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_one_handed_toggle"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__one_handed_mode"
android:src="@drawable/ic_keyboard_arrow_right"/>
<!-- TODO: find better icon for editing -->
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/quick_action_switch_to_editing_context"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__switch_to_editing_context"
android:src="@drawable/ic_format_italic"/>
</LinearLayout>
<!-- TODO: integrate a KeyboardView instead of hardcoding these buttons -->
<LinearLayout
android:id="@+id/number_row"
style="@style/SmartbarContainer"
android:visibility="gone"
tools:ignore="HardcodedText">
<Button
android:id="@+id/number_row_1"
style="@style/SmartbarCandidate"
android:text="1"/>
<Button
android:id="@+id/number_row_2"
style="@style/SmartbarCandidate"
android:text="2"/>
<Button
android:id="@+id/number_row_3"
style="@style/SmartbarCandidate"
android:text="3"/>
<Button
android:id="@+id/number_row_4"
style="@style/SmartbarCandidate"
android:text="4"/>
<Button
android:id="@+id/number_row_5"
style="@style/SmartbarCandidate"
android:text="5"/>
<Button
android:id="@+id/number_row_6"
style="@style/SmartbarCandidate"
android:text="6"/>
<Button
android:id="@+id/number_row_7"
style="@style/SmartbarCandidate"
android:text="7"/>
<Button
android:id="@+id/number_row_8"
style="@style/SmartbarCandidate"
android:text="8"/>
<Button
android:id="@+id/number_row_9"
style="@style/SmartbarCandidate"
android:text="9"/>
<Button
android:id="@+id/number_row_0"
style="@style/SmartbarCandidate"
android:text="0"/>
</LinearLayout>
<!-- TODO: integrate a KeyboardView instead of hardcoding these buttons -->
<LinearLayout
android:id="@+id/clipboard_cursor_row"
style="@style/SmartbarContainer"
android:visibility="gone"
tools:ignore="HardcodedText">
<ImageButton
android:id="@+id/cc_select_all"
style="@style/SmartbarCandidate"
android:src="@drawable/ic_select_all"
android:tint="@drawable/button_key_enable_color_selector"/>
<ImageButton
android:id="@+id/cc_copy"
style="@style/SmartbarCandidate"
android:src="@drawable/ic_content_copy"
android:tint="@drawable/button_key_enable_color_selector"/>
<ImageButton
android:id="@+id/cc_arrow_left"
style="@style/SmartbarCandidate"
android:src="@drawable/ic_keyboard_arrow_left"
android:tint="@drawable/button_key_enable_color_selector"/>
<ImageButton
android:id="@+id/cc_arrow_right"
style="@style/SmartbarCandidate"
android:src="@drawable/ic_keyboard_arrow_right"
android:tint="@drawable/button_key_enable_color_selector"/>
<ImageButton
android:id="@+id/cc_cut"
style="@style/SmartbarCandidate"
android:src="@drawable/ic_content_cut"
android:tint="@drawable/button_key_enable_color_selector"/>
<ImageButton
android:id="@+id/cc_paste"
style="@style/SmartbarCandidate"
android:src="@drawable/ic_content_paste"
android:tint="@drawable/button_key_enable_color_selector"/>
</LinearLayout>
<!-- Placeholder on the right which reserves the space for a second button -->
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="@dimen/smartbar_button_margin"
android:clickable="false"
android:visibility="invisible"/>
</LinearLayout>
<!-- TODO: integrate a KeyboardView instead of hardcoding these buttons -->
<LinearLayout
android:id="@+id/number_row"
style="@style/SmartbarContainer"
android:visibility="gone"
tools:ignore="HardcodedText">
android:id="@+id/smartbar_variant_back_only"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="gone">
<Button
android:id="@+id/number_row_1"
style="@style/SmartbarCandidate"
android:text="1" />
<Button
android:id="@+id/number_row_2"
style="@style/SmartbarCandidate"
android:text="2" />
<Button
android:id="@+id/number_row_3"
style="@style/SmartbarCandidate"
android:text="3" />
<Button
android:id="@+id/number_row_4"
style="@style/SmartbarCandidate"
android:text="4" />
<Button
android:id="@+id/number_row_5"
style="@style/SmartbarCandidate"
android:text="5" />
<Button
android:id="@+id/number_row_6"
style="@style/SmartbarCandidate"
android:text="6" />
<Button
android:id="@+id/number_row_7"
style="@style/SmartbarCandidate"
android:text="7" />
<Button
android:id="@+id/number_row_8"
style="@style/SmartbarCandidate"
android:text="8" />
<Button
android:id="@+id/number_row_9"
style="@style/SmartbarCandidate"
android:text="9" />
<Button
android:id="@+id/number_row_0"
style="@style/SmartbarCandidate"
android:text="0" />
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:id="@+id/back_button"
style="@style/SmartbarQuickAction"
android:contentDescription="@string/smartbar__quick_action__exit_editing"
android:src="@drawable/ic_arrow_back"/>
</LinearLayout>
<!-- Placeholder on the right which reserves the space for a second button -->
<dev.patrickgold.florisboard.ime.text.smartbar.SmartbarQuickActionButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="@dimen/smartbar_button_margin"
android:clickable="false"
android:visibility="invisible" />
</dev.patrickgold.florisboard.ime.text.smartbar.SmartbarView>

View File

@@ -6,14 +6,14 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/smartbar" />
<include layout="@layout/smartbar"/>
<!-- KeyboardViews will be inserted in ViewFlipper below dynamically -->
<ViewFlipper
<dev.patrickgold.florisboard.ime.core.FlorisViewFlipper
android:id="@+id/text_input_view_flipper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:measureAllChildren="false">
android:measureAllChildren="true">
<LinearLayout
android:id="@+id/keyboard_preview"
@@ -31,6 +31,8 @@
</LinearLayout>
</ViewFlipper>
<include layout="@layout/editing_layout"/>
</dev.patrickgold.florisboard.ime.core.FlorisViewFlipper>
</LinearLayout>

View File

@@ -0,0 +1,29 @@
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/settings__theme__preset_dialog_selected_theme"
android:textColor="?android:textColorPrimary"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/settings__theme__preset_dialog_available_themes"
android:textColor="?android:textColorPrimary"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,36 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:clickable="true"
android:focusable="true"
android:background="?selectableItemBackground">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
tools:text="Theme Name"
android:textColor="?android:textColorSecondary"/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1.0"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:src="@drawable/ic_keyboard_arrow_right"
android:contentDescription="@string/settings__theme__preset_dialog_alt_arrow_right"
app:tint="?android:textColorSecondary"/>
</LinearLayout>

View File

@@ -6,17 +6,24 @@
android:icon="@drawable/ic_more_vert"
android:title="@string/settings__menu"
app:showAsAction="always">
<menu>
<item
android:id="@+id/settings__menu_help"
android:orderInCategory="1"
android:title="@string/settings__menu_help" />
android:title="@string/settings__menu_help"/>
<item
android:id="@+id/settings__menu_advanced"
android:orderInCategory="2"
android:title="@string/settings__menu_advanced"/>
<item
android:id="@+id/settings__menu_about"
android:orderInCategory="2"
android:title="@string/settings__menu_about" />
android:orderInCategory="3"
android:title="@string/settings__menu_about"/>
</menu>
</item>
</menu>

View File

@@ -4,26 +4,26 @@
<item
android:id="@+id/settings__navigation__home"
android:icon="@drawable/ic_home"
android:title="@string/settings__navigation__home" />
android:title="@string/settings__navigation__home"/>
<item
android:id="@+id/settings__navigation__keyboard"
android:icon="@drawable/ic_keyboard"
android:title="@string/settings__navigation__keyboard" />
android:title="@string/settings__navigation__keyboard"/>
<item
android:id="@+id/settings__navigation__looknfeel"
android:id="@+id/settings__navigation__typing"
android:icon="@drawable/ic_spellcheck"
android:title="@string/settings__navigation__typing"/>
<item
android:id="@+id/settings__navigation__theme"
android:icon="@drawable/ic_palette"
android:title="@string/settings__navigation__looknfeel" />
android:title="@string/settings__navigation__theme"/>
<item
android:id="@+id/settings__navigation__gestures"
android:icon="@drawable/ic_gesture"
android:title="@string/settings__navigation__gestures" />
<item
android:id="@+id/settings__navigation__advanced"
android:icon="@drawable/ic_more_vert"
android:title="@string/settings__navigation__advanced" />
android:title="@string/settings__navigation__gestures"/>
</menu>

View File

@@ -0,0 +1,123 @@
<resources>
<string name="key__phone_pause">Pausa</string>
<string name="key__phone_wait">Attendi</string>
<string name="key_popup__threedots_alt">Icona a tre puntini.Se visibile, indica che è possibile utilizzare più lettere se premuto a lungo.</string>
<!-- Media strings -->
<string name="media__tab__emojis">Emojis</string>
<string name="media__tab__emoticons">Emoticons</string>
<string name="media__tab__kaomoji">Kaomoji</string>
<!-- Emoji strings -->
<string name="emoji__category__smileys_emotion">Smileys &amp; Emotions</string>
<string name="emoji__category__people_body">Persone &amp; Corpo</string>
<string name="emoji__category__animals_nature">Animali &amp; Natura</string>
<string name="emoji__category__food_drink">Cibo &amp; Bevande</string>
<string name="emoji__category__travel_places">Viaggi &amp; Luoghi</string>
<string name="emoji__category__activities">Attività</string>
<string name="emoji__category__objects">Oggetti</string>
<string name="emoji__category__symbols">Simboli</string>
<string name="emoji__category__flags">Bandiere</string>
<!-- Smartbar strings -->
<string name="smartbar__quick_action_toggle__alt">Attiva / disattiva azione rapida. Se premuto, alterna i suggerimenti di parole ed i pulsanti di azione rapida.</string>
<string name="smartbar__quick_action__one_handed_mode">Attiva / disattiva la modalità a una mano.</string>
<string name="smartbar__quick_action__open_settings">Apri Impostazioni.</string>
<string name="smartbar__quick_action__switch_to_media_context">Passa alla visualizzazione dei media.</string>
<!-- Settings UI strings -->
<string name="settings__title">Impostazioni</string>
<string name="settings__menu">Altre opzioni</string>
<string name="settings__menu_about">Informazioni su</string>
<string name="settings__menu_help">Aiuto &amp; feedback</string>
<string name="settings__navigation__home">Home</string>
<string name="settings__navigation__keyboard">Tastiera</string>
<string name="settings__navigation__typing">Digitazione</string>
<string name="settings__navigation__theme">Tema</string>
<string name="settings__navigation__gestures">Gesti</string>
<string name="settings__home__title">Benvenuto in %s</string>
<string name="settings__home__ime_not_enabled">FlorisBoard non è abilitato nel sistema e quindi non sarà disponibile come metodo di immissione.Clicca quì per risolvere questo problema.</string>
<string name="settings__home__ime_not_selected">FlorisBoard non è la tastiera predefinita. Clicca quì per risolvere questo problema.</string>
<string name="settings__home__contribute">Grazie per aver provato FlorisBoard! Questo progetto è ancora in fase alfa e quindi manca di alcune funzionalità. Se trovate qualche bug o volete dare un suggerimento, date un\'occhiata al repo su GitHub e segnalate un problema. Questo aiuta a rendere FlorisBoard migliore. Grazie!</string>
<string name="settings__localization__title">Lingue &amp; Layout della tastiera</string>
<string name="settings__localization__subtype_no_subtypes_configured_warning">Sembra che tu non abbia configurato nessuno stile di input personalizzato. Come ripiego verrà utilizzato lo stile input English/QWERTY!</string>
<string name="settings__localization__subtype_add">Aggiungi</string>
<string name="settings__localization__subtype_add_title">Aggiungi stile input</string>
<string name="settings__localization__subtype_apply">Applica</string>
<string name="settings__localization__subtype_cancel">Annulla</string>
<string name="settings__localization__subtype_delete">Elimina</string>
<string name="settings__localization__subtype_edit_title">Modifica stile di input</string>
<string name="settings__localization__subtype_locale">Locale</string>
<string name="settings__localization__subtype_layout">Layout della tastiera</string>
<string name="settings__localization__subtype_error_already_exists">Questo stile di input esiste già !</string>
<string name="settings__theme__title">Tema tastiera</string>
<string name="pref__theme__name__label">Tema tastiera</string>
<string name="settings__keyboard__title">Tastiera preferenze</string>
<string name="pref__keyboard__group_layout__label">Layout</string>
<string name="pref__keyboard__one_handed_mode__label">Modalità ad una mano</string>
<string name="pref__keyboard__height_factor__label">Altezza tastiera</string>
<string name="pref__keyboard__group_keypress__label">Pressione tasti</string>
<string name="pref__keyboard__sound_enabled__label">Suono pressione tasti</string>
<string name="pref__keyboard__sound_volume__label">Volume del suono alla pressione dei tasti</string>
<string name="pref__keyboard__vibration_enabled__label">Vibrazione alla pressione dei tasti</string>
<string name="pref__keyboard__vibration_strength__label">Intensità della vibrazione alla pressione dei tasti</string>
<string name="pref__keyboard__popup_visible__label">Visibilità Popup</string>
<string name="pref__keyboard__popup_visible__summary">Mostra popup quando si preme un tasto</string>
<string name="pref__keyboard__long_press_delay__label">Ritardo lunga pressione tasti</string>
<string name="settings__typing__title">Esperienza di digitazione</string>
<string name="pref__suggestion__title">Suggerimenti</string>
<string name="pref__suggestion__enabled__label">Visualizza suggerimenti mentre digiti</string>
<string name="pref__suggestion__enabled__summary">Verrà visualizzato nella parte superiore della tastiera</string>
<string name="pref__suggestion__use_pref_words__label">Suggerimenti per la parola successiva</string>
<string name="pref__suggestion__use_pref_words__summary">Utilizzare le parole precedenti per generare suggerimenti</string>
<string name="pref__correction__title">Correzioni</string>
<string name="pref__correction__double_space_period__label">Doppio tocco barra spaziatrice</string>
<string name="pref__correction__double_space_period__summary">Doppio tocco su barra spaziatrice per mettere il punto (.) seguito da uno spazio</string>
<string name="settings__gestures__title">Gesti &amp; Digitazione a scorrimento</string>
<string name="settings__advanced__title">Avanzate</string>
<string name="pref__advanced__settings_theme__label">Impostazioni tema</string>
<string name="pref__advanced__show_app_icon__label">Mostra icona nel launcher</string>
<!-- About UI strings -->
<string name="about__title">Informazioni su</string>
<string name="about__app_icon_content_description">Icona dell\'app FlorisBoard</string>
<string name="about__view_licenses">Licenze open source</string>
<string name="about__view_privacy_policy">Norme sulla privacy</string>
<string name="about__view_source_code">Codice sorgente</string>
<string name="about__license__title">Licenze open source</string>
<!-- Setup UI strings -->
<string name="setup__title">Configurazione</string>
<string name="setup__prev_button">Precedente</string>
<string name="setup__cancel_button">Annulla</string>
<string name="setup__next_button">Avanti</string>
<string name="setup__finish_button">Fine</string>
<string name="setup__ok_button">OK</string>
<string name="setup__welcome__title">Benvenuto!</string>
<string name="setup__welcome__intro">Grazie per aver provato FlorisBoard! Prima che possiate iniziare ad usarlo, dobbiamo fare le solite cose e abilitarlo nelle impostazioni di sistema, impostare la vostra lingua/ il layout preferito, ecc... Ma non preoccuparti: segui questa procedura guidata </string>
<string name="setup__welcome__privacy">[[ TODO: inserisci quì la descrizione della privacy ]]</string>
<string name="setup__welcome__trust">Il codice sorgente di FlorisBoard è accessibile pubblicamente a chiunque, quindi puoi facilmente rivedere cosa fa FlorisBoard in background. Controlla il link nel repository in basso.</string>
<string name="setup__welcome__contribute">Un\'ultima cosa prima di iniziare l\'installazione - se riscontri errori / arresti anomali / problemi con FlorisBoard o hai una richiesta di funzionalità - vai al repository GitHub collegato di seguito e presenta un problema. Questo aiuta a migliorare l\'esperienza per tutti gli utenti!</string>
<string name="setup__welcome__outro">Per avviare l\'installazione, fai clic su <i>AVANTI</i>.</string>
<string name="setup__enable_ime__title">Abilita FlorisBoard</string>
<string name="setup__enable_ime__text_before_enabled">Android richiede che ogni tastiera personalizzata debba essere abilitata manualmente prima di poterla utilizzare. Fai clic sul pulsante in basso per passare a <i>Lingua &amp; Input</i>impostazioni, quindi assicurati di selezionare\'<i>FlorisBoard</i>\'.</string>
<string name="setup__enable_ime__text_button_language_and_input">Apri lingue &amp; Impostazioni di input</string>
<string name="setup__enable_ime__text_after_enabled">FlorisBoard è stato abilitato con successo. Per continuare, fai clic su <i>AVANTI</i>!</string>
<string name="setup__make_default__title">Rendi FlorisBoard predefinita</string>
<string name="setup__make_default__text_before_switch">FlorisBoard è ora abilitato nel tuo sistema. Per usarlo attivamente, passa a FlorisBoard selezionandolo nella finestra di dialogo del selettore di input!</string>
<string name="setup__make_default__text_switch_button">Cambia tastiera</string>
<string name="setup__make_default__text_after_switch">Hai cambiato con successo la tastiera predefinita su FlorisBoard!</string>
<string name="setup__finish__title">Installazione terminata!</string>
</resources>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pref__looknfeel__height_factor__entries">
<item>Extra-short</item>
<item>Short</item>
<item>Mid-short</item>
<item>Normal</item>
<item>Mid-tall</item>
<item>Tall</item>
<item>Extra-tall</item>
<string-array name="pref__keyboard__height_factor__entries">
<item>@string/pref__keyboard__height_factor__extra_short</item>
<item>@string/pref__keyboard__height_factor__short</item>
<item>@string/pref__keyboard__height_factor__mid_short</item>
<item>@string/pref__keyboard__height_factor__normal</item>
<item>@string/pref__keyboard__height_factor__mid_tall</item>
<item>@string/pref__keyboard__height_factor__tall</item>
<item>@string/pref__keyboard__height_factor__extra_tall</item>
</string-array>
<string-array name="pref__looknfeel__height_factor__values">
<string-array name="pref__keyboard__height_factor__values">
<item>extra_short</item>
<item>short</item>
<item>mid_short</item>
@@ -19,21 +19,21 @@
<item>extra_tall</item>
</string-array>
<string-array name="pref__looknfeel__one_handed_mode__entries">
<item>Off</item>
<item>Right-handed mode</item>
<item>Left-handed mode</item>
<string-array name="pref__keyboard__one_handed_mode__entries">
<item>@string/pref__keyboard__one_handed_mode__off</item>
<item>@string/pref__keyboard__one_handed_mode__right</item>
<item>@string/pref__keyboard__one_handed_mode__left</item>
</string-array>
<string-array name="pref__looknfeel__one_handed_mode__values">
<string-array name="pref__keyboard__one_handed_mode__values">
<item>off</item>
<item>end</item>
<item>start</item>
</string-array>
<string-array name="pref__advanced__settings_theme__entries">
<item>System default</item>
<item>Light</item>
<item>Dark</item>
<item>@string/settings__system_default</item>
<item>@string/pref__advanced__settings_theme__light</item>
<item>@string/pref__advanced__settings_theme__dark</item>
</string-array>
<string-array name="pref__advanced__settings_theme__values">
<item>auto</item>
@@ -41,12 +41,12 @@
<item>dark</item>
</string-array>
<string-array name="pref__theme__name__entries">
<item>Floris Light</item>
<item>Floris Dark</item>
<string-array name="pref__suggestion__show_instead__entries">
<item>@string/pref__suggestion__show_instead__number_row</item>
<item>@string/pref__suggestion__show_instead__clipboard_cursor_tools</item>
</string-array>
<string-array name="pref__theme__name__values">
<item>floris_light</item>
<item>floris_dark</item>
<string-array name="pref__suggestion__show_instead__values">
<item>number_row</item>
<item>clipboard_cursor_tools</item>
</string-array>
</resources>

View File

@@ -1,39 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DialogSeekBarPreferenceAttrs">
<attr name="android:defaultValue" format="integer"/>
<attr name="systemDefaultValue" format="integer"/>
<attr name="systemDefaultValueText" format="reference|string"/>
<attr name="min" format="integer"/>
<attr name="max" format="integer"/>
<attr name="seekBarIncrement" format="integer"/>
<attr name="unit" format="string"/>
</declare-styleable>
<declare-styleable name="KeyboardTheme">
<attr name="keyboardViewStyle" format="reference" />
<attr name="keyboardRowViewStyle" format="reference" />
<declare-styleable name="EditingKeyView">
<attr name="android:text" format="string|reference"/>
</declare-styleable>
<attr name="semiTransparentColor" format="color" />
<declare-styleable name="KeyboardThemeBase">
<attr name="keyboardViewStyle" format="reference"/>
<attr name="keyboardRowViewStyle" format="reference"/>
<attr name="key_bgColor" format="color" />
<attr name="key_bgColorPressed" format="color" />
<attr name="key_fgColor" format="color" />
<attr name="key_enter_fgColor" format="color" />
<attr name="key_popup_bgColor" format="color" />
<attr name="key_popup_fgColor" format="color" />
<attr name="key_popup_extended_bgColor" format="color" />
<attr name="key_popup_extended_bgColorActive" format="color" />
<attr name="key_popup_extended_shadowColor" format="color" />
<attr name="keyboard_bgColor" format="color" />
<attr name="emoji_key_bgColor" format="color" />
<attr name="emoji_key_bgColorPressed" format="color" />
<attr name="emoji_key_fgColor" format="color" />
<attr name="one_handed_bgColor" format="color" />
<attr name="one_handed_button_fgColor" format="color" />
<attr name="smartbar_bgColor" format="color" />
<attr name="smartbar_fgColor" format="color" />
<attr name="smartbar_button_bgColor" format="color" />
<attr name="smartbar_button_bgColorPressed" format="color" />
<attr name="smartbar_button_fgColor" format="color" />
<attr name="smartbar_candidate_fgColor" format="color" />
<attr name="semiTransparentColor" format="color"/>
<attr name="inputView_bgColorFallback" format="color"/>
</declare-styleable>
<declare-styleable name="SettingsTheme">

View File

@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="inputView_baseHeight">248dp</dimen>
<dimen name="smartbar_baseHeight">40dp</dimen>
<dimen name="textKeyboardView_baseHeight">208dp</dimen>
<dimen name="mediaKeyboardView_baseHeight">@dimen/inputView_baseHeight</dimen>
<dimen name="key_width">33dp</dimen>
<dimen name="key_height">42dp</dimen>
<dimen name="emoji_key_width">@dimen/key_height</dimen>
@@ -8,6 +13,7 @@
<dimen name="key_marginH">2dp</dimen>
<dimen name="key_marginV">5dp</dimen>
<dimen name="keyboard_row_marginH">@dimen/key_marginH</dimen>
<dimen name="keyboard_preview_margin">16dp</dimen>
<dimen name="key_borderRadius">6dp</dimen>

View File

@@ -1,26 +1,17 @@
<resources>
<string name="app_name" translatable="false">FlorisBoard</string>
<string name="florisboard__repo_url" translatable="false">https://github.com/florisboard/florisboard</string>
<string name="florisboard__privacy_policy_url" translatable="false">https://gist.github.com/patrickgold/a18f1e47468d72f0868afc69d6faaf0b</string>
<string name="key__phone_pause">Pause</string>
<string name="key__phone_wait">Wait</string>
<string name="key__view_characters">ABC</string>
<string name="key__view_numeric">1 2\n3 4</string>
<string name="key__view_phone">123</string>
<string name="key__view_phone2">* #</string>
<string name="key__view_symbols">\?123</string>
<string name="key__view_symbols2">=\\&lt;</string>
<string name="key_popup__threedots_alt">Three-dot icon. If visible, indicates that more letters can be used if longer pressed.</string>
<!-- One-handed strings -->
<string name="one_handed__close_btn_content_description">Close one-handed mode.</string>
<string name="one_handed__move_start_btn_content_description">Move keyboard to the left.</string>
<string name="one_handed__move_end_btn_content_description">Move keyboard to the right.</string>
<!-- Media strings -->
<string name="media__tab__emojis">Emojis</string>
<string name="media__tab__emoticons">Emoticons</string>
<string name="media__tab__emoticons_label">;-)</string>
<string name="media__tab__kaomoji">Kaomoji</string>
<string name="media__tab__kaomoji_label">(^-^*)/</string>
<!-- Emoji strings -->
<string name="emoji__category__smileys_emotion">Smileys &amp; Emotions</string>
@@ -35,62 +26,122 @@
<!-- Smartbar strings -->
<string name="smartbar__quick_action_toggle__alt">Quick action toggle. If pressed, toggles between the word suggestions and the quick action buttons.</string>
<string name="smartbar__quick_action__exit_editing">Exit text editing panel.</string>
<string name="smartbar__quick_action__one_handed_mode">Toggle the state of the one-handed mode.</string>
<string name="smartbar__quick_action__open_settings">Open settings.</string>
<string name="smartbar__quick_action__switch_to_editing_context">Switch to text editing panel.</string>
<string name="smartbar__quick_action__switch_to_media_context">Switch to media input view.</string>
<!-- Settings UI strings -->
<string name="settings__title">Settings</string>
<string name="settings__menu">More options</string>
<string name="settings__menu_about">About</string>
<string name="settings__menu_advanced">@string/settings__advanced__title</string>
<string name="settings__menu_help">Help &amp; feedback</string>
<string name="settings__navigation__home">Home</string>
<string name="settings__navigation__keyboard">Keyboard</string>
<string name="settings__navigation__looknfeel">Look &amp; feel</string>
<string name="settings__navigation__typing">Typing</string>
<string name="settings__navigation__theme">Theme</string>
<string name="settings__navigation__gestures">Gestures</string>
<string name="settings__navigation__advanced">Advanced</string>
<string name="settings__default">Default</string>
<string name="settings__system_default">System default</string>
<string name="settings__home__title">Welcome to %s</string>
<string name="settings__home__ime_not_enabled">FlorisBoard is not enabled in the system and thus won\'t be available as an input method in the input picker. Click here to resolve this issue.</string>
<string name="settings__home__ime_not_selected">FlorisBoard is not selected as the default input method. Click here to resolve this issue.</string>
<string name="settings__home__contribute">Thanks for trying out FlorisBoard! This project is still in alpha and therefore missing features. If you find any bugs or want to make a suggestion, please check out the repo on GitHub and file an issue. This helps making FlorisBoard better. Thank you!</string>
<string name="settings__keyboard__title">Keyboard &amp; Text Correction</string>
<string name="settings__keyboard__subtype_no_subtypes_configured_warning">It seems that you haven\'t configured any subtypes. As a fallback the subtype English/QWERTY will be used!</string>
<string name="settings__keyboard__subtype_add">Add</string>
<string name="settings__keyboard__subtype_add_title">Add subtype</string>
<string name="settings__keyboard__subtype_apply">Apply</string>
<string name="settings__keyboard__subtype_cancel">Cancel</string>
<string name="settings__keyboard__subtype_delete">Delete</string>
<string name="settings__keyboard__subtype_edit_title">Edit subtype</string>
<string name="settings__keyboard__subtype_locale">Locale</string>
<string name="settings__keyboard__subtype_layout">Keyboard layout</string>
<string name="settings__keyboard__subtype_error_already_exists">This subtype already exists!</string>
<string name="settings__localization__title">Languages &amp; Keyboard layouts</string>
<string name="settings__localization__subtype_no_subtypes_configured_warning">It seems that you haven\'t configured any subtypes. As a fallback the subtype English/QWERTY will be used!</string>
<string name="settings__localization__subtype_add">Add</string>
<string name="settings__localization__subtype_add_title">Add subtype</string>
<string name="settings__localization__subtype_apply">Apply</string>
<string name="settings__localization__subtype_cancel">Cancel</string>
<string name="settings__localization__subtype_delete">Delete</string>
<string name="settings__localization__subtype_edit_title">Edit subtype</string>
<string name="settings__localization__subtype_locale">Locale</string>
<string name="settings__localization__subtype_layout">Keyboard layout</string>
<string name="settings__localization__subtype_error_already_exists">This subtype already exists!</string>
<string name="settings__theme__title">Keyboard theme</string>
<string name="settings__theme__undefined">Undefined</string>
<string name="settings__theme__preset_title">Theme</string>
<string name="settings__theme__preset_summary">Custom (based on %s)</string>
<string name="settings__theme__preset_dialog_selected_theme">Selected theme:</string>
<string name="settings__theme__preset_dialog_available_themes">Available themes:</string>
<string name="settings__theme__preset_dialog_alt_arrow_right">Arrow right</string>
<string name="settings__theme__background">Background color</string>
<string name="settings__theme__background_active">Background color when active</string>
<string name="settings__theme__background_pressed">Background color when pressed</string>
<string name="settings__theme__foreground">Foreground color</string>
<string name="settings__theme__foreground_alt">Foreground color (alternative)</string>
<string name="settings__theme__foreground_capslock">Foreground color (caps lock)</string>
<string name="settings__theme__dialog_title">Select a color</string>
<string name="settings__theme__group_window">Window &amp; System</string>
<string name="settings__theme__group_keyboard">Keyboard</string>
<string name="settings__theme__group_key">Key</string>
<string name="settings__theme__group_key_enter">Enter key</string>
<string name="settings__theme__group_key_popup">Key popup</string>
<string name="settings__theme__group_key_shift">Shift key</string>
<string name="settings__theme__group_media">Media context</string>
<string name="settings__theme__group_one_handed">One-handed</string>
<string name="settings__theme__group_one_handed_button">One-handed button</string>
<string name="settings__theme__group_smartbar">Smartbar</string>
<string name="settings__theme__group_smartbar_button">Smartbar button</string>
<string name="pref__theme__name__label">Keyboard Theme</string>
<string name="pref__theme__colorPrimary_title">Primary color</string>
<string name="pref__theme__colorPrimary_summary">Applied to main media tab ripple and selection highlight</string>
<string name="pref__theme__colorPrimaryDark_title">Primary color (dark)</string>
<string name="pref__theme__colorPrimaryDark_summary">Currently not used, reserved for future implementation</string>
<string name="pref__theme__colorAccent_title">Accent color</string>
<string name="pref__theme__colorAccent_summary">Applied to emoji tab ripple</string>
<string name="pref__theme__navBarColor_title">Navigation bar color</string>
<string name="pref__theme__navBarColor_summary">The background of the navigation bar.</string>
<string name="pref__theme__navBarIsLight_title">Navigation bar dark foreground</string>
<string name="pref__theme__navBarIsLight_summary">Set to ON for dark or to OFF for light foreground.</string>
<string name="settings__keyboard__title">Keyboard Preferences</string>
<string name="pref__keyboard__group_layout__label">Layout</string>
<string name="pref__keyboard__one_handed_mode__label">One-handed mode</string>
<string name="pref__keyboard__one_handed_mode__off">Off</string>
<string name="pref__keyboard__one_handed_mode__right">Right-handed mode</string>
<string name="pref__keyboard__one_handed_mode__left">Left-handed mode</string>
<string name="pref__keyboard__height_factor__label">Keyboard height</string>
<string name="pref__keyboard__height_factor__extra_short">Extra-short</string>
<string name="pref__keyboard__height_factor__short">Short</string>
<string name="pref__keyboard__height_factor__mid_short">Mid-short</string>
<string name="pref__keyboard__height_factor__normal">Normal</string>
<string name="pref__keyboard__height_factor__mid_tall">Mid-tall</string>
<string name="pref__keyboard__height_factor__tall">Tall</string>
<string name="pref__keyboard__height_factor__extra_tall">Extra-tall</string>
<string name="pref__keyboard__group_keypress__label">Key press</string>
<string name="pref__keyboard__sound_enabled__label">Sound on key press</string>
<string name="pref__keyboard__sound_volume__label">Sound volume on key press</string>
<string name="pref__keyboard__vibration_enabled__label">Vibrate on key press</string>
<string name="pref__keyboard__vibration_strength__label">Vibration strength on key press</string>
<string name="pref__keyboard__popup_visible__label">PopUp Visibility</string>
<string name="pref__keyboard__popup_visible__summary">Show popup when you press a key</string>
<string name="pref__keyboard__long_press_delay__label">Long key press delay</string>
<string name="settings__typing__title">Typing experience</string>
<string name="pref__suggestion__title">Suggestions</string>
<string name="pref__suggestion__enabled__label">[NYI] Display suggestions while you type</string>
<string name="pref__suggestion__enabled__summary">Will show on top of the keyboard</string>
<string name="pref__suggestion__show_instead__label">What to show instead of suggestions</string>
<string name="pref__suggestion__show_instead__number_row">Number row</string>
<string name="pref__suggestion__show_instead__clipboard_cursor_tools">Clipboard cursor tools</string>
<string name="pref__suggestion__use_pref_words__label">[NYI] Next-word suggestions</string>
<string name="pref__suggestion__use_pref_words__summary">Use previous words for generating suggestions</string>
<string name="pref__correction__title">Corrections</string>
<string name="pref__correction__double_space_period__label">Double-space period</string>
<string name="pref__correction__double_space_period__summary">Tapping twice on spacebar inserts a period followed by a space</string>
<string name="settings__looknfeel__title">Look &amp; feel</string>
<string name="pref__looknfeel__group_layout__label">Layout</string>
<string name="pref__looknfeel__height_factor__label">Keyboard height</string>
<string name="pref__looknfeel__one_handed_mode__label">One-handed mode</string>
<string name="pref__looknfeel__group_keypress__label">Key press</string>
<string name="pref__looknfeel__long_press_delay__label">Long key press delay</string>
<string name="pref__looknfeel__sound_enabled__label">Sound on key press</string>
<string name="pref__looknfeel__sound_volume__label">Sound volume on key press</string>
<string name="pref__looknfeel__vibration_enabled__label">Vibrate on key press</string>
<string name="pref__looknfeel__vibration_strength__label">Vibration strength on key press</string>
<string name="pref__theme__name__label">Keyboard Theme</string>
<string name="settings__gestures__title">Gestures</string>
<string name="settings__gestures__title">Gestures &amp; Glide typing</string>
<string name="settings__advanced__title">Advanced</string>
<string name="pref__advanced__settings_theme__label">Settings theme</string>
<string name="pref__advanced__settings_theme__light">Light</string>
<string name="pref__advanced__settings_theme__dark">Dark</string>
<string name="pref__advanced__show_app_icon__label">Show app icon in launcher</string>
<!-- About UI strings -->

View File

@@ -0,0 +1,19 @@
<resources>
<string name="app_name" translatable="false">FlorisBoard</string>
<string name="florisboard__repo_url" translatable="false">https://github.com/florisboard/florisboard</string>
<string name="florisboard__privacy_policy_url" translatable="false">https://gist.github.com/patrickgold/a18f1e47468d72f0868afc69d6faaf0b</string>
<string name="key__view_characters" translatable="false">ABC</string>
<string name="key__view_numeric" translatable="false">1 2\n3 4</string>
<string name="key__view_phone" translatable="false">123</string>
<string name="key__view_phone2" translatable="false">* #</string>
<string name="key__view_symbols" translatable="false">\?123</string>
<string name="key__view_symbols2" translatable="false">=\\&lt;</string>
<string name="key__view_half_space" translatable="false">&#8626;</string>
<string name="key__view_keshida" translatable="false">"یــــ"</string>
<!-- Media strings -->
<string name="media__tab__emoticons_label" translatable="false">;-)</string>
<string name="media__tab__kaomoji_label" translatable="false">(^-^*)/</string>
</resources>

View File

@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="OneHandedPanel">
<item name="android:layout_width">@dimen/one_handed_width</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">0</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:background">?one_handed_bgColor</item>
<item name="android:gravity">center</item>
<item name="android:orientation">vertical</item>
</style>
@@ -18,7 +16,7 @@
<item name="android:autoMirrored">true</item>
<item name="android:background">@drawable/button_transparent_bg_on_press</item>
<item name="android:padding">0dp</item>
<item name="android:tint">?one_handed_button_fgColor</item>
<item name="android:tint">#000000</item>
</style>
<style name="SmartbarCandidate">
@@ -54,10 +52,10 @@
<item name="android:layout_height">match_parent</item>
<item name="android:layout_margin">@dimen/smartbar_button_margin</item>
<item name="android:background">@drawable/shape_oval</item>
<item name="android:backgroundTint">?smartbar_button_bgColor</item>
<item name="android:backgroundTint">#FFFFFF</item>
<item name="android:padding">@dimen/smartbar_button_padding</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:tint">?smartbar_button_fgColor</item>
<item name="android:tint">#000000</item>
</style>
<style name="SmartbarQuickAction.Toggle">
@@ -65,6 +63,15 @@
<item name="android:autoMirrored">true</item>
</style>
<style name="TextEditingButton" parent="Widget.AppCompat.Button.Borderless">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">0dp</item>
<item name="android:background">@drawable/button_transparent_bg_on_press_with_border</item>
<item name="android:soundEffectsEnabled">false</item>
<item name="android:hapticFeedbackEnabled">false</item>
<item name="android:scaleType">center</item>
</style>
<style name="SettingsCardView" parent="Widget.MaterialComponents.CardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>

View File

@@ -1,88 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="KeyboardTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="KeyboardThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="KeyboardTheme.FlorisLight">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">?keyboard_bgColor</item>
<style name="KeyboardThemeBase.Day">
<item name="android:navigationBarColor" tools:targetApi="o_mr1">#FFFFFF</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
<item name="android:colorControlNormal">#8A000000</item><!-- Black, semi transparent -->
<item name="android:colorButtonNormal">#4A000000</item><!-- Black, semi transparent -->
<item name="android:textColor">#000000</item><!-- Black -->
<item name="semiTransparentColor">#20000000</item><!-- Black, semi transparent -->
<item name="key_bgColor">#FFFFFF</item><!-- White -->
<item name="key_bgColorPressed">#F5F5F5</item><!-- Gray 100 -->
<item name="key_fgColor">?android:textColor</item>
<item name="key_enter_fgColor">#FFFFFF</item><!-- White -->
<item name="key_popup_bgColor">#EEEEEE</item><!-- Gray 200 -->
<item name="key_popup_fgColor">?android:textColor</item>
<item name="key_popup_extended_bgColor">@android:color/transparent</item>
<item name="key_popup_extended_bgColorActive">#BDBDBD</item><!-- Gray 400 -->
<item name="key_popup_extended_shadowColor">#CDCACACA</item>
<item name="keyboard_bgColor">#E0E0E0</item><!-- Gray 300 -->
<item name="emoji_key_bgColor">?keyboard_bgColor</item>
<item name="emoji_key_bgColorPressed">?key_popup_extended_bgColorActive</item>
<item name="emoji_key_fgColor">#757575</item><!-- Gray 600 -->
<item name="one_handed_bgColor">#E8F5E9</item><!-- Green 50 -->
<item name="one_handed_button_fgColor">#424242</item><!-- Gray 800 -->
<item name="smartbar_bgColor">?keyboard_bgColor</item>
<item name="smartbar_fgColor">?android:textColor</item>
<item name="smartbar_button_bgColor">?key_bgColor</item>
<item name="smartbar_button_bgColorPressed">?key_bgColorPressed</item>
<item name="smartbar_button_fgColor">?key_fgColor</item>
<item name="smartbar_candidate_fgColor">?android:textColor</item>
<item name="inputView_bgColorFallback">#FFFFFF</item>
</style>
<style name="KeyboardTheme.FlorisDark">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">?keyboard_bgColor</item>
<style name="KeyboardThemeBase.Night">
<item name="android:navigationBarColor" tools:targetApi="o_mr1">#000000</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
<item name="android:colorControlNormal">#B3FFFFFF</item><!-- White, semi transparent -->
<item name="android:colorButtonNormal">#73FFFFFF</item><!-- White, semi transparent -->
<item name="android:textColor">#FFFFFF</item><!-- White -->
<item name="semiTransparentColor">#20FFFFFF</item><!-- White, semi transparent -->
<item name="key_bgColor">#424242</item><!-- Gray 800 -->
<item name="key_bgColorPressed">#616161</item><!-- Gray 700 -->
<item name="key_fgColor">?android:textColor</item>
<item name="key_enter_fgColor">#FFFFFF</item><!-- White -->
<item name="key_popup_bgColor">#757575</item><!-- Gray 600 -->
<item name="key_popup_fgColor">?android:textColor</item>
<item name="key_popup_extended_bgColor">@android:color/transparent</item>
<item name="key_popup_extended_bgColorActive">#BDBDBD</item><!-- Gray 400 -->
<item name="key_popup_extended_shadowColor">#CD353535</item>
<item name="keyboard_bgColor">#212121</item><!-- Gray 900 -->
<item name="emoji_key_bgColor">?keyboard_bgColor</item>
<item name="emoji_key_bgColorPressed">?key_popup_extended_bgColorActive</item>
<item name="emoji_key_fgColor">#BDBDBD</item><!-- Gray 400 -->
<item name="one_handed_bgColor">#1B5E20</item><!-- Green 900 -->
<item name="one_handed_button_fgColor">#EEEEEE</item><!-- Gray 200 -->
<item name="smartbar_bgColor">?keyboard_bgColor</item>
<item name="smartbar_fgColor">?android:textColor</item>
<item name="smartbar_button_bgColor">?key_bgColor</item>
<item name="smartbar_button_bgColorPressed">?key_bgColorPressed</item>
<item name="smartbar_button_fgColor">?key_fgColor</item>
<item name="smartbar_candidate_fgColor">?android:textColor</item>
<item name="inputView_bgColorFallback">#000000</item>
</style>
<style name="SettingsToolbarTheme" parent="ThemeOverlay.AppCompat.DayNight.ActionBar">

View File

@@ -1,15 +0,0 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:fragment="dev.patrickgold.florisboard.settings.SettingsMainActivity$KeyboardFragment"
app:key="pref__keyboard"
app:icon="@drawable/ic_keyboard"
app:title="@string/settings__keyboard__title" />
<Preference
app:fragment="dev.patrickgold.florisboard.settings.SettingsMainActivity$AdvancedFragment"
app:key="pref__advanced"
app:icon="@drawable/ic_more_horiz"
app:title="@string/settings__advanced__title" />
</PreferenceScreen>

View File

@@ -9,13 +9,13 @@
app:key="advanced__settings_theme"
app:iconSpaceReserved="false"
app:title="@string/pref__advanced__settings_theme__label"
app:useSimpleSummaryProvider="true" />
app:useSimpleSummaryProvider="true"/>
<SwitchPreferenceCompat
android:defaultValue="true"
app:key="advanced__show_app_icon"
app:iconSpaceReserved="false"
app:title="@string/pref__advanced__show_app_icon__label"
app:useSimpleSummaryProvider="true" />
app:useSimpleSummaryProvider="true"/>
</PreferenceScreen>

View File

@@ -0,0 +1,9 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:title="Not yet implemented..."
app:iconSpaceReserved="false"/>
</PreferenceScreen>

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