Skip to content

Commit

Permalink
Show loading spinner on buttons in AccountScreen
Browse files Browse the repository at this point in the history
  • Loading branch information
kl committed Oct 29, 2024
1 parent 63638d7 commit 73b212b
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class AccountScreenTest {
accountNumber = DUMMY_ACCOUNT_NUMBER,
accountExpiry = null,
showSitePayment = false,
showLogoutLoading = false,
showManageAccountLoading = false,
)
)
}
Expand All @@ -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,
)
Expand All @@ -92,6 +96,8 @@ class AccountScreenTest {
accountNumber = DUMMY_ACCOUNT_NUMBER,
accountExpiry = null,
showSitePayment = false,
showLogoutLoading = false,
showManageAccountLoading = false,
),
onRedeemVoucherClick = mockedClickHandler,
)
Expand All @@ -117,6 +123,8 @@ class AccountScreenTest {
accountNumber = DUMMY_ACCOUNT_NUMBER,
accountExpiry = null,
showSitePayment = false,
showLogoutLoading = false,
showManageAccountLoading = false,
),
onLogoutClick = mockedClickHandler,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,6 +75,7 @@ fun NegativeButton(
disabledContainerColor = MaterialTheme.colorScheme.errorDisabled,
),
isEnabled: Boolean = true,
isLoading: Boolean = false,
icon: @Composable (() -> Unit)? = null,
) {
BaseButton(
Expand All @@ -82,6 +84,7 @@ fun NegativeButton(
text = text,
modifier = modifier,
isEnabled = isEnabled,
isLoading = isLoading,
trailingIcon = icon,
)
}
Expand All @@ -100,6 +103,7 @@ fun VariantButton(
disabledContainerColor = MaterialTheme.colorScheme.tertiaryDisabled,
),
isEnabled: Boolean = true,
isLoading: Boolean = false,
icon: @Composable (() -> Unit)? = null,
) {
BaseButton(
Expand All @@ -108,6 +112,7 @@ fun VariantButton(
text = text,
modifier = modifier,
isEnabled = isEnabled,
isLoading = isLoading,
trailingIcon = icon,
)
}
Expand All @@ -125,6 +130,7 @@ fun PrimaryButton(
disabledContainerColor = MaterialTheme.colorScheme.primaryDisabled,
),
isEnabled: Boolean = true,
isLoading: Boolean = false,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
) {
Expand All @@ -134,6 +140,7 @@ fun PrimaryButton(
text = text,
modifier = modifier,
isEnabled = isEnabled,
isLoading = isLoading,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
)
Expand All @@ -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,
) {
Expand Down Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class AccountUiStatePreviewParameterProvider : PreviewParameterProvider<AccountU
),
)
),
showLogoutLoading = false,
showManageAccountLoading = false,
)
) + generateOtherStates()
}
Expand All @@ -51,5 +53,7 @@ private fun generateOtherStates(): Sequence<AccountUiState> =
accountExpiry = null,
showSitePayment = false,
billingPaymentState = state,
showLogoutLoading = false,
showManageAccountLoading = false,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ fun AccountScreen(
text = stringResource(id = R.string.manage_account),
onClick = onManageAccountClick,
modifier = Modifier.padding(bottom = Dimens.buttonSpacing),
isLoading = state.showManageAccountLoading,
)
}

Expand All @@ -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,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
)
Expand All @@ -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) },
Expand Down Expand Up @@ -142,13 +158,17 @@ 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() =
AccountUiState(
deviceName = null,
accountNumber = null,
accountExpiry = null,
showLogoutLoading = false,
showManageAccountLoading = false,
showSitePayment = false,
billingPaymentState = PaymentState.Loading,
)
Expand Down

0 comments on commit 73b212b

Please sign in to comment.