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,
navController,
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) {
Column(Modifier.weight(1f)) {
KeyboardView(
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() })
KeyBoardView(viewModel = viewModel)
}
}
}
@@ -92,20 +79,7 @@ fun Portrait(viewModel: ICounterViewModel) {
)
if (!viewModel.keyboardHidden) {
KeyboardView(
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() })
KeyBoardView(viewModel = viewModel)
}
}
}
@@ -133,11 +107,12 @@ internal class PreviewViewModel : ICounterViewModel {
override var isAFocused: Boolean = false
override var isBFocused: Boolean = false
override var requestFocusA: FocusRequester = FocusRequester()
override var requestFocusB: FocusRequester = FocusRequester()
override var activeValue: String = currentScoreA
override var inactiveValue: String = currentScoreB
override var keyboardHidden: Boolean = false
override fun giveFocusToAIfNone() {
override fun focusLastInput() {
}
override fun updateOtherScore() {
@@ -180,4 +155,10 @@ internal class PreviewViewModel : ICounterViewModel {
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 javax.inject.Inject
interface ICounterViewModel {
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
private enum class Focused { TEAM_A, TEAM_B }
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 isValidTichuRound(): Boolean
fun updateSubmitButton()
@@ -39,11 +38,23 @@ interface ICounterViewModel {
fun negateClicked()
fun addSub100Clicked(toAdd: Int)
fun deleteClicked()
fun updateNameA(value: String)
fun updateNameB(value: String)
fun updateFocusStateA(state: Boolean)
fun updateFocusStateB(state: Boolean)
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
@@ -53,28 +64,43 @@ class CounterViewModel @Inject constructor(
ViewModel(), ICounterViewModel {
override var roundScoreList by mutableStateOf(emptyList<Round>())
private set
override var totalScoreA by mutableStateOf(0)
private set
override var totalScoreB by mutableStateOf(0)
private set
override var teamNameA by mutableStateOf("")
private set
override var teamNameB by mutableStateOf("")
private set
override var currentScoreA by mutableStateOf("")
private set
override var currentScoreB by mutableStateOf("")
private set
override var enableSubmit by mutableStateOf(false)
private set
override var isAFocused by mutableStateOf(false)
private set
override var isBFocused by mutableStateOf(false)
private set
override var requestFocusA by mutableStateOf(FocusRequester())
private set
override var requestFocusB by mutableStateOf(FocusRequester())
private set
override var keyboardHidden by mutableStateOf(false)
private set
override var activeValue: String
get() {
@@ -108,6 +134,9 @@ class CounterViewModel @Inject constructor(
}
}
private var lastFocused = Focused.TEAM_A
init {
viewModelScope.launch {
gameRepository.getActiveGameFlow().collect {
@@ -126,9 +155,10 @@ class CounterViewModel @Inject constructor(
}
}
override fun giveFocusToAIfNone() {
if (!isAFocused && !isBFocused) {
requestFocusA.requestFocus()
override fun focusLastInput() {
when(lastFocused){
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) {
giveFocusToAIfNone()
focusLastInput()
activeValue += digit
updateOtherScore()
@@ -178,7 +208,7 @@ class CounterViewModel @Inject constructor(
}
override fun negateClicked() {
giveFocusToAIfNone()
focusLastInput()
activeValue = if (activeValue.contains("-")) {
activeValue.replace("-", "")
@@ -190,7 +220,7 @@ class CounterViewModel @Inject constructor(
}
override fun addSub100Clicked(toAdd: Int) {
giveFocusToAIfNone()
focusLastInput()
activeValue = try {
val temp = activeValue.toInt() + toAdd
@@ -231,10 +261,16 @@ class CounterViewModel @Inject constructor(
override fun updateFocusStateA(state: Boolean) {
isAFocused = state
if(state){
lastFocused = Focused.TEAM_A
}
}
override fun updateFocusStateB(state: Boolean) {
isBFocused = state
if(state){
lastFocused = Focused.TEAM_B
}
}
override fun swapInputScores() {
@@ -242,4 +278,12 @@ class CounterViewModel @Inject constructor(
currentScoreA = currentScoreB
currentScoreB = swap
}
override fun hideKeyboard() {
keyboardHidden = true
}
override fun showKeyboard() {
keyboardHidden = false
}
}

View File

@@ -1,6 +1,7 @@
package me.zobrist.tichucounter.ui.counter
import android.content.res.Configuration
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
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.SwapHoriz
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.FocusState
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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
fun KeyboardView(
scoreA: String,
scoreB: String,
requestFocus: FocusRequester,
requestFocusA: FocusRequester,
requestFocusB: FocusRequester,
enableSubmit: Boolean,
focusStateA: Boolean,
focusStateB: Boolean,
updateFocusStateA: (Boolean) -> Unit,
updateFocusStateB: (Boolean) -> Unit,
digitClicked: (String) -> Unit,
@@ -40,22 +64,15 @@ fun KeyboardView(
hideKeyboardClicked: () -> Unit,
onSwapClicked: () -> Unit
) {
val keyboardController = LocalSoftwareKeyboardController.current
Column {
Row(Modifier.height(IntrinsicSize.Max)) {
Column(Modifier.weight(1f)) {
CenteredTextField(
scoreA,
"0",
Modifier
.focusRequester(requestFocus)
.onFocusChanged {
keyboardController?.hide()
updateFocusStateA(it.isFocused)
}
)
focusStateA,
requestFocusA
) { updateFocusStateA(it.isFocused) }
}
Surface(
@@ -76,12 +93,11 @@ fun KeyboardView(
CenteredTextField(
scoreB,
"0",
Modifier
.onFocusChanged {
keyboardController?.hide()
focusStateB,
requestFocusB
) {
updateFocusStateB(it.isFocused)
}
)
}
}
@@ -225,23 +241,64 @@ fun KeyboardIconButton(icon: ImageVector, enabled: Boolean = true, onClicked: ()
fun CenteredTextField(
value: 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(
value = value,
onValueChange = { },
placeholder = {
if(!focused){
Text(
placeholder,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
},
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
singleLine = 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 {
Surface {
KeyboardView(
"",
"350",
"1",
"3511",
FocusRequester(),
false,
{},
{},
{},
{},
{},
{},
{},
{},
{})
FocusRequester(),
enableSubmit = false,
focusStateA = true,
focusStateB = false,
updateFocusStateA = {},
updateFocusStateB = {},
digitClicked = {},
addSub100Clicked = {},
deleteClicked = {},
negateClicked = {},
submitClicked = {},
hideKeyboardClicked = {},
onSwapClicked = {})
}
}
}