Compare commits

..

45 Commits

Author SHA1 Message Date
Patrick Goldinger
15caf66370 Release v0.2.1 2020-10-18 18:36:37 +02:00
Patrick Goldinger
ae0a8e551b Merge pull request #19 from florisboard/feat-gestures
Add gestures & scrolling space bar
2020-10-18 18:27:49 +02:00
Patrick Goldinger
cb4bedfc2c Add delete word and switch to prev subtype swipe action / Fix bugs
- Remove NYI tag from gesture preferences
- Adjust velocity threshold values
2020-10-18 18:16:46 +02:00
Patrick Goldinger
7d63a6885c Add velocity threshold / Fix bugs in gesture detection 2020-10-18 16:14:26 +02:00
Patrick Goldinger
841d797b7c Add custom gesture detector and listener interface 2020-10-17 21:04:09 +02:00
Patrick Goldinger
0c9ba5326a Add basic gesture support (up, down, left, right)
Using the Android GestureDetector. Will probably be replaced by custom
implementation.
2020-10-15 19:10:08 +02:00
Patrick Goldinger
7c5a7dc148 Add gestures and glide typing preferences
Also add backbone access in PrefHelper, base for gestures implementation.
2020-10-09 16:59:15 +02:00
Patrick Goldinger
37fc714729 Update feature roadmap / Add link to IzzySoft's repo 2020-10-08 19:39:49 +02:00
Patrick Goldinger
ec7d65ebc0 Add changelogs beginning with version 0.2.0 (12)
Based on suggestion of @IzzySoft in #1.
2020-10-08 16:14:45 +02:00
Patrick Goldinger
5670af16d6 Merge pull request #18 from IzzySoft/master
formatting full_description.txt
2020-10-08 16:09:17 +02:00
Izzy
6b39a846e6 formatting full_description.txt 2020-10-07 23:45:01 +02:00
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
112 changed files with 4785 additions and 1281 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

@@ -3,8 +3,16 @@
An open-source keyboard for Android. Currently in alpha stage.
#### Public Alpha Test Programme
Wanna try it out on your device? You can join the public alpha test
programme on Google Play. To become a tester, follow these steps:
Wanna try it out on your device? Use one of the following options:
_A. IzzySoft's repo for F-Droid_:
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge">](https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard)
_B. Google Play Public Alpha Test_:
You can join the public alpha test programme on Google Play. To become a
tester, follow these steps:
1. Join the
[FlorisBoard Public Alpha Test](https://groups.google.com/g/florisboard-public-alpha-test)
Google Group to be able to access the testing programme.
@@ -18,6 +26,8 @@ programme on Google Play. To become a tester, follow these steps:
4. Finished! You will receive future versions of FlorisBoard via Google
Play.
_C. Use the APK provided in the release section of this repo_
##### Giving feedback
If you want to give feedback to FlorisBoard, there are 2 ways to do so,
as listed below:
@@ -29,54 +39,55 @@ as listed below:
Thank you for contributing to FlorisBoard!
##### Note on F-Droid release
FlorisBoard is currently only available through Google Play, but it is
planned to also release it via F-Droid later on. There is no exact
timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
FlorisBoard is currently available through Google Play and IzzySoft's
repo for F-Droid, but is currently in the inclusion process for the main
F-Droid repo. Planned proper F-Droid release is version 0.3.0.
---
![Preview image](https://patrickgold.dev/media/previews/florisboard.png)
<img src="https://patrickgold.dev/media/previews/florisboard.png"
height="256" alt="Preview Image">
## Feature roadmap
### Basics
* [x] Implementation of the keyboard core (InputMethodService)
* [x] Own implementation of deprecated KeyboardView (base only)
* [x] Custom implementation of deprecated KeyboardView (base only)
* [x] Caps + Caps Lock
* [x] Key popups
* [x] Extended key popups (e.g. a -> á, à, ä, ...) (needs tweaks for
emojis)
* [x] Extended key popups (e.g. a -> á, à, ä, ...)
* [x] Key press sound/vibration
* [x] Portrait orientation support
* [x] Landscape orientation support (needs tweaks)
* [ ] Tablet screen support
* [ ] Tablet screen support (0.4.0)
### 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); more coming in
future versions
* [x] Non-latin character layouts (Persian)
* [x] Adapt to situation in app (password, url, text, etc. )
* [x] Special character layout(s)
* [x] Numeric layout
* [x] Numeric layout (advanced)
* [x] Phone number layout
* [x] Emoji layout (popups buggy atm)
* [x] Emoji layout (tweaks: 0.3.0)
* [x] Emoticon layout
* [ ] Kaomoji layout
* [ ] Kaomoji layout (0.3.0)
### Preferences
* [x] Setup wizard
* [x] Preferences screen
* [x] Customize look and behaviour of keyboard (currently only
light/dark theme)
* [ ] Theme customization
* [ ] Theme import/export (?)
* [x] Customize look and behaviour of keyboard
* [x] Theme presets (currently only day/night theme)
* [x] Theme customization
* [ ] Theme import/export (0.4.0 or 0.5.0)
* [x] Subtype selection (language/layout)
* [x] Keyboard behaviour preferences
* [ ] Text suggestion / Auto correct preferences
* [ ] Gesture preferences
* [ ] Text suggestion / Auto correct preferences (0.4.0 or 0.5.0)
* [x] Gesture preferences (0.3.0)
### Composing suggestions
### Composing suggestions (0.4.0 or 0.5.0)
* [ ] Auto suggest words from precompiled dictionary
* [ ] Auto suggest words from user dictionary
* [ ] Auto suggest contacts
@@ -84,26 +95,33 @@ 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 (?)
* [ ] Floating keyboard
* [ ] Gesture support
* [ ] Glide typing (?)
* [x] Clipboard/cursor tools
* [ ] Floating keyboard (0.4.0)
* [x] Gesture support (0.3.0)
* [ ] Glide typing (0.3.0)
* [x] Full integration in IME service list of Android (xml/method)
(integration is internal-only, because Android's default subtype
implementation not really allows for dynamic language/layout
pairs, only compile-time defined ones)
* [ ] Description and settings reference in System Language & Input
* [ ] (dev only) Generate well-structured documentation of code
* [ ] ...
Note: (?) = not sure if it will be implemented
Note:
## Used libraries and icons
(?) = not sure if it will be implemented
(0.x.0) = planned version when feature will be implemented.
## 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 10
versionName "0.1.1"
versionCode 13
versionName "0.2.1"
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

@@ -44,7 +44,8 @@
{ "code": 1577, "label": "ة" }
],
"ک": [
{ "code": 1603, "label": "ك" }
{ "code": 1603, "label": "ك" },
{ "code": 1706, "label": "ڪ"}
],
"ز": [
{ "code": 1688, "label": "ژ" }

View File

@@ -21,6 +21,7 @@
] },
{ "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" },

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
@@ -32,12 +34,12 @@ import android.view.inputmethod.CursorAnchorInfo
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.ImageButton
import android.widget.LinearLayout
import com.squareup.moshi.Json
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.media.MediaInputManager
import dev.patrickgold.florisboard.ime.text.TextInputManager
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyData
import dev.patrickgold.florisboard.settings.SettingsMainActivity
@@ -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)
@@ -379,6 +398,19 @@ class FlorisBoard : InputMethodService() {
}
}
/**
* Executes a given [SwipeAction]. Ignores any [SwipeAction] but the ones relevant for this
* class.
*/
fun executeSwipeAction(swipeAction: SwipeAction) {
when (swipeAction) {
SwipeAction.HIDE_KEYBOARD -> requestHideSelf(0)
SwipeAction.SWITCH_TO_PREV_SUBTYPE -> switchToPrevSubtype()
SwipeAction.SWITCH_TO_NEXT_SUBTYPE -> switchToNextSubtype()
else -> textInputManager.executeSwipeAction(swipeAction)
}
}
/**
* Hides the IME and launches [SettingsMainActivity].
*/
@@ -398,6 +430,11 @@ class FlorisBoard : InputMethodService() {
return subtypeManager.subtypes.size > 1
}
fun switchToPrevSubtype() {
activeSubtype = subtypeManager.switchToPrevSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
}
fun switchToNextSubtype() {
activeSubtype = subtypeManager.switchToNextSubtype() ?: Subtype.DEFAULT
onSubtypeChanged(activeSubtype)
@@ -435,26 +472,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 +502,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 +523,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 +556,6 @@ class FlorisBoard : InputMethodService() {
fun onWindowShown() {}
fun onWindowHidden() {}
fun onConfigurationChanged(newConfig: Configuration) {}
fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {}
fun onUpdateSelection(
oldSelStart: Int,
@@ -510,6 +566,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

@@ -21,6 +21,9 @@ import android.content.SharedPreferences
import android.provider.Settings
import androidx.preference.PreferenceManager
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.ime.text.gestures.DistanceThreshold
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.gestures.VelocityThreshold
import dev.patrickgold.florisboard.util.VersionName
import kotlin.collections.HashMap
@@ -37,9 +40,11 @@ class PrefHelper(
val advanced = Advanced(this)
val correction = Correction(this)
val gestures = Gestures(this)
val glide = Glide(this)
val internal = Internal(this)
val keyboard = Keyboard(this)
val looknfeel = Looknfeel(this)
val localization = Localization(this)
val suggestion = Suggestion(this)
val theme = Theme(this)
@@ -110,15 +115,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 +146,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 +189,103 @@ class PrefHelper(
}
/**
* Wrapper class for internal preferences.
* Wrapper class for gestures preferences.
*/
class Gestures(private val prefHelper: PrefHelper) {
companion object {
const val SWIPE_UP = "gestures__swipe_up"
const val SWIPE_DOWN = "gestures__swipe_down"
const val SWIPE_LEFT = "gestures__swipe_left"
const val SWIPE_RIGHT = "gestures__swipe_right"
const val SPACE_BAR_SWIPE_LEFT = "gestures__space_bar_swipe_left"
const val SPACE_BAR_SWIPE_RIGHT = "gestures__space_bar_swipe_right"
const val DELETE_KEY_SWIPE_LEFT = "gestures__delete_key_swipe_left"
const val SWIPE_VELOCITY_THRESHOLD = "gestures__swipe_velocity_threshold"
const val SWIPE_DISTANCE_THRESHOLD = "gestures__swipe_distance_threshold"
}
var swipeUp: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_UP, "no_action"))
set(v) = prefHelper.setPref(SWIPE_UP, v)
var swipeDown: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_DOWN, "no_action"))
set(v) = prefHelper.setPref(SWIPE_DOWN, v)
var swipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(SWIPE_LEFT, v)
var swipeRight: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SWIPE_RIGHT, "no_action"))
set(v) = prefHelper.setPref(SWIPE_RIGHT, v)
var spaceBarSwipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_LEFT, v)
var spaceBarSwipeRight: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(SPACE_BAR_SWIPE_RIGHT, "no_action"))
set(v) = prefHelper.setPref(SPACE_BAR_SWIPE_RIGHT, v)
var deleteKeySwipeLeft: SwipeAction
get() = SwipeAction.fromString(prefHelper.getPref(DELETE_KEY_SWIPE_LEFT, "no_action"))
set(v) = prefHelper.setPref(DELETE_KEY_SWIPE_LEFT, v)
var swipeVelocityThreshold: VelocityThreshold
get() = VelocityThreshold.fromString(prefHelper.getPref(SWIPE_VELOCITY_THRESHOLD, "normal"))
set(v) = prefHelper.setPref(SWIPE_VELOCITY_THRESHOLD, v)
var swipeDistanceThreshold: DistanceThreshold
get() = DistanceThreshold.fromString(prefHelper.getPref(SWIPE_DISTANCE_THRESHOLD, "normal"))
set(v) = prefHelper.setPref(SWIPE_DISTANCE_THRESHOLD, v)
}
/**
* Wrapper class for glide preferences.
*/
class Glide(private val prefHelper: PrefHelper) {
companion object {
const val ENABLED = "glide__enabled"
const val SHOW_TRAIL = "glide__show_trail"
}
var enabled: Boolean
get() = prefHelper.getPref(ENABLED, false)
set(v) = prefHelper.setPref(ENABLED, v)
var showTrail: Boolean
get() = prefHelper.getPref(SHOW_TRAIL, false)
set(v) = prefHelper.setPref(SHOW_TRAIL, v)
}
/**
* Wrapper class for internal preferences. A preference qualifies as an internal pref if the
* user has no ability to control this preference's value directly (via a UI pref view).
*/
class Internal(private val prefHelper: PrefHelper) {
companion object {
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val VERSION_ON_INSTALL = "internal__version_on_install"
const val VERSION_LAST_USE = "internal__version_last_use"
const val VERSION_LAST_CHANGELOG = "internal__version_last_changelog"
const val IS_IME_SET_UP = "internal__is_ime_set_up"
const val 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 +293,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 +312,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 +374,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,11 +212,39 @@ class SubtypeManager(
}
}
subtypes = subtypeList
if (subtypeToRemove.id == prefs.keyboard.activeSubtypeId) {
if (subtypeToRemove.id == prefs.localization.activeSubtypeId) {
getActiveSubtype()
}
}
/**
* Switch to the previous subtype in the subtype list if possible.
*
* @returns The new active subtype or null if the determination process failed.
*/
fun switchToPrevSubtype(): Subtype? {
val subtypeList = subtypes
val activeSubtype = getActiveSubtype() ?: return null
var triggerNextSubtype = false
var newActiveSubtype: Subtype? = null
for (subtype in subtypeList.reversed()) {
if (triggerNextSubtype) {
triggerNextSubtype = false
newActiveSubtype = subtype
} else if (subtype == activeSubtype) {
triggerNextSubtype = true
}
}
if (triggerNextSubtype) {
newActiveSubtype = subtypeList.last()
}
prefs.localization.activeSubtypeId = when (newActiveSubtype) {
null -> Subtype.DEFAULT.id
else -> newActiveSubtype.id
}
return newActiveSubtype
}
/**
* Switch to the next subtype in the subtype list if possible.
*
@@ -236,10 +264,10 @@ class SubtypeManager(
}
}
if (triggerNextSubtype) {
newActiveSubtype = subtypeList[0]
newActiveSubtype = subtypeList.first()
}
prefs.keyboard.activeSubtypeId = when (newActiveSubtype) {
null -> -1
prefs.localization.activeSubtypeId = when (newActiveSubtype) {
null -> Subtype.DEFAULT.id
else -> newActiveSubtype.id
}
return newActiveSubtype

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,8 @@ 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.gestures.SwipeAction
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 +62,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 +86,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 +109,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 +138,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 +154,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 +229,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()
@@ -248,14 +261,19 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
/**
* 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.isQuickActionsVisible = false
isManualSelectionMode = false
isManualSelectionModeLeft = false
isManualSelectionModeRight = false
}
override fun onSubtypeChanged(newSubtype: Subtype) {
@@ -272,15 +290,25 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
*/
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
cursorAnchorInfo ?: return
lastCursorAnchorInfo = cursorAnchorInfo
val ic = florisboard.currentInputConnection
if (isComposingEnabled) {
if (cursorAnchorInfo.selectionEnd - cursorAnchorInfo.selectionStart == 0) {
val isNewSelectionInBoundsOfOld =
cursorAnchorInfo.selectionStart >= (selectionStart - 1) &&
cursorAnchorInfo.selectionStart <= (selectionStart + 1) &&
cursorAnchorInfo.selectionEnd >= (selectionEnd - 1) &&
cursorAnchorInfo.selectionEnd <= (selectionEnd + 1)
selectionStart = cursorAnchorInfo.selectionStart
selectionEnd = cursorAnchorInfo.selectionEnd
val inputText =
(ic?.getExtractedText(ExtractedTextRequest(), 0)?.text ?: "").toString()
selectionEndMax = inputText.length
// TODO: separate composing text from delete swipe word detection
//if (isComposingEnabled) {
if (!isTextSelected) {
val newCursorPos = cursorAnchorInfo.selectionStart
val prevComposingText = (cursorAnchorInfo.composingText ?: "").toString()
val inputText =
(ic?.getExtractedText(ExtractedTextRequest(), 0)?.text ?: "").toString()
setComposingTextBasedOnInput(inputText, newCursorPos)
if ((newCursorPos == cursorPos) && (composingText == prevComposingText)) {
// Ignore this, as nothing has changed
@@ -299,9 +327,14 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
resetComposingText()
}
smartbarManager.generateCandidatesFromComposing(composingText)
//}
if (!isNewSelectionInBoundsOfOld) {
isManualSelectionMode = false
isManualSelectionModeLeft = false
isManualSelectionModeRight = false
}
isTextSelected = cursorAnchorInfo.selectionEnd - cursorAnchorInfo.selectionStart != 0
updateCapsState()
smartbarManager.onUpdateCursorAnchorInfo(cursorAnchorInfo)
}
/**
@@ -400,6 +433,52 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
}
}
/**
* Executes a given [SwipeAction]. Ignores any [SwipeAction] but the ones relevant for this
* class.
*/
fun executeSwipeAction(swipeAction: SwipeAction) {
when (swipeAction) {
SwipeAction.DELETE_WORD -> handleDeleteWord()
SwipeAction.MOVE_CURSOR_DOWN -> handleArrow(KeyCode.ARROW_DOWN)
SwipeAction.MOVE_CURSOR_UP -> handleArrow(KeyCode.ARROW_UP)
SwipeAction.MOVE_CURSOR_LEFT -> handleArrow(KeyCode.ARROW_LEFT)
SwipeAction.MOVE_CURSOR_RIGHT -> handleArrow(KeyCode.ARROW_RIGHT)
SwipeAction.SHIFT -> handleShift()
else -> {}
}
}
/**
* 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.
*/
@@ -407,12 +486,29 @@ 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()
}
/**
* Handles a [KeyCode.DELETE_WORD] event.
*/
private fun handleDeleteWord() {
val ic = florisboard.currentInputConnection
ic?.beginBatchEdit()
isManualSelectionMode = false
isManualSelectionModeLeft = false
isManualSelectionModeRight = false
ic?.setComposingText("", 1)
ic?.finishComposingText()
if (ic?.getTextBeforeCursor(1, 0)?.length ?: 0 > 0) {
ic?.deleteSurroundingText(1, 0)
}
composingText = null
composingTextStart = null
ic?.endBatchEdit()
}
@@ -421,37 +517,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()
}
/**
@@ -499,6 +582,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
@@ -510,6 +781,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

@@ -0,0 +1,40 @@
/*
* 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.gestures
import java.util.*
/**
* Enum for declaring the distance thresholds for swipe gestures.
*/
enum class DistanceThreshold {
VERY_SHORT,
SHORT,
NORMAL,
LONG,
VERY_LONG;
companion object {
fun fromString(string: String): DistanceThreshold {
return valueOf(string.toUpperCase(Locale.ROOT))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.gestures
import java.util.*
/**
* Enum for declaring the possible actions for swipe gestures.
*/
enum class SwipeAction {
NO_ACTION,
DELETE_WORD,
HIDE_KEYBOARD,
MOVE_CURSOR_UP,
MOVE_CURSOR_DOWN,
MOVE_CURSOR_LEFT,
MOVE_CURSOR_RIGHT,
SHIFT,
SWITCH_TO_PREV_SUBTYPE,
SWITCH_TO_NEXT_SUBTYPE;
companion object {
fun fromString(string: String): SwipeAction {
return valueOf(string.toUpperCase(Locale.ROOT))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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.gestures
import android.content.Context
import android.util.DisplayMetrics
import android.view.MotionEvent
import dev.patrickgold.florisboard.R
import java.lang.Exception
import kotlin.math.*
/**
* Wrapper class which holds all enums, interfaces and classes for detecting a swipe gesture.
*/
abstract class SwipeGesture {
/**
* Class which detects swipes based on given [MotionEvent]s. Only supports single-finger swipes
* and ignores additional pointers provided, if any.
*
* @property listener The listener to report detected swipes to.
*/
class Detector(private val context: Context, private val listener: Listener) {
private val eventList: MutableList<MotionEvent> = mutableListOf()
private var indexFirst: Int = 0
private var indexLastMoveRecognized: Int = 0
var distanceThreshold: DistanceThreshold = DistanceThreshold.NORMAL
var velocityThreshold: VelocityThreshold = VelocityThreshold.NORMAL
fun onTouchEvent(event: MotionEvent): Boolean {
try {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
clearEventList()
eventList.add(MotionEvent.obtainNoHistory(event))
}
MotionEvent.ACTION_MOVE -> {
eventList.add(MotionEvent.obtainNoHistory(event))
val lastEvent = eventList[indexLastMoveRecognized]
val diffX = event.x - lastEvent.x
val diffY = event.y - lastEvent.y
val distanceThresholdNV = numericValue(distanceThreshold) / 2.0f
return if (abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) {
indexLastMoveRecognized = eventList.size - 1
val direction = detectDirection(diffX.toDouble(), diffY.toDouble())
listener.onSwipe(direction, Type.TOUCH_MOVE)
} else {
false
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
val firstEvent = eventList[indexFirst]
val diffX = event.x - firstEvent.x
val diffY = event.y - firstEvent.y
val distanceThresholdNV = numericValue(distanceThreshold)
val velocityThresholdNV = numericValue(velocityThreshold)
val velocity =
((convertPixelsToDp(
sqrt(diffX.pow(2) + diffY.pow(2)),
context
) / event.downTime) * 10.0f.pow(8)).toInt()
clearEventList()
return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) && velocity >= velocityThresholdNV) {
val direction = detectDirection(diffX.toDouble(), diffY.toDouble())
listener.onSwipe(direction, Type.TOUCH_UP)
} else {
false
}
}
MotionEvent.ACTION_CANCEL -> {
clearEventList()
}
else -> return false
}
return false
} catch(e: Exception) {
return false
}
}
/**
* Calculates the angle based on the given x any y lengths. The returned angle is in degree
* and goes clockwise, beginning with 0° at +x, 90° at +y, 180° at -y and 270° at -y.
*
* Coordinate system (based on the Android display coordinate system):
* -y
* -x 00 +x
* +y
*/
private fun angle(diffX: Double, diffY: Double): Double {
val tmpAngle = abs(360 * atan(diffY / diffX) / (2 * PI))
return if (diffX < 0 && diffY >= 0) {
180.0f - tmpAngle
} else if (diffX < 0 && diffY < 0) {
180.0f + tmpAngle
} else if (diffX >= 0 && diffY < 0) {
360.0f - tmpAngle
} else {
tmpAngle
}
}
/**
* Detects the direction of a finger swipe by two given events.
*/
private fun detectDirection(diffX: Double, diffY: Double): Direction {
val diffAngle = angle(diffX, diffY) / 360
return when {
diffAngle >= (1/16.0f) && diffAngle < (3/16.0f) -> Direction.DOWN_RIGHT
diffAngle >= (3/16.0f) && diffAngle < (5/16.0f) -> Direction.DOWN
diffAngle >= (5/16.0f) && diffAngle < (7/16.0f) -> Direction.DOWN_LEFT
diffAngle >= (7/16.0f) && diffAngle < (9/16.0f) -> Direction.LEFT
diffAngle >= (9/16.0f) && diffAngle < (11/16.0f) -> Direction.UP_LEFT
diffAngle >= (11/16.0f) && diffAngle < (13/16.0f) -> Direction.UP
diffAngle >= (13/16.0f) && diffAngle < (15/16.0f) -> Direction.UP_RIGHT
else -> Direction.RIGHT
}
}
/**
* Cleans up and clears the event list.
*/
private fun clearEventList() {
for (event in eventList) {
event.recycle()
}
eventList.clear()
indexFirst = 0
indexLastMoveRecognized = 0
}
/**
* Returns a numeric value for a given [DistanceThreshold], based on the values defined in
* the resources dimens.xml file.
*/
private fun numericValue(of: DistanceThreshold): Double {
return when (of) {
DistanceThreshold.VERY_SHORT -> context.resources.getDimension(R.dimen.gesture_distance_threshold_very_short)
DistanceThreshold.SHORT -> context.resources.getDimension(R.dimen.gesture_distance_threshold_short)
DistanceThreshold.NORMAL -> context.resources.getDimension(R.dimen.gesture_distance_threshold_normal)
DistanceThreshold.LONG -> context.resources.getDimension(R.dimen.gesture_distance_threshold_long)
DistanceThreshold.VERY_LONG -> context.resources.getDimension(R.dimen.gesture_distance_threshold_very_long)
}.toDouble()
}
/**
* Returns a numeric value for a given [VelocityThreshold], based on the values defined in
* the resources dimens.xml file.
*/
private fun numericValue(of: VelocityThreshold): Double {
return when (of) {
VelocityThreshold.VERY_SLOW -> context.resources.getInteger(R.integer.gesture_velocity_threshold_very_slow)
VelocityThreshold.SLOW -> context.resources.getInteger(R.integer.gesture_velocity_threshold_slow)
VelocityThreshold.NORMAL -> context.resources.getInteger(R.integer.gesture_velocity_threshold_normal)
VelocityThreshold.FAST -> context.resources.getInteger(R.integer.gesture_velocity_threshold_fast)
VelocityThreshold.VERY_FAST -> context.resources.getInteger(R.integer.gesture_velocity_threshold_very_fast)
}.toDouble()
}
/**
* This method converts device specific pixels to density independent pixels.
*
* Source: https://stackoverflow.com/a/9563438/6801193 (by Muhammad Nabeel Arif)
*
* @param px A value in px (pixels) unit. Which we need to convert into db
* @param context Context to get resources and device specific display metrics
* @return A float value to represent dp equivalent to px value
*/
private fun convertPixelsToDp(px: Float, context: Context): Float {
return px / (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
}
}
interface Listener {
fun onSwipe(direction: Direction, type: Type): Boolean
}
enum class Direction {
UP_LEFT,
UP,
UP_RIGHT,
RIGHT,
DOWN_RIGHT,
DOWN,
DOWN_LEFT,
LEFT,
}
enum class Type {
TOUCH_UP,
TOUCH_MOVE;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.gestures
import java.util.*
/**
* Enum for declaring the velocity thresholds for swipe gestures.
*/
enum class VelocityThreshold {
VERY_SLOW,
SLOW,
NORMAL,
FAST,
VERY_FAST;
companion object {
fun fromString(string: String): VelocityThreshold {
return valueOf(string.toUpperCase(Locale.ROOT))
}
}
override fun toString(): String {
return super.toString().toLowerCase(Locale.ROOT)
}
}

View File

@@ -70,5 +70,6 @@ object KeyCode {
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,11 @@ 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.gestures.SwipeGesture
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.*
/**
@@ -51,7 +52,7 @@ import java.util.*
class KeyView(
private val keyboardView: KeyboardView,
val data: KeyData
) : View(keyboardView.context) {
) : View(keyboardView.context), SwipeGesture.Listener {
private var isKeyPressed: Boolean = false
set(value) {
@@ -60,6 +61,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
@@ -77,6 +79,7 @@ class KeyView(
}
var florisboard: FlorisBoard? = null
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
var touchHitBox: Rect = Rect(-1, -1, -1, -1)
init {
@@ -172,9 +175,22 @@ class KeyView(
*/
fun onFlorisTouchEvent(event: MotionEvent?): Boolean {
event ?: return false
if (swipeGestureDetector.onTouchEvent(event)) {
isKeyPressed = false
osHandler?.removeCallbacksAndMessages(null)
osTimer?.cancel()
osTimer = null
keyboardView.popupManager.hide()
return true
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
keyboardView.popupManager.show(this)
shouldBlockNextKeyCode = false
florisboard?.prefs?.keyboard?.let {
if (it.popupEnabled){
keyboardView.popupManager.show(this)
}
}
isKeyPressed = true
florisboard?.keyPressVibrate()
florisboard?.keyPressSound(data)
@@ -190,7 +206,7 @@ class KeyView(
}
}, 500, 50)
}
val delayMillis = keyboardView.prefs.looknfeel.longPressDelay
val delayMillis = prefs.keyboard.longPressDelay
if (osHandler == null) {
osHandler = Handler()
}
@@ -248,6 +264,34 @@ class KeyView(
return true
}
/**
* Swipe event handler. Listens to touch_move left/right swipes and triggers the swipe action
* defined in the prefs.
*/
override fun onSwipe(direction: SwipeGesture.Direction, type: SwipeGesture.Type): Boolean {
return when (data.code) {
KeyCode.SPACE -> when (type) {
SwipeGesture.Type.TOUCH_MOVE -> when (direction) {
SwipeGesture.Direction.LEFT -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
osHandler?.removeCallbacksAndMessages(null)
shouldBlockNextKeyCode = true
true
}
SwipeGesture.Direction.RIGHT -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeRight)
osHandler?.removeCallbacksAndMessages(null)
shouldBlockNextKeyCode = true
true
}
else -> false
}
else -> false
}
else -> false
}
}
/**
* Solution base from this great StackOverflow answer which explained and helped a lot
* for handling onMeasure():
@@ -333,20 +377,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 +481,14 @@ class KeyView(
updateKeyPressedBackground()
if (data.type == KeyType.CHARACTER && data.code != KeyCode.SPACE
&& data.code != KeyCode.HALF_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 +502,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 +536,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 +546,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 -> {
@@ -516,6 +571,9 @@ class KeyView(
KeyCode.HALF_SPACE -> {
label = resources.getString(R.string.key__view_half_space)
}
KeyCode.KESHIDA -> {
label = resources.getString(R.string.key__view_keshida)
}
}
}
@@ -550,12 +608,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
@@ -32,25 +29,27 @@ 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 dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyView
import dev.patrickgold.florisboard.ime.text.layout.ComputedLayoutData
import dev.patrickgold.florisboard.util.getColorFromAttr
import dev.patrickgold.florisboard.ime.text.gestures.SwipeGesture
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import kotlin.math.roundToInt
/**
* Manages the layout of the keyboard, key measurement, key selection and all touch events.
* Supports multi touch events.
*
* TODO: Implement swipe gesture support
* 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.
*
* @property florisboard Reference to instance of core class [FlorisBoard].
*/
class KeyboardView : LinearLayout {
class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Listener {
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 +57,23 @@ 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()
private var initialKeyCode: Int = 0
var isPreviewMode: Boolean = false
var popupManager = KeyPopupManager<KeyboardView, KeyView>(this)
lateinit var prefs: PrefHelper
private val prefs: PrefHelper = PrefHelper.getDefaultInstance(context)
private val swipeGestureDetector = SwipeGesture.Detector(context, this)
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)
onWindowShown()
}
/**
@@ -107,6 +108,13 @@ class KeyboardView : LinearLayout {
popupManager.dismissAllPopups()
}
override fun onWindowShown() {
swipeGestureDetector.apply {
distanceThreshold = prefs.gestures.swipeDistanceThreshold
velocityThreshold = prefs.gestures.swipeVelocityThreshold
}
}
/**
* Catch all events which are designated for child views.
*/
@@ -124,6 +132,12 @@ class KeyboardView : LinearLayout {
return false
}
val eventFloris = MotionEvent.obtainNoHistory(event)
if (swipeGestureDetector.onTouchEvent(event)) {
sendFlorisTouchEvent(eventFloris, MotionEvent.ACTION_CANCEL)
activeKeyView = null
activePointerId = null
return true
}
val pointerIndex = event.actionIndex
var pointerId = event.getPointerId(pointerIndex)
when (event.actionMasked) {
@@ -134,6 +148,7 @@ class KeyboardView : LinearLayout {
activeX = event.getX(pointerIndex)
activeY = event.getY(pointerIndex)
searchForActiveKeyView()
initialKeyCode = activeKeyView?.data?.code ?: 0
sendFlorisTouchEvent(eventFloris, MotionEvent.ACTION_DOWN)
} else if (activePointerId != pointerId) {
// New pointer arrived. Send ACTION_UP to current active view and move on
@@ -142,6 +157,7 @@ class KeyboardView : LinearLayout {
activeX = event.getX(pointerIndex)
activeY = event.getY(pointerIndex)
searchForActiveKeyView()
initialKeyCode = activeKeyView?.data?.code ?: 0
sendFlorisTouchEvent(eventFloris, MotionEvent.ACTION_DOWN)
}
}
@@ -199,6 +215,45 @@ class KeyboardView : LinearLayout {
})
}
/**
* Swipe event handler. Listens to touch_up swipes and executes the swipe action defined for it
* in the prefs.
*/
override fun onSwipe(direction: SwipeGesture.Direction, type: SwipeGesture.Type): Boolean {
return when {
initialKeyCode == KeyCode.DELETE -> {
if (type == SwipeGesture.Type.TOUCH_UP && direction == SwipeGesture.Direction.LEFT) {
florisboard?.executeSwipeAction(prefs.gestures.deleteKeySwipeLeft)
true
} else {
false
}
}
initialKeyCode > KeyCode.SPACE && !popupManager.isShowingExtendedPopup -> when {
!prefs.glide.enabled -> when (type) {
SwipeGesture.Type.TOUCH_UP -> {
val swipeAction = when (direction) {
SwipeGesture.Direction.UP -> prefs.gestures.swipeUp
SwipeGesture.Direction.DOWN -> prefs.gestures.swipeDown
SwipeGesture.Direction.LEFT -> prefs.gestures.swipeLeft
SwipeGesture.Direction.RIGHT -> prefs.gestures.swipeRight
else -> SwipeAction.NO_ACTION
}
if (swipeAction != SwipeAction.NO_ACTION) {
florisboard?.executeSwipeAction(swipeAction)
true
} else {
false
}
}
else -> false
}
else -> false
}
else -> false
}
}
/**
* Searches for an active key view at [activeX]/[activeY].
*/
@@ -237,32 +292,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 +353,4 @@ class KeyboardView : LinearLayout {
}
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
colorDrawable.color = getColorFromAttr(context, R.attr.keyboard_bgColor)
}
}

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,17 +12,16 @@ 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
@@ -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,18 +51,37 @@ 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 ->
isQuickActionsVisible = false
when (v.id) {
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 {
isQuickActionsVisible = !isQuickActionsVisible
@@ -89,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) {
@@ -99,13 +114,23 @@ 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() {
@@ -119,40 +144,14 @@ 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.PHONE, KeyboardMode.PHONE2 -> {
smartbarView?.visibility = View.GONE
smartbarView?.setActiveVariant(null)
}
else -> {
smartbarView?.visibility = View.VISIBLE
smartbarView?.setActiveVariant(R.id.smartbar_variant_default)
isQuickActionsVisible = false
}
}
@@ -162,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) {
//
}
@@ -202,39 +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
if (isQuickActionsVisible) {
smartbarView.candidatesView?.visibility = View.GONE
smartbarView.numberRowView?.visibility = View.GONE
smartbarView.quickActionsView?.visibility = View.VISIBLE
smartbarView.quickActionToggle?.rotation = -180.0f
smartbarView.setActiveContainer(R.id.quick_actions)
smartbarView.findViewById<View>(R.id.quick_action_toggle)?.rotation = -180.0f
} else {
if (florisboard.prefs.suggestion.enabled) {
smartbarView.candidatesView?.visibility = View.VISIBLE
smartbarView.numberRowView?.visibility = View.GONE
smartbarView.quickActionsView?.visibility = View.GONE
if (isComposingEnabled) {
smartbarView.setActiveContainer(R.id.candidates)
} else if (textInputManager.getActiveKeyboardMode() == KeyboardMode.CHARACTERS) {
smartbarView.candidatesView?.visibility = View.GONE
smartbarView.numberRowView?.visibility = View.VISIBLE
smartbarView.quickActionsView?.visibility = View.GONE
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.candidatesView?.visibility = View.GONE
smartbarView.numberRowView?.visibility = View.GONE
smartbarView.quickActionsView?.visibility = View.GONE
smartbarView.setActiveContainer(null)
}
smartbarView.quickActionToggle?.rotation = 0.0f
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

@@ -32,26 +32,44 @@
<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__looknfeel">Aspetto &amp; funzionalità</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__navigation__advanced">Avanzate</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__keyboard__title">Tastiera &amp; Correzione del testo</string>
<string name="settings__keyboard__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__keyboard__subtype_add">Aggiungi</string>
<string name="settings__keyboard__subtype_add_title">Aggiungi stile input</string>
<string name="settings__keyboard__subtype_apply">Applica</string>
<string name="settings__keyboard__subtype_cancel">Annulla</string>
<string name="settings__keyboard__subtype_delete">Elimina</string>
<string name="settings__keyboard__subtype_edit_title">Modifica stile di input</string>
<string name="settings__keyboard__subtype_locale">Locale</string>
<string name="settings__keyboard__subtype_layout">Layout della tastiera</string>
<string name="settings__keyboard__subtype_error_already_exists">Questo stile di input esiste già !</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>
@@ -61,19 +79,7 @@
<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__looknfeel__title">Aspetto &amp; funzionalità</string>
<string name="pref__looknfeel__group_layout__label">Layout</string>
<string name="pref__looknfeel__height_factor__label">Altezza tastiera</string>
<string name="pref__looknfeel__one_handed_mode__label">Modalità ad una mano</string>
<string name="pref__looknfeel__group_keypress__label">Pressione tasti</string>
<string name="pref__looknfeel__long_press_delay__label">Ritardo lunga pressione tasti</string>
<string name="pref__looknfeel__sound_enabled__label">Suono pressione tasti</string>
<string name="pref__looknfeel__sound_volume__label">Volume del suono alla pressione dei tasti</string>
<string name="pref__looknfeel__vibration_enabled__label">Vibrazione alla pressione dei tasti</string>
<string name="pref__looknfeel__vibration_strength__label">Intensità della vibrazione alla pressione dei tasti</string>
<string name="pref__theme__name__label">Tema tastiera</string>
<string name="settings__gestures__title">Gesti</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>
@@ -97,7 +103,7 @@
<string name="setup__ok_button">OK</string>
<string name="setup__welcome__title">Benvenuto!</string>
<string name="setup__welcome__intro">TGrazie 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__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>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pref__looknfeel__height_factor__entries">
<item>@string/pref__looknfeel__height_factor__extra_short</item>
<item>@string/pref__looknfeel__height_factor__short</item>
<item>@string/pref__looknfeel__height_factor__mid_short</item>
<item>@string/pref__looknfeel__height_factor__normal</item>
<item>@string/pref__looknfeel__height_factor__mid_tall</item>
<item>@string/pref__looknfeel__height_factor__tall</item>
<item>@string/pref__looknfeel__height_factor__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,12 +19,12 @@
<item>extra_tall</item>
</string-array>
<string-array name="pref__looknfeel__one_handed_mode__entries">
<item>@string/pref__looknfeel__one_handed_mode__off</item>
<item>@string/pref__looknfeel__one_handed_mode__right</item>
<item>@string/pref__looknfeel__one_handed_mode__left</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>
@@ -41,12 +41,67 @@
<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>
<string-array name="pref__gestures__swipe_action__entries">
<item>@string/pref__gestures__swipe_action__no_action</item>
<item>@string/pref__gestures__swipe_action__delete_word</item>
<item>@string/pref__gestures__swipe_action__hide_keyboard</item>
<item>@string/pref__gestures__swipe_action__move_cursor_up</item>
<item>@string/pref__gestures__swipe_action__move_cursor_down</item>
<item>@string/pref__gestures__swipe_action__move_cursor_left</item>
<item>@string/pref__gestures__swipe_action__move_cursor_right</item>
<item>@string/pref__gestures__swipe_action__shift</item>
<item>@string/pref__gestures__swipe_action__switch_to_prev_subtype</item>
<item>@string/pref__gestures__swipe_action__switch_to_next_subtype</item>
</string-array>
<string-array name="pref__gestures__swipe_action__values">
<item>no_action</item>
<item>delete_word</item>
<item>hide_keyboard</item>
<item>move_cursor_up</item>
<item>move_cursor_down</item>
<item>move_cursor_left</item>
<item>move_cursor_right</item>
<item>shift</item>
<item>switch_to_prev_subtype</item>
<item>switch_to_next_subtype</item>
</string-array>
<string-array name="pref__gestures__swipe_velocity_threshold__entries">
<item>@string/pref__gestures__swipe_velocity_threshold__very_slow</item>
<item>@string/pref__gestures__swipe_velocity_threshold__slow</item>
<item>@string/pref__gestures__swipe_velocity_threshold__normal</item>
<item>@string/pref__gestures__swipe_velocity_threshold__fast</item>
<item>@string/pref__gestures__swipe_velocity_threshold__very_fast</item>
</string-array>
<string-array name="pref__gestures__swipe_velocity_threshold__values">
<item>very_slow</item>
<item>slow</item>
<item>normal</item>
<item>fast</item>
<item>very_fast</item>
</string-array>
<string-array name="pref__gestures__swipe_distance_threshold__entries">
<item>@string/pref__gestures__swipe_distance_threshold__very_short</item>
<item>@string/pref__gestures__swipe_distance_threshold__short</item>
<item>@string/pref__gestures__swipe_distance_threshold__normal</item>
<item>@string/pref__gestures__swipe_distance_threshold__long</item>
<item>@string/pref__gestures__swipe_distance_threshold__very_long</item>
</string-array>
<string-array name="pref__gestures__swipe_distance_threshold__values">
<item>very_short</item>
<item>short</item>
<item>normal</item>
<item>long</item>
<item>very_long</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>
@@ -28,4 +34,16 @@
<dimen name="smartbar_height">40dp</dimen>
<dimen name="smartbar_button_margin">4dp</dimen>
<dimen name="smartbar_button_padding">6dp</dimen>
<dimen name="gesture_distance_threshold_very_short">24dp</dimen>
<dimen name="gesture_distance_threshold_short">28dp</dimen>
<dimen name="gesture_distance_threshold_normal">32dp</dimen>
<dimen name="gesture_distance_threshold_long">36dp</dimen>
<dimen name="gesture_distance_threshold_very_long">40dp</dimen>
<integer name="gesture_velocity_threshold_very_slow">50</integer>
<integer name="gesture_velocity_threshold_slow">60</integer>
<integer name="gesture_velocity_threshold_normal">70</integer>
<integer name="gesture_velocity_threshold_fast">80</integer>
<integer name="gesture_velocity_threshold_very_fast">90</integer>
</resources>

View File

@@ -3,6 +3,11 @@
<string name="key__phone_wait">Wait</string>
<string name="key_popup__threedots_alt">Three-dot icon. If visible, indicates that more letters can be used if longer pressed.</string>
<!-- 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>
@@ -21,20 +26,24 @@
<!-- 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>
@@ -42,49 +51,127 @@
<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">Display suggestions while you type</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__height_factor__extra_short">Extra-short</string>
<string name="pref__looknfeel__height_factor__short">Short</string>
<string name="pref__looknfeel__height_factor__mid_short">Mid-short</string>
<string name="pref__looknfeel__height_factor__normal">Normal</string>
<string name="pref__looknfeel__height_factor__mid_tall">Mid-tall</string>
<string name="pref__looknfeel__height_factor__tall">Tall</string>
<string name="pref__looknfeel__height_factor__extra_tall">Extra-tall</string>
<string name="pref__looknfeel__one_handed_mode__label">One-handed mode</string>
<string name="pref__looknfeel__one_handed_mode__off">Off</string>
<string name="pref__looknfeel__one_handed_mode__right">Right-handed mode</string>
<string name="pref__looknfeel__one_handed_mode__left">Left-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="pref__glide__title">Glide typing</string>
<string name="pref__glide__enabled__label">[NYI] Enable glide typing</string>
<string name="pref__glide__enabled__summary">Type in a word by sliding your finger through its letters</string>
<string name="pref__glide__show_trail__label">[NYI] Show glide trail</string>
<string name="pref__glide__show_trail__summary">Will disappear after each word</string>
<string name="pref__gestures__title">Gestures</string>
<string name="pref__gestures__swipe_action__no_action">No action</string>
<string name="pref__gestures__swipe_action__delete_word">Delete word</string>
<string name="pref__gestures__swipe_action__hide_keyboard">Hide keyboard</string>
<string name="pref__gestures__swipe_action__move_cursor_up">Move cursor up</string>
<string name="pref__gestures__swipe_action__move_cursor_down">Move cursor down</string>
<string name="pref__gestures__swipe_action__move_cursor_left">Move cursor left</string>
<string name="pref__gestures__swipe_action__move_cursor_right">Move cursor right</string>
<string name="pref__gestures__swipe_action__shift">Shift</string>
<string name="pref__gestures__swipe_action__switch_to_prev_subtype">Switch to previous subtype</string>
<string name="pref__gestures__swipe_action__switch_to_next_subtype">Switch to next subtype</string>
<string name="pref__gestures__swipe_up__label">Swipe up</string>
<string name="pref__gestures__swipe_down__label">Swipe down</string>
<string name="pref__gestures__swipe_left__label">Swipe left</string>
<string name="pref__gestures__swipe_right__label">Swipe right</string>
<string name="pref__gestures__space_bar_swipe_left__label">Space bar swipe left</string>
<string name="pref__gestures__space_bar_swipe_right__label">Space bar swipe right</string>
<string name="pref__gestures__delete_key_swipe_left__label">Delete key swipe left</string>
<string name="pref__gestures__swipe_velocity_threshold__label">Swipe velocity threshold</string>
<string name="pref__gestures__swipe_velocity_threshold__very_slow">Very slow</string>
<string name="pref__gestures__swipe_velocity_threshold__slow">Slow</string>
<string name="pref__gestures__swipe_velocity_threshold__normal">Normal</string>
<string name="pref__gestures__swipe_velocity_threshold__fast">Fast</string>
<string name="pref__gestures__swipe_velocity_threshold__very_fast">Very fast</string>
<string name="pref__gestures__swipe_distance_threshold__label">Swipe distance threshold</string>
<string name="pref__gestures__swipe_distance_threshold__very_short">Very short</string>
<string name="pref__gestures__swipe_distance_threshold__short">Short</string>
<string name="pref__gestures__swipe_distance_threshold__normal">Normal</string>
<string name="pref__gestures__swipe_distance_threshold__long">Long</string>
<string name="pref__gestures__swipe_distance_threshold__very_long">Very long</string>
<string name="settings__advanced__title">Advanced</string>
<string name="pref__advanced__settings_theme__label">Settings theme</string>

View File

@@ -11,6 +11,7 @@
<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>

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,119 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:iconSpaceReserved="false"
app:key="glide"
app:title="@string/pref__glide__title">
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
android:defaultValue="false"
app:disableDependentsState="true"
app:key="glide__enabled"
app:title="@string/pref__glide__enabled__label"
android:summary="@string/pref__glide__enabled__summary"/>
<!--<SwitchPreferenceCompat
app:iconSpaceReserved="false"
android:defaultValue="true"
app:key="glide__show_trail"
app:title="@string/pref__glide__show_trail__label"
android:summary="@string/pref__glide__show_trail__summary"/>-->
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:key="gestures"
app:title="@string/pref__gestures__title">
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="shift"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:dependency="glide__enabled"
app:key="gestures__swipe_up"
app:title="@string/pref__gestures__swipe_up__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="hide_keyboard"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:dependency="glide__enabled"
app:key="gestures__swipe_down"
app:title="@string/pref__gestures__swipe_down__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="switch_to_next_subtype"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:dependency="glide__enabled"
app:key="gestures__swipe_left"
app:title="@string/pref__gestures__swipe_left__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="switch_to_prev_subtype"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:dependency="glide__enabled"
app:key="gestures__swipe_right"
app:title="@string/pref__gestures__swipe_right__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="move_cursor_left"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:key="gestures__space_bar_swipe_left"
app:title="@string/pref__gestures__space_bar_swipe_left__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="move_cursor_right"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:key="gestures__space_bar_swipe_right"
app:title="@string/pref__gestures__space_bar_swipe_right__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="delete_word"
app:entries="@array/pref__gestures__swipe_action__entries"
app:entryValues="@array/pref__gestures__swipe_action__values"
app:key="gestures__delete_key_swipe_left"
app:title="@string/pref__gestures__delete_key_swipe_left__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="normal"
app:entries="@array/pref__gestures__swipe_velocity_threshold__entries"
app:entryValues="@array/pref__gestures__swipe_velocity_threshold__values"
app:key="gestures__swipe_velocity_threshold"
app:title="@string/pref__gestures__swipe_velocity_threshold__label"
app:useSimpleSummaryProvider="true"/>
<ListPreference
app:iconSpaceReserved="false"
android:defaultValue="normal"
app:entries="@array/pref__gestures__swipe_distance_threshold__entries"
app:entryValues="@array/pref__gestures__swipe_distance_threshold__values"
app:key="gestures__swipe_distance_threshold"
app:title="@string/pref__gestures__swipe_distance_threshold__label"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -4,35 +4,89 @@
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref__suggestion__title">
app:title="@string/pref__keyboard__group_layout__label">
<SwitchPreferenceCompat
android:defaultValue="true"
app:key="suggestion__enabled"
<ListPreference
android:defaultValue="off"
app:entries="@array/pref__keyboard__one_handed_mode__entries"
app:entryValues="@array/pref__keyboard__one_handed_mode__values"
app:key="keyboard__one_handed_mode"
app:iconSpaceReserved="false"
app:title="@string/pref__suggestion__enabled__label"
app:summary="@string/pref__suggestion__enabled__summary"/>
app:title="@string/pref__keyboard__one_handed_mode__label"
app:useSimpleSummaryProvider="true"/>
<SwitchPreferenceCompat
android:defaultValue="true"
app:dependency="suggestion__enabled"
app:key="suggestion__use_prev_words"
<ListPreference
android:defaultValue="normal"
app:entries="@array/pref__keyboard__height_factor__entries"
app:entryValues="@array/pref__keyboard__height_factor__values"
app:key="keyboard__height_factor"
app:iconSpaceReserved="false"
app:title="@string/pref__suggestion__use_pref_words__label"
app:summary="@string/pref__suggestion__use_pref_words__summary"/>
app:title="@string/pref__keyboard__height_factor__label"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/pref__correction__title">
app:title="@string/pref__keyboard__group_keypress__label">
<SwitchPreferenceCompat
android:defaultValue="true"
app:key="correction__double_space_period"
android:key="keyboard__sound_enabled"
app:iconSpaceReserved="false"
app:title="@string/pref__correction__double_space_period__label"
app:summary="@string/pref__correction__double_space_period__summary"/>
app:title="@string/pref__keyboard__sound_enabled__label"/>
<dev.patrickgold.florisboard.settings.components.DialogSeekBarPreference
app:allowDividerAbove="false"
android:defaultValue="-1"
app:systemDefaultValue="-1"
app:systemDefaultValueText="@string/settings__system_default"
app:dependency="keyboard__sound_enabled"
app:key="keyboard__sound_volume"
app:min="0"
app:max="100"
app:iconSpaceReserved="false"
app:title="@string/pref__keyboard__sound_volume__label"
app:seekBarIncrement="1"
app:unit="%"/>
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="keyboard__vibration_enabled"
app:iconSpaceReserved="false"
app:title="@string/pref__keyboard__vibration_enabled__label"/>
<dev.patrickgold.florisboard.settings.components.DialogSeekBarPreference
app:allowDividerAbove="false"
android:defaultValue="-1"
app:systemDefaultValue="-1"
app:systemDefaultValueText="@string/settings__system_default"
app:dependency="keyboard__vibration_enabled"
app:key="keyboard__vibration_strength"
app:min="0"
app:max="100"
app:iconSpaceReserved="false"
app:title="@string/pref__keyboard__vibration_strength__label"
app:seekBarIncrement="1"
app:unit="%"/>
<SwitchPreferenceCompat
android:defaultValue="true"
app:key="keyboard__popup_enabled"
app:iconSpaceReserved="false"
app:title="@string/pref__keyboard__popup_visible__label"
app:summary="@string/pref__keyboard__popup_visible__summary"/>
<dev.patrickgold.florisboard.settings.components.DialogSeekBarPreference
app:allowDividerAbove="false"
android:defaultValue="300"
app:key="keyboard__long_press_delay"
app:min="100"
app:max="700"
app:iconSpaceReserved="false"
app:title="@string/pref__keyboard__long_press_delay__label"
app:seekBarIncrement="10"
app:unit=" ms"/>
</PreferenceCategory>

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