Skip to content

Commit

Permalink
Add payment support to account screen
Browse files Browse the repository at this point in the history
  • Loading branch information
Pururun committed Sep 19, 2023
1 parent 1f2abdf commit e89a37f
Show file tree
Hide file tree
Showing 15 changed files with 4,336 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fun ChangelogDialog(changesList: List<String>, version: String, onDismiss: () ->
shape = MaterialTheme.shapes.small
) {
Text(
text = stringResource(R.string.changes_dialog_dismiss_button),
text = stringResource(R.string.got_it),
fontSize = 18.sp
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fun InfoDialog(message: String, additionalInfo: String? = null, onDismiss: () ->
shape = MaterialTheme.shapes.small
) {
Text(
text = stringResource(R.string.changes_dialog_dismiss_button),
text = stringResource(R.string.got_it),
fontSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.mullvad.mullvadvpn.compose.dialog

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R

@Preview
@Composable
fun PreviewPaymentBillingErrorDialog() {
PaymentBillingErrorDialog(onTryAgain = {}, onClose = {})
}

@Composable
fun PaymentBillingErrorDialog(onTryAgain: () -> Unit, onClose: () -> Unit) {
PaymentDialog(
title = stringResource(id = R.string.payment_billing_error_dialog_title),
message = stringResource(id = R.string.payment_billing_error_dialog_message),
icon = R.drawable.icon_fail,
onConfirmClick = onClose,
confirmText = stringResource(id = R.string.cancel),
onDismissRequest = onClose,
dismissText = stringResource(id = R.string.try_again),
onDismissClick = onTryAgain
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.mullvad.mullvadvpn.compose.dialog

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R

@Preview
@Composable
fun PreviewPaymentCompletedDialog() {
PaymentCompletedDialog {}
}

@Composable
fun PaymentCompletedDialog(onClose: () -> Unit) {
PaymentDialog(
title = stringResource(id = R.string.payment_completed_dialog_title),
message = stringResource(id = R.string.payment_completed_dialog_message),
icon = R.drawable.icon_success,
onConfirmClick = onClose,
confirmText = stringResource(id = R.string.got_it),
onDismissRequest = onClose
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package net.mullvad.mullvadvpn.compose.dialog

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.ActionButton
import net.mullvad.mullvadvpn.lib.theme.AlphaDescription
import net.mullvad.mullvadvpn.lib.theme.Dimens

@Preview
@Composable
private fun PreviewPaymentDialog() {
PaymentDialog(
title = "Payment was unsuccessful",
message = "We were unable to verify your paymenmt, please try again",
icon = R.drawable.icon_fail,
onConfirmClick = {},
confirmText = "Cancel",
onDismissRequest = {},
dismissText = "Try again",
onDismissClick = {},
)
}

@Composable
fun PaymentDialog(
title: String,
message: String,
icon: Int,
onConfirmClick: () -> Unit,
confirmText: String,
dismissText: String? = null,
onDismissClick: () -> Unit = {},
onDismissRequest: () -> Unit
) {
AlertDialog(
title = {
Column {
Icon(
modifier = Modifier.fillMaxWidth().height(Dimens.iconHeight),
painter = painterResource(id = icon),
contentDescription = ""
)
Text(text = title, style = MaterialTheme.typography.headlineSmall)
}
},
text = { Text(text = message, style = MaterialTheme.typography.bodySmall) },
containerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.onBackground,
textContentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = AlphaDescription),
onDismissRequest = onDismissRequest,
dismissButton = {
dismissText?.let {
ActionButton(
text = dismissText,
onClick = onDismissClick,
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError,
)
)
}
},
confirmButton = {
ActionButton(
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
),
text = confirmText,
onClick = onConfirmClick
)
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.mullvad.mullvadvpn.compose.dialog

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R

@Preview
@Composable
fun PreviewPaymentVerificationErrorDialog() {
PaymentVerificationErrorDialog(onTryAgain = {}, onClose = {})
}

@Composable
fun PaymentVerificationErrorDialog(onTryAgain: () -> Unit, onClose: () -> Unit) {
PaymentDialog(
title = stringResource(id = R.string.payment_verification_error_dialog_title),
message = stringResource(id = R.string.payment_verification_error_dialog_message),
icon = R.drawable.icon_fail,
onConfirmClick = onClose,
confirmText = stringResource(id = R.string.cancel),
onDismissRequest = onClose,
dismissText = stringResource(id = R.string.try_again),
onDismissClick = onTryAgain
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
Expand All @@ -26,7 +28,6 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import me.onebone.toolbar.ScrollStrategy
import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.ActionButton
import net.mullvad.mullvadvpn.compose.component.CollapsingToolbarScaffold
Expand All @@ -35,10 +36,15 @@ import net.mullvad.mullvadvpn.compose.component.CopyableObfuscationView
import net.mullvad.mullvadvpn.compose.component.InformationView
import net.mullvad.mullvadvpn.compose.component.MissingPolicy
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.dialog.PaymentBillingErrorDialog
import net.mullvad.mullvadvpn.compose.dialog.PaymentCompletedDialog
import net.mullvad.mullvadvpn.compose.dialog.PaymentVerificationErrorDialog
import net.mullvad.mullvadvpn.compose.state.AccountDialogState
import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
import net.mullvad.mullvadvpn.compose.state.PaymentState
import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
import net.mullvad.mullvadvpn.lib.payment.PaymentProduct
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.util.toExpiryDateString
import net.mullvad.mullvadvpn.viewmodel.AccountViewModel
Expand All @@ -52,7 +58,12 @@ private fun PreviewAccountScreen() {
AccountUiState(
deviceName = "Test Name",
accountNumber = "1234123412341234",
accountExpiry = null
accountExpiry = null,
webPaymentAvailable = true,
billingPaymentState =
PaymentState.PaymentAvailable(
listOf(PaymentProduct("productId", price = "34 SEK"))
)
),
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow(),
enterTransitionEndAction = MutableSharedFlow()
Expand All @@ -68,6 +79,10 @@ fun AccountScreen(
onRedeemVoucherClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
onLogoutClick: () -> Unit = {},
onPurchaseBillingProductClick: (productId: String) -> Unit = {},
onDialogClose: () -> Unit = {},
onTryVerificationAgain: () -> Unit = {},
onTryFetchProductsAgain: () -> Unit = {},
onBackClick: () -> Unit = {}
) {
val context = LocalContext.current
Expand All @@ -79,6 +94,41 @@ fun AccountScreen(
LaunchedEffect(Unit) {
enterTransitionEndAction.collect { systemUiController.setStatusBarColor(backgroundColor) }
}
LaunchedEffect(Unit) {
viewActions.collect { viewAction ->
if (viewAction is AccountViewModel.ViewAction.OpenAccountManagementPageInBrowser) {
context.openAccountPageInBrowser(viewAction.token)
}
}
}

when (uiState.dialogState) {
AccountDialogState.NoDialog -> {
// Show nothing
}
AccountDialogState.PurchaseComplete -> {
PaymentCompletedDialog(onClose = onDialogClose)
}
AccountDialogState.BillingError -> {
PaymentBillingErrorDialog(
onTryAgain = {
onDialogClose()
onTryFetchProductsAgain()
},
onClose = onDialogClose
)
}
AccountDialogState.VerificationError -> {
PaymentVerificationErrorDialog(
onTryAgain = {
onDialogClose()
onTryVerificationAgain()
},
onClose = onDialogClose
)
}
}

CollapsingToolbarScaffold(
backgroundColor = MaterialTheme.colorScheme.background,
modifier = Modifier.fillMaxSize(),
Expand All @@ -101,14 +151,6 @@ fun AccountScreen(
)
},
) {
LaunchedEffect(Unit) {
viewActions.collect { viewAction ->
if (viewAction is AccountViewModel.ViewAction.OpenAccountManagementPageInBrowser) {
context.openAccountPageInBrowser(viewAction.token)
}
}
}

val scrollState = rememberScrollState()

Column(
Expand Down Expand Up @@ -158,7 +200,52 @@ fun AccountScreen(

Spacer(modifier = Modifier.weight(1f))

if (BuildConfig.BUILD_TYPE != BuildTypes.RELEASE) {
when (uiState.billingPaymentState) {
PaymentState.BillingError,
PaymentState.GenericError -> {
// We show some kind of dialog error at the top
}
PaymentState.Loading -> {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.onBackground,
modifier =
Modifier.padding(
start = Dimens.sideMargin,
end = Dimens.sideMargin,
bottom = Dimens.screenVerticalMargin
)
.size(
width = Dimens.progressIndicatorSize,
height = Dimens.progressIndicatorSize
)
.align(Alignment.CenterHorizontally)
)
}
PaymentState.NoPayment -> {
// Show nothing
}
is PaymentState.PaymentAvailable -> {
uiState.billingPaymentState.products.forEach { product ->
ActionButton(
text = stringResource(id = R.string.add_30_days_time_x, product.price),
onClick = { onPurchaseBillingProductClick(product.productId) },
modifier =
Modifier.padding(
start = Dimens.sideMargin,
end = Dimens.sideMargin,
bottom = Dimens.screenVerticalMargin
),
colors =
ButtonDefaults.buttonColors(
contentColor = MaterialTheme.colorScheme.onPrimary,
containerColor = MaterialTheme.colorScheme.surface
)
)
}
}
}

if (uiState.webPaymentAvailable) {
ActionButton(
text = stringResource(id = R.string.manage_account),
onClick = onManageAccountClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sealed interface AccountDialogState {

data object VerificationError: AccountDialogState

data object PurchaseError: AccountDialogState
data object BillingError: AccountDialogState

data object PurchaseComplete: AccountDialogState
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.apache.commons.validator.routines.InetAddressValidator
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.parameter.parametersOf
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.koin.dsl.onClose
Expand Down Expand Up @@ -75,8 +76,17 @@ val uiModule = module {

single<IChangelogDataProvider> { ChangelogDataProvider(get()) }

single { (activity: Activity) ->
PaymentRepository(
activity = activity,
showWebPayment = BuildConfig.BUILD_TYPE != BuildTypes.RELEASE
)
}

// View models
viewModel { AccountViewModel(get(), get(), get(), get()) }
viewModel { (activity: Activity) ->
AccountViewModel(get(), get(), get() { parametersOf(activity) }, get())
}
viewModel {
ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG)
}
Expand Down
Loading

0 comments on commit e89a37f

Please sign in to comment.