diff options
| author | Kalle Lindström <karl.lindstrom@mullvad.net> | 2024-10-24 13:55:22 +0200 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2024-10-30 13:04:40 +0100 |
| commit | 1cfd376fe82c1164a2706d410e9e2232d804ffad (patch) | |
| tree | 45db5abc5ad1760b2683abb3f50498bd37af5ef0 /android/app/src | |
| parent | 3bbdf3455e85df030b1def0bca83f2037a87f478 (diff) | |
| download | mullvadvpn-1cfd376fe82c1164a2706d410e9e2232d804ffad.tar.xz mullvadvpn-1cfd376fe82c1164a2706d410e9e2232d804ffad.zip | |
Show loading spinner on buttons in AccountScreen
Diffstat (limited to 'android/app/src')
6 files changed, 82 insertions, 17 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt index beb310452d..dba40b40dd 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt @@ -45,6 +45,8 @@ class AccountScreenTest { accountNumber = DUMMY_ACCOUNT_NUMBER, accountExpiry = null, showSitePayment = false, + showLogoutLoading = false, + showManageAccountLoading = false, ) ) } @@ -63,10 +65,12 @@ class AccountScreenTest { AccountScreen( state = AccountUiState( - showSitePayment = true, deviceName = DUMMY_DEVICE_NAME, accountNumber = DUMMY_ACCOUNT_NUMBER, accountExpiry = null, + showSitePayment = true, + showLogoutLoading = false, + showManageAccountLoading = false, ), onManageAccountClick = mockedClickHandler, ) @@ -92,6 +96,8 @@ class AccountScreenTest { accountNumber = DUMMY_ACCOUNT_NUMBER, accountExpiry = null, showSitePayment = false, + showLogoutLoading = false, + showManageAccountLoading = false, ), onRedeemVoucherClick = mockedClickHandler, ) @@ -117,6 +123,8 @@ class AccountScreenTest { accountNumber = DUMMY_ACCOUNT_NUMBER, accountExpiry = null, showSitePayment = false, + showLogoutLoading = false, + showManageAccountLoading = false, ), onLogoutClick = mockedClickHandler, ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ExternalActionButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ExternalActionButton.kt index 2666ab1485..a5db39659b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ExternalActionButton.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ExternalActionButton.kt @@ -2,7 +2,6 @@ package net.mullvad.mullvadvpn.compose.button import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.OpenInNew -import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -34,24 +33,41 @@ private fun PreviewExternalButtonLongText() { } } +@Preview +@Composable +private fun PreviewExternalButtonSpinner() { + AppTheme { + ExternalButton( + onClick = {}, + text = "Button text is long and is trying to take up space that is large", + isEnabled = true, + isLoading = true, + ) + } +} + @Composable fun ExternalButton( onClick: () -> Unit, text: String, modifier: Modifier = Modifier, isEnabled: Boolean = true, + isLoading: Boolean = false, ) { VariantButton( text = text, onClick = onClick, modifier = modifier, isEnabled = isEnabled, + isLoading = isLoading, icon = { - Icon( - imageVector = Icons.AutoMirrored.Filled.OpenInNew, - tint = MaterialTheme.colorScheme.onTertiary, - contentDescription = null, - ) + if (!isLoading) { + Icon( + imageVector = Icons.AutoMirrored.Filled.OpenInNew, + tint = MaterialTheme.colorScheme.onTertiary, + contentDescription = null, + ) + } }, ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/MullvadButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/MullvadButton.kt index af8e1a30d3..510f15827d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/MullvadButton.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/MullvadButton.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorSmall import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.lib.theme.Dimens import net.mullvad.mullvadvpn.lib.theme.color.Alpha20 @@ -74,6 +75,7 @@ fun NegativeButton( disabledContainerColor = MaterialTheme.colorScheme.errorDisabled, ), isEnabled: Boolean = true, + isLoading: Boolean = false, icon: @Composable (() -> Unit)? = null, ) { BaseButton( @@ -82,6 +84,7 @@ fun NegativeButton( text = text, modifier = modifier, isEnabled = isEnabled, + isLoading = isLoading, trailingIcon = icon, ) } @@ -100,6 +103,7 @@ fun VariantButton( disabledContainerColor = MaterialTheme.colorScheme.tertiaryDisabled, ), isEnabled: Boolean = true, + isLoading: Boolean = false, icon: @Composable (() -> Unit)? = null, ) { BaseButton( @@ -108,6 +112,7 @@ fun VariantButton( text = text, modifier = modifier, isEnabled = isEnabled, + isLoading = isLoading, trailingIcon = icon, ) } @@ -125,6 +130,7 @@ fun PrimaryButton( disabledContainerColor = MaterialTheme.colorScheme.primaryDisabled, ), isEnabled: Boolean = true, + isLoading: Boolean = false, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, ) { @@ -134,6 +140,7 @@ fun PrimaryButton( text = text, modifier = modifier, isEnabled = isEnabled, + isLoading = isLoading, leadingIcon = leadingIcon, trailingIcon = trailingIcon, ) @@ -146,6 +153,7 @@ private fun BaseButton( text: String, modifier: Modifier = Modifier, isEnabled: Boolean = true, + isLoading: Boolean = false, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, ) { @@ -176,14 +184,18 @@ private fun BaseButton( trailingIcon() } } - Text( - text = text, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f), - ) + if (isLoading) { + MullvadCircularProgressIndicatorSmall() + } else { + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + } when { trailingIcon != null -> Box(modifier = Modifier.padding(horizontal = Dimens.smallPadding)) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt index e6baaf9c20..a3b0845f31 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt @@ -33,6 +33,8 @@ class AccountUiStatePreviewParameterProvider : PreviewParameterProvider<AccountU ), ) ), + showLogoutLoading = false, + showManageAccountLoading = false, ) ) + generateOtherStates() } @@ -51,5 +53,7 @@ private fun generateOtherStates(): Sequence<AccountUiState> = accountExpiry = null, showSitePayment = false, billingPaymentState = state, + showLogoutLoading = false, + showManageAccountLoading = false, ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt index 2599b0f982..32ab727370 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt @@ -198,6 +198,7 @@ fun AccountScreen( text = stringResource(id = R.string.manage_account), onClick = onManageAccountClick, modifier = Modifier.padding(bottom = Dimens.buttonSpacing), + isLoading = state.showManageAccountLoading, ) } @@ -207,7 +208,11 @@ fun AccountScreen( isEnabled = true, ) - NegativeButton(text = stringResource(id = R.string.log_out), onClick = onLogoutClick) + NegativeButton( + text = stringResource(id = R.string.log_out), + onClick = onLogoutClick, + isLoading = state.showLogoutLoading, + ) } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt index 048a6fc913..8297375ee3 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -37,16 +38,23 @@ class AccountViewModel( private val _uiSideEffect = Channel<UiSideEffect>() val uiSideEffect = _uiSideEffect.receiveAsFlow() + private val isLoggingOut = MutableStateFlow(false) + private val isLoadingAccountPage = MutableStateFlow(false) + val uiState: StateFlow<AccountUiState> = combine( deviceRepository.deviceState.filterIsInstance<DeviceState.LoggedIn>(), accountData(), paymentUseCase.paymentAvailability, - ) { deviceState, accountData, paymentAvailability -> + isLoggingOut, + isLoadingAccountPage, + ) { deviceState, accountData, paymentAvailability, isLoggingOut, isLoadingAccountPage -> AccountUiState( deviceName = deviceState.device.displayName(), accountNumber = deviceState.accountNumber, accountExpiry = accountData?.expiryDate, + showLogoutLoading = isLoggingOut, + showManageAccountLoading = isLoadingAccountPage, showSitePayment = !isPlayBuild, billingPaymentState = paymentAvailability?.toPaymentState(), ) @@ -67,16 +75,24 @@ class AccountViewModel( .distinctUntilChanged() fun onManageAccountClick() { + if (isLoadingAccountPage.value) return + isLoadingAccountPage.value = true + viewModelScope.launch { val wwwAuthToken = accountRepository.getWebsiteAuthToken() _uiSideEffect.send(UiSideEffect.OpenAccountManagementPageInBrowser(wwwAuthToken)) + isLoadingAccountPage.value = false } } fun onLogoutClick() { + if (isLoggingOut.value) return + isLoggingOut.value = true + viewModelScope.launch { accountRepository .logout() + .also { isLoggingOut.value = false } .fold( { _uiSideEffect.send(UiSideEffect.GenericError) }, { _uiSideEffect.send(UiSideEffect.NavigateToLogin) }, @@ -142,6 +158,8 @@ data class AccountUiState( val accountExpiry: DateTime?, val showSitePayment: Boolean, val billingPaymentState: PaymentState? = null, + val showLogoutLoading: Boolean = false, + val showManageAccountLoading: Boolean = false, ) { companion object { fun default() = @@ -149,6 +167,8 @@ data class AccountUiState( deviceName = null, accountNumber = null, accountExpiry = null, + showLogoutLoading = false, + showManageAccountLoading = false, showSitePayment = false, billingPaymentState = PaymentState.Loading, ) |
