Add undo redo functionality. Move new game to navigation drawer
This commit is contained in:
@@ -73,17 +73,10 @@ abstract class BaseActivity : AppCompatActivity(),
|
||||
}
|
||||
|
||||
private fun setLanguage(language: Language) {
|
||||
|
||||
val locale = when (language) {
|
||||
Language.ENGLISH -> "en"
|
||||
Language.GERMAN -> "de"
|
||||
else -> null
|
||||
}
|
||||
|
||||
val currentLocale = AppCompatDelegate.getApplicationLocales()[0].toString()
|
||||
|
||||
if (locale != null && locale != currentLocale) {
|
||||
val newLocale = LocaleListCompat.forLanguageTags(locale)
|
||||
if (language.value != null && language.value != currentLocale) {
|
||||
val newLocale = LocaleListCompat.forLanguageTags(language.value)
|
||||
AppCompatDelegate.setApplicationLocales(newLocale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
@@ -87,7 +88,11 @@ class MainActivity : BaseActivity() {
|
||||
) {
|
||||
composable("counter") {
|
||||
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.topBarTitle = stringResource(R.string.app_name)
|
||||
mainViewModel.topBarNavigationAction =
|
||||
@@ -140,10 +145,10 @@ class MainActivity : BaseActivity() {
|
||||
},
|
||||
actions = {
|
||||
actions.forEach {
|
||||
IconButton(onClick = { it.action() }) {
|
||||
IconButton(onClick = { it.action() }, enabled = it.isActive) {
|
||||
Icon(
|
||||
imageVector = it.imageVector,
|
||||
contentDescription = null
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -167,7 +172,40 @@ class MainActivity : BaseActivity() {
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
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 ->
|
||||
NavigationDrawerItem(
|
||||
icon = { Icon(screen.icon, contentDescription = null) },
|
||||
@@ -203,8 +241,4 @@ class MainActivity : BaseActivity() {
|
||||
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() }
|
||||
|
||||
}
|
||||
@@ -2,4 +2,4 @@ package me.zobrist.tichucounter.domain
|
||||
|
||||
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)
|
||||
@@ -2,6 +2,7 @@ package me.zobrist.tichucounter.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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) {
|
||||
try {
|
||||
val lastRound = roundDao.getAllForGame(activeGame.uid).last()
|
||||
roundDao.delete(lastRound)
|
||||
} catch (_: NoSuchElementException) {
|
||||
roundDao.delete(getLastRound()!!)
|
||||
} catch (_: NullPointerException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,40 +2,84 @@ package me.zobrist.tichucounter.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
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.TopBarAction
|
||||
import me.zobrist.tichucounter.repository.GameRepository
|
||||
import java.util.NoSuchElementException
|
||||
import javax.inject.Inject
|
||||
|
||||
@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 topBarActions by mutableStateOf(emptyList<TopBarAction>())
|
||||
var topBarIcon by mutableStateOf(Icons.Filled.Menu)
|
||||
|
||||
|
||||
var isUndoActionActive by mutableStateOf(false)
|
||||
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() {
|
||||
topBarNavigationAction.aciton()
|
||||
}
|
||||
|
||||
fun undoLastRound() {
|
||||
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() {
|
||||
viewModelScope.launch {
|
||||
redoRounds.clear()
|
||||
gameRepository.newGame()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,6 @@
|
||||
<string name="submit">Übermitteln</string>
|
||||
<string name="on">Ein</string>
|
||||
<string name="off">Aus</string>
|
||||
<string name="newGame">Neues Spiel</string>
|
||||
|
||||
</resources>
|
||||
@@ -21,8 +21,6 @@
|
||||
<string name="dark">Dark</string>
|
||||
<string name="display">Display</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_history">History</string>
|
||||
@@ -30,7 +28,7 @@
|
||||
<string name="activate">Activate</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="submit">Submit</string>
|
||||
<string name="title_activity_main">MainActivity</string>
|
||||
<string name="on">On</string>
|
||||
<string name="off">Off</string>
|
||||
<string name="newGame">New Game</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user