diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt index 14b29da2..ea985132 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.outlined.MoreVert @@ -44,6 +45,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -53,7 +55,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalTextToolbar +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -69,6 +75,7 @@ import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard import com.sadellie.unitto.feature.calculator.components.DragDownView import com.sadellie.unitto.feature.calculator.components.HistoryList import com.sadellie.unitto.feature.calculator.components.InputTextField +import com.sadellie.unitto.feature.calculator.components.UnittoTextToolbar import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.* @@ -81,20 +88,37 @@ internal fun CalculatorRoute( navigateToSettings: () -> Unit, viewModel: CalculatorViewModel = hiltViewModel() ) { + val clipboardManager = LocalClipboardManager.current val uiState = viewModel.uiState.collectAsStateWithLifecycle() - CalculatorScreen( - uiState = uiState.value, - navigateToMenu = navigateToMenu, - navigateToSettings = navigateToSettings, - addSymbol = viewModel::addSymbol, - clearSymbols = viewModel::clearSymbols, - deleteSymbol = viewModel::deleteSymbol, - onCursorChange = viewModel::onCursorChange, - toggleAngleMode = viewModel::toggleCalculatorMode, - evaluate = viewModel::evaluate, - clearHistory = viewModel::clearHistory - ) + fun copyToClipboard() { + val clipboardText = clipboardManager.getText() ?: return + // This method is called immediately after copying formatted text, we replace it with the + // the unformatted version. + clipboardManager.setText( + AnnotatedString(Formatter.removeFormat(clipboardText.text)) + ) + } + + CompositionLocalProvider( + LocalTextToolbar provides UnittoTextToolbar( + view = LocalView.current, + copyCallback = ::copyToClipboard + ) + ) { + CalculatorScreen( + uiState = uiState.value, + navigateToMenu = navigateToMenu, + navigateToSettings = navigateToSettings, + addSymbol = viewModel::addSymbol, + clearSymbols = viewModel::clearSymbols, + deleteSymbol = viewModel::deleteSymbol, + onCursorChange = viewModel::onCursorChange, + toggleAngleMode = viewModel::toggleCalculatorMode, + evaluate = viewModel::evaluate, + clearHistory = viewModel::clearHistory + ) + } } @Composable @@ -209,16 +233,18 @@ private fun CalculatorScreen( pasteCallback = addSymbol, cutCallback = deleteSymbol ) - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), - text = Formatter.format(uiState.output), - textAlign = TextAlign.End, - softWrap = false, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), - style = NumbersTextStyleDisplayMedium, - ) + SelectionContainer { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + text = Formatter.format(uiState.output), + textAlign = TextAlign.End, + softWrap = false, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), + style = NumbersTextStyleDisplayMedium, + ) + } // Handle Box( Modifier diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt index 1d2d93a6..b6ab6402 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.History import androidx.compose.material3.Icon @@ -115,36 +116,38 @@ private fun HistoryListItem( historyItem: HistoryItem, onTextClick: (String) -> Unit ) { - Column(modifier = modifier) { - Box( - Modifier.clickable { onTextClick(historyItem.expression) } - ) { - Text( - text = Formatter.format(historyItem.expression), - maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .horizontalScroll(rememberScrollState(), reverseScrolling = true), - style = NumbersTextStyleDisplayMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.End - ) - } - Box( - Modifier.clickable { onTextClick(historyItem.result) } - ) { - Text( - text = Formatter.format(historyItem.result), - maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .horizontalScroll(rememberScrollState(), reverseScrolling = true), - style = NumbersTextStyleDisplayMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), - textAlign = TextAlign.End - ) + SelectionContainer { + Column(modifier = modifier) { + Box( + Modifier.clickable { onTextClick(historyItem.expression) } + ) { + Text( + text = Formatter.format(historyItem.expression), + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .horizontalScroll(rememberScrollState(), reverseScrolling = true), + style = NumbersTextStyleDisplayMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.End + ) + } + Box( + Modifier.clickable { onTextClick(historyItem.result) } + ) { + Text( + text = Formatter.format(historyItem.result), + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .horizontalScroll(rememberScrollState(), reverseScrolling = true), + style = NumbersTextStyleDisplayMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), + textAlign = TextAlign.End + ) + } } } } diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt index aaf4ebe6..2056c03b 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt @@ -30,7 +30,6 @@ 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.AnnotatedString import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import com.sadellie.unitto.core.base.Separator @@ -57,21 +56,16 @@ internal fun InputTextField( } fun copyToClipboard() = clipboardManager.setText( - AnnotatedString( - Formatter.removeFormat(formattedInput.text) - ) + formattedInput.annotatedString.subSequence(formattedInput.selection) ) CompositionLocalProvider( LocalTextInputService provides null, LocalTextToolbar provides UnittoTextToolbar( view = LocalView.current, + copyCallback = ::copyToClipboard, pasteCallback = { pasteCallback(clipboardManager.getText()?.text ?: "") }, - cutCallback = { - copyToClipboard() - cutCallback() - }, - copyCallback = ::copyToClipboard + cutCallback = { copyToClipboard(); cutCallback() } ) ) { BasicTextField( diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt index 9aa2779f..46d32b8c 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt @@ -27,9 +27,9 @@ import androidx.compose.ui.platform.TextToolbarStatus internal class UnittoTextToolbar( private val view: View, - private val pasteCallback: () -> Unit, - private val cutCallback: () -> Unit, - private val copyCallback: () -> Unit + private val copyCallback: () -> Unit, + private val pasteCallback: (() -> Unit)? = null, + private val cutCallback: (() -> Unit)? = null ) : TextToolbar { private var actionMode: ActionMode? = null @@ -45,8 +45,10 @@ internal class UnittoTextToolbar( onSelectAllRequested: (() -> Unit)? ) { textActionModeCallback.rect = rect - textActionModeCallback.onCopyRequested = copyCallback - textActionModeCallback.onCutRequested = cutCallback + textActionModeCallback.onCopyRequested = { onCopyRequested?.invoke(); copyCallback.invoke() } + textActionModeCallback.onCutRequested = cutCallback?.let { + { it.invoke(); onCutRequested?.invoke() } + } textActionModeCallback.onPasteRequested = pasteCallback textActionModeCallback.onSelectAllRequested = onSelectAllRequested if (actionMode == null) {