feature/winning-points #50

Merged
fabian merged 2 commits from feature/winning-points into develop 2023-09-01 18:30:30 +02:00
11 changed files with 260 additions and 86 deletions

View File

@@ -40,12 +40,11 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.android.play.core.review.ReviewManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zobrist.tichucounter.domain.DrawerItem import me.zobrist.tichucounter.domain.DrawerItem
import me.zobrist.tichucounter.domain.ISettingsChangeListener import me.zobrist.tichucounter.domain.ISystemSettingsChangeListener
import me.zobrist.tichucounter.domain.KeepScreenOn import me.zobrist.tichucounter.domain.KeepScreenOn
import me.zobrist.tichucounter.domain.Language import me.zobrist.tichucounter.domain.Language
import me.zobrist.tichucounter.domain.ReviewService import me.zobrist.tichucounter.domain.ReviewService
@@ -70,7 +69,7 @@ import me.zobrist.tichucounter.ui.settings.SettingsViewModel
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity(), ISettingsChangeListener { class MainActivity : AppCompatActivity(), ISystemSettingsChangeListener {
@Inject @Inject
lateinit var settingsAdapter: SettingsAdapter lateinit var settingsAdapter: SettingsAdapter
@@ -192,7 +191,6 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
var topBarState by remember { mutableStateOf(TopBarState()) } var topBarState by remember { mutableStateOf(TopBarState()) }
var snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } var snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
val scope = rememberCoroutineScope()
Scaffold( Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
@@ -211,7 +209,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
startDestination = Route.COUNTER.name, startDestination = Route.COUNTER.name,
modifier = Modifier.padding(paddings) modifier = Modifier.padding(paddings)
) { ) {
composable(Route.COUNTER.name) { this.composable(Route.COUNTER.name.toString()) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@@ -231,15 +229,16 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
mainViewModel.activeGameHasRounds, mainViewModel.activeGameHasRounds,
{ expanded = true } { expanded = true }
) { ) {
val newGameTranslated = stringResource(R.string.new_game)
DropDownMenu( DropDownMenu(
mapOf("new" to R.string.newGame), listOf(newGameTranslated),
"", "",
expanded, expanded,
) { ) {
expanded = false expanded = false
it?.let { it?.let {
when (it) { when (it) {
"new" -> mainViewModel.newGame() newGameTranslated -> mainViewModel.newGame()
} }
} }
} }

View File

@@ -2,27 +2,17 @@ package me.zobrist.tichucounter.domain
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.play.core.review.ReviewManagerFactory import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.testing.FakeReviewManager
import dagger.hilt.android.internal.Contexts
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.android.scopes.FragmentScoped
import dagger.hilt.android.scopes.ViewScoped
import java.time.Duration
import java.time.Period
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
class ReviewService @Inject constructor(@ActivityContext private val appContext: Context) { class ReviewService @Inject constructor(@ActivityContext private val appContext: Context) {
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext) private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
private val THREE_MONTHS : Long = 7776000000 private val THREE_MONTHS: Long = 7776000000
private var requestCalled: Int private var requestCalled: Int
get() = sharedPreferences.getInt("requestCalled", 0) get() = sharedPreferences.getInt("requestCalled", 0)
@@ -43,10 +33,8 @@ class ReviewService @Inject constructor(@ActivityContext private val appContext:
fun request() { fun request() {
requestCalled += 1 requestCalled += 1
if(requestCalled >= 3) if (requestCalled >= 3) {
{ if (nextReviewedDate.time < System.currentTimeMillis()) {
if(nextReviewedDate.time < System.currentTimeMillis())
{
requestCalled = 0 requestCalled = 0
nextReviewedDate = Date(System.currentTimeMillis() + THREE_MONTHS) nextReviewedDate = Date(System.currentTimeMillis() + THREE_MONTHS)

View File

@@ -4,7 +4,6 @@ import android.content.Context
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -17,12 +16,19 @@ enum class Language(val value: LocaleListCompat) {
enum class KeepScreenOn(val value: Boolean) { ON(true), OFF(false) } enum class KeepScreenOn(val value: Boolean) { ON(true), OFF(false) }
interface ISettingsChangeListener { typealias VictoryPoints = Int
interface ISettingsChangeListener
interface ISystemSettingsChangeListener : ISettingsChangeListener {
fun onLanguageChanged(language: Language) fun onLanguageChanged(language: Language)
fun onThemeChanged(theme: Theme) fun onThemeChanged(theme: Theme)
fun onScreenOnChanged(keepOn: KeepScreenOn) fun onScreenOnChanged(keepOn: KeepScreenOn)
} }
interface IGameSettingsChangeListener : ISettingsChangeListener {
fun onVictoryPointsChanged(victoryPoints: Int)
}
@Singleton @Singleton
class SettingsAdapter @Inject constructor(@ApplicationContext private val context: Context) { class SettingsAdapter @Inject constructor(@ApplicationContext private val context: Context) {
@@ -37,9 +43,9 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
var keepScreenOn: KeepScreenOn var keepScreenOn: KeepScreenOn
private set private set
var reviewDialogShownDate: Date
get() = Date(sharedPreferences.getLong("reviewDialogShownDate", 0)) var victoryPoints: Int
set(value) = updatePreference("reviewDialogShownDate", value.time) private set
init { init {
language = try { language = try {
@@ -59,14 +65,22 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
} catch (_: java.lang.Exception) { } catch (_: java.lang.Exception) {
KeepScreenOn.OFF KeepScreenOn.OFF
} }
victoryPoints = sharedPreferences.getInt(VictoryPoints::class.simpleName, 1000)
} }
fun registerOnChangeListener(listener: ISettingsChangeListener) { fun registerOnChangeListener(listener: ISettingsChangeListener) {
listenerList.add(listener) listenerList.add(listener)
listener.onThemeChanged(theme) if (listener is ISystemSettingsChangeListener) {
listener.onLanguageChanged(language) listener.onThemeChanged(theme)
listener.onScreenOnChanged(keepScreenOn) listener.onLanguageChanged(language)
listener.onScreenOnChanged(keepScreenOn)
}
if (listener is IGameSettingsChangeListener) {
listener.onVictoryPointsChanged(victoryPoints)
}
} }
fun unregisterOnChangeListener(listener: ISettingsChangeListener?) { fun unregisterOnChangeListener(listener: ISettingsChangeListener?) {
@@ -93,6 +107,12 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
notifyListeners(setting) notifyListeners(setting)
} }
fun setVictoryPoints(setting: Int) {
this.victoryPoints = setting
updatePreference(VictoryPoints::class.simpleName, setting)
notifyListeners(setting)
}
private fun updatePreference(name: String?, value: String) { private fun updatePreference(name: String?, value: String) {
val editor = sharedPreferences.edit() val editor = sharedPreferences.edit()
editor.putString(name, value) editor.putString(name, value)
@@ -105,22 +125,33 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
editor.apply() editor.apply()
} }
private fun updatePreference(name: String?, value: Int) {
val editor = sharedPreferences.edit()
editor.putInt(name, value)
editor.apply()
}
private fun notifyListeners(language: Language) { private fun notifyListeners(language: Language) {
listenerList.forEach { listenerList.filterIsInstance<ISystemSettingsChangeListener>().forEach {
it.onLanguageChanged(language) it.onLanguageChanged(language)
} }
} }
private fun notifyListeners(theme: Theme) { private fun notifyListeners(theme: Theme) {
listenerList.forEach { listenerList.filterIsInstance<ISystemSettingsChangeListener>().forEach {
it.onThemeChanged(theme) it.onThemeChanged(theme)
} }
} }
private fun notifyListeners(keepScreenOn: KeepScreenOn) { private fun notifyListeners(victoryPoints: VictoryPoints) {
listenerList.forEach { listenerList.filterIsInstance<IGameSettingsChangeListener>().forEach {
it.onScreenOnChanged(keepScreenOn) it.onVictoryPointsChanged(victoryPoints)
} }
} }
private fun notifyListeners(keepScreenOn: KeepScreenOn) {
listenerList.filterIsInstance<ISystemSettingsChangeListener>().forEach {
it.onScreenOnChanged(keepScreenOn)
}
}
} }

View File

@@ -7,10 +7,8 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.ReviewService
import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.repository.GameRepository
import javax.inject.Inject import javax.inject.Inject

View File

@@ -7,25 +7,29 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun <T> DropDownMenu(map: Map<T, Int>, selected: T, expanded: Boolean, onSelected: (T?) -> Unit) { fun <T> DropDownMenu(
list: Collection<T>,
selected: T,
expanded: Boolean,
onSelected: (T?) -> Unit
) {
DropdownMenu( DropdownMenu(
expanded = expanded, expanded = expanded,
onDismissRequest = { onSelected(null) } onDismissRequest = { onSelected(null) }
) { ) {
map.forEach { list.forEach {
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
onSelected(it.key) onSelected(it)
}, },
trailingIcon = { trailingIcon = {
if (it.key == selected) { if (it == selected) {
Icon(Icons.Outlined.Check, null) Icon(Icons.Outlined.Check, null)
} }
}, },
text = { Text(stringResource(it.value)) }, text = { Text(it.toString()) },
) )
} }
} }

View File

@@ -3,7 +3,13 @@ package me.zobrist.tichucounter.ui.counter
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.EmojiEvents
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -12,7 +18,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import me.zobrist.tichucounter.R
import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.ui.AppTheme import me.zobrist.tichucounter.ui.AppTheme
@@ -23,6 +31,18 @@ fun Counter(viewModel: ICounterViewModel = PreviewViewModel()) {
var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) } var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }
orientation = LocalConfiguration.current.orientation orientation = LocalConfiguration.current.orientation
if (viewModel.showVictoryDialog) {
GameVictoryDialog(
viewModel.totalScoreA,
viewModel.totalScoreB,
viewModel.teamNameA,
viewModel.teamNameB,
{ viewModel.victoryDialogExecuted(false) })
{
viewModel.victoryDialogExecuted(true)
}
}
Surface { Surface {
if (orientation == Configuration.ORIENTATION_LANDSCAPE) { if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
Landscape(viewModel) Landscape(viewModel)
@@ -102,6 +122,54 @@ fun CounterViewPreview() {
} }
} }
@Preview()
@Composable
fun GameVictoryDialog(
pointsA: Int = 2000,
pointsB: Int = 50,
nameA: String = "nameA",
nameB: String = "nameB",
onDismiss: () -> Unit = {},
onNewGame: () -> Unit = {},
) {
val winner = if (pointsA > pointsB) {
nameA
} else {
nameB
}
val message = if (pointsA == pointsB) {
stringResource(R.string.draw_message, winner, 100)
} else {
stringResource(R.string.victory_message)
}
val title = if (pointsA == pointsB) {
stringResource(R.string.draw_title)
} else {
stringResource(R.string.victory_title, winner)
}
AlertDialog(
onDismissRequest = { onDismiss() },
dismissButton = {
TextButton({ onDismiss() }) {
Text(stringResource(R.string.continue_play))
}
},
confirmButton = {
TextButton({ onNewGame() }) {
Text(stringResource(R.string.new_game))
}
},
icon = { Icon(Icons.Outlined.EmojiEvents, null) },
title = { Text(title) },
text = { Text(message) }
)
}
internal class PreviewViewModel : ICounterViewModel { internal class PreviewViewModel : ICounterViewModel {
override var roundScoreList: List<Round> = override var roundScoreList: List<Round> =
listOf(Round(1, 10, 90), Round(1, 50, 50), Round(1, 70, 30)) listOf(Round(1, 10, 90), Round(1, 50, 50), Round(1, 70, 30))
@@ -123,6 +191,7 @@ internal class PreviewViewModel : ICounterViewModel {
listOf("TeamA", "asdffd", "TeamB", "really really long Team Name that is way too long") listOf("TeamA", "asdffd", "TeamB", "really really long Team Name that is way too long")
override val teamNameSuggestionsB: List<String> = override val teamNameSuggestionsB: List<String> =
listOf("TeamA", "asdffd", "TeamB", "really really long Team Name that is way too long") listOf("TeamA", "asdffd", "TeamB", "really really long Team Name that is way too long")
override var showVictoryDialog: Boolean = false
override fun focusLastInput() { override fun focusLastInput() {
} }
@@ -155,6 +224,9 @@ internal class PreviewViewModel : ICounterViewModel {
override fun updateNameB(value: String) { override fun updateNameB(value: String) {
} }
override fun victoryDialogExecuted(result: Boolean) {
}
override fun updateFocusStateA(state: Boolean) { override fun updateFocusStateA(state: Boolean) {
} }

View File

@@ -11,7 +11,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.entity.Game
import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.IGameSettingsChangeListener
import me.zobrist.tichucounter.domain.SettingsAdapter
import me.zobrist.tichucounter.domain.Tichu import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.domain.digitCount import me.zobrist.tichucounter.domain.digitCount
import me.zobrist.tichucounter.domain.getTotalPoints import me.zobrist.tichucounter.domain.getTotalPoints
@@ -58,16 +61,19 @@ interface ICounterViewModel : IKeyBoardViewModel {
val teamNameB: String val teamNameB: String
val teamNameSuggestionsA: List<String> val teamNameSuggestionsA: List<String>
val teamNameSuggestionsB: List<String> val teamNameSuggestionsB: List<String>
val showVictoryDialog: Boolean
fun updateNameA(value: String) fun updateNameA(value: String)
fun updateNameB(value: String) fun updateNameB(value: String)
fun victoryDialogExecuted(result: Boolean)
} }
@HiltViewModel @HiltViewModel
class CounterViewModel @Inject constructor( class CounterViewModel @Inject constructor(
private val gameRepository: GameRepository private val gameRepository: GameRepository,
private val settings: SettingsAdapter
) : ) :
ViewModel(), ICounterViewModel { ViewModel(), ICounterViewModel, IGameSettingsChangeListener {
override var roundScoreList by mutableStateOf(emptyList<Round>()) override var roundScoreList by mutableStateOf(emptyList<Round>())
private set private set
@@ -113,6 +119,8 @@ class CounterViewModel @Inject constructor(
override var teamNameSuggestionsB by mutableStateOf(listOf<String>()) override var teamNameSuggestionsB by mutableStateOf(listOf<String>())
private set private set
override var showVictoryDialog by mutableStateOf(false)
private set
override var activeValue: String override var activeValue: String
get() { get() {
@@ -155,11 +163,14 @@ class CounterViewModel @Inject constructor(
private var distinctTeamNames = listOf<String>() private var distinctTeamNames = listOf<String>()
private var victoryDialogShown = false
private var lastGame: Game? = null
init { init {
viewModelScope.launch { viewModelScope.launch {
gameRepository.getActiveGameFlow().collect { gameRepository.getActiveGameFlow().collect {
if (it != null) { if (it != null) {
val score = it.getTotalPoints() val score = it.getTotalPoints()
roundScoreList = it.rounds roundScoreList = it.rounds
@@ -171,6 +182,16 @@ class CounterViewModel @Inject constructor(
buildTeamNameSuggestions() buildTeamNameSuggestions()
if (it.game.uid != lastGame?.uid) {
victoryDialogShown = false
lastGame = it.game
}
if (!victoryDialogShown) {
if (totalScoreA >= settings.victoryPoints || totalScoreB >= settings.victoryPoints) {
showVictoryDialog = true
}
}
} }
} }
} }
@@ -182,6 +203,12 @@ class CounterViewModel @Inject constructor(
buildTeamNameSuggestions() buildTeamNameSuggestions()
} }
} }
settings.registerOnChangeListener(this)
}
override fun onCleared() {
settings.unregisterOnChangeListener(this)
} }
override fun focusLastInput() { override fun focusLastInput() {
@@ -290,6 +317,17 @@ class CounterViewModel @Inject constructor(
} }
} }
override fun victoryDialogExecuted(result: Boolean) {
showVictoryDialog = false
victoryDialogShown = true
if (result) {
viewModelScope.launch {
gameRepository.newGame()
}
}
}
override fun updateFocusStateA(state: Boolean) { override fun updateFocusStateA(state: Boolean) {
isAFocused = state isAFocused = state
if (state) { if (state) {
@@ -363,4 +401,8 @@ class CounterViewModel @Inject constructor(
return filtered.sorted().sortedBy { it.length }.take(10) return filtered.sorted().sortedBy { it.length }.take(10)
} }
override fun onVictoryPointsChanged(victoryPoints: Int) {
victoryDialogShown = false
}
} }

View File

@@ -44,6 +44,7 @@ val themeMap = mapOf(
Theme.LIGHT to R.string.light Theme.LIGHT to R.string.light
) )
val victoryPointsList = listOf(500, 1000, 1500, 2000)
@Composable @Composable
fun SettingsView(viewModel: SettingsViewModel) { fun SettingsView(viewModel: SettingsViewModel) {
@@ -51,9 +52,11 @@ fun SettingsView(viewModel: SettingsViewModel) {
viewModel.screenOn.value, viewModel.screenOn.value,
viewModel.language, viewModel.language,
viewModel.theme, viewModel.theme,
viewModel.victoryPoints,
{ viewModel.updateScreenOn(it) }, { viewModel.updateScreenOn(it) },
{ viewModel.updateLanguage(it) }, { viewModel.updateLanguage(it) },
{ viewModel.updateTheme(it) }) { viewModel.updateTheme(it) },
{ viewModel.updateVictoryPoints(it) })
} }
@Composable @Composable
@@ -61,11 +64,22 @@ fun SettingsView(
valueScreenOn: Boolean = true, valueScreenOn: Boolean = true,
valueLanguage: Language = Language.ENGLISH, valueLanguage: Language = Language.ENGLISH,
valueTheme: Theme = Theme.DARK, valueTheme: Theme = Theme.DARK,
valueVictoryPoints: Int = 1000,
updateScreenOn: (KeepScreenOn) -> Unit = {}, updateScreenOn: (KeepScreenOn) -> Unit = {},
updateLanguage: (Language) -> Unit = {}, updateLanguage: (Language) -> Unit = {},
updateTheme: (Theme) -> Unit = {} updateTheme: (Theme) -> Unit = {},
updateVictoryPoints: (Int) -> Unit = {}
) { ) {
Column { Column(
Modifier
.padding(20.dp)
) {
Text(
text = stringResource(R.string.display),
style = MaterialTheme.typography.headlineMedium
)
BooleanSetting( BooleanSetting(
stringResource(R.string.keep_screen_on), stringResource(R.string.keep_screen_on),
valueScreenOn valueScreenOn
@@ -82,6 +96,18 @@ fun SettingsView(
themeMap, themeMap,
valueTheme, valueTheme,
) { updateTheme(it) } ) { updateTheme(it) }
Text(
text = stringResource(R.string.game),
style = MaterialTheme.typography.headlineMedium
)
ListSetting(
stringResource(R.string.victory_points),
victoryPointsList,
valueVictoryPoints
) { updateVictoryPoints(it) }
} }
} }
@@ -90,7 +116,7 @@ fun BooleanSetting(name: String, value: Boolean, updateValue: (Boolean) -> Unit)
Row( Row(
Modifier Modifier
.padding(20.dp) .padding(bottom = 15.dp, top = 5.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
Column(Modifier.weight(5f)) { Column(Modifier.weight(5f)) {
@@ -119,21 +145,32 @@ fun BooleanSetting(name: String, value: Boolean, updateValue: (Boolean) -> Unit)
@Composable @Composable
fun <T> StringSetting(name: String, map: Map<T, Int>, selected: T, onSelected: (T) -> Unit) { fun <T> StringSetting(name: String, map: Map<T, Int>, selected: T, onSelected: (T) -> Unit) {
val translated = map.map { it.key to stringResource(it.value) }.toMap()
val getValue = map.map { stringResource(it.value) to it.key }.toMap()
ListSetting(
name,
translated.values,
translated[selected]
) { getValue[it]?.let { it1 -> onSelected(it1) } }
}
@Composable
fun <T> ListSetting(name: String, list: Collection<T>, selected: T, onSelected: (T) -> Unit) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
Row( Row(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(20.dp) .padding(bottom = 15.dp, top = 5.dp)
.clickable { expanded = true }) { .clickable { expanded = true }) {
Column(Modifier.weight(5f)) { Column(Modifier.weight(5f)) {
Text(name, style = MaterialTheme.typography.bodyLarge, overflow = TextOverflow.Ellipsis) Text(name, style = MaterialTheme.typography.bodyLarge, overflow = TextOverflow.Ellipsis)
map[selected]?.let { Text(
Text( selected.toString(),
stringResource(it), style = MaterialTheme.typography.labelLarge
style = MaterialTheme.typography.labelLarge )
)
}
} }
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
@@ -142,15 +179,15 @@ fun <T> StringSetting(name: String, map: Map<T, Int>, selected: T, onSelected: (
contentDescription = null, contentDescription = null,
modifier = Modifier.align(End) modifier = Modifier.align(End)
) )
}
DropDownMenu( DropDownMenu(
map, list,
selected, selected,
expanded, expanded,
) { ) {
expanded = false expanded = false
it?.let { onSelected(it) } it?.let { onSelected(it) }
}
} }
} }
} }
@@ -167,20 +204,3 @@ 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,
) {}
}
}
}

View File

@@ -24,6 +24,9 @@ class SettingsViewModel @Inject constructor(private val settings: SettingsAdapte
var screenOn by mutableStateOf(settings.keepScreenOn) var screenOn by mutableStateOf(settings.keepScreenOn)
private set private set
var victoryPoints by mutableStateOf(settings.victoryPoints)
private set
fun updateLanguage(language: Language) { fun updateLanguage(language: Language) {
settings.setLanguage(language) settings.setLanguage(language)
this.language = settings.language this.language = settings.language
@@ -39,4 +42,8 @@ class SettingsViewModel @Inject constructor(private val settings: SettingsAdapte
screenOn = settings.keepScreenOn screenOn = settings.keepScreenOn
} }
fun updateVictoryPoints(value: Int) {
settings.setVictoryPoints(value)
victoryPoints = settings.victoryPoints
}
} }

View File

@@ -12,7 +12,7 @@
<string name="menu_settings">Einstellungen</string> <string name="menu_settings">Einstellungen</string>
<string name="on">Ein</string> <string name="on">Ein</string>
<string name="off">Aus</string> <string name="off">Aus</string>
<string name="newGame">Neues Spiel</string> <string name="new_game">Neues Spiel</string>
<string name="delete_inactive_title">Verlauf löschen</string> <string name="delete_inactive_title">Verlauf löschen</string>
<string name="delete_inactive_text">Wirklich den gesamten Verlauf löschen? Diese Aktion kann nicht rückgängig gemacht werden.</string> <string name="delete_inactive_text">Wirklich den gesamten Verlauf löschen? Diese Aktion kann nicht rückgängig gemacht werden.</string>
<string name="cancel">Abbrechen</string> <string name="cancel">Abbrechen</string>
@@ -29,5 +29,11 @@
<string name="undo_question">RÜCKGÄNGIG</string> <string name="undo_question">RÜCKGÄNGIG</string>
<string name="activated_success">Spiel aktiviert.</string> <string name="activated_success">Spiel aktiviert.</string>
<string name="to_calculator_question">WEITERSPIELEN</string> <string name="to_calculator_question">WEITERSPIELEN</string>
<string name="display">Anzeige</string>
<string name="game">Spiel</string>
<string name="victory_points">Siegespunkte</string>
<string name="victory_title">%1$s hat gewonnen</string>
<string name="draw_message">Sieht aus, als ob ihr ein neues Spiel starten solltet, um das endgültig zu klären.</string>
<string name="draw_title">Unentschieden</string>
<string name="victory_message">Herzliche Gratulation! Wie wäre es mit einer Revanche?</string>
</resources> </resources>

View File

@@ -15,7 +15,7 @@
<string name="menu_settings">Settings</string> <string name="menu_settings">Settings</string>
<string name="on">On</string> <string name="on">On</string>
<string name="off">Off</string> <string name="off">Off</string>
<string name="newGame">New Game</string> <string name="new_game">New Game</string>
<string name="delete_inactive_title">Delete history</string> <string name="delete_inactive_title">Delete history</string>
<string name="delete_inactive_text">You really want to delete the history? This action can\'t be undone.</string> <string name="delete_inactive_text">You really want to delete the history? This action can\'t be undone.</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
@@ -28,9 +28,16 @@
<string name="menu_about">About</string> <string name="menu_about">About</string>
<string name="contact_us">Contact us</string> <string name="contact_us">Contact us</string>
<string name="play_store" translatable="false">Play Store</string> <string name="play_store" translatable="false">Play Store</string>
<string name="continue_play">Continue</string> <string name="continue_play">Continue game</string>
<string name="delete_success">Game deleted.</string> <string name="delete_success">Game deleted.</string>
<string name="undo_question">UNDO</string> <string name="undo_question">UNDO</string>
<string name="activated_success">Game activated.</string> <string name="activated_success">Game activated.</string>
<string name="to_calculator_question">CONTINUE PLAYING</string> <string name="to_calculator_question">CONTINUE PLAYING</string>
<string name="display">Display</string>
<string name="game">Game</string>
<string name="victory_points">Victory points</string>
<string name="victory_title">%1$s won the game</string>
<string name="draw_message">Looks like you should start a new game to settle this for good.</string>
<string name="draw_title">Draw</string>
<string name="victory_message">Congratulations! How about a rematch?</string>
</resources> </resources>