diff --git a/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt b/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt index a83c63f..9e3b1b0 100644 --- a/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt @@ -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) } } diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index bad98ef..179709a 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -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() } - } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt b/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt index 9770f28..02ce8c4 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt @@ -2,4 +2,4 @@ package me.zobrist.tichucounter.domain import androidx.compose.ui.graphics.vector.ImageVector -class TopBarAction(val imageVector: ImageVector, val action: () -> Unit) \ No newline at end of file +class TopBarAction(val imageVector: ImageVector, val isActive: Boolean, val action: () -> Unit) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt b/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt index 148cfd6..b928382 100644 --- a/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt +++ b/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt @@ -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) { } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt index 914dff8..693aed5 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt @@ -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() + private var expectedRoundCount = 0 var topBarTitle by mutableStateOf("") var topBarActions by mutableStateOf(emptyList()) 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() } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 540bc48..ef22474 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -24,5 +24,6 @@ Übermitteln Ein Aus + Neues Spiel \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0356480..f75671d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,8 +21,6 @@ Dark Display Settings - app@zobrist.me - Navigation header Counter History @@ -30,7 +28,7 @@ Activate Delete Submit - MainActivity On Off + New Game \ No newline at end of file