Show blinking cursor in keyboard view. Give focus back to last focused input.
All checks were successful
continuous-integration/drone/push Build is passing

closes #12
This commit is contained in:
2023-01-27 18:07:16 +01:00
parent c71b608a7b
commit 58d4fc0e43
4 changed files with 187 additions and 102 deletions

View File

@@ -269,7 +269,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
scope, scope,
navController, navController,
counterViewModel.keyboardHidden && (currentDestination?.hierarchy?.any { it.route == "counter" } == true) counterViewModel.keyboardHidden && (currentDestination?.hierarchy?.any { it.route == "counter" } == true)
) { counterViewModel.keyboardHidden = false } ) { counterViewModel.showKeyboard() }
} }
} }

View File

@@ -51,20 +51,7 @@ fun Landscape(viewModel: ICounterViewModel) {
} }
if (!viewModel.keyboardHidden) { if (!viewModel.keyboardHidden) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
KeyboardView( KeyBoardView(viewModel = viewModel)
viewModel.currentScoreA,
viewModel.currentScoreB,
viewModel.requestFocusA,
viewModel.enableSubmit,
{ viewModel.updateFocusStateA(it) },
{ viewModel.updateFocusStateB(it) },
{ viewModel.digitClicked(it) },
{ viewModel.addSub100Clicked(it) },
{ viewModel.deleteClicked() },
{ viewModel.negateClicked() },
{ viewModel.submitClicked() },
{ viewModel.keyboardHidden = true },
{ viewModel.swapInputScores() })
} }
} }
} }
@@ -92,20 +79,7 @@ fun Portrait(viewModel: ICounterViewModel) {
) )
if (!viewModel.keyboardHidden) { if (!viewModel.keyboardHidden) {
KeyboardView( KeyBoardView(viewModel = viewModel)
viewModel.currentScoreA,
viewModel.currentScoreB,
viewModel.requestFocusA,
viewModel.enableSubmit,
{ viewModel.updateFocusStateA(it) },
{ viewModel.updateFocusStateB(it) },
{ viewModel.digitClicked(it) },
{ viewModel.addSub100Clicked(it) },
{ viewModel.deleteClicked() },
{ viewModel.negateClicked() },
{ viewModel.submitClicked() },
{ viewModel.keyboardHidden = true },
{ viewModel.swapInputScores() })
} }
} }
} }
@@ -133,11 +107,12 @@ internal class PreviewViewModel : ICounterViewModel {
override var isAFocused: Boolean = false override var isAFocused: Boolean = false
override var isBFocused: Boolean = false override var isBFocused: Boolean = false
override var requestFocusA: FocusRequester = FocusRequester() override var requestFocusA: FocusRequester = FocusRequester()
override var requestFocusB: FocusRequester = FocusRequester()
override var activeValue: String = currentScoreA override var activeValue: String = currentScoreA
override var inactiveValue: String = currentScoreB override var inactiveValue: String = currentScoreB
override var keyboardHidden: Boolean = false override var keyboardHidden: Boolean = false
override fun giveFocusToAIfNone() { override fun focusLastInput() {
} }
override fun updateOtherScore() { override fun updateOtherScore() {
@@ -180,4 +155,10 @@ internal class PreviewViewModel : ICounterViewModel {
override fun swapInputScores() { override fun swapInputScores() {
} }
override fun hideKeyboard() {
}
override fun showKeyboard() {
}
} }

View File

@@ -14,23 +14,22 @@ import me.zobrist.tichucounter.domain.getTotalPoints
import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.repository.GameRepository
import javax.inject.Inject import javax.inject.Inject
interface ICounterViewModel { private enum class Focused { TEAM_A, TEAM_B }
var roundScoreList: List<Round>
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
var keyboardHidden: Boolean
fun giveFocusToAIfNone() interface IKeyBoardViewModel {
val currentScoreA: String
val currentScoreB: String
val enableSubmit: Boolean
val isAFocused: Boolean
val isBFocused: Boolean
val requestFocusA: FocusRequester
val requestFocusB: FocusRequester
val activeValue: String
val inactiveValue: String
val keyboardHidden: Boolean
fun focusLastInput()
fun updateOtherScore() fun updateOtherScore()
fun isValidTichuRound(): Boolean fun isValidTichuRound(): Boolean
fun updateSubmitButton() fun updateSubmitButton()
@@ -39,11 +38,23 @@ interface ICounterViewModel {
fun negateClicked() fun negateClicked()
fun addSub100Clicked(toAdd: Int) fun addSub100Clicked(toAdd: Int)
fun deleteClicked() fun deleteClicked()
fun updateNameA(value: String)
fun updateNameB(value: String)
fun updateFocusStateA(state: Boolean) fun updateFocusStateA(state: Boolean)
fun updateFocusStateB(state: Boolean) fun updateFocusStateB(state: Boolean)
fun swapInputScores() fun swapInputScores()
fun hideKeyboard()
fun showKeyboard()
}
interface ICounterViewModel: IKeyBoardViewModel {
val roundScoreList: List<Round>
val totalScoreA: Int
val totalScoreB: Int
val teamNameA: String
val teamNameB: String
fun updateNameA(value: String)
fun updateNameB(value: String)
} }
@HiltViewModel @HiltViewModel
@@ -53,28 +64,43 @@ class CounterViewModel @Inject constructor(
ViewModel(), ICounterViewModel { ViewModel(), ICounterViewModel {
override var roundScoreList by mutableStateOf(emptyList<Round>()) override var roundScoreList by mutableStateOf(emptyList<Round>())
private set
override var totalScoreA by mutableStateOf(0) override var totalScoreA by mutableStateOf(0)
private set
override var totalScoreB by mutableStateOf(0) override var totalScoreB by mutableStateOf(0)
private set
override var teamNameA by mutableStateOf("") override var teamNameA by mutableStateOf("")
private set
override var teamNameB by mutableStateOf("") override var teamNameB by mutableStateOf("")
private set
override var currentScoreA by mutableStateOf("") override var currentScoreA by mutableStateOf("")
private set
override var currentScoreB by mutableStateOf("") override var currentScoreB by mutableStateOf("")
private set
override var enableSubmit by mutableStateOf(false) override var enableSubmit by mutableStateOf(false)
private set
override var isAFocused by mutableStateOf(false) override var isAFocused by mutableStateOf(false)
private set
override var isBFocused by mutableStateOf(false) override var isBFocused by mutableStateOf(false)
private set
override var requestFocusA by mutableStateOf(FocusRequester()) override var requestFocusA by mutableStateOf(FocusRequester())
private set
override var requestFocusB by mutableStateOf(FocusRequester())
private set
override var keyboardHidden by mutableStateOf(false) override var keyboardHidden by mutableStateOf(false)
private set
override var activeValue: String override var activeValue: String
get() { get() {
@@ -108,6 +134,9 @@ class CounterViewModel @Inject constructor(
} }
} }
private var lastFocused = Focused.TEAM_A
init { init {
viewModelScope.launch { viewModelScope.launch {
gameRepository.getActiveGameFlow().collect { gameRepository.getActiveGameFlow().collect {
@@ -126,9 +155,10 @@ class CounterViewModel @Inject constructor(
} }
} }
override fun giveFocusToAIfNone() { override fun focusLastInput() {
if (!isAFocused && !isBFocused) { when(lastFocused){
requestFocusA.requestFocus() Focused.TEAM_A -> if(!isAFocused) requestFocusA.requestFocus()
Focused.TEAM_B -> if(!isBFocused) requestFocusB.requestFocus()
} }
} }
@@ -170,7 +200,7 @@ class CounterViewModel @Inject constructor(
} }
override fun digitClicked(digit: String) { override fun digitClicked(digit: String) {
giveFocusToAIfNone() focusLastInput()
activeValue += digit activeValue += digit
updateOtherScore() updateOtherScore()
@@ -178,7 +208,7 @@ class CounterViewModel @Inject constructor(
} }
override fun negateClicked() { override fun negateClicked() {
giveFocusToAIfNone() focusLastInput()
activeValue = if (activeValue.contains("-")) { activeValue = if (activeValue.contains("-")) {
activeValue.replace("-", "") activeValue.replace("-", "")
@@ -190,7 +220,7 @@ class CounterViewModel @Inject constructor(
} }
override fun addSub100Clicked(toAdd: Int) { override fun addSub100Clicked(toAdd: Int) {
giveFocusToAIfNone() focusLastInput()
activeValue = try { activeValue = try {
val temp = activeValue.toInt() + toAdd val temp = activeValue.toInt() + toAdd
@@ -231,10 +261,16 @@ class CounterViewModel @Inject constructor(
override fun updateFocusStateA(state: Boolean) { override fun updateFocusStateA(state: Boolean) {
isAFocused = state isAFocused = state
if(state){
lastFocused = Focused.TEAM_A
}
} }
override fun updateFocusStateB(state: Boolean) { override fun updateFocusStateB(state: Boolean) {
isBFocused = state isBFocused = state
if(state){
lastFocused = Focused.TEAM_B
}
} }
override fun swapInputScores() { override fun swapInputScores() {
@@ -242,4 +278,12 @@ class CounterViewModel @Inject constructor(
currentScoreA = currentScoreB currentScoreA = currentScoreB
currentScoreB = swap currentScoreB = swap
} }
override fun hideKeyboard() {
keyboardHidden = true
}
override fun showKeyboard() {
keyboardHidden = false
}
} }

View File

@@ -1,6 +1,7 @@
package me.zobrist.tichucounter.ui.counter package me.zobrist.tichucounter.ui.counter
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Backspace import androidx.compose.material.icons.outlined.Backspace
@@ -8,28 +9,51 @@ import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.KeyboardHide import androidx.compose.material.icons.outlined.KeyboardHide
import androidx.compose.material.icons.outlined.SwapHoriz import androidx.compose.material.icons.outlined.SwapHoriz
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Alignment
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.focus.FocusState
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import me.zobrist.tichucounter.ui.AppTheme import me.zobrist.tichucounter.ui.AppTheme
@Composable
fun KeyBoardView(viewModel: IKeyBoardViewModel) {
KeyboardView(
viewModel.currentScoreA,
viewModel.currentScoreB,
viewModel.requestFocusA,
viewModel.requestFocusB,
viewModel.enableSubmit,
viewModel.isAFocused,
viewModel.isBFocused,
{ viewModel.updateFocusStateA(it) },
{ viewModel.updateFocusStateB(it) },
{ viewModel.digitClicked(it) },
{ viewModel.addSub100Clicked(it) },
{ viewModel.deleteClicked() },
{ viewModel.negateClicked() },
{ viewModel.submitClicked() },
{ viewModel.hideKeyboard() },
{ viewModel.swapInputScores() }
)
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
fun KeyboardView( fun KeyboardView(
scoreA: String, scoreA: String,
scoreB: String, scoreB: String,
requestFocus: FocusRequester, requestFocusA: FocusRequester,
requestFocusB: FocusRequester,
enableSubmit: Boolean, enableSubmit: Boolean,
focusStateA: Boolean,
focusStateB: Boolean,
updateFocusStateA: (Boolean) -> Unit, updateFocusStateA: (Boolean) -> Unit,
updateFocusStateB: (Boolean) -> Unit, updateFocusStateB: (Boolean) -> Unit,
digitClicked: (String) -> Unit, digitClicked: (String) -> Unit,
@@ -40,22 +64,15 @@ fun KeyboardView(
hideKeyboardClicked: () -> Unit, hideKeyboardClicked: () -> Unit,
onSwapClicked: () -> Unit onSwapClicked: () -> Unit
) { ) {
val keyboardController = LocalSoftwareKeyboardController.current
Column { Column {
Row(Modifier.height(IntrinsicSize.Max)) { Row(Modifier.height(IntrinsicSize.Max)) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
CenteredTextField( CenteredTextField(
scoreA, scoreA,
"0", "0",
Modifier focusStateA,
.focusRequester(requestFocus) requestFocusA
.onFocusChanged { ) { updateFocusStateA(it.isFocused) }
keyboardController?.hide()
updateFocusStateA(it.isFocused)
}
)
} }
Surface( Surface(
@@ -76,12 +93,11 @@ fun KeyboardView(
CenteredTextField( CenteredTextField(
scoreB, scoreB,
"0", "0",
Modifier focusStateB,
.onFocusChanged { requestFocusB
keyboardController?.hide() ) {
updateFocusStateB(it.isFocused) updateFocusStateB(it.isFocused)
} }
)
} }
} }
@@ -225,23 +241,64 @@ fun KeyboardIconButton(icon: ImageVector, enabled: Boolean = true, onClicked: ()
fun CenteredTextField( fun CenteredTextField(
value: String, value: String,
placeholder: String, placeholder: String,
modifier: Modifier, focused: Boolean,
focusRequester: FocusRequester? = null,
onFocusStateChanged: (FocusState) -> Unit
) { ) {
val modifier = if(focusRequester != null) {
Modifier.focusRequester(focusRequester)
} else
{
Modifier
}
Box(contentAlignment = Alignment.Center) {
TextField( TextField(
value = value, value = value,
onValueChange = { }, onValueChange = { },
placeholder = { placeholder = {
if(!focused){
Text( Text(
placeholder, placeholder,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
}
}, },
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
singleLine = true, singleLine = true,
readOnly = true, readOnly = true,
modifier = modifier.fillMaxWidth() modifier = modifier
.fillMaxWidth()
.onFocusChanged {
onFocusStateChanged(it)
}
) )
if(focused)
{
val cursorColor = MaterialTheme.colorScheme.onSurface
val infiniteTransition = rememberInfiniteTransition()
val alpha by infiniteTransition.animateFloat(
0f,
cursorColor.alpha,
animationSpec = infiniteRepeatable(
animation = tween(500),
repeatMode = RepeatMode.Reverse
)
)
Row {
Text(text = value, color = cursorColor.copy(alpha = 0f))
Divider(
modifier = Modifier
.padding(start = 3.dp, top = 15.dp, bottom = 15.dp)
.width(1.dp)
.fillMaxHeight(),
color = cursorColor.copy(alpha = alpha))
}
}
}
} }
@@ -252,19 +309,22 @@ fun KeyboardViewPreview() {
AppTheme { AppTheme {
Surface { Surface {
KeyboardView( KeyboardView(
"", "1",
"350", "3511",
FocusRequester(), FocusRequester(),
false, FocusRequester(),
{}, enableSubmit = false,
{}, focusStateA = true,
{}, focusStateB = false,
{}, updateFocusStateA = {},
{}, updateFocusStateB = {},
{}, digitClicked = {},
{}, addSub100Clicked = {},
{}, deleteClicked = {},
{}) negateClicked = {},
submitClicked = {},
hideKeyboardClicked = {},
onSwapClicked = {})
} }
} }
} }