From eac916d8ec2a9bb930797389eb94654eab4ac78a Mon Sep 17 00:00:00 2001 From: Fabian Zobrist Date: Fri, 20 Jan 2023 11:33:05 +0100 Subject: [PATCH] Style keyboard. Add Counter previerw. --- .../tichucounter/ui/counter/CounterView.kt | 77 +++++- .../ui/counter/CounterViewModel.kt | 93 ++++--- .../tichucounter/ui/counter/KeyboardView.kt | 235 ++++++++++-------- .../tichucounter/ui/counter/TeamScoresView.kt | 9 +- 4 files changed, 263 insertions(+), 151 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 3ee9807..44d67a0 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 @@ -6,11 +6,15 @@ import androidx.compose.foundation.layout.Row import androidx.compose.material3.Surface import androidx.compose.runtime.* 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.ui.AppTheme @Composable -fun Counter(viewModel: CounterViewModel) { +fun Counter(viewModel: ICounterViewModel = PreviewViewModel()) { var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) } orientation = LocalConfiguration.current.orientation @@ -25,7 +29,7 @@ fun Counter(viewModel: CounterViewModel) { } @Composable -fun Landscape(viewModel: CounterViewModel) { +fun Landscape(viewModel: ICounterViewModel) { Row { Column(Modifier.weight(1f)) { TeamNamesView( @@ -65,7 +69,7 @@ fun Landscape(viewModel: CounterViewModel) { } @Composable -fun Portrait(viewModel: CounterViewModel) { +fun Portrait(viewModel: ICounterViewModel) { Column { TeamNamesView( @@ -98,4 +102,71 @@ fun Portrait(viewModel: CounterViewModel) { { viewModel.negateClicked() } ) { viewModel.submitClicked() } } +} + + +@Preview(name = "Light Mode") +@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Composable +fun CounterViewPreview() { + AppTheme { + Counter() + } +} + +internal class PreviewViewModel: ICounterViewModel { + override var roundScoreList: List = listOf(Round(1, 10, 90), Round(1,50,50), Round(1,70,30)) + override var totalScoreA: Int = 350 + override var totalScoreB: Int = 750 + override var teamNameA: String = "Team A" + override var teamNameB: String = "Team B" + override var currentScoreA: String = "" + override var currentScoreB: String = "45" + override var enableSubmit: Boolean = false + override var isAFocused: Boolean = false + override var isBFocused: Boolean = false + override var requestFocusA: FocusRequester = FocusRequester() + override var activeValue: String = currentScoreA + override var inactiveValue: String = currentScoreB + + override fun giveFocusToAIfNone() { + } + + override fun updateOtherScore() { + } + + override fun isValidTichuRound(): Boolean { + return true + } + + override fun updateSubmitButton() { + } + + override fun submitClicked() { + } + + override fun digitClicked(digit: String) { + } + + override fun negateClicked() { + } + + override fun addSub100Clicked(toAdd: Int) { + } + + override fun deleteClicked() { + } + + override fun updateNameA(value: String) { + } + + override fun updateNameB(value: String) { + } + + override fun updateFocusStateA(state: Boolean) { + } + + override fun updateFocusStateB(state: Boolean) { + } + } \ 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 1beedaa..ed5e0d3 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 @@ -15,45 +15,67 @@ import me.zobrist.tichucounter.domain.Tichu 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 + + fun giveFocusToAIfNone() + fun updateOtherScore() + fun isValidTichuRound(): Boolean + fun updateSubmitButton() + fun submitClicked() + fun digitClicked(digit: String) + fun negateClicked() + fun addSub100Clicked(toAdd: Int) + fun deleteClicked() + fun updateNameA(value: String) + fun updateNameB(value: String) + fun updateFocusStateA(state: Boolean) + fun updateFocusStateB(state: Boolean) +} + @HiltViewModel class CounterViewModel @Inject constructor( private val gameRepository: GameRepository, private val roundDao: RoundDao, private val gameDao: GameDao ) : - ViewModel() { + ViewModel(), ICounterViewModel { - var roundScoreList by mutableStateOf(emptyList()) + override var roundScoreList by mutableStateOf(emptyList()) - var totalScoreA by mutableStateOf(0) - private set + override var totalScoreA by mutableStateOf(0) - var totalScoreB by mutableStateOf(0) - private set + override var totalScoreB by mutableStateOf(0) - var teamNameA by mutableStateOf("") - private set + override var teamNameA by mutableStateOf("") - var teamNameB by mutableStateOf("") - private set + override var teamNameB by mutableStateOf("") - var currentScoreA by mutableStateOf("") - private set + override var currentScoreA by mutableStateOf("") - var currentScoreB by mutableStateOf("") - private set + override var currentScoreB by mutableStateOf("") - var enableSubmit by mutableStateOf(false) - private set + override var enableSubmit by mutableStateOf(false) - var isAFocused by mutableStateOf(false) + override var isAFocused by mutableStateOf(false) - var isBFocused by mutableStateOf(false) + override var isBFocused by mutableStateOf(false) - var requestFocusA by mutableStateOf(FocusRequester()) - private set + override var requestFocusA by mutableStateOf(FocusRequester()) - private var activeValue: String + override var activeValue: String get() { return if (isBFocused) { currentScoreB @@ -69,7 +91,7 @@ class CounterViewModel @Inject constructor( } } - private var inactiveValue: String + override var inactiveValue: String get() { return if (isAFocused) { currentScoreB @@ -109,13 +131,13 @@ class CounterViewModel @Inject constructor( } } - private fun giveFocusToAIfNone() { + override fun giveFocusToAIfNone() { if (!isAFocused && !isBFocused) { requestFocusA.requestFocus() } } - private fun updateOtherScore() { + override fun updateOtherScore() { inactiveValue = try { val tichu = Tichu() val myScore = activeValue.toInt() @@ -130,7 +152,7 @@ class CounterViewModel @Inject constructor( } } - private fun isValidTichuRound(): Boolean { + override fun isValidTichuRound(): Boolean { return try { val tichu = Tichu() tichu.isValidRound(currentScoreA.toInt(), currentScoreB.toInt()) @@ -139,11 +161,11 @@ class CounterViewModel @Inject constructor( } } - private fun updateSubmitButton() { + override fun updateSubmitButton() { enableSubmit = isValidTichuRound() } - fun submitClicked() { + override fun submitClicked() { viewModelScope.launch { gameRepository.addRoundToActiveGame(currentScoreA.toInt(), currentScoreB.toInt()) } @@ -152,7 +174,7 @@ class CounterViewModel @Inject constructor( enableSubmit = false } - fun digitClicked(digit: String) { + override fun digitClicked(digit: String) { giveFocusToAIfNone() activeValue += digit @@ -160,7 +182,7 @@ class CounterViewModel @Inject constructor( updateSubmitButton() } - fun negateClicked() { + override fun negateClicked() { giveFocusToAIfNone() activeValue = if (activeValue.contains("-")) { @@ -172,7 +194,7 @@ class CounterViewModel @Inject constructor( updateSubmitButton() } - fun addSub100Clicked(toAdd: Int) { + override fun addSub100Clicked(toAdd: Int) { giveFocusToAIfNone() activeValue = try { @@ -188,14 +210,15 @@ class CounterViewModel @Inject constructor( updateSubmitButton() } - fun deleteClicked() { + override fun deleteClicked() { if (activeValue != "") { activeValue = activeValue.dropLast(1) } updateOtherScore() + updateSubmitButton() } - fun updateNameA(value: String) { + override fun updateNameA(value: String) { viewModelScope.launch { val game = gameRepository.activeGame game.nameA = value @@ -203,7 +226,7 @@ class CounterViewModel @Inject constructor( } } - fun updateNameB(value: String) { + override fun updateNameB(value: String) { viewModelScope.launch { val game = gameRepository.activeGame game.nameB = value @@ -211,11 +234,11 @@ class CounterViewModel @Inject constructor( } } - fun updateFocusStateA(state: Boolean) { + override fun updateFocusStateA(state: Boolean) { isAFocused = state } - fun updateFocusStateB(state: Boolean) { + override fun updateFocusStateB(state: Boolean) { isBFocused = state } } \ 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 470b71d..b80f469 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 @@ -15,21 +15,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.helper.widget.MotionPlaceholder import me.zobrist.tichucounter.R import me.zobrist.tichucounter.ui.AppTheme -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@OptIn(ExperimentalComposeUiApi::class) @Composable fun KeyboardView( scoreA: String, scoreB: String, - requestFocusA: FocusRequester, + requestFocus: FocusRequester, enableSubmit: Boolean, updateFocusStateA: (Boolean) -> Unit, updateFocusStateB: (Boolean) -> Unit, @@ -44,139 +47,156 @@ fun KeyboardView( Column { Row { Column(Modifier.weight(1f)) { - CenteredTextField( scoreA, - keyboardController, - requestFocusA - ) { - updateFocusStateA(it) - } + "0", + Modifier + .focusRequester(requestFocus) + .onFocusChanged { + keyboardController?.hide() + updateFocusStateA(it.isFocused) + } + ) } Column(Modifier.weight(1f)) { CenteredTextField( scoreB, - keyboardController, - null - ) { - updateFocusStateB(it) + "0", + Modifier + .onFocusChanged { + keyboardController?.hide() + updateFocusStateB(it.isFocused) + } + ) + } + } + + Row { + Column(Modifier.weight(1f)) { + KeyboardTextButton("1") { + digitClicked("1") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("2") { + digitClicked("2") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("3") { + digitClicked("3") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("+100") { + addSub100Clicked(100) + } + } + } + Row { + Column(Modifier.weight(1f)) { + KeyboardTextButton("4") { + digitClicked("4") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("5") { + digitClicked("5") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("6") { + digitClicked("6") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("-100") { + addSub100Clicked(-100) } } } Row { - TextButton( - onClick = { digitClicked("1") }, - modifier = Modifier.weight(1F) - ) { Text("1") } - TextButton( - onClick = { digitClicked("2") }, - modifier = Modifier.weight(1F) - ) { Text("2") } - TextButton( - onClick = { digitClicked("3") }, - modifier = Modifier.weight(1F) - ) { Text("3") } - TextButton( - onClick = { addSub100Clicked(100) }, - modifier = Modifier.weight(1F) - ) { Text("+100") } - - } - Row { - TextButton( - onClick = { digitClicked("4") }, - modifier = Modifier.weight(1F) - ) { Text("4") } - TextButton( - onClick = { digitClicked("5") }, - modifier = Modifier.weight(1F) - ) { Text("5") } - TextButton( - onClick = { digitClicked("6") }, - modifier = Modifier.weight(1F) - ) { Text("6") } - TextButton( - onClick = { addSub100Clicked(-100) }, - modifier = Modifier.weight(1F) - ) { Text("-100") } - } - Row { - TextButton( - onClick = { digitClicked("7") }, - modifier = Modifier.weight(1F) - ) { Text("7") } - TextButton( - onClick = { digitClicked("8") }, - modifier = Modifier.weight(1F) - ) { Text("8") } - TextButton( - onClick = { digitClicked("9") }, - modifier = Modifier.weight(1F) - ) { Text("9") } - IconButton( - onClick = { deleteClicked() }, - modifier = Modifier.weight(1F) - ) { - Icon( - Icons.Outlined.Backspace, - stringResource(R.string.back), - tint = MaterialTheme.colorScheme.primary - ) + Column(Modifier.weight(1f)) { + KeyboardTextButton("7") { + digitClicked("7") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("8") { + digitClicked("8") + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("9") { + digitClicked("9") + } + } + Column(Modifier.weight(1f)) { + KeyboardIconButton(Icons.Outlined.Backspace) { + deleteClicked() + } } } + Row { - TextButton( - onClick = { negateClicked() }, - modifier = Modifier.weight(1F) - ) { Text("+/-") } - TextButton( - onClick = { digitClicked("0") }, - modifier = Modifier.weight(1F) - ) { Text("0") } - Spacer(modifier = Modifier.weight(1F)) - IconButton( - onClick = { submitClicked() }, - modifier = Modifier.weight(1F), - enabled = enableSubmit - ) { - Icon( - Icons.Outlined.Check, - stringResource(R.string.submit), - tint = MaterialTheme.colorScheme.primary - ) + Column(Modifier.weight(1f)) { + KeyboardTextButton("+/-") { + negateClicked() + } + } + Column(Modifier.weight(1f)) { + KeyboardTextButton("0") { + digitClicked("0") + } + } + Column(Modifier.weight(2f)) { + KeyboardIconButton(Icons.Outlined.Check, enableSubmit) { + submitClicked() + } } } } } -@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) +@Composable +fun KeyboardTextButton(text: String, onClicked: () -> Unit) { + ElevatedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { onClicked() }, + ) { Text(text) } +} + +@Composable +fun KeyboardIconButton(icon: ImageVector, enabled: Boolean = true, onClicked: () -> Unit) { + + ElevatedButton( + onClick = { onClicked() }, + modifier = Modifier.fillMaxWidth(), + enabled = enabled, + ) { + Icon( + icon, + contentDescription = null + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CenteredTextField( value: String, - keyboardController: SoftwareKeyboardController?, - focusRequester: FocusRequester?, - updateFocusState: (Boolean) -> Unit + placeholder: String, + modifier: Modifier, ) { - var modifier = Modifier - .onFocusChanged { - keyboardController?.hide() - updateFocusState(it.isFocused) - } - .fillMaxWidth() - - if (focusRequester != null) { - modifier.focusRequester(focusRequester) - } - TextField( value = value, onValueChange = { }, placeholder = { Text( - "0", + placeholder, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) @@ -184,12 +204,11 @@ fun CenteredTextField( textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), singleLine = true, readOnly = true, - modifier = modifier + modifier = modifier.fillMaxWidth() ) } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Preview(name = "Light Mode") @Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) @Composable @@ -210,6 +229,4 @@ fun KeyboardViewPreview() { {}) } } - - } \ No newline at end of file diff --git a/app/src/main/java/me/zobrist/tichucounter/ui/counter/TeamScoresView.kt b/app/src/main/java/me/zobrist/tichucounter/ui/counter/TeamScoresView.kt index 6ace042..6215a36 100644 --- a/app/src/main/java/me/zobrist/tichucounter/ui/counter/TeamScoresView.kt +++ b/app/src/main/java/me/zobrist/tichucounter/ui/counter/TeamScoresView.kt @@ -3,6 +3,7 @@ package me.zobrist.tichucounter.ui.counter import android.content.res.Configuration import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -21,18 +22,18 @@ fun TeamScoresView(scoreA: Int, scoreB: Int) { ElevatedCard() { Row() { Text( - style = MaterialTheme.typography.bodyLarge, + style = MaterialTheme.typography.headlineSmall, text = scoreA.toString(), - modifier = Modifier.weight(5f), + modifier = Modifier.weight(5f).padding(6.dp), textAlign = TextAlign.Center ) Spacer(modifier = Modifier.weight(1f)) Text( - style = MaterialTheme.typography.bodyLarge, + style = MaterialTheme.typography.headlineSmall, text = scoreB.toString(), - modifier = Modifier.weight(5f), + modifier = Modifier.weight(5f).padding(6.dp), textAlign = TextAlign.Center ) }