Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15caf66370 | ||
|
|
ae0a8e551b | ||
|
|
cb4bedfc2c | ||
|
|
7d63a6885c | ||
|
|
841d797b7c | ||
|
|
0c9ba5326a | ||
|
|
7c5a7dc148 | ||
|
|
37fc714729 | ||
|
|
ec7d65ebc0 | ||
|
|
5670af16d6 | ||
|
|
6b39a846e6 |
62
README.md
62
README.md
@@ -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.
|
||||
|
||||
---
|
||||
|
||||

|
||||
<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 layouts (QWERTY, QWERTZ, AZERTY, Swiss, Spanish,
|
||||
Norwegian, Swedish/Finnish, Icelandic, Danish)
|
||||
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)
|
||||
* [x] Customize look and behaviour of keyboard
|
||||
* [x] Theme presets (currently only day/night theme)
|
||||
* [x] Theme customization
|
||||
* [ ] Theme import/export (?)
|
||||
* [ ] 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
|
||||
@@ -85,17 +96,22 @@ timeline for this, but I aim for the 0.2.0 or 0.3.0 release.
|
||||
### Other useful features
|
||||
* [x] One-handed mode
|
||||
* [x] Clipboard/cursor tools
|
||||
* [ ] Floating keyboard
|
||||
* [ ] Gesture support
|
||||
* [ ] Glide typing (?)
|
||||
* [ ] 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:
|
||||
|
||||
(?) = 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)
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
applicationId "dev.patrickgold.florisboard"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 12
|
||||
versionName "0.2.0"
|
||||
versionCode 13
|
||||
versionName "0.2.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -34,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
|
||||
@@ -398,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].
|
||||
*/
|
||||
@@ -417,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)
|
||||
|
||||
@@ -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,6 +40,8 @@ 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 localization = Localization(this)
|
||||
@@ -183,6 +188,68 @@ class PrefHelper(
|
||||
private set
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
|
||||
@@ -217,6 +217,34 @@ class SubtypeManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the previous subtype in the subtype list if possible.
|
||||
*
|
||||
* @returns The new active subtype or null if the determination process failed.
|
||||
*/
|
||||
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.localization.activeSubtypeId = when (newActiveSubtype) {
|
||||
null -> -1
|
||||
null -> Subtype.DEFAULT.id
|
||||
else -> newActiveSubtype.id
|
||||
}
|
||||
return newActiveSubtype
|
||||
|
||||
@@ -31,6 +31,7 @@ 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
|
||||
@@ -303,7 +304,8 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
val inputText =
|
||||
(ic?.getExtractedText(ExtractedTextRequest(), 0)?.text ?: "").toString()
|
||||
selectionEndMax = inputText.length
|
||||
if (isComposingEnabled) {
|
||||
// TODO: separate composing text from delete swipe word detection
|
||||
//if (isComposingEnabled) {
|
||||
if (!isTextSelected) {
|
||||
val newCursorPos = cursorAnchorInfo.selectionStart
|
||||
val prevComposingText = (cursorAnchorInfo.composingText ?: "").toString()
|
||||
@@ -325,7 +327,7 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
resetComposingText()
|
||||
}
|
||||
smartbarManager.generateCandidatesFromComposing(composingText)
|
||||
}
|
||||
//}
|
||||
if (!isNewSelectionInBoundsOfOld) {
|
||||
isManualSelectionMode = false
|
||||
isManualSelectionModeLeft = false
|
||||
@@ -431,6 +433,22 @@ 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].
|
||||
*
|
||||
@@ -475,6 +493,25 @@ class TextInputManager private constructor() : CoroutineScope by MainScope(),
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [KeyCode.ENTER] event.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ 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.setBackgroundTintColor2
|
||||
@@ -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) {
|
||||
@@ -78,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 {
|
||||
@@ -173,8 +175,17 @@ 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 -> {
|
||||
shouldBlockNextKeyCode = false
|
||||
florisboard?.prefs?.keyboard?.let {
|
||||
if (it.popupEnabled){
|
||||
keyboardView.popupManager.show(this)
|
||||
@@ -253,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():
|
||||
|
||||
@@ -29,8 +29,11 @@ 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.ime.text.gestures.SwipeGesture
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
@@ -39,11 +42,9 @@ import kotlin.math.roundToInt
|
||||
* background of this keyboard is the background of the underlying mainViewFlipper. This prevents
|
||||
* rendering issues when a keyboard is being loaded for the first time.
|
||||
*
|
||||
* TODO: Implement swipe gesture support
|
||||
*
|
||||
* @property florisboard Reference to instance of core class [FlorisBoard].
|
||||
*/
|
||||
class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
class KeyboardView : LinearLayout, FlorisBoard.EventListener, SwipeGesture.Listener {
|
||||
private var activeKeyView: KeyView? = null
|
||||
private var activePointerId: Int? = null
|
||||
private var activeX: Float = 0.0f
|
||||
@@ -57,9 +58,11 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
var desiredKeyWidth: Int = resources.getDimension(R.dimen.key_width).toInt()
|
||||
var desiredKeyHeight: Int = resources.getDimension(R.dimen.key_height).toInt()
|
||||
var florisboard: FlorisBoard? = FlorisBoard.getInstanceOrNull()
|
||||
private var initialKeyCode: Int = 0
|
||||
var isPreviewMode: Boolean = false
|
||||
var popupManager = KeyPopupManager<KeyboardView, KeyView>(this)
|
||||
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)
|
||||
@@ -70,6 +73,7 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
florisboard?.addEventListener(this)
|
||||
onWindowShown()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,6 +108,13 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
popupManager.dismissAllPopups()
|
||||
}
|
||||
|
||||
override fun onWindowShown() {
|
||||
swipeGestureDetector.apply {
|
||||
distanceThreshold = prefs.gestures.swipeDistanceThreshold
|
||||
velocityThreshold = prefs.gestures.swipeVelocityThreshold
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch all events which are designated for child views.
|
||||
*/
|
||||
@@ -121,6 +132,12 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
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) {
|
||||
@@ -131,6 +148,7 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
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
|
||||
@@ -139,6 +157,7 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
activeX = event.getX(pointerIndex)
|
||||
activeY = event.getY(pointerIndex)
|
||||
searchForActiveKeyView()
|
||||
initialKeyCode = activeKeyView?.data?.code ?: 0
|
||||
sendFlorisTouchEvent(eventFloris, MotionEvent.ACTION_DOWN)
|
||||
}
|
||||
}
|
||||
@@ -196,6 +215,45 @@ class KeyboardView : LinearLayout, FlorisBoard.EventListener {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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].
|
||||
*/
|
||||
|
||||
@@ -49,4 +49,59 @@
|
||||
<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>
|
||||
|
||||
@@ -34,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>
|
||||
|
||||
@@ -137,6 +137,41 @@
|
||||
<string name="pref__correction__double_space_period__summary">Tapping twice on spacebar inserts a period followed by a space</string>
|
||||
|
||||
<string name="settings__gestures__title">Gestures & Glide typing</string>
|
||||
<string name="pref__glide__title">Glide typing</string>
|
||||
<string name="pref__glide__enabled__label">[NYI] Enable glide typing</string>
|
||||
<string name="pref__glide__enabled__summary">Type in a word by sliding your finger through its letters</string>
|
||||
<string name="pref__glide__show_trail__label">[NYI] Show glide trail</string>
|
||||
<string name="pref__glide__show_trail__summary">Will disappear after each word</string>
|
||||
<string name="pref__gestures__title">Gestures</string>
|
||||
<string name="pref__gestures__swipe_action__no_action">No action</string>
|
||||
<string name="pref__gestures__swipe_action__delete_word">Delete word</string>
|
||||
<string name="pref__gestures__swipe_action__hide_keyboard">Hide keyboard</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_up">Move cursor up</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_down">Move cursor down</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_left">Move cursor left</string>
|
||||
<string name="pref__gestures__swipe_action__move_cursor_right">Move cursor right</string>
|
||||
<string name="pref__gestures__swipe_action__shift">Shift</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_prev_subtype">Switch to previous subtype</string>
|
||||
<string name="pref__gestures__swipe_action__switch_to_next_subtype">Switch to next subtype</string>
|
||||
<string name="pref__gestures__swipe_up__label">Swipe up</string>
|
||||
<string name="pref__gestures__swipe_down__label">Swipe down</string>
|
||||
<string name="pref__gestures__swipe_left__label">Swipe left</string>
|
||||
<string name="pref__gestures__swipe_right__label">Swipe right</string>
|
||||
<string name="pref__gestures__space_bar_swipe_left__label">Space bar swipe left</string>
|
||||
<string name="pref__gestures__space_bar_swipe_right__label">Space bar swipe right</string>
|
||||
<string name="pref__gestures__delete_key_swipe_left__label">Delete key swipe left</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__label">Swipe velocity threshold</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_slow">Very slow</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__slow">Slow</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__normal">Normal</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__fast">Fast</string>
|
||||
<string name="pref__gestures__swipe_velocity_threshold__very_fast">Very fast</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__label">Swipe distance threshold</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_short">Very short</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__short">Short</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__normal">Normal</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__long">Long</string>
|
||||
<string name="pref__gestures__swipe_distance_threshold__very_long">Very long</string>
|
||||
|
||||
<string name="settings__advanced__title">Advanced</string>
|
||||
<string name="pref__advanced__settings_theme__label">Settings theme</string>
|
||||
|
||||
@@ -2,8 +2,118 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<Preference
|
||||
app:title="Not yet implemented..."
|
||||
app:iconSpaceReserved="false"/>
|
||||
<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>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.0'
|
||||
ext.kotlin_version = '1.4.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
9
fastlane/metadata/android/en-US/changelogs/12.txt
Normal file
9
fastlane/metadata/android/en-US/changelogs/12.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
- Add theming capability
|
||||
- Preset selection
|
||||
- Modify each color individually
|
||||
- Themes are now defined in assets/ime/theme/ as a json file, which allows for a more convenient way to add custom themes (base for future feature)
|
||||
- Restructure settings naming scheme and structure
|
||||
- Add back button functionality in settings
|
||||
- Add clipboard/cursor tools
|
||||
- Improve layout measurement and height behaviour
|
||||
- Fix enter action key bug (#17)
|
||||
4
fastlane/metadata/android/en-US/changelogs/13.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/13.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Add gestures & scrolling space bar
|
||||
- Swipe up / down / left / right
|
||||
- Swipe left from delete key (defaults to deleting current word)
|
||||
- Moving your finger along the space bar will move the cursor
|
||||
@@ -1,17 +1,12 @@
|
||||
FlorisBoard is an open-source keyboard aimed at providing you with an easy way to type while respecting your privacy.
|
||||
|
||||
Note: This project is currently in alpha stage. If you want to see a feature being implemented or want to report a bug, please visit this project's repository (linked in the end of the description) on GitHub and file an issue. This helps making FlorisBoard even better! Thank you!
|
||||
|
||||
Currently implemented and fully working features:
|
||||
* Latin keyboard layouts
|
||||
* QWERTY, QWERTZ, AZERTY, Spanish, Norwegian, Swedish/Finnish, Danish, Icelandic and Swiss. More coming in future releases
|
||||
* Easy switching between languages/layouts by defining subtypes in the settings
|
||||
* Keyboard layouts for typing in a (phone) number
|
||||
* Special characters input
|
||||
* Emoji/Emoticon keyboard
|
||||
* One-handed/compact mode for easier typing on large devices
|
||||
* Light and dark theme for the input UI (more themes will come in future releases)
|
||||
* Customization of key press sound/vibration
|
||||
|
||||
Source code (Apache 2.0) and project management:
|
||||
https://github.com/florisboard/florisboard
|
||||
<p><i>FlorisBoard</i> is an open-source keyboard aimed at providing you with an easy way to type while respecting your privacy.</p>
|
||||
<p><b>Note:</b> This project is currently in alpha stage. If you want to see a feature being implemented or want to report a bug, please visit this project's repository (linked in the end of the description) on GitHub and file an issue. This helps making FlorisBoard even better! Thank you!</p>
|
||||
<p><br><b>Currently implemented and fully working features:</b></p><ul>
|
||||
<li>Latin keyboard layouts</li>
|
||||
<li>QWERTY, QWERTZ, AZERTY, Spanish, Norwegian, Swedish/Finnish, Danish, Icelandic and Swiss. More coming in future releases</li>
|
||||
<li>Easy switching between languages/layouts by defining subtypes in the settings</li>
|
||||
<li>Keyboard layouts for typing in a (phone) number</li>
|
||||
<li>Special characters input</li>
|
||||
<li>Emoji/Emoticon keyboard</li>
|
||||
<li>One-handed/compact mode for easier typing on large devices</li>
|
||||
<li>Light and dark theme for the input UI (more themes will come in future releases)</li>
|
||||
<li>Customization of key press sound/vibration</li></ul>
|
||||
9
fastlane/metadata/android/it/changelogs/12.txt
Normal file
9
fastlane/metadata/android/it/changelogs/12.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
- Aggiunta di capacità di tematizzazione
|
||||
- Selezione delle preimpostazioni
|
||||
- Modificare ogni colore individualmente
|
||||
- I temi sono ora definiti in asset/imo/tema/ come un file json, che permette un modo più conveniente per aggiungere temi personalizzati (base per la funzione futura)
|
||||
- Schema di denominazione e struttura delle impostazioni della ristrutturazione
|
||||
- Aggiungere la funzionalità del pulsante indietro nelle impostazioni
|
||||
- Aggiunta di strumenti per appunti/cursori
|
||||
- Migliorare la misurazione del layout e il comportamento in altezza
|
||||
- Correggere il bug del tasto azione (#17)
|
||||
4
fastlane/metadata/android/it/changelogs/13.txt
Normal file
4
fastlane/metadata/android/it/changelogs/13.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Aggiungi gesti e barra spaziatrice a scorrimento
|
||||
- Scorri su / giù / sinistra / destra
|
||||
- Passare il dito a sinistra dal tasto di cancellazione (default per cancellare la parola corrente)
|
||||
- Muovendo il dito lungo la barra spaziatrice, il cursore si sposta
|
||||
@@ -1,17 +1,12 @@
|
||||
FlorisBoard è una tastiera open source mirata a fornirti un modo semplice per digitare nel rispetto della tua privacy.
|
||||
|
||||
Nota: questo progetto è attualmente in fase alpha. Se desideri vedere una funzionalità implementata o segnalare un bug, visita il repository di questo progetto (link alla fine della descrizione) su GitHub e segnala un problema. Questo aiuta a rendere FlorisBoard ancora migliore! Grazie!
|
||||
|
||||
Funzionalità attualmente implementate e completamente funzionanti:
|
||||
* Layout della tastiera
|
||||
* QWERTY, QWERTZ, AZERTY, Spagnolo, norvegese, svedese,finlandese, danese, islandese e svizzero. Altre novità in arrivo nelle versioni future
|
||||
* Facile passaggio tra lingue / layout nelle impostazioni
|
||||
* Layout di tastiera tipo tastierino telefonico per digitazione di un numero
|
||||
* Inserimento di caratteri speciali
|
||||
* Tastiera Emoji/Emoticon
|
||||
* Modalità con una sola mano per digitare più facilmente su dispositivi di grandi dimensioni
|
||||
* Tema chiaro e scuro per l'interfaccia utente e per tastiera (altri temi arriveranno nelle versioni future)
|
||||
* Personalizzazione del suono / vibrazione della pressione dei tasti
|
||||
|
||||
Codice sorgente (Apache 2.0) e gestione del progetto:
|
||||
https://github.com/florisboard/florisboard
|
||||
<p><i>FlorisBoard</i> è una tastiera open source mirata a fornirti un modo semplice per digitare nel rispetto della tua privacy.</p>
|
||||
<p><b>Nota:</b> questo progetto è attualmente in fase alpha. Se desideri vedere una funzionalità implementata o segnalare un bug, visita il repository di questo progetto (link alla fine della descrizione) su GitHub e segnala un problema. Questo aiuta a rendere FlorisBoard ancora migliore! Grazie!</p>
|
||||
<p><br><b>Funzionalità attualmente implementate e completamente funzionanti:</b></p><ul>
|
||||
<li>Layout della tastiera</li>
|
||||
<li>QWERTY, QWERTZ, AZERTY, Spagnolo, norvegese, svedese,finlandese, danese, islandese e svizzero. Altre novità in arrivo nelle versioni future</li>
|
||||
<li>Facile passaggio tra lingue / layout nelle impostazioni</li>
|
||||
<li>Layout di tastiera tipo tastierino telefonico per digitazione di un numero</li>
|
||||
<li>Inserimento di caratteri speciali</li>
|
||||
<li>Tastiera Emoji/Emoticon</li>
|
||||
<li>Modalità con una sola mano per digitare più facilmente su dispositivi di grandi dimensioni</li>
|
||||
<li>Tema chiaro e scuro per l'interfaccia utente e per tastiera (altri temi arriveranno nelle versioni future)</li>
|
||||
<li>Personalizzazione del suono / vibrazione della pressione dei tasti</li></ul>
|
||||
Reference in New Issue
Block a user