mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add undo, redo, paste actions
This commit is contained in:
parent
9cccdcb79d
commit
e2999ada34
20
java/res/drawable/clipboard.xml
Normal file
20
java/res/drawable/clipboard.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M16,4h2a2,2 0,0 1,2 2v14a2,2 0,0 1,-2 2H6a2,2 0,0 1,-2 -2V6a2,2 0,0 1,2 -2h2"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M9,2L15,2A1,1 0,0 1,16 3L16,5A1,1 0,0 1,15 6L9,6A1,1 0,0 1,8 5L8,3A1,1 0,0 1,9 2z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
20
java/res/drawable/redo.xml
Normal file
20
java/res/drawable/redo.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M15,14l5,-5l-5,-5"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4,20v-7a4,4 0,0 1,4 -4h12"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
20
java/res/drawable/undo.xml
Normal file
20
java/res/drawable/undo.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M9,14l-5,-5l5,-5"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M20,20v-7a4,4 0,0 0,-4 -4H4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
@ -2,6 +2,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="voice_input_action_title">Voice Input</string>
|
<string name="voice_input_action_title">Voice Input</string>
|
||||||
<string name="theme_switcher_action_title">Theme Switcher</string>
|
<string name="theme_switcher_action_title">Theme Switcher</string>
|
||||||
|
<string name="clipboard_action_title">Paste from Clipboard</string>
|
||||||
|
<string name="undo_action_title">Undo</string>
|
||||||
|
<string name="redo_action_title">Redo</string>
|
||||||
|
|
||||||
<string name="amoled_dark_theme_name">AMOLED Dark Purple</string>
|
<string name="amoled_dark_theme_name">AMOLED Dark Purple</string>
|
||||||
<string name="classic_material_dark_theme_name">AOSP Material Dark</string>
|
<string name="classic_material_dark_theme_name">AOSP Material Dark</string>
|
||||||
|
@ -5,6 +5,8 @@ import android.content.res.Configuration
|
|||||||
import android.inputmethodservice.InputMethodService
|
import android.inputmethodservice.InputMethodService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.view.KeyCharacterMap
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.CompletionInfo
|
import android.view.inputmethod.CompletionInfo
|
||||||
@ -277,7 +279,9 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
@Composable
|
@Composable
|
||||||
private fun LegacyKeyboardView(hidden: Boolean) {
|
private fun LegacyKeyboardView(hidden: Boolean) {
|
||||||
val modifier = if(hidden) {
|
val modifier = if(hidden) {
|
||||||
Modifier.clipToBounds().size(0.dp)
|
Modifier
|
||||||
|
.clipToBounds()
|
||||||
|
.size(0.dp)
|
||||||
} else {
|
} else {
|
||||||
Modifier.onSizeChanged {
|
Modifier.onSizeChanged {
|
||||||
inputViewHeight = it.height
|
inputViewHeight = it.height
|
||||||
@ -703,6 +707,15 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun sendCodePointEvent(codePoint: Int) {
|
||||||
|
latinIMELegacy.onCodeInput(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendKeyEvent(keyCode: Int, metaState: Int) {
|
||||||
|
latinIMELegacy.mInputLogic.sendDownUpKeyEvent(keyCode, metaState)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
|
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
|
||||||
return createInlineSuggestionsRequest(this, this.activeColorScheme)
|
return createInlineSuggestionsRequest(this, this.activeColorScheme)
|
||||||
|
@ -1132,7 +1132,7 @@ public final class InputLogic {
|
|||||||
// As for the case where we don't know the cursor position, it can happen
|
// As for the case where we don't know the cursor position, it can happen
|
||||||
// because of bugs in the framework. But the framework should know, so the next
|
// because of bugs in the framework. But the framework should know, so the next
|
||||||
// best thing is to leave it to whatever it thinks is best.
|
// best thing is to leave it to whatever it thinks is best.
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0);
|
||||||
int totalDeletedLength = 1;
|
int totalDeletedLength = 1;
|
||||||
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
|
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
|
||||||
// If this is an accelerated (i.e., double) deletion, then we need to
|
// If this is an accelerated (i.e., double) deletion, then we need to
|
||||||
@ -1140,7 +1140,7 @@ public final class InputLogic {
|
|||||||
// the previous word, and will lose it after next deletion.
|
// the previous word, and will lose it after next deletion.
|
||||||
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
|
||||||
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
inputTransaction.mSettingsValues, currentKeyboardScriptId);
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0);
|
||||||
totalDeletedLength++;
|
totalDeletedLength++;
|
||||||
}
|
}
|
||||||
StatsUtils.onBackspacePressed(totalDeletedLength);
|
StatsUtils.onBackspacePressed(totalDeletedLength);
|
||||||
@ -2021,13 +2021,13 @@ public final class InputLogic {
|
|||||||
*
|
*
|
||||||
* @param keyCode the key code to send inside the key event.
|
* @param keyCode the key code to send inside the key event.
|
||||||
*/
|
*/
|
||||||
private void sendDownUpKeyEvent(final int keyCode) {
|
public void sendDownUpKeyEvent(final int keyCode, final int metaState) {
|
||||||
final long eventTime = SystemClock.uptimeMillis();
|
final long eventTime = SystemClock.uptimeMillis();
|
||||||
mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
|
mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
|
||||||
KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
|
KeyEvent.ACTION_DOWN, keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
|
||||||
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
|
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
|
||||||
mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
|
mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
|
||||||
KeyEvent.ACTION_UP, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
|
KeyEvent.ACTION_UP, keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
|
||||||
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
|
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2045,7 +2045,7 @@ public final class InputLogic {
|
|||||||
// TODO: Remove this special handling of digit letters.
|
// TODO: Remove this special handling of digit letters.
|
||||||
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
||||||
if (codePoint >= '0' && codePoint <= '9') {
|
if (codePoint >= '0' && codePoint <= '9') {
|
||||||
sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0);
|
sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2055,7 +2055,7 @@ public final class InputLogic {
|
|||||||
// a hardware keyboard event on pressing enter or delete. This is bad for many
|
// a hardware keyboard event on pressing enter or delete. This is bad for many
|
||||||
// reasons (there are race conditions with commits) but some applications are
|
// reasons (there are race conditions with commits) but some applications are
|
||||||
// relying on this behavior so we continue to support it for older apps.
|
// relying on this behavior so we continue to support it for older apps.
|
||||||
sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
|
sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER, 0);
|
||||||
} else {
|
} else {
|
||||||
mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1);
|
mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ interface KeyboardManagerForAction {
|
|||||||
fun triggerSystemVoiceInput()
|
fun triggerSystemVoiceInput()
|
||||||
|
|
||||||
fun updateTheme(newTheme: ThemeOption)
|
fun updateTheme(newTheme: ThemeOption)
|
||||||
|
|
||||||
|
fun sendCodePointEvent(codePoint: Int)
|
||||||
|
fun sendKeyEvent(keyCode: Int, metaState: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActionWindow {
|
interface ActionWindow {
|
||||||
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -60,8 +61,11 @@ import org.futo.inputmethod.latin.SuggestedWords
|
|||||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
|
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
|
||||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
|
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
|
||||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
||||||
|
import org.futo.inputmethod.latin.uix.actions.ClipboardAction
|
||||||
import org.futo.inputmethod.latin.uix.actions.EmojiAction
|
import org.futo.inputmethod.latin.uix.actions.EmojiAction
|
||||||
|
import org.futo.inputmethod.latin.uix.actions.RedoAction
|
||||||
import org.futo.inputmethod.latin.uix.actions.ThemeAction
|
import org.futo.inputmethod.latin.uix.actions.ThemeAction
|
||||||
|
import org.futo.inputmethod.latin.uix.actions.UndoAction
|
||||||
import org.futo.inputmethod.latin.uix.actions.VoiceInputAction
|
import org.futo.inputmethod.latin.uix.actions.VoiceInputAction
|
||||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
||||||
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||||
@ -287,7 +291,7 @@ fun ActionItem(action: Action, onSelect: (Action) -> Unit) {
|
|||||||
cornerRadius = CornerRadius(radius, radius)
|
cornerRadius = CornerRadius(radius, radius)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.width(64.dp)
|
.width(50.dp)
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
colors = IconButtonDefaults.iconButtonColors(contentColor = contentCol)
|
colors = IconButtonDefaults.iconButtonColors(contentColor = contentCol)
|
||||||
) {
|
) {
|
||||||
@ -317,12 +321,9 @@ fun RowScope.ActionItems(onSelect: (Action) -> Unit) {
|
|||||||
ActionItem(EmojiAction, onSelect)
|
ActionItem(EmojiAction, onSelect)
|
||||||
ActionItem(VoiceInputAction, onSelect)
|
ActionItem(VoiceInputAction, onSelect)
|
||||||
ActionItem(ThemeAction, onSelect)
|
ActionItem(ThemeAction, onSelect)
|
||||||
|
ActionItem(UndoAction, onSelect)
|
||||||
Box(modifier = Modifier
|
ActionItem(RedoAction, onSelect)
|
||||||
.fillMaxHeight()
|
ActionItem(ClipboardAction, onSelect)
|
||||||
.weight(1.0f)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -386,7 +387,11 @@ fun ActionBar(
|
|||||||
ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value }
|
ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value }
|
||||||
|
|
||||||
if(isActionsOpen.value) {
|
if(isActionsOpen.value) {
|
||||||
ActionItems(onActionActivated)
|
LazyRow {
|
||||||
|
item {
|
||||||
|
ActionItems(onActionActivated)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
} else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
InlineSuggestions(inlineSuggestions)
|
InlineSuggestions(inlineSuggestions)
|
||||||
} else if(words != null) {
|
} else if(words != null) {
|
||||||
@ -399,7 +404,9 @@ fun ActionBar(
|
|||||||
Spacer(modifier = Modifier.weight(1.0f))
|
Spacer(modifier = Modifier.weight(1.0f))
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionItemSmall(VoiceInputAction, onActionActivated)
|
if(!isActionsOpen.value) {
|
||||||
|
ActionItemSmall(VoiceInputAction, onActionActivated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package org.futo.inputmethod.latin.uix.actions
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import org.futo.inputmethod.latin.R
|
||||||
|
import org.futo.inputmethod.latin.uix.Action
|
||||||
|
|
||||||
|
val ClipboardAction = Action(
|
||||||
|
icon = R.drawable.clipboard,
|
||||||
|
name = R.string.clipboard_action_title,
|
||||||
|
simplePressImpl = { manager, _ ->
|
||||||
|
manager.sendKeyEvent(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON)
|
||||||
|
},
|
||||||
|
windowImpl = null,
|
||||||
|
)
|
@ -55,6 +55,7 @@ import kotlinx.serialization.json.jsonArray
|
|||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import org.futo.inputmethod.latin.R
|
import org.futo.inputmethod.latin.R
|
||||||
|
import org.futo.inputmethod.latin.common.Constants
|
||||||
import org.futo.inputmethod.latin.uix.Action
|
import org.futo.inputmethod.latin.uix.Action
|
||||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||||
import org.futo.inputmethod.latin.uix.PersistentActionState
|
import org.futo.inputmethod.latin.uix.PersistentActionState
|
||||||
@ -293,9 +294,9 @@ val EmojiAction = Action(
|
|||||||
}, onExit = {
|
}, onExit = {
|
||||||
manager.closeActionWindow()
|
manager.closeActionWindow()
|
||||||
}, onSpace = {
|
}, onSpace = {
|
||||||
manager.typeText(" ")
|
manager.sendCodePointEvent(Constants.CODE_SPACE)
|
||||||
}, onBackspace = {
|
}, onBackspace = {
|
||||||
manager.backspace(1)
|
manager.sendCodePointEvent(Constants.CODE_DELETE)
|
||||||
}, bitmaps = state.bitmaps, emojis = emojis)
|
}, bitmaps = state.bitmaps, emojis = emojis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package org.futo.inputmethod.latin.uix.actions
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import org.futo.inputmethod.latin.R
|
||||||
|
import org.futo.inputmethod.latin.uix.Action
|
||||||
|
|
||||||
|
val UndoAction = Action(
|
||||||
|
icon = R.drawable.undo,
|
||||||
|
name = R.string.undo_action_title,
|
||||||
|
simplePressImpl = { manager, _ ->
|
||||||
|
manager.sendKeyEvent(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON)
|
||||||
|
},
|
||||||
|
windowImpl = null,
|
||||||
|
)
|
||||||
|
val RedoAction = Action(
|
||||||
|
icon = R.drawable.redo,
|
||||||
|
name = R.string.redo_action_title,
|
||||||
|
simplePressImpl = { manager, _ ->
|
||||||
|
manager.sendKeyEvent(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
|
||||||
|
},
|
||||||
|
windowImpl = null,
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user