Move all functionality to viewmodel. Use simple button format.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.zobrist.tichucounter.domain
|
||||
|
||||
import me.zobrist.tichucounter.data.Round
|
||||
import javax.inject.Inject
|
||||
|
||||
class Tichu @Inject constructor() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,19 +14,124 @@ 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
|
||||
class KeyboardViewModel @Inject constructor(private val gameRepository: GameRepository) :
|
||||
ViewModel(), IKeyboardViewModel {
|
||||
override var scoreA = mutableStateOf( "")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -37,13 +36,13 @@ class TichuUnitTest {
|
||||
assertGeneratedRound(tichu, 400, 0)
|
||||
|
||||
//Good rounds trough Tichu
|
||||
assertValidRound(tichu, 0, 0)
|
||||
assertValidRound(tichu, -100, 0)
|
||||
assertValidRound(tichu, 0, 0)
|
||||
assertValidRound(tichu, -100, 0)
|
||||
|
||||
//Bad rounds
|
||||
assertInvalidRound(tichu, 5, 12)
|
||||
assertInvalidRound(tichu, 12, 5)
|
||||
assertInvalidRound(tichu, 5, 55)
|
||||
assertInvalidRound(tichu, 5, 12)
|
||||
assertInvalidRound(tichu, 12, 5)
|
||||
assertInvalidRound(tichu, 5, 55)
|
||||
}
|
||||
|
||||
private fun assertGeneratedRound(tichu: Tichu, scoreA: Int, expectedScoreB: Int) {
|
||||
|
||||
Reference in New Issue
Block a user