Compare commits

...

11 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
23 changed files with 900 additions and 73 deletions

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 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)

View File

@@ -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"
}

View File

@@ -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)

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,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).

View File

@@ -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

View File

@@ -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.
*/

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

@@ -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():

View File

@@ -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].
*/

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 &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

@@ -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>

View File

@@ -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

View 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)

View 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

View File

@@ -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>

View 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)

View 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

View File

@@ -1,17 +1,12 @@
FlorisBoard &egrave una tastiera open source mirata a fornirti un modo semplice per digitare nel rispetto della tua privacy.
Nota: questo progetto &egrave 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&agrave; 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&agrave; 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> &egrave una tastiera open source mirata a fornirti un modo semplice per digitare nel rispetto della tua privacy.</p>
<p><b>Nota:</b> questo progetto &egrave 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&agrave; 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&agrave; 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>