Delete calculator history item by one

closes #58
This commit is contained in:
Sad Ellie 2024-02-04 23:48:22 +03:00
parent 9a865b4049
commit 402f48ee1f
8 changed files with 135 additions and 61 deletions

View File

@ -50,3 +50,5 @@ internal fun ClipboardManager.copy(value: TextFieldValue) = this.setText(
.text
)
)
internal const val PLAIN_TEXT_LABEL = "plain text"

View File

@ -23,7 +23,6 @@ import android.content.Context
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.Text
@ -36,7 +35,6 @@ import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.theme.LocalNumberTypography
@Composable
@ -45,7 +43,7 @@ fun FixedExpressionInputTextField(
value: String,
formatterSymbols: FormatterSymbols,
textColor: Color,
onClick: (cleanValue: String) -> Unit
onClick: () -> Unit,
) {
val clipboardManager = FormattedExpressionClipboardManager(
formatterSymbols = formatterSymbols,
@ -55,13 +53,13 @@ fun FixedExpressionInputTextField(
CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
SelectionContainer(
modifier = modifier
.clickable { onClick(value) }
.fillMaxWidth()
.padding(horizontal = 8.dp)
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
modifier = Modifier
.horizontalScroll(rememberScrollState()) // Must be first
.clickable(onClick = onClick)
.then(modifier)
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = value.formatExpression(formatterSymbols),
style = LocalNumberTypography.current.displaySmall
.copy(color = textColor, textAlign = TextAlign.End),

View File

@ -53,6 +53,10 @@ class CalculatorHistoryRepositoryImpl @Inject constructor(
)
}
override suspend fun delete(item: HistoryItem) {
calculatorHistoryDao.delete(item.id)
}
override suspend fun clear() {
calculatorHistoryDao.clear()
}

View File

@ -32,6 +32,9 @@ interface CalculatorHistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg historyEntity: CalculatorHistoryEntity)
@Query("DELETE FROM calculator_history WHERE entityId = :entityId")
suspend fun delete(entityId: Int)
@Query("DELETE FROM calculator_history")
suspend fun clear()
}

View File

@ -26,7 +26,11 @@ interface CalculatorHistoryRepository {
suspend fun add(
expression: String,
result: String
result: String,
)
suspend fun delete(
item: HistoryItem,
)
suspend fun clear()

View File

@ -101,7 +101,8 @@ internal fun CalculatorRoute(
toggleCalculatorMode = viewModel::updateRadianMode,
equal = viewModel::equal,
clearHistory = viewModel::clearHistory,
addBracket = viewModel::addBracket
addBracket = viewModel::addBracket,
onDelete = viewModel::deleteHistoryItem,
)
}
@ -117,7 +118,8 @@ internal fun CalculatorScreen(
onCursorChange: (TextRange) -> Unit,
toggleCalculatorMode: (Boolean) -> Unit,
equal: () -> Unit,
clearHistory: () -> Unit
clearHistory: () -> Unit,
onDelete: (HistoryItem) -> Unit,
) {
when (uiState) {
is CalculatorUIState.Loading -> EmptyScreen()
@ -132,7 +134,8 @@ internal fun CalculatorScreen(
toggleAngleMode = { toggleCalculatorMode(!uiState.radianMode) },
equal = equal,
clearHistory = clearHistory,
addBracket = addBracket
addBracket = addBracket,
onDelete = onDelete,
)
}
}
@ -149,18 +152,27 @@ private fun Ready(
onCursorChange: (TextRange) -> Unit,
toggleAngleMode: () -> Unit,
equal: () -> Unit,
clearHistory: () -> Unit
clearHistory: () -> Unit,
onDelete: (HistoryItem) -> Unit,
) {
val focusManager = LocalFocusManager.current
var showClearHistoryDialog by rememberSaveable { mutableStateOf(false) }
var showClearHistoryButton by rememberSaveable { mutableStateOf(false) }
val dragState = remember {
AnchoredDraggableState(
initialValue = DragState.CLOSED,
positionalThreshold = { 0f },
velocityThreshold = { 0f },
animationSpec = tween()
)
}
val isOpen = dragState.currentValue == DragState.OPEN
ScaffoldWithTopBar(
title = { Text(stringResource(R.string.calculator_title)) },
navigationIcon = { MenuButton { navigateToMenu() } },
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceVariant),
actions = {
Crossfade(showClearHistoryButton, label = "Clear button reveal") {
Crossfade(isOpen, label = "Clear button reveal") {
if (it) {
IconButton(
onClick = { showClearHistoryDialog = true },
@ -187,15 +199,6 @@ private fun Ready(
val textBoxHeight = maxHeight * textBoxFill
val dragState = remember {
AnchoredDraggableState(
initialValue = DragState.CLOSED,
positionalThreshold = { 0f },
velocityThreshold = { 0f },
animationSpec = tween()
)
}
var historyListHeight by remember { mutableStateOf(0.dp) }
val keyboardHeight by remember(historyListHeight, textBoxHeight) {
derivedStateOf {
@ -234,10 +237,6 @@ private fun Ready(
focusManager.clearFocus()
}
LaunchedEffect(dragState.currentValue) {
showClearHistoryButton = dragState.currentValue == DragState.OPEN
}
BackHandler(dragState.currentValue != DragState.CLOSED) {
coroutineScope.launch {
dragState.animateTo(DragState.CLOSED)
@ -251,7 +250,9 @@ private fun Ready(
.height(historyListHeight),
historyItems = uiState.history,
formatterSymbols = uiState.formatterSymbols,
addTokens = addSymbol
addTokens = addSymbol,
onDelete = onDelete,
showDeleteButtons = isOpen
)
TextBox(
@ -274,7 +275,14 @@ private fun Ready(
CalculatorKeyboard(
modifier = Modifier
.semantics { testTag = "ready" }
.offset { IntOffset(x = 0, y = (historyListHeight + textBoxHeight).toPx().roundToInt()) }
.offset {
IntOffset(
x = 0,
y = (historyListHeight + textBoxHeight)
.toPx()
.roundToInt()
)
}
.height(keyboardHeight)
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp),
@ -375,6 +383,7 @@ private fun PreviewCalculatorScreen() {
toggleCalculatorMode = {},
equal = {},
clearHistory = {},
addBracket = {}
addBracket = {},
onDelete = {},
)
}

View File

@ -32,6 +32,7 @@ import com.sadellie.unitto.core.ui.common.textfield.getTextField
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.common.isExpression
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.HistoryItem
import com.sadellie.unitto.data.model.repository.CalculatorHistoryRepository
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
@ -180,6 +181,10 @@ internal class CalculatorViewModel @Inject constructor(
calculatorHistoryRepository.clear()
}
fun deleteHistoryItem(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO) {
calculatorHistoryRepository.delete(item)
}
fun equal() = viewModelScope.launch {
val prefs = _prefs.value ?: return@launch
if (_equalClicked.value) return@launch

View File

@ -18,10 +18,15 @@
package com.sadellie.unitto.feature.calculator.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
@ -29,7 +34,9 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -53,8 +60,13 @@ internal fun HistoryList(
historyItems: List<HistoryItem>,
formatterSymbols: FormatterSymbols,
addTokens: (String) -> Unit,
onDelete: (HistoryItem) -> Unit,
showDeleteButtons: Boolean,
) {
if (historyItems.isEmpty()) {
Crossfade(
targetState = historyItems.isEmpty()
) { emptyList ->
if (emptyList) {
HistoryListPlaceholder(
modifier = modifier,
)
@ -62,11 +74,14 @@ internal fun HistoryList(
HistoryListContent(
modifier = modifier,
historyItems = historyItems,
addTokens = addTokens,
formatterSymbols = formatterSymbols,
addTokens = addTokens,
onDelete = onDelete,
showDeleteButtons = showDeleteButtons,
)
}
}
}
@Composable
private fun HistoryListPlaceholder(
@ -92,19 +107,24 @@ private fun HistoryListPlaceholder(
private fun HistoryListContent(
modifier: Modifier,
historyItems: List<HistoryItem>,
addTokens: (String) -> Unit,
formatterSymbols: FormatterSymbols,
addTokens: (String) -> Unit,
onDelete: (HistoryItem) -> Unit,
showDeleteButtons: Boolean,
) {
val state = rememberLazyListState()
val focusManager = LocalFocusManager.current
// Very bad workaround for https://issuetracker.google.com/issues/295745063
// Will remove once the fix is released
// Selection handles cause lag
LaunchedEffect(state.isScrollInProgress) {
focusManager.clearFocus(true)
}
LaunchedEffect(historyItems) { state.scrollToItem(0) }
LaunchedEffect(historyItems) {
// Don't scroll when the UI is in state where user can delete an item. This fixes items
// placement animation
if (!showDeleteButtons) state.scrollToItem(0)
}
LazyColumn(
modifier = modifier,
@ -113,9 +133,12 @@ private fun HistoryListContent(
) {
items(historyItems, { it.id }) { historyItem ->
HistoryListItem(
modifier = Modifier.animateItemPlacement(),
historyItem = historyItem,
formatterSymbols = formatterSymbols,
addTokens = addTokens,
onDelete = { onDelete(historyItem) },
showDeleteButton = showDeleteButtons,
)
}
}
@ -127,26 +150,50 @@ private fun HistoryListItem(
historyItem: HistoryItem,
formatterSymbols: FormatterSymbols,
addTokens: (String) -> Unit,
onDelete: () -> Unit,
showDeleteButton: Boolean,
) {
Column(
Row(
modifier = modifier.height(HistoryItemHeight),
verticalArrangement = Arrangement.Center
verticalAlignment = Alignment.CenterVertically,
) {
AnimatedVisibility(visible = showDeleteButton) {
IconButton(onClick = onDelete) {
Icon(
modifier = Modifier,
imageVector = Icons.Outlined.Close,
contentDescription = stringResource(R.string.delete_label),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
Column(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.End
) {
FixedExpressionInputTextField(
modifier = Modifier
.fillMaxWidth(),
value = historyItem.expression,
formatterSymbols = formatterSymbols,
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
onClick = addTokens,
onClick = { addTokens(historyItem.expression) },
)
FixedExpressionInputTextField(
modifier = Modifier
.fillMaxWidth(),
value = historyItem.result,
formatterSymbols = formatterSymbols,
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
onClick = addTokens,
onClick = { addTokens(historyItem.result) },
)
}
}
}
internal val HistoryItemHeight = 108.dp
@ -179,6 +226,8 @@ private fun PreviewHistoryList() {
.fillMaxSize(),
historyItems = historyItems,
formatterSymbols = FormatterSymbols.Spaces,
addTokens = {}
addTokens = {},
onDelete = {},
showDeleteButtons = true,
)
}