Add undo redo functionality. Move new game to navigation drawer

This commit is contained in:
2023-01-20 19:28:50 +01:00
parent f44b51c075
commit cd39384207
7 changed files with 111 additions and 32 deletions

View File

@@ -73,17 +73,10 @@ abstract class BaseActivity : AppCompatActivity(),
} }
private fun setLanguage(language: Language) { private fun setLanguage(language: Language) {
val locale = when (language) {
Language.ENGLISH -> "en"
Language.GERMAN -> "de"
else -> null
}
val currentLocale = AppCompatDelegate.getApplicationLocales()[0].toString() val currentLocale = AppCompatDelegate.getApplicationLocales()[0].toString()
if (locale != null && locale != currentLocale) { if (language.value != null && language.value != currentLocale) {
val newLocale = LocaleListCompat.forLanguageTags(locale) val newLocale = LocaleListCompat.forLanguageTags(language.value)
AppCompatDelegate.setApplicationLocales(newLocale) AppCompatDelegate.setApplicationLocales(newLocale)
} }
} }

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -87,7 +88,11 @@ class MainActivity : BaseActivity() {
) { ) {
composable("counter") { composable("counter") {
Counter(counterViewModel) Counter(counterViewModel)
mainViewModel.topBarActions = (listOf(undoAction, newGameAction)) mainViewModel.topBarActions = (listOf(
TopBarAction(Icons.Outlined.Undo, mainViewModel.isUndoActionActive) { mainViewModel.undoLastRound() },
TopBarAction(Icons.Outlined.Redo, mainViewModel.isRedoActionActive) { mainViewModel.redoLastRound() }
))
mainViewModel.topBarIcon = Icons.Outlined.Menu mainViewModel.topBarIcon = Icons.Outlined.Menu
mainViewModel.topBarTitle = stringResource(R.string.app_name) mainViewModel.topBarTitle = stringResource(R.string.app_name)
mainViewModel.topBarNavigationAction = mainViewModel.topBarNavigationAction =
@@ -140,10 +145,10 @@ class MainActivity : BaseActivity() {
}, },
actions = { actions = {
actions.forEach { actions.forEach {
IconButton(onClick = { it.action() }) { IconButton(onClick = { it.action() }, enabled = it.isActive) {
Icon( Icon(
imageVector = it.imageVector, imageVector = it.imageVector,
contentDescription = null contentDescription = null,
) )
} }
} }
@@ -167,7 +172,40 @@ class MainActivity : BaseActivity() {
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
ModalDrawerSheet { ModalDrawerSheet {
Spacer(Modifier.height(12.dp))
Spacer(Modifier.height(20.dp))
NavigationDrawerItem(
icon = { Icon(Icons.Outlined.RestartAlt, contentDescription = null) },
colors = NavigationDrawerItemDefaults.colors(
unselectedContainerColor = MaterialTheme.colorScheme.secondaryContainer
),
label = { Text(stringResource(R.string.newGame)) },
selected = false,
onClick = {
scope.launch { drawerState.close() }
mainViewModel.newGame()
navController.navigate("counter") {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
Spacer(Modifier.height(20.dp))
Divider()
items.forEach { screen -> items.forEach { screen ->
NavigationDrawerItem( NavigationDrawerItem(
icon = { Icon(screen.icon, contentDescription = null) }, icon = { Icon(screen.icon, contentDescription = null) },
@@ -203,8 +241,4 @@ class MainActivity : BaseActivity() {
object Settings : Screen("settings", Icons.Outlined.Settings, R.string.menu_settings) object Settings : Screen("settings", Icons.Outlined.Settings, R.string.menu_settings)
} }
private val undoAction = TopBarAction(Icons.Outlined.Undo) { mainViewModel.undoLastRound() }
private val newGameAction = TopBarAction(Icons.Outlined.Add) { mainViewModel.newGame() }
} }

View File

@@ -2,4 +2,4 @@ package me.zobrist.tichucounter.domain
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
class TopBarAction(val imageVector: ImageVector, val action: () -> Unit) class TopBarAction(val imageVector: ImageVector, val isActive: Boolean, val action: () -> Unit)

View File

@@ -2,6 +2,7 @@ package me.zobrist.tichucounter.repository
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.zobrist.tichucounter.data.Game import me.zobrist.tichucounter.data.Game
@@ -57,12 +58,20 @@ class GameRepository @Inject constructor(
} }
} }
suspend fun revertLastRound() { suspend fun getLastRound(): Round? {
return try {
withContext(Dispatchers.IO) {
roundDao.getAllForGame(activeGame.uid).last()
}
} catch (_:NoSuchElementException) {
null
}
}
suspend fun deleteLastRound() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val lastRound = roundDao.getAllForGame(activeGame.uid).last() roundDao.delete(getLastRound()!!)
roundDao.delete(lastRound) } catch (_: NullPointerException) {
} catch (_: NoSuchElementException) {
} }
} }
} }

View File

@@ -2,40 +2,84 @@ package me.zobrist.tichucounter.ui
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.runtime.getValue import androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.data.RoundDao
import me.zobrist.tichucounter.domain.NavigationAction import me.zobrist.tichucounter.domain.NavigationAction
import me.zobrist.tichucounter.domain.TopBarAction import me.zobrist.tichucounter.domain.TopBarAction
import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.repository.GameRepository
import java.util.NoSuchElementException
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class MainViewModel @Inject constructor(private val gameRepository: GameRepository) : ViewModel() { class MainViewModel @Inject constructor(private val gameRepository: GameRepository, roundDao: RoundDao) : ViewModel() {
private var redoRounds = mutableStateListOf<Round>()
private var expectedRoundCount = 0
var topBarTitle by mutableStateOf("") var topBarTitle by mutableStateOf("")
var topBarActions by mutableStateOf(emptyList<TopBarAction>()) var topBarActions by mutableStateOf(emptyList<TopBarAction>())
var topBarIcon by mutableStateOf(Icons.Filled.Menu) var topBarIcon by mutableStateOf(Icons.Filled.Menu)
var isUndoActionActive by mutableStateOf(false)
var topBarNavigationAction by mutableStateOf(NavigationAction {}) var topBarNavigationAction by mutableStateOf(NavigationAction {})
val isRedoActionActive: Boolean
get() = redoRounds.isNotEmpty()
init {
viewModelScope.launch {
roundDao.getForActiveGame().collect() {
isUndoActionActive = it.isNotEmpty()
if(expectedRoundCount != it.count()) {
redoRounds.clear()
}
expectedRoundCount = it.count()
}
}
}
fun onNavigateClicked() { fun onNavigateClicked() {
topBarNavigationAction.aciton() topBarNavigationAction.aciton()
} }
fun undoLastRound() { fun undoLastRound() {
viewModelScope.launch { viewModelScope.launch {
gameRepository.revertLastRound() val round = gameRepository.getLastRound()
if (round != null) {
redoRounds.add(round)
expectedRoundCount--
gameRepository.deleteLastRound()
} }
} }
}
fun redoLastRound() {
viewModelScope.launch {
try {
val round = redoRounds.last()
redoRounds.remove(round)
expectedRoundCount++
gameRepository.addRoundToActiveGame(round.scoreA, round.scoreB)
}catch (_: NoSuchElementException)
{
}
}
}
fun clearRedoList() {
redoRounds.clear()
}
fun newGame() { fun newGame() {
viewModelScope.launch { viewModelScope.launch {
redoRounds.clear()
gameRepository.newGame() gameRepository.newGame()
} }
} }

View File

@@ -24,5 +24,6 @@
<string name="submit">Übermitteln</string> <string name="submit">Übermitteln</string>
<string name="on">Ein</string> <string name="on">Ein</string>
<string name="off">Aus</string> <string name="off">Aus</string>
<string name="newGame">Neues Spiel</string>
</resources> </resources>

View File

@@ -21,8 +21,6 @@
<string name="dark">Dark</string> <string name="dark">Dark</string>
<string name="display">Display</string> <string name="display">Display</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="nav_header_subtitle">app@zobrist.me</string>
<string name="nav_header_desc">Navigation header</string>
<string name="menu_counter">Counter</string> <string name="menu_counter">Counter</string>
<string name="menu_history">History</string> <string name="menu_history">History</string>
@@ -30,7 +28,7 @@
<string name="activate">Activate</string> <string name="activate">Activate</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="submit">Submit</string> <string name="submit">Submit</string>
<string name="title_activity_main">MainActivity</string>
<string name="on">On</string> <string name="on">On</string>
<string name="off">Off</string> <string name="off">Off</string>
<string name="newGame">New Game</string>
</resources> </resources>