From 95a401581d99061eee4d687a7970b9e925c7022b Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Tue, 21 Feb 2023 18:59:06 +0400 Subject: [PATCH] Clear history UX/UI improved - Show clear history button only when history view is fully expanded - History view placeholder - AlertDialog closes after clearing history - List item height callback is called only once --- core/base/src/main/res/values/strings.xml | 1 + .../feature/calculator/CalculatorScreen.kt | 53 +++++--- .../calculator/components/HistoryList.kt | 114 +++++++++++++----- 3 files changed, 124 insertions(+), 44 deletions(-) diff --git a/core/base/src/main/res/values/strings.xml b/core/base/src/main/res/values/strings.xml index debd58a2..e7778fdc 100644 --- a/core/base/src/main/res/values/strings.xml +++ b/core/base/src/main/res/values/strings.xml @@ -1028,6 +1028,7 @@ Clear Clear history All expressions from history will be deleted forever. This action can\'t be undone! + No history Number of decimal places 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 3d8238e0..b5684f20 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 @@ -18,7 +18,10 @@ package com.sadellie.unitto.feature.calculator +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable @@ -101,6 +104,7 @@ private fun CalculatorScreen( evaluate: () -> Unit, clearHistory: () -> Unit ) { + var showClearHistoryButton by rememberSaveable { mutableStateOf(false) } var showClearHistoryDialog by rememberSaveable { mutableStateOf(false) } var draggedAmount by remember { mutableStateOf(0f) } val dragAmountAnimated by animateFloatAsState(draggedAmount) @@ -113,15 +117,21 @@ private fun CalculatorScreen( navigateUpAction = navigateUpAction, colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceVariant), actions = { - IconButton( - onClick = { showClearHistoryDialog = true }, - content = { - Icon( - Icons.Default.Delete, - stringResource(R.string.calculator_clear_history_title) - ) - } - ) + AnimatedVisibility( + visible = showClearHistoryButton, + enter = fadeIn(), + exit = fadeOut() + ) { + IconButton( + onClick = { showClearHistoryDialog = true }, + content = { + Icon( + Icons.Default.Delete, + stringResource(R.string.calculator_clear_history_title) + ) + } + ) + } } ) { paddingValues -> DragDownView( @@ -153,10 +163,14 @@ private fun CalculatorScreen( state = rememberDraggableState { delta -> draggedAmount = (draggedAmount + delta).coerceAtLeast(0f) }, - onDragStopped = { + onDragStopped = { _ -> // Snap to closest anchor (0, one history item, all history items) draggedAmount = listOf(0, historyItemHeight, maxDragAmount) .minBy { abs(draggedAmount.roundToInt() - it) } + .also { + // Show button only when fully history view is fully expanded + showClearHistoryButton = it == maxDragAmount + } .toFloat() } ), @@ -218,12 +232,21 @@ private fun CalculatorScreen( Text(stringResource(R.string.calculator_clear_history_support)) }, confirmButton = { - TextButton(onClick = clearHistory) { Text(stringResource(R.string.calculator_clear_history_label)) } + TextButton( + onClick = { + clearHistory() + showClearHistoryDialog = false + } + ) { + Text(stringResource(R.string.calculator_clear_history_label)) + } }, dismissButton = { - TextButton(onClick = { - showClearHistoryDialog = false - }) { Text(stringResource(R.string.cancel_label)) } + TextButton( + onClick = { showClearHistoryDialog = false } + ) { + Text(stringResource(R.string.cancel_label)) + } }, onDismissRequest = { showClearHistoryDialog = false } ) @@ -247,7 +270,7 @@ private fun PreviewCalculatorScreen() { ).map { HistoryItem( date = dtf.parse(it)!!, - expression = "12345123451234512345123451234512345123451234512345123451234512345", + expression = "12345".repeat(10), result = "67890" ) } 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 293ef413..d9b122a1 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 @@ -18,21 +18,35 @@ package com.sadellie.unitto.feature.calculator.components +import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +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.material.icons.Icons +import androidx.compose.material.icons.filled.History +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium import com.sadellie.unitto.data.model.HistoryItem +import com.sadellie.unitto.feature.calculator.R import java.text.SimpleDateFormat import java.util.* @@ -42,37 +56,77 @@ internal fun HistoryList( historyItems: List, historyItemHeightCallback: (Int) -> Unit ) { - LazyColumn( - modifier = modifier, - reverseLayout = true - ) { - items(historyItems) { historyItem -> - Column( - Modifier.onPlaced { historyItemHeightCallback(it.size.height) } - ) { - Text( - text = historyItem.expression, - maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .horizontalScroll(rememberScrollState(), reverseScrolling = true), - style = NumbersTextStyleDisplayMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.End - ) - Text( - text = historyItem.result, - maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .horizontalScroll(rememberScrollState(), reverseScrolling = true), - style = NumbersTextStyleDisplayMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), - textAlign = TextAlign.End - ) + val verticalArrangement by remember(historyItems) { + derivedStateOf { + if (historyItems.isEmpty()) { + Arrangement.Center + } else { + Arrangement.spacedBy(16.dp, Alignment.Bottom) } } } + + LazyColumn( + modifier = modifier, + reverseLayout = true, + verticalArrangement = verticalArrangement + ) { + if (historyItems.isEmpty()) { + item { + Column( + modifier = Modifier + .onPlaced { historyItemHeightCallback(it.size.height) } + .fillParentMaxWidth() + .padding(vertical = 32.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon(Icons.Default.History, null) + Text(stringResource(R.string.calculator_no_history)) + } + } + } else { + // We do this so that callback for items height is called only once + item { + HistoryListItem( + modifier = Modifier.onPlaced { historyItemHeightCallback(it.size.height) }, + historyItem = historyItems.first() + ) + } + items(historyItems.drop(1)) { historyItem -> + HistoryListItem(historyItem = historyItem) + } + } + } +} + +@Composable +private fun HistoryListItem( + modifier: Modifier = Modifier, + historyItem: HistoryItem +) { + Column(modifier = modifier) { + Text( + text = historyItem.expression, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState(), reverseScrolling = true), + style = NumbersTextStyleDisplayMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.End + ) + Text( + text = historyItem.result, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState(), reverseScrolling = true), + style = NumbersTextStyleDisplayMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), + textAlign = TextAlign.End + ) + } } @Preview @@ -98,7 +152,9 @@ private fun PreviewHistoryList() { } HistoryList( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) + .fillMaxSize(), historyItems = historyItems, historyItemHeightCallback = {} )