diff --git a/app/src/keyboards/java/be/scri/helpers/KeyHandler.kt b/app/src/keyboards/java/be/scri/helpers/KeyHandler.kt
index 790630911..ae8cb8a76 100644
--- a/app/src/keyboards/java/be/scri/helpers/KeyHandler.kt
+++ b/app/src/keyboards/java/be/scri/helpers/KeyHandler.kt
@@ -100,6 +100,10 @@ class KeyHandler(
handleModeChangeKey()
true
}
+ KeyboardBase.KEYCODE_FLOAT_TOGGLE -> {
+ ime.toggleFloatingMode()
+ true
+ }
KeyboardBase.KEYCODE_SPACE -> handleSpaceKeyPress(previousWasLastKeySpace)
in KeyboardBase.NAVIGATION_KEYS -> {
handleNavigationKey(code)
diff --git a/app/src/keyboards/java/be/scri/helpers/KeyboardLanguageMappingConstants.kt b/app/src/keyboards/java/be/scri/helpers/KeyboardLanguageMappingConstants.kt
index 28984886f..9ae3997d8 100644
--- a/app/src/keyboards/java/be/scri/helpers/KeyboardLanguageMappingConstants.kt
+++ b/app/src/keyboards/java/be/scri/helpers/KeyboardLanguageMappingConstants.kt
@@ -49,4 +49,16 @@ object KeyboardLanguageMappingConstants {
"RU" to RUInterfaceVariables.PLURAL_KEY_LBL,
"SV" to SVInterfaceVariables.PLURAL_KEY_LBL,
)
+
+ val floatPlaceholder =
+ mapOf(
+ "EN" to "Float",
+ "ES" to "Flotante",
+ "DE" to "Schweben",
+ "IT" to "Fluttuante",
+ "FR" to "Flottant",
+ "PT" to "Flutuante",
+ "RU" to "Плавающая",
+ "SV" to "Flytande",
+ )
}
diff --git a/app/src/keyboards/java/be/scri/helpers/ui/KeyboardUIManager.kt b/app/src/keyboards/java/be/scri/helpers/ui/KeyboardUIManager.kt
index 102bf5986..dd54cf087 100644
--- a/app/src/keyboards/java/be/scri/helpers/ui/KeyboardUIManager.kt
+++ b/app/src/keyboards/java/be/scri/helpers/ui/KeyboardUIManager.kt
@@ -56,6 +56,10 @@ class KeyboardUIManager(
fun onCloseClicked()
+ fun onFloatClicked()
+
+ fun isFloatingModeActive(): Boolean
+
fun onEmojiSelected(emoji: String)
fun onSuggestionClicked(suggestion: String)
@@ -73,6 +77,8 @@ class KeyboardUIManager(
fun processLinguisticSuggestions(word: String)
fun isNumericKeyboardActive(): Boolean
+
+ fun getKeyboardWidth(): Int
}
var keyboardView: KeyboardView = binding.keyboardView
@@ -80,6 +86,8 @@ class KeyboardUIManager(
// UI Elements
var pluralBtn: Button? = binding.pluralBtn
+ var floatBtn: Button? = null
+ var separatorFloat: View? = null
var emojiBtnPhone1: Button? = binding.emojiBtnPhone1
var emojiSpacePhone: View? = binding.emojiSpacePhone
var emojiBtnPhone2: Button? = binding.emojiBtnPhone2
@@ -210,6 +218,7 @@ class KeyboardUIManager(
listOf(binding.translateBtn, binding.conjugateBtn, binding.pluralBtn).forEachIndexed { index, button ->
button.visibility = View.VISIBLE
button.background = null
+ button.foreground = null
button.setTextColor(textColor)
button.text = HintUtils.getBaseAutoSuggestions(language).getOrNull(index)
button.isAllCaps = false
@@ -279,11 +288,31 @@ class KeyboardUIManager(
button.backgroundTintList = ContextCompat.getColorStateList(context, R.color.theme_scribe_blue)
button.setTextColor(buttonTextColor)
button.textSize = GeneralKeyboardIME.SUGGESTION_SIZE
+ button.isAllCaps = false
}
- binding.translateBtn.text = translatePlaceholder[langAlias] ?: "Translate"
- binding.conjugateBtn.text = conjugatePlaceholder[langAlias] ?: "Conjugate"
- binding.pluralBtn.text = pluralPlaceholder[langAlias] ?: "Plural"
+ val isFloating = listener.isFloatingModeActive()
+ if (isFloating) {
+ binding.translateBtn.text = ""
+ binding.conjugateBtn.text = ""
+ binding.pluralBtn.text = ""
+
+ binding.translateBtn.foreground = ContextCompat.getDrawable(context, R.drawable.ic_translate_command)
+ binding.conjugateBtn.foreground = ContextCompat.getDrawable(context, R.drawable.ic_conjugate_command)
+ binding.pluralBtn.foreground = ContextCompat.getDrawable(context, R.drawable.ic_plural_command)
+
+ binding.translateBtn.foregroundGravity = android.view.Gravity.CENTER
+ binding.conjugateBtn.foregroundGravity = android.view.Gravity.CENTER
+ binding.pluralBtn.foregroundGravity = android.view.Gravity.CENTER
+ } else {
+ binding.translateBtn.text = translatePlaceholder[langAlias] ?: "Translate"
+ binding.conjugateBtn.text = conjugatePlaceholder[langAlias] ?: "Conjugate"
+ binding.pluralBtn.text = pluralPlaceholder[langAlias] ?: "Plural"
+
+ binding.translateBtn.foreground = null
+ binding.conjugateBtn.foreground = null
+ binding.pluralBtn.foreground = null
+ }
val separatorColor = (if (isUserDarkMode) GeneralKeyboardIME.DARK_THEME else GeneralKeyboardIME.LIGHT_THEME).toColorInt()
binding.separator2.setBackgroundColor(separatorColor)
@@ -600,7 +629,8 @@ class KeyboardUIManager(
*/
fun initializeKeyboard(xmlId: Int) {
val enterKeyType = listener.getCurrentEnterKeyType()
- keyboard = KeyboardBase(context, xmlId, enterKeyType)
+ val width = listener.getKeyboardWidth()
+ keyboard = KeyboardBase(context, xmlId, enterKeyType, width)
keyboardView.setKeyboard(keyboard!!)
keyboardView.mOnKeyboardActionListener = listener.onKeyboardActionListener()
keyboardView.requestLayout()
@@ -668,6 +698,10 @@ class KeyboardUIManager(
binding.separator4.visibility = View.GONE
binding.separator5.visibility = View.GONE
binding.separator6.visibility = View.GONE
+
+ binding.translateBtn.foreground = null
+ binding.conjugateBtn.foreground = null
+ pluralBtn?.foreground = null
}
/**
diff --git a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt
index 57c7e1ca8..33b2124b7 100644
--- a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt
+++ b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt
@@ -8,6 +8,8 @@ import android.content.Context
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.graphics.Color
+import android.graphics.Rect
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
@@ -20,7 +22,10 @@ import android.text.InputType.TYPE_CLASS_PHONE
import android.text.InputType.TYPE_MASK_CLASS
import android.util.Log
import android.view.KeyEvent
+import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.EditorInfo.IME_ACTION_NONE
import android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION
@@ -35,6 +40,7 @@ import androidx.core.graphics.toColorInt
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import be.scri.R
import be.scri.activities.MainActivity
import be.scri.databinding.InputMethodViewBinding
@@ -232,7 +238,7 @@ abstract class GeneralKeyboardIME(
keyboardView = uiManager.keyboardView
// Initial keyboard setup.
- keyboard = KeyboardBase(this, getKeyboardLayoutXML(), enterKeyType)
+ keyboard = KeyboardBase(this, getKeyboardLayoutXML(), enterKeyType, getKeyboardWidth())
keyboardView?.apply {
setVibrate = getIsVibrateEnabled(applicationContext, language)
@@ -245,6 +251,12 @@ abstract class GeneralKeyboardIME(
currentState = ScribeState.IDLE
saveConjugateModeType("none")
+ viewBinding.root.post {
+ disableParentClipping(viewBinding.root)
+ }
+ initFloatingMode()
+ setupFloatingDragListener()
+
refreshUI()
return viewBinding.root
@@ -272,21 +284,53 @@ abstract class GeneralKeyboardIME(
*/
override fun onComputeInsets(outInsets: Insets) {
super.onComputeInsets(outInsets)
- // Access root view via UI manager if initialized.
if (this::uiManager.isInitialized) {
val inputView = uiManager.binding.root
if (inputView.visibility == View.VISIBLE && inputView.height > 0) {
val location = IntArray(2)
inputView.getLocationInWindow(location)
- outInsets.visibleTopInsets = location[1]
- outInsets.contentTopInsets = location[1]
- outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE
+
+ if (isFloatingMode) {
+ // In floating mode, report zero insets so Android doesn't
+ // push app content up or render IME chrome (∨ / 🌐 buttons)
+ // below the card. The touchable region is restricted to the
+ // card bounds so taps outside reach the underlying app.
+ outInsets.visibleTopInsets = 0
+ outInsets.contentTopInsets = 0
+ outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION
+
+ val card = binding.keyboardCard
+ val density = resources.displayMetrics.density
+ if (card.width > 0 && card.height > 0) {
+ val centerX = card.left + card.width / 2f + card.translationX
+ val centerY = card.top + card.height / 2f + card.translationY
+ val visualW = card.width * card.scaleX
+ val visualH = card.height * card.scaleY
+ val left = (centerX - visualW / 2f).toInt()
+ val top = (centerY - visualH / 2f).toInt()
+ val right = (centerX + visualW / 2f).toInt()
+ val bottom = (centerY + visualH / 2f).toInt()
+
+ val rect = Rect(left, top, right, bottom)
+ // Expand touchable region slightly to allow resizing handles to be clickable
+ val margin = (25 * density).toInt()
+ rect.inset(-margin, -margin)
+ outInsets.touchableRegion.set(rect)
+ } else {
+ outInsets.touchableRegion.setEmpty()
+ }
+ } else {
+ outInsets.visibleTopInsets = location[1]
+ outInsets.contentTopInsets = location[1]
+ outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE
+ }
}
}
}
override fun onWindowShown() {
super.onWindowShown()
+ applyFloatingModeState()
applyNavBarColor()
keyboardView?.setPreview = isShowPopupOnKeypressEnabled(applicationContext, language)
keyboardView?.setVibrate = getIsVibrateEnabled(applicationContext, language)
@@ -319,7 +363,7 @@ abstract class GeneralKeyboardIME(
loadLanguageData()
- keyboard = KeyboardBase(this, keyboardXml, enterKeyType)
+ keyboard = KeyboardBase(this, keyboardXml, enterKeyType, getKeyboardWidth())
keyboardView?.setKeyboard(keyboard!!)
if (this::uiManager.isInitialized && keyboardXml == R.xml.keys_symbols) {
@@ -465,7 +509,7 @@ abstract class GeneralKeyboardIME(
override fun onActionUp() {
if (switchToLetters) {
keyboardMode = keyboardLetters
- keyboard = KeyboardBase(this, getKeyboardLayoutXML(), enterKeyType)
+ keyboard = KeyboardBase(this, getKeyboardLayoutXML(), enterKeyType, getKeyboardWidth())
val editorInfo = currentInputEditorInfo
if (editorInfo != null && editorInfo.inputType != InputType.TYPE_NULL && keyboard?.mShiftState != SHIFT_ON_PERMANENT) {
if (currentInputConnection.getCursorCapsMode(editorInfo.inputType) != 0) {
@@ -586,36 +630,66 @@ abstract class GeneralKeyboardIME(
private fun applyNavBarColor() {
val window = window?.window ?: return
- val isDarkMode = getIsDarkModeOrNot(applicationContext)
- val colorRes = if (isDarkMode) R.color.dark_keyboard_bg_color else R.color.light_keyboard_bg_color
- val color = ContextCompat.getColor(this, colorRes)
+ window.decorView.post {
+ val isDarkMode = getIsDarkModeOrNot(applicationContext)
+ val colorRes = if (isDarkMode) R.color.dark_keyboard_bg_color else R.color.light_keyboard_bg_color
+ val color = ContextCompat.getColor(this, colorRes)
- if (Build.VERSION.SDK_INT >= 35) {
WindowCompat.setDecorFitsSystemWindows(window, false)
- } else {
- window.navigationBarColor = Color.TRANSPARENT
- }
+ if (Build.VERSION.SDK_INT < 35) {
+ window.navigationBarColor = Color.TRANSPARENT
+ }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- window.isNavigationBarContrastEnforced = false
- }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ window.isNavigationBarContrastEnforced = false
+ }
- window.decorView.setBackgroundColor(color)
- val insetsController = WindowCompat.getInsetsController(window, window.decorView)
- insetsController.isAppearanceLightNavigationBars = isLightColor(color)
+ if (isFloatingMode) {
+ window.decorView.setBackgroundColor(Color.TRANSPARENT)
+ } else {
+ window.decorView.setBackgroundColor(color)
+ }
+ val insetsController = WindowCompat.getInsetsController(window, window.decorView)
+ insetsController.isAppearanceLightNavigationBars = isLightColor(color)
+
+ if (isFloatingMode) {
+ insetsController.hide(WindowInsetsCompat.Type.navigationBars())
+ insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ @Suppress("DEPRECATION")
+ window.decorView.systemUiVisibility = (
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ )
+ } else {
+ insetsController.show(WindowInsetsCompat.Type.navigationBars())
+ @Suppress("DEPRECATION")
+ window.decorView.systemUiVisibility = 0
+ }
- if (this::uiManager.isInitialized) {
- uiManager.binding.root.setBackgroundColor(color)
+ if (this::uiManager.isInitialized) {
+ if (isFloatingMode) {
+ uiManager.binding.root.setBackgroundColor(Color.TRANSPARENT)
+ // Keep drag bar and pill color in sync with dark/light mode changes
+ val kbBgColor = ContextCompat.getColor(this, if (isDarkMode) R.color.dark_keyboard_bg_color else R.color.light_keyboard_bg_color)
+ uiManager.binding.floatingDragBar.setBackgroundColor(kbBgColor)
+ // Pill: dark mode → 30% white, light mode → 25% black
+ val pillColor = if (isDarkMode) 0x4DFFFFFF.toInt() else 0x40000000.toInt()
+ uiManager.binding.floatingDragHandle.setColorFilter(pillColor)
+ } else {
+ uiManager.binding.root.setBackgroundColor(color)
+ }
- ViewCompat.setOnApplyWindowInsetsListener(uiManager.binding.root) { view, insets ->
- val insetTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
- val navBarHeight = insets.getInsets(insetTypes).bottom
- view.setPadding(0, 0, 0, navBarHeight)
- insets
- }
+ ViewCompat.setOnApplyWindowInsetsListener(uiManager.binding.root) { view, insets ->
+ val insetTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
+ val navBarHeight = insets.getInsets(insetTypes).bottom
+ val paddingBottom = if (isFloatingMode) 0 else navBarHeight
+ view.setPadding(0, 0, 0, paddingBottom)
+ insets
+ }
- uiManager.binding.root.post {
- ViewCompat.requestApplyInsets(uiManager.binding.root)
+ uiManager.binding.root.post {
+ ViewCompat.requestApplyInsets(uiManager.binding.root)
+ }
}
}
}
@@ -732,6 +806,12 @@ abstract class GeneralKeyboardIME(
moveToIdleState()
}
+ override fun onFloatClicked() {
+ toggleFloatingMode()
+ }
+
+ override fun isFloatingModeActive(): Boolean = isFloatingMode
+
override fun onEmojiSelected(emoji: String) {
if (emoji.isNotEmpty()) {
insertEmoji(emoji, currentInputConnection, emojiKeywords, emojiMaxKeywordLength)
@@ -1214,7 +1294,7 @@ abstract class GeneralKeyboardIME(
this.keyboardMode = keyboardSymbols
getPrimarySymbolKeyboardLayoutXML()
}
- keyboard = KeyboardBase(this, keyboardXml, enterKeyType)
+ keyboard = KeyboardBase(this, keyboardXml, enterKeyType, getKeyboardWidth())
keyboardView!!.setKeyboard(keyboard!!)
if (keyboardXml == R.xml.keys_symbols) {
handleModeChange(keyboardMode, keyboardView, this)
@@ -1242,7 +1322,7 @@ abstract class GeneralKeyboardIME(
this.keyboardMode = keyboardLetters
getKeyboardLayoutXML()
}
- keyboard = KeyboardBase(context, keyboardXml, enterKeyType)
+ keyboard = KeyboardBase(context, keyboardXml, enterKeyType, getKeyboardWidth())
if (this.keyboardMode == keyboardLetters) {
val wasShifted = keyboard?.mShiftState == SHIFT_ON_ONE_CHAR || keyboard?.mShiftState == SHIFT_ON_PERMANENT
if (wasShifted) {
@@ -1736,6 +1816,7 @@ abstract class GeneralKeyboardIME(
button.textSize = SUGGESTION_SIZE
button.setOnClickListener(null)
button.background = null
+ button.foreground = null
button.setTextColor(textColor)
button.setOnClickListener {
currentInputConnection?.commitText("$text ", 1)
@@ -1936,4 +2017,520 @@ abstract class GeneralKeyboardIME(
* Disables all auto-suggestions and resets the suggestion buttons to their default, inactive state.
*/
fun disableAutoSuggest() = uiManager.disableAutoSuggest(language)
+
+ // MARK: Floating Keyboard Integration
+
+ override fun getKeyboardWidth(): Int =
+ if (isFloatingMode) {
+ val density = resources.displayMetrics.density
+ val screenWidth = resources.displayMetrics.widthPixels
+ val floatWidth = (320f * density).toInt()
+ Math.min(floatWidth, (screenWidth * 0.85f).toInt())
+ } else {
+ resources.displayMetrics.widthPixels
+ }
+
+ private fun recreateKeyboard() {
+ if (!this::uiManager.isInitialized) return
+ val xmlId = getCurrentKeyboardLayoutXML()
+ val currentShiftState = keyboard?.mShiftState ?: SHIFT_OFF
+ keyboard = KeyboardBase(this, xmlId, enterKeyType, getKeyboardWidth())
+ keyboard?.setShifted(currentShiftState)
+ keyboardView?.setKeyboard(keyboard!!)
+
+ if (xmlId == R.xml.keys_symbols) {
+ uiManager.setupCurrencySymbol(language)
+ }
+ keyboardView?.invalidateAllKeys()
+ }
+
+ var isFloatingMode: Boolean = false
+ private set
+
+ private var isUpdatePending = false
+ private var lastAppliedFloatingMode: Boolean? = null
+
+ fun initFloatingMode() {
+ isFloatingMode = PreferencesHelper.getIsFloatingModeEnabled(this, language)
+ lastAppliedFloatingMode = null
+ applyFloatingModeState()
+ }
+
+ fun toggleFloatingMode() {
+ isFloatingMode = !isFloatingMode
+ PreferencesHelper.setIsFloatingModeEnabled(this, language, isFloatingMode)
+ // Reset the cached mode so applyFloatingModeState always treats this as a change
+ lastAppliedFloatingMode = null
+ applyFloatingModeState()
+ window?.window?.decorView?.requestLayout()
+ }
+
+ private fun applyFloatingModeState() {
+ if (!this::uiManager.isInitialized) return
+ val card = binding.keyboardCard
+ val dragBar = binding.floatingDragBar
+ val density = resources.displayMetrics.density
+ val root = binding.root
+ val win = window?.window
+
+ // Only recreate the keyboard when the floating mode actually changes.
+ // Calling applyFloatingModeState from onWindowShown should not rebuild
+ // the keyboard every time a text field is focused.
+ val modeChanged = lastAppliedFloatingMode != isFloatingMode
+ lastAppliedFloatingMode = isFloatingMode
+
+ val rootWidth = ViewGroup.LayoutParams.MATCH_PARENT
+ val rootHeight = if (isFloatingMode) ViewGroup.LayoutParams.MATCH_PARENT else ViewGroup.LayoutParams.WRAP_CONTENT
+ val rootParams = root.layoutParams ?: ViewGroup.LayoutParams(rootWidth, rootHeight)
+ rootParams.width = rootWidth
+ rootParams.height = rootHeight
+ root.layoutParams = rootParams
+ root.minimumHeight = 0
+
+ val parentViewGroup = root.parent as? ViewGroup
+ if (parentViewGroup != null) {
+ val pParams = parentViewGroup.layoutParams
+ if (pParams != null) {
+ pParams.width = rootWidth
+ pParams.height = rootHeight
+ parentViewGroup.layoutParams = pParams
+ }
+ }
+
+ if (isFloatingMode) {
+ setBackDisposition(BACK_DISPOSITION_ADJUST_NOTHING)
+ win?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ win?.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
+ } else {
+ setBackDisposition(BACK_DISPOSITION_DEFAULT)
+ win?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ win?.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
+ }
+
+ if (isFloatingMode) {
+ val params = card.layoutParams
+ if (params != null) {
+ params.width = getKeyboardWidth()
+ card.layoutParams = params
+ }
+
+ val scaleFactor = PreferencesHelper.getFloatingScale(this, language)
+ card.scaleX = scaleFactor
+ card.scaleY = scaleFactor
+
+ // Setup resize corner handlers
+ binding.resizeHandleTopLeft.setOnTouchListener(resizeTouchListener)
+ binding.resizeHandleTopRight.setOnTouchListener(resizeTouchListener)
+ binding.resizeHandleBottomLeft.setOnTouchListener(resizeTouchListener)
+ binding.resizeHandleBottomRight.setOnTouchListener(resizeTouchListener)
+
+ // Hide initially
+ binding.resizeHandleTopLeft.visibility = View.GONE
+ binding.resizeHandleTopRight.visibility = View.GONE
+ binding.resizeHandleBottomLeft.visibility = View.GONE
+ binding.resizeHandleBottomRight.visibility = View.GONE
+
+ val isDarkMode = getIsDarkModeOrNot(this)
+ val kbBgColorRes = if (isDarkMode) R.color.dark_keyboard_bg_color else R.color.light_keyboard_bg_color
+ val kbBgColor = ContextCompat.getColor(this, kbBgColorRes)
+
+ // Build a floating card background that matches the keyboard's actual theme color
+ val floatingBg =
+ GradientDrawable().apply {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = 16f * density
+ setColor(kbBgColor)
+ setStroke((1f * density).toInt(), 0x40888888.toInt())
+ }
+ card.background = floatingBg
+ card.elevation = 8f * density
+ card.clipToOutline = true
+
+ // Drag bar must match the keyboard background — not the default (always-light) color
+ binding.floatingDragBar.setBackgroundColor(kbBgColor)
+ // Pill color: visible on both dark and light keyboard backgrounds
+ val pillColor = if (isDarkMode) 0x4DFFFFFF.toInt() else 0x40000000.toInt()
+ binding.floatingDragHandle.setColorFilter(pillColor)
+
+ dragBar.visibility = View.VISIBLE
+
+ if (modeChanged) recreateKeyboard()
+
+ card.post {
+ disableParentClipping(root)
+ var storedX = PreferencesHelper.getFloatingX(this, language)
+ var storedY = PreferencesHelper.getFloatingY(this, language)
+ val currentScale = PreferencesHelper.getFloatingScale(this, language)
+
+ // Default starting position: 100dp from the bottom of the screen
+ if (storedY == 0f) storedY = 100f * density
+
+ val screenWidth = resources.displayMetrics.widthPixels
+ val screenHeight = resources.displayMetrics.heightPixels
+ val cardWidth = card.width.toFloat()
+ val cardHeight = card.height.toFloat()
+
+ if (cardWidth > 0f && cardHeight > 0f) {
+ val maxTranslationX = (screenWidth - cardWidth * currentScale) / 2f
+ val minTranslationX = -maxTranslationX
+
+ val minTranslationY = 0f
+ val maxTranslationY = screenHeight.toFloat() - cardHeight * currentScale
+
+ val targetX = storedX.coerceInSafe(minTranslationX, maxTranslationX)
+ val targetY = storedY.coerceInSafe(minTranslationY, maxTranslationY)
+
+ updateFloatingViewsPosition(targetX, targetY, currentScale)
+
+ val attr = win?.attributes
+ if (attr != null) {
+ attr.gravity = android.view.Gravity.TOP or android.view.Gravity.START
+ attr.x = 0
+ attr.y = 0
+ attr.width = ViewGroup.LayoutParams.MATCH_PARENT
+ attr.height = ViewGroup.LayoutParams.MATCH_PARENT
+ win.attributes = attr
+ }
+ root.requestLayout()
+ }
+ }
+ } else {
+ val params = card.layoutParams
+ if (params != null) {
+ params.width = ViewGroup.LayoutParams.MATCH_PARENT
+ card.layoutParams = params
+ }
+
+ card.scaleX = 1.0f
+ card.scaleY = 1.0f
+
+ val isDarkMode = getIsDarkModeOrNot(this)
+ val kbBgColorRes = if (isDarkMode) R.color.dark_keyboard_bg_color else R.color.light_keyboard_bg_color
+ card.background = ColorDrawable(ContextCompat.getColor(this, kbBgColorRes))
+ card.elevation = 0f
+ card.clipToOutline = false
+
+ dragBar.visibility = View.GONE
+
+ if (modeChanged) recreateKeyboard()
+
+ // Reset translations immediately so the card doesn't sit at a stale
+ // floating position while the window re-layouts to WRAP_CONTENT.
+ card.translationX = 0f
+ card.translationY = 0f
+
+ binding.resizeHandleTopLeft.translationX = 0f
+ binding.resizeHandleTopLeft.translationY = 0f
+ binding.resizeHandleTopRight.translationX = 0f
+ binding.resizeHandleTopRight.translationY = 0f
+ binding.resizeHandleBottomLeft.translationX = 0f
+ binding.resizeHandleBottomLeft.translationY = 0f
+ binding.resizeHandleBottomRight.translationX = 0f
+ binding.resizeHandleBottomRight.translationY = 0f
+
+ // Hide resize handles
+ binding.resizeHandleTopLeft.visibility = View.GONE
+ binding.resizeHandleTopRight.visibility = View.GONE
+ binding.resizeHandleBottomLeft.visibility = View.GONE
+ binding.resizeHandleBottomRight.visibility = View.GONE
+
+ // Apply window attributes first, then force a layout pass to ensure
+ // the command options bar is fully visible after returning to docked mode.
+ val attr = win?.attributes
+ if (attr != null) {
+ attr.gravity = android.view.Gravity.BOTTOM
+ attr.x = 0
+ attr.y = 0
+ attr.width = ViewGroup.LayoutParams.MATCH_PARENT
+ attr.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ win.attributes = attr
+ }
+
+ // Post a second pass to guarantee translations are zero after the
+ // window has finished resizing — FLAG_LAYOUT_NO_LIMITS removal is
+ // async and can cause a stale layout frame where the bar is clipped.
+ card.post {
+ card.translationX = 0f
+ card.translationY = 0f
+ root.requestLayout()
+ }
+ }
+ applyNavBarColor()
+ }
+
+ private fun updateFloatingViewsPosition(
+ targetX: Float,
+ targetY: Float,
+ scale: Float,
+ ) {
+ val card = binding.keyboardCard
+ val screenHeight = resources.displayMetrics.heightPixels.toFloat()
+
+ val cardWidth = card.width.toFloat()
+ val cardHeight = card.height.toFloat()
+ if (cardWidth == 0f || cardHeight == 0f) return
+
+ card.scaleX = scale
+ card.scaleY = scale
+
+ val transX = targetX
+ val transY = (screenHeight - cardHeight * scale) / 2f - targetY
+
+ card.translationX = transX
+ card.translationY = transY
+
+ val scaleOffset = scale - 1.0f
+ val halfW = cardWidth / 2f
+ val halfH = cardHeight / 2f
+
+ binding.resizeHandleTopLeft.translationX = transX - halfW * scaleOffset
+ binding.resizeHandleTopLeft.translationY = transY - halfH * scaleOffset
+
+ binding.resizeHandleTopRight.translationX = transX + halfW * scaleOffset
+ binding.resizeHandleTopRight.translationY = transY - halfH * scaleOffset
+
+ binding.resizeHandleBottomLeft.translationX = transX - halfW * scaleOffset
+ binding.resizeHandleBottomLeft.translationY = transY + halfH * scaleOffset
+
+ binding.resizeHandleBottomRight.translationX = transX + halfW * scaleOffset
+ binding.resizeHandleBottomRight.translationY = transY + halfH * scaleOffset
+ }
+
+ private fun disableParentClipping(view: View) {
+ var p = view.parent
+ while (p is ViewGroup) {
+ p.clipChildren = false
+ p.clipToPadding = false
+ p = p.parent
+ }
+ }
+
+ private var initialX = 0f
+ private var initialY = 0f
+ private var initialTranslationX = 0f
+ private var initialTranslationY = 0f
+ private var maxTranslationX = 0f
+ private var minTranslationX = 0f
+ private var minTranslationY = 0f
+ private var maxTranslationY = 0f
+
+ private val cornerHideHandler = android.os.Handler(android.os.Looper.getMainLooper())
+ private val hideCornersRunnable =
+ Runnable {
+ animateHideCorners()
+ }
+
+ private var initialTouchX = 0f
+ private var initialTouchY = 0f
+ private var initialScale = 1.0f
+ private var keyboardCenterX = 0f
+ private var keyboardCenterY = 0f
+ private var initialDistance = 0f
+ private var isResizing = false
+
+ private fun showCorners() {
+ cornerHideHandler.removeCallbacks(hideCornersRunnable)
+
+ val corners =
+ listOf(
+ binding.resizeHandleTopLeft,
+ binding.resizeHandleTopRight,
+ binding.resizeHandleBottomLeft,
+ binding.resizeHandleBottomRight,
+ )
+
+ for (corner in corners) {
+ corner.animate().cancel()
+ corner.alpha = 1f
+ corner.visibility = View.VISIBLE
+ }
+ }
+
+ private fun startHideCornersTimer() {
+ cornerHideHandler.removeCallbacks(hideCornersRunnable)
+ cornerHideHandler.postDelayed(hideCornersRunnable, 3000)
+ }
+
+ private fun animateHideCorners() {
+ val corners =
+ listOf(
+ binding.resizeHandleTopLeft,
+ binding.resizeHandleTopRight,
+ binding.resizeHandleBottomLeft,
+ binding.resizeHandleBottomRight,
+ )
+
+ for (corner in corners) {
+ corner
+ .animate()
+ .alpha(0f)
+ .setDuration(300)
+ .withEndAction {
+ corner.visibility = View.GONE
+ }.start()
+ }
+ }
+
+ private fun applyScaleAndPosition(scale: Float) {
+ val card = binding.keyboardCard
+ val screenHeight = resources.displayMetrics.heightPixels.toFloat()
+ val cardHeight = card.height.toFloat()
+
+ // Recover current logical Y from live translationY (inverse of updateFloatingViewsPosition formula)
+ // transY = (screenHeight - cardHeight * scale) / 2f - targetY
+ // => targetY = (screenHeight - cardHeight * scale) / 2f - transY
+ // Use the previous scale to get the consistent Y value before scale changes
+ val liveX = card.translationX
+ val liveTransY = card.translationY
+ val prevScale = card.scaleX
+ val liveY = (screenHeight - cardHeight * prevScale) / 2f - liveTransY
+
+ updateFloatingViewsPosition(liveX, liveY, scale)
+ }
+
+ private val resizeTouchListener =
+ View.OnTouchListener { _, event ->
+ if (!isFloatingMode) return@OnTouchListener false
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ isResizing = true
+ showCorners()
+
+ initialTouchX = event.rawX
+ initialTouchY = event.rawY
+ initialScale = PreferencesHelper.getFloatingScale(this, language)
+
+ val card = binding.keyboardCard
+ val location = IntArray(2)
+ card.getLocationOnScreen(location)
+
+ keyboardCenterX = location[0] + card.width / 2f
+ keyboardCenterY = location[1] + card.height / 2f
+
+ initialDistance =
+ Math
+ .hypot(
+ (event.rawX - keyboardCenterX).toDouble(),
+ (event.rawY - keyboardCenterY).toDouble(),
+ ).toFloat()
+
+ true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (!isResizing) return@OnTouchListener false
+
+ val currentDistance =
+ Math
+ .hypot(
+ (event.rawX - keyboardCenterX).toDouble(),
+ (event.rawY - keyboardCenterY).toDouble(),
+ ).toFloat()
+
+ if (initialDistance > 0) {
+ var targetScale = initialScale * (currentDistance / initialDistance)
+ targetScale = targetScale.coerceIn(0.7f, 1.3f)
+ applyScaleAndPosition(targetScale)
+ }
+ true
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ isResizing = false
+ startHideCornersTimer()
+
+ val finalScale = binding.keyboardCard.scaleX
+ PreferencesHelper.setFloatingScale(this, language, finalScale)
+
+ // Save live position so applyFloatingModeState restores it correctly
+ val card = binding.keyboardCard
+ val screenHeight = resources.displayMetrics.heightPixels.toFloat()
+ val cardHeight = card.height.toFloat()
+ val liveY = (screenHeight - cardHeight * finalScale) / 2f - card.translationY
+ PreferencesHelper.setFloatingX(this, language, card.translationX)
+ PreferencesHelper.setFloatingY(this, language, liveY)
+
+ applyFloatingModeState()
+ true
+ }
+ else -> false
+ }
+ }
+
+ @android.annotation.SuppressLint("ClickableViewAccessibility")
+ fun setupFloatingDragListener() {
+ if (!this::uiManager.isInitialized) return
+
+ binding.floatingDragHandle.setOnTouchListener { _, event ->
+ if (!isFloatingMode) return@setOnTouchListener false
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ initialX = event.rawX
+ initialY = event.rawY
+
+ val displayMetrics = resources.displayMetrics
+ val screenWidth = displayMetrics.widthPixels
+ val screenHeight = displayMetrics.heightPixels
+ val cardWidth = binding.keyboardCard.width.toFloat()
+ val cardHeight = binding.keyboardCard.height.toFloat()
+ val scaleFactor = PreferencesHelper.getFloatingScale(this@GeneralKeyboardIME, language)
+
+ maxTranslationX = (screenWidth - cardWidth * scaleFactor) / 2f
+ minTranslationX = -maxTranslationX
+
+ minTranslationY = 0f
+ maxTranslationY = screenHeight.toFloat() - cardHeight * scaleFactor
+
+ initialTranslationX = PreferencesHelper.getFloatingX(this@GeneralKeyboardIME, language).coerceInSafe(minTranslationX, maxTranslationX)
+ initialTranslationY = PreferencesHelper.getFloatingY(this@GeneralKeyboardIME, language).coerceInSafe(minTranslationY, maxTranslationY)
+
+ showCorners()
+ true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val deltaX = event.rawX - initialX
+ val deltaY = event.rawY - initialY
+
+ var targetX = initialTranslationX + deltaX
+ var targetY = initialTranslationY - deltaY
+
+ targetX = targetX.coerceInSafe(minTranslationX, maxTranslationX)
+ targetY = targetY.coerceInSafe(minTranslationY, maxTranslationY)
+
+ val scaleFactor = PreferencesHelper.getFloatingScale(this@GeneralKeyboardIME, language)
+ updateFloatingViewsPosition(targetX, targetY, scaleFactor)
+
+ true
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ val deltaX = event.rawX - initialX
+ val deltaY = event.rawY - initialY
+ var finalTargetX = initialTranslationX + deltaX
+ var finalTargetY = initialTranslationY - deltaY
+ finalTargetX = finalTargetX.coerceInSafe(minTranslationX, maxTranslationX)
+ finalTargetY = finalTargetY.coerceInSafe(minTranslationY, maxTranslationY)
+
+ val scaleFactor = PreferencesHelper.getFloatingScale(this@GeneralKeyboardIME, language)
+ updateFloatingViewsPosition(finalTargetX, finalTargetY, scaleFactor)
+
+ PreferencesHelper.setFloatingX(this@GeneralKeyboardIME, language, finalTargetX)
+ PreferencesHelper.setFloatingY(this@GeneralKeyboardIME, language, finalTargetY)
+
+ binding.root.requestLayout()
+ startHideCornersTimer()
+ true
+ }
+ else -> false
+ }
+ }
+ }
+}
+
+private fun Float.coerceInSafe(
+ bound1: Float,
+ bound2: Float,
+): Float {
+ val minVal = if (bound1 < bound2) bound1 else bound2
+ val maxVal = if (bound1 > bound2) bound1 else bound2
+ return this.coerceIn(minVal, maxVal)
}
diff --git a/app/src/main/java/be/scri/helpers/KeyboardBase.kt b/app/src/main/java/be/scri/helpers/KeyboardBase.kt
index 4af94453d..e18aa8eec 100644
--- a/app/src/main/java/be/scri/helpers/KeyboardBase.kt
+++ b/app/src/main/java/be/scri/helpers/KeyboardBase.kt
@@ -37,6 +37,8 @@ class KeyboardBase {
val keyboardLetters: Int
fun isSearchBar(): Boolean
+
+ fun isFloatingModeActive(): Boolean
}
/** Horizontal gap default for all rows */
@@ -81,6 +83,7 @@ class KeyboardBase {
private const val WIDTH_DIVIDER = 10
const val KEYCODE_SHIFT = -1
const val KEYCODE_MODE_CHANGE = -2
+ const val KEYCODE_FLOAT_TOGGLE = -10
const val KEYCODE_ENTER = -4
const val KEYCODE_DELETE = -5
const val KEYCODE_SPACE = 32
@@ -411,8 +414,9 @@ class KeyboardBase {
context: Context,
@XmlRes xmlLayoutResId: Int,
enterKeyType: Int,
+ customWidth: Int? = null,
) {
- mDisplayWidth = context.resources.displayMetrics.widthPixels
+ mDisplayWidth = customWidth ?: context.resources.displayMetrics.widthPixels
mDefaultHorizontalGap = 0
mDefaultWidth = mDisplayWidth / WIDTH_DIVIDER
mDefaultHeight = mDefaultWidth
@@ -590,6 +594,23 @@ class KeyboardBase {
key.gap = 0
}
+ if (!isSearchBar) {
+ if (key.code == KEYCODE_MODE_CHANGE) {
+ key.width = (mDisplayWidth * 0.115).toInt()
+ } else if (currentRow.mKeys.any { it.code == KEYCODE_FLOAT_TOGGLE }) {
+ if (key.code == ','.code) {
+ key.width = (mDisplayWidth * 0.075).toInt()
+ } else if (key.label == "_") {
+ key.width = (mDisplayWidth * 0.075).toInt()
+ } else if (key.code == KEYCODE_SPACE) {
+ val isLettersLayout = currentRow.mKeys.none { it.label == "_" }
+ if (isLettersLayout) {
+ key.width = (mDisplayWidth * 0.475).toInt()
+ }
+ }
+ }
+ }
+
mKeys!!.add(key)
if (key.code == KEYCODE_ENTER) {
val enterResourceId =
@@ -627,6 +648,29 @@ class KeyboardBase {
if (x > mMinWidth) {
mMinWidth = x
}
+ if (key.code == KEYCODE_MODE_CHANGE && !isSearchBar) {
+ val floatKey = Key(currentRow!!)
+ floatKey.code = KEYCODE_FLOAT_TOGGLE
+ floatKey.width = (mDisplayWidth * 0.08).toInt()
+ floatKey.gap = (mDisplayWidth * 0.005).toInt()
+ floatKey.x = x + floatKey.gap
+ floatKey.y = y
+ floatKey.height = currentRow.defaultHeight
+
+ val isFloating = provider?.isFloatingModeActive() == true
+ val floatResourceId =
+ if (isFloating) {
+ R.drawable.ic_keyboard_dismiss
+ } else {
+ R.drawable.ic_float_keyboard
+ }
+ floatKey.icon = context.resources.getDrawable(floatResourceId, context.theme)
+ floatKey.icon?.setBounds(0, 0, floatKey.icon!!.intrinsicWidth, floatKey.icon!!.intrinsicHeight)
+
+ mKeys!!.add(floatKey)
+ currentRow.mKeys.add(floatKey)
+ x += floatKey.gap + floatKey.width
+ }
} else if (inRow) {
inRow = false
y += currentRow!!.defaultHeight
diff --git a/app/src/main/java/be/scri/helpers/PreferencesHelper.kt b/app/src/main/java/be/scri/helpers/PreferencesHelper.kt
index 1427bb8a0..b5bde4204 100644
--- a/app/src/main/java/be/scri/helpers/PreferencesHelper.kt
+++ b/app/src/main/java/be/scri/helpers/PreferencesHelper.kt
@@ -632,4 +632,79 @@ object PreferencesHelper {
val sharedPref = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
return sharedPref.getBoolean(INCREASE_TEXT_SIZE, false)
}
+
+ // MARK: Floating Keyboard Preferences
+
+ private const val FLOATING_MODE_ENABLED = "floating_mode_enabled"
+ private const val FLOATING_X = "floating_x"
+ private const val FLOATING_Y = "floating_y"
+ private const val FLOATING_SCALE = "floating_scale"
+
+ fun setIsFloatingModeEnabled(
+ context: Context,
+ language: String,
+ enabled: Boolean,
+ ) {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ sharedPref.edit { putBoolean(getLanguageSpecificPreferenceKey(FLOATING_MODE_ENABLED, language), enabled) }
+ }
+
+ fun getIsFloatingModeEnabled(
+ context: Context,
+ language: String,
+ ): Boolean {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ return sharedPref.getBoolean(getLanguageSpecificPreferenceKey(FLOATING_MODE_ENABLED, language), false)
+ }
+
+ fun setFloatingX(
+ context: Context,
+ language: String,
+ x: Float,
+ ) {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ sharedPref.edit { putFloat(getLanguageSpecificPreferenceKey(FLOATING_X, language), x) }
+ }
+
+ fun getFloatingX(
+ context: Context,
+ language: String,
+ ): Float {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ return sharedPref.getFloat(getLanguageSpecificPreferenceKey(FLOATING_X, language), 0f)
+ }
+
+ fun setFloatingY(
+ context: Context,
+ language: String,
+ y: Float,
+ ) {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ sharedPref.edit { putFloat(getLanguageSpecificPreferenceKey(FLOATING_Y, language), y) }
+ }
+
+ fun getFloatingY(
+ context: Context,
+ language: String,
+ ): Float {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ return sharedPref.getFloat(getLanguageSpecificPreferenceKey(FLOATING_Y, language), 0f)
+ }
+
+ fun setFloatingScale(
+ context: Context,
+ language: String,
+ scale: Float,
+ ) {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ sharedPref.edit { putFloat(getLanguageSpecificPreferenceKey(FLOATING_SCALE, language), scale) }
+ }
+
+ fun getFloatingScale(
+ context: Context,
+ language: String,
+ ): Float {
+ val sharedPref = context.getSharedPreferences(SCRIBE_PREFS, Context.MODE_PRIVATE)
+ return sharedPref.getFloat(getLanguageSpecificPreferenceKey(FLOATING_SCALE, language), 1.0f)
+ }
}
diff --git a/app/src/main/java/be/scri/views/KeyboardView.kt b/app/src/main/java/be/scri/views/KeyboardView.kt
index fd9372cf5..16e223b96 100644
--- a/app/src/main/java/be/scri/views/KeyboardView.kt
+++ b/app/src/main/java/be/scri/views/KeyboardView.kt
@@ -746,6 +746,17 @@ class KeyboardView
}
}
+ override fun onSizeChanged(
+ w: Int,
+ h: Int,
+ oldw: Int,
+ oldh: Int,
+ ) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ mKeyboardChanged = true
+ invalidateAllKeys()
+ }
+
/**
* Compute the average distance between adjacent keys (horizontally and vertically)
* and square it to get the proximity threshold.
diff --git a/app/src/main/res/drawable/floating_keyboard_background.xml b/app/src/main/res/drawable/floating_keyboard_background.xml
new file mode 100644
index 000000000..4b34336d7
--- /dev/null
+++ b/app/src/main/res/drawable/floating_keyboard_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_conjugate_command.xml b/app/src/main/res/drawable/ic_conjugate_command.xml
new file mode 100644
index 000000000..e23f6748c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_conjugate_command.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_drag_handle.xml b/app/src/main/res/drawable/ic_drag_handle.xml
new file mode 100644
index 000000000..9f5cdf1c9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_drag_handle.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_float_keyboard.xml b/app/src/main/res/drawable/ic_float_keyboard.xml
new file mode 100644
index 000000000..308a85fc9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_float_keyboard.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_keyboard_dismiss.xml b/app/src/main/res/drawable/ic_keyboard_dismiss.xml
new file mode 100644
index 000000000..f371a2722
--- /dev/null
+++ b/app/src/main/res/drawable/ic_keyboard_dismiss.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_plural_command.xml b/app/src/main/res/drawable/ic_plural_command.xml
new file mode 100644
index 000000000..450ca615a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_plural_command.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_resize_corner.xml b/app/src/main/res/drawable/ic_resize_corner.xml
new file mode 100644
index 000000000..868810a6d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_resize_corner.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_translate_command.xml b/app/src/main/res/drawable/ic_translate_command.xml
new file mode 100644
index 000000000..5120996c6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_translate_command.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/input_method_view.xml b/app/src/main/res/layout/input_method_view.xml
index f79cf8133..9f8bb26ce 100644
--- a/app/src/main/res/layout/input_method_view.xml
+++ b/app/src/main/res/layout/input_method_view.xml
@@ -11,7 +11,20 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyboard_holder"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+
@@ -25,7 +38,7 @@
app:layout_constraintBottom_toTopOf="@id/keyboard_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="parent">
+ app:layout_constraintTop_toTopOf="parent">
+
+
@@ -225,12 +247,12 @@
android:id="@+id/plural_btn"
android:layout_width="0dp"
android:layout_height="@dimen/command_button_height"
- android:layout_marginEnd="@dimen/tiny_margin"
android:background="@drawable/cmd_key_background_rounded"
android:contentDescription="@string/command_bar"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ android:layout_marginEnd="@dimen/tiny_margin"
app:layout_constraintStart_toEndOf="@+id/separator_3"
app:layout_constraintTop_toTopOf="parent" />
@@ -673,7 +695,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
index 56a9fcab3..f4f15d8c0 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -23,6 +23,7 @@
@color/md_grey_800_dark
@color/you_button_background_color
@color/dark_scribe_blue
+ #FFB0B0B0
@color/dark_keyboard_bg_color
@color/dark_key_color
@color/dark_keyboard_bg_color
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index e8065be55..13555101c 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -24,6 +24,7 @@
@color/dark_scribe_color
+ #FF555555
@color/light_scribe_color
#808080
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8431bc8dc..3dd17a727 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,4 +13,5 @@
Enter
Delete
Change keyboard type
+ Drag keyboard