Skip to content

Commit 819de86

Browse files
committed
Merge branch 'add-monospace-font-droid-2499'
2 parents bc64759 + fbfdbb4 commit 819de86

5 files changed

Lines changed: 76 additions & 18 deletions

File tree

android/lib/feature/account/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountNumberView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.feature.account.impl
22

33
import androidx.compose.runtime.Composable
44
import androidx.compose.ui.Modifier
5+
import androidx.compose.ui.text.font.FontFamily
56
import net.mullvad.mullvadvpn.lib.common.util.groupPasswordModeWithSpaces
67
import net.mullvad.mullvadvpn.lib.common.util.groupWithSpaces
78

@@ -16,6 +17,7 @@ fun AccountNumberView(
1617
if (obfuscateWithPasswordDots) accountNumber.groupPasswordModeWithSpaces()
1718
else accountNumber.groupWithSpaces(),
1819
modifier = modifier,
20+
fontFamily = FontFamily.Monospace,
1921
whenMissing = MissingPolicy.SHOW_SPINNER,
2022
)
2123
}

android/lib/feature/account/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/account/impl/InformationView.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.compose.material3.MaterialTheme
66
import androidx.compose.runtime.Composable
77
import androidx.compose.ui.Alignment
88
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.text.font.FontFamily
910
import androidx.compose.ui.tooling.preview.Preview
1011
import net.mullvad.mullvadvpn.lib.ui.designsystem.MullvadCircularProgressIndicatorSmall
1112
import net.mullvad.mullvadvpn.lib.ui.theme.Dimens
@@ -28,6 +29,7 @@ fun InformationView(
2829
modifier: Modifier = Modifier,
2930
whenMissing: MissingPolicy = MissingPolicy.SHOW_VIEW,
3031
maxLines: Int = 1,
32+
fontFamily: FontFamily? = null,
3133
) {
3234
return if (content.isNotEmpty()) {
3335
AutoResizeText(
@@ -37,6 +39,7 @@ fun InformationView(
3739
maxTextSize = MaterialTheme.typography.titleMedium.fontSize,
3840
maxLines = maxLines,
3941
modifier = modifier.padding(vertical = Dimens.smallPadding),
42+
fontFamily = fontFamily,
4043
)
4144
} else {
4245
when (whenMissing) {

android/lib/feature/account/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/account/impl/Text.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
1313
import androidx.compose.ui.draw.drawWithContent
1414
import androidx.compose.ui.graphics.Color
1515
import androidx.compose.ui.text.TextStyle
16+
import androidx.compose.ui.text.font.FontFamily
1617
import androidx.compose.ui.unit.TextUnit
1718
import androidx.compose.ui.unit.sp
1819

@@ -28,6 +29,7 @@ fun AutoResizeText(
2829
style: TextStyle = LocalTextStyle.current,
2930
maxLines: Int = Int.MAX_VALUE,
3031
color: Color = MaterialTheme.colorScheme.onSurface,
32+
fontFamily: FontFamily? = null,
3133
) {
3234
var adjustedFontSize by remember { mutableFloatStateOf(maxTextSize.value) }
3335
var isReadyToDraw by remember { mutableStateOf(false) }
@@ -38,6 +40,7 @@ fun AutoResizeText(
3840
style = style,
3941
color = color,
4042
fontSize = adjustedFontSize.sp,
43+
fontFamily = fontFamily,
4144
onTextLayout = {
4245
if (it.didOverflowHeight && isReadyToDraw.not()) {
4346
val nextFontSizeValue = adjustedFontSize - textSizeStep.value

android/lib/feature/home/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/home/impl/welcome/WelcomeScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalResources
3131
import androidx.compose.ui.platform.LocalUriHandler
3232
import androidx.compose.ui.platform.testTag
3333
import androidx.compose.ui.res.stringResource
34+
import androidx.compose.ui.text.font.FontFamily
3435
import androidx.compose.ui.text.style.TextOverflow
3536
import androidx.compose.ui.tooling.preview.Preview
3637
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -280,6 +281,7 @@ private fun AccountNumberRow(snackbarHostState: SnackbarHostState, state: Welcom
280281
text = state.accountNumber?.value?.groupWithSpaces() ?: "",
281282
modifier = Modifier.weight(1f),
282283
style = MaterialTheme.typography.headlineSmall,
284+
fontFamily = FontFamily.Monospace,
283285
color = MaterialTheme.colorScheme.onSurface,
284286
)
285287

android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginScreen.kt

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ import androidx.compose.ui.autofill.ContentType
4646
import androidx.compose.ui.draw.clip
4747
import androidx.compose.ui.focus.FocusRequester
4848
import androidx.compose.ui.focus.focusProperties
49+
import androidx.compose.ui.layout.AlignmentLine
50+
import androidx.compose.ui.layout.FirstBaseline
51+
import androidx.compose.ui.layout.layout
4952
import androidx.compose.ui.platform.LocalContext
5053
import androidx.compose.ui.platform.LocalLayoutDirection
5154
import androidx.compose.ui.platform.LocalResources
@@ -56,8 +59,10 @@ import androidx.compose.ui.semantics.contentType
5659
import androidx.compose.ui.semantics.semantics
5760
import androidx.compose.ui.text.AnnotatedString
5861
import androidx.compose.ui.text.SpanStyle
62+
import androidx.compose.ui.text.font.FontFamily
5963
import androidx.compose.ui.text.input.ImeAction
6064
import androidx.compose.ui.text.input.KeyboardType
65+
import androidx.compose.ui.text.rememberTextMeasurer
6166
import androidx.compose.ui.text.style.TextDecoration
6267
import androidx.compose.ui.text.style.TextDirection
6368
import androidx.compose.ui.text.style.TextOverflow
@@ -157,6 +162,7 @@ fun Login(
157162
message = resources.getString(R.string.error_occurred)
158163
)
159164
}
165+
160166
is ApiUnreachableInfoDialogResult.Success -> {
161167
when (it.arg.action) {
162168
LoginAction.LOGIN -> vm.login(state.accountNumberInput)
@@ -170,14 +176,19 @@ fun Login(
170176
when (it) {
171177
LoginUiSideEffect.NavigateToWelcome ->
172178
navigator.navigate(WelcomeNavKey, clearBackStack = true)
179+
173180
is LoginUiSideEffect.NavigateToConnect ->
174181
navigator.navigate(ConnectNavKey, clearBackStack = true)
182+
175183
is LoginUiSideEffect.TooManyDevices ->
176184
navigator.navigate(DeviceListNavKey(it.accountNumber))
185+
177186
LoginUiSideEffect.NavigateToOutOfTime ->
178187
navigator.navigate(OutOfTimeNavKey, clearBackStack = true)
188+
179189
LoginUiSideEffect.NavigateToCreateAccountConfirmation ->
180190
navigator.navigate(CreateAccountConfirmationNavKey)
191+
181192
LoginUiSideEffect.GenericError ->
182193
snackbarHostState.showSnackbarImmediately(
183194
message = resources.getString(R.string.error_occurred)
@@ -366,30 +377,21 @@ private fun ColumnScope.LoginInput(
366377
accountNumberVisualTransformation(showPassword, if (showLastChar) 1 else 0),
367378
enabled = state.loginState is LoginState.Idle,
368379
colors = mullvadWhiteTextFieldColors(),
369-
textStyle = MaterialTheme.typography.bodyLarge.copy(textDirection = TextDirection.Ltr),
380+
textStyle =
381+
MaterialTheme.typography.bodyLarge.copy(
382+
textDirection = TextDirection.Ltr,
383+
fontFamily = FontFamily.Monospace,
384+
),
370385
isError = state.loginState.isError(),
371386
)
372387

373388
AnimatedVisibility(
374389
visible = state.lastUsedAccount != null && state.loginState is LoginState.Idle
375390
) {
376-
val token = state.lastUsedAccount?.value.orEmpty()
377-
val accountTransformation =
378-
remember(showPassword) {
379-
accountNumberVisualTransformation(
380-
showPassword,
381-
showLastX = ACCOUNT_NUMBER_CHUNK_SIZE,
382-
)
383-
}
384-
val transformedText =
385-
remember(token, accountTransformation) {
386-
accountTransformation.filter(AnnotatedString(token)).text
387-
}
388-
389-
// Since content is number we should always do Ltr
390391
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
391392
AccountDropDownItem(
392-
accountNumber = transformedText.toString(),
393+
accountNumber = state.lastUsedAccount?.value.orEmpty(),
394+
showPassword = showPassword,
393395
onClick = {
394396
state.lastUsedAccount?.let {
395397
onAccountNumberChange(it.value)
@@ -416,6 +418,7 @@ private fun LoginIcon(loginState: LoginState, modifier: Modifier = Modifier) {
416418
} else {
417419
// If view is Idle, we display empty box to keep the same size as other states
418420
}
421+
419422
is LoginState.Loading -> MullvadCircularProgressIndicatorLarge()
420423
LoginState.Success ->
421424
Image(
@@ -436,8 +439,10 @@ private fun LoginState.title(): String =
436439
is LoginUiStateError.LoginError -> R.string.login_fail_title
437440
is LoginUiStateError.CreateAccountError ->
438441
R.string.create_account_fail_title
442+
439443
null -> R.string.log_in
440444
}
445+
441446
is LoginState.Loading -> R.string.logging_in_title
442447
LoginState.Success -> R.string.logged_in_title
443448
}
@@ -472,23 +477,28 @@ private fun LoginState.supportingText(
472477
(loginUiStateError is LoginUiStateError.LoginError.ApiUnreachable ||
473478
loginUiStateError is LoginUiStateError.CreateAccountError.ApiUnreachable)
474479
-> apiUnreachableText(loginUiStateError, onShowApiUnreachableDialog)
480+
475481
is LoginState.Idle -> {
476482
when (loginUiStateError) {
477483
LoginUiStateError.LoginError.InvalidCredentials -> R.string.login_fail_description
478484
is LoginUiStateError.LoginError.InvalidInput -> R.string.login_error_invalid_input
479485
LoginUiStateError.LoginError.NoInternetConnection,
480486
LoginUiStateError.CreateAccountError.NoInternetConnection ->
481487
R.string.no_internet_connection
488+
482489
LoginUiStateError.LoginError.ApiUnreachable,
483490
LoginUiStateError.CreateAccountError.ApiUnreachable -> R.string.api_unreachable
491+
484492
LoginUiStateError.LoginError.TooManyAttempts,
485493
LoginUiStateError.CreateAccountError.TooManyAttempts ->
486494
R.string.login_error_too_many_attempts
495+
487496
is LoginUiStateError.LoginError.Unknown -> R.string.error_occurred
488497
LoginUiStateError.CreateAccountError.Unknown -> R.string.failed_to_create_account
489498
null -> null
490499
}?.toAnnotatedString()
491500
}
501+
492502
is LoginState.Loading.CreatingAccount -> R.string.creating_new_account.toAnnotatedString()
493503
is LoginState.Loading.LoggingIn -> R.string.logging_in_description.toAnnotatedString()
494504
LoginState.Success -> R.string.logged_in_description.toAnnotatedString()
@@ -516,11 +526,21 @@ private fun Int.toAnnotatedString(): AnnotatedString = AnnotatedString(stringRes
516526
@Composable
517527
private fun AccountDropDownItem(
518528
modifier: Modifier = Modifier,
529+
showPassword: Boolean,
519530
accountNumber: String,
520531
enabled: Boolean,
521532
onClick: () -> Unit,
522533
onDeleteClick: () -> Unit,
523534
) {
535+
val accountTransformation =
536+
remember(showPassword) {
537+
accountNumberVisualTransformation(showPassword, showLastX = ACCOUNT_NUMBER_CHUNK_SIZE)
538+
}
539+
val transformedText =
540+
remember(accountNumber, accountTransformation) {
541+
accountTransformation.filter(AnnotatedString(accountNumber)).text
542+
}
543+
524544
Row(
525545
modifier =
526546
modifier
@@ -534,6 +554,19 @@ private fun AccountDropDownItem(
534554
.height(IntrinsicSize.Min),
535555
verticalAlignment = Alignment.CenterVertically,
536556
) {
557+
558+
// Hack, our PASSWORD_UNICODE dot char changes the baseline height, so this workaround
559+
// ensures we always place it at the same baseline
560+
val textStyle = MaterialTheme.typography.bodyLarge.copy(fontFamily = FontFamily.Monospace)
561+
// Measure the digit baseline once to use as a fixed reference
562+
val textMeasurer = rememberTextMeasurer()
563+
val digitBaseline =
564+
remember(textStyle) {
565+
textMeasurer
566+
.measure(text = AnnotatedString("0"), style = textStyle, maxLines = 1)
567+
.firstBaseline
568+
}
569+
537570
Box(
538571
modifier =
539572
Modifier.clickable(enabled = enabled, onClick = onClick)
@@ -543,9 +576,24 @@ private fun AccountDropDownItem(
543576
contentAlignment = Alignment.CenterStart,
544577
) {
545578
Text(
546-
text = accountNumber,
579+
text = transformedText,
547580
overflow = TextOverflow.Clip,
548-
style = MaterialTheme.typography.bodyLarge,
581+
style = textStyle,
582+
maxLines = 1,
583+
// Place text according to baseline so text does not jump as user hide/show password
584+
modifier =
585+
Modifier.layout { measurable, constraints ->
586+
val placeable = measurable.measure(constraints)
587+
val actualBaseline = placeable[FirstBaseline]
588+
// Shift the text so its baseline aligns with the digit baseline
589+
val yOffset =
590+
if (actualBaseline != AlignmentLine.Unspecified) {
591+
digitBaseline.toInt() - actualBaseline
592+
} else {
593+
0
594+
}
595+
layout(placeable.width, placeable.height) { placeable.place(0, yOffset) }
596+
},
549597
)
550598
}
551599
IconButton(

0 commit comments

Comments
 (0)