Show snackbar with undo option. Don't jump back to calculator on history click.
All checks were successful
Build Android / build (push) Has been cancelled
All checks were successful
Build Android / build (push) Has been cancelled
This commit is contained in:
@@ -23,6 +23,8 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -180,8 +182,11 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
|
||||
) {
|
||||
|
||||
var topBarState by remember { mutableStateOf(TopBarState()) }
|
||||
var snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
if (showFab) {
|
||||
FloatingActionButton(
|
||||
@@ -245,7 +250,10 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
|
||||
topBarState =
|
||||
TopBarState(title = stringResource(R.string.menu_history)) { scope.launch { drawerState.open() } }
|
||||
|
||||
HistoryList(historyViewModel) { navController.navigate(Route.COUNTER) }
|
||||
HistoryList(
|
||||
historyViewModel,
|
||||
snackbarHostState
|
||||
) { navController.navigate(Route.COUNTER) }
|
||||
}
|
||||
composable(Route.SETTINGS) {
|
||||
topBarState =
|
||||
|
||||
@@ -3,6 +3,7 @@ package me.zobrist.tichucounter.ui.history
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -13,7 +14,9 @@ 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.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.DeleteForever
|
||||
@@ -27,6 +30,9 @@ import androidx.compose.material3.DismissValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.SwipeToDismiss
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -35,6 +41,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
@@ -46,6 +53,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import me.zobrist.tichucounter.R
|
||||
import me.zobrist.tichucounter.data.GameWithScores
|
||||
import me.zobrist.tichucounter.data.entity.Game
|
||||
@@ -58,11 +66,15 @@ import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun HistoryList(
|
||||
viewModel: HistoryViewModel, navigateToCalculator: () -> Unit
|
||||
viewModel: HistoryViewModel,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
navigateToCalculator: () -> Unit,
|
||||
) {
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val lazyListState = rememberLazyListState()
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
DeleteConfirmDialog(showDeleteDialog) {
|
||||
showDeleteDialog = false
|
||||
if (it) {
|
||||
@@ -70,14 +82,48 @@ fun HistoryList(
|
||||
}
|
||||
}
|
||||
|
||||
val deletedMessage = stringResource(id = R.string.delete_success)
|
||||
val deletedActionLabel = stringResource(id = R.string.undo_question)
|
||||
|
||||
val activatedMessage = stringResource(id = R.string.activated_success)
|
||||
val activatedActionLabel = stringResource(id = R.string.to_calculator_question)
|
||||
|
||||
HistoryList(
|
||||
viewModel.gameAndHistory,
|
||||
{
|
||||
viewModel.activateGame(it)
|
||||
navigateToCalculator()
|
||||
games = viewModel.gameAndHistory,
|
||||
onOpenClicked = {
|
||||
scope.launch {
|
||||
viewModel.activateGame(it)
|
||||
lazyListState.animateScrollToItem(0)
|
||||
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = activatedMessage,
|
||||
actionLabel = activatedActionLabel,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
navigateToCalculator()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ viewModel.deleteGame(it) },
|
||||
{ showDeleteDialog = true },
|
||||
onDeleteClicked = {
|
||||
scope.launch {
|
||||
viewModel.markToDelete(it)
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = deletedMessage,
|
||||
actionLabel = deletedActionLabel,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.Dismissed) {
|
||||
viewModel.deleteGame(it)
|
||||
} else {
|
||||
viewModel.unmarkToDelete(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
onDeleteAllClicked = { showDeleteDialog = true },
|
||||
lazyListState = lazyListState
|
||||
)
|
||||
}
|
||||
|
||||
@@ -104,16 +150,17 @@ fun DeleteConfirmDialog(show: Boolean = true, onExecuted: (Boolean) -> Unit = {}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HistoryList(
|
||||
games: List<GameWithScores>,
|
||||
onOpenClicked: (gameId: Long) -> Unit,
|
||||
onDeleteClicked: (gameId: Long) -> Unit,
|
||||
onDeleteAllClicked: () -> Unit
|
||||
|
||||
onDeleteAllClicked: () -> Unit,
|
||||
lazyListState: LazyListState = LazyListState(),
|
||||
) {
|
||||
Row {
|
||||
LazyColumn {
|
||||
LazyColumn(state = lazyListState) {
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 10.dp, end = 10.dp),
|
||||
@@ -122,7 +169,7 @@ fun HistoryList(
|
||||
)
|
||||
}
|
||||
items(games.filter { it.game.active }) {
|
||||
HistoryListItem(it)
|
||||
HistoryListItem(it, Modifier.animateItemPlacement())
|
||||
}
|
||||
|
||||
if (games.count() > 1) {
|
||||
@@ -137,7 +184,9 @@ fun HistoryList(
|
||||
items(items = games.filter { !it.game.active }, key = {
|
||||
it.hashCode()
|
||||
}) {
|
||||
DismissibleHistoryListItem(it, onOpenClicked, onDeleteClicked)
|
||||
DismissibleHistoryListItem(
|
||||
it, onOpenClicked, onDeleteClicked, Modifier.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
@@ -145,7 +194,8 @@ fun HistoryList(
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp, end = 4.dp, top = 10.dp)
|
||||
.align(CenterVertically)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.animateItemPlacement(),
|
||||
onClick = { onDeleteAllClicked() }) {
|
||||
Icon(imageVector = Icons.Outlined.DeleteForever, contentDescription = null)
|
||||
Text(text = stringResource(id = R.string.deleteAll))
|
||||
@@ -161,23 +211,24 @@ fun HistoryList(
|
||||
fun DismissibleHistoryListItem(
|
||||
game: GameWithScores,
|
||||
onOpenClicked: (gameId: Long) -> Unit,
|
||||
onDeleteClicked: (gameId: Long) -> Unit
|
||||
onDeleteClicked: (gameId: Long) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
val density = LocalDensity.current
|
||||
val dismissState = rememberDismissState(
|
||||
positionalThreshold = { with(density) { 100.dp.toPx() } },
|
||||
confirmValueChange = {
|
||||
if (it == DismissValue.DismissedToStart) {
|
||||
onDeleteClicked(game.game.uid)
|
||||
}
|
||||
if (it == DismissValue.DismissedToEnd) {
|
||||
onOpenClicked(game.game.uid)
|
||||
}
|
||||
true
|
||||
})
|
||||
val dismissState =
|
||||
rememberDismissState(positionalThreshold = { with(density) { 100.dp.toPx() } },
|
||||
confirmValueChange = {
|
||||
if (it == DismissValue.DismissedToStart) {
|
||||
onDeleteClicked(game.game.uid)
|
||||
}
|
||||
if (it == DismissValue.DismissedToEnd) {
|
||||
onOpenClicked(game.game.uid)
|
||||
}
|
||||
true
|
||||
})
|
||||
|
||||
SwipeToDismiss(state = dismissState, background = {
|
||||
SwipeToDismiss(modifier = modifier, state = dismissState, background = {
|
||||
val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
|
||||
val color by animateColorAsState(
|
||||
when (dismissState.targetValue) {
|
||||
@@ -209,8 +260,10 @@ fun DismissibleHistoryListItem(
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = alignment
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
icon, contentDescription = null, modifier = Modifier.scale(scale)
|
||||
)
|
||||
@@ -224,7 +277,7 @@ fun DismissibleHistoryListItem(
|
||||
|
||||
@Composable
|
||||
fun HistoryListItem(
|
||||
game: GameWithScores
|
||||
game: GameWithScores, modifier: Modifier = Modifier
|
||||
) {
|
||||
val format =
|
||||
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
|
||||
@@ -240,7 +293,7 @@ fun HistoryListItem(
|
||||
val totalScores = game.getTotalPoints()
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 4.dp), colors = cardColor
|
||||
) {
|
||||
@@ -284,5 +337,5 @@ private fun HistoryListPreview() {
|
||||
Game(false, "TeamA5", "TeamB5", Date(), Date()), listOf(Round(5, 50, 90))
|
||||
)
|
||||
)
|
||||
HistoryList(tempData, {}, {}) {}
|
||||
HistoryList(tempData, {}, {}, {})
|
||||
}
|
||||
|
||||
@@ -20,16 +20,27 @@ class HistoryViewModel @Inject constructor(
|
||||
var gameAndHistory by mutableStateOf(emptyList<GameWithScores>())
|
||||
private set
|
||||
|
||||
private var fullList: List<GameWithScores> = emptyList()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
|
||||
gameRepository.getAllWithRoundFlow().collect { games ->
|
||||
gameAndHistory =
|
||||
fullList =
|
||||
games.sortedBy { it.game.modified }.sortedBy { it.game.active }.reversed()
|
||||
gameAndHistory = fullList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markToDelete(gameId: Long) {
|
||||
gameAndHistory = fullList.filter { it.game.uid != gameId }
|
||||
}
|
||||
|
||||
fun unmarkToDelete(gameId: Long) {
|
||||
gameAndHistory = fullList
|
||||
}
|
||||
|
||||
fun deleteGame(gameId: Long) {
|
||||
viewModelScope.launch {
|
||||
gameRepository.deleteGame(gameId)
|
||||
|
||||
@@ -25,5 +25,9 @@
|
||||
<string name="menu_about">About</string>
|
||||
<string name="contact_us">Schreib uns</string>
|
||||
<string name="continue_play">Weiterspielen</string>
|
||||
<string name="delete_success">Spiel gelöscht.</string>
|
||||
<string name="undo_question">RÜCKGÄNGIG</string>
|
||||
<string name="activated_success">Spiel aktiviert.</string>
|
||||
<string name="to_calculator_question">WEITERSPIELEN</string>
|
||||
|
||||
</resources>
|
||||
@@ -29,4 +29,8 @@
|
||||
<string name="contact_us">Contact us</string>
|
||||
<string name="play_store" translatable="false">Play Store</string>
|
||||
<string name="continue_play">Continue</string>
|
||||
<string name="delete_success">Game deleted.</string>
|
||||
<string name="undo_question">UNDO</string>
|
||||
<string name="activated_success">Game activated.</string>
|
||||
<string name="to_calculator_question">CONTINUE PLAYING</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user