mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Compare commits
14 Commits
2c632bd4bd
...
ccbd59ee80
Author | SHA1 | Date | |
---|---|---|---|
|
ccbd59ee80 | ||
|
2f8d847186 | ||
|
112b7a291a | ||
|
2e2bba2ac2 | ||
|
28f3c07d5f | ||
|
1c9c94b83d | ||
|
da95c69668 | ||
|
a668a3c801 | ||
|
c5cb5efabd | ||
|
7fdf54ec61 | ||
|
2ff68bf7cd | ||
|
3d8233be92 | ||
|
32b27b84c0 | ||
|
4566a37e16 |
@ -53,7 +53,7 @@
|
||||
<!-- Spoken description for the "To Alpha" keyboard key. -->
|
||||
<string name="spoken_description_to_alpha">Letters</string>
|
||||
<!-- Spoken description for the "To Numbers" keyboard key. -->
|
||||
<string name="spoken_description_to_numeric">Numbers</string>
|
||||
<string name="spoken_description_to_numeric">Digits</string>
|
||||
<!-- Spoken description for the "Settings" keyboard key. -->
|
||||
<string name="spoken_description_settings">Settings</string>
|
||||
<!-- Spoken description for the "Tab" keyboard key. -->
|
||||
@ -77,6 +77,12 @@
|
||||
<!-- Spoken description for the "Previous" action keyboard key. -->
|
||||
<string name="spoken_description_action_previous">Previous</string>
|
||||
|
||||
|
||||
<!-- Spoken description for the "To Numbers" keyboard key. -->
|
||||
<string name="spoken_description_to_alt_0">Page 1</string>
|
||||
<string name="spoken_description_to_alt_1">Page 2</string>
|
||||
<string name="spoken_description_to_alt_2">Page 3</string>
|
||||
|
||||
<!-- Spoken feedback after turning "Shift" mode on. -->
|
||||
<string name="spoken_description_shiftmode_on">Shift enabled</string>
|
||||
<!-- Spoken feedback after turning "Caps lock" mode on. -->
|
||||
@ -91,6 +97,8 @@
|
||||
<string name="spoken_description_mode_phone">Phone mode</string>
|
||||
<!-- Spoken feedback after changing to the shifted phone dialer (symbols) keyboard. -->
|
||||
<string name="spoken_description_mode_phone_shift">Phone symbols mode</string>
|
||||
<!-- Spoken feedback after changing to the digits keyboard. -->
|
||||
<string name="spoken_description_mode_digits">Digits mode</string>
|
||||
|
||||
<!-- Spoken feedback when the keyboard is hidden. -->
|
||||
<string name="announce_keyboard_hidden">Keyboard hidden</string>
|
||||
|
@ -16,6 +16,11 @@
|
||||
<string name="more_actions_action_title">All Actions</string>
|
||||
<string name="bug_viewer_action_title">Bug Viewer</string>
|
||||
|
||||
<string name="left_handed_keyboard_action_title">Left-Handed Keyboard</string>
|
||||
<string name="right_handed_keyboard_action_title">Right-Handed Keyboard</string>
|
||||
<string name="split_keyboard_action_title">Split Keyboard</string>
|
||||
<string name="floating_keyboard_action_title">Floating Keyboard</string>
|
||||
|
||||
<string name="action_kind_action_key">Action Key</string>
|
||||
<string name="action_kind_pinned_key">Pinned Action(s)</string>
|
||||
<string name="action_kind_favorites">Favorite Actions</string>
|
||||
|
@ -62,6 +62,10 @@ final class KeyCodeDescriptionMapper {
|
||||
mKeyCodeMap.put(Constants.CODE_SHIFT, R.string.spoken_description_shift);
|
||||
mKeyCodeMap.put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic);
|
||||
mKeyCodeMap.put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol);
|
||||
mKeyCodeMap.put(Constants.CODE_TO_NUMBER_LAYOUT, R.string.spoken_description_to_numeric);
|
||||
mKeyCodeMap.put(Constants.CODE_TO_ALT_0_LAYOUT, R.string.spoken_description_to_alt_0);
|
||||
mKeyCodeMap.put(Constants.CODE_TO_ALT_1_LAYOUT, R.string.spoken_description_to_alt_1);
|
||||
mKeyCodeMap.put(Constants.CODE_TO_ALT_2_LAYOUT, R.string.spoken_description_to_alt_2);
|
||||
mKeyCodeMap.put(Constants.CODE_TAB, R.string.spoken_description_tab);
|
||||
mKeyCodeMap.put(Constants.CODE_LANGUAGE_SWITCH,
|
||||
R.string.spoken_description_language_switch);
|
||||
|
@ -181,7 +181,16 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView>
|
||||
node.setFocusable(true);
|
||||
node.setScreenReaderFocusable(true);
|
||||
|
||||
if(k.isActionKey() || k.getCode() == Constants.CODE_SWITCH_ALPHA_SYMBOL || k.getCode() == Constants.CODE_EMOJI || k.getCode() == Constants.CODE_SYMBOL_SHIFT || (k.getCode() >= Constants.CODE_ACTION_0 && k.getCode() <= Constants.CODE_ACTION_MAX)) {
|
||||
if(k.isActionKey() ||
|
||||
k.getCode() == Constants.CODE_SWITCH_ALPHA_SYMBOL ||
|
||||
k.getCode() == Constants.CODE_EMOJI ||
|
||||
k.getCode() == Constants.CODE_SYMBOL_SHIFT ||
|
||||
k.getCode() == Constants.CODE_TO_ALT_0_LAYOUT ||
|
||||
k.getCode() == Constants.CODE_TO_ALT_1_LAYOUT ||
|
||||
k.getCode() == Constants.CODE_TO_ALT_2_LAYOUT ||
|
||||
k.getCode() == Constants.CODE_TO_NUMBER_LAYOUT ||
|
||||
(k.getCode() >= Constants.CODE_ACTION_0 && k.getCode() <= Constants.CODE_ACTION_MAX)
|
||||
) {
|
||||
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
|
||||
node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
|
||||
node.setClickable(true);
|
||||
|
@ -18,10 +18,8 @@ package org.futo.inputmethod.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import org.futo.inputmethod.keyboard.Key;
|
||||
import org.futo.inputmethod.keyboard.KeyDetector;
|
||||
@ -30,7 +28,6 @@ import org.futo.inputmethod.keyboard.KeyboardId;
|
||||
import org.futo.inputmethod.keyboard.MainKeyboardView;
|
||||
import org.futo.inputmethod.keyboard.PointerTracker;
|
||||
import org.futo.inputmethod.latin.R;
|
||||
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils;
|
||||
|
||||
/**
|
||||
* This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance
|
||||
@ -191,6 +188,9 @@ public final class MainKeyboardAccessibilityDelegate
|
||||
case KeyboardId.ELEMENT_PHONE_SYMBOLS:
|
||||
resId = R.string.spoken_description_mode_phone_shift;
|
||||
break;
|
||||
case KeyboardId.ELEMENT_NUMBER:
|
||||
resId = R.string.spoken_description_mode_digits;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import android.text.TextUtils;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import org.futo.inputmethod.compat.EditorInfoCompatUtils;
|
||||
import org.futo.inputmethod.latin.RichInputMethodSubtype;
|
||||
import org.futo.inputmethod.latin.settings.LongPressKeySettings;
|
||||
import org.futo.inputmethod.latin.utils.InputTypeUtils;
|
||||
|
||||
|
@ -140,40 +140,16 @@ public final class KeyboardSwitcher implements SwitchActions {
|
||||
}
|
||||
|
||||
|
||||
final KeyboardSizingCalculator sizingCalculator = new KeyboardSizingCalculator(mLatinIMELegacy.getInputMethodService());
|
||||
final KeyboardSizingCalculator sizingCalculator = ((LatinIME)mLatinIMELegacy.getInputMethodService()).getSizingCalculator();
|
||||
final ComputedKeyboardSize computedSize = sizingCalculator.calculate(layoutSetName, settingsValues.mIsNumberRowEnabled);
|
||||
|
||||
int keyboardWidth = 0;
|
||||
int keyboardHeight = 0;
|
||||
|
||||
int splitLayoutWidth = 0;
|
||||
|
||||
Rect padding = new Rect();
|
||||
|
||||
Window window = mLatinIMELegacy.getInputMethodService().getWindow().getWindow();
|
||||
|
||||
if(computedSize instanceof SplitKeyboardSize) {
|
||||
keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(window, res);
|
||||
keyboardHeight = ((SplitKeyboardSize) computedSize).getHeight();
|
||||
splitLayoutWidth = ((SplitKeyboardSize) computedSize).getSplitLayoutWidth();
|
||||
padding = ((SplitKeyboardSize) computedSize).getPadding();
|
||||
}else if(computedSize instanceof RegularKeyboardSize) {
|
||||
keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(window, res);
|
||||
keyboardHeight = ((RegularKeyboardSize) computedSize).getHeight();
|
||||
padding = ((RegularKeyboardSize) computedSize).getPadding();
|
||||
}
|
||||
|
||||
final KeyboardLayoutSetV2Params params = new KeyboardLayoutSetV2Params(
|
||||
keyboardWidth,
|
||||
keyboardHeight,
|
||||
padding,
|
||||
computedSize,
|
||||
layoutSetName,
|
||||
subtype.getLocale(),
|
||||
editorInfo == null ? new EditorInfo() : editorInfo,
|
||||
settingsValues.mIsNumberRowEnabled,
|
||||
sizingCalculator.calculateGap(),
|
||||
splitLayoutWidth != 0,
|
||||
splitLayoutWidth,
|
||||
settingsValues.mShowsActionKey ? settingsValues.mActionKeyId : null,
|
||||
LongPressKeySettings.load(mThemeContext)
|
||||
);
|
||||
@ -353,9 +329,7 @@ public final class KeyboardSwitcher implements SwitchActions {
|
||||
}
|
||||
|
||||
public boolean isShowingMoreKeysPanel() {
|
||||
if (isShowingEmojiPalettes()) {
|
||||
return false;
|
||||
}
|
||||
if(mKeyboardView == null) return false;
|
||||
return mKeyboardView.isShowingMoreKeysPanel();
|
||||
}
|
||||
|
||||
|
@ -132,16 +132,8 @@ public final class KeyPreviewChoreographer {
|
||||
final int keyPreviewPosition;
|
||||
int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
|
||||
+ CoordinateUtils.x(originCoords);
|
||||
if (previewX < 0) {
|
||||
previewX = 0;
|
||||
keyPreviewPosition = KeyPreviewView.POSITION_LEFT;
|
||||
} else if (previewX > keyboardViewWidth - previewWidth) {
|
||||
previewX = keyboardViewWidth - previewWidth;
|
||||
keyPreviewPosition = KeyPreviewView.POSITION_RIGHT;
|
||||
} else {
|
||||
keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
|
||||
}
|
||||
final boolean hasMoreKeys = (key.getMoreKeys() != null);
|
||||
keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
|
||||
final boolean hasMoreKeys = !key.getMoreKeys().isEmpty();
|
||||
keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition);
|
||||
// The key preview is placed vertically above the top edge of the parent key with an
|
||||
// arbitrary offset.
|
||||
|
@ -126,8 +126,8 @@ internal data class SavedKeyboardState(
|
||||
class KeyboardState(private val switchActions: SwitchActions) {
|
||||
companion object {
|
||||
private const val TAG = "KeyboardState"
|
||||
private const val DEBUG_EVENT = true
|
||||
private const val DEBUG_INTERNAL_ACTION = true
|
||||
private const val DEBUG_EVENT = false
|
||||
private const val DEBUG_INTERNAL_ACTION = false
|
||||
}
|
||||
|
||||
private val shiftKeyState = ShiftKeyState("Shift")
|
||||
|
@ -12,6 +12,7 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.CompletionInfo
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InlineSuggestionsRequest
|
||||
@ -21,10 +22,14 @@ import android.view.inputmethod.InputMethodSubtype
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@ -75,28 +80,27 @@ import org.futo.inputmethod.latin.uix.theme.applyWindowColors
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
|
||||
import org.futo.inputmethod.updates.scheduleUpdateCheckingJob
|
||||
import org.futo.inputmethod.v2keyboard.ComputedKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.FloatingKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardSettings
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardSizeSettingKind
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardSizeStateProvider
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
|
||||
import org.futo.inputmethod.v2keyboard.getHeight
|
||||
|
||||
private class UnlockedBroadcastReceiver(val onDeviceUnlocked: () -> Unit) : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
println("Unlocked Broadcast Receiver: ${intent?.action}")
|
||||
if (intent?.action == Intent.ACTION_USER_UNLOCKED) {
|
||||
onDeviceUnlocked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
|
||||
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, FoldStateProvider,
|
||||
KeyboardSizeStateProvider {
|
||||
|
||||
open class InputMethodServiceCompose : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
|
||||
private lateinit var mLifecycleRegistry: LifecycleRegistry
|
||||
private lateinit var mViewModelStore: ViewModelStore
|
||||
private lateinit var mSavedStateRegistryController: SavedStateRegistryController
|
||||
|
||||
|
||||
|
||||
fun setOwners() {
|
||||
val decorView = window.window?.decorView
|
||||
if (decorView?.findViewTreeLifecycleOwner() == null) {
|
||||
@ -110,11 +114,59 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
mLifecycleRegistry = LifecycleRegistry(this)
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
|
||||
|
||||
mViewModelStore = ViewModelStore()
|
||||
|
||||
mSavedStateRegistryController = SavedStateRegistryController.create(this)
|
||||
mSavedStateRegistryController.performRestore(null)
|
||||
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||
}
|
||||
|
||||
override fun onStartInputView(editorInfo: EditorInfo?, restarting: Boolean) {
|
||||
super.onStartInputView(editorInfo, restarting)
|
||||
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = mLifecycleRegistry
|
||||
override val savedStateRegistry: SavedStateRegistry
|
||||
get() = mSavedStateRegistryController.savedStateRegistry
|
||||
override val viewModelStore: ViewModelStore
|
||||
get() = mViewModelStore
|
||||
|
||||
internal var composeView: ComposeView? = null
|
||||
|
||||
override fun onCreateInputView(): View =
|
||||
ComposeView(this).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setParentCompositionContext(null)
|
||||
|
||||
setOwners()
|
||||
|
||||
composeView = this
|
||||
}
|
||||
}
|
||||
|
||||
class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripController,
|
||||
DynamicThemeProviderOwner, FoldStateProvider, KeyboardSizeStateProvider {
|
||||
val latinIMELegacy = LatinIMELegacy(
|
||||
this as InputMethodService,
|
||||
this as LatinIMELegacy.SuggestionStripController
|
||||
)
|
||||
|
||||
|
||||
val inputLogic get() = latinIMELegacy.mInputLogic
|
||||
|
||||
lateinit var languageModelFacilitator: LanguageModelFacilitator
|
||||
@ -122,6 +174,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
val uixManager = UixManager(this)
|
||||
lateinit var suggestionBlacklist: SuggestionBlacklist
|
||||
|
||||
val sizingCalculator = KeyboardSizingCalculator(this, uixManager)
|
||||
|
||||
private var activeThemeOption: ThemeOption? = null
|
||||
private var activeColorScheme = VoiceInputTheme.obtainColors(this)
|
||||
private var pendingRecreateKeyboard: Boolean = false
|
||||
@ -130,6 +184,13 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
val colorScheme get() = activeColorScheme
|
||||
val keyboardColor get() = drawableProvider?.primaryKeyboardColor?.let { androidx.compose.ui.graphics.Color(it) } ?: colorScheme.surface
|
||||
|
||||
val size: MutableState<ComputedKeyboardSize?> = mutableStateOf(null)
|
||||
private fun calculateSize(): ComputedKeyboardSize
|
||||
= sizingCalculator.calculate(
|
||||
latinIMELegacy.mKeyboardSwitcher.keyboard?.mId?.mKeyboardLayoutSetName ?: "qwerty",
|
||||
latinIMELegacy.mKeyboardSwitcher.keyboard?.mId?.mNumberRow ?: false
|
||||
)
|
||||
|
||||
private var drawableProvider: DynamicThemeProvider? = null
|
||||
|
||||
private var lastEditorInfo: EditorInfo? = null
|
||||
@ -204,7 +265,26 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSizeUpdated() {
|
||||
val newSize = calculateSize()
|
||||
val shouldInvalidateKeyboard = size.value?.let { oldSize ->
|
||||
when {
|
||||
oldSize is FloatingKeyboardSize && newSize is FloatingKeyboardSize -> {
|
||||
oldSize.width != newSize.width || oldSize.height != newSize.height
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
} ?: true
|
||||
|
||||
size.value = newSize
|
||||
|
||||
if(shouldInvalidateKeyboard) {
|
||||
invalidateKeyboard(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidateKeyboard(refreshSettings: Boolean = false) {
|
||||
size.value = calculateSize()
|
||||
settingsRefreshRequired = settingsRefreshRequired || refreshSettings
|
||||
|
||||
if(!uixManager.isMainKeyboardHidden) {
|
||||
@ -256,16 +336,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
val filter = IntentFilter(Intent.ACTION_USER_UNLOCKED)
|
||||
registerReceiver(unlockReceiver, filter)
|
||||
|
||||
mLifecycleRegistry = LifecycleRegistry(this)
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
|
||||
|
||||
mViewModelStore = ViewModelStore()
|
||||
|
||||
mSavedStateRegistryController = SavedStateRegistryController.create(this)
|
||||
mSavedStateRegistryController.performRestore(null)
|
||||
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||
|
||||
suggestionBlacklist = SuggestionBlacklist(latinIMELegacy.mSettings, this, lifecycleScope)
|
||||
|
||||
Subtypes.addDefaultSubtypesIfNecessary(this)
|
||||
@ -348,6 +418,21 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
// Listen to size changes
|
||||
launchJob {
|
||||
val prev: MutableMap<KeyboardSizeSettingKind, String?> =
|
||||
KeyboardSizeSettingKind.entries.associateWith { null }.toMutableMap()
|
||||
|
||||
dataStore.data.collect { data ->
|
||||
prev.keys.toList().forEach {
|
||||
if(data[KeyboardSettings[it]!!.key] != prev[it]) {
|
||||
prev[it] = data[KeyboardSettings[it]!!.key]
|
||||
onSizeUpdated()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uixManager.onCreate()
|
||||
|
||||
Settings.getInstance().settingsChangedListeners.add { oldSettings, newSettings ->
|
||||
@ -364,7 +449,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
unregisterReceiver(unlockReceiver)
|
||||
|
||||
stopJobs()
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
viewModelStore.clear()
|
||||
|
||||
languageModelFacilitator.saveHistoryLog()
|
||||
@ -381,6 +465,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
Log.w("LatinIME", "Configuration changed")
|
||||
size.value = calculateSize()
|
||||
latinIMELegacy.onConfigurationChanged(newConfig)
|
||||
super.onConfigurationChanged(newConfig)
|
||||
}
|
||||
@ -390,22 +475,22 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
|
||||
private var legacyInputView: View? = null
|
||||
private var touchableHeight: Int = 0
|
||||
override fun onCreateInputView(): View {
|
||||
Log.w("LatinIME", "Create input view")
|
||||
legacyInputView = latinIMELegacy.onCreateInputView()
|
||||
val composeView = super.onCreateInputView()
|
||||
|
||||
val composeView = uixManager.createComposeView()
|
||||
legacyInputView = latinIMELegacy.onCreateInputView()
|
||||
latinIMELegacy.setComposeInputView(composeView)
|
||||
|
||||
uixManager.setContent()
|
||||
|
||||
return composeView
|
||||
}
|
||||
|
||||
private var inputViewHeight: Int = -1
|
||||
|
||||
// Both called by UixManager
|
||||
fun updateTouchableHeight(to: Int) { touchableHeight = to }
|
||||
fun getInputViewHeight(): Int = inputViewHeight
|
||||
fun getViewHeight(): Int = composeView?.height ?: resources.displayMetrics.heightPixels
|
||||
fun getViewWidth(): Int = composeView?.width ?: resources.displayMetrics.widthPixels
|
||||
|
||||
private var isInputModal = false
|
||||
fun setInputModal(to: Boolean) {
|
||||
@ -426,11 +511,11 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
key(legacyInputView) {
|
||||
AndroidView(factory = {
|
||||
legacyInputView!!
|
||||
legacyInputView!!.also {
|
||||
if(it.parent != null) (it.parent as ViewGroup).removeView(it)
|
||||
}
|
||||
}, modifier = modifier, onRelease = {
|
||||
val view = it as InputView
|
||||
view.deallocateMemory()
|
||||
@ -445,7 +530,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
legacyInputView = newView
|
||||
|
||||
uixManager.setContent()
|
||||
uixManager.getComposeView()?.let {
|
||||
composeView?.let {
|
||||
latinIMELegacy.setComposeInputView(it)
|
||||
}
|
||||
|
||||
@ -455,7 +540,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
override fun setInputView(view: View?) {
|
||||
super.setInputView(view)
|
||||
|
||||
uixManager.getComposeView()?.let {
|
||||
composeView?.let {
|
||||
latinIMELegacy.setComposeInputView(it)
|
||||
}
|
||||
|
||||
@ -473,8 +558,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
|
||||
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
|
||||
mLifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
|
||||
lastEditorInfo = info
|
||||
|
||||
super.onStartInputView(info, restarting)
|
||||
@ -561,36 +644,55 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
}
|
||||
|
||||
override fun onComputeInsets(outInsets: Insets?) {
|
||||
val composeView = uixManager.getComposeView()
|
||||
|
||||
// This method may be called before {@link #setInputView(View)}.
|
||||
if (legacyInputView == null || composeView == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val inputHeight: Int = composeView.height
|
||||
if (latinIMELegacy.isImeSuppressedByHardwareKeyboard && !legacyInputView!!.isShown) {
|
||||
// If there is a hardware keyboard and a visible software keyboard view has been hidden,
|
||||
// no visual element will be shown on the screen.
|
||||
latinIMELegacy.setInsets(outInsets!!.apply {
|
||||
contentTopInsets = inputHeight
|
||||
visibleTopInsets = inputHeight
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
val visibleTopY = inputHeight - touchableHeight
|
||||
|
||||
val touchLeft = 0
|
||||
val touchTop = if(isInputModal) { 0 } else { visibleTopY }
|
||||
val touchRight = composeView.width
|
||||
val touchBottom = inputHeight
|
||||
|
||||
val viewHeight = composeView!!.height
|
||||
val size = size.value ?: return
|
||||
latinIMELegacy.setInsets(outInsets!!.apply {
|
||||
touchableInsets = Insets.TOUCHABLE_INSETS_REGION;
|
||||
touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
|
||||
contentTopInsets = visibleTopY
|
||||
visibleTopInsets = visibleTopY
|
||||
when(size) {
|
||||
is FloatingKeyboardSize -> {
|
||||
val height = uixManager.touchableHeight
|
||||
|
||||
val left = size.bottomOrigin.first
|
||||
val bottomYFromBottom = size.bottomOrigin.second
|
||||
var bottom = viewHeight - bottomYFromBottom
|
||||
var top = bottom - height
|
||||
val right = left + size.width
|
||||
|
||||
if(top < 0) {
|
||||
bottom -= top
|
||||
top -= top
|
||||
}
|
||||
|
||||
touchableInsets = Insets.TOUCHABLE_INSETS_REGION
|
||||
touchableRegion.set(left, top, right, bottom)
|
||||
contentTopInsets = viewHeight
|
||||
visibleTopInsets = viewHeight
|
||||
}
|
||||
else -> {
|
||||
touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT
|
||||
|
||||
val touchableHeight = uixManager.touchableHeight
|
||||
val topInset = if(touchableHeight < 1 || touchableHeight >= viewHeight - 1) {
|
||||
val actionBarHeight = sizingCalculator.calculateTotalActionBarHeightPx()
|
||||
|
||||
viewHeight - size.getHeight() - actionBarHeight
|
||||
} else {
|
||||
viewHeight - touchableHeight
|
||||
}
|
||||
|
||||
contentTopInsets = topInset
|
||||
visibleTopInsets = topInset
|
||||
}
|
||||
}
|
||||
|
||||
if(isInputModal || latinIMELegacy.mKeyboardSwitcher?.isShowingMoreKeysPanel == true) {
|
||||
touchableInsets = Insets.TOUCHABLE_INSETS_REGION
|
||||
touchableRegion.set(0, 0, composeView!!.width, composeView!!.height)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -737,14 +839,6 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
return super.getCurrentInputConnection()
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = mLifecycleRegistry
|
||||
override val savedStateRegistry: SavedStateRegistry
|
||||
get() = mSavedStateRegistryController.savedStateRegistry
|
||||
override val viewModelStore: ViewModelStore
|
||||
get() = mViewModelStore
|
||||
|
||||
|
||||
private fun onDeviceUnlocked() {
|
||||
Log.i("LatinIME", "DEVICE has UNLOCKED!!! Reloading settings...")
|
||||
// Every place that called getDefaultSharedPreferences now needs to be refreshed or call it again
|
||||
|
@ -1617,10 +1617,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
// punctuation suggestions (if it's disabled).
|
||||
@Override
|
||||
public void setNeutralSuggestionStrip() {
|
||||
final SettingsValues currentSettings = mSettings.getCurrent();
|
||||
final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
|
||||
? SuggestedWords.getEmptyInstance()
|
||||
: currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
|
||||
final SuggestedWords neutralSuggestions = SuggestedWords.getEmptyInstance();
|
||||
setSuggestedWords(neutralSuggestions);
|
||||
}
|
||||
|
||||
|
@ -439,6 +439,7 @@ public final class InputLogic {
|
||||
// The cursor has been moved : we now accept to perform recapitalization
|
||||
mRecapitalizeStatus.enable();
|
||||
// We moved the cursor. If we are touching a word, we need to resume suggestion.
|
||||
mIsAutoCorrectionIndicatorOn = false;
|
||||
mLatinIMELegacy.mHandler.postResumeSuggestions(true /* shouldDelay */);
|
||||
// Stop the last recapitalization, if started.
|
||||
mRecapitalizeStatus.stop();
|
||||
|
@ -164,8 +164,8 @@ public class SettingsValues {
|
||||
final String autoCorrectionThresholdRawValue = mAutoCorrectEnabled
|
||||
? res.getString(R.string.auto_correction_threshold_mode_index_modest)
|
||||
: res.getString(R.string.auto_correction_threshold_mode_index_off);
|
||||
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
|
||||
mTransformerPredictionEnabled = readTransformerPredictionEnabled(prefs, res);
|
||||
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res) || mTransformerPredictionEnabled;
|
||||
mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
|
||||
mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration());
|
||||
mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true);
|
||||
|
@ -15,7 +15,9 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import org.futo.inputmethod.latin.LatinIME
|
||||
import org.futo.inputmethod.latin.SuggestionBlacklist
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
|
||||
import java.util.Locale
|
||||
|
||||
interface ActionInputTransaction {
|
||||
@ -66,8 +68,11 @@ interface KeyboardManagerForAction {
|
||||
fun activateAction(action: Action)
|
||||
fun showActionEditor()
|
||||
|
||||
fun getSuggestionBlacklist(): SuggestionBlacklist
|
||||
fun getLatinIMEForDebug(): LatinIME
|
||||
fun isDeviceLocked(): Boolean
|
||||
|
||||
fun getSizingCalculator(): KeyboardSizingCalculator
|
||||
}
|
||||
|
||||
interface ActionWindow {
|
||||
|
@ -72,6 +72,7 @@ import androidx.compose.ui.text.ExperimentalTextApi
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.drawText
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@ -87,6 +88,7 @@ import org.futo.inputmethod.latin.SuggestedWords
|
||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
|
||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_EMOJI_SUGGESTION
|
||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
|
||||
import org.futo.inputmethod.latin.SuggestionBlacklist
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewListener
|
||||
import org.futo.inputmethod.latin.uix.actions.FavoriteActions
|
||||
@ -98,7 +100,6 @@ import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
|
||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
||||
import org.futo.inputmethod.latin.uix.theme.Typography
|
||||
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||
import java.lang.Integer.min
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -220,17 +221,9 @@ fun AutoFitText(
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean, onClick: () -> Unit, onLongClick: () -> Unit) {
|
||||
val word = try {
|
||||
words.getWord(idx)
|
||||
} catch(e: IndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
|
||||
val wordInfo = try {
|
||||
words.getInfo(idx)
|
||||
} catch(e: IndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
val wordInfo = words.getInfoOrNull(idx)
|
||||
val isVerbatim = wordInfo?.kind == KIND_TYPED
|
||||
val word = wordInfo?.mWord
|
||||
|
||||
val actualIsPrimary = isPrimary && (words.mWillAutoCorrect || ((wordInfo?.isExactMatch) == true))
|
||||
|
||||
@ -277,9 +270,12 @@ fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean,
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
|
||||
if (word != null) {
|
||||
AutoFitText(word, style = textStyle, modifier = textModifier
|
||||
.align(Center)
|
||||
.padding(2.dp))
|
||||
val modifier = textModifier.align(Center).padding(2.dp)
|
||||
if(isVerbatim) {
|
||||
AutoFitText('"' + word + '"', style = textStyle.copy(fontStyle = FontStyle.Italic), modifier = modifier)
|
||||
} else {
|
||||
AutoFitText(word, style = textStyle, modifier = modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,87 +292,137 @@ fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean,
|
||||
}
|
||||
|
||||
|
||||
data class SuggestionLayout(
|
||||
/** Set to the word to be autocorrected to */
|
||||
val autocorrectMatch: SuggestedWordInfo?,
|
||||
|
||||
// Show the most probable in the middle, then left, then right
|
||||
val ORDER_OF_SUGGESTIONS = listOf(1, 0, 2)
|
||||
/** Other words, sorted by likelihood */
|
||||
val sortedMatches: List<SuggestedWordInfo>,
|
||||
|
||||
/** Emoji suggestions if they are to be shown */
|
||||
val emojiMatches: List<SuggestedWordInfo>,
|
||||
|
||||
/** The exact word the user typed */
|
||||
val verbatimWord: SuggestedWordInfo?,
|
||||
|
||||
/** Set to true if the best match is so unlikely that we should show verbatim instead */
|
||||
val areSuggestionsClueless: Boolean,
|
||||
|
||||
/** Set to true if this is a gesture update, and we should only show one suggestion */
|
||||
val isGestureBatch: Boolean,
|
||||
|
||||
val presentableSuggestions: List<SuggestedWordInfo>
|
||||
)
|
||||
|
||||
fun SuggestedWords.getInfoOrNull(idx: Int): SuggestedWordInfo? = try {
|
||||
getInfo(idx)
|
||||
} catch(e: IndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
|
||||
fun makeSuggestionLayout(words: SuggestedWords, blacklist: SuggestionBlacklist): SuggestionLayout {
|
||||
val typedWord = words.getInfoOrNull(SuggestedWords.INDEX_OF_TYPED_WORD)?.let {
|
||||
if(it.kind == KIND_TYPED) { it } else { null }
|
||||
}?.let {
|
||||
if(blacklist.isSuggestedWordOk(it)) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val autocorrectMatch = words.getInfoOrNull(SuggestedWords.INDEX_OF_AUTO_CORRECTION)?.let {
|
||||
if(words.mWillAutoCorrect) { it } else { null }
|
||||
}
|
||||
|
||||
// We actually have to avoid sorting these because they are provided sorted in an important order
|
||||
|
||||
val emojiMatches = words.mSuggestedWordInfoList.filter {
|
||||
it.kind == KIND_EMOJI_SUGGESTION
|
||||
}
|
||||
|
||||
val sortedMatches = words.mSuggestedWordInfoList.filter {
|
||||
it != typedWord && it.kind != KIND_TYPED && it != autocorrectMatch && !emojiMatches.contains(it)
|
||||
}
|
||||
|
||||
val areSuggestionsClueless = (autocorrectMatch ?: sortedMatches.getOrNull(0))?.let {
|
||||
it.mOriginatesFromTransformerLM && it.mScore < -50
|
||||
} ?: false
|
||||
|
||||
val isGestureBatch = words.mInputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH
|
||||
|
||||
val presentableSuggestions = (
|
||||
listOf(
|
||||
typedWord,
|
||||
autocorrectMatch,
|
||||
) + sortedMatches
|
||||
).filterNotNull()
|
||||
|
||||
return SuggestionLayout(
|
||||
autocorrectMatch = autocorrectMatch,
|
||||
sortedMatches = sortedMatches,
|
||||
emojiMatches = emojiMatches,
|
||||
verbatimWord = typedWord,
|
||||
areSuggestionsClueless = areSuggestionsClueless,
|
||||
isGestureBatch = isGestureBatch,
|
||||
presentableSuggestions = presentableSuggestions
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.SuggestionItems(words: SuggestedWords, onClick: (i: Int) -> Unit, onLongClick: (i: Int) -> Unit) {
|
||||
val maxSuggestions = min(ORDER_OF_SUGGESTIONS.size, words.size())
|
||||
val layout = makeSuggestionLayout(words, LocalManager.current.getSuggestionBlacklist())
|
||||
|
||||
if(maxSuggestions == 0) {
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
return
|
||||
}
|
||||
|
||||
if(maxSuggestions == 1 || words.mInputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH) {
|
||||
SuggestionItem(
|
||||
words,
|
||||
0,
|
||||
isPrimary = true,
|
||||
onClick = { onClick(0) },
|
||||
onLongClick = { onLongClick(0) }
|
||||
)
|
||||
|
||||
return
|
||||
} else if(words.mInputStyle == SuggestedWords.INPUT_STYLE_TAIL_BATCH && maxSuggestions > 1) {
|
||||
//words.mSuggestedWordInfoList.removeAt(0);
|
||||
}
|
||||
|
||||
|
||||
var offset = 0
|
||||
|
||||
try {
|
||||
val info = words.getInfo(0)
|
||||
if (info.kind == KIND_TYPED && !info.isExactMatch && !info.isExactMatchWithIntentionalOmission) {
|
||||
offset = 1
|
||||
val suggestionItem = @Composable { suggestion: SuggestedWordInfo? ->
|
||||
if(suggestion != null) {
|
||||
val idx = words.indexOf(suggestion)
|
||||
SuggestionItem(
|
||||
words,
|
||||
idx,
|
||||
isPrimary = idx == SuggestedWords.INDEX_OF_AUTO_CORRECTION,
|
||||
onClick = { onClick(idx) },
|
||||
onLongClick = { onLongClick(idx) }
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.weight(1.0f))
|
||||
}
|
||||
} catch(_: IndexOutOfBoundsException) {
|
||||
|
||||
}
|
||||
|
||||
// Check for "clueless" suggestions, and display typed word in center if so
|
||||
try {
|
||||
if(offset == 1) {
|
||||
val info = words.getInfo(1)
|
||||
if(info.mOriginatesFromTransformerLM && info.mScore < -50) {
|
||||
offset = 0;
|
||||
println(layout)
|
||||
when {
|
||||
layout.isGestureBatch ||
|
||||
layout.presentableSuggestions.size <= 1 -> suggestionItem(layout.presentableSuggestions.firstOrNull())
|
||||
|
||||
layout.autocorrectMatch != null -> {
|
||||
var supplementalSuggestionIndex = 0
|
||||
if(layout.emojiMatches.isEmpty()) {
|
||||
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex++))
|
||||
} else {
|
||||
suggestionItem(layout.emojiMatches[0])
|
||||
}
|
||||
SuggestionSeparator()
|
||||
suggestionItem(layout.autocorrectMatch)
|
||||
SuggestionSeparator()
|
||||
|
||||
if(layout.verbatimWord != null && layout.verbatimWord.mWord != layout.autocorrectMatch.mWord) {
|
||||
suggestionItem(layout.verbatimWord)
|
||||
} else {
|
||||
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex))
|
||||
}
|
||||
}
|
||||
} catch(_: IndexOutOfBoundsException) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
val suggestionOrder = mutableListOf(
|
||||
ORDER_OF_SUGGESTIONS[0] + offset,
|
||||
ORDER_OF_SUGGESTIONS[1] + offset,
|
||||
if(offset == 1) { 0 - offset } else { ORDER_OF_SUGGESTIONS[2] } + offset,
|
||||
)
|
||||
|
||||
// Find emoji
|
||||
try {
|
||||
for(i in 0 until words.size()) {
|
||||
val info = words.getInfo(i)
|
||||
if(info.mKindAndFlags == KIND_EMOJI_SUGGESTION && i > 2) {
|
||||
suggestionOrder[0] = i
|
||||
else -> {
|
||||
var supplementalSuggestionIndex = 1
|
||||
if(layout.emojiMatches.isEmpty()) {
|
||||
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex++))
|
||||
} else {
|
||||
suggestionItem(layout.emojiMatches[0])
|
||||
}
|
||||
SuggestionSeparator()
|
||||
suggestionItem(layout.sortedMatches.getOrNull(0))
|
||||
SuggestionSeparator()
|
||||
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex))
|
||||
}
|
||||
} catch(_: IndexOutOfBoundsException) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
for (i in 0 until maxSuggestions) {
|
||||
SuggestionItem(
|
||||
words,
|
||||
suggestionOrder[i],
|
||||
isPrimary = i == (maxSuggestions / 2),
|
||||
onClick = { onClick(suggestionOrder[i]) },
|
||||
onLongClick = { onLongClick(suggestionOrder[i]) }
|
||||
)
|
||||
|
||||
if (i < maxSuggestions - 1) SuggestionSeparator()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@ -30,6 +29,7 @@ import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2Params
|
||||
import org.futo.inputmethod.v2keyboard.LayoutManager
|
||||
import org.futo.inputmethod.v2keyboard.RegularKeyboardSize
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -76,23 +76,8 @@ fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp, locale: Locale? = null
|
||||
}
|
||||
}
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val isLandscape = false//configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
|
||||
val widthPx: Int
|
||||
val heightPx: Int
|
||||
|
||||
when {
|
||||
isLandscape -> {
|
||||
widthPx = (500.0 * context.resources.displayMetrics.density).roundToInt()
|
||||
heightPx = (180.0 * context.resources.displayMetrics.density).roundToInt()
|
||||
}
|
||||
|
||||
else -> {
|
||||
widthPx = (320.0 * context.resources.displayMetrics.density).roundToInt()
|
||||
heightPx = (200.0 * context.resources.displayMetrics.density).roundToInt()
|
||||
}
|
||||
}
|
||||
val widthPx: Int = (320.0 * context.resources.displayMetrics.density).roundToInt()
|
||||
val heightPx: Int = (200.0 * context.resources.displayMetrics.density).roundToInt()
|
||||
|
||||
val keyboard = remember { mutableStateOf<Keyboard?>(null) }
|
||||
|
||||
@ -107,16 +92,12 @@ fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp, locale: Locale? = null
|
||||
val layoutSet = KeyboardLayoutSetV2(
|
||||
context,
|
||||
KeyboardLayoutSetV2Params(
|
||||
width = widthPx,
|
||||
height = heightPx,
|
||||
padding = Rect(),
|
||||
computedSize = RegularKeyboardSize(width = widthPx, height = heightPx, padding = Rect()),
|
||||
gap = 4.0f,
|
||||
keyboardLayoutSet = id,
|
||||
locale = loc ?: Locale.ENGLISH,
|
||||
editorInfo = editorInfo,
|
||||
numberRow = numberRow,
|
||||
useSplitLayout = isLandscape,
|
||||
splitLayoutWidth = widthPx * 2 / 3,
|
||||
bottomActionKey = null
|
||||
)
|
||||
)
|
||||
|
130
java/src/org/futo/inputmethod/latin/uix/ResizerRect.kt
Normal file
130
java/src/org/futo/inputmethod/latin/uix/ResizerRect.kt
Normal file
@ -0,0 +1,130 @@
|
||||
package org.futo.inputmethod.latin.uix
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.toSize
|
||||
|
||||
|
||||
enum class CurrentDraggingTarget {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
Center
|
||||
}
|
||||
|
||||
private fun CurrentDraggingTarget.computeOffset(size: Size): Offset = when(this) {
|
||||
CurrentDraggingTarget.TopLeft -> Offset(0.0f, 0.0f)
|
||||
CurrentDraggingTarget.TopRight -> Offset(size.width, 0.0f)
|
||||
CurrentDraggingTarget.BottomLeft -> Offset(0.0f, size.height)
|
||||
CurrentDraggingTarget.BottomRight -> Offset(size.width, size.height)
|
||||
CurrentDraggingTarget.Center -> Offset(size.width * 0.5f, size.height * 0.5f)
|
||||
}
|
||||
|
||||
private fun CurrentDraggingTarget.computeOffset(size: IntSize): Offset =
|
||||
computeOffset(size.toSize())
|
||||
|
||||
private fun CurrentDraggingTarget.dragDelta(offset: Offset): DragDelta = when(this) {
|
||||
CurrentDraggingTarget.TopLeft -> DragDelta(left = offset.x, top = offset.y)
|
||||
CurrentDraggingTarget.TopRight -> DragDelta(right = offset.x, top = offset.y)
|
||||
CurrentDraggingTarget.BottomLeft -> DragDelta(left = offset.x, bottom = offset.y)
|
||||
CurrentDraggingTarget.BottomRight -> DragDelta(right = offset.x, bottom = offset.y)
|
||||
CurrentDraggingTarget.Center -> DragDelta(
|
||||
left = offset.x,
|
||||
right = offset.x,
|
||||
top = offset.y,
|
||||
bottom = offset.y
|
||||
)
|
||||
}
|
||||
|
||||
data class DragDelta(
|
||||
val left: Float = 0.0f,
|
||||
val top: Float = 0.0f,
|
||||
val right: Float = 0.0f,
|
||||
val bottom: Float = 0.0f
|
||||
)
|
||||
|
||||
|
||||
@Composable
|
||||
fun BoxScope.ResizerRect(onDragged: (DragDelta) -> Boolean, showResetApply: Boolean, onApply: () -> Unit, onReset: () -> Unit) {
|
||||
val shape = RectangleShape
|
||||
|
||||
val draggingState = remember { mutableStateOf<CurrentDraggingTarget?>(null) }
|
||||
val wasAccepted = remember { mutableStateOf(true) }
|
||||
|
||||
Box(Modifier
|
||||
.matchParentSize()
|
||||
.background(MaterialTheme.colorScheme.background.copy(alpha = 0.5f), shape)
|
||||
.border(3.dp, MaterialTheme.colorScheme.primary, shape)
|
||||
.pointerInput(Unit) {
|
||||
detectDragGestures(
|
||||
onDragStart = { offset ->
|
||||
draggingState.value = CurrentDraggingTarget.entries.minBy {
|
||||
offset.minus(it.computeOffset(size)).getDistanceSquared()
|
||||
}
|
||||
},
|
||||
onDrag = { _, amount ->
|
||||
draggingState.value?.let {
|
||||
wasAccepted.value = onDragged(it.dragDelta(amount))
|
||||
}
|
||||
},
|
||||
onDragEnd = {
|
||||
draggingState.value = null
|
||||
wasAccepted.value = true
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
val primaryInverseColor = MaterialTheme.colorScheme.inversePrimary
|
||||
val errorColor = MaterialTheme.colorScheme.error
|
||||
val radius = with(LocalDensity.current) { 24.dp.toPx() }
|
||||
|
||||
Canvas(Modifier.matchParentSize(), onDraw = {
|
||||
CurrentDraggingTarget.entries.forEach {
|
||||
drawCircle(
|
||||
color = if (!wasAccepted.value) {
|
||||
errorColor
|
||||
} else if (draggingState.value == it) {
|
||||
primaryInverseColor
|
||||
} else {
|
||||
primaryColor
|
||||
},
|
||||
radius = radius,
|
||||
center = it.computeOffset(size)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (showResetApply) {
|
||||
Row(Modifier.align(Alignment.BottomCenter).padding(16.dp)) {
|
||||
TextButton({ onReset() }) { Text("Reset") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
TextButton({ onApply() }) { Text("Apply") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.content.ClipDescription
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
@ -23,22 +24,33 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.absolutePadding
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@ -46,17 +58,24 @@ import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
@ -75,6 +94,7 @@ import org.futo.inputmethod.latin.LatinIME
|
||||
import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.latin.SuggestedWords
|
||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
|
||||
import org.futo.inputmethod.latin.SuggestionBlacklist
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.inputlogic.InputLogic
|
||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewListener
|
||||
@ -96,6 +116,15 @@ import org.futo.inputmethod.updates.deferManualUpdate
|
||||
import org.futo.inputmethod.updates.isManualUpdateTimeExpired
|
||||
import org.futo.inputmethod.updates.openManualUpdateCheck
|
||||
import org.futo.inputmethod.updates.retrieveSavedLastUpdateCheckResult
|
||||
import org.futo.inputmethod.v2keyboard.ComputedKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.FloatingKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
|
||||
import org.futo.inputmethod.v2keyboard.OneHandedDirection
|
||||
import org.futo.inputmethod.v2keyboard.OneHandedKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.RegularKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.SplitKeyboardSize
|
||||
import org.futo.inputmethod.v2keyboard.getPadding
|
||||
import org.futo.inputmethod.v2keyboard.getWidth
|
||||
import java.util.Locale
|
||||
|
||||
val LocalManager = staticCompositionLocalOf<KeyboardManagerForAction> {
|
||||
@ -279,7 +308,7 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
|
||||
override fun announce(s: String) {
|
||||
AccessibilityUtils.init(getContext())
|
||||
if(AccessibilityUtils.getInstance().isAccessibilityEnabled) {
|
||||
AccessibilityUtils.getInstance().announceForAccessibility(uixManager.getComposeView(), s)
|
||||
AccessibilityUtils.getInstance().announceForAccessibility(uixManager.composeView, s)
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +324,12 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
|
||||
return getContext().isDeviceLocked
|
||||
}
|
||||
|
||||
override fun getSizingCalculator(): KeyboardSizingCalculator =
|
||||
latinIME.sizingCalculator
|
||||
|
||||
override fun getLatinIMEForDebug(): LatinIME = latinIME
|
||||
|
||||
override fun getSuggestionBlacklist(): SuggestionBlacklist = latinIME.suggestionBlacklist
|
||||
}
|
||||
|
||||
data class ActiveDialogRequest(
|
||||
@ -305,11 +339,12 @@ data class ActiveDialogRequest(
|
||||
)
|
||||
|
||||
class UixManager(private val latinIME: LatinIME) {
|
||||
internal val composeView: ComposeView?
|
||||
get() = latinIME.composeView
|
||||
|
||||
private var shouldShowSuggestionStrip: Boolean = true
|
||||
private var suggestedWords: SuggestedWords? = null
|
||||
|
||||
private var composeView: ComposeView? = null
|
||||
|
||||
private var currWindowAction: Action? = null
|
||||
private var persistentStates: HashMap<Action, PersistentActionState?> = hashMapOf()
|
||||
|
||||
@ -327,6 +362,9 @@ class UixManager(private val latinIME: LatinIME) {
|
||||
latinIME.deferSetSetting(ActionBarExpanded, isActionsExpanded.value)
|
||||
}
|
||||
|
||||
val actionsExpanded: Boolean
|
||||
get() = isActionsExpanded.value
|
||||
|
||||
private var isShowingActionEditor = mutableStateOf(false)
|
||||
fun showActionEditor() {
|
||||
isShowingActionEditor.value = true
|
||||
@ -337,6 +375,12 @@ class UixManager(private val latinIME: LatinIME) {
|
||||
var isInputOverridden = mutableStateOf(false)
|
||||
|
||||
var currWindowActionWindow: ActionWindow? = null
|
||||
val isActionWindowDocked: Boolean
|
||||
get() = currWindowActionWindow != null
|
||||
|
||||
private var measuredTouchableHeight = 0
|
||||
val touchableHeight: Int
|
||||
get() = measuredTouchableHeight
|
||||
|
||||
val isMainKeyboardHidden get() = mainKeyboardHidden
|
||||
|
||||
@ -626,46 +670,249 @@ class UixManager(private val latinIME: LatinIME) {
|
||||
)
|
||||
}
|
||||
|
||||
fun setContent() {
|
||||
composeView?.setContent {
|
||||
UixThemeWrapper(latinIME.colorScheme) {
|
||||
DataStoreCacheProvider {
|
||||
CompositionLocalProvider(LocalManager provides keyboardManagerForAction) {
|
||||
CompositionLocalProvider(LocalThemeProvider provides latinIME.getDrawableProvider()) {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
CompositionLocalProvider(LocalFoldingState provides foldingOptions.value) {
|
||||
InputDarkener(isInputOverridden.value || isShowingActionEditor.value) {
|
||||
closeActionWindow()
|
||||
isShowingActionEditor.value = false
|
||||
}
|
||||
@Composable
|
||||
private fun OffsetPositioner(offset: Offset, content: @Composable () -> Unit) {
|
||||
Column(modifier = Modifier.fillMaxHeight().absoluteOffset { IntOffset(offset.x.toInt(), 0) }) {
|
||||
Spacer(Modifier.weight(1.0f))
|
||||
content()
|
||||
Spacer(Modifier.height(with(LocalDensity.current) { offset.y.toDp() }))
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
Surface(modifier = Modifier.onSizeChanged {
|
||||
latinIME.updateTouchableHeight(it.height)
|
||||
}, color = latinIME.keyboardColor) {
|
||||
Box {
|
||||
Column {
|
||||
when {
|
||||
currWindowActionWindow != null -> ActionViewWithHeader(
|
||||
currWindowActionWindow!!
|
||||
)
|
||||
@Composable
|
||||
private fun KeyboardSurface(
|
||||
requiredWidthPx: Int,
|
||||
backgroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = RectangleShape,
|
||||
padding: Rect = Rect(),
|
||||
content: @Composable BoxScope.() -> Unit
|
||||
) = with(LocalDensity.current) {
|
||||
Box(modifier
|
||||
.onSizeChanged { measuredTouchableHeight = it.height}
|
||||
.background(backgroundColor, shape)
|
||||
.requiredWidth(requiredWidthPx.toDp())
|
||||
.absolutePadding(
|
||||
left = padding.left.toDp(),
|
||||
top = padding.top.toDp(),
|
||||
right = padding.right.toDp(),
|
||||
bottom = padding.bottom.toDp(),
|
||||
)
|
||||
.clipToBounds()
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentColor provides contentColorFor(backgroundColor)) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> MainKeyboardViewWithActionBar()
|
||||
}
|
||||
@Composable
|
||||
private fun FloatingKeyboardContents(
|
||||
pointerInputKey: Any?,
|
||||
onDragged: (Offset) -> Unit,
|
||||
onDragEnd: () -> Unit,
|
||||
onResizerOpen: () -> Unit,
|
||||
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
||||
) {
|
||||
// Content
|
||||
Box(Modifier.fillMaxWidth()) {
|
||||
content(4.dp)
|
||||
}
|
||||
|
||||
latinIME.LegacyKeyboardView(hidden = isMainKeyboardHidden)
|
||||
}
|
||||
// Bottom drag bar
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(20.dp)
|
||||
.pointerInput(pointerInputKey) {
|
||||
detectDragGestures(
|
||||
onDrag = { _, dragAmount -> onDragged(dragAmount)},
|
||||
onDragEnd = { onDragEnd() })
|
||||
}) {
|
||||
|
||||
ForgetWordDialog()
|
||||
}
|
||||
IconButton(onClick = {
|
||||
onResizerOpen()
|
||||
}, Modifier.align(Alignment.CenterEnd)) {
|
||||
Icon(Icons.Default.Menu, contentDescription = "resize")
|
||||
}
|
||||
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(0.6f).height(4.dp)
|
||||
.align(Alignment.TopCenter).background(
|
||||
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||
RoundedCornerShape(100)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@Composable
|
||||
private fun FloatingKeyboardWindow(
|
||||
size: FloatingKeyboardSize,
|
||||
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
||||
) = with(LocalDensity.current) {
|
||||
val offset = remember(size) { mutableStateOf(Offset(size.bottomOrigin.first.toFloat(), size.bottomOrigin.second.toFloat())) }
|
||||
|
||||
ActionEditorHost()
|
||||
}
|
||||
val resizing = remember { mutableStateOf(false) }
|
||||
|
||||
val onDragDelta: (DragDelta) -> Boolean = remember { { delta ->
|
||||
// Matching the necessary coordinate space
|
||||
var deltaX = delta.left
|
||||
var deltaY = -delta.bottom
|
||||
var deltaWidth = delta.right - delta.left
|
||||
var deltaHeight = delta.bottom - delta.top
|
||||
|
||||
var result = true
|
||||
|
||||
// TODO: Limit the values so that we do not go off-screen
|
||||
// If we have reached a minimum limit, return false
|
||||
|
||||
// Basic limiting for minimum size
|
||||
val currSettings = latinIME.sizingCalculator.getSavedSettings()
|
||||
val currSize = Size(
|
||||
currSettings.floatingWidthDp.dp.toPx(),
|
||||
currSettings.floatingHeightDp.dp.toPx()
|
||||
)
|
||||
|
||||
if(currSize.width + deltaWidth < 200.dp.toPx()) {
|
||||
deltaWidth = deltaWidth.coerceAtLeast(200.dp.toPx() - currSize.width)
|
||||
deltaX = 0.0f
|
||||
result = false
|
||||
}
|
||||
|
||||
if(currSize.height + deltaHeight < 160.dp.toPx()) {
|
||||
deltaHeight = deltaHeight.coerceAtLeast(160.dp.toPx() - currSize.height)
|
||||
deltaY = 0.0f
|
||||
result = false
|
||||
}
|
||||
|
||||
latinIME.sizingCalculator.editSavedSettings { settings ->
|
||||
settings.copy(
|
||||
floatingBottomOriginDp = Pair(
|
||||
settings.floatingBottomOriginDp.first + deltaX.toDp().value,
|
||||
settings.floatingBottomOriginDp.second + deltaY.toDp().value
|
||||
),
|
||||
floatingWidthDp = settings.floatingWidthDp + deltaWidth.toDp().value,
|
||||
floatingHeightDp = settings.floatingHeightDp + deltaHeight.toDp().value
|
||||
)
|
||||
}
|
||||
|
||||
result
|
||||
} }
|
||||
|
||||
OffsetPositioner(offset.value) {
|
||||
KeyboardSurface(
|
||||
requiredWidthPx = size.width,
|
||||
backgroundColor = latinIME.keyboardColor,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
padding = size.decorationPadding
|
||||
) {
|
||||
Column {
|
||||
FloatingKeyboardContents(
|
||||
pointerInputKey = size,
|
||||
onDragged = { dragAmount ->
|
||||
var newOffset = offset.value.copy(
|
||||
x = offset.value.x + dragAmount.x,
|
||||
y = offset.value.y - dragAmount.y
|
||||
)
|
||||
|
||||
// Ensure we are not out of bounds
|
||||
newOffset = newOffset.copy(
|
||||
newOffset.x.coerceAtLeast(0.0f),
|
||||
newOffset.y.coerceAtLeast(0.0f)
|
||||
)
|
||||
newOffset = newOffset.copy(
|
||||
newOffset.x.coerceAtMost(
|
||||
latinIME.getViewWidth().toFloat() - size.width
|
||||
),
|
||||
newOffset.y.coerceAtMost(
|
||||
latinIME.getViewHeight().toFloat() - measuredTouchableHeight
|
||||
)
|
||||
)
|
||||
|
||||
offset.value = newOffset
|
||||
},
|
||||
onDragEnd = {
|
||||
latinIME.sizingCalculator.editSavedSettings { settings ->
|
||||
settings.copy(
|
||||
floatingBottomOriginDp = Pair(
|
||||
offset.value.x.toDp().value,
|
||||
offset.value.y.toDp().value
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
onResizerOpen = {
|
||||
resizing.value = true
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
if(resizing.value) {
|
||||
ResizerRect(onDragDelta, showResetApply = true, onApply = {
|
||||
resizing.value = false
|
||||
}, onReset = { })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NonFloatingKeyboardWindow(
|
||||
size: ComputedKeyboardSize,
|
||||
content: @Composable BoxScope.(actionBarGap: Dp) -> Unit
|
||||
) = with(LocalDensity.current) {
|
||||
OffsetPositioner(Offset(0.0f, 0.0f)) {
|
||||
KeyboardSurface(
|
||||
requiredWidthPx = size.getWidth(),
|
||||
backgroundColor = latinIME.keyboardColor,
|
||||
padding = size.getPadding()
|
||||
) {
|
||||
val actionBarGap = 4.dp
|
||||
|
||||
when(size) {
|
||||
is OneHandedKeyboardSize -> {
|
||||
Box(modifier = Modifier.width(size.layoutWidth.toDp()).align(
|
||||
when(size.direction) {
|
||||
OneHandedDirection.Left -> Alignment.CenterStart
|
||||
OneHandedDirection.Right -> Alignment.CenterEnd
|
||||
}
|
||||
)) {
|
||||
content(actionBarGap)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
content(actionBarGap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KeyboardWindowSelector(content: @Composable BoxScope.(actionBarGap: Dp) -> Unit) {
|
||||
val size = latinIME.size.value
|
||||
when(size) {
|
||||
is FloatingKeyboardSize -> FloatingKeyboardWindow(size, content)
|
||||
|
||||
is OneHandedKeyboardSize,
|
||||
is RegularKeyboardSize,
|
||||
is SplitKeyboardSize -> NonFloatingKeyboardWindow(size, content)
|
||||
|
||||
null -> return
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProvidersAndWrapper(content: @Composable () -> Unit) {
|
||||
UixThemeWrapper(latinIME.colorScheme) {
|
||||
DataStoreCacheProvider {
|
||||
CompositionLocalProvider(LocalManager provides keyboardManagerForAction) {
|
||||
CompositionLocalProvider(LocalThemeProvider provides latinIME.getDrawableProvider()) {
|
||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||
CompositionLocalProvider(LocalFoldingState provides foldingOptions.value) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -674,6 +921,37 @@ class UixManager(private val latinIME: LatinIME) {
|
||||
}
|
||||
}
|
||||
|
||||
fun setContent() {
|
||||
composeView?.setContent {
|
||||
ProvidersAndWrapper {
|
||||
InputDarkener(isInputOverridden.value || isShowingActionEditor.value) {
|
||||
closeActionWindow()
|
||||
isShowingActionEditor.value = false
|
||||
}
|
||||
|
||||
KeyboardWindowSelector { gap ->
|
||||
Column {
|
||||
when {
|
||||
currWindowActionWindow != null -> ActionViewWithHeader(
|
||||
currWindowActionWindow!!
|
||||
)
|
||||
|
||||
else -> MainKeyboardViewWithActionBar()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(gap))
|
||||
|
||||
latinIME.LegacyKeyboardView(hidden = isMainKeyboardHidden)
|
||||
}
|
||||
|
||||
ForgetWordDialog()
|
||||
}
|
||||
|
||||
ActionEditorHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun showUpdateNoticeIfNeeded() {
|
||||
if(!BuildConfig.UPDATE_CHECKING) return
|
||||
|
||||
@ -734,28 +1012,6 @@ class UixManager(private val latinIME: LatinIME) {
|
||||
}
|
||||
}
|
||||
|
||||
fun createComposeView(): View {
|
||||
if(composeView != null) {
|
||||
composeView = null
|
||||
//throw IllegalStateException("Attempted to create compose view, when one is already created!")
|
||||
}
|
||||
|
||||
composeView = ComposeView(latinIME).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setParentCompositionContext(null)
|
||||
|
||||
latinIME.setOwners()
|
||||
}
|
||||
|
||||
setContent()
|
||||
|
||||
return composeView!!
|
||||
}
|
||||
|
||||
fun getComposeView(): View? {
|
||||
return composeView
|
||||
}
|
||||
|
||||
fun onColorSchemeChanged() {
|
||||
setContent()
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package org.futo.inputmethod.latin.uix.actions
|
||||
|
||||
import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.latin.uix.Action
|
||||
import org.futo.inputmethod.v2keyboard.KeyboardMode
|
||||
import org.futo.inputmethod.v2keyboard.OneHandedDirection
|
||||
|
||||
val LeftHandedKeyboardAction = Action(
|
||||
icon = R.drawable.arrow_left,
|
||||
name = R.string.left_handed_keyboard_action_title,
|
||||
simplePressImpl = { manager, _ ->
|
||||
manager.getSizingCalculator().editSavedSettings {
|
||||
if(it.currentMode == KeyboardMode.OneHanded
|
||||
&& it.oneHandedDirection == OneHandedDirection.Left) {
|
||||
it.copy(
|
||||
currentMode = KeyboardMode.Regular
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
oneHandedDirection = OneHandedDirection.Left,
|
||||
currentMode = KeyboardMode.OneHanded
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
windowImpl = null,
|
||||
)
|
||||
|
||||
val RightHandedKeyboardAction = Action(
|
||||
icon = R.drawable.arrow_right,
|
||||
name = R.string.right_handed_keyboard_action_title,
|
||||
simplePressImpl = { manager, _ ->
|
||||
manager.getSizingCalculator().editSavedSettings {
|
||||
if(it.currentMode == KeyboardMode.OneHanded
|
||||
&& it.oneHandedDirection == OneHandedDirection.Right) {
|
||||
it.copy(
|
||||
currentMode = KeyboardMode.Regular
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
oneHandedDirection = OneHandedDirection.Right,
|
||||
currentMode = KeyboardMode.OneHanded
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
windowImpl = null,
|
||||
)
|
||||
|
||||
val SplitKeyboardAction = Action(
|
||||
icon = R.drawable.arrow_down,
|
||||
name = R.string.split_keyboard_action_title,
|
||||
simplePressImpl = { manager, _ ->
|
||||
manager.getSizingCalculator().editSavedSettings {
|
||||
if(it.currentMode == KeyboardMode.Split) {
|
||||
it.copy(
|
||||
currentMode = KeyboardMode.Regular
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
currentMode = KeyboardMode.Split
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
windowImpl = null,
|
||||
)
|
||||
|
||||
val FloatingKeyboardAction = Action(
|
||||
icon = R.drawable.arrow_up,
|
||||
name = R.string.floating_keyboard_action_title,
|
||||
simplePressImpl = { manager, _ ->
|
||||
manager.getSizingCalculator().editSavedSettings {
|
||||
if(it.currentMode == KeyboardMode.Floating) {
|
||||
it.copy(
|
||||
currentMode = KeyboardMode.Regular
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
currentMode = KeyboardMode.Floating
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
windowImpl = null,
|
||||
)
|
@ -31,7 +31,11 @@ val AllActionsMap = mapOf(
|
||||
"copy" to CopyAction,
|
||||
"select_all" to SelectAllAction,
|
||||
"more" to MoreActionsAction,
|
||||
"bugs" to BugViewerAction
|
||||
"bugs" to BugViewerAction,
|
||||
"onehanded_left" to LeftHandedKeyboardAction,
|
||||
"onehanded_right" to RightHandedKeyboardAction,
|
||||
"split_keyboard" to SplitKeyboardAction,
|
||||
"floating_keyboard" to FloatingKeyboardAction
|
||||
)
|
||||
|
||||
val ActionToId = AllActionsMap.entries.associate { it.value to it.key }
|
||||
|
@ -240,7 +240,7 @@ public class LanguageModelFacilitator(
|
||||
try {
|
||||
inputLogic.mWordComposer.setAutoCorrection(null)
|
||||
|
||||
if(values.composedData.mTypedWord.length > BinaryDictionary.DICTIONARY_MAX_WORD_LENGTH) {
|
||||
if(values.composedData.mTypedWord.length > BinaryDictionary.DICTIONARY_MAX_WORD_LENGTH-1) {
|
||||
inputLogic.mSuggestionStripViewAccessor.setNeutralSuggestionStrip()
|
||||
return
|
||||
}
|
||||
|
@ -76,16 +76,12 @@ fun getPrimaryLayoutOverride(editorInfo: EditorInfo?): String? {
|
||||
}
|
||||
|
||||
data class KeyboardLayoutSetV2Params(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val padding: Rect,
|
||||
val computedSize: ComputedKeyboardSize,
|
||||
val keyboardLayoutSet: String,
|
||||
val locale: Locale,
|
||||
val editorInfo: EditorInfo?,
|
||||
val numberRow: Boolean,
|
||||
val gap: Float = 4.0f,
|
||||
val useSplitLayout: Boolean,
|
||||
val splitLayoutWidth: Int,
|
||||
val bottomActionKey: Int?,
|
||||
val longPressKeySettings: LongPressKeySettings? = null
|
||||
)
|
||||
@ -184,14 +180,15 @@ class KeyboardLayoutSetV2 internal constructor(
|
||||
NumberRowMode.AlwaysDisabled -> false
|
||||
}
|
||||
|
||||
private val widthMinusPadding = params.width - params.padding.left - params.padding.right
|
||||
private val heightMinusPadding = params.height - params.padding.top - params.padding.bottom
|
||||
private val height = params.computedSize.getHeight()
|
||||
|
||||
private val padding = params.computedSize.getPadding()
|
||||
|
||||
private val widthMinusPadding = params.computedSize.getTotalKeyboardWidth()
|
||||
private val heightMinusPadding = height - padding.top - padding.bottom
|
||||
|
||||
private val singularRowHeight: Double
|
||||
get() = heightMinusPadding?.let { it / 4.0 } ?: run {
|
||||
(ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) *
|
||||
keyboardHeightMultiplier
|
||||
}
|
||||
get() = heightMinusPadding / 4.0
|
||||
|
||||
fun getKeyboard(element: KeyboardLayoutElement): Keyboard {
|
||||
|
||||
@ -221,10 +218,8 @@ class KeyboardLayoutSetV2 internal constructor(
|
||||
}
|
||||
|
||||
val layoutParams = LayoutParams(
|
||||
size = params.computedSize,
|
||||
gap = params.gap.dp,
|
||||
useSplitLayout = params.useSplitLayout,
|
||||
splitLayoutWidth = params.splitLayoutWidth,
|
||||
padding = params.padding,
|
||||
standardRowHeight = singularRowHeight,
|
||||
element = element
|
||||
)
|
||||
|
@ -2,12 +2,27 @@ package org.futo.inputmethod.v2keyboard
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.floatPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.window.layout.FoldingFeature
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.encoding.CompositeDecoder
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.encoding.decodeStructure
|
||||
import kotlinx.serialization.encoding.encodeStructure
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.futo.inputmethod.latin.FoldStateProvider
|
||||
import org.futo.inputmethod.latin.LatinIME
|
||||
import org.futo.inputmethod.latin.uix.SettingsKey
|
||||
import org.futo.inputmethod.latin.uix.UixManager
|
||||
import org.futo.inputmethod.latin.uix.getSettingBlocking
|
||||
import org.futo.inputmethod.latin.uix.setSettingBlocking
|
||||
import org.futo.inputmethod.latin.utils.ResourceUtils
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -17,13 +32,140 @@ interface KeyboardSizeStateProvider {
|
||||
|
||||
sealed class ComputedKeyboardSize()
|
||||
|
||||
class RegularKeyboardSize(val height: Int, val padding: Rect) : ComputedKeyboardSize()
|
||||
class RegularKeyboardSize(val height: Int, val width: Int, val padding: Rect) : ComputedKeyboardSize()
|
||||
|
||||
class SplitKeyboardSize(val height: Int, val padding: Rect, val splitLayoutWidth: Int) : ComputedKeyboardSize()
|
||||
class SplitKeyboardSize(val height: Int, val width: Int, val padding: Rect, val splitLayoutWidth: Int) : ComputedKeyboardSize()
|
||||
|
||||
//class OneHandedKeyboardSize(val height: Int, val offset: Int, val sideInset: Int, val isLeft: Boolean, val width: Int): ComputedKeyboardSize()
|
||||
//class FloatingKeyboardSize(val x: Int, val y: Int, val width: Int, val height: Int): ComputedKeyboardSize()
|
||||
enum class OneHandedDirection {
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
class OneHandedKeyboardSize(val height: Int, val width: Int, val padding: Rect, val layoutWidth: Int, val direction: OneHandedDirection): ComputedKeyboardSize()
|
||||
|
||||
class FloatingKeyboardSize(
|
||||
val bottomOrigin: Pair<Int, Int>,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val decorationPadding: Rect
|
||||
): ComputedKeyboardSize()
|
||||
|
||||
fun ComputedKeyboardSize.getHeight(): Int = when(this) {
|
||||
is FloatingKeyboardSize -> height
|
||||
is OneHandedKeyboardSize -> height
|
||||
is RegularKeyboardSize -> height
|
||||
is SplitKeyboardSize -> height
|
||||
}
|
||||
|
||||
fun ComputedKeyboardSize.getWidth(): Int = when(this) {
|
||||
is FloatingKeyboardSize -> width
|
||||
is OneHandedKeyboardSize -> width
|
||||
is RegularKeyboardSize -> width
|
||||
is SplitKeyboardSize -> width
|
||||
}
|
||||
|
||||
fun ComputedKeyboardSize.getPadding(): Rect = when(this) {
|
||||
is FloatingKeyboardSize -> decorationPadding
|
||||
is OneHandedKeyboardSize -> padding
|
||||
is RegularKeyboardSize -> padding
|
||||
is SplitKeyboardSize -> padding
|
||||
}
|
||||
|
||||
fun ComputedKeyboardSize.getTotalKeyboardWidth(): Int = when(this) {
|
||||
is FloatingKeyboardSize -> width - decorationPadding.left - decorationPadding.right
|
||||
is OneHandedKeyboardSize -> layoutWidth
|
||||
is RegularKeyboardSize -> width - padding.left - padding.right
|
||||
is SplitKeyboardSize -> width - padding.left - padding.right
|
||||
}
|
||||
|
||||
enum class KeyboardMode {
|
||||
Regular,
|
||||
Split,
|
||||
OneHanded,
|
||||
Floating
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Serializer(forClass = Rect::class)
|
||||
object RectSerializer : KSerializer<Rect> {
|
||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Rect") {
|
||||
element<Int>("left")
|
||||
element<Int>("top")
|
||||
element<Int>("right")
|
||||
element<Int>("bottom")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Rect) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
encodeIntElement(descriptor, 0, value.left)
|
||||
encodeIntElement(descriptor, 1, value.top)
|
||||
encodeIntElement(descriptor, 2, value.right)
|
||||
encodeIntElement(descriptor, 3, value.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Rect {
|
||||
return decoder.decodeStructure(descriptor) {
|
||||
var left = 0
|
||||
var top = 0
|
||||
var right = 0
|
||||
var bottom = 0
|
||||
|
||||
while (true) {
|
||||
when (val index = decodeElementIndex(descriptor)) {
|
||||
0 -> left = decodeIntElement(descriptor, 0)
|
||||
1 -> top = decodeIntElement(descriptor, 1)
|
||||
2 -> right = decodeIntElement(descriptor, 2)
|
||||
3 -> bottom = decodeIntElement(descriptor, 3)
|
||||
CompositeDecoder.DECODE_DONE -> break
|
||||
else -> error("Unexpected index: $index")
|
||||
}
|
||||
}
|
||||
|
||||
Rect(left, top, right, bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Serializable
|
||||
data class SavedKeyboardSizingSettings(
|
||||
val currentMode: KeyboardMode,
|
||||
val heightMultiplier: Float,
|
||||
val paddingDp: @Serializable(RectSerializer::class) Rect,
|
||||
|
||||
// Split
|
||||
val splitWidthFraction: Float,
|
||||
|
||||
// One handed, values with respect to left handed mode
|
||||
// left = padding
|
||||
// right = width + padding
|
||||
// bottom = padding for bottom
|
||||
val oneHandedRectDp: @Serializable(RectSerializer::class) Rect,
|
||||
val oneHandedDirection: OneHandedDirection,
|
||||
|
||||
// Floating
|
||||
// bottom left of the floating keyboard, relative to bottom left of screen, .second is Y up
|
||||
val floatingBottomOriginDp: Pair<Float, Float>,
|
||||
val floatingWidthDp: Float,
|
||||
val floatingHeightDp: Float
|
||||
) {
|
||||
fun toJsonString(): String =
|
||||
Json.encodeToString(this)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromJsonString(s: String): SavedKeyboardSizingSettings? =
|
||||
try {
|
||||
Json.decodeFromString(s)
|
||||
} catch (e: Exception) {
|
||||
//e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class KeyboardSizeSettingKind {
|
||||
Portrait,
|
||||
@ -31,117 +173,230 @@ enum class KeyboardSizeSettingKind {
|
||||
FoldableInnerDisplay
|
||||
}
|
||||
|
||||
val SplitKeyboardSettings = mapOf(
|
||||
KeyboardSizeSettingKind.Portrait to SettingsKey(
|
||||
booleanPreferencesKey("split_keyboard_portrait"), false),
|
||||
KeyboardSizeSettingKind.Landscape to SettingsKey(
|
||||
booleanPreferencesKey("split_keyboard_landscape"), true),
|
||||
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
|
||||
booleanPreferencesKey("split_keyboard_fold"), true),
|
||||
val DefaultKeyboardSettings = mapOf(
|
||||
KeyboardSizeSettingKind.Portrait to SavedKeyboardSizingSettings(
|
||||
currentMode = KeyboardMode.Regular,
|
||||
heightMultiplier = 1.0f,
|
||||
paddingDp = Rect(2, 4, 2, 10),
|
||||
splitWidthFraction = 4.0f / 5.0f,
|
||||
oneHandedDirection = OneHandedDirection.Right,
|
||||
oneHandedRectDp = Rect(4, 4, 364, 30),
|
||||
floatingBottomOriginDp = Pair(0.0f, 0.0f),
|
||||
floatingHeightDp = 240.0f,
|
||||
floatingWidthDp = 360.0f
|
||||
),
|
||||
|
||||
KeyboardSizeSettingKind.Landscape to SavedKeyboardSizingSettings(
|
||||
currentMode = KeyboardMode.Split,
|
||||
heightMultiplier = 0.9f,
|
||||
paddingDp = Rect(8, 2, 8, 2),
|
||||
splitWidthFraction = 3.0f / 5.0f,
|
||||
oneHandedDirection = OneHandedDirection.Right,
|
||||
oneHandedRectDp = Rect(4, 4, 364, 30),
|
||||
floatingBottomOriginDp = Pair(0.0f, 0.0f),
|
||||
floatingHeightDp = 240.0f,
|
||||
floatingWidthDp = 360.0f
|
||||
),
|
||||
|
||||
KeyboardSizeSettingKind.FoldableInnerDisplay to SavedKeyboardSizingSettings(
|
||||
currentMode = KeyboardMode.Split,
|
||||
heightMultiplier = 0.67f,
|
||||
paddingDp = Rect(44, 4, 44, 8),
|
||||
splitWidthFraction = 3.0f / 5.0f,
|
||||
oneHandedDirection = OneHandedDirection.Right,
|
||||
oneHandedRectDp = Rect(4, 4, 364, 30),
|
||||
floatingBottomOriginDp = Pair(0.0f, 0.0f),
|
||||
floatingHeightDp = 240.0f,
|
||||
floatingWidthDp = 360.0f
|
||||
),
|
||||
)
|
||||
|
||||
val KeyboardHeightSettings = mapOf(
|
||||
val KeyboardSettings = mapOf(
|
||||
KeyboardSizeSettingKind.Portrait to SettingsKey(
|
||||
floatPreferencesKey("keyboardHeightMultiplier"), 1.0f),
|
||||
stringPreferencesKey("keyboard_settings_portrait"), ""),
|
||||
KeyboardSizeSettingKind.Landscape to SettingsKey(
|
||||
floatPreferencesKey("keyboard_height_landscape"), 0.9f),
|
||||
stringPreferencesKey("keyboard_settings_landscape"), ""),
|
||||
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
|
||||
floatPreferencesKey("keyboard_height_fold"), 0.67f),
|
||||
stringPreferencesKey("keyboard_settings_fold"), ""),
|
||||
)
|
||||
|
||||
val KeyboardOffsetSettings = mapOf(
|
||||
KeyboardSizeSettingKind.Portrait to SettingsKey(
|
||||
floatPreferencesKey("keyboard_offset_portrait"), 8.0f),
|
||||
KeyboardSizeSettingKind.Landscape to SettingsKey(
|
||||
floatPreferencesKey("keyboard_offset_landscape"), 0.0f),
|
||||
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
|
||||
floatPreferencesKey("keyboard_offset_fold"), 8.0f),
|
||||
)
|
||||
class KeyboardSizingCalculator(val context: Context, val uixManager: UixManager) {
|
||||
val sizeStateProvider = context as KeyboardSizeStateProvider
|
||||
val foldStateProvider = context as FoldStateProvider
|
||||
|
||||
val KeyboardSideInsetSettings = mapOf(
|
||||
KeyboardSizeSettingKind.Portrait to SettingsKey(
|
||||
floatPreferencesKey("keyboard_inset_portrait"), 2.0f),
|
||||
KeyboardSizeSettingKind.Landscape to SettingsKey(
|
||||
floatPreferencesKey("keyboard_inset_landscape"), 8.0f),
|
||||
KeyboardSizeSettingKind.FoldableInnerDisplay to SettingsKey(
|
||||
floatPreferencesKey("keyboard_inset_fold"), 44.0f),
|
||||
)
|
||||
private fun dp(v: Number): Int =
|
||||
(v.toFloat() * context.resources.displayMetrics.density).toInt()
|
||||
|
||||
private fun dp(v: Rect): Rect =
|
||||
Rect(dp(v.left), dp(v.top), dp(v.right), dp(v.bottom))
|
||||
|
||||
private fun limitFloating(rectPx: Rect): Rect {
|
||||
val width = rectPx.width()
|
||||
val height = rectPx.height()
|
||||
|
||||
val minWidth = dp(160)
|
||||
val minHeight = dp(160)
|
||||
|
||||
if(width < minWidth) {
|
||||
val delta = minWidth - width
|
||||
rectPx.left -= delta / 2
|
||||
rectPx.right += delta / 2
|
||||
}
|
||||
|
||||
if(height < minHeight) {
|
||||
val delta = minHeight - height
|
||||
rectPx.top -= delta
|
||||
rectPx.bottom += delta
|
||||
}
|
||||
|
||||
val maxWidth = context.resources.displayMetrics.widthPixels * 2 / 3
|
||||
val maxHeight = context.resources.displayMetrics.heightPixels * 2 / 3
|
||||
|
||||
if(width > maxWidth) {
|
||||
val delta = width - maxWidth
|
||||
rectPx.left += delta / 2
|
||||
rectPx.right -= delta / 2
|
||||
}
|
||||
|
||||
if(height > maxHeight) {
|
||||
val delta = height - maxHeight
|
||||
rectPx.top += delta / 2
|
||||
rectPx.bottom -= delta / 2
|
||||
}
|
||||
|
||||
|
||||
class KeyboardSizingCalculator(val context: Context) {
|
||||
private val sizeStateProvider = context as KeyboardSizeStateProvider
|
||||
private val foldStateProvider = context as FoldStateProvider
|
||||
val originX = rectPx.left
|
||||
val originY = rectPx.top
|
||||
|
||||
private fun isSplitKeyboard(mode: KeyboardSizeSettingKind): Boolean =
|
||||
context.getSettingBlocking(SplitKeyboardSettings[mode]!!)
|
||||
if(originX < 0){
|
||||
rectPx.left -= originX
|
||||
rectPx.right -= originX
|
||||
}
|
||||
|
||||
private fun heightMultiplier(mode: KeyboardSizeSettingKind): Float =
|
||||
context.getSettingBlocking(KeyboardHeightSettings[mode]!!)
|
||||
if(originY < 0) {
|
||||
rectPx.top -= originY
|
||||
rectPx.bottom -= originY
|
||||
}
|
||||
|
||||
private fun bottomOffsetPx(mode: KeyboardSizeSettingKind): Int =
|
||||
(context.getSettingBlocking(KeyboardOffsetSettings[mode]!!) * context.resources.displayMetrics.density).toInt()
|
||||
if(rectPx.right > context.resources.displayMetrics.widthPixels) {
|
||||
val delta = rectPx.right - context.resources.displayMetrics.widthPixels
|
||||
rectPx.right -= delta
|
||||
rectPx.left -= delta
|
||||
}
|
||||
if(rectPx.bottom < 0) {
|
||||
val delta = rectPx.bottom
|
||||
rectPx.top -= delta
|
||||
rectPx.bottom -= delta
|
||||
}
|
||||
|
||||
private fun sideInsetPx(mode: KeyboardSizeSettingKind): Int =
|
||||
(context.getSettingBlocking(KeyboardSideInsetSettings[mode]!!) * context.resources.displayMetrics.density).toInt()
|
||||
return rectPx
|
||||
}
|
||||
|
||||
private fun topPaddingPx(mode: KeyboardSizeSettingKind): Int =
|
||||
(when(mode) {
|
||||
KeyboardSizeSettingKind.Portrait -> 4.0f
|
||||
KeyboardSizeSettingKind.Landscape -> 0.0f
|
||||
KeyboardSizeSettingKind.FoldableInnerDisplay -> 8.0f
|
||||
} * context.resources.displayMetrics.density).toInt()
|
||||
fun getSavedSettings(): SavedKeyboardSizingSettings =
|
||||
SavedKeyboardSizingSettings.fromJsonString(context.getSettingBlocking(
|
||||
KeyboardSettings[sizeStateProvider.currentSizeState]!!
|
||||
)) ?: DefaultKeyboardSettings[sizeStateProvider.currentSizeState]!!
|
||||
|
||||
fun editSavedSettings(transform: (SavedKeyboardSizingSettings) -> SavedKeyboardSizingSettings) {
|
||||
val sizeState = sizeStateProvider.currentSizeState
|
||||
|
||||
val savedSettings = SavedKeyboardSizingSettings.fromJsonString(context.getSettingBlocking(
|
||||
KeyboardSettings[sizeState]!!
|
||||
)) ?: DefaultKeyboardSettings[sizeState]!!
|
||||
|
||||
val transformed = transform(savedSettings)
|
||||
|
||||
if(transformed != savedSettings) {
|
||||
context.setSettingBlocking(KeyboardSettings[sizeState]!!.key, transformed.toJsonString())
|
||||
}
|
||||
}
|
||||
|
||||
fun calculate(layoutName: String, isNumberRowActive: Boolean): ComputedKeyboardSize {
|
||||
val savedSettings = getSavedSettings()
|
||||
|
||||
val layout = LayoutManager.getLayout(context, layoutName)
|
||||
val effectiveRowCount = layout.effectiveRows.size
|
||||
|
||||
val configuration = context.resources.configuration
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
|
||||
val mode = sizeStateProvider.currentSizeState
|
||||
|
||||
val isSplit = isSplitKeyboard(mode)
|
||||
val heightMultiplier = heightMultiplier(mode)
|
||||
val bottomOffset = bottomOffsetPx(mode)
|
||||
val sideInset = sideInsetPx(mode)
|
||||
val topPadding = topPaddingPx(mode)
|
||||
|
||||
val singularRowHeight = (ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) * heightMultiplier
|
||||
val singularRowHeight = (ResourceUtils.getDefaultKeyboardHeight(context.resources) / 4.0) *
|
||||
savedSettings.heightMultiplier
|
||||
|
||||
val numRows = 4.0 +
|
||||
((effectiveRowCount - 5) / 2.0).coerceAtLeast(0.0) +
|
||||
if(isNumberRowActive) { 0.5 } else { 0.0 }
|
||||
|
||||
println("Num rows; $numRows, $effectiveRowCount ($layoutName) ($layout)")
|
||||
|
||||
val recommendedHeight = numRows * singularRowHeight
|
||||
|
||||
|
||||
val foldState = foldStateProvider.foldState.feature
|
||||
|
||||
val window = (context as LatinIME).window.window
|
||||
val width = ResourceUtils.getDefaultKeyboardWidth(window, context.resources)
|
||||
|
||||
return when {
|
||||
// Special case: 50% screen height no matter the row count or settings
|
||||
foldState != null && foldState.state == FoldingFeature.State.HALF_OPENED && foldState.orientation == FoldingFeature.Orientation.HORIZONTAL ->
|
||||
SplitKeyboardSize(
|
||||
displayMetrics.heightPixels / 2 - (displayMetrics.density * 80.0f).toInt(),
|
||||
Rect(
|
||||
height = displayMetrics.heightPixels / 2 - (displayMetrics.density * 80.0f).toInt(),
|
||||
width = width,
|
||||
padding = Rect(
|
||||
(displayMetrics.density * 44.0f).roundToInt(),
|
||||
(displayMetrics.density * 20.0f).roundToInt(),
|
||||
(displayMetrics.density * 50.0f).roundToInt(),
|
||||
(displayMetrics.density * 44.0f).roundToInt(),
|
||||
(displayMetrics.density * 12.0f).roundToInt(),
|
||||
),
|
||||
displayMetrics.widthPixels * 3 / 5
|
||||
splitLayoutWidth = displayMetrics.widthPixels * 3 / 5
|
||||
)
|
||||
|
||||
isSplit -> SplitKeyboardSize(
|
||||
recommendedHeight.roundToInt(),
|
||||
Rect(sideInset, topPadding, sideInset, bottomOffset),
|
||||
displayMetrics.widthPixels * 3 / 5)
|
||||
savedSettings.currentMode == KeyboardMode.Split ->
|
||||
SplitKeyboardSize(
|
||||
height = recommendedHeight.roundToInt(),
|
||||
width = width,
|
||||
padding = dp(savedSettings.paddingDp),
|
||||
splitLayoutWidth = (displayMetrics.widthPixels * savedSettings.splitWidthFraction).toInt()
|
||||
)
|
||||
|
||||
else -> RegularKeyboardSize(
|
||||
recommendedHeight.roundToInt(),
|
||||
Rect(sideInset, topPadding, sideInset, bottomOffset),
|
||||
)
|
||||
savedSettings.currentMode == KeyboardMode.OneHanded ->
|
||||
OneHandedKeyboardSize(
|
||||
height = recommendedHeight.roundToInt(),
|
||||
width = width,
|
||||
padding = dp(savedSettings.oneHandedRectDp).let { rect ->
|
||||
when(savedSettings.oneHandedDirection) {
|
||||
OneHandedDirection.Left -> Rect(rect.left, rect.top, rect.left, rect.bottom)
|
||||
OneHandedDirection.Right -> Rect(rect.left, rect.top, rect.left, rect.bottom)
|
||||
}
|
||||
},
|
||||
layoutWidth = dp(savedSettings.oneHandedRectDp.width()).coerceAtMost(displayMetrics.widthPixels * 9 / 10),
|
||||
direction = savedSettings.oneHandedDirection
|
||||
)
|
||||
|
||||
savedSettings.currentMode == KeyboardMode.Floating -> {
|
||||
val singularRowHeightFloat = dp(savedSettings.floatingHeightDp) / 4.0f
|
||||
val recommendedHeightFloat = singularRowHeightFloat * numRows
|
||||
FloatingKeyboardSize(
|
||||
bottomOrigin = Pair(
|
||||
dp(savedSettings.floatingBottomOriginDp.first),
|
||||
dp(savedSettings.floatingBottomOriginDp.second)
|
||||
),
|
||||
width = dp(savedSettings.floatingWidthDp),
|
||||
height = recommendedHeightFloat.toInt(),
|
||||
decorationPadding = dp(
|
||||
Rect(
|
||||
8,
|
||||
8,
|
||||
8,
|
||||
8
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else ->
|
||||
RegularKeyboardSize(
|
||||
height = recommendedHeight.roundToInt(),
|
||||
width = width,
|
||||
padding = dp(savedSettings.paddingDp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,4 +410,14 @@ class KeyboardSizingCalculator(val context: Context) {
|
||||
|
||||
return (minDp / 100.0f).coerceIn(3.0f, 6.0f)
|
||||
}
|
||||
|
||||
fun calculateSuggestionBarHeightDp(): Float {
|
||||
return 40.0f
|
||||
}
|
||||
|
||||
fun calculateTotalActionBarHeightPx(): Int =
|
||||
when {
|
||||
uixManager.actionsExpanded -> dp(2 * calculateSuggestionBarHeightDp())
|
||||
else -> dp(calculateSuggestionBarHeightDp())
|
||||
}
|
||||
}
|
@ -81,10 +81,8 @@ data class LayoutRow(
|
||||
)
|
||||
|
||||
data class LayoutParams(
|
||||
val size: ComputedKeyboardSize,
|
||||
val gap: Dp,
|
||||
val useSplitLayout: Boolean,
|
||||
val splitLayoutWidth: Int,
|
||||
val padding: Rect,
|
||||
val standardRowHeight: Double,
|
||||
val element: KeyboardLayoutElement,
|
||||
)
|
||||
@ -131,8 +129,6 @@ data class LayoutEngine(
|
||||
}
|
||||
|
||||
private fun computeRowHeight(): Double {
|
||||
//val normalKeyboardHeight = ((rowHeight.value + verticalGap.value) * density) * 3
|
||||
|
||||
val normalKeyboardHeight = totalRowHeight
|
||||
|
||||
// divide by total row height
|
||||
@ -140,19 +136,29 @@ data class LayoutEngine(
|
||||
BottomRowHeightMode.Fixed -> ((normalKeyboardHeight - layoutParams.standardRowHeight) / rows.filter { !it.isBottomRow }.sumOf { it.rowHeight })
|
||||
BottomRowHeightMode.Flexible -> (normalKeyboardHeight) / rows.sumOf { it.rowHeight }
|
||||
}
|
||||
//return ((normalKeyboardHeight - bottomRowHeightPx) / rows.filter { !it.isBottomRow }.sumOf { it.rowHeight })
|
||||
//return (normalKeyboardHeight) / rows.sumOf { it.rowHeight }
|
||||
}
|
||||
|
||||
private val isSplitLayout = layoutParams.useSplitLayout
|
||||
private val isSplitLayout = layoutParams.size is SplitKeyboardSize
|
||||
private val isOneHandedLayout = layoutParams.size is OneHandedKeyboardSize
|
||||
|
||||
private val layoutWidth = if(isSplitLayout) {
|
||||
layoutParams.splitLayoutWidth
|
||||
(layoutParams.size as SplitKeyboardSize).splitLayoutWidth
|
||||
} else if(isOneHandedLayout) {
|
||||
(layoutParams.size as OneHandedKeyboardSize).layoutWidth
|
||||
} else {
|
||||
params.mId.mWidth
|
||||
}
|
||||
|
||||
private val unsplitLayoutWidth = params.mId.mWidth
|
||||
private val unsplitLayoutWidth = if(isSplitLayout) {
|
||||
params.mId.mWidth
|
||||
} else {
|
||||
layoutWidth
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
private val padding = Rect(0, 0, 0, 0)
|
||||
private val xOffset = 0
|
||||
|
||||
private val minimumBottomFunctionalKeyWidth = (layoutWidth * keyboard.minimumBottomRowFunctionalKeyWidth)
|
||||
|
||||
private val regularKeyWidth = computeRegularKeyWidth()
|
||||
@ -551,10 +557,10 @@ data class LayoutEngine(
|
||||
}
|
||||
|
||||
private fun addRowAlignLeft(row: List<LayoutEntry>, y: Int, height: Int)
|
||||
= addRow(row, 0.0f + layoutParams.padding.left, y, height)
|
||||
= addRow(row, 0.0f + padding.left + xOffset, y, height)
|
||||
|
||||
private fun addRowAlignRight(row: List<LayoutEntry>, y: Int, height: Int) {
|
||||
val startingOffset = params.mId.mWidth - row.sumOf { it.widthPx.toDouble() }.toFloat() + layoutParams.padding.left
|
||||
val startingOffset = params.mId.mWidth - row.sumOf { it.widthPx.toDouble() }.toFloat() + padding.left
|
||||
addRow(row, startingOffset, y, height)
|
||||
}
|
||||
|
||||
@ -570,7 +576,7 @@ data class LayoutEngine(
|
||||
}
|
||||
|
||||
private fun addKeys(rows: List<LayoutRow>): Int {
|
||||
var currentY = 0.0f + layoutParams.padding.top
|
||||
var currentY = 0.0f + padding.top
|
||||
rows.forEach { row ->
|
||||
addRow(row, currentY.toInt())
|
||||
currentY += row.height
|
||||
@ -588,14 +594,14 @@ data class LayoutEngine(
|
||||
|
||||
val rows = computeRows(this.rows)
|
||||
|
||||
val totalKeyboardHeight = addKeys(rows).let { totalRowHeight.roundToInt() } + layoutParams.padding.top + layoutParams.padding.bottom
|
||||
val totalKeyboardHeight = addKeys(rows).let { totalRowHeight.roundToInt() } + padding.top + padding.bottom
|
||||
|
||||
params.mOccupiedHeight = totalKeyboardHeight - verticalGapPx.roundToInt()
|
||||
params.mOccupiedWidth = params.mId.mWidth + layoutParams.padding.left + layoutParams.padding.right
|
||||
params.mTopPadding = 0//layoutParams.padding.top
|
||||
params.mBottomPadding = 0//layoutParams.padding.bottom
|
||||
params.mLeftPadding = 0//layoutParams.padding.left
|
||||
params.mRightPadding = 0//layoutParams.padding.right
|
||||
params.mOccupiedWidth = params.mId.mWidth + padding.left + padding.right
|
||||
params.mTopPadding = 0
|
||||
params.mBottomPadding = 0
|
||||
params.mLeftPadding = 0
|
||||
params.mRightPadding = 0
|
||||
|
||||
params.mBaseWidth = params.mOccupiedWidth
|
||||
params.mDefaultKeyWidth = regularKeyWidth.roundToInt()
|
||||
|
@ -33,7 +33,7 @@ object LayoutManager {
|
||||
|
||||
private fun getAllLayoutPaths(assetManager: AssetManager): List<String> {
|
||||
return listFilesRecursively(assetManager, "layouts").filter {
|
||||
(it.endsWith(".yml") || it.endsWith(".yaml")) && it != "mapping.yaml"
|
||||
(it.endsWith(".yml") || it.endsWith(".yaml")) && it != "layouts/mapping.yaml"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,9 +43,7 @@ private fun symsForCoord(keyCoordinate: KeyCoordinate): String {
|
||||
if(centeredCol < 0) return ""
|
||||
|
||||
val letter = row.getOrNull(centeredCol)
|
||||
if(letter == 'ñ') {
|
||||
println("It's ñ")
|
||||
}
|
||||
|
||||
return if(letter != null) {
|
||||
"!text/qwertysyms_$letter"
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user