summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorKalle Lindström <karl.lindstrom@mullvad.net>2024-10-24 13:55:22 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-10-30 13:04:40 +0100
commit1cfd376fe82c1164a2706d410e9e2232d804ffad (patch)
tree45db5abc5ad1760b2683abb3f50498bd37af5ef0 /android/app/src
parent3bbdf3455e85df030b1def0bca83f2037a87f478 (diff)
downloadmullvadvpn-1cfd376fe82c1164a2706d410e9e2232d804ffad.tar.xz
mullvadvpn-1cfd376fe82c1164a2706d410e9e2232d804ffad.zip
Show loading spinner on buttons in AccountScreen
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ExternalActionButton.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/MullvadButton.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt22
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,
)