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) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -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) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user