Show blinking cursor in keyboard view. Give focus back to last focused input.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
closes #12
This commit is contained in:
@@ -269,7 +269,7 @@ class MainActivity : AppCompatActivity(), ISettingsChangeListener {
|
||||
scope,
|
||||
navController,
|
||||
counterViewModel.keyboardHidden && (currentDestination?.hierarchy?.any { it.route == "counter" } == true)
|
||||
) { counterViewModel.keyboardHidden = false }
|
||||
) { counterViewModel.showKeyboard() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
updateFocusStateB(it.isFocused)
|
||||
}
|
||||
)
|
||||
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
|
||||
) {
|
||||
TextField(
|
||||
value = value,
|
||||
onValueChange = { },
|
||||
placeholder = {
|
||||
Text(
|
||||
placeholder,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
||||
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()
|
||||
.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
|
||||
)
|
||||
)
|
||||
},
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
|
||||
singleLine = true,
|
||||
readOnly = true,
|
||||
modifier = modifier.fillMaxWidth()
|
||||
)
|
||||
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 = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user