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,
selection = TextRange(uiState.selection.first, uiState.selection.last)
),
onCursorChange = onCursorChange
onCursorChange = onCursorChange,
pasteCallback = addSymbol
)
AnimatedVisibility(visible = uiState.output.isNotEmpty()) {
Text(

View File

@ -121,6 +121,8 @@ internal class CalculatorViewModel @Inject constructor(
}
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 }
}

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.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
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.style.TextAlign
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
@ -32,16 +35,24 @@ import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
internal fun InputTextField(
modifier: Modifier,
value: TextFieldValue,
onCursorChange: (IntRange) -> Unit
onCursorChange: (IntRange) -> Unit,
pasteCallback: (String) -> Unit
) {
val clipboardManager = LocalClipboardManager.current
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(
modifier = modifier,
singleLine = true,
value = value,
onValueChange = { onCursorChange(it.selection.start..it.selection.end) },
onValueChange = {
onCursorChange(it.selection.start..it.selection.end)
},
textStyle = NumbersTextStyleDisplayLarge.copy(
textAlign = TextAlign.End,
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)
)
}
}