diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-11-16 02:02:30 +0100 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-11-16 09:34:26 +0100 |
| commit | 91a030a18450c5b7bc531544c54df0445746f1fa (patch) | |
| tree | 2fc905270638fe32476544bfcbaa9f9cd3079535 /android | |
| parent | 868e35cb9a814fea6c313eb93849ab426326be53 (diff) | |
| download | mullvadvpn-91a030a18450c5b7bc531544c54df0445746f1fa.tar.xz mullvadvpn-91a030a18450c5b7bc531544c54df0445746f1fa.zip | |
Add payment support to AccountViewModel
Diffstat (limited to 'android')
16 files changed, 278 insertions, 23 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 3da373a368..a3035f0a12 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -317,6 +317,9 @@ dependencies { implementation(project(Dependencies.Mullvad.themeLib)) implementation(project(Dependencies.Mullvad.paymentLib)) + // Play implementation + playImplementation(project(Dependencies.Mullvad.billingLib)) + implementation(Dependencies.androidMaterial) implementation(Dependencies.commonsValidator) implementation(Dependencies.AndroidX.appcompat) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ChangelogDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ChangelogDialog.kt index 89af2eafe9..9ce21c6bac 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ChangelogDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/ChangelogDialog.kt @@ -50,10 +50,7 @@ fun ChangelogDialog(changesList: List<String>, version: String, onDismiss: () -> } }, confirmButton = { - PrimaryButton( - text = stringResource(R.string.changes_dialog_dismiss_button), - onClick = onDismiss - ) + PrimaryButton(text = stringResource(R.string.got_it), onClick = onDismiss) }, containerColor = MaterialTheme.colorScheme.background, titleContentColor = MaterialTheme.colorScheme.onBackground diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt index ad12932405..d032a9fa8e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/InfoDialog.kt @@ -88,7 +88,7 @@ fun InfoDialog(message: String, additionalInfo: String? = null, onDismiss: () -> confirmButton = { PrimaryButton( modifier = Modifier.wrapContentHeight().fillMaxWidth(), - text = stringResource(R.string.changes_dialog_dismiss_button), + text = stringResource(R.string.got_it), onClick = onDismiss, ) }, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt index 14afdbcf24..c5b619a9cd 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt @@ -120,7 +120,7 @@ fun RedeemVoucherDialog( stringResource( id = if (uiState.voucherViewModelState is VoucherDialogState.Success) - R.string.changes_dialog_dismiss_button + R.string.got_it else R.string.cancel ), onClick = { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/payment/PaymentDialogData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/payment/PaymentDialogData.kt new file mode 100644 index 0000000000..9876964610 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/payment/PaymentDialogData.kt @@ -0,0 +1,26 @@ +package net.mullvad.mullvadvpn.compose.dialog.payment + +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.lib.payment.model.ProductId + +data class PaymentDialogData( + val title: Int? = null, + val message: Int? = null, + val icon: PaymentDialogIcon? = null, + val confirmAction: PaymentDialogAction? = null, + val dismissAction: PaymentDialogAction? = null, + val closeOnDismiss: Boolean = true, + val successfulPayment: Boolean = false +) + +sealed class PaymentDialogAction(val message: Int) { + data object Close : PaymentDialogAction(R.string.got_it) + + data class RetryPurchase(val productId: ProductId) : PaymentDialogAction(R.string.try_again) +} + +enum class PaymentDialogIcon { + SUCCESS, + FAIL, + LOADING +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/PaymentState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/PaymentState.kt new file mode 100644 index 0000000000..60f8d5864f --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/PaymentState.kt @@ -0,0 +1,19 @@ +package net.mullvad.mullvadvpn.compose.state + +import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct + +sealed interface PaymentState { + data object Loading : PaymentState + + data object NoPayment : PaymentState + + data object NoProductsFounds : PaymentState + + data class PaymentAvailable(val products: List<PaymentProduct>) : PaymentState + + sealed interface Error : PaymentState { + data object Generic : Error + + data object Billing : Error + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt index de8801557a..6ed744aef9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt @@ -12,6 +12,7 @@ import net.mullvad.mullvadvpn.applist.ApplicationsProvider import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher import net.mullvad.mullvadvpn.lib.ipc.MessageHandler +import net.mullvad.mullvadvpn.lib.payment.PaymentProvider import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.mullvadvpn.repository.ChangelogRepository import net.mullvad.mullvadvpn.repository.DeviceRepository @@ -22,7 +23,10 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling import net.mullvad.mullvadvpn.usecase.AccountExpiryNotificationUseCase +import net.mullvad.mullvadvpn.usecase.EmptyPaymentUseCase import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase +import net.mullvad.mullvadvpn.usecase.PaymentUseCase +import net.mullvad.mullvadvpn.usecase.PlayPaymentUseCase import net.mullvad.mullvadvpn.usecase.PortRangeUseCase import net.mullvad.mullvadvpn.usecase.RelayListUseCase import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase @@ -100,8 +104,20 @@ val uiModule = module { single { RelayListListener(get()) } + // Will be resolved using from either of the two PaymentModule.kt classes. + single { PaymentProvider(get()) } + + single<PaymentUseCase> { + val paymentRepository = get<PaymentProvider>().paymentRepository + if (paymentRepository != null) { + PlayPaymentUseCase(paymentRepository = paymentRepository) + } else { + EmptyPaymentUseCase() + } + } + // View models - viewModel { AccountViewModel(get(), get(), get()) } + viewModel { AccountViewModel(get(), get(), get(), get()) } viewModel { ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG) } @@ -114,10 +130,10 @@ val uiModule = module { viewModel { SettingsViewModel(get(), get()) } viewModel { VoucherDialogViewModel(get(), get()) } viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get()) } - viewModel { WelcomeViewModel(get(), get(), get(), get()) } + viewModel { WelcomeViewModel(get(), get(), get()) } viewModel { ReportProblemViewModel(get()) } viewModel { ViewLogsViewModel(get()) } - viewModel { OutOfTimeViewModel(get(), get(), get(), get()) } + viewModel { OutOfTimeViewModel(get(), get(), get()) } } const val SELF_PACKAGE_NAME = "SELF_PACKAGE_NAME" diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt index 98b0c0576c..f299b8c956 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.withTimeoutOrNull import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.dialog.ChangelogDialog +import net.mullvad.mullvadvpn.di.paymentModule import net.mullvad.mullvadvpn.di.uiModule import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.isNotificationPermissionGranted import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration @@ -54,6 +55,7 @@ import net.mullvad.mullvadvpn.viewmodel.ChangelogDialogUiState import net.mullvad.mullvadvpn.viewmodel.ChangelogViewModel import org.koin.android.ext.android.getKoin import org.koin.core.context.loadKoinModules +import org.koin.dsl.bind open class MainActivity : FragmentActivity() { private val requestNotificationPermissionLauncher = @@ -78,7 +80,7 @@ open class MainActivity : FragmentActivity() { private var currentDeviceState: DeviceState? = null override fun onCreate(savedInstanceState: Bundle?) { - loadKoinModules(uiModule) + loadKoinModules(listOf(uiModule, paymentModule)) getKoin().apply { accountRepository = get() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PaymentAvailabilityExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PaymentAvailabilityExtensions.kt new file mode 100644 index 0000000000..6a69a807f1 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PaymentAvailabilityExtensions.kt @@ -0,0 +1,19 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.compose.state.PaymentState +import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability + +fun PaymentAvailability.toPaymentState(): PaymentState = + when (this) { + PaymentAvailability.Error.ServiceUnavailable, + PaymentAvailability.Error.BillingUnavailable -> PaymentState.Error.Billing + is PaymentAvailability.Error.Other -> PaymentState.Error.Generic + is PaymentAvailability.ProductsAvailable -> PaymentState.PaymentAvailable(products) + PaymentAvailability.ProductsUnavailable -> PaymentState.NoPayment + PaymentAvailability.NoProductsFounds -> PaymentState.NoProductsFounds + PaymentAvailability.Loading -> PaymentState.Loading + // Unrecoverable error states + PaymentAvailability.Error.DeveloperError, + PaymentAvailability.Error.FeatureNotSupported, + PaymentAvailability.Error.ItemUnavailable -> PaymentState.NoPayment + } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PurchaseResultExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PurchaseResultExtensions.kt new file mode 100644 index 0000000000..bf6dbec35e --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PurchaseResultExtensions.kt @@ -0,0 +1,78 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogAction +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogData +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogIcon +import net.mullvad.mullvadvpn.lib.payment.model.ProductId +import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult + +fun PurchaseResult.toPaymentDialogData(): PaymentDialogData? = + when (this) { + // Idle states + PurchaseResult.Completed.Cancelled, + PurchaseResult.BillingFlowStarted, + is PurchaseResult.Error.BillingError -> { + // Show nothing + null + } + // Fetching products and obfuscated id loading state + PurchaseResult.FetchingProducts, + PurchaseResult.FetchingObfuscationId -> + PaymentDialogData( + title = R.string.loading_connecting, + icon = PaymentDialogIcon.LOADING, + closeOnDismiss = false + ) + // Verifying loading states + PurchaseResult.VerificationStarted -> + PaymentDialogData( + title = R.string.loading_verifying, + icon = PaymentDialogIcon.LOADING, + closeOnDismiss = false + ) + // Pending state + PurchaseResult.Completed.Pending, + is PurchaseResult.Error.VerificationError -> + PaymentDialogData( + title = R.string.payment_pending_dialog_title, + message = R.string.payment_pending_dialog_message, + confirmAction = PaymentDialogAction.Close + ) + // Success state + PurchaseResult.Completed.Success -> + PaymentDialogData( + title = R.string.payment_completed_dialog_title, + message = R.string.payment_completed_dialog_message, + icon = PaymentDialogIcon.SUCCESS, + confirmAction = PaymentDialogAction.Close, + successfulPayment = true + ) + // Error states + is PurchaseResult.Error.TransactionIdError -> + PaymentDialogData( + title = R.string.payment_obfuscation_id_error_dialog_title, + message = R.string.payment_obfuscation_id_error_dialog_message, + icon = PaymentDialogIcon.FAIL, + confirmAction = PaymentDialogAction.Close, + dismissAction = PaymentDialogAction.RetryPurchase(productId = this.productId), + ) + is PurchaseResult.Error.FetchProductsError, + is PurchaseResult.Error.NoProductFound -> { + PaymentDialogData( + title = R.string.payment_billing_error_dialog_title, + message = R.string.payment_billing_error_dialog_message, + icon = PaymentDialogIcon.FAIL, + confirmAction = PaymentDialogAction.Close, + dismissAction = + PaymentDialogAction.RetryPurchase( + productId = + when (this) { + is PurchaseResult.Error.FetchProductsError -> this.productId + is PurchaseResult.Error.NoProductFound -> this.productId + else -> ProductId("") + } + ), + ) + } + } 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 fb3e3d6393..5f72167499 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 @@ -1,39 +1,54 @@ package net.mullvad.mullvadvpn.viewmodel +import android.app.Activity import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogData +import net.mullvad.mullvadvpn.compose.state.PaymentState +import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.mullvadvpn.repository.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache +import net.mullvad.mullvadvpn.usecase.PaymentUseCase +import net.mullvad.mullvadvpn.util.toPaymentDialogData +import net.mullvad.mullvadvpn.util.toPaymentState import org.joda.time.DateTime class AccountViewModel( - private var accountRepository: AccountRepository, - private var serviceConnectionManager: ServiceConnectionManager, + private val accountRepository: AccountRepository, + private val serviceConnectionManager: ServiceConnectionManager, + private val paymentUseCase: PaymentUseCase, deviceRepository: DeviceRepository ) : ViewModel() { private val _uiSideEffect = MutableSharedFlow<UiSideEffect>(extraBufferCapacity = 1) private val _enterTransitionEndAction = MutableSharedFlow<Unit>() + val uiSideEffect = _uiSideEffect.asSharedFlow() - val uiState = - combine(deviceRepository.deviceState, accountRepository.accountExpiryState) { - deviceState, - accountExpiry -> + val uiState: StateFlow<AccountUiState> = + combine( + deviceRepository.deviceState, + accountRepository.accountExpiryState, + paymentUseCase.purchaseResult, + paymentUseCase.paymentAvailability + ) { deviceState, accountExpiry, purchaseResult, paymentAvailability -> AccountUiState( - deviceName = deviceState.deviceName(), - accountNumber = deviceState.token(), - accountExpiry = accountExpiry.date() + deviceName = deviceState.deviceName() ?: "", + accountNumber = deviceState.token() ?: "", + accountExpiry = accountExpiry.date(), + paymentDialogData = purchaseResult?.toPaymentDialogData(), + billingPaymentState = paymentAvailability?.toPaymentState() ) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), AccountUiState.default()) @@ -42,7 +57,9 @@ class AccountViewModel( val enterTransitionEndAction = _enterTransitionEndAction.asSharedFlow() init { - accountRepository.fetchAccountExpiry() + updateAccountExpiry() + verifyPurchases() + fetchPaymentAvailability() } fun onManageAccountClick() { @@ -63,6 +80,40 @@ class AccountViewModel( viewModelScope.launch { _enterTransitionEndAction.emit(Unit) } } + fun startBillingPayment(productId: ProductId, activityProvider: () -> Activity) { + viewModelScope.launch { paymentUseCase.purchaseProduct(productId, activityProvider) } + } + + private fun verifyPurchases() { + viewModelScope.launch { + paymentUseCase.verifyPurchases() + updateAccountExpiry() + } + } + + private fun fetchPaymentAvailability() { + viewModelScope.launch { paymentUseCase.queryPaymentAvailability() } + } + + fun onClosePurchaseResultDialog(success: Boolean) { + // We are closing the dialog without any action, this can happen either if an error occurred + // during the purchase or the purchase ended successfully. + // In those cases we want to update the both the payment availability and the account + // expiry. + if (success) { + updateAccountExpiry() + } else { + fetchPaymentAvailability() + } + viewModelScope.launch { + paymentUseCase.resetPurchaseResult() // So that we do not show the dialog again. + } + } + + private fun updateAccountExpiry() { + accountRepository.fetchAccountExpiry() + } + sealed class UiSideEffect { data class OpenAccountManagementPageInBrowser(val token: String) : UiSideEffect() } @@ -71,14 +122,18 @@ class AccountViewModel( data class AccountUiState( val deviceName: String?, val accountNumber: String?, - val accountExpiry: DateTime? + val accountExpiry: DateTime?, + val billingPaymentState: PaymentState? = null, + val paymentDialogData: PaymentDialogData? = null ) { companion object { fun default() = AccountUiState( deviceName = DeviceState.Unknown.deviceName(), accountNumber = DeviceState.Unknown.token(), - accountExpiry = AccountExpiry.Missing.date() + accountExpiry = AccountExpiry.Missing.date(), + billingPaymentState = PaymentState.Loading, + paymentDialogData = null, ) } } diff --git a/android/app/src/oss/kotlin/net/mullvad/mullvadvpn/di/PaymentModule.kt b/android/app/src/oss/kotlin/net/mullvad/mullvadvpn/di/PaymentModule.kt new file mode 100644 index 0000000000..cb5cb649a6 --- /dev/null +++ b/android/app/src/oss/kotlin/net/mullvad/mullvadvpn/di/PaymentModule.kt @@ -0,0 +1,6 @@ +package net.mullvad.mullvadvpn.di + +import net.mullvad.mullvadvpn.lib.payment.PaymentProvider +import org.koin.dsl.module + +val paymentModule = module { single { PaymentProvider(null) } } diff --git a/android/app/src/play/kotlin/net/mullvad/mullvadvpn/di/PaymentModule.kt b/android/app/src/play/kotlin/net/mullvad/mullvadvpn/di/PaymentModule.kt new file mode 100644 index 0000000000..82738b5246 --- /dev/null +++ b/android/app/src/play/kotlin/net/mullvad/mullvadvpn/di/PaymentModule.kt @@ -0,0 +1,14 @@ +package net.mullvad.mullvadvpn.di + +import net.mullvad.mullvadvpn.lib.billing.BillingPaymentRepository +import net.mullvad.mullvadvpn.lib.billing.BillingRepository +import net.mullvad.mullvadvpn.lib.billing.PlayPurchaseRepository +import net.mullvad.mullvadvpn.lib.payment.PaymentProvider +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val paymentModule = module { + single { BillingRepository(androidContext()) } + single { PaymentProvider(BillingPaymentRepository(get(), get())) } + single { PlayPurchaseRepository(get()) } +} diff --git a/android/buildSrc/src/main/kotlin/Extensions.kt b/android/buildSrc/src/main/kotlin/Extensions.kt index 8659cb8ecb..0115aa9f30 100644 --- a/android/buildSrc/src/main/kotlin/Extensions.kt +++ b/android/buildSrc/src/main/kotlin/Extensions.kt @@ -13,3 +13,6 @@ fun String.isNonStableVersion(): Boolean { fun DependencyHandler.`leakCanaryImplementation`(dependencyNotation: Any): Dependency? = add("leakCanaryImplementation", dependencyNotation) + +fun DependencyHandler.`playImplementation`(dependencyNotation: Any): Dependency? = + add("playImplementation", dependencyNotation) diff --git a/android/lib/payment/src/main/kotlin/net/mullvad/mullvadvpn/lib/payment/PaymentProvider.kt b/android/lib/payment/src/main/kotlin/net/mullvad/mullvadvpn/lib/payment/PaymentProvider.kt new file mode 100644 index 0000000000..431b406dc0 --- /dev/null +++ b/android/lib/payment/src/main/kotlin/net/mullvad/mullvadvpn/lib/payment/PaymentProvider.kt @@ -0,0 +1,3 @@ +package net.mullvad.mullvadvpn.lib.payment + +@JvmInline value class PaymentProvider(val paymentRepository: PaymentRepository?) diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index a769d91bcb..f3b0b0d157 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -151,7 +151,6 @@ <string name="show_account_number">Show account number</string> <string name="failed_to_remove_device">Failed to remove device</string> <string name="changes_dialog_subtitle">Changes in this version:</string> - <string name="changes_dialog_dismiss_button">Got it!</string> <string name="always_on_vpn_error_notification_title">Always-on VPN assigned to other app</string> <string name="always_on_vpn_error_notification_content"> <![CDATA[Unable to start tunnel connection. Please disable Always-on VPN for <b>%s</b> before using Mullvad VPN.]]> @@ -229,4 +228,19 @@ <string name="less_than_one_day">less than one day</string> <string name="top_bar_time_left">Time left: %s</string> <string name="top_bar_device_name">Device name: %s</string> + <string name="add_30_days_time_x">Add 30 days time (%s)</string> + <string name="add_30_days_time">Add 30 days time</string> + <string name="payment_completed_dialog_title">Time was successfully added</string> + <string name="payment_completed_dialog_message">30 days was added to your account.</string> + <string name="got_it">Got it!</string> + <string name="payment_billing_error_dialog_title">Google Play unavailable</string> + <string name="payment_billing_error_dialog_message">We were unable to start the payment process, please make sure you have the latest version of Google Play.</string> + <string name="payment_obfuscation_id_error_dialog_title">Mullvad services unavailable</string> + <string name="payment_obfuscation_id_error_dialog_message">We were unable to start the payment process, please try again later.</string> + <string name="payment_status_pending">Google Play payment pending</string> + <string name="payment_status_verification_in_progress">Verifying purchase</string> + <string name="payment_pending_dialog_title">Verifying purchase</string> + <string name="payment_pending_dialog_message">We are currently verifying your purchase, this might take some time. Your time will be added if the verification is successful.</string> + <string name="loading_connecting">Connecting...</string> + <string name="loading_verifying">Verifying purchase...</string> </resources> |
