Paste text in calculator

This commit is contained in:
Sad Ellie 2023-02-15 14:27:23 +04:00
parent 3bc9891b81
commit 684d7f2d9a
7 changed files with 283 additions and 5 deletions

View File

@ -79,7 +79,8 @@ private fun CalculatorScreen(
text = uiState.input, text = uiState.input,
selection = TextRange(uiState.selection.first, uiState.selection.last) selection = TextRange(uiState.selection.first, uiState.selection.last)
), ),
onCursorChange = onCursorChange onCursorChange = onCursorChange,
pasteCallback = addSymbol
) )
AnimatedVisibility(visible = uiState.output.isNotEmpty()) { AnimatedVisibility(visible = uiState.output.isNotEmpty()) {
Text( Text(

View File

@ -121,6 +121,8 @@ internal class CalculatorViewModel @Inject constructor(
} }
fun onCursorChange(selection: IntRange) { fun onCursorChange(selection: IntRange) {
// When we paste, selection is set to the length of the pasted text (start and end)
if (selection.first > _input.value.length) return
_selection.update { selection } _selection.update { selection }
} }

View File

@ -0,0 +1,60 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.annotation.RequiresApi
@RequiresApi(23)
internal class FloatingTextActionModeCallback(
private val callback: UnittoActionModeCallback
) : ActionMode.Callback2() {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return callback.onActionItemClicked(mode, item)
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return callback.onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return callback.onPrepareActionMode()
}
override fun onDestroyActionMode(mode: ActionMode?) {
callback.onDestroyActionMode()
}
override fun onGetContentRect(
mode: ActionMode?,
view: View?,
outRect: android.graphics.Rect?
) {
val rect = callback.rect
outRect?.set(
rect.left.toInt(),
rect.top.toInt(),
rect.right.toInt(),
rect.bottom.toInt()
)
}
}

View File

@ -23,7 +23,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalTextInputService import androidx.compose.ui.platform.LocalTextInputService
import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
@ -32,16 +35,24 @@ import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
internal fun InputTextField( internal fun InputTextField(
modifier: Modifier, modifier: Modifier,
value: TextFieldValue, value: TextFieldValue,
onCursorChange: (IntRange) -> Unit onCursorChange: (IntRange) -> Unit,
pasteCallback: (String) -> Unit
) { ) {
val clipboardManager = LocalClipboardManager.current
CompositionLocalProvider( CompositionLocalProvider(
// FIXME Can't paste if this is null LocalTextInputService provides null,
LocalTextInputService provides null LocalTextToolbar provides UnittoTextToolbar(
view = LocalView.current,
pasteCallback = { pasteCallback(clipboardManager.getText()?.text ?: "") }
)
) { ) {
BasicTextField( BasicTextField(
modifier = modifier, modifier = modifier,
singleLine = true,
value = value, value = value,
onValueChange = { onCursorChange(it.selection.start..it.selection.end) }, onValueChange = {
onCursorChange(it.selection.start..it.selection.end)
},
textStyle = NumbersTextStyleDisplayLarge.copy( textStyle = NumbersTextStyleDisplayLarge.copy(
textAlign = TextAlign.End, textAlign = TextAlign.End,
color = MaterialTheme.colorScheme.onBackground color = MaterialTheme.colorScheme.onBackground

View File

@ -0,0 +1,81 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import androidx.compose.ui.geometry.Rect
private const val MENU_ITEM_COPY = 0
private const val MENU_ITEM_PASTE = 1
private const val MENU_ITEM_CUT = 2
private const val MENU_ITEM_SELECT_ALL = 3
internal class UnittoActionModeCallback(
var rect: Rect = Rect.Zero,
var onCopyRequested: (() -> Unit)? = null,
var onPasteRequested: (() -> Unit)? = null,
var onCutRequested: (() -> Unit)? = null,
var onSelectAllRequested: (() -> Unit)? = null
) {
fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
requireNotNull(menu)
requireNotNull(mode)
onCopyRequested?.let {
menu.add(0, MENU_ITEM_COPY, 0, android.R.string.copy)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
onPasteRequested?.let {
menu.add(0, MENU_ITEM_PASTE, 1, android.R.string.paste)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
onCutRequested?.let {
menu.add(0, MENU_ITEM_CUT, 2, android.R.string.cut)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
onSelectAllRequested?.let {
menu.add(0, MENU_ITEM_SELECT_ALL, 3, android.R.string.selectAll)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
return true
}
fun onPrepareActionMode(): Boolean {
return false
}
fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
when (item!!.itemId) {
MENU_ITEM_COPY -> onCopyRequested?.invoke()
MENU_ITEM_PASTE -> onPasteRequested?.invoke()
MENU_ITEM_CUT -> onCutRequested?.invoke()
MENU_ITEM_SELECT_ALL -> onSelectAllRequested?.invoke()
else -> return false
}
mode?.finish()
return true
}
fun onDestroyActionMode() {}
}

View File

@ -0,0 +1,43 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
internal class UnittoPrimaryTextActionModeCallback(
private val callback: UnittoActionModeCallback
) : ActionMode.Callback {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return callback.onActionItemClicked(mode, item)
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return callback.onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return callback.onPrepareActionMode()
}
override fun onDestroyActionMode(mode: ActionMode?) {
callback.onDestroyActionMode()
}
}

View File

@ -0,0 +1,80 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
import android.os.Build
import android.view.ActionMode
import android.view.View
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.platform.TextToolbar
import androidx.compose.ui.platform.TextToolbarStatus
internal class UnittoTextToolbar(
private val view: View,
private val pasteCallback: () -> Unit
) : TextToolbar {
private var actionMode: ActionMode? = null
private val textActionModeCallback: UnittoActionModeCallback = UnittoActionModeCallback()
override var status: TextToolbarStatus = TextToolbarStatus.Hidden
private set
override fun showMenu(
rect: Rect,
onCopyRequested: (() -> Unit)?,
onPasteRequested: (() -> Unit)?,
onCutRequested: (() -> Unit)?,
onSelectAllRequested: (() -> Unit)?
) {
textActionModeCallback.rect = rect
textActionModeCallback.onCopyRequested = onCopyRequested
textActionModeCallback.onCutRequested = onCutRequested
textActionModeCallback.onPasteRequested = { pasteCallback(); onPasteRequested?.invoke() }
textActionModeCallback.onSelectAllRequested = onSelectAllRequested
if (actionMode == null) {
status = TextToolbarStatus.Shown
actionMode = startActionMode(view, textActionModeCallback)
} else {
actionMode?.invalidate()
}
}
override fun hide() {
status = TextToolbarStatus.Hidden
actionMode?.finish()
actionMode = null
}
}
private fun startActionMode(
view: View,
textActionModeCallback: UnittoActionModeCallback
): ActionMode {
return if (Build.VERSION.SDK_INT >= 23) {
view.startActionMode(
FloatingTextActionModeCallback(textActionModeCallback),
ActionMode.TYPE_FLOATING
)
} else {
// Old devices use toolbar instead of a floating menu
view.startActionMode(
UnittoPrimaryTextActionModeCallback(textActionModeCallback)
)
}
}