taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit 5a96d14b3ece9553ea45c1418fda0f2ba21811a8
parent d1da72fad71abb5f33e4e25dd8e49e87b2aa4d3c
Author: Iván Ávalos <avalos@disroot.org>
Date:   Sat,  2 Nov 2024 00:05:40 +0100

[wallet] more UI improvements

Diffstat:
Mwallet/src/main/java/net/taler/wallet/MainFragment.kt | 36++++++++++++++++++++++++++++--------
Mwallet/src/main/java/net/taler/wallet/compose/AmountInputFIeld.kt | 16+++++++++-------
Mwallet/src/main/java/net/taler/wallet/compose/SelectionModeTopAppBar.kt | 4++++
Mwallet/src/main/java/net/taler/wallet/transactions/TransactionsComposable.kt | 123++++++++++++++++++++++++++++++++++++++++---------------------------------------
4 files changed, 103 insertions(+), 76 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt b/wallet/src/main/java/net/taler/wallet/MainFragment.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState @@ -31,6 +32,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize @@ -58,6 +60,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -67,6 +70,7 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -83,6 +87,7 @@ import net.taler.wallet.compose.GridMenuItem import net.taler.wallet.compose.TalerSurface import net.taler.wallet.compose.collectAsStateLifecycleAware import net.taler.wallet.settings.SettingsFragment +import kotlin.math.roundToInt class MainFragment: Fragment() { @@ -90,7 +95,7 @@ class MainFragment: Fragment() { private val model: MainViewModel by activityViewModels() - @OptIn(ExperimentalMaterial3Api::class) + @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -125,24 +130,39 @@ class MainFragment: Fragment() { tooltip = { PlainTooltip { Text(stringResource(R.string.actions)) } }, state = rememberTooltipState(), ) { + var offsetY by remember { mutableFloatStateOf(0f) } + DemandAttention { LargeFloatingActionButton( modifier = Modifier .requiredSize(86.dp) .padding(8.dp) + .offset { IntOffset(0, offsetY.roundToInt() / 6) } .draggable( orientation = Orientation.Vertical, - state = rememberDraggableState { }, - onDragStopped = { onScanQr() }, + state = rememberDraggableState { delta -> + if (delta < 0) { offsetY += delta } + }, + onDragStopped = { + offsetY = 0.0f + onScanQr() + }, ), shape = CircleShape, onClick = { showSheet = true }, ) { - Icon( - painterResource(R.drawable.ic_actions), - modifier = Modifier.size(38.dp), - contentDescription = stringResource(R.string.actions), - ) + if (offsetY == 0.0f) { + Icon( + painterResource(R.drawable.ic_actions), + modifier = Modifier.size(38.dp), + contentDescription = stringResource(R.string.actions), + ) + } else { + Icon( + painterResource(R.drawable.ic_scan_qr), + contentDescription = stringResource(R.string.actions), + ) + } } } } diff --git a/wallet/src/main/java/net/taler/wallet/compose/AmountInputFIeld.kt b/wallet/src/main/java/net/taler/wallet/compose/AmountInputFIeld.kt @@ -81,13 +81,15 @@ fun AmountCurrencyField( readOnly = readOnly, ) - CurrencyDropdown( - modifier = Modifier.weight(1f), - currencies = currencies, - onCurrencyChanged = { onAmountChanged(amount.copy(currency = it)) }, - initialCurrency = amount.currency, - readOnly = !editableCurrency, - ) + if (editableCurrency) { + CurrencyDropdown( + modifier = Modifier.weight(1f), + currencies = currencies, + onCurrencyChanged = { onAmountChanged(amount.copy(currency = it)) }, + initialCurrency = amount.currency, + readOnly = false, + ) + } } } diff --git a/wallet/src/main/java/net/taler/wallet/compose/SelectionModeTopAppBar.kt b/wallet/src/main/java/net/taler/wallet/compose/SelectionModeTopAppBar.kt @@ -16,6 +16,7 @@ package net.taler.wallet.compose +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Delete @@ -30,6 +31,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import net.taler.wallet.R @Composable @@ -81,5 +83,7 @@ fun SelectionModeTopAppBar( colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.surfaceContainer, ), + + windowInsets = WindowInsets(0.dp), ) } \ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsComposable.kt @@ -21,18 +21,15 @@ import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.defaultMinSize 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.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -42,7 +39,6 @@ import androidx.compose.material.icons.rounded.CheckCircle import androidx.compose.material.icons.rounded.RadioButtonUnchecked import androidx.compose.material3.AlertDialog import androidx.compose.material3.Badge -import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.ListItem @@ -62,7 +58,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -83,7 +81,6 @@ import net.taler.wallet.cleanExchange import net.taler.wallet.compose.LoadingScreen import net.taler.wallet.compose.SelectionModeTopAppBar import net.taler.wallet.compose.TalerSurface -import net.taler.wallet.launchInAppBrowser import net.taler.wallet.transactions.AmountType.Negative import net.taler.wallet.transactions.AmountType.Neutral import net.taler.wallet.transactions.AmountType.Positive @@ -157,68 +154,68 @@ fun TransactionsComposable( } } - LazyColumn( - Modifier - .consumeWindowInsets(innerPadding) - .fillMaxHeight(), - contentPadding = innerPadding, - ) { - item { - if (selectionMode) SelectionModeTopAppBar( - selectedItems = selectedItems, - resetSelectionMode = { - selectionMode = false - selectedItems.clear() - }, - onSelectAllClicked = { - selectedItems.clear() - selectedItems += txResult.transactions.map { it.transactionId } - }, - onDeleteClicked = { - showDeleteDialog = true - }, - ) - } + Column(Modifier.fillMaxSize()) { + if (selectionMode) SelectionModeTopAppBar( + selectedItems = selectedItems, + resetSelectionMode = { + selectionMode = false + selectedItems.clear() + }, + onSelectAllClicked = { + selectedItems.clear() + selectedItems += txResult.transactions.map { it.transactionId } + }, + onDeleteClicked = { + showDeleteDialog = true + }, + ) - item { - TransactionsHeader( - balance = balance, - spec = currencySpec, - onShowBalancesClicked = onShowBalancesClicked, - ) - } + LazyColumn( + Modifier + .consumeWindowInsets(innerPadding) + .fillMaxHeight(), + contentPadding = innerPadding, + ) { + item { + TransactionsHeader( + balance = balance, + spec = currencySpec, + onShowBalancesClicked = onShowBalancesClicked, + ) + } - items(txResult.transactions, key = { it.transactionId }) { tx -> - val isSelected = selectedItems.contains(tx.transactionId) - - TransactionRow( - tx, currencySpec, - isSelected = isSelected, - selectionMode = selectionMode, - onTransactionClick = { - if (selectionMode) { - if (isSelected) { - selectedItems.remove(tx.transactionId) + items(txResult.transactions, key = { it.transactionId }) { tx -> + val isSelected = selectedItems.contains(tx.transactionId) + + TransactionRow( + tx, currencySpec, + isSelected = isSelected, + selectionMode = selectionMode, + onTransactionClick = { + if (selectionMode) { + if (isSelected) { + selectedItems.remove(tx.transactionId) + } else { + selectedItems.add(tx.transactionId) + } } else { - selectedItems.add(tx.transactionId) + onTransactionClick(tx) } - } else { - onTransactionClick(tx) - } - }, - onTransactionSelect = { - if (selectionMode) { - if (isSelected) { - selectedItems.remove(tx.transactionId) + }, + onTransactionSelect = { + if (selectionMode) { + if (isSelected) { + selectedItems.remove(tx.transactionId) + } else { + selectedItems.add(tx.transactionId) + } } else { + selectionMode = true selectedItems.add(tx.transactionId) } - } else { - selectionMode = true - selectedItems.add(tx.transactionId) - } - }, - ) + }, + ) + } } } } @@ -307,6 +304,7 @@ fun TransactionRow( onTransactionSelect: () -> Unit, ) { val context = LocalContext.current + val haptic = LocalHapticFeedback.current Column { ListItem( @@ -314,7 +312,10 @@ fun TransactionRow( .defaultMinSize(minHeight = 80.dp) .combinedClickable( onClick = onTransactionClick, - onLongClick = onTransactionSelect, + onLongClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onTransactionSelect() + }, ), trailingContent = { Box(