mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
parent
9a865b4049
commit
402f48ee1f
@ -50,3 +50,5 @@ internal fun ClipboardManager.copy(value: TextFieldValue) = this.setText(
|
||||
.text
|
||||
)
|
||||
)
|
||||
|
||||
internal const val PLAIN_TEXT_LABEL = "plain text"
|
||||
|
@ -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),
|
||||
|
@ -53,6 +53,10 @@ class CalculatorHistoryRepositoryImpl @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun delete(item: HistoryItem) {
|
||||
calculatorHistoryDao.delete(item.id)
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
calculatorHistoryDao.clear()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -26,7 +26,11 @@ interface CalculatorHistoryRepository {
|
||||
|
||||
suspend fun add(
|
||||
expression: String,
|
||||
result: String
|
||||
result: String,
|
||||
)
|
||||
|
||||
suspend fun delete(
|
||||
item: HistoryItem,
|
||||
)
|
||||
|
||||
suspend fun clear()
|
||||
|
@ -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 = {},
|
||||
)
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user