Skip to content

Commit

Permalink
Add billing payments to AccountViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
Pururun committed Sep 18, 2023
1 parent 3e90e3b commit 32af23d
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.mullvad.mullvadvpn.compose.state

sealed interface AccountDialogState {
data object NoDialog: AccountDialogState

data object VerificationError: AccountDialogState

data object PurchaseError: AccountDialogState

data object PurchaseComplete: AccountDialogState
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package net.mullvad.mullvadvpn.compose.state
import org.joda.time.DateTime

data class AccountUiState(
val deviceName: String,
val accountNumber: String,
val accountExpiry: DateTime?
val deviceName: String = "",
val accountNumber: String = "",
val accountExpiry: DateTime? = null,
val webPaymentAvailable: Boolean = false,
val billingPaymentState: PaymentState = PaymentState.Loading,
val purchaseLoading: Boolean = false,
val dialogState: AccountDialogState = AccountDialogState.NoDialog
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.mullvad.mullvadvpn.compose.state

import net.mullvad.mullvadvpn.lib.payment.PaymentProduct

sealed interface PaymentState {
data object Loading : PaymentState

data object NoPayment : PaymentState

data object GenericError : PaymentState

data object BillingError : PaymentState

data class PaymentAvailable(val products: List<PaymentProduct>): PaymentState
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.mullvad.mullvadvpn.di

import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
Expand All @@ -8,7 +9,9 @@ import kotlinx.coroutines.Dispatchers
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.applist.ApplicationsIconManager
import net.mullvad.mullvadvpn.applist.ApplicationsProvider
import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
import net.mullvad.mullvadvpn.lib.payment.PaymentRepository
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.ChangelogRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
Expand Down Expand Up @@ -73,7 +76,7 @@ val uiModule = module {
single<IChangelogDataProvider> { ChangelogDataProvider(get()) }

// View models
viewModel { AccountViewModel(get(), get(), get()) }
viewModel { AccountViewModel(get(), get(), get(), get()) }
viewModel {
ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.AccountDialogState
import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.compose.state.PaymentState
import net.mullvad.mullvadvpn.lib.payment.BillingPaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.PaymentRepository
import net.mullvad.mullvadvpn.lib.payment.PurchaseResult
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
Expand All @@ -18,34 +25,45 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
class AccountViewModel(
private var accountRepository: AccountRepository,
private var serviceConnectionManager: ServiceConnectionManager,
private val paymentRepository: PaymentRepository,
deviceRepository: DeviceRepository
) : ViewModel() {

init {
viewModelScope.launch {
paymentRepository.verifyPurchases()
}
}

private val _dialogState = MutableStateFlow<AccountDialogState>(AccountDialogState.NoDialog)
private val _purchaseLoading = MutableStateFlow(false)
private val _viewActions = MutableSharedFlow<ViewAction>(extraBufferCapacity = 1)
private val _enterTransitionEndAction = MutableSharedFlow<Unit>()
val viewActions = _viewActions.asSharedFlow()

private val vmState: StateFlow<AccountUiState> =
combine(deviceRepository.deviceState, accountRepository.accountExpiryState) {
deviceState,
accountExpiry ->
combine(
deviceRepository.deviceState,
accountRepository.accountExpiryState,
paymentRepository.productsFlow(),
_purchaseLoading,
_dialogState
) { deviceState, accountExpiry, paymentAvailability, purchaseLoading, dialogState ->
AccountUiState(
deviceName = deviceState.deviceName() ?: "",
accountNumber = deviceState.token() ?: "",
accountExpiry = accountExpiry.date()
accountExpiry = accountExpiry.date(),
webPaymentAvailable = paymentAvailability?.webPaymentAvailable ?: false,
billingPaymentState =
paymentAvailability?.billingPaymentAvailability?.toPaymentState()
?: PaymentState.Loading,
purchaseLoading = purchaseLoading,
dialogState = dialogState
)
}
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
AccountUiState(deviceName = "", accountNumber = "", accountExpiry = null)
)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), AccountUiState())
val uiState =
vmState.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
AccountUiState(deviceName = "", accountNumber = "", accountExpiry = null)
)
vmState.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), AccountUiState())

val enterTransitionEndAction = _enterTransitionEndAction.asSharedFlow()

Expand All @@ -67,6 +85,46 @@ class AccountViewModel(
viewModelScope.launch { _enterTransitionEndAction.emit(Unit) }
}

fun startBillingPayment(productId: String) {
viewModelScope.launch {
_purchaseLoading.tryEmit(true)
val result = paymentRepository.purchaseBillingProduct(productId)
_purchaseLoading.tryEmit(false)
when (result) {
PurchaseResult.PurchaseCancelled -> {
// Do nothing
}
PurchaseResult.PurchaseCompleted -> {
// Show purchase completed dialog
_dialogState.tryEmit(AccountDialogState.PurchaseComplete)
}
PurchaseResult.PurchaseError -> {
// Show purchase error dialog
_dialogState.tryEmit(AccountDialogState.PurchaseError)
}
PurchaseResult.VerificationError -> {
// Show verification error dialog
_dialogState.tryEmit(AccountDialogState.VerificationError)
}
}
}
}

private fun PaymentRepository.productsFlow() = callbackFlow {
this.trySend(null)
this.trySend(this@productsFlow.queryAvailablePaymentTypes())
}

private fun BillingPaymentAvailability.toPaymentState(): PaymentState =
when (this) {
BillingPaymentAvailability.Error.ServiceUnavailable,
BillingPaymentAvailability.Error.BillingUnavailable -> PaymentState.BillingError
is BillingPaymentAvailability.Error.Other -> PaymentState.GenericError
is BillingPaymentAvailability.ProductsAvailable ->
PaymentState.PaymentAvailable(products)
BillingPaymentAvailability.ProductsUnavailable -> PaymentState.NoPayment
}

sealed class ViewAction {
data class OpenAccountManagementPageInBrowser(val token: String) : ViewAction()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.mullvad.mullvadvpn.lib.payment

import android.app.Activity
import kotlinx.coroutines.flow.singleOrNull
import net.mullvad.mullvadvpn.lib.billing.BillingRepository
import net.mullvad.mullvadvpn.lib.billing.model.BillingProduct
Expand All @@ -10,7 +11,8 @@ import net.mullvad.mullvadvpn.lib.billing.model.QueryProductResult
import net.mullvad.mullvadvpn.lib.billing.model.QueryPurchasesResult

class PaymentRepository(
private val billingRepository: BillingRepository,
activity: Activity,
private val billingRepository: BillingRepository = BillingRepository(activity = activity),
private val showWebPayment: Boolean
) {
suspend fun queryAvailablePaymentTypes(): PaymentAvailability =
Expand Down

0 comments on commit 32af23d

Please sign in to comment.