summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson90@gmail.com>2023-09-26 15:58:52 +0200
committerDavid Göransson <david.goransson90@gmail.com>2023-09-27 08:23:26 +0200
commit1ede00280c0042c6402778d5bb7c1f01ba5823b2 (patch)
treef96e38bcdf11d73a31a78908f72ad2780cf4503c /android
parent1988046e5f10391d5de2b97557d6fc15f85284cf (diff)
downloadmullvadvpn-1ede00280c0042c6402778d5bb7c1f01ba5823b2.tar.xz
mullvadvpn-1ede00280c0042c6402778d5bb7c1f01ba5823b2.zip
Fix Dpad navigation
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt179
1 files changed, 96 insertions, 83 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt
index df815e3dec..3c4d9e1202 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt
@@ -1,26 +1,28 @@
package net.mullvad.mullvadvpn.compose.screen
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -33,7 +35,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.layout.ContentScale
@@ -42,8 +50,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.window.PopupProperties
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.ActionButton
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
@@ -128,7 +136,7 @@ fun LoginScreen(
}
@Composable
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalComposeUiApi::class)
private fun LoginContent(
uiState: LoginUiState,
onAccountNumberChange: (String) -> Unit,
@@ -143,7 +151,9 @@ private fun LoginContent(
modifier = Modifier.fillMaxWidth().padding(bottom = Dimens.smallPadding)
)
- var expanded by remember { mutableStateOf(false) }
+ var tfFocusState: FocusState? by remember { mutableStateOf(null) }
+ var ddFocusState: FocusState? by remember { mutableStateOf(null) }
+ val expandedDropdown = tfFocusState?.hasFocus ?: false || ddFocusState?.hasFocus ?: false
Text(
modifier = Modifier.padding(bottom = Dimens.smallPadding),
@@ -156,81 +166,70 @@ private fun LoginContent(
MaterialTheme.colorScheme.onPrimary
},
)
- ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
- TextField(
- modifier =
- Modifier.then(
- // Using menuAnchor while not showing a dropdown will cause keyboard to
- // open and app to crash on navigation
- if (uiState.lastUsedAccount != null) Modifier.menuAnchor() else Modifier
- )
- .fillMaxWidth(),
- value = uiState.accountNumberInput,
- label = {
- Text(
- text = stringResource(id = R.string.login_description),
- color = Color.Unspecified
- )
- },
- keyboardActions =
- KeyboardActions(onDone = { onLoginClick(uiState.accountNumberInput) }),
- keyboardOptions =
- KeyboardOptions(
- imeAction =
- if (uiState.loginButtonEnabled) ImeAction.Done else ImeAction.None,
- keyboardType = KeyboardType.NumberPassword
- ),
- onValueChange = onAccountNumberChange,
- singleLine = true,
- maxLines = 1,
- visualTransformation = accountTokenVisualTransformation(),
- enabled = uiState.loginState is Idle,
- colors =
- TextFieldDefaults.colors(
- focusedTextColor = Color.Black,
- unfocusedTextColor = Color.Gray,
- disabledTextColor = Color.Gray,
- errorTextColor = Color.Black,
- cursorColor = MaterialTheme.colorScheme.background,
- focusedPlaceholderColor = MaterialTheme.colorScheme.background,
- unfocusedPlaceholderColor = MaterialTheme.colorScheme.primary,
- focusedLabelColor = MaterialTheme.colorScheme.background,
- disabledLabelColor = Color.Gray,
- unfocusedLabelColor = MaterialTheme.colorScheme.background,
- focusedLeadingIconColor = Color.Black,
- unfocusedSupportingTextColor = Color.Black,
- ),
- isError = uiState.loginState.isError(),
- )
- // If we have a previous account, show dropdown for quick re-login
- uiState.lastUsedAccount?.let { token ->
- DropdownMenu(
- modifier =
- Modifier.background(MaterialTheme.colorScheme.background)
- .exposedDropdownSize(true),
- expanded = expanded,
- properties = PopupProperties(focusable = false),
- onDismissRequest = { expanded = false }
- ) {
- val accountTransformation = remember { accountTokenVisualTransformation() }
- val transformedText =
- remember(token.value) {
- accountTransformation.filter(AnnotatedString(token.value)).text
- }
+ TextField(
+ modifier =
+ // Fix for DPad navigation
+ Modifier.onFocusChanged { tfFocusState = it }
+ .focusProperties {
+ left = FocusRequester.Cancel
+ right = FocusRequester.Cancel
+ }
+ .fillMaxWidth(),
+ value = uiState.accountNumberInput,
+ label = {
+ Text(
+ text = stringResource(id = R.string.login_description),
+ color = Color.Unspecified
+ )
+ },
+ keyboardActions =
+ KeyboardActions(onDone = { onLoginClick(uiState.accountNumberInput) }),
+ keyboardOptions =
+ KeyboardOptions(
+ imeAction = if (uiState.loginButtonEnabled) ImeAction.Done else ImeAction.None,
+ keyboardType = KeyboardType.NumberPassword
+ ),
+ onValueChange = onAccountNumberChange,
+ singleLine = true,
+ maxLines = 1,
+ visualTransformation = accountTokenVisualTransformation(),
+ enabled = uiState.loginState is Idle,
+ colors =
+ TextFieldDefaults.colors(
+ focusedTextColor = Color.Black,
+ unfocusedTextColor = Color.Gray,
+ disabledTextColor = Color.Gray,
+ errorTextColor = Color.Black,
+ cursorColor = MaterialTheme.colorScheme.background,
+ focusedPlaceholderColor = MaterialTheme.colorScheme.background,
+ unfocusedPlaceholderColor = MaterialTheme.colorScheme.primary,
+ focusedLabelColor = MaterialTheme.colorScheme.background,
+ disabledLabelColor = Color.Gray,
+ unfocusedLabelColor = MaterialTheme.colorScheme.background,
+ focusedLeadingIconColor = Color.Black,
+ unfocusedSupportingTextColor = Color.Black,
+ ),
+ isError = uiState.loginState.isError(),
+ )
+
+ AnimatedVisibility(visible = uiState.lastUsedAccount != null && expandedDropdown) {
+ val token = uiState.lastUsedAccount?.value.orEmpty()
+ val accountTransformation = remember { accountTokenVisualTransformation() }
+ val transformedText =
+ remember(token) { accountTransformation.filter(AnnotatedString(token)).text }
- AccountDropDownItem(
- accountToken = transformedText.toString(),
- onClick = {
- onAccountNumberChange(token.value)
- expanded = false
- onLoginClick(token.value)
- }
- ) {
- onDeleteHistoryClick()
+ AccountDropDownItem(
+ modifier = Modifier.onFocusChanged { ddFocusState = it },
+ accountToken = transformedText.toString(),
+ onClick = {
+ uiState.lastUsedAccount?.let {
+ onAccountNumberChange(it.value)
+ onLoginClick(it.value)
}
- }
- }
+ },
+ onDeleteClick = onDeleteHistoryClick
+ )
}
Spacer(modifier = Modifier.size(Dimens.largePadding))
@@ -318,20 +317,34 @@ private fun LoginState.supportingText(): String? {
@Composable
private fun AccountDropDownItem(
+ modifier: Modifier = Modifier,
accountToken: String,
onClick: () -> Unit,
onDeleteClick: () -> Unit
) {
Row(
- modifier = Modifier.clickable(onClick = onClick),
+ modifier =
+ modifier
+ .clip(
+ MaterialTheme.shapes.medium.copy(
+ topStart = CornerSize(0f),
+ topEnd = CornerSize(0f)
+ )
+ )
+ .background(MaterialTheme.colorScheme.background)
+ .height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically
) {
- Text(
+ Box(
modifier =
- Modifier.weight(1f)
+ Modifier.clickable(onClick = onClick)
+ .fillMaxHeight()
+ .weight(1f)
.padding(horizontal = Dimens.mediumPadding, vertical = Dimens.smallPadding),
- text = accountToken
- )
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Text(text = accountToken, overflow = TextOverflow.Clip)
+ }
IconButton(onClick = onDeleteClick) {
Icon(
painter = painterResource(id = R.drawable.account_history_remove_pressed),