From c3c6c253bc4e057695c9ed674bf470dcac40fd32 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 21 Jan 2023 15:58:18 +0100 Subject: [PATCH 01/26] no message --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 814c627..6628138 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ def versionPropertiesFile = rootProject.file("version.properties") def keystoreProperties = new Properties() def versionProperties = new Properties() -def versionMajor = 1 -def versionMinor = 1 +def versionMajor = 2 +def versionMinor = 0 // Load your keystore.properties file into the keystoreProperties object. keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) From 984afb610fb4ccdbd7281b6011a4ad73cbdc42d3 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 21 Jan 2023 16:32:07 +0100 Subject: [PATCH 02/26] Add counter again to navigation. --- .../me/zobrist/tichucounter/MainActivity.kt | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 42bc7c8..011c2a6 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -131,18 +131,18 @@ class MainActivity : BaseActivity() { openDialog = true } ) - mainViewModel.topBarIcon = Icons.Outlined.ArrowBack + mainViewModel.topBarIcon = Icons.Outlined.Menu mainViewModel.topBarTitle = stringResource(R.string.menu_history) mainViewModel.topBarNavigationAction = - NavigationAction { navController.navigate("counter") } + NavigationAction { scope.launch { drawerState.open() } } } composable("settings") { SettingsView(settingsViewModel) mainViewModel.topBarActions = emptyList() - mainViewModel.topBarIcon = Icons.Outlined.ArrowBack + mainViewModel.topBarIcon = Icons.Outlined.Menu mainViewModel.topBarTitle = stringResource(R.string.menu_settings) mainViewModel.topBarNavigationAction = - NavigationAction { navController.navigate("counter") } + NavigationAction { scope.launch { drawerState.open() } } } } } @@ -193,6 +193,7 @@ class MainActivity : BaseActivity() { val navController = rememberNavController() val items = listOf( + Screen("counter", Icons.Outlined.Calculate, R.string.app_name), Screen("history", Icons.Outlined.List, R.string.menu_history), Screen("settings", Icons.Outlined.Settings, R.string.menu_settings) ) @@ -211,7 +212,7 @@ class MainActivity : BaseActivity() { NavigationDrawerItem( icon = { Icon(Icons.Outlined.RestartAlt, contentDescription = null) }, colors = NavigationDrawerItemDefaults.colors( - unselectedContainerColor = MaterialTheme.colorScheme.secondaryContainer + unselectedContainerColor = MaterialTheme.colorScheme.primaryContainer ), label = { Text(stringResource(R.string.newGame)) }, selected = false, @@ -219,25 +220,17 @@ class MainActivity : BaseActivity() { 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() + Divider(Modifier.padding(top = 20.dp, bottom = 20.dp)) items.forEach { screen -> NavigationDrawerItem( From 4d37e77f551517ac47b2ad5adb1867d78dc161ab Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 21 Jan 2023 16:40:00 +0100 Subject: [PATCH 03/26] Fix round index. --- .../java/me/zobrist/tichucounter/ui/counter/RoundListView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt index d16ca8a..f2d5417 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt @@ -50,7 +50,7 @@ private fun RoundListItem(round: Round, index: Int) { textAlign = TextAlign.Center ) Text( - text = index.toString(), + text = (index + 1).toString(), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f), textAlign = TextAlign.Center From c1567efe52b344dca7628b51b1b65d5a5fb5bb83 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 21 Jan 2023 18:12:30 +0100 Subject: [PATCH 04/26] [#11] Close dropdown list first. the callback might restart the application on a settings change and keep the menu open. --- .../java/me/zobrist/tichucounter/ui/settings/SettingsView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt index fd86dc5..d6a05e9 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt @@ -136,8 +136,8 @@ fun StringSetting(name: String, map: Map, selected: T, onSelected: ( map.forEach { DropdownMenuItem( onClick = { - onSelected(it.key) expanded = false + onSelected(it.key) }, text = { Text(stringResource(it.value)) }, trailingIcon = { From 33e57bcfd764a5ced7dc12c6c9ed56a9771a6663 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 21 Jan 2023 18:21:40 +0100 Subject: [PATCH 05/26] Show fab only on counter screen. --- app/src/main/java/me/zobrist/tichucounter/MainActivity.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 011c2a6..09344dc 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -240,16 +240,10 @@ class MainActivity : BaseActivity() { onClick = { scope.launch { drawerState.close() } navController.navigate(screen.route) { - // 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 } }, @@ -263,7 +257,7 @@ class MainActivity : BaseActivity() { drawerState, scope, navController, - counterViewModel.keyboardHidden + counterViewModel.keyboardHidden && (currentDestination?.hierarchy?.any { it.route == "counter" } == true) ) { counterViewModel.keyboardHidden = false } } } From db58e475d1061206fffac9c596ce628797b15c12 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sun, 22 Jan 2023 17:57:51 +0100 Subject: [PATCH 06/26] [#11] Change how settings adapter work. Directly set system settings in MainActivity. --- .../me/zobrist/tichucounter/BaseActivity.kt | 83 ------------- .../me/zobrist/tichucounter/MainActivity.kt | 49 +++++++- .../tichucounter/domain/SettingsAdapter.kt | 111 +++++++++++++----- .../tichucounter/ui/settings/SettingsView.kt | 7 +- .../ui/settings/SettingsViewModel.kt | 5 +- 5 files changed, 135 insertions(+), 120 deletions(-) delete mode 100644 app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt diff --git a/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt b/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt deleted file mode 100644 index e9a040b..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/BaseActivity.kt +++ /dev/null @@ -1,83 +0,0 @@ -package me.zobrist.tichucounter - -import android.content.SharedPreferences -import android.os.Bundle -import android.view.WindowManager -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.app.AppCompatDelegate -import androidx.core.os.LocaleListCompat -import androidx.preference.PreferenceManager -import dagger.hilt.android.AndroidEntryPoint -import me.zobrist.tichucounter.domain.Language -import me.zobrist.tichucounter.domain.SettingsAdapter -import me.zobrist.tichucounter.domain.Theme -import javax.inject.Inject - -@AndroidEntryPoint -abstract class BaseActivity : AppCompatActivity(), - SharedPreferences.OnSharedPreferenceChangeListener { - - @Inject - lateinit var settingsAdapter: SettingsAdapter - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - keepScreenOn(settingsAdapter.keepScreenOn) - updateTheme(settingsAdapter.theme) - - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(this) - } - - override fun onResume() { - super.onResume() - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(this) - } - - override fun onPause() { - super.onPause() - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { - when (key) { - settingsAdapter.language::class.simpleName -> setLanguage(settingsAdapter.language) - settingsAdapter.keepScreenOn::class.simpleName -> keepScreenOn(settingsAdapter.keepScreenOn) - settingsAdapter.theme::class.simpleName -> updateTheme(settingsAdapter.theme) - } - } - - - private fun updateTheme(theme: Theme) { - - val themeValue = when (theme) { - Theme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO - Theme.DARK -> AppCompatDelegate.MODE_NIGHT_YES - Theme.DEFAULT -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } - - if (themeValue != AppCompatDelegate.getDefaultNightMode()) { - AppCompatDelegate.setDefaultNightMode(themeValue) - delegate.applyDayNight() - } - } - - private fun keepScreenOn(keepOn: Boolean) { - if (keepOn) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - } - - private fun setLanguage(language: Language) { - val currentLocale = AppCompatDelegate.getApplicationLocales()[0].toString() - - if (language.value != currentLocale) { - val newLocale = LocaleListCompat.forLanguageTags(language.value) - AppCompatDelegate.setApplicationLocales(newLocale) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 09344dc..8dac229 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -1,9 +1,12 @@ package me.zobrist.tichucounter import android.os.Bundle +import android.view.WindowManager import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -15,6 +18,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.core.os.LocaleListCompat import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController @@ -26,8 +30,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import me.zobrist.tichucounter.domain.NavigationAction -import me.zobrist.tichucounter.domain.TopBarAction +import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.ui.AppTheme import me.zobrist.tichucounter.ui.MainViewModel @@ -39,11 +42,14 @@ import me.zobrist.tichucounter.ui.settings.SettingsViewModel import javax.inject.Inject @AndroidEntryPoint -class MainActivity : BaseActivity() { +class MainActivity : AppCompatActivity(), ISettingsChangeListener { @Inject lateinit var gameRepository: GameRepository + @Inject + lateinit var settingsAdapter: SettingsAdapter + private val counterViewModel: CounterViewModel by viewModels() private val historyViewModel: HistoryViewModel by viewModels() private val settingsViewModel: SettingsViewModel by viewModels() @@ -53,6 +59,8 @@ class MainActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + settingsAdapter.registerOnChangeListener(this) + setContent { AppTheme { val systemUiController = rememberSystemUiController() @@ -62,6 +70,41 @@ class MainActivity : BaseActivity() { } } + override fun onDestroy() { + super.onDestroy() + settingsAdapter.unregisterOnChangeListener(this) + } + + override fun onLanguageChanged(language: Language) { + val currentLocale = AppCompatDelegate.getApplicationLocales()[0].toString() + + if (language.value != currentLocale) { + val newLocale = LocaleListCompat.forLanguageTags(language.value) + AppCompatDelegate.setApplicationLocales(newLocale) + } + } + + override fun onThemeChanged(theme: Theme) { + val themeValue = when (theme) { + Theme.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO + Theme.DARK -> AppCompatDelegate.MODE_NIGHT_YES + Theme.DEFAULT -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } + + if (themeValue != AppCompatDelegate.getDefaultNightMode()) { + AppCompatDelegate.setDefaultNightMode(themeValue) + delegate.applyDayNight() + } + } + + override fun onScreenOnChanged(keepOn: KeepScreenOn) { + if (keepOn.value) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun MyScaffoldLayout( diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt b/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt index 65bfb82..ca83692 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt @@ -5,37 +5,90 @@ import androidx.appcompat.app.AppCompatDelegate.getApplicationLocales import androidx.preference.PreferenceManager import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject +import javax.inject.Singleton enum class Theme { DEFAULT, DARK, LIGHT } enum class Language(val value: String) { ENGLISH("en"), GERMAN("de") } +enum class KeepScreenOn(val value: Boolean) { ON(true), OFF(false) } +interface ISettingsChangeListener { + fun onLanguageChanged(language: Language) + fun onThemeChanged(theme: Theme) + fun onScreenOnChanged(keepOn: KeepScreenOn) +} +@Singleton class SettingsAdapter @Inject constructor(@ApplicationContext private val context: Context) { private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + private var listenerList = mutableListOf() - val language: Language - get() { - return try { - var setting = sharedPreferences.getString(Language::class.simpleName, null) - enumValueOf(setting!!) - } catch (_: NullPointerException) { - val current = getCurrentAppLanguage() - setLanguage(current) - current - } + var language: Language + private set + + var theme: Theme + private set + + var keepScreenOn: KeepScreenOn + private set + + init { + language = try { + enumValueOf(sharedPreferences.getString(Language::class.simpleName, null)!!) + } catch (_: NullPointerException) { + getCurrentAppLanguage() } - val theme: Theme - get() { - val setting = sharedPreferences.getString(Theme::class.simpleName, Theme.DEFAULT.name) - return enumValueOf(setting!!) + theme = try { + enumValueOf(sharedPreferences.getString(Theme::class.simpleName, null)!!) + } catch (_: java.lang.Exception) { + Theme.DEFAULT } - val keepScreenOn: Boolean - get() { - return sharedPreferences.getBoolean("keep_screen_on", false) + keepScreenOn = try { + enumValueOf(sharedPreferences.getString(KeepScreenOn::class.simpleName, null)!!) + } catch (_: java.lang.Exception) { + KeepScreenOn.OFF } + } + + fun registerOnChangeListener(listener: ISettingsChangeListener) { + listenerList.add(listener) + + listener.onThemeChanged(theme) + listener.onLanguageChanged(language) + listener.onScreenOnChanged(keepScreenOn) + } + + fun unregisterOnChangeListener(listener: ISettingsChangeListener?) { + if (listener != null) { + listenerList.remove(listener) + } + } + + fun setLanguage(language: Language) { + this.language = language + updatePreference(Language::class.simpleName, language.name) + notifyListeners(language) + } + + fun setTheme(theme: Theme) { + this.theme = theme + updatePreference(Theme::class.simpleName, theme.name) + notifyListeners(theme) + } + + fun setKeepScreenOn(setting: KeepScreenOn) { + this.keepScreenOn = setting + updatePreference(KeepScreenOn::class.simpleName, setting.name) + notifyListeners(setting) + } + + private fun updatePreference(name: String?, value: String) { + val editor = sharedPreferences.edit() + editor.putString(name, value) + editor.apply() + } private fun getCurrentAppLanguage(): Language { return when (getApplicationLocales()[0].toString()) { @@ -44,22 +97,22 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex } } - fun setLanguage(language: Language) { - val editor = sharedPreferences.edit() - editor.putString(Language::class.simpleName, language.name) - editor.apply() + private fun notifyListeners(language: Language) { + listenerList.forEach { + it.onLanguageChanged(language) + } } - fun setTheme(theme: Theme) { - val editor = sharedPreferences.edit() - editor.putString(Theme::class.simpleName, theme.name) - editor.apply() + private fun notifyListeners(theme: Theme) { + listenerList.forEach { + it.onThemeChanged(theme) + } } - fun setKeepScreenOn(setting: Boolean) { - val editor = sharedPreferences.edit() - editor.putBoolean("keep_screen_on", setting) - editor.apply() + private fun notifyListeners(keepScreenOn: KeepScreenOn) { + listenerList.forEach { + it.onScreenOnChanged(keepScreenOn) + } } } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt index d6a05e9..bb02db3 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import me.zobrist.tichucounter.R +import me.zobrist.tichucounter.domain.KeepScreenOn import me.zobrist.tichucounter.domain.Language import me.zobrist.tichucounter.domain.Theme import me.zobrist.tichucounter.ui.AppTheme @@ -38,7 +39,7 @@ val themeMap = mapOf( @Composable fun SettingsView(viewModel: SettingsViewModel) { SettingsView( - viewModel.screenOn, + viewModel.screenOn.value, viewModel.language, viewModel.theme, { viewModel.updateScreenOn(it) }, @@ -51,7 +52,7 @@ fun SettingsView( valueScreenOn: Boolean = true, valueLanguage: Language = Language.ENGLISH, valueTheme: Theme = Theme.DARK, - updateScreenOn: (Boolean) -> Unit = {}, + updateScreenOn: (KeepScreenOn) -> Unit = {}, updateLanguage: (Language) -> Unit = {}, updateTheme: (Theme) -> Unit = {} ) { @@ -59,7 +60,7 @@ fun SettingsView( BooleanSetting( stringResource(R.string.keep_screen_on), valueScreenOn - ) { updateScreenOn(it) } + ) { updateScreenOn(if (it) KeepScreenOn.ON else KeepScreenOn.OFF) } StringSetting( stringResource(R.string.choose_language_text), diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsViewModel.kt index 3db0b89..b37e1bd 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsViewModel.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import me.zobrist.tichucounter.domain.KeepScreenOn import me.zobrist.tichucounter.domain.Language import me.zobrist.tichucounter.domain.SettingsAdapter import me.zobrist.tichucounter.domain.Theme @@ -20,7 +21,7 @@ class SettingsViewModel @Inject constructor(private val settings: SettingsAdapte var theme by mutableStateOf(settings.theme) private set - var screenOn by mutableStateOf(false) + var screenOn by mutableStateOf(settings.keepScreenOn) private set fun updateLanguage(language: Language) { @@ -33,7 +34,7 @@ class SettingsViewModel @Inject constructor(private val settings: SettingsAdapte this.theme = settings.theme } - fun updateScreenOn(value: Boolean) { + fun updateScreenOn(value: KeepScreenOn) { settings.setKeepScreenOn(value) screenOn = settings.keepScreenOn } From 9ca830a70768b8a7331e50a7eacfbddab9dcfb7d Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sun, 22 Jan 2023 18:36:38 +0100 Subject: [PATCH 07/26] [#11] ApplyDayNight should not be used. --- app/src/main/java/me/zobrist/tichucounter/MainActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 8dac229..20c51fc 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.core.os.LocaleListCompat +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController @@ -29,6 +30,7 @@ import androidx.navigation.compose.rememberNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.repository.GameRepository @@ -93,7 +95,6 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { if (themeValue != AppCompatDelegate.getDefaultNightMode()) { AppCompatDelegate.setDefaultNightMode(themeValue) - delegate.applyDayNight() } } From f40b66077ba7d013609cbbe11f0158cb6a78f139 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sun, 22 Jan 2023 20:45:45 +0100 Subject: [PATCH 08/26] [#11] Add a small delay before applying theme so compose has enough time to update remember states before the theme is applied. --- app/src/main/java/me/zobrist/tichucounter/MainActivity.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 20c51fc..4d30c65 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -94,7 +94,11 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } if (themeValue != AppCompatDelegate.getDefaultNightMode()) { - AppCompatDelegate.setDefaultNightMode(themeValue) + lifecycleScope.launch { + // Give compose a bit of time to update the state. + delay(50) + AppCompatDelegate.setDefaultNightMode(themeValue) + } } } From 8d24e46687455893a08818d3e0d49362514a20fa Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sun, 22 Jan 2023 21:40:47 +0100 Subject: [PATCH 09/26] Simplify database handling. --- .../1.json | 12 +-- .../2.json | 102 ++++++++++++++++++ .../zobrist/tichucounter/data/AppDatabase.kt | 2 + .../java/me/zobrist/tichucounter/data/Game.kt | 15 --- .../zobrist/tichucounter/data/GameAndScore.kt | 16 --- .../me/zobrist/tichucounter/data/GameDao.kt | 22 ++-- .../tichucounter/data/GameWithScores.kt | 17 +++ .../me/zobrist/tichucounter/data/IEntity.kt | 5 - .../me/zobrist/tichucounter/data/IGame.kt | 11 -- .../me/zobrist/tichucounter/data/IRound.kt | 7 -- .../me/zobrist/tichucounter/data/Round.kt | 12 --- .../me/zobrist/tichucounter/data/RoundDao.kt | 18 +--- .../zobrist/tichucounter/data/entity/Game.kt | 16 +++ .../tichucounter/data/entity/IEntity.kt | 5 + .../zobrist/tichucounter/data/entity/Round.kt | 13 +++ .../domain/GameWithScoresExtension.kt | 17 +++ .../tichucounter/repository/GameRepository.kt | 16 ++- .../zobrist/tichucounter/ui/MainViewModel.kt | 22 ++-- .../tichucounter/ui/counter/CounterView.kt | 2 +- .../ui/counter/CounterViewModel.kt | 35 +++--- .../tichucounter/ui/counter/RoundListView.kt | 2 +- .../tichucounter/ui/history/HistoryView.kt | 48 ++++++--- .../ui/history/HistoryViewModel.kt | 9 +- 23 files changed, 265 insertions(+), 159 deletions(-) create mode 100644 app/schemas/me.zobrist.tichucounter.data.AppDatabase/2.json delete mode 100644 app/src/main/java/me/zobrist/tichucounter/data/Game.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/data/GameAndScore.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/data/GameWithScores.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/data/IEntity.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/data/IGame.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/data/IRound.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/data/Round.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/data/entity/IEntity.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt diff --git a/app/schemas/me.zobrist.tichucounter.data.AppDatabase/1.json b/app/schemas/me.zobrist.tichucounter.data.AppDatabase/1.json index 992f833..324b84a 100644 --- a/app/schemas/me.zobrist.tichucounter.data.AppDatabase/1.json +++ b/app/schemas/me.zobrist.tichucounter.data.AppDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "f07e88c78e54c69c73890495a2121bf4", + "identityHash": "1739540cd7d5436941316932a1036d83", "entities": [ { "tableName": "Round", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`gameId` INTEGER NOT NULL, `scoreA` INTEGER NOT NULL, `scoreB` INTEGER NOT NULL, `uid` INTEGER PRIMARY KEY AUTOINCREMENT)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`gameId` INTEGER NOT NULL, `scoreA` INTEGER NOT NULL, `scoreB` INTEGER NOT NULL, `uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "gameId", @@ -30,7 +30,7 @@ "fieldPath": "uid", "columnName": "uid", "affinity": "INTEGER", - "notNull": false + "notNull": true } ], "primaryKey": { @@ -44,7 +44,7 @@ }, { "tableName": "Game", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`active` INTEGER NOT NULL, `nameA` TEXT NOT NULL, `nameB` TEXT NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `uid` INTEGER PRIMARY KEY AUTOINCREMENT)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`active` INTEGER NOT NULL, `nameA` TEXT NOT NULL, `nameB` TEXT NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "active", @@ -80,7 +80,7 @@ "fieldPath": "uid", "columnName": "uid", "affinity": "INTEGER", - "notNull": false + "notNull": true } ], "primaryKey": { @@ -96,7 +96,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f07e88c78e54c69c73890495a2121bf4')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1739540cd7d5436941316932a1036d83')" ] } } \ No newline at end of file diff --git a/app/schemas/me.zobrist.tichucounter.data.AppDatabase/2.json b/app/schemas/me.zobrist.tichucounter.data.AppDatabase/2.json new file mode 100644 index 0000000..914f81c --- /dev/null +++ b/app/schemas/me.zobrist.tichucounter.data.AppDatabase/2.json @@ -0,0 +1,102 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "1739540cd7d5436941316932a1036d83", + "entities": [ + { + "tableName": "Round", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`gameId` INTEGER NOT NULL, `scoreA` INTEGER NOT NULL, `scoreB` INTEGER NOT NULL, `uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "gameId", + "columnName": "gameId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scoreA", + "columnName": "scoreA", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scoreB", + "columnName": "scoreB", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Game", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`active` INTEGER NOT NULL, `nameA` TEXT NOT NULL, `nameB` TEXT NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "active", + "columnName": "active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nameA", + "columnName": "nameA", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "nameB", + "columnName": "nameB", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uid" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1739540cd7d5436941316932a1036d83')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/AppDatabase.kt b/app/src/main/java/me/zobrist/tichucounter/data/AppDatabase.kt index 431437d..06eca31 100644 --- a/app/src/main/java/me/zobrist/tichucounter/data/AppDatabase.kt +++ b/app/src/main/java/me/zobrist/tichucounter/data/AppDatabase.kt @@ -3,6 +3,8 @@ package me.zobrist.tichucounter.data import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import me.zobrist.tichucounter.data.entity.Game +import me.zobrist.tichucounter.data.entity.Round @Database(entities = [Round::class, Game::class], version = 1) @TypeConverters(DateConverter::class) diff --git a/app/src/main/java/me/zobrist/tichucounter/data/Game.kt b/app/src/main/java/me/zobrist/tichucounter/data/Game.kt deleted file mode 100644 index 94255ae..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/data/Game.kt +++ /dev/null @@ -1,15 +0,0 @@ -package me.zobrist.tichucounter.data - -import androidx.room.Entity -import androidx.room.PrimaryKey -import java.util.* - -@Entity -data class Game( - override var active: Boolean, - override var nameA: String, - override var nameB: String, - override val created: Date, - override var modified: Date, - @PrimaryKey(autoGenerate = true) override val uid: Long? = null -) : IGame, IEntity \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/GameAndScore.kt b/app/src/main/java/me/zobrist/tichucounter/data/GameAndScore.kt deleted file mode 100644 index 658ee50..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/data/GameAndScore.kt +++ /dev/null @@ -1,16 +0,0 @@ -package me.zobrist.tichucounter.data - -import androidx.room.Entity -import java.util.* - -@Entity -data class GameAndScore( - override var active: Boolean, - override var nameA: String, - override var nameB: String, - override val created: Date, - override var modified: Date, - override var gameId: Long, - override var scoreA: Int, - override var scoreB: Int, -) : IGame, IRound \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/GameDao.kt b/app/src/main/java/me/zobrist/tichucounter/data/GameDao.kt index 79d4519..00a9855 100644 --- a/app/src/main/java/me/zobrist/tichucounter/data/GameDao.kt +++ b/app/src/main/java/me/zobrist/tichucounter/data/GameDao.kt @@ -2,6 +2,7 @@ package me.zobrist.tichucounter.data import androidx.room.* import kotlinx.coroutines.flow.Flow +import me.zobrist.tichucounter.data.entity.Game @Dao @@ -10,19 +11,13 @@ interface GameDao : DaoBase { @Query("SELECT * FROM game") fun getAll(): Flow> - @Query( - "SELECT active, " + - "nameA, " + - "nameB, " + - "created, " + - "modified, " + - "game.uid as gameId, " + - "COALESCE(SUM(round.scoreA), 0) as scoreA, " + - "COALESCE(SUM(round.scoreB), 0) as scoreB " + - "FROM game " + - "LEFT JOIN round ON round.gameId = game.uid GROUP BY game.uid ORDER BY modified DESC" - ) - fun getAllWithPoints(): Flow> + @Transaction + @Query("SELECT * FROM game where uid ") + fun getGamesWithRounds(): Flow> + + @Transaction + @Query("SELECT * FROM game WHERE active is 1") + fun getActiveWithRounds(): Flow @Query("SELECT * FROM game WHERE uid is :gameId") fun getGameById(gameId: Long): Flow @@ -30,6 +25,7 @@ interface GameDao : DaoBase { @Query("SELECT * FROM game WHERE active is 1") fun getActive(): Flow + @Query("UPDATE game SET active = 1 WHERE uid is :gameId;") fun setActive(gameId: Long) diff --git a/app/src/main/java/me/zobrist/tichucounter/data/GameWithScores.kt b/app/src/main/java/me/zobrist/tichucounter/data/GameWithScores.kt new file mode 100644 index 0000000..44d2d54 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/data/GameWithScores.kt @@ -0,0 +1,17 @@ +package me.zobrist.tichucounter.data + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Relation +import me.zobrist.tichucounter.data.entity.Game +import me.zobrist.tichucounter.data.entity.Round + +@Entity +data class GameWithScores( + @Embedded val game: Game, + @Relation( + parentColumn = "uid", + entityColumn = "gameId" + ) + val rounds: List +) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/IEntity.kt b/app/src/main/java/me/zobrist/tichucounter/data/IEntity.kt deleted file mode 100644 index c01aff7..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/data/IEntity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package me.zobrist.tichucounter.data - -interface IEntity { - val uid: Long? -} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/IGame.kt b/app/src/main/java/me/zobrist/tichucounter/data/IGame.kt deleted file mode 100644 index 3afbdff..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/data/IGame.kt +++ /dev/null @@ -1,11 +0,0 @@ -package me.zobrist.tichucounter.data - -import java.util.* - -interface IGame { - var active: Boolean - var nameA: String - var nameB: String - val created: Date - var modified: Date -} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/IRound.kt b/app/src/main/java/me/zobrist/tichucounter/data/IRound.kt deleted file mode 100644 index 6b62039..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/data/IRound.kt +++ /dev/null @@ -1,7 +0,0 @@ -package me.zobrist.tichucounter.data - -interface IRound { - var gameId: Long - var scoreA: Int - var scoreB: Int -} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/Round.kt b/app/src/main/java/me/zobrist/tichucounter/data/Round.kt deleted file mode 100644 index 828fbf5..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/data/Round.kt +++ /dev/null @@ -1,12 +0,0 @@ -package me.zobrist.tichucounter.data - -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity -data class Round( - override var gameId: Long, - override var scoreA: Int, - override var scoreB: Int, - @PrimaryKey(autoGenerate = true) override val uid: Long? = null -) : IRound, IEntity \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/RoundDao.kt b/app/src/main/java/me/zobrist/tichucounter/data/RoundDao.kt index 4063e76..2da3a09 100644 --- a/app/src/main/java/me/zobrist/tichucounter/data/RoundDao.kt +++ b/app/src/main/java/me/zobrist/tichucounter/data/RoundDao.kt @@ -1,7 +1,7 @@ package me.zobrist.tichucounter.data import androidx.room.* -import kotlinx.coroutines.flow.Flow +import me.zobrist.tichucounter.data.entity.Round @Dao interface RoundDao : DaoBase { @@ -12,20 +12,4 @@ interface RoundDao : DaoBase { @Query("SELECT * FROM round WHERE gameId is :gameId") fun getAllForGame(gameId: Long?): List - @Query( - "SELECT gameId, SUM(scoreA) as scoreA, SUM(scoreB) as scoreB " + - "FROM round " + - "LEFT JOIN game ON game.uid = round.gameId " + - "WHERE game.active == 1" - ) - @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) - fun getRoundSumForActiveGame(): Flow - - @Query( - "SELECT gameId, scoreA, scoreB, round.uid " + - "FROM round " + - "LEFT JOIN game ON game.uid = round.gameId " + - "WHERE game.active == 1" - ) - fun getForActiveGame(): Flow> } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt b/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt new file mode 100644 index 0000000..96af3f6 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt @@ -0,0 +1,16 @@ +package me.zobrist.tichucounter.data.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import me.zobrist.tichucounter.data.entity.IEntity +import java.util.* + +@Entity +data class Game( + var active: Boolean, + var nameA: String, + var nameB: String, + val created: Date, + var modified: Date, + @PrimaryKey(autoGenerate = true) override val uid: Long = 0 +) : IEntity \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/entity/IEntity.kt b/app/src/main/java/me/zobrist/tichucounter/data/entity/IEntity.kt new file mode 100644 index 0000000..23b9c73 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/data/entity/IEntity.kt @@ -0,0 +1,5 @@ +package me.zobrist.tichucounter.data.entity + +interface IEntity { + val uid: Long +} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt b/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt new file mode 100644 index 0000000..036306b --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt @@ -0,0 +1,13 @@ +package me.zobrist.tichucounter.data.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import me.zobrist.tichucounter.data.entity.IEntity + +@Entity +data class Round( + var gameId: Long, + var scoreA: Int, + var scoreB: Int, + @PrimaryKey(autoGenerate = true) override val uid: Long = 0 +) : IEntity \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt b/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt new file mode 100644 index 0000000..b47834e --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt @@ -0,0 +1,17 @@ +package me.zobrist.tichucounter.domain + +import me.zobrist.tichucounter.data.GameWithScores + +class GameWithScoresExtension { +} + +fun GameWithScores.getTotalPoints(): Pair { + var scoreA = 0 + var scoreB = 0 + + this.rounds.forEach { + scoreA += it.scoreA + scoreB += it.scoreB + } + return Pair(scoreA, scoreB) +} \ 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 8ae41f6..3e76669 100644 --- a/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt +++ b/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt @@ -2,13 +2,13 @@ package me.zobrist.tichucounter.repository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.zobrist.tichucounter.data.Game -import me.zobrist.tichucounter.data.GameDao -import me.zobrist.tichucounter.data.Round -import me.zobrist.tichucounter.data.RoundDao +import me.zobrist.tichucounter.data.* +import me.zobrist.tichucounter.data.entity.Game +import me.zobrist.tichucounter.data.entity.Round import java.util.* import javax.inject.Inject @@ -116,4 +116,12 @@ class GameRepository @Inject constructor( } } } + + fun getActiveGameFlow(): Flow { + return gameDao.getActiveWithRounds() + } + + fun getAllWithRoundFlow(): Flow> { + return gameDao.getGamesWithRounds() + } } \ No newline at end of file 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 cc08b6b..0f88146 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt @@ -10,8 +10,7 @@ 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.data.entity.Round import me.zobrist.tichucounter.domain.NavigationAction import me.zobrist.tichucounter.domain.TopBarAction import me.zobrist.tichucounter.repository.GameRepository @@ -19,8 +18,7 @@ import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( - private val gameRepository: GameRepository, - roundDao: RoundDao + private val gameRepository: GameRepository ) : ViewModel() { private var redoRounds = mutableStateListOf() @@ -37,14 +35,18 @@ class MainViewModel @Inject constructor( init { viewModelScope.launch { - roundDao.getForActiveGame().collect { - isUndoActionActive = it.isNotEmpty() - if (expectedRoundCount != it.count()) { - redoRounds.clear() + gameRepository.getActiveGameFlow().collect { + + if (it != null) { + isUndoActionActive = it.rounds.isNotEmpty() + + if (expectedRoundCount != it.rounds.count()) { + redoRounds.clear() + } + + expectedRoundCount = it.rounds.count() } - - expectedRoundCount = it.count() } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt index 3a9ea6b..e4d394a 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.tooling.preview.Preview -import me.zobrist.tichucounter.data.Round +import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.ui.AppTheme diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt index 74351c2..3e927c1 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt @@ -8,10 +8,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import me.zobrist.tichucounter.data.GameDao -import me.zobrist.tichucounter.data.Round -import me.zobrist.tichucounter.data.RoundDao +import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.domain.Tichu +import me.zobrist.tichucounter.domain.getTotalPoints import me.zobrist.tichucounter.repository.GameRepository import javax.inject.Inject @@ -48,9 +47,7 @@ interface ICounterViewModel { @HiltViewModel class CounterViewModel @Inject constructor( - private val gameRepository: GameRepository, - private val roundDao: RoundDao, - private val gameDao: GameDao + private val gameRepository: GameRepository ) : ViewModel(), ICounterViewModel { @@ -112,24 +109,18 @@ class CounterViewModel @Inject constructor( init { viewModelScope.launch { - roundDao.getForActiveGame().collect { - roundScoreList = it - } - } - - viewModelScope.launch { - gameDao.getActive().collect { + gameRepository.getActiveGameFlow().collect { if (it != null) { - teamNameA = it.nameA - teamNameB = it.nameB - } - } - } - viewModelScope.launch { - roundDao.getRoundSumForActiveGame().collect { score -> - totalScoreA = score.scoreA - totalScoreB = score.scoreB + val score = it.getTotalPoints() + + roundScoreList = it.rounds + totalScoreA = score.first + totalScoreB = score.second + + teamNameA = it.game.nameA + teamNameB = it.game.nameB + } } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt index f2d5417..e9d3dfb 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/RoundListView.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch -import me.zobrist.tichucounter.data.Round +import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.ui.AppTheme @Composable diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt index 26bdd03..f059554 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt @@ -15,7 +15,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import me.zobrist.tichucounter.R -import me.zobrist.tichucounter.data.GameAndScore +import me.zobrist.tichucounter.data.entity.Game +import me.zobrist.tichucounter.data.GameWithScores +import me.zobrist.tichucounter.data.entity.Round +import me.zobrist.tichucounter.domain.getTotalPoints import java.text.DateFormat import java.util.* @@ -65,7 +68,7 @@ fun DeleteConfirmDialog(show: Boolean = true, onExecuted: (Boolean) -> Unit = {} @Composable fun HistoryList( - games: List, + games: List, onOpenClicked: (GameId: Long) -> Unit, onDeleteClicked: (GameId: Long) -> Unit ) { @@ -80,7 +83,7 @@ fun HistoryList( @Composable fun HistoryListItem( - game: GameAndScore, + game: GameWithScores, onOpenClicked: (GameId: Long) -> Unit, onDeleteClicked: (GameId: Long) -> Unit ) { @@ -88,13 +91,15 @@ fun HistoryListItem( DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()) - val cardColor = if (game.active) { + val cardColor = if (game.game.active) { CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) } else { CardDefaults.cardColors() } + val totalScores = game.getTotalPoints() + Card( modifier = Modifier .fillMaxWidth() @@ -107,18 +112,18 @@ fun HistoryListItem( ) { Column(Modifier.weight(4f)) { Text( - text = game.nameA + " vs " + game.nameB, + text = game.game.nameA + " vs " + game.game.nameB, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.headlineSmall ) Text( - text = game.scoreA.toString() + " : " + game.scoreB.toString(), + text = totalScores.first.toString() + " : " + totalScores.second.toString(), style = MaterialTheme.typography.bodyLarge ) Spacer(modifier = Modifier.padding(5.dp)) Text( - text = format.format(game.modified), + text = format.format(game.game.modified), style = MaterialTheme.typography.labelSmall ) } @@ -128,11 +133,11 @@ fun HistoryListItem( .width(70.dp) ) { - ElevatedButton(onClick = { onOpenClicked(game.gameId) }, enabled = true) { + ElevatedButton(onClick = { onOpenClicked(game.game.uid) }, enabled = true) { Icon(Icons.Outlined.OpenInFull, null) } ElevatedButton( - onClick = { onDeleteClicked(game.gameId) }, enabled = !game.active + onClick = { onDeleteClicked(game.game.uid) }, enabled = !game.game.active ) { Icon(Icons.Outlined.Delete, null) } @@ -145,11 +150,26 @@ fun HistoryListItem( @Composable private fun HistoryListPreview() { val tempData = listOf( - GameAndScore(true, "abc", "def", Date(), Date(), 1, 10, 50), - GameAndScore(false, "ADTH", "dogfg", Date(), Date(), 2, 20, 60), - GameAndScore(false, "TeamA3 langer Name", "TeamB3", Date(), Date(), 3, 30, 70), - GameAndScore(false, "TeamA4", "TeamB4", Date(), Date(), 4, 40, 80), - GameAndScore(false, "TeamA5", "TeamB5", Date(), Date(), 5, 50, 90) + GameWithScores( + Game(true, "abc", "def", Date(), Date()), + listOf(Round(1, 550, 500)) + ), + GameWithScores( + Game(false, "ADTH", "dogfg", Date(), Date()), + listOf(Round(2, 20, 60)) + ), + GameWithScores( + Game(false, "TeamA3 langer Name", "TeamB3", Date(), Date()), + listOf(Round(3, 30, 70)) + ), + GameWithScores( + Game(false, "TeamA4", "TeamB4", Date(), Date()), + listOf(Round(4, 40, 80)) + ), + GameWithScores( + Game(false, "TeamA5", "TeamB5", Date(), Date()), + listOf(Round(5, 50, 90)) + ) ) HistoryList(tempData, {}) {} } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt index affda48..55c9555 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt @@ -7,24 +7,23 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import me.zobrist.tichucounter.data.GameAndScore -import me.zobrist.tichucounter.data.GameDao +import me.zobrist.tichucounter.data.GameWithScores import me.zobrist.tichucounter.repository.GameRepository import javax.inject.Inject @HiltViewModel class HistoryViewModel @Inject constructor( - private val gameDao: GameDao, private val gameRepository: GameRepository ) : ViewModel() { - var gameAndHistory by mutableStateOf(emptyList()) + var gameAndHistory by mutableStateOf(emptyList()) private set init { viewModelScope.launch { - gameDao.getAllWithPoints().collect { games -> + + gameRepository.getAllWithRoundFlow().collect() { games -> gameAndHistory = games } } From c54f63736eef5676047a0f2adf711d2c73bad7a0 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sun, 22 Jan 2023 21:42:38 +0100 Subject: [PATCH 10/26] Enable drawer close gesture. --- app/src/main/java/me/zobrist/tichucounter/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 4d30c65..76a9189 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -251,7 +251,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { ModalNavigationDrawer( drawerState = drawerState, - gesturesEnabled = false, + gesturesEnabled = drawerState.isOpen, drawerContent = { ModalDrawerSheet { From ca88bd105449ee61031d207d730e74ad5f07beb1 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Mon, 23 Jan 2023 22:12:37 +0100 Subject: [PATCH 11/26] Move ui variable from viewModel to compose function. Move TopBar to separate file. --- .../me/zobrist/tichucounter/MainActivity.kt | 75 ++++++------------- .../zobrist/tichucounter/data/entity/Game.kt | 1 - .../zobrist/tichucounter/data/entity/Round.kt | 1 - .../tichucounter/repository/GameRepository.kt | 4 +- .../zobrist/tichucounter/ui/MainViewModel.kt | 13 ---- .../java/me/zobrist/tichucounter/ui/TopBar.kt | 44 +++++++++++ .../tichucounter/ui/history/HistoryView.kt | 2 +- 7 files changed, 70 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 76a9189..a6b1ad3 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier 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.core.os.LocaleListCompat import androidx.lifecycle.lifecycleScope @@ -36,6 +35,7 @@ import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.ui.AppTheme import me.zobrist.tichucounter.ui.MainViewModel +import me.zobrist.tichucounter.ui.TopBar import me.zobrist.tichucounter.ui.counter.* import me.zobrist.tichucounter.ui.history.HistoryList import me.zobrist.tichucounter.ui.history.HistoryViewModel @@ -120,6 +120,11 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { fabAction: () -> Unit ) { + var topBarTitle by remember { mutableStateOf("") } + var topBarIcon by remember { mutableStateOf(Icons.Filled.Menu) } + var topBarActions by remember { mutableStateOf(emptyList()) } + var topBarNavigationAction by remember { mutableStateOf(NavigationAction {}) } + Scaffold( floatingActionButton = { if (showFab) { @@ -131,10 +136,10 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { }, topBar = { TopBar( - mainViewModel.topBarTitle, - mainViewModel.topBarIcon, - { mainViewModel.onNavigateClicked() }, - mainViewModel.topBarActions + topBarTitle, + topBarIcon, + { topBarNavigationAction.action() }, + topBarActions ) }) { @@ -145,7 +150,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { ) { composable("counter") { Counter(counterViewModel) - mainViewModel.topBarActions = (listOf( + topBarActions = (listOf( TopBarAction( Icons.Outlined.Undo, mainViewModel.isUndoActionActive @@ -156,9 +161,9 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { ) { mainViewModel.redoLastRound() } )) - mainViewModel.topBarIcon = Icons.Outlined.Menu - mainViewModel.topBarTitle = stringResource(R.string.app_name) - mainViewModel.topBarNavigationAction = + topBarIcon = Icons.Outlined.Menu + topBarTitle = stringResource(R.string.app_name) + topBarNavigationAction = NavigationAction { scope.launch { drawerState.open() } } } composable("history") { @@ -171,7 +176,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } openDialog = false }) { navController.navigate("counter") } - mainViewModel.topBarActions = listOf( + topBarActions = listOf( TopBarAction( Icons.Outlined.DeleteForever, true @@ -179,59 +184,23 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { openDialog = true } ) - mainViewModel.topBarIcon = Icons.Outlined.Menu - mainViewModel.topBarTitle = stringResource(R.string.menu_history) - mainViewModel.topBarNavigationAction = + topBarIcon = Icons.Outlined.Menu + topBarTitle = stringResource(R.string.menu_history) + topBarNavigationAction = NavigationAction { scope.launch { drawerState.open() } } } composable("settings") { SettingsView(settingsViewModel) - mainViewModel.topBarActions = emptyList() - mainViewModel.topBarIcon = Icons.Outlined.Menu - mainViewModel.topBarTitle = stringResource(R.string.menu_settings) - mainViewModel.topBarNavigationAction = + topBarActions = emptyList() + topBarIcon = Icons.Outlined.Menu + topBarTitle = stringResource(R.string.menu_settings) + topBarNavigationAction = NavigationAction { scope.launch { drawerState.open() } } } } } } - @OptIn(ExperimentalMaterial3Api::class) - @Composable - private fun TopBar( - title: String, - icon: ImageVector, - navigateAction: () -> Unit, - actions: List - ) { - TopAppBar( - title = { - Text( - title, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - }, - navigationIcon = { - IconButton(onClick = { navigateAction() }) { - Icon( - imageVector = icon, - contentDescription = "Localized description" - ) - } - }, - actions = { - actions.forEach { - IconButton(onClick = { it.action() }, enabled = it.isActive) { - Icon( - imageVector = it.imageVector, - contentDescription = null, - ) - } - } - } - ) - } @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt b/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt index 96af3f6..e70a2de 100644 --- a/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt +++ b/app/src/main/java/me/zobrist/tichucounter/data/entity/Game.kt @@ -2,7 +2,6 @@ package me.zobrist.tichucounter.data.entity import androidx.room.Entity import androidx.room.PrimaryKey -import me.zobrist.tichucounter.data.entity.IEntity import java.util.* @Entity diff --git a/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt b/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt index 036306b..2e21660 100644 --- a/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt +++ b/app/src/main/java/me/zobrist/tichucounter/data/entity/Round.kt @@ -2,7 +2,6 @@ package me.zobrist.tichucounter.data.entity import androidx.room.Entity import androidx.room.PrimaryKey -import me.zobrist.tichucounter.data.entity.IEntity @Entity data class Round( 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 3e76669..e52893c 100644 --- a/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt +++ b/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt @@ -6,7 +6,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.zobrist.tichucounter.data.* +import me.zobrist.tichucounter.data.GameDao +import me.zobrist.tichucounter.data.GameWithScores +import me.zobrist.tichucounter.data.RoundDao import me.zobrist.tichucounter.data.entity.Game import me.zobrist.tichucounter.data.entity.Round import java.util.* 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 0f88146..14a5001 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt @@ -1,7 +1,5 @@ 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.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -11,8 +9,6 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import me.zobrist.tichucounter.data.entity.Round -import me.zobrist.tichucounter.domain.NavigationAction -import me.zobrist.tichucounter.domain.TopBarAction import me.zobrist.tichucounter.repository.GameRepository import javax.inject.Inject @@ -24,11 +20,7 @@ class MainViewModel @Inject constructor( 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() @@ -51,11 +43,6 @@ class MainViewModel @Inject constructor( } } - - fun onNavigateClicked() { - topBarNavigationAction.action() - } - fun undoLastRound() { viewModelScope.launch { val round = gameRepository.getLastRound() diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt b/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt new file mode 100644 index 0000000..2a8a473 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt @@ -0,0 +1,44 @@ +package me.zobrist.tichucounter.ui + +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextOverflow +import me.zobrist.tichucounter.domain.TopBarAction + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TopBar( + title: String, + icon: ImageVector, + navigateAction: () -> Unit, + actions: List +) { + TopAppBar( + title = { + Text( + title, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton(onClick = { navigateAction() }) { + Icon( + imageVector = icon, + contentDescription = "Localized description" + ) + } + }, + actions = { + actions.forEach { + IconButton(onClick = { it.action() }, enabled = it.isActive) { + Icon( + imageVector = it.imageVector, + contentDescription = null, + ) + } + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt index f059554..14dc030 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt @@ -15,8 +15,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import me.zobrist.tichucounter.R -import me.zobrist.tichucounter.data.entity.Game import me.zobrist.tichucounter.data.GameWithScores +import me.zobrist.tichucounter.data.entity.Game import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.domain.getTotalPoints import java.text.DateFormat From 4346af3d2b11dc911b831c516f779f07873df61c Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 12:03:36 +0100 Subject: [PATCH 12/26] Improve settings composable. Remove delay before setting the theme. as this did not help [#11] --- .../me/zobrist/tichucounter/MainActivity.kt | 8 +-- .../tichucounter/ui/settings/SettingsView.kt | 68 ++++++++++++++----- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index a6b1ad3..3d62e81 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.os.LocaleListCompat -import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController @@ -29,7 +28,6 @@ import androidx.navigation.compose.rememberNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.repository.GameRepository @@ -94,11 +92,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } if (themeValue != AppCompatDelegate.getDefaultNightMode()) { - lifecycleScope.launch { - // Give compose a bit of time to update the state. - delay(50) - AppCompatDelegate.setDefaultNightMode(themeValue) - } + AppCompatDelegate.setDefaultNightMode(themeValue) } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt index bb02db3..28d5d3e 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt @@ -119,7 +119,12 @@ fun StringSetting(name: String, map: Map, selected: T, onSelected: ( .clickable { expanded = true }) { Column(Modifier.weight(5f)) { Text(name, style = MaterialTheme.typography.bodyLarge, overflow = TextOverflow.Ellipsis) - Text(stringResource(map[selected]!!), style = MaterialTheme.typography.labelLarge) + map[selected]?.let { + Text( + stringResource(it), + style = MaterialTheme.typography.labelLarge + ) + } } Column(Modifier.weight(1f)) { @@ -130,23 +135,35 @@ fun StringSetting(name: String, map: Map, selected: T, onSelected: ( ) } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } + DropDownMenu( + map, + selected, + expanded, ) { - map.forEach { - DropdownMenuItem( - onClick = { - expanded = false - onSelected(it.key) - }, - text = { Text(stringResource(it.value)) }, - trailingIcon = { - if (it.key == selected) { - Icon(Icons.Outlined.Check, contentDescription = null) - } - }) - } + expanded = false + it?.let { onSelected(it) } + } + } +} + +@Composable +fun DropDownMenu(map: Map, selected: T, expanded: Boolean, onSelected: (T?) -> Unit) { + DropdownMenu( + expanded = expanded, + onDismissRequest = { onSelected(null) } + ) { + map.forEach { + DropdownMenuItem( + onClick = { + onSelected(it.key) + }, + trailingIcon = { + if (it.key == selected) { + Icon(Icons.Outlined.Check, null) + } + }, + text = { Text(stringResource(it.value)) }, + ) } } } @@ -162,3 +179,20 @@ fun SettingsViewPreview() { } } } + +@Preview(name = "Light Mode") +@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +fun StringSettingPreview() { + + AppTheme { + Surface { + DropDownMenu( + themeMap, + Theme.LIGHT, + true, + ) {} + } + } +} + From f52bfa64ce12f54db33a1de2e2eb863f8ccfa060 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 12:36:49 +0100 Subject: [PATCH 13/26] [#18] Add a swap score button. closes #18 --- .../tichucounter/ui/counter/CounterView.kt | 9 ++++++-- .../ui/counter/CounterViewModel.kt | 7 ++++++ .../tichucounter/ui/counter/KeyboardView.kt | 22 +++++++++++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt index e4d394a..eb8bf35 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt @@ -63,7 +63,8 @@ fun Landscape(viewModel: ICounterViewModel) { { viewModel.deleteClicked() }, { viewModel.negateClicked() }, { viewModel.submitClicked() }, - { viewModel.keyboardHidden = true }) + { viewModel.keyboardHidden = true }, + { viewModel.swapInputScores() }) } } } @@ -102,7 +103,8 @@ fun Portrait(viewModel: ICounterViewModel) { { viewModel.deleteClicked() }, { viewModel.negateClicked() }, { viewModel.submitClicked() }, - { viewModel.keyboardHidden = true }) + { viewModel.keyboardHidden = true }, + { viewModel.swapInputScores() }) } } } @@ -174,4 +176,7 @@ internal class PreviewViewModel : ICounterViewModel { override fun updateFocusStateB(state: Boolean) { } + override fun swapInputScores() { + } + } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt index 3e927c1..c16a86d 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt @@ -43,6 +43,7 @@ interface ICounterViewModel { fun updateNameB(value: String) fun updateFocusStateA(state: Boolean) fun updateFocusStateB(state: Boolean) + fun swapInputScores() } @HiltViewModel @@ -235,4 +236,10 @@ class CounterViewModel @Inject constructor( override fun updateFocusStateB(state: Boolean) { isBFocused = state } + + override fun swapInputScores() { + val swap = currentScoreA + currentScoreA = currentScoreB + currentScoreB = swap + } } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt index 28340c0..a42b224 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Backspace import androidx.compose.material.icons.outlined.Check import androidx.compose.material.icons.outlined.KeyboardHide +import androidx.compose.material.icons.outlined.SwapHoriz import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi @@ -36,12 +37,14 @@ fun KeyboardView( deleteClicked: () -> Unit, negateClicked: () -> Unit, submitClicked: () -> Unit, - hideKeyboardClicked: () -> Unit + hideKeyboardClicked: () -> Unit, + onSwapClicked: () -> Unit ) { val keyboardController = LocalSoftwareKeyboardController.current + Column { - Row { + Row(Modifier.height(IntrinsicSize.Max)) { Column(Modifier.weight(1f)) { CenteredTextField( scoreA, @@ -55,6 +58,19 @@ fun KeyboardView( ) } + Surface( + Modifier + .wrapContentWidth() + .fillMaxHeight(), + tonalElevation = 3.dp, + shape = MaterialTheme.shapes.extraSmall + ) { + Column { + IconButton(onClick = onSwapClicked) { + Icon(Icons.Outlined.SwapHoriz, null) + } + } + } Column(Modifier.weight(1f)) { CenteredTextField( @@ -69,6 +85,7 @@ fun KeyboardView( } } + Row { Column(Modifier.weight(1f)) { KeyboardTextButton("1") { @@ -246,6 +263,7 @@ fun KeyboardViewPreview() { {}, {}, {}, + {}, {}) } } From 89f2e3ecd5126c1f6a5dfad16f58807c90246df9 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 13:11:36 +0100 Subject: [PATCH 14/26] Improve default color scheme. --- .../java/me/zobrist/tichucounter/ui/Color.kt | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt b/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt index b073fd2..ecc2897 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt @@ -1,21 +1,19 @@ -@file:Suppress("unused", "unused", "unused") - package me.zobrist.tichucounter.ui import androidx.compose.ui.graphics.Color -val md_theme_light_primary = Color(0xFFBE0034) +val md_theme_light_primary = Color(0xFF9C404D) val md_theme_light_onPrimary = Color(0xFFFFFFFF) -val md_theme_light_primaryContainer = Color(0xFFFFDADA) -val md_theme_light_onPrimaryContainer = Color(0xFF40000B) -val md_theme_light_secondary = Color(0xFF6E5D00) +val md_theme_light_primaryContainer = Color(0xFFFFDADB) +val md_theme_light_onPrimaryContainer = Color(0xFF40000F) +val md_theme_light_secondary = Color(0xFF765659) val md_theme_light_onSecondary = Color(0xFFFFFFFF) -val md_theme_light_secondaryContainer = Color(0xFFFFE261) -val md_theme_light_onSecondaryContainer = Color(0xFF221B00) -val md_theme_light_tertiary = Color(0xFF76592F) +val md_theme_light_secondaryContainer = Color(0xFFFFDADB) +val md_theme_light_onSecondaryContainer = Color(0xFF2C1517) +val md_theme_light_tertiary = Color(0xFF775930) val md_theme_light_onTertiary = Color(0xFFFFFFFF) -val md_theme_light_tertiaryContainer = Color(0xFFFFDDB1) -val md_theme_light_onTertiaryContainer = Color(0xFF291800) +val md_theme_light_tertiaryContainer = Color(0xFFFFDDB5) +val md_theme_light_onTertiaryContainer = Color(0xFF2A1800) val md_theme_light_error = Color(0xFFBA1A1A) val md_theme_light_errorContainer = Color(0xFFFFDAD6) val md_theme_light_onError = Color(0xFFFFFFFF) @@ -24,29 +22,29 @@ val md_theme_light_background = Color(0xFFFFFBFF) val md_theme_light_onBackground = Color(0xFF201A1A) val md_theme_light_surface = Color(0xFFFFFBFF) val md_theme_light_onSurface = Color(0xFF201A1A) -val md_theme_light_surfaceVariant = Color(0xFFF4DDDD) -val md_theme_light_onSurfaceVariant = Color(0xFF524343) -val md_theme_light_outline = Color(0xFF857373) -val md_theme_light_inverseOnSurface = Color(0xFFFBEEED) +val md_theme_light_surfaceVariant = Color(0xFFF4DDDE) +val md_theme_light_onSurfaceVariant = Color(0xFF524344) +val md_theme_light_outline = Color(0xFF857374) +val md_theme_light_inverseOnSurface = Color(0xFFFBEEEE) val md_theme_light_inverseSurface = Color(0xFF362F2F) -val md_theme_light_inversePrimary = Color(0xFFFFB3B5) +val md_theme_light_inversePrimary = Color(0xFFFFB2B9) val md_theme_light_shadow = Color(0xFF000000) -val md_theme_light_surfaceTint = Color(0xFFBE0034) -val md_theme_light_outlineVariant = Color(0xFFD7C1C1) +val md_theme_light_surfaceTint = Color(0xFF9C404D) +val md_theme_light_outlineVariant = Color(0xFFD7C1C2) val md_theme_light_scrim = Color(0xFF000000) -val md_theme_dark_primary = Color(0xFFFFB3B5) -val md_theme_dark_onPrimary = Color(0xFF680018) -val md_theme_dark_primaryContainer = Color(0xFF920026) -val md_theme_dark_onPrimaryContainer = Color(0xFFFFDADA) -val md_theme_dark_secondary = Color(0xFFE6C500) -val md_theme_dark_onSecondary = Color(0xFF3A3000) -val md_theme_dark_secondaryContainer = Color(0xFF534600) -val md_theme_dark_onSecondaryContainer = Color(0xFFFFE261) -val md_theme_dark_tertiary = Color(0xFFE6C18D) -val md_theme_dark_onTertiary = Color(0xFF422C05) -val md_theme_dark_tertiaryContainer = Color(0xFF5C421A) -val md_theme_dark_onTertiaryContainer = Color(0xFFFFDDB1) +val md_theme_dark_primary = Color(0xFFFFB2B9) +val md_theme_dark_onPrimary = Color(0xFF5F1222) +val md_theme_dark_primaryContainer = Color(0xFF7D2937) +val md_theme_dark_onPrimaryContainer = Color(0xFFFFDADB) +val md_theme_dark_secondary = Color(0xFFE5BDBF) +val md_theme_dark_onSecondary = Color(0xFF44292C) +val md_theme_dark_secondaryContainer = Color(0xFF5C3F41) +val md_theme_dark_onSecondaryContainer = Color(0xFFFFDADB) +val md_theme_dark_tertiary = Color(0xFFE8C08E) +val md_theme_dark_onTertiary = Color(0xFF442B06) +val md_theme_dark_tertiaryContainer = Color(0xFF5D411B) +val md_theme_dark_onTertiaryContainer = Color(0xFFFFDDB5) val md_theme_dark_error = Color(0xFFFFB4AB) val md_theme_dark_errorContainer = Color(0xFF93000A) val md_theme_dark_onError = Color(0xFF690005) @@ -55,16 +53,16 @@ val md_theme_dark_background = Color(0xFF201A1A) val md_theme_dark_onBackground = Color(0xFFECE0DF) val md_theme_dark_surface = Color(0xFF201A1A) val md_theme_dark_onSurface = Color(0xFFECE0DF) -val md_theme_dark_surfaceVariant = Color(0xFF524343) -val md_theme_dark_onSurfaceVariant = Color(0xFFD7C1C1) -val md_theme_dark_outline = Color(0xFF9F8C8C) +val md_theme_dark_surfaceVariant = Color(0xFF524344) +val md_theme_dark_onSurfaceVariant = Color(0xFFD7C1C2) +val md_theme_dark_outline = Color(0xFF9F8C8D) val md_theme_dark_inverseOnSurface = Color(0xFF201A1A) val md_theme_dark_inverseSurface = Color(0xFFECE0DF) -val md_theme_dark_inversePrimary = Color(0xFFBE0034) +val md_theme_dark_inversePrimary = Color(0xFF9C404D) val md_theme_dark_shadow = Color(0xFF000000) -val md_theme_dark_surfaceTint = Color(0xFFFFB3B5) -val md_theme_dark_outlineVariant = Color(0xFF524343) +val md_theme_dark_surfaceTint = Color(0xFFFFB2B9) +val md_theme_dark_outlineVariant = Color(0xFF524344) val md_theme_dark_scrim = Color(0xFF000000) -val seed = Color(0xFFED0043) +val seed = Color(0xFF833842) From c71b608a7b9533c44ef6fa682d0fe22f7a3b1873 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 13:18:00 +0100 Subject: [PATCH 15/26] [#13] Hide keyboard in landscape. closes #13 --- .../tichucounter/ui/counter/CounterView.kt | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt index eb8bf35..04fc179 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt @@ -49,22 +49,23 @@ fun Landscape(viewModel: ICounterViewModel) { Modifier.weight(1f) ) } - - Column(Modifier.weight(1f)) { - KeyboardView( - viewModel.currentScoreA, - viewModel.currentScoreB, - viewModel.requestFocusA, - viewModel.enableSubmit, - { viewModel.updateFocusStateA(it) }, - { viewModel.updateFocusStateB(it) }, - { viewModel.digitClicked(it) }, - { viewModel.addSub100Clicked(it) }, - { viewModel.deleteClicked() }, - { viewModel.negateClicked() }, - { viewModel.submitClicked() }, - { viewModel.keyboardHidden = true }, - { viewModel.swapInputScores() }) + if (!viewModel.keyboardHidden) { + Column(Modifier.weight(1f)) { + KeyboardView( + viewModel.currentScoreA, + viewModel.currentScoreB, + viewModel.requestFocusA, + viewModel.enableSubmit, + { viewModel.updateFocusStateA(it) }, + { viewModel.updateFocusStateB(it) }, + { viewModel.digitClicked(it) }, + { viewModel.addSub100Clicked(it) }, + { viewModel.deleteClicked() }, + { viewModel.negateClicked() }, + { viewModel.submitClicked() }, + { viewModel.keyboardHidden = true }, + { viewModel.swapInputScores() }) + } } } } From 58d4fc0e436364aced11e03e824d4bdc9f9b869b Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 18:07:16 +0100 Subject: [PATCH 16/26] Show blinking cursor in keyboard view. Give focus back to last focused input. closes #12 --- .../me/zobrist/tichucounter/MainActivity.kt | 2 +- .../tichucounter/ui/counter/CounterView.kt | 39 ++--- .../ui/counter/CounterViewModel.kt | 92 ++++++++--- .../tichucounter/ui/counter/KeyboardView.kt | 156 ++++++++++++------ 4 files changed, 187 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 3d62e81..9b1b0e3 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -269,7 +269,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { scope, navController, counterViewModel.keyboardHidden && (currentDestination?.hierarchy?.any { it.route == "counter" } == true) - ) { counterViewModel.keyboardHidden = false } + ) { counterViewModel.showKeyboard() } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt index 04fc179..1113d76 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterView.kt @@ -51,20 +51,7 @@ fun Landscape(viewModel: ICounterViewModel) { } if (!viewModel.keyboardHidden) { Column(Modifier.weight(1f)) { - KeyboardView( - viewModel.currentScoreA, - viewModel.currentScoreB, - viewModel.requestFocusA, - viewModel.enableSubmit, - { viewModel.updateFocusStateA(it) }, - { viewModel.updateFocusStateB(it) }, - { viewModel.digitClicked(it) }, - { viewModel.addSub100Clicked(it) }, - { viewModel.deleteClicked() }, - { viewModel.negateClicked() }, - { viewModel.submitClicked() }, - { viewModel.keyboardHidden = true }, - { viewModel.swapInputScores() }) + KeyBoardView(viewModel = viewModel) } } } @@ -92,20 +79,7 @@ fun Portrait(viewModel: ICounterViewModel) { ) if (!viewModel.keyboardHidden) { - KeyboardView( - viewModel.currentScoreA, - viewModel.currentScoreB, - viewModel.requestFocusA, - viewModel.enableSubmit, - { viewModel.updateFocusStateA(it) }, - { viewModel.updateFocusStateB(it) }, - { viewModel.digitClicked(it) }, - { viewModel.addSub100Clicked(it) }, - { viewModel.deleteClicked() }, - { viewModel.negateClicked() }, - { viewModel.submitClicked() }, - { viewModel.keyboardHidden = true }, - { viewModel.swapInputScores() }) + KeyBoardView(viewModel = viewModel) } } } @@ -133,11 +107,12 @@ internal class PreviewViewModel : ICounterViewModel { override var isAFocused: Boolean = false override var isBFocused: Boolean = false override var requestFocusA: FocusRequester = FocusRequester() + override var requestFocusB: FocusRequester = FocusRequester() override var activeValue: String = currentScoreA override var inactiveValue: String = currentScoreB override var keyboardHidden: Boolean = false - override fun giveFocusToAIfNone() { + override fun focusLastInput() { } override fun updateOtherScore() { @@ -180,4 +155,10 @@ internal class PreviewViewModel : ICounterViewModel { override fun swapInputScores() { } + override fun hideKeyboard() { + } + + override fun showKeyboard() { + } + } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt index c16a86d..cc8d3d1 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt @@ -14,23 +14,22 @@ import me.zobrist.tichucounter.domain.getTotalPoints import me.zobrist.tichucounter.repository.GameRepository import javax.inject.Inject -interface ICounterViewModel { - var roundScoreList: List - var totalScoreA: Int - var totalScoreB: Int - var teamNameA: String - var teamNameB: String - var currentScoreA: String - var currentScoreB: String - var enableSubmit: Boolean - var isAFocused: Boolean - var isBFocused: Boolean - var requestFocusA: FocusRequester - var activeValue: String - var inactiveValue: String - var keyboardHidden: Boolean +private enum class Focused { TEAM_A, TEAM_B } - fun giveFocusToAIfNone() +interface IKeyBoardViewModel { + + val currentScoreA: String + val currentScoreB: String + val enableSubmit: Boolean + val isAFocused: Boolean + val isBFocused: Boolean + val requestFocusA: FocusRequester + val requestFocusB: FocusRequester + val activeValue: String + val inactiveValue: String + val keyboardHidden: Boolean + + fun focusLastInput() fun updateOtherScore() fun isValidTichuRound(): Boolean fun updateSubmitButton() @@ -39,11 +38,23 @@ interface ICounterViewModel { fun negateClicked() fun addSub100Clicked(toAdd: Int) fun deleteClicked() - fun updateNameA(value: String) - fun updateNameB(value: String) fun updateFocusStateA(state: Boolean) fun updateFocusStateB(state: Boolean) fun swapInputScores() + fun hideKeyboard() + fun showKeyboard() + +} + +interface ICounterViewModel: IKeyBoardViewModel { + val roundScoreList: List + val totalScoreA: Int + val totalScoreB: Int + val teamNameA: String + val teamNameB: String + + fun updateNameA(value: String) + fun updateNameB(value: String) } @HiltViewModel @@ -53,28 +64,43 @@ class CounterViewModel @Inject constructor( ViewModel(), ICounterViewModel { override var roundScoreList by mutableStateOf(emptyList()) + private set override var totalScoreA by mutableStateOf(0) + private set override var totalScoreB by mutableStateOf(0) + private set override var teamNameA by mutableStateOf("") + private set override var teamNameB by mutableStateOf("") + private set override var currentScoreA by mutableStateOf("") + private set override var currentScoreB by mutableStateOf("") + private set override var enableSubmit by mutableStateOf(false) + private set override var isAFocused by mutableStateOf(false) + private set override var isBFocused by mutableStateOf(false) + private set override var requestFocusA by mutableStateOf(FocusRequester()) + private set + + override var requestFocusB by mutableStateOf(FocusRequester()) + private set override var keyboardHidden by mutableStateOf(false) + private set override var activeValue: String get() { @@ -108,6 +134,9 @@ class CounterViewModel @Inject constructor( } } + + private var lastFocused = Focused.TEAM_A + init { viewModelScope.launch { gameRepository.getActiveGameFlow().collect { @@ -126,9 +155,10 @@ class CounterViewModel @Inject constructor( } } - override fun giveFocusToAIfNone() { - if (!isAFocused && !isBFocused) { - requestFocusA.requestFocus() + override fun focusLastInput() { + when(lastFocused){ + Focused.TEAM_A -> if(!isAFocused) requestFocusA.requestFocus() + Focused.TEAM_B -> if(!isBFocused) requestFocusB.requestFocus() } } @@ -170,7 +200,7 @@ class CounterViewModel @Inject constructor( } override fun digitClicked(digit: String) { - giveFocusToAIfNone() + focusLastInput() activeValue += digit updateOtherScore() @@ -178,7 +208,7 @@ class CounterViewModel @Inject constructor( } override fun negateClicked() { - giveFocusToAIfNone() + focusLastInput() activeValue = if (activeValue.contains("-")) { activeValue.replace("-", "") @@ -190,7 +220,7 @@ class CounterViewModel @Inject constructor( } override fun addSub100Clicked(toAdd: Int) { - giveFocusToAIfNone() + focusLastInput() activeValue = try { val temp = activeValue.toInt() + toAdd @@ -231,10 +261,16 @@ class CounterViewModel @Inject constructor( override fun updateFocusStateA(state: Boolean) { isAFocused = state + if(state){ + lastFocused = Focused.TEAM_A + } } override fun updateFocusStateB(state: Boolean) { isBFocused = state + if(state){ + lastFocused = Focused.TEAM_B + } } override fun swapInputScores() { @@ -242,4 +278,12 @@ class CounterViewModel @Inject constructor( currentScoreA = currentScoreB currentScoreB = swap } + + override fun hideKeyboard() { + keyboardHidden = true + } + + override fun showKeyboard() { + keyboardHidden = false + } } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt index a42b224..600c62d 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt @@ -1,6 +1,7 @@ package me.zobrist.tichucounter.ui.counter import android.content.res.Configuration +import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Backspace @@ -8,28 +9,51 @@ import androidx.compose.material.icons.outlined.Check import androidx.compose.material.icons.outlined.KeyboardHide import androidx.compose.material.icons.outlined.SwapHoriz import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.FocusState import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import me.zobrist.tichucounter.ui.AppTheme +@Composable +fun KeyBoardView(viewModel: IKeyBoardViewModel) { + KeyboardView( + viewModel.currentScoreA, + viewModel.currentScoreB, + viewModel.requestFocusA, + viewModel.requestFocusB, + viewModel.enableSubmit, + viewModel.isAFocused, + viewModel.isBFocused, + { viewModel.updateFocusStateA(it) }, + { viewModel.updateFocusStateB(it) }, + { viewModel.digitClicked(it) }, + { viewModel.addSub100Clicked(it) }, + { viewModel.deleteClicked() }, + { viewModel.negateClicked() }, + { viewModel.submitClicked() }, + { viewModel.hideKeyboard() }, + { viewModel.swapInputScores() } + ) +} -@OptIn(ExperimentalComposeUiApi::class) @Composable fun KeyboardView( scoreA: String, scoreB: String, - requestFocus: FocusRequester, + requestFocusA: FocusRequester, + requestFocusB: FocusRequester, enableSubmit: Boolean, + focusStateA: Boolean, + focusStateB: Boolean, updateFocusStateA: (Boolean) -> Unit, updateFocusStateB: (Boolean) -> Unit, digitClicked: (String) -> Unit, @@ -40,22 +64,15 @@ fun KeyboardView( hideKeyboardClicked: () -> Unit, onSwapClicked: () -> Unit ) { - val keyboardController = LocalSoftwareKeyboardController.current - - Column { Row(Modifier.height(IntrinsicSize.Max)) { Column(Modifier.weight(1f)) { CenteredTextField( scoreA, "0", - Modifier - .focusRequester(requestFocus) - .onFocusChanged { - keyboardController?.hide() - updateFocusStateA(it.isFocused) - } - ) + focusStateA, + requestFocusA + ) { updateFocusStateA(it.isFocused) } } Surface( @@ -76,12 +93,11 @@ fun KeyboardView( CenteredTextField( scoreB, "0", - Modifier - .onFocusChanged { - keyboardController?.hide() - updateFocusStateB(it.isFocused) - } - ) + focusStateB, + requestFocusB + ) { + updateFocusStateB(it.isFocused) + } } } @@ -225,23 +241,64 @@ fun KeyboardIconButton(icon: ImageVector, enabled: Boolean = true, onClicked: () fun CenteredTextField( value: String, placeholder: String, - modifier: Modifier, + focused: Boolean, + focusRequester: FocusRequester? = null, + onFocusStateChanged: (FocusState) -> Unit ) { - TextField( - value = value, - onValueChange = { }, - placeholder = { - Text( - placeholder, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + + val modifier = if(focusRequester != null) { + Modifier.focusRequester(focusRequester) + } else + { + Modifier + } + + Box(contentAlignment = Alignment.Center) { + TextField( + value = value, + onValueChange = { }, + placeholder = { + if(!focused){ + Text( + placeholder, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + }, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + singleLine = true, + readOnly = true, + modifier = modifier + .fillMaxWidth() + .onFocusChanged { + onFocusStateChanged(it) + } + ) + if(focused) + { + val cursorColor = MaterialTheme.colorScheme.onSurface + val infiniteTransition = rememberInfiniteTransition() + val alpha by infiniteTransition.animateFloat( + 0f, + cursorColor.alpha, + animationSpec = infiniteRepeatable( + animation = tween(500), + repeatMode = RepeatMode.Reverse + ) ) - }, - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - singleLine = true, - readOnly = true, - modifier = modifier.fillMaxWidth() - ) + Row { + + Text(text = value, color = cursorColor.copy(alpha = 0f)) + Divider( + modifier = Modifier + .padding(start = 3.dp, top = 15.dp, bottom = 15.dp) + .width(1.dp) + .fillMaxHeight(), + color = cursorColor.copy(alpha = alpha)) + } + } + } } @@ -252,19 +309,22 @@ fun KeyboardViewPreview() { AppTheme { Surface { KeyboardView( - "", - "350", + "1", + "3511", FocusRequester(), - false, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}) + FocusRequester(), + enableSubmit = false, + focusStateA = true, + focusStateB = false, + updateFocusStateA = {}, + updateFocusStateB = {}, + digitClicked = {}, + addSub100Clicked = {}, + deleteClicked = {}, + negateClicked = {}, + submitClicked = {}, + hideKeyboardClicked = {}, + onSwapClicked = {}) } } } \ No newline at end of file From cd8f1959af00fed585291c6ebb7d512fb4fa655a Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 20:13:14 +0100 Subject: [PATCH 17/26] Simplify history page. Fix warnings. --- .../domain/GameWithScoresExtension.kt | 4 -- .../tichucounter/repository/GameRepository.kt | 6 +-- .../java/me/zobrist/tichucounter/ui/Color.kt | 2 + .../ui/composables/DropDownMenu.kt | 32 ++++++++++++++ .../ui/counter/CounterViewModel.kt | 12 +++--- .../tichucounter/ui/counter/KeyboardView.kt | 13 +++--- .../tichucounter/ui/history/HistoryView.kt | 43 +++++++++++++------ .../ui/history/HistoryViewModel.kt | 5 ++- .../tichucounter/ui/settings/SettingsView.kt | 23 +--------- app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 11 files changed, 86 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/me/zobrist/tichucounter/ui/composables/DropDownMenu.kt diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt b/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt index b47834e..46154e7 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt @@ -1,10 +1,6 @@ package me.zobrist.tichucounter.domain import me.zobrist.tichucounter.data.GameWithScores - -class GameWithScoresExtension { -} - fun GameWithScores.getTotalPoints(): Pair { var scoreA = 0 var scoreB = 0 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 e52893c..5716266 100644 --- a/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt +++ b/app/src/main/java/me/zobrist/tichucounter/repository/GameRepository.kt @@ -83,7 +83,7 @@ class GameRepository @Inject constructor( withContext(Dispatchers.IO) { val active = activeGame active.modified = Date() - val round = Round(active.uid!!, scoreA, scoreB) + val round = Round(active.uid, scoreA, scoreB) roundDao.insert(round) gameDao.update(active) } @@ -92,7 +92,7 @@ class GameRepository @Inject constructor( suspend fun deleteGame(uid: Long) { withContext(Dispatchers.IO) { try { - gameDao.getGameById(uid).take(1).collect() { + gameDao.getGameById(uid).take(1).collect { gameDao.delete(it) val rounds = roundDao.getAllForGame(it.uid) roundDao.delete(rounds) @@ -105,7 +105,7 @@ class GameRepository @Inject constructor( suspend fun deleteAllInactive() { withContext(Dispatchers.IO) { try { - gameDao.getAll().take(1).collect() { games -> + gameDao.getAll().take(1).collect { games -> val activeId = games.first { it.active }.uid val gamesToDelete = games.filter { !it.active } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt b/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt index ecc2897..4dea1c7 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/Color.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package me.zobrist.tichucounter.ui import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/composables/DropDownMenu.kt b/app/src/main/java/me/zobrist/tichucounter/ui/composables/DropDownMenu.kt new file mode 100644 index 0000000..12a9700 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/ui/composables/DropDownMenu.kt @@ -0,0 +1,32 @@ +package me.zobrist.tichucounter.ui.composables + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource + +@Composable +fun DropDownMenu(map: Map, selected: T, expanded: Boolean, onSelected: (T?) -> Unit) { + DropdownMenu( + expanded = expanded, + onDismissRequest = { onSelected(null) } + ) { + map.forEach { + DropdownMenuItem( + onClick = { + onSelected(it.key) + }, + trailingIcon = { + if (it.key == selected) { + Icon(Icons.Outlined.Check, null) + } + }, + text = { Text(stringResource(it.value)) }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt index cc8d3d1..e6ae019 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/CounterViewModel.kt @@ -46,7 +46,7 @@ interface IKeyBoardViewModel { } -interface ICounterViewModel: IKeyBoardViewModel { +interface ICounterViewModel : IKeyBoardViewModel { val roundScoreList: List val totalScoreA: Int val totalScoreB: Int @@ -156,9 +156,9 @@ class CounterViewModel @Inject constructor( } override fun focusLastInput() { - when(lastFocused){ - Focused.TEAM_A -> if(!isAFocused) requestFocusA.requestFocus() - Focused.TEAM_B -> if(!isBFocused) requestFocusB.requestFocus() + when (lastFocused) { + Focused.TEAM_A -> if (!isAFocused) requestFocusA.requestFocus() + Focused.TEAM_B -> if (!isBFocused) requestFocusB.requestFocus() } } @@ -261,14 +261,14 @@ class CounterViewModel @Inject constructor( override fun updateFocusStateA(state: Boolean) { isAFocused = state - if(state){ + if (state) { lastFocused = Focused.TEAM_A } } override fun updateFocusStateB(state: Boolean) { isBFocused = state - if(state){ + if (state) { lastFocused = Focused.TEAM_B } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt index 600c62d..273cd8b 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/KeyboardView.kt @@ -246,10 +246,9 @@ fun CenteredTextField( onFocusStateChanged: (FocusState) -> Unit ) { - val modifier = if(focusRequester != null) { + val modifier = if (focusRequester != null) { Modifier.focusRequester(focusRequester) - } else - { + } else { Modifier } @@ -258,7 +257,7 @@ fun CenteredTextField( value = value, onValueChange = { }, placeholder = { - if(!focused){ + if (!focused) { Text( placeholder, textAlign = TextAlign.Center, @@ -275,8 +274,7 @@ fun CenteredTextField( onFocusStateChanged(it) } ) - if(focused) - { + if (focused) { val cursorColor = MaterialTheme.colorScheme.onSurface val infiniteTransition = rememberInfiniteTransition() val alpha by infiniteTransition.animateFloat( @@ -295,7 +293,8 @@ fun CenteredTextField( .padding(start = 3.dp, top = 15.dp, bottom = 15.dp) .width(1.dp) .fillMaxHeight(), - color = cursorColor.copy(alpha = alpha)) + color = cursorColor.copy(alpha = alpha) + ) } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt index 14dc030..b110bf5 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt @@ -1,14 +1,14 @@ package me.zobrist.tichucounter.ui.history +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.OpenInFull +import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -19,6 +19,7 @@ import me.zobrist.tichucounter.data.GameWithScores import me.zobrist.tichucounter.data.entity.Game import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.domain.getTotalPoints +import me.zobrist.tichucounter.ui.composables.DropDownMenu import java.text.DateFormat import java.util.* @@ -103,7 +104,8 @@ fun HistoryListItem( Card( modifier = Modifier .fillMaxWidth() - .padding(all = 4.dp), + .padding(all = 4.dp) + .clickable { onOpenClicked(game.game.uid) }, colors = cardColor ) { Row( @@ -130,16 +132,33 @@ fun HistoryListItem( Column( Modifier .wrapContentSize() - .width(70.dp) + .width(40.dp) ) { - ElevatedButton(onClick = { onOpenClicked(game.game.uid) }, enabled = true) { - Icon(Icons.Outlined.OpenInFull, null) - } - ElevatedButton( - onClick = { onDeleteClicked(game.game.uid) }, enabled = !game.game.active - ) { - Icon(Icons.Outlined.Delete, null) + if (!game.game.active) { + var expanded by remember { mutableStateOf(false) } + + Icon( + modifier = Modifier + .padding(start = 20.dp, bottom = 20.dp) + .clickable { expanded = true }, + imageVector = Icons.Outlined.MoreVert, + contentDescription = null + ) + + + DropDownMenu( + mapOf("delete" to R.string.delete), + "", + expanded, + ) { + expanded = false + it?.let { + when (it) { + "delete" -> onDeleteClicked(game.game.uid) + } + } + } } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt index 55c9555..5f57b21 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt @@ -23,8 +23,9 @@ class HistoryViewModel @Inject constructor( init { viewModelScope.launch { - gameRepository.getAllWithRoundFlow().collect() { games -> - gameAndHistory = games + gameRepository.getAllWithRoundFlow().collect { games -> + gameAndHistory = + games.sortedBy { it.game.modified }.sortedBy { it.game.active }.reversed() } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt index 28d5d3e..5df492a 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowDropDown -import androidx.compose.material.icons.outlined.Check import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment.Companion.End @@ -22,6 +21,7 @@ import me.zobrist.tichucounter.domain.KeepScreenOn import me.zobrist.tichucounter.domain.Language import me.zobrist.tichucounter.domain.Theme import me.zobrist.tichucounter.ui.AppTheme +import me.zobrist.tichucounter.ui.composables.DropDownMenu val languageMap = mapOf( @@ -146,27 +146,6 @@ fun StringSetting(name: String, map: Map, selected: T, onSelected: ( } } -@Composable -fun DropDownMenu(map: Map, selected: T, expanded: Boolean, onSelected: (T?) -> Unit) { - DropdownMenu( - expanded = expanded, - onDismissRequest = { onSelected(null) } - ) { - map.forEach { - DropdownMenuItem( - onClick = { - onSelected(it.key) - }, - trailingIcon = { - if (it.key == selected) { - Icon(Icons.Outlined.Check, null) - } - }, - text = { Text(stringResource(it.value)) }, - ) - } - } -} @Preview(name = "Light Mode") @Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 05711ba..5a3e327 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -13,11 +13,10 @@ Ein Aus Neues Spiel - "Erstellt: %s " - Bearbeitet: %s Verlauf löschen Wirklich den gesamten Verlauf löschen? Diese Aktion kann nicht rückgängig gemacht werden. Abbrechen Ok + Löschen \ 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 f8b7bbc..4e7cd38 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,10 +16,9 @@ On Off New Game - Created: %s - Modified: %s Delete history You really want to delete the the history? This action can\'t be undone. Cancel Ok + Delete \ No newline at end of file From 9ae0890f71eed5f60f246d4f493def601fdacf4b Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 20:52:17 +0100 Subject: [PATCH 18/26] Restyle history page --- .../me/zobrist/tichucounter/MainActivity.kt | 17 +---- .../zobrist/tichucounter/ui/MainViewModel.kt | 6 -- .../tichucounter/ui/history/HistoryView.kt | 62 ++++++++++++++++--- .../ui/history/HistoryViewModel.kt | 5 ++ app/src/main/res/values-de/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + 6 files changed, 67 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 9b1b0e3..b64fddc 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -162,22 +162,9 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } composable("history") { - var openDialog by remember { mutableStateOf(false) } + HistoryList(historyViewModel) { navController.navigate("counter") } - HistoryList(historyViewModel, openDialog, { deleteAll -> - if (deleteAll) { - mainViewModel.deleteAllInactiveGames() - } - openDialog = false - }) { navController.navigate("counter") } - topBarActions = listOf( - TopBarAction( - Icons.Outlined.DeleteForever, - true - ) { - openDialog = true - } - ) + topBarActions = emptyList() topBarIcon = Icons.Outlined.Menu topBarTitle = stringResource(R.string.menu_history) topBarNavigationAction = 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 14a5001..f8a9b91 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt @@ -72,10 +72,4 @@ class MainViewModel @Inject constructor( gameRepository.newGame() } } - - fun deleteAllInactiveGames() { - viewModelScope.launch { - gameRepository.deleteAllInactive() - } - } } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt index b110bf5..2dfc42e 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryView.kt @@ -6,9 +6,11 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.DeleteForever import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -27,19 +29,27 @@ import java.util.* @Composable fun HistoryList( viewModel: HistoryViewModel, - showDeleteDialog: Boolean, - onDialogExecuted: (Boolean) -> Unit, navigateToCalculator: () -> Unit ) { - DeleteConfirmDialog(showDeleteDialog, onDialogExecuted) + var showDeleteDialog by remember { mutableStateOf(false) } - HistoryList(viewModel.gameAndHistory, + DeleteConfirmDialog(showDeleteDialog) { + showDeleteDialog = false + if (it) { + viewModel.deleteAllInactiveGames() + } + } + + HistoryList( + viewModel.gameAndHistory, { viewModel.activateGame(it) navigateToCalculator() }, - { viewModel.deleteGame(it) }) + { viewModel.deleteGame(it) }, + { showDeleteDialog = true }, + ) } @Preview @@ -71,13 +81,49 @@ fun DeleteConfirmDialog(show: Boolean = true, onExecuted: (Boolean) -> Unit = {} fun HistoryList( games: List, onOpenClicked: (GameId: Long) -> Unit, - onDeleteClicked: (GameId: Long) -> Unit + onDeleteClicked: (GameId: Long) -> Unit, + onDeleteAllClicked: () -> Unit + ) { Row { LazyColumn { - items(games) { + item { + Text( + modifier = Modifier.padding(start = 10.dp, end = 10.dp), + text = stringResource(R.string.active), + style = MaterialTheme.typography.headlineSmall + ) + } + items(games.filter { it.game.active }) { HistoryListItem(it, onOpenClicked, onDeleteClicked) } + + if (games.count() > 1) { + item { + Text( + modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp), + text = stringResource(R.string.inactive), + style = MaterialTheme.typography.headlineSmall + ) + } + + items(games.filter { !it.game.active }) { + HistoryListItem(it, onOpenClicked, onDeleteClicked) + } + + item { + Button( + enabled = games.count() > 1, + modifier = Modifier + .padding(start = 4.dp, end = 4.dp, top = 10.dp) + .align(CenterVertically) + .fillMaxWidth(), + onClick = { onDeleteAllClicked() }) { + Icon(imageVector = Icons.Outlined.DeleteForever, contentDescription = null) + Text(text = stringResource(id = R.string.deleteAll)) + } + } + } } } } @@ -190,5 +236,5 @@ private fun HistoryListPreview() { listOf(Round(5, 50, 90)) ) ) - HistoryList(tempData, {}) {} + HistoryList(tempData, {}, {}) {} } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt index 5f57b21..10da62c 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt @@ -42,4 +42,9 @@ class HistoryViewModel @Inject constructor( } } + fun deleteAllInactiveGames() { + viewModelScope.launch { + gameRepository.deleteAllInactive() + } + } } \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5a3e327..daf97c4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -18,5 +18,8 @@ Abbrechen Ok Löschen + Alle löschen + Aktives Spiel + Vergangene Spiele \ 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 4e7cd38..2dc7822 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,4 +21,7 @@ Cancel Ok Delete + Delete all + Current Game + Past Games \ No newline at end of file From 02213f41b6b06e50259d90c599887396addc56c3 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 27 Jan 2023 23:09:08 +0100 Subject: [PATCH 19/26] Add default android locale. Simplify language settings. --- .../me/zobrist/tichucounter/MainActivity.kt | 13 ++----------- .../domain/GameWithScoresExtension.kt | 1 + .../tichucounter/domain/SettingsAdapter.kt | 18 ++++++++---------- .../ui/history/HistoryViewModel.kt | 1 + .../tichucounter/ui/settings/SettingsView.kt | 1 + 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index b64fddc..b9d39c2 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.core.os.LocaleListCompat import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController @@ -76,12 +75,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } override fun onLanguageChanged(language: Language) { - val currentLocale = AppCompatDelegate.getApplicationLocales()[0].toString() - - if (language.value != currentLocale) { - val newLocale = LocaleListCompat.forLanguageTags(language.value) - AppCompatDelegate.setApplicationLocales(newLocale) - } + AppCompatDelegate.setApplicationLocales(language.value) } override fun onThemeChanged(theme: Theme) { @@ -90,10 +84,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { Theme.DARK -> AppCompatDelegate.MODE_NIGHT_YES Theme.DEFAULT -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } - - if (themeValue != AppCompatDelegate.getDefaultNightMode()) { - AppCompatDelegate.setDefaultNightMode(themeValue) - } + AppCompatDelegate.setDefaultNightMode(themeValue) } override fun onScreenOnChanged(keepOn: KeepScreenOn) { diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt b/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt index 46154e7..5094493 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/GameWithScoresExtension.kt @@ -1,6 +1,7 @@ package me.zobrist.tichucounter.domain import me.zobrist.tichucounter.data.GameWithScores + fun GameWithScores.getTotalPoints(): Pair { var scoreA = 0 var scoreB = 0 diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt b/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt index ca83692..452e678 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/SettingsAdapter.kt @@ -1,14 +1,19 @@ package me.zobrist.tichucounter.domain import android.content.Context -import androidx.appcompat.app.AppCompatDelegate.getApplicationLocales +import androidx.core.os.LocaleListCompat import androidx.preference.PreferenceManager import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton enum class Theme { DEFAULT, DARK, LIGHT } -enum class Language(val value: String) { ENGLISH("en"), GERMAN("de") } +enum class Language(val value: LocaleListCompat) { + DEFAULT(LocaleListCompat.getEmptyLocaleList()), + ENGLISH(LocaleListCompat.forLanguageTags("en")), + GERMAN(LocaleListCompat.forLanguageTags("de")) +} + enum class KeepScreenOn(val value: Boolean) { ON(true), OFF(false) } interface ISettingsChangeListener { @@ -36,7 +41,7 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex language = try { enumValueOf(sharedPreferences.getString(Language::class.simpleName, null)!!) } catch (_: NullPointerException) { - getCurrentAppLanguage() + Language.DEFAULT } theme = try { @@ -90,13 +95,6 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex editor.apply() } - private fun getCurrentAppLanguage(): Language { - return when (getApplicationLocales()[0].toString()) { - "de" -> Language.GERMAN - else -> Language.ENGLISH - } - } - private fun notifyListeners(language: Language) { listenerList.forEach { it.onLanguageChanged(language) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt index 10da62c..ed3f587 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/history/HistoryViewModel.kt @@ -42,6 +42,7 @@ class HistoryViewModel @Inject constructor( } } + fun deleteAllInactiveGames() { viewModelScope.launch { gameRepository.deleteAllInactive() diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt index 5df492a..38840be 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/settings/SettingsView.kt @@ -25,6 +25,7 @@ import me.zobrist.tichucounter.ui.composables.DropDownMenu val languageMap = mapOf( + Language.DEFAULT to R.string.android_default_text, Language.ENGLISH to R.string.english, Language.GERMAN to R.string.german ) From 48374c5980dafde4d89d3b4c3a00daed347d9576 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 00:18:26 +0100 Subject: [PATCH 20/26] Move new game back to app bar. --- .../me/zobrist/tichucounter/MainActivity.kt | 57 +++++++++---------- .../tichucounter/domain/TopBarAction.kt | 3 +- .../zobrist/tichucounter/ui/MainViewModel.kt | 6 ++ .../java/me/zobrist/tichucounter/ui/TopBar.kt | 1 + 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index b9d39c2..ca1a9d1 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -33,6 +33,7 @@ import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.ui.AppTheme import me.zobrist.tichucounter.ui.MainViewModel import me.zobrist.tichucounter.ui.TopBar +import me.zobrist.tichucounter.ui.composables.DropDownMenu import me.zobrist.tichucounter.ui.counter.* import me.zobrist.tichucounter.ui.history.HistoryList import me.zobrist.tichucounter.ui.history.HistoryViewModel @@ -126,24 +127,45 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { { topBarNavigationAction.action() }, topBarActions ) - }) { + }) { paddings -> NavHost( navController = navController, startDestination = "counter", - modifier = Modifier.padding(it) + modifier = Modifier.padding(paddings) ) { composable("counter") { + + var expanded by remember { mutableStateOf(false) } + Counter(counterViewModel) topBarActions = (listOf( TopBarAction( Icons.Outlined.Undo, - mainViewModel.isUndoActionActive - ) { mainViewModel.undoLastRound() }, + mainViewModel.isUndoActionActive, + { mainViewModel.undoLastRound() }), TopBarAction( Icons.Outlined.Redo, - mainViewModel.isRedoActionActive - ) { mainViewModel.redoLastRound() } + mainViewModel.isRedoActionActive, + { mainViewModel.redoLastRound() }), + TopBarAction( + Icons.Outlined.MoreVert, + mainViewModel.activeGameHasRounds, + { expanded = true } + ) { + DropDownMenu( + mapOf("new" to R.string.newGame), + "", + expanded, + ) { + expanded = false + it?.let { + when (it) { + "new" -> mainViewModel.newGame() + } + } + } + }, )) topBarIcon = Icons.Outlined.Menu @@ -198,29 +220,6 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { Spacer(Modifier.height(20.dp)) - NavigationDrawerItem( - icon = { Icon(Icons.Outlined.RestartAlt, contentDescription = null) }, - colors = NavigationDrawerItemDefaults.colors( - unselectedContainerColor = MaterialTheme.colorScheme.primaryContainer - ), - label = { Text(stringResource(R.string.newGame)) }, - selected = false, - onClick = { - scope.launch { drawerState.close() } - mainViewModel.newGame() - navController.navigate("counter") { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) - ) - - Divider(Modifier.padding(top = 20.dp, bottom = 20.dp)) - items.forEach { screen -> NavigationDrawerItem( icon = { Icon(screen.icon, contentDescription = null) }, 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 02ce8c4..4820f71 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt @@ -1,5 +1,6 @@ package me.zobrist.tichucounter.domain +import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector -class TopBarAction(val imageVector: ImageVector, val isActive: Boolean, val action: () -> Unit) \ No newline at end of file +class TopBarAction(val imageVector: ImageVector, val isActive: Boolean, val action: () -> Unit, val composeCode: @Composable () -> Unit = {}) \ No newline at end of file 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 f8a9b91..53fde44 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/MainViewModel.kt @@ -17,6 +17,7 @@ class MainViewModel @Inject constructor( private val gameRepository: GameRepository ) : ViewModel() { + private var redoRounds = mutableStateListOf() private var expectedRoundCount = 0 @@ -25,11 +26,16 @@ class MainViewModel @Inject constructor( val isRedoActionActive: Boolean get() = redoRounds.isNotEmpty() + var activeGameHasRounds by mutableStateOf(false) + private set + init { viewModelScope.launch { gameRepository.getActiveGameFlow().collect { + activeGameHasRounds = it?.rounds?.isNotEmpty() == true + if (it != null) { isUndoActionActive = it.rounds.isNotEmpty() diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt b/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt index 2a8a473..ecfcb4e 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt @@ -37,6 +37,7 @@ fun TopBar( imageVector = it.imageVector, contentDescription = null, ) + it.composeCode() } } } From 6aedb0d7f96c357d02c85ec8c128d4a815bd0ca1 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 10:06:58 +0100 Subject: [PATCH 21/26] Navigate with enum defines. Create new TopBarState --- .../me/zobrist/tichucounter/MainActivity.kt | 166 +++++++++--------- .../tichucounter/domain/NavExtensions.kt | 18 ++ .../tichucounter/domain/NavigationAction.kt | 3 - .../me/zobrist/tichucounter/domain/Route.kt | 3 + .../tichucounter/domain/TopBarAction.kt | 7 +- .../tichucounter/domain/TopBarState.kt | 12 ++ .../java/me/zobrist/tichucounter/ui/TopBar.kt | 11 ++ 7 files changed, 129 insertions(+), 91 deletions(-) create mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/NavigationAction.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/Route.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/TopBarState.kt diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index ca1a9d1..7cd6e40 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -17,11 +17,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavDestination.Companion.hierarchy -import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController @@ -106,10 +103,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { fabAction: () -> Unit ) { - var topBarTitle by remember { mutableStateOf("") } - var topBarIcon by remember { mutableStateOf(Icons.Filled.Menu) } - var topBarActions by remember { mutableStateOf(emptyList()) } - var topBarNavigationAction by remember { mutableStateOf(NavigationAction {}) } + var topBarState by remember { mutableStateOf(TopBarState()) } Scaffold( floatingActionButton = { @@ -120,76 +114,63 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } } }, - topBar = { - TopBar( - topBarTitle, - topBarIcon, - { topBarNavigationAction.action() }, - topBarActions - ) - }) { paddings -> + topBar = { TopBar(topBarState) }) { paddings -> NavHost( navController = navController, - startDestination = "counter", + startDestination = Route.COUNTER.name, modifier = Modifier.padding(paddings) ) { - composable("counter") { + composable(Route.COUNTER) { var expanded by remember { mutableStateOf(false) } - Counter(counterViewModel) - topBarActions = (listOf( - TopBarAction( - Icons.Outlined.Undo, - mainViewModel.isUndoActionActive, - { mainViewModel.undoLastRound() }), - TopBarAction( - Icons.Outlined.Redo, - mainViewModel.isRedoActionActive, - { mainViewModel.redoLastRound() }), - TopBarAction( - Icons.Outlined.MoreVert, - mainViewModel.activeGameHasRounds, - { expanded = true } - ) { - DropDownMenu( - mapOf("new" to R.string.newGame), - "", - expanded, + topBarState = TopBarState( + title = stringResource(R.string.app_name), + actions = (listOf( + TopBarAction( + Icons.Outlined.Undo, + mainViewModel.isUndoActionActive, + { mainViewModel.undoLastRound() }), + TopBarAction( + Icons.Outlined.Redo, + mainViewModel.isRedoActionActive, + { mainViewModel.redoLastRound() }), + TopBarAction( + Icons.Outlined.MoreVert, + mainViewModel.activeGameHasRounds, + { expanded = true } ) { - expanded = false - it?.let { - when (it) { - "new" -> mainViewModel.newGame() + DropDownMenu( + mapOf("new" to R.string.newGame), + "", + expanded, + ) { + expanded = false + it?.let { + when (it) { + "new" -> mainViewModel.newGame() + } } } - } - }, + }, - )) - topBarIcon = Icons.Outlined.Menu - topBarTitle = stringResource(R.string.app_name) - topBarNavigationAction = - NavigationAction { scope.launch { drawerState.open() } } + )) + ) { scope.launch { drawerState.open() } } + + Counter(counterViewModel) } - composable("history") { + composable(Route.HISTORY) { + topBarState = + TopBarState(title = stringResource(R.string.menu_history)) { scope.launch { drawerState.open() } } - HistoryList(historyViewModel) { navController.navigate("counter") } - - topBarActions = emptyList() - topBarIcon = Icons.Outlined.Menu - topBarTitle = stringResource(R.string.menu_history) - topBarNavigationAction = - NavigationAction { scope.launch { drawerState.open() } } + HistoryList(historyViewModel) { navController.navigate(Route.COUNTER) } } - composable("settings") { + composable(Route.SETTINGS) { + topBarState = + TopBarState(title = stringResource(R.string.menu_settings)) { scope.launch { drawerState.open() } } + SettingsView(settingsViewModel) - topBarActions = emptyList() - topBarIcon = Icons.Outlined.Menu - topBarTitle = stringResource(R.string.menu_settings) - topBarNavigationAction = - NavigationAction { scope.launch { drawerState.open() } } } } } @@ -204,40 +185,27 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { val navController = rememberNavController() val items = listOf( - Screen("counter", Icons.Outlined.Calculate, R.string.app_name), - Screen("history", Icons.Outlined.List, R.string.menu_history), - Screen("settings", Icons.Outlined.Settings, R.string.menu_settings) + Screen(Route.COUNTER, Icons.Outlined.Calculate, R.string.app_name), + Screen(Route.HISTORY, Icons.Outlined.List, R.string.menu_history), + Screen(Route.SETTINGS, Icons.Outlined.Settings, R.string.menu_settings) ) val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination + val currentDestination = + Route.valueOf(navBackStackEntry?.destination?.route ?: Route.COUNTER.name) ModalNavigationDrawer( drawerState = drawerState, gesturesEnabled = drawerState.isOpen, drawerContent = { - ModalDrawerSheet { + DrawerContent( + screens = items, + selectedScreen = items.first { it.route == currentDestination }) { + scope.launch { + drawerState.close() - Spacer(Modifier.height(20.dp)) - - items.forEach { screen -> - NavigationDrawerItem( - icon = { Icon(screen.icon, contentDescription = null) }, - label = { Text(stringResource(screen.resourceId)) }, - selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, - onClick = { - scope.launch { drawerState.close() } - navController.navigate(screen.route) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } - }, - modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) - ) } + navController.navigate(it) } } ) { @@ -245,10 +213,34 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { drawerState, scope, navController, - counterViewModel.keyboardHidden && (currentDestination?.hierarchy?.any { it.route == "counter" } == true) + counterViewModel.keyboardHidden && (currentDestination == Route.COUNTER) ) { counterViewModel.showKeyboard() } } } - private class Screen(val route: String, val icon: ImageVector, @StringRes val resourceId: Int) + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun DrawerContent( + screens: List, + selectedScreen: Screen, + onElementClicked: (Route) -> Unit + ) { + + ModalDrawerSheet { + + Spacer(Modifier.height(20.dp)) + + screens.forEach { screen -> + NavigationDrawerItem( + icon = { Icon(screen.icon, contentDescription = null) }, + label = { Text(stringResource(screen.resourceId)) }, + selected = screen == selectedScreen, + onClick = { onElementClicked(screen.route) }, + modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + ) + } + } + } + + private class Screen(val route: Route, val icon: ImageVector, @StringRes val resourceId: Int) } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt b/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt new file mode 100644 index 0000000..3121f23 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt @@ -0,0 +1,18 @@ +package me.zobrist.tichucounter.domain + +import androidx.compose.runtime.Composable +import androidx.navigation.* +import androidx.navigation.compose.composable + +fun NavController.navigate(route: Route) { + this.navigate(route.name) +} + +fun NavGraphBuilder.composable( + route: Route, + arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (NavBackStackEntry) -> Unit +) { + this.composable(route.name, arguments, deepLinks, content) +} diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/NavigationAction.kt b/app/src/main/java/me/zobrist/tichucounter/domain/NavigationAction.kt deleted file mode 100644 index eb9dd5b..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/domain/NavigationAction.kt +++ /dev/null @@ -1,3 +0,0 @@ -package me.zobrist.tichucounter.domain - -class NavigationAction(val action: () -> Unit) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt b/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt new file mode 100644 index 0000000..4ec7402 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt @@ -0,0 +1,3 @@ +package me.zobrist.tichucounter.domain + +enum class Route { COUNTER, HISTORY, SETTINGS } 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 4820f71..0a9eb39 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/TopBarAction.kt @@ -3,4 +3,9 @@ package me.zobrist.tichucounter.domain import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector -class TopBarAction(val imageVector: ImageVector, val isActive: Boolean, val action: () -> Unit, val composeCode: @Composable () -> Unit = {}) \ No newline at end of file +class TopBarAction( + val imageVector: ImageVector, + val isActive: Boolean, + val action: () -> Unit, + val composeCode: @Composable () -> Unit = {} +) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/TopBarState.kt b/app/src/main/java/me/zobrist/tichucounter/domain/TopBarState.kt new file mode 100644 index 0000000..46bbd46 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/domain/TopBarState.kt @@ -0,0 +1,12 @@ +package me.zobrist.tichucounter.domain + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Menu +import androidx.compose.ui.graphics.vector.ImageVector + +data class TopBarState( + var title: String = "", + var icon: ImageVector = Icons.Outlined.Menu, + var actions: List = emptyList(), + var onNavigate: () -> Unit = {} +) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt b/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt index ecfcb4e..5ff2232 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt @@ -5,6 +5,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextOverflow import me.zobrist.tichucounter.domain.TopBarAction +import me.zobrist.tichucounter.domain.TopBarState + +@Composable +fun TopBar(topBarState: TopBarState) { + TopBar( + topBarState.title, + topBarState.icon, + topBarState.onNavigate, + topBarState.actions + ) +} @OptIn(ExperimentalMaterial3Api::class) @Composable From e1e25ff6075603dc0570db83334c447d04ebd03d Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 10:14:06 +0100 Subject: [PATCH 22/26] Move drawerContent to own file. --- .../me/zobrist/tichucounter/MainActivity.kt | 117 +++++++----------- .../me/zobrist/tichucounter/domain/Screen.kt | 6 + .../zobrist/tichucounter/ui/DrawerContent.kt | 36 ++++++ 3 files changed, 84 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt create mode 100644 app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 7cd6e40..c95a7a5 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.WindowManager import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.layout.* @@ -14,9 +13,7 @@ import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState @@ -26,8 +23,8 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import me.zobrist.tichucounter.domain.* -import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.ui.AppTheme +import me.zobrist.tichucounter.ui.DrawerContent import me.zobrist.tichucounter.ui.MainViewModel import me.zobrist.tichucounter.ui.TopBar import me.zobrist.tichucounter.ui.composables.DropDownMenu @@ -41,9 +38,6 @@ import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity(), ISettingsChangeListener { - @Inject - lateinit var gameRepository: GameRepository - @Inject lateinit var settingsAdapter: SettingsAdapter @@ -93,6 +87,47 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } } + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun NavigationDrawer() { + val drawerState = rememberDrawerState(DrawerValue.Closed) + val scope = rememberCoroutineScope() + val navController = rememberNavController() + + val items = listOf( + Screen(Route.COUNTER, Icons.Outlined.Calculate, R.string.app_name), + Screen(Route.HISTORY, Icons.Outlined.List, R.string.menu_history), + Screen(Route.SETTINGS, Icons.Outlined.Settings, R.string.menu_settings) + ) + + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = + Route.valueOf(navBackStackEntry?.destination?.route ?: Route.COUNTER.name) + + ModalNavigationDrawer( + drawerState = drawerState, + gesturesEnabled = drawerState.isOpen, + drawerContent = { + DrawerContent( + screens = items, + selectedScreen = items.first { it.route == currentDestination }) { + scope.launch { + drawerState.close() + + } + navController.navigate(it) + } + } + ) { + MyScaffoldLayout( + drawerState, + scope, + navController, + counterViewModel.keyboardHidden && (currentDestination == Route.COUNTER) + ) { counterViewModel.showKeyboard() } + } + } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun MyScaffoldLayout( @@ -175,72 +210,4 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { } } } - - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - private fun NavigationDrawer() { - val drawerState = rememberDrawerState(DrawerValue.Closed) - val scope = rememberCoroutineScope() - val navController = rememberNavController() - - val items = listOf( - Screen(Route.COUNTER, Icons.Outlined.Calculate, R.string.app_name), - Screen(Route.HISTORY, Icons.Outlined.List, R.string.menu_history), - Screen(Route.SETTINGS, Icons.Outlined.Settings, R.string.menu_settings) - ) - - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = - Route.valueOf(navBackStackEntry?.destination?.route ?: Route.COUNTER.name) - - ModalNavigationDrawer( - drawerState = drawerState, - gesturesEnabled = drawerState.isOpen, - drawerContent = { - DrawerContent( - screens = items, - selectedScreen = items.first { it.route == currentDestination }) { - scope.launch { - drawerState.close() - - } - navController.navigate(it) - } - } - ) { - MyScaffoldLayout( - drawerState, - scope, - navController, - counterViewModel.keyboardHidden && (currentDestination == Route.COUNTER) - ) { counterViewModel.showKeyboard() } - } - } - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - private fun DrawerContent( - screens: List, - selectedScreen: Screen, - onElementClicked: (Route) -> Unit - ) { - - ModalDrawerSheet { - - Spacer(Modifier.height(20.dp)) - - screens.forEach { screen -> - NavigationDrawerItem( - icon = { Icon(screen.icon, contentDescription = null) }, - label = { Text(stringResource(screen.resourceId)) }, - selected = screen == selectedScreen, - onClick = { onElementClicked(screen.route) }, - modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) - ) - } - } - } - - private class Screen(val route: Route, val icon: ImageVector, @StringRes val resourceId: Int) } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt b/app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt new file mode 100644 index 0000000..6b5415e --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt @@ -0,0 +1,6 @@ +package me.zobrist.tichucounter.domain + +import androidx.annotation.StringRes +import androidx.compose.ui.graphics.vector.ImageVector + +class Screen(val route: Route, val icon: ImageVector, @StringRes val resourceId: Int) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt b/app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt new file mode 100644 index 0000000..cdf3705 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt @@ -0,0 +1,36 @@ +package me.zobrist.tichucounter.ui + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import me.zobrist.tichucounter.domain.Route +import me.zobrist.tichucounter.domain.Screen + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DrawerContent( + screens: List, + selectedScreen: Screen, + onElementClicked: (Route) -> Unit +) { + + ModalDrawerSheet { + + Spacer(Modifier.height(20.dp)) + + screens.forEach { screen -> + NavigationDrawerItem( + icon = { Icon(screen.icon, contentDescription = null) }, + label = { Text(stringResource(screen.resourceId)) }, + selected = screen == selectedScreen, + onClick = { onElementClicked(screen.route) }, + modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + ) + } + } +} \ No newline at end of file From ae0f85bec0b2b4d7b954f6778b8fbfbc64d8a2b7 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 10:25:07 +0100 Subject: [PATCH 23/26] Rename screen to DrawerItem --- .../me/zobrist/tichucounter/MainActivity.kt | 14 +++++++------- .../zobrist/tichucounter/domain/DrawerItem.kt | 5 +++++ .../me/zobrist/tichucounter/domain/Screen.kt | 6 ------ .../ui/{ => layout}/DrawerContent.kt | 17 ++++++++--------- .../tichucounter/ui/{ => layout}/TopBar.kt | 2 +- 5 files changed, 21 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/DrawerItem.kt delete mode 100644 app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt rename app/src/main/java/me/zobrist/tichucounter/ui/{ => layout}/DrawerContent.kt (64%) rename app/src/main/java/me/zobrist/tichucounter/ui/{ => layout}/TopBar.kt (97%) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index c95a7a5..d762d37 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -24,9 +24,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.ui.AppTheme -import me.zobrist.tichucounter.ui.DrawerContent +import me.zobrist.tichucounter.ui.layout.DrawerContent import me.zobrist.tichucounter.ui.MainViewModel -import me.zobrist.tichucounter.ui.TopBar +import me.zobrist.tichucounter.ui.layout.TopBar import me.zobrist.tichucounter.ui.composables.DropDownMenu import me.zobrist.tichucounter.ui.counter.* import me.zobrist.tichucounter.ui.history.HistoryList @@ -95,9 +95,9 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { val navController = rememberNavController() val items = listOf( - Screen(Route.COUNTER, Icons.Outlined.Calculate, R.string.app_name), - Screen(Route.HISTORY, Icons.Outlined.List, R.string.menu_history), - Screen(Route.SETTINGS, Icons.Outlined.Settings, R.string.menu_settings) + DrawerItem(Route.COUNTER, Icons.Outlined.Calculate, stringResource(R.string.app_name)), + DrawerItem(Route.HISTORY, Icons.Outlined.List, stringResource(R.string.menu_history)), + DrawerItem(Route.SETTINGS, Icons.Outlined.Settings, stringResource(R.string.menu_settings)) ) val navBackStackEntry by navController.currentBackStackEntryAsState() @@ -109,8 +109,8 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { gesturesEnabled = drawerState.isOpen, drawerContent = { DrawerContent( - screens = items, - selectedScreen = items.first { it.route == currentDestination }) { + drawerItems = items, + selectedDrawerItem = items.first { it.route == currentDestination }) { scope.launch { drawerState.close() diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/DrawerItem.kt b/app/src/main/java/me/zobrist/tichucounter/domain/DrawerItem.kt new file mode 100644 index 0000000..b3757ef --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/domain/DrawerItem.kt @@ -0,0 +1,5 @@ +package me.zobrist.tichucounter.domain + +import androidx.compose.ui.graphics.vector.ImageVector + +data class DrawerItem(val route: Route, val menuIcon: ImageVector, val menuName: String) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt b/app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt deleted file mode 100644 index 6b5415e..0000000 --- a/app/src/main/java/me/zobrist/tichucounter/domain/Screen.kt +++ /dev/null @@ -1,6 +0,0 @@ -package me.zobrist.tichucounter.domain - -import androidx.annotation.StringRes -import androidx.compose.ui.graphics.vector.ImageVector - -class Screen(val route: Route, val icon: ImageVector, @StringRes val resourceId: Int) \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt b/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt similarity index 64% rename from app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt rename to app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt index cdf3705..445ab86 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/DrawerContent.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt @@ -1,4 +1,4 @@ -package me.zobrist.tichucounter.ui +package me.zobrist.tichucounter.ui.layout import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height @@ -6,16 +6,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import me.zobrist.tichucounter.domain.Route -import me.zobrist.tichucounter.domain.Screen +import me.zobrist.tichucounter.domain.DrawerItem @OptIn(ExperimentalMaterial3Api::class) @Composable fun DrawerContent( - screens: List, - selectedScreen: Screen, + drawerItems: List, + selectedDrawerItem: DrawerItem, onElementClicked: (Route) -> Unit ) { @@ -23,11 +22,11 @@ fun DrawerContent( Spacer(Modifier.height(20.dp)) - screens.forEach { screen -> + drawerItems.forEach { screen -> NavigationDrawerItem( - icon = { Icon(screen.icon, contentDescription = null) }, - label = { Text(stringResource(screen.resourceId)) }, - selected = screen == selectedScreen, + icon = { Icon(screen.menuIcon, contentDescription = null) }, + label = { Text(screen.menuName) }, + selected = screen == selectedDrawerItem, onClick = { onElementClicked(screen.route) }, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) ) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt b/app/src/main/java/me/zobrist/tichucounter/ui/layout/TopBar.kt similarity index 97% rename from app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt rename to app/src/main/java/me/zobrist/tichucounter/ui/layout/TopBar.kt index 5ff2232..a4dbd23 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/TopBar.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/layout/TopBar.kt @@ -1,4 +1,4 @@ -package me.zobrist.tichucounter.ui +package me.zobrist.tichucounter.ui.layout import androidx.compose.material3.* import androidx.compose.runtime.Composable From 2e8d6a7a4e50313c1fced417c4b127729dc84ec5 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 11:19:37 +0100 Subject: [PATCH 24/26] Add App name to navigation drawer. --- .../me/zobrist/tichucounter/MainActivity.kt | 2 +- .../tichucounter/ui/layout/DrawerContent.kt | 43 ++++++++++++++++--- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/strings.xml | 3 +- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index d762d37..871610d 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { val navController = rememberNavController() val items = listOf( - DrawerItem(Route.COUNTER, Icons.Outlined.Calculate, stringResource(R.string.app_name)), + DrawerItem(Route.COUNTER, Icons.Outlined.Calculate, stringResource(R.string.menu_counter)), DrawerItem(Route.HISTORY, Icons.Outlined.List, stringResource(R.string.menu_history)), DrawerItem(Route.SETTINGS, Icons.Outlined.Settings, stringResource(R.string.menu_settings)) ) diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt b/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt index 445ab86..45a86f9 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt @@ -1,14 +1,24 @@ package me.zobrist.tichucounter.ui.layout -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import android.content.res.Configuration +import androidx.compose.foundation.Image + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import me.zobrist.tichucounter.domain.Route -import me.zobrist.tichucounter.domain.DrawerItem +import me.zobrist.tichucounter.BuildConfig +import me.zobrist.tichucounter.R +import me.zobrist.tichucounter.domain.* +import me.zobrist.tichucounter.ui.AppTheme +import me.zobrist.tichucounter.ui.counter.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -20,7 +30,8 @@ fun DrawerContent( ModalDrawerSheet { - Spacer(Modifier.height(20.dp)) + Text(modifier = Modifier.padding(start = 10.dp, top = 10.dp), text = stringResource(R.string.app_name), style = MaterialTheme.typography.headlineSmall) + Divider(modifier = Modifier.padding(10.dp)) drawerItems.forEach { screen -> NavigationDrawerItem( @@ -32,4 +43,22 @@ fun DrawerContent( ) } } +} + +@Preview(name = "Light Mode") +@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +fun DrawerContentPreview() { + + val counter = DrawerItem(Route.COUNTER, Icons.Outlined.Calculate, "Counter") + val history = DrawerItem(Route.HISTORY, Icons.Outlined.List, "History") + val settings = DrawerItem(Route.SETTINGS, Icons.Outlined.Settings, "Settings") + AppTheme { + Surface { + DrawerContent( + listOf(counter, history, settings), + counter + ) {} + } + } } \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index daf97c4..be60463 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -21,5 +21,6 @@ Alle löschen Aktives Spiel Vergangene Spiele + Counter \ 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 2dc7822..268f1e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,5 +23,6 @@ Delete Delete all Current Game - Past Games + Old Games + Counter \ No newline at end of file From c41816898e027acea97f691dbc4eae4cb6354477 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 12:33:45 +0100 Subject: [PATCH 25/26] Add about page. --- .../me/zobrist/tichucounter/MainActivity.kt | 29 +++++++-- .../me/zobrist/tichucounter/domain/Route.kt | 2 +- .../tichucounter/ui/about/AboutView.kt | 57 ++++++++++++++++++ .../tichucounter/ui/layout/DrawerContent.kt | 10 +-- app/src/main/res/drawable/app_logo.png | Bin 0 -> 16593 bytes app/src/main/res/values/strings.xml | 1 + 6 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/me/zobrist/tichucounter/ui/about/AboutView.kt create mode 100644 app/src/main/res/drawable/app_logo.png diff --git a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt index 871610d..b964c6c 100644 --- a/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt +++ b/app/src/main/java/me/zobrist/tichucounter/MainActivity.kt @@ -24,13 +24,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.ui.AppTheme -import me.zobrist.tichucounter.ui.layout.DrawerContent import me.zobrist.tichucounter.ui.MainViewModel -import me.zobrist.tichucounter.ui.layout.TopBar +import me.zobrist.tichucounter.ui.about.AboutView import me.zobrist.tichucounter.ui.composables.DropDownMenu import me.zobrist.tichucounter.ui.counter.* import me.zobrist.tichucounter.ui.history.HistoryList import me.zobrist.tichucounter.ui.history.HistoryViewModel +import me.zobrist.tichucounter.ui.layout.DrawerContent +import me.zobrist.tichucounter.ui.layout.TopBar import me.zobrist.tichucounter.ui.settings.SettingsView import me.zobrist.tichucounter.ui.settings.SettingsViewModel import javax.inject.Inject @@ -95,9 +96,22 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { val navController = rememberNavController() val items = listOf( - DrawerItem(Route.COUNTER, Icons.Outlined.Calculate, stringResource(R.string.menu_counter)), + DrawerItem( + Route.COUNTER, + Icons.Outlined.Calculate, + stringResource(R.string.menu_counter) + ), DrawerItem(Route.HISTORY, Icons.Outlined.List, stringResource(R.string.menu_history)), - DrawerItem(Route.SETTINGS, Icons.Outlined.Settings, stringResource(R.string.menu_settings)) + DrawerItem( + Route.SETTINGS, + Icons.Outlined.Settings, + stringResource(R.string.menu_settings) + ), + DrawerItem( + Route.ABOUT, + Icons.Outlined.Info, + stringResource(R.string.menu_about) + ) ) val navBackStackEntry by navController.currentBackStackEntryAsState() @@ -207,6 +221,13 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener { SettingsView(settingsViewModel) } + + composable(Route.ABOUT) { + topBarState = + TopBarState(title = stringResource(R.string.menu_about)) { scope.launch { drawerState.open() } } + + AboutView() + } } } } diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt b/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt index 4ec7402..5ae62a8 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/Route.kt @@ -1,3 +1,3 @@ package me.zobrist.tichucounter.domain -enum class Route { COUNTER, HISTORY, SETTINGS } +enum class Route { COUNTER, HISTORY, SETTINGS, ABOUT } diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/about/AboutView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/about/AboutView.kt new file mode 100644 index 0000000..9202b49 --- /dev/null +++ b/app/src/main/java/me/zobrist/tichucounter/ui/about/AboutView.kt @@ -0,0 +1,57 @@ +package me.zobrist.tichucounter.ui.about + +import android.content.res.Configuration +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.Top +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import me.zobrist.tichucounter.BuildConfig +import me.zobrist.tichucounter.R +import me.zobrist.tichucounter.ui.AppTheme + + +@Composable +fun AboutView() { + Row(Modifier.padding(20.dp)) { + Image( + modifier = Modifier + .height(80.dp) + .padding(end = 10.dp) + .align(Top), + painter = painterResource(R.drawable.app_logo), + contentDescription = null, + contentScale = ContentScale.Fit + ) + + Column { + Text( + text = stringResource(id = R.string.app_name), + style = MaterialTheme.typography.headlineMedium + ) + Text(text = "V" + BuildConfig.VERSION_NAME) + } + } +} + +@Preview(name = "Light Mode") +@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +fun AboutViewPreview() { + AppTheme() { + Surface { + AboutView() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt b/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt index 45a86f9..e025a4a 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/layout/DrawerContent.kt @@ -1,8 +1,6 @@ package me.zobrist.tichucounter.ui.layout import android.content.res.Configuration -import androidx.compose.foundation.Image - import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -10,11 +8,9 @@ import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import me.zobrist.tichucounter.BuildConfig import me.zobrist.tichucounter.R import me.zobrist.tichucounter.domain.* import me.zobrist.tichucounter.ui.AppTheme @@ -30,7 +26,11 @@ fun DrawerContent( ModalDrawerSheet { - Text(modifier = Modifier.padding(start = 10.dp, top = 10.dp), text = stringResource(R.string.app_name), style = MaterialTheme.typography.headlineSmall) + Text( + modifier = Modifier.padding(start = 10.dp, top = 10.dp), + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineSmall + ) Divider(modifier = Modifier.padding(10.dp)) drawerItems.forEach { screen -> diff --git a/app/src/main/res/drawable/app_logo.png b/app/src/main/res/drawable/app_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7681534e3a404f11be3224f7b20a0c1233f833f6 GIT binary patch literal 16593 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 Game Old Games Counter + About \ No newline at end of file From 661b88b9617df6170b8571ecbc16aa69c285a365 Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Sat, 28 Jan 2023 13:06:50 +0100 Subject: [PATCH 26/26] Keep back stack of navigation controller clean. --- .../zobrist/tichucounter/domain/NavExtensions.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt b/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt index 3121f23..68771fb 100644 --- a/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt +++ b/app/src/main/java/me/zobrist/tichucounter/domain/NavExtensions.kt @@ -5,7 +5,19 @@ import androidx.navigation.* import androidx.navigation.compose.composable fun NavController.navigate(route: Route) { - this.navigate(route.name) + this.navigate(route.name){ + // 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(Route.COUNTER.name) { + 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 + } } fun NavGraphBuilder.composable(