Use compose for keyboard.
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:
@@ -5,7 +5,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class Tichu @Inject constructor() {
|
class Tichu @Inject constructor() {
|
||||||
|
|
||||||
fun calculateOtherScore(score: Int?): Int? {
|
fun calculateOtherScore(score: Int): Int? {
|
||||||
if (score == null) {
|
if (score == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -21,10 +21,10 @@ class Tichu @Inject constructor() {
|
|||||||
return 100 - (score % 100)
|
return 100 - (score % 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isValidRound(round: Round): Boolean {
|
fun isValidRound(scoreA: Int?, scoreB: Int?): Boolean {
|
||||||
if (round.scoreA == null || round.scoreB == null) {
|
if (scoreA == null || scoreB == null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return (round.scoreA!!.isMultipleOf5()) && round.scoreB!!.isMultipleOf5() && (round.scoreA!! + round.scoreB!!).isMultipleOf100()
|
return (scoreA!!.isMultipleOf5()) && scoreB!!.isMultipleOf5() && (scoreA!! + scoreB!!).isMultipleOf100()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,43 +42,6 @@ class CounterFragment : FragmentBase<FragmentCounterBinding>(), MenuProvider {
|
|||||||
this, viewLifecycleOwner, Lifecycle.State.RESUMED
|
this, viewLifecycleOwner, Lifecycle.State.RESUMED
|
||||||
)
|
)
|
||||||
|
|
||||||
keyboardViewModel.scoreA.observe(viewLifecycleOwner) { value ->
|
|
||||||
val tichu = Tichu()
|
|
||||||
|
|
||||||
val oldValue = currentRound.scoreA
|
|
||||||
currentRound.scoreA = value
|
|
||||||
|
|
||||||
if (ignoreNextUpdate) {
|
|
||||||
ignoreNextUpdate = false
|
|
||||||
} else {
|
|
||||||
if (currentRound.scoreA?.let { oldValue?.getAbsoluteDifference(it) } != 100) {
|
|
||||||
ignoreNextUpdate = true
|
|
||||||
currentRound.scoreB = tichu.calculateOtherScore(value)
|
|
||||||
keyboardViewModel.setScoreB(currentRound.scoreB)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyboardViewModel.setSubmitButtonEnable(tichu.isValidRound(currentRound))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyboardViewModel.scoreB.observe(viewLifecycleOwner) { value ->
|
|
||||||
val tichu = Tichu()
|
|
||||||
|
|
||||||
val oldValue = currentRound.scoreB
|
|
||||||
currentRound.scoreB = value
|
|
||||||
|
|
||||||
if (ignoreNextUpdate) {
|
|
||||||
ignoreNextUpdate = false
|
|
||||||
} else {
|
|
||||||
if (currentRound.scoreB?.let { oldValue?.getAbsoluteDifference(it) } != 100) {
|
|
||||||
ignoreNextUpdate = true
|
|
||||||
currentRound.scoreA = tichu.calculateOtherScore(value)
|
|
||||||
keyboardViewModel.setScoreA(currentRound.scoreA)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyboardViewModel.setSubmitButtonEnable(tichu.isValidRound(currentRound))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
|||||||
@@ -1,17 +1,33 @@
|
|||||||
package me.zobrist.tichucounter.ui.counter
|
package me.zobrist.tichucounter.ui.counter
|
||||||
|
|
||||||
import android.content.Context
|
import android.icu.number.FormattedNumber
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.InputMethodManager
|
import androidx.compose.foundation.layout.Column
|
||||||
import android.widget.EditText
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
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.geometry.Rect
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.*
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import me.zobrist.tichucounter.databinding.FragmentKeyboardBinding
|
import me.zobrist.tichucounter.databinding.FragmentKeyboardBinding
|
||||||
|
import me.zobrist.tichucounter.domain.Tichu
|
||||||
import me.zobrist.tichucounter.ui.FragmentBase
|
import me.zobrist.tichucounter.ui.FragmentBase
|
||||||
|
import me.zobrist.tichucounter.ui.history.IHistoryFragmentViewModel
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
|
class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
|
||||||
@@ -19,226 +35,280 @@ class Keyboard : FragmentBase<FragmentKeyboardBinding>() {
|
|||||||
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentKeyboardBinding
|
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentKeyboardBinding
|
||||||
get() = FragmentKeyboardBinding::inflate
|
get() = FragmentKeyboardBinding::inflate
|
||||||
|
|
||||||
private var unhandledNegation: Boolean = false
|
|
||||||
|
|
||||||
private val viewModel: KeyboardViewModel by activityViewModels()
|
private val viewModel: KeyboardViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
private val requester = FocusRequester()
|
||||||
|
private var isAFocused: Boolean = false
|
||||||
|
private var isBFocused: Boolean = false
|
||||||
|
|
||||||
binding.inputTeamA.setRawInputType(InputType.TYPE_NULL)
|
var enableSubmit = mutableStateOf(false)
|
||||||
binding.inputTeamB.setRawInputType(InputType.TYPE_NULL)
|
|
||||||
binding.inputTeamA.requestFocus()
|
|
||||||
|
|
||||||
disableSubmitButton()
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
|
||||||
viewModel.enableSubmitButton.observe(viewLifecycleOwner) { enabled ->
|
snapshotFlow { viewModel.scoreA.value }
|
||||||
if (enabled) enableSubmitButton() else disableSubmitButton()
|
.onEach {
|
||||||
}
|
enableSubmitOnValidRound(viewModel)
|
||||||
|
|
||||||
viewModel.scoreA.observe(viewLifecycleOwner) { value ->
|
|
||||||
updateScore(binding.inputTeamA, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.scoreB.observe(viewLifecycleOwner) { value ->
|
|
||||||
updateScore(binding.inputTeamB, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
setListeners()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setListeners() {
|
|
||||||
binding.inputTeamA.setOnFocusChangeListener { _, b ->
|
|
||||||
if (b) {
|
|
||||||
hideKeyboard()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
binding.inputTeamB.setOnFocusChangeListener { _, b ->
|
snapshotFlow { viewModel.scoreB.value }
|
||||||
if (b) {
|
.onEach {
|
||||||
hideKeyboard()
|
enableSubmitOnValidRound(viewModel)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
binding.buttonAdd100.setOnClickListener {
|
return ComposeView(requireContext()).apply {
|
||||||
var value = getActiveValue()
|
// Dispose of the Composition when the view's LifecycleOwner
|
||||||
|
// is destroyed
|
||||||
value = if (value != null) {
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
value + 100
|
setContent {
|
||||||
} else {
|
MaterialTheme {
|
||||||
100
|
KeyboardView(viewModel)
|
||||||
}
|
|
||||||
setActiveValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.buttonSub100.setOnClickListener {
|
|
||||||
var value = getActiveValue()
|
|
||||||
|
|
||||||
value = if (value != null) {
|
|
||||||
value!! - 100
|
|
||||||
} else {
|
|
||||||
-100
|
|
||||||
}
|
|
||||||
setActiveValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button0.setOnClickListener {
|
|
||||||
appendToFocusedScore(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button1.setOnClickListener {
|
|
||||||
appendToFocusedScore(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button2.setOnClickListener {
|
|
||||||
appendToFocusedScore(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button3.setOnClickListener {
|
|
||||||
appendToFocusedScore(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button4.setOnClickListener {
|
|
||||||
appendToFocusedScore(4)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button5.setOnClickListener {
|
|
||||||
appendToFocusedScore(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button6.setOnClickListener {
|
|
||||||
appendToFocusedScore(6)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button7.setOnClickListener {
|
|
||||||
appendToFocusedScore(7)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button8.setOnClickListener {
|
|
||||||
appendToFocusedScore(8)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.button9.setOnClickListener {
|
|
||||||
appendToFocusedScore(9)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.buttonInv.setOnClickListener {
|
|
||||||
var value = getActiveValue()
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
unhandledNegation = if (getActiveText() == "-") {
|
|
||||||
setActiveText("")
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
setActiveText("-")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = value?.times(-1)
|
|
||||||
setActiveValue(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.buttonBack.setOnClickListener {
|
|
||||||
var value = getActiveValue()
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
value = try {
|
|
||||||
value.toString().dropLast(1).toInt()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setActiveValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.submit.setOnClickListener {
|
|
||||||
viewModel.submitButtonClicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideKeyboard() {
|
private fun enableSubmitOnValidRound(viewModel: IKeyboardViewModel) {
|
||||||
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
enableSubmit.value = try {
|
||||||
imm?.hideSoftInputFromWindow(view?.windowToken, 0)
|
val tichu = Tichu()
|
||||||
|
if(tichu.isValidRound(viewModel.scoreA.value.toInt(), viewModel.scoreB.value.toInt()))
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} catch(_:Exception)
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun giveFocusToAIfNone() {
|
private fun giveFocusToAIfNone() {
|
||||||
if (!binding.inputTeamA.isFocused && !binding.inputTeamB.isFocused) {
|
if(!isAFocused && !isBFocused)
|
||||||
binding.inputTeamA.requestFocus()
|
{
|
||||||
|
requester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateScore(field: EditText, score: Int?) {
|
private fun appendToFocusedScore(toAppend: String, viewModel: IKeyboardViewModel) {
|
||||||
if (score == null) {
|
val value = getActiveValue(viewModel)
|
||||||
field.setText("")
|
value.value += toAppend
|
||||||
return
|
updateOtherScore(viewModel)
|
||||||
}
|
|
||||||
|
|
||||||
val text = try {
|
|
||||||
score.toString()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
field.setText(text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun appendToFocusedScore(toAppend: Int) {
|
private fun updateOtherScore(viewModel: IKeyboardViewModel) {
|
||||||
var value = getActiveValue()
|
val value = getActiveValue(viewModel)
|
||||||
|
|
||||||
if (value != null) {
|
try {
|
||||||
value = value.times(10)
|
val tichu = Tichu()
|
||||||
value = value.plus(toAppend)
|
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 {
|
} else {
|
||||||
value = toAppend
|
value.value = "-" + value.value
|
||||||
if (unhandledNegation) {
|
}
|
||||||
value = value.times(-1)
|
|
||||||
unhandledNegation = false
|
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
|
||||||
|
fun KeyboardView(viewModel: IKeyboardViewModel = DefaultViewModel()) {
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextToolbar provides EmptyTextToolbar
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = viewModel.scoreA.value,
|
||||||
|
onValueChange = { },
|
||||||
|
placeholder = { Text("0") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.onFocusChanged {
|
||||||
|
keyboardController?.hide()
|
||||||
|
isAFocused = it.isFocused
|
||||||
|
}
|
||||||
|
.focusRequester(requester)
|
||||||
|
.weight(1f),
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
cursorColor = Color.Transparent,
|
||||||
|
errorCursorColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
value = viewModel.scoreB.value,
|
||||||
|
onValueChange = { },
|
||||||
|
placeholder = { Text("0") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.onFocusChanged {
|
||||||
|
keyboardController?.hide()
|
||||||
|
isBFocused = it.isFocused
|
||||||
|
}
|
||||||
|
.weight(1f),
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
cursorColor = Color.Transparent,
|
||||||
|
errorCursorColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("1", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("1") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("2", viewModel ) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("2") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("3", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("3") }
|
||||||
|
Button(onClick = { addToActiveInput(100, viewModel) }, modifier = Modifier.weight(1F)) { Text("+100") }
|
||||||
|
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("4", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("4") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("5", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("5") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("6", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("6") }
|
||||||
|
Button(onClick = { addToActiveInput(-100, viewModel) }, modifier = Modifier.weight(1F)) { Text("-100") }
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("7", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("7") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("8", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("8") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("9", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("9") }
|
||||||
|
Button(onClick = { removeFromActiveInput(viewModel) }, modifier = Modifier.weight(1F)) { Text("DEL") }
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Button(onClick = { negateActiveInput(viewModel) }, modifier = Modifier.weight(1F)) { Text("+/-") }
|
||||||
|
Button(
|
||||||
|
onClick = { appendToFocusedScore("0", viewModel) },
|
||||||
|
modifier = Modifier.weight(1F)
|
||||||
|
) { Text("0") }
|
||||||
|
Spacer(modifier = Modifier.weight(1F))
|
||||||
|
Button(onClick = { submit(viewModel) }, modifier = Modifier.weight(1F), enabled = enableSubmit.value) { Text("ENTER") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setActiveValue(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getActiveValue(): Int? {
|
private fun submit(viewModel: IKeyboardViewModel) {
|
||||||
giveFocusToAIfNone()
|
viewModel.submitScore(viewModel.scoreA.value.toInt(), viewModel.scoreB.value.toInt())
|
||||||
if (binding.inputTeamA.isFocused) {
|
viewModel.scoreA.value = ""
|
||||||
return viewModel.scoreA.value
|
viewModel.scoreB.value = ""
|
||||||
}
|
enableSubmit.value = false
|
||||||
return viewModel.scoreB.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setActiveValue(value: Int?) {
|
object EmptyTextToolbar : TextToolbar {
|
||||||
giveFocusToAIfNone()
|
override val status: TextToolbarStatus = TextToolbarStatus.Hidden
|
||||||
if (binding.inputTeamA.isFocused) {
|
|
||||||
viewModel.setScoreA(value)
|
override fun hide() {}
|
||||||
} else {
|
|
||||||
viewModel.setScoreB(value)
|
override fun showMenu(
|
||||||
|
rect: Rect,
|
||||||
|
onCopyRequested: (() -> Unit)?,
|
||||||
|
onPasteRequested: (() -> Unit)?,
|
||||||
|
onCutRequested: (() -> Unit)?,
|
||||||
|
onSelectAllRequested: (() -> Unit)?,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getActiveText(): String {
|
internal class DefaultViewModel : IKeyboardViewModel {
|
||||||
giveFocusToAIfNone()
|
override var scoreA: MutableState<String> = mutableStateOf("")
|
||||||
if (binding.inputTeamA.isFocused) {
|
override var scoreB: MutableState<String> = mutableStateOf("")
|
||||||
return binding.inputTeamA.text.toString()
|
|
||||||
}
|
|
||||||
return binding.inputTeamB.text.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setActiveText(value: String) {
|
override fun submitScore(scoreA: Int, scoreB: Int) {
|
||||||
giveFocusToAIfNone()
|
|
||||||
if (binding.inputTeamA.isFocused) {
|
|
||||||
binding.inputTeamA.setText(value)
|
|
||||||
} else {
|
|
||||||
binding.inputTeamB.setText(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableSubmitButton() {
|
|
||||||
binding.submit.imageAlpha = 255 // 0 being transparent and 255 being opaque
|
|
||||||
binding.submit.isEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun disableSubmitButton() {
|
|
||||||
binding.submit.imageAlpha = 60 // 0 being transparent and 255 being opaque
|
|
||||||
binding.submit.isEnabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,54 +1,37 @@
|
|||||||
package me.zobrist.tichucounter.ui.counter
|
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.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
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 kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.zobrist.tichucounter.domain.Tichu
|
||||||
import me.zobrist.tichucounter.repository.GameRepository
|
import me.zobrist.tichucounter.repository.GameRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
interface IKeyboardViewModel {
|
||||||
|
var scoreA: MutableState<String>
|
||||||
|
var scoreB: MutableState<String>
|
||||||
|
fun submitScore(scoreA: Int, scoreB: Int)
|
||||||
|
}
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class KeyboardViewModel @Inject constructor(private val gameRepository: GameRepository) :
|
class KeyboardViewModel @Inject constructor(private val gameRepository: GameRepository) :
|
||||||
ViewModel() {
|
ViewModel(), IKeyboardViewModel {
|
||||||
private val _scoreA: MutableLiveData<Int?> = MutableLiveData()
|
override var scoreA = mutableStateOf( "")
|
||||||
private val _scoreB: MutableLiveData<Int?> = MutableLiveData()
|
override var scoreB = mutableStateOf("")
|
||||||
private val _enableSubmitButton: MutableLiveData<Boolean> = MutableLiveData()
|
|
||||||
|
|
||||||
val scoreA: LiveData<Int?>
|
|
||||||
get() {
|
|
||||||
return _scoreA
|
|
||||||
}
|
|
||||||
|
|
||||||
val scoreB: LiveData<Int?>
|
override fun submitScore(scoreA: Int, scoreB: Int) {
|
||||||
get() {
|
|
||||||
return _scoreB
|
|
||||||
}
|
|
||||||
|
|
||||||
val enableSubmitButton: LiveData<Boolean>
|
|
||||||
get() {
|
|
||||||
return _enableSubmitButton
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setScoreA(score: Int?) {
|
|
||||||
_scoreA.value = score
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setScoreB(score: Int?) {
|
|
||||||
_scoreB.value = score
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSubmitButtonEnable(enabled: Boolean) {
|
|
||||||
_enableSubmitButton.value = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
fun submitButtonClicked() {
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
gameRepository.addRoundToActiveGame(scoreA.value!!, scoreB.value!!)
|
gameRepository.addRoundToActiveGame(scoreA, scoreB)
|
||||||
_scoreA.value = null
|
|
||||||
_scoreB.value = null
|
|
||||||
setSubmitButtonEnable(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,13 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardElevation
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -82,23 +84,28 @@ class HistoryFragment : Fragment() {
|
|||||||
Row() {
|
Row() {
|
||||||
Text(
|
Text(
|
||||||
text = game.nameA,
|
text = game.nameA,
|
||||||
style = MaterialTheme.typography.headlineSmall)
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = game.scoreA.toString(),
|
text = game.scoreA.toString(),
|
||||||
style = MaterialTheme.typography.headlineSmall)
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Row() {
|
Row() {
|
||||||
Text(
|
Text(
|
||||||
text = game.nameB,
|
text = game.nameB,
|
||||||
style = MaterialTheme.typography.headlineSmall)
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = game.scoreB.toString(),
|
text = game.scoreB.toString(),
|
||||||
style = MaterialTheme.typography.headlineSmall)
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Row() {
|
Row() {
|
||||||
Text(
|
Text(
|
||||||
text = format.format(game.modified),
|
text = format.format(game.modified),
|
||||||
style = MaterialTheme.typography.labelSmall)
|
style = MaterialTheme.typography.labelSmall
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,8 +116,30 @@ class HistoryFragment : Fragment() {
|
|||||||
override val gameAndHistory: State<List<GameAndScore>>
|
override val gameAndHistory: State<List<GameAndScore>>
|
||||||
get() {
|
get() {
|
||||||
val tempData = mutableListOf<GameAndScore>()
|
val tempData = mutableListOf<GameAndScore>()
|
||||||
tempData.add(GameAndScore(false, "TeamA1sdfdsf", "TeamB1", Date(), Date(), 1, 10, 50))
|
tempData.add(
|
||||||
tempData.add(GameAndScore(true, "TeamA2", "TeamB2sdfsdf", Date(), Date(), 2, 20, 60))
|
GameAndScore(
|
||||||
|
false,
|
||||||
|
"TeamA1sdfdsf",
|
||||||
|
"TeamB1",
|
||||||
|
Date(),
|
||||||
|
Date(),
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
50
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tempData.add(
|
||||||
|
GameAndScore(
|
||||||
|
true,
|
||||||
|
"TeamA2",
|
||||||
|
"TeamB2sdfsdf",
|
||||||
|
Date(),
|
||||||
|
Date(),
|
||||||
|
2,
|
||||||
|
20,
|
||||||
|
60
|
||||||
|
)
|
||||||
|
)
|
||||||
tempData.add(GameAndScore(false, "TeamA3", "TeamB3", Date(), Date(), 3, 30, 70))
|
tempData.add(GameAndScore(false, "TeamA3", "TeamB3", Date(), Date(), 3, 30, 70))
|
||||||
tempData.add(GameAndScore(false, "TeamA4", "TeamB4", Date(), Date(), 4, 40, 80))
|
tempData.add(GameAndScore(false, "TeamA4", "TeamB4", Date(), Date(), 4, 40, 80))
|
||||||
tempData.add(GameAndScore(false, "TeamA5", "TeamB5", Date(), Date(), 5, 50, 90))
|
tempData.add(GameAndScore(false, "TeamA5", "TeamB5", Date(), Date(), 5, 50, 90))
|
||||||
|
|||||||
@@ -37,26 +37,26 @@ class TichuUnitTest {
|
|||||||
assertGeneratedRound(tichu, 400, 0)
|
assertGeneratedRound(tichu, 400, 0)
|
||||||
|
|
||||||
//Good rounds trough Tichu
|
//Good rounds trough Tichu
|
||||||
assertValidRound(tichu, Round(1, 0, 0))
|
assertValidRound(tichu, 0, 0)
|
||||||
assertValidRound(tichu, Round(1, -100, 0))
|
assertValidRound(tichu, -100, 0)
|
||||||
|
|
||||||
//Bad rounds
|
//Bad rounds
|
||||||
assertInvalidRound(tichu, Round(1, 5, 12))
|
assertInvalidRound(tichu, 5, 12)
|
||||||
assertInvalidRound(tichu, Round(1, 12, 5))
|
assertInvalidRound(tichu, 12, 5)
|
||||||
assertInvalidRound(tichu, Round(1, 5, 55))
|
assertInvalidRound(tichu, 5, 55)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertGeneratedRound(tichu: Tichu, scoreA: Int, expectedScoreB: Int) {
|
private fun assertGeneratedRound(tichu: Tichu, scoreA: Int, expectedScoreB: Int) {
|
||||||
val round = Round(1, scoreA, tichu.calculateOtherScore(scoreA))
|
val scoreB = tichu.calculateOtherScore(scoreA)
|
||||||
assertEquals(expectedScoreB, round.scoreB)
|
assertEquals(expectedScoreB, scoreB)
|
||||||
assertTrue(tichu.isValidRound(round))
|
assertTrue(tichu.isValidRound(scoreA, scoreB!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertInvalidRound(tichu: Tichu, round: Round) {
|
private fun assertInvalidRound(tichu: Tichu, scoreA: Int, scoreB: Int) {
|
||||||
assertFalse(tichu.isValidRound(round))
|
assertFalse(tichu.isValidRound(scoreA, scoreB))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertValidRound(tichu: Tichu, round: Round) {
|
private fun assertValidRound(tichu: Tichu, scoreA: Int, scoreB: Int) {
|
||||||
assertTrue(tichu.isValidRound(round))
|
assertTrue(tichu.isValidRound(scoreA, scoreB))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user