Move all functionality to viewmodel. Use simple button format.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2023-01-07 19:30:56 +01:00
parent ae6210073d
commit 5f6da1d7d4
6 changed files with 192 additions and 183 deletions

View File

@@ -97,6 +97,7 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.compose.material:material-icons-extended:1.3.1'
implementation 'androidx.activity:activity-compose:1.3.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"

View File

@@ -1,6 +1,5 @@
package me.zobrist.tichucounter.domain
import me.zobrist.tichucounter.data.Round
import javax.inject.Inject
class Tichu @Inject constructor() {

View File

@@ -13,8 +13,6 @@ import kotlinx.coroutines.launch
import me.zobrist.tichucounter.R
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.databinding.FragmentCounterBinding
import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.domain.getAbsoluteDifference
import me.zobrist.tichucounter.repository.GameRepository
import me.zobrist.tichucounter.ui.FragmentBase
import javax.inject.Inject

View File

@@ -1,6 +1,5 @@
package me.zobrist.tichucounter.ui.counter
import android.icu.number.FormattedNumber
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -8,8 +7,14 @@ import android.view.ViewGroup
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Backspace
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -18,16 +23,13 @@ import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.zobrist.tichucounter.R
import me.zobrist.tichucounter.databinding.FragmentKeyboardBinding
import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.ui.FragmentBase
import me.zobrist.tichucounter.ui.history.IHistoryFragmentViewModel
@AndroidEntryPoint
class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
@@ -38,28 +40,12 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
private val viewModel: KeyboardViewModel by activityViewModels()
private val requester = FocusRequester()
private var isAFocused: Boolean = false
private var isBFocused: Boolean = false
var enableSubmit = mutableStateOf(false)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
snapshotFlow { viewModel.scoreA.value }
.onEach {
enableSubmitOnValidRound(viewModel)
}
snapshotFlow { viewModel.scoreB.value }
.onEach {
enableSubmitOnValidRound(viewModel)
}
return ComposeView(requireContext()).apply {
// Dispose of the Composition when the view's LifecycleOwner
// is destroyed
@@ -72,110 +58,6 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
}
}
private fun enableSubmitOnValidRound(viewModel: IKeyboardViewModel) {
enableSubmit.value = try {
val tichu = Tichu()
if(tichu.isValidRound(viewModel.scoreA.value.toInt(), viewModel.scoreB.value.toInt()))
{
true
}
false
} catch(_:Exception)
{
false
}
}
private fun giveFocusToAIfNone() {
if(!isAFocused && !isBFocused)
{
requester.requestFocus()
}
}
private fun appendToFocusedScore(toAppend: String, viewModel: IKeyboardViewModel) {
val value = getActiveValue(viewModel)
value.value += toAppend
updateOtherScore(viewModel)
}
private fun updateOtherScore(viewModel: IKeyboardViewModel) {
val value = getActiveValue(viewModel)
try {
val tichu = Tichu()
val myScore = value.value.toInt()
val hisScore = tichu.calculateOtherScore(myScore)
if(tichu.isValidRound(myScore, hisScore))
{
updateInactiveValue(hisScore?.toString() ?: "", viewModel)
} else
{
updateInactiveValue("", viewModel)
}
} catch(_: Exception) {
updateInactiveValue("", viewModel)
}
}
private fun negateActiveInput(viewModel: IKeyboardViewModel) {
val value = getActiveValue(viewModel)
if(value.value.contains("-"))
{
value.value = value.value.replace("-", "")
} else {
value.value = "-" + value.value
}
updateOtherScore(viewModel)
}
private fun addToActiveInput(toAdd: Int, viewModel: IKeyboardViewModel) {
val value = getActiveValue(viewModel)
try {
val temp = value.value.toInt() + toAdd
value.value = temp.toString()
} catch (e: Exception) {
value.value = toAdd.toString()
}
}
private fun removeFromActiveInput(viewModel: IKeyboardViewModel) {
val value = getActiveValue(viewModel)
if(value.value != "") {
value.value = value.value.dropLast(1)
}
updateOtherScore(viewModel)
}
private fun getActiveValue(viewModel: IKeyboardViewModel): MutableState<String> {
giveFocusToAIfNone()
if (isBFocused) {
return viewModel.scoreB
}
return viewModel.scoreA
}
private fun updateInactiveValue(value: String, viewModel: IKeyboardViewModel){
giveFocusToAIfNone()
if (isBFocused) {
viewModel.scoreA.value = value
} else
{
viewModel.scoreB.value = value
}
try {
val tichu = Tichu()
enableSubmit.value = tichu.isValidRound(viewModel.scoreA.value.toInt(), viewModel.scoreB.value.toInt())
} catch(_: java.lang.NumberFormatException) {
enableSubmit.value = false
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Preview
@Composable
@@ -195,9 +77,9 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
modifier = Modifier
.onFocusChanged {
keyboardController?.hide()
isAFocused = it.isFocused
viewModel.isAFocused.value = it.isFocused
}
.focusRequester(requester)
.focusRequester(viewModel.requestFocusA.value)
.weight(1f),
colors = TextFieldDefaults.textFieldColors(
cursorColor = Color.Transparent,
@@ -212,7 +94,7 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
modifier = Modifier
.onFocusChanged {
keyboardController?.hide()
isBFocused = it.isFocused
viewModel.isBFocused.value = it.isFocused
}
.weight(1f),
colors = TextFieldDefaults.textFieldColors(
@@ -224,70 +106,79 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
}
Row {
Button(
onClick = { appendToFocusedScore("1", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("1") },
modifier = Modifier.weight(1F)
) { Text("1") }
Button(
onClick = { appendToFocusedScore("2", viewModel ) },
TextButton(
onClick = { viewModel.appendToFocusedScore("2") },
modifier = Modifier.weight(1F)
) { Text("2") }
Button(
onClick = { appendToFocusedScore("3", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("3") },
modifier = Modifier.weight(1F)
) { Text("3") }
Button(onClick = { addToActiveInput(100, viewModel) }, modifier = Modifier.weight(1F)) { Text("+100") }
TextButton(
onClick = { viewModel.addToActiveInput(100) },
modifier = Modifier.weight(1F)
) { Text("+100") }
}
Row {
Button(
onClick = { appendToFocusedScore("4", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("4") },
modifier = Modifier.weight(1F)
) { Text("4") }
Button(
onClick = { appendToFocusedScore("5", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("5") },
modifier = Modifier.weight(1F)
) { Text("5") }
Button(
onClick = { appendToFocusedScore("6", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("6") },
modifier = Modifier.weight(1F)
) { Text("6") }
Button(onClick = { addToActiveInput(-100, viewModel) }, modifier = Modifier.weight(1F)) { Text("-100") }
TextButton(
onClick = { viewModel.addToActiveInput(-100) },
modifier = Modifier.weight(1F)
) { Text("-100") }
}
Row {
Button(
onClick = { appendToFocusedScore("7", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("7") },
modifier = Modifier.weight(1F)
) { Text("7") }
Button(
onClick = { appendToFocusedScore("8", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("8") },
modifier = Modifier.weight(1F)
) { Text("8") }
Button(
onClick = { appendToFocusedScore("9", viewModel) },
TextButton(
onClick = { viewModel.appendToFocusedScore("9") },
modifier = Modifier.weight(1F)
) { Text("9") }
Button(onClick = { removeFromActiveInput(viewModel) }, modifier = Modifier.weight(1F)) { Text("DEL") }
IconButton(
onClick = { viewModel.removeLastCharFromActive() },
modifier = Modifier.weight(1F)
) { Icon(Icons.Outlined.Backspace, stringResource(R.string.submit)) }
}
Row {
Button(onClick = { negateActiveInput(viewModel) }, modifier = Modifier.weight(1F)) { Text("+/-") }
Button(
onClick = { appendToFocusedScore("0", viewModel) },
TextButton(
onClick = { viewModel.negateActiveInput() },
modifier = Modifier.weight(1F)
) { Text("+/-") }
TextButton(
onClick = { viewModel.appendToFocusedScore("0") },
modifier = Modifier.weight(1F)
) { Text("0") }
Spacer(modifier = Modifier.weight(1F))
Button(onClick = { submit(viewModel) }, modifier = Modifier.weight(1F), enabled = enableSubmit.value) { Text("ENTER") }
IconButton(
onClick = { viewModel.submitScore() },
modifier = Modifier.weight(1F),
enabled = viewModel.enableSubmit.value
) { Icon(Icons.Outlined.Check, stringResource(R.string.submit)) }
}
}
}
private fun submit(viewModel: IKeyboardViewModel) {
viewModel.submitScore(viewModel.scoreA.value.toInt(), viewModel.scoreB.value.toInt())
viewModel.scoreA.value = ""
viewModel.scoreB.value = ""
enableSubmit.value = false
}
object EmptyTextToolbar : TextToolbar {
override val status: TextToolbarStatus = TextToolbarStatus.Hidden
@@ -306,8 +197,29 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
internal class DefaultViewModel : IKeyboardViewModel {
override var scoreA: MutableState<String> = mutableStateOf("")
override var scoreB: MutableState<String> = mutableStateOf("")
override var enableSubmit = mutableStateOf(false)
override var isAFocused = mutableStateOf(false)
override var isBFocused = mutableStateOf(false)
override var requestFocusA = mutableStateOf(FocusRequester())
override fun submitScore(scoreA: Int, scoreB: Int) {
override fun appendToFocusedScore(toAppend: String) {
TODO("Not yet implemented")
}
override fun negateActiveInput() {
TODO("Not yet implemented")
}
override fun addToActiveInput(toAdd: Int) {
TODO("Not yet implemented")
}
override fun removeLastCharFromActive() {
TODO("Not yet implemented")
}
override fun submitScore() {
TODO("Not yet implemented")
}
}

View File

@@ -1,16 +1,11 @@
package me.zobrist.tichucounter.ui.counter
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.compose.ui.focus.FocusRequester
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.repository.GameRepository
@@ -19,7 +14,20 @@ import javax.inject.Inject
interface IKeyboardViewModel {
var scoreA: MutableState<String>
var scoreB: MutableState<String>
fun submitScore(scoreA: Int, scoreB: Int)
var enableSubmit: MutableState<Boolean>
var isAFocused: MutableState<Boolean>
var isBFocused: MutableState<Boolean>
var requestFocusA: MutableState<FocusRequester>
fun appendToFocusedScore(toAppend: String)
fun negateActiveInput()
fun addToActiveInput(toAdd: Int)
fun removeLastCharFromActive()
fun submitScore()
}
@HiltViewModel
@@ -27,11 +35,103 @@ class KeyboardViewModel @Inject constructor(private val gameRepository: GameRepo
ViewModel(), IKeyboardViewModel {
override var scoreA = mutableStateOf("")
override var scoreB = mutableStateOf("")
override var enableSubmit = mutableStateOf(false)
override var isAFocused = mutableStateOf(false)
override var isBFocused = mutableStateOf(false)
override var requestFocusA = mutableStateOf(FocusRequester())
override fun submitScore(scoreA: Int, scoreB: Int) {
override fun submitScore() {
viewModelScope.launch {
gameRepository.addRoundToActiveGame(scoreA, scoreB)
gameRepository.addRoundToActiveGame(scoreA.value.toInt(), scoreB.value.toInt())
}
scoreA.value = ""
scoreB.value = ""
enableSubmit.value = false
}
override fun appendToFocusedScore(toAppend: String) {
val value = getActiveValue()
value.value += toAppend
updateOtherScore()
}
override fun negateActiveInput() {
val value = getActiveValue()
if (value.value.contains("-")) {
value.value = value.value.replace("-", "")
} else {
value.value = "-" + value.value
}
updateOtherScore()
}
override fun addToActiveInput(toAdd: Int) {
val value = getActiveValue()
try {
val temp = value.value.toInt() + toAdd
value.value = temp.toString()
} catch (e: Exception) {
value.value = toAdd.toString()
}
}
override fun removeLastCharFromActive() {
val value = getActiveValue()
if (value.value != "") {
value.value = value.value.dropLast(1)
}
updateOtherScore()
}
private fun giveFocusToAIfNone() {
if (!isAFocused.value && !isBFocused.value) {
requestFocusA.value.requestFocus()
}
}
private fun updateOtherScore() {
val value = getActiveValue()
try {
val tichu = Tichu()
val myScore = value.value.toInt()
val hisScore = tichu.calculateOtherScore(myScore)
if (tichu.isValidRound(myScore, hisScore)) {
updateInactiveValue(hisScore?.toString() ?: "")
} else {
updateInactiveValue("")
}
} catch (_: Exception) {
updateInactiveValue("")
}
}
private fun getActiveValue(): MutableState<String> {
giveFocusToAIfNone()
if (isBFocused.value) {
return scoreB
}
return scoreA
}
private fun updateInactiveValue(value: String) {
giveFocusToAIfNone()
if (isBFocused.value) {
scoreA.value = value
} else {
scoreB.value = value
}
try {
val tichu = Tichu()
enableSubmit.value = tichu.isValidRound(scoreA.value.toInt(), scoreB.value.toInt())
} catch (_: java.lang.NumberFormatException) {
enableSubmit.value = false
}
}
}

View File

@@ -1,6 +1,5 @@
package me.zobrist.tichucounter
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.domain.Tichu
import org.junit.Assert.*
import org.junit.Test