diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index dc5baece5b..a02c3a2a34 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -5,6 +5,7 @@ package org.thoughtcrime.securesms.backup.v2 +import androidx.annotation.WorkerThread import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -41,6 +42,7 @@ import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupReader import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupWriter import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeFeature +import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository import org.thoughtcrime.securesms.database.DistributionListTables import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord @@ -88,6 +90,7 @@ object BackupRepository { Log.i(TAG, "Resetting initialized state due to 401.") SignalStore.backup().backupsInitialized = false } + 403 -> { Log.i(TAG, "Bad auth credential. Clearing stored credentials.") SignalStore.backup().clearAllCredentials() @@ -95,6 +98,13 @@ object BackupRepository { } } + @WorkerThread + fun turnOffAndDeleteBackup() { + RecurringInAppPaymentRepository.cancelActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) + SignalStore.backup().areBackupsEnabled = false + SignalStore.backup().backupTier = null + } + fun export(outputStream: OutputStream, append: (ByteArray) -> Unit, plaintext: Boolean = false) { val eventTimer = EventTimer() val writer: BackupExportWriter = if (plaintext) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerFragment.kt index bb0706bcce..48997b5e0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerFragment.kt @@ -12,18 +12,12 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsText import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity -import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository import org.thoughtcrime.securesms.components.settings.configure -import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.util.BottomSheetUtil class BecomeASustainerFragment : DSLSettingsBottomSheetFragment() { - private val viewModel: BecomeASustainerViewModel by viewModels( - factoryProducer = { - BecomeASustainerViewModel.Factory(RecurringInAppPaymentRepository(AppDependencies.donationsService)) - } - ) + private val viewModel: BecomeASustainerViewModel by viewModels() override fun bindAdapter(adapter: DSLSettingsAdapter) { BadgePreview.register(adapter) diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerViewModel.kt index 6f86d203f4..be792104b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/none/BecomeASustainerViewModel.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.badges.self.none import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy @@ -10,7 +9,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository import org.thoughtcrime.securesms.util.livedata.Store -class BecomeASustainerViewModel(subscriptionsRepository: RecurringInAppPaymentRepository) : ViewModel() { +class BecomeASustainerViewModel : ViewModel() { private val store = Store(BecomeASustainerState()) @@ -19,7 +18,7 @@ class BecomeASustainerViewModel(subscriptionsRepository: RecurringInAppPaymentRe private val disposables = CompositeDisposable() init { - disposables += subscriptionsRepository.getSubscriptions().subscribeBy( + disposables += RecurringInAppPaymentRepository.getSubscriptions().subscribeBy( onError = { Log.w(TAG, "Could not load subscriptions.") }, onSuccess = { subscriptions -> store.update { @@ -36,10 +35,4 @@ class BecomeASustainerViewModel(subscriptionsRepository: RecurringInAppPaymentRe companion object { private val TAG = Log.tag(BecomeASustainerViewModel::class.java) } - - class Factory(private val subscriptionsRepository: RecurringInAppPaymentRepository) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return modelClass.cast(BecomeASustainerViewModel(subscriptionsRepository))!! - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewFragment.kt index 488b771075..dadc17f9ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewFragment.kt @@ -13,9 +13,7 @@ import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsText -import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository import org.thoughtcrime.securesms.components.settings.configure -import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.navigation.safeNavigate @@ -31,7 +29,7 @@ class BadgesOverviewFragment : DSLSettingsFragment( private val lifecycleDisposable = LifecycleDisposable() private val viewModel: BadgesOverviewViewModel by viewModels( factoryProducer = { - BadgesOverviewViewModel.Factory(BadgeRepository(requireContext()), RecurringInAppPaymentRepository(AppDependencies.donationsService)) + BadgesOverviewViewModel.Factory(BadgeRepository(requireContext())) } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt index 5485853ba1..74726985d0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt @@ -23,8 +23,7 @@ import java.util.Optional private val TAG = Log.tag(BadgesOverviewViewModel::class.java) class BadgesOverviewViewModel( - private val badgeRepository: BadgeRepository, - private val subscriptionsRepository: RecurringInAppPaymentRepository + private val badgeRepository: BadgeRepository ) : ViewModel() { private val store = Store(BadgesOverviewState()) private val eventSubject = PublishSubject.create() @@ -51,8 +50,8 @@ class BadgesOverviewViewModel( } disposables += Single.zip( - subscriptionsRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION), - subscriptionsRepository.getSubscriptions() + RecurringInAppPaymentRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION), + RecurringInAppPaymentRepository.getSubscriptions() ) { active, all -> if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) { Optional.ofNullable(all.firstOrNull { it.level == active.activeSubscription?.level }?.badge?.id) @@ -89,11 +88,10 @@ class BadgesOverviewViewModel( } class Factory( - private val badgeRepository: BadgeRepository, - private val subscriptionsRepository: RecurringInAppPaymentRepository + private val badgeRepository: BadgeRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return requireNotNull(modelClass.cast(BadgesOverviewViewModel(badgeRepository, subscriptionsRepository))) + return requireNotNull(modelClass.cast(BadgesOverviewViewModel(badgeRepository))) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt index c58c62201f..68cabf2867 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt @@ -15,9 +15,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.livedata.Store -class AppSettingsViewModel( - recurringInAppPaymentRepository: RecurringInAppPaymentRepository = RecurringInAppPaymentRepository(AppDependencies.donationsService) -) : ViewModel() { +class AppSettingsViewModel : ViewModel() { private val store = Store( AppSettingsState( @@ -40,7 +38,7 @@ class AppSettingsViewModel( store.update(unreadPaymentsLiveData) { payments, state -> state.copy(unreadPaymentsCount = payments.map { it.unreadCount }.orElse(0)) } store.update(selfLiveData) { self, state -> state.copy(self = self) } - disposables += recurringInAppPaymentRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION).subscribeBy( + disposables += RecurringInAppPaymentRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION).subscribeBy( onSuccess = { activeSubscription -> store.update { state -> state.copy(allowUserToGoToDonationManagementScreen = activeSubscription.isActive || InAppDonations.hasAtLeastOnePaymentMethodAvailable()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt index 6f77293b64..403275d785 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsFragment.kt @@ -12,12 +12,17 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialogDefaults +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState @@ -27,17 +32,19 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties import androidx.navigation.fragment.findNavController -import com.google.android.material.snackbar.Snackbar import kotlinx.collections.immutable.persistentListOf import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -83,7 +90,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() { @Composable override fun FragmentContent() { - val state by viewModel.state + val state by viewModel.state.collectAsState() val callbacks = remember { Callbacks() } RemoteBackupsSettingsContent( @@ -142,7 +149,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() { } override fun onTurnOffAndDeleteBackupsConfirm() { - // TODO [alex] CheckoutFlowStartFragment.launchForBackupsCancellation(childFragmentManager) + viewModel.turnOffAndDeleteBackups() } override fun onBackupsTypeClick() { @@ -164,12 +171,6 @@ class RemoteBackupsSettingsFragment : ComposeFragment() { super.onResume() viewModel.refresh() } - -// override fun onCheckoutFlowResult(result: CheckoutFlowStartFragment.Result) { -// if (result is CheckoutFlowStartFragment.Result.CancelationSuccess) { -// Snackbar.make(requireView(), R.string.SubscribeFragment__your_subscription_has_been_cancelled, Snackbar.LENGTH_LONG).show() -// } -// } } /** @@ -331,6 +332,13 @@ private fun RemoteBackupsSettingsContent( onDismiss = contentCallbacks::onDialogDismissed ) } + + RemoteBackupsSettingsState.Dialog.DELETING_BACKUP, RemoteBackupsSettingsState.Dialog.BACKUP_DELETED -> { + DeletingBackupDialog( + backupDeleted = requestedDialog == RemoteBackupsSettingsState.Dialog.BACKUP_DELETED, + onDismiss = contentCallbacks::onDialogDismissed + ) + } } LaunchedEffect(requestedSnackbar) { @@ -509,6 +517,60 @@ private fun TurnOffAndDeleteBackupsDialog( ) } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun DeletingBackupDialog( + backupDeleted: Boolean, + onDismiss: () -> Unit +) { + BasicAlertDialog( + onDismissRequest = onDismiss, + properties = DialogProperties( + dismissOnBackPress = false, + dismissOnClickOutside = false + ) + ) { + Surface( + shape = AlertDialogDefaults.shape, + color = AlertDialogDefaults.containerColor + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .defaultMinSize(minWidth = 232.dp) + .padding(bottom = 60.dp) + ) { + if (backupDeleted) { + Icon( + painter = painterResource(id = R.drawable.symbol_check_light_24), + contentDescription = null, + tint = Color(0xFF09B37B), + modifier = Modifier + .padding(top = 58.dp, bottom = 9.dp) + .size(48.dp) + ) + Text( + text = stringResource(id = R.string.RemoteBackupsSettingsFragment__backup_deleted), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } else { + CircularProgressIndicator( + modifier = Modifier + .padding(top = 64.dp, bottom = 20.dp) + .size(48.dp) + ) + Text( + text = stringResource(id = R.string.RemoteBackupsSettingsFragment__deleting_backup), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable private fun BackupFrequencyDialog( @@ -642,6 +704,17 @@ private fun TurnOffAndDeleteBackupsDialogPreview() { } } +@SignalPreview +@Composable +private fun DeleteBackupDialogPreview() { + Previews.Preview { + DeletingBackupDialog( + backupDeleted = true, + onDismiss = {} + ) + } +} + @SignalPreview @Composable private fun BackupFrequencyDialogPreview() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsState.kt index 6029a262bc..f037d8a08d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsState.kt @@ -22,7 +22,9 @@ data class RemoteBackupsSettingsState( enum class Dialog { NONE, TURN_OFF_AND_DELETE_BACKUPS, - BACKUP_FREQUENCY + BACKUP_FREQUENCY, + DELETING_BACKUP, + BACKUP_DELETED } enum class Snackbar { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsViewModel.kt index e6808a9151..1a9ff9e723 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/backups/RemoteBackupsSettingsViewModel.kt @@ -5,11 +5,16 @@ package org.thoughtcrime.securesms.components.settings.app.chats.backups -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.thoughtcrime.securesms.backup.v2.BackupFrequency import org.thoughtcrime.securesms.backup.v2.BackupRepository import org.thoughtcrime.securesms.backup.v2.BackupV2Event @@ -17,12 +22,13 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobs.BackupMessagesJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.service.MessageBackupListener +import kotlin.time.Duration.Companion.milliseconds /** * ViewModel for state management of RemoteBackupsSettingsFragment */ class RemoteBackupsSettingsViewModel : ViewModel() { - private val internalState = mutableStateOf( + private val internalState = MutableStateFlow( RemoteBackupsSettingsState( messageBackupsType = null, lastBackupTimestamp = SignalStore.backup().lastBackupTime, @@ -31,7 +37,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() { ) ) - val state: State = internalState + val state: StateFlow = internalState init { refresh() @@ -39,22 +45,22 @@ class RemoteBackupsSettingsViewModel : ViewModel() { fun setCanBackUpUsingCellular(canBackUpUsingCellular: Boolean) { SignalStore.backup().backupWithCellular = canBackUpUsingCellular - internalState.value = state.value.copy(canBackUpUsingCellular = canBackUpUsingCellular) + internalState.update { it.copy(canBackUpUsingCellular = canBackUpUsingCellular) } } fun setBackupsFrequency(backupsFrequency: BackupFrequency) { SignalStore.backup().backupFrequency = backupsFrequency - internalState.value = state.value.copy(backupsFrequency = backupsFrequency) + internalState.update { it.copy(backupsFrequency = backupsFrequency) } MessageBackupListener.setNextBackupTimeToIntervalFromNow() MessageBackupListener.schedule(AppDependencies.application) } fun requestDialog(dialog: RemoteBackupsSettingsState.Dialog) { - internalState.value = state.value.copy(dialog = dialog) + internalState.update { it.copy(dialog = dialog) } } fun requestSnackbar(snackbar: RemoteBackupsSettingsState.Snackbar) { - internalState.value = state.value.copy(snackbar = snackbar) + internalState.update { it.copy(snackbar = snackbar) } } fun refresh() { @@ -62,35 +68,49 @@ class RemoteBackupsSettingsViewModel : ViewModel() { val tier = SignalStore.backup().backupTier val backupType = if (tier != null) BackupRepository.getBackupsType(tier) else null - internalState.value = state.value.copy( - messageBackupsType = backupType, - lastBackupTimestamp = SignalStore.backup().lastBackupTime, - backupSize = SignalStore.backup().totalBackupSize, - backupsFrequency = SignalStore.backup().backupFrequency - ) + internalState.update { + it.copy( + messageBackupsType = backupType, + lastBackupTimestamp = SignalStore.backup().lastBackupTime, + backupSize = SignalStore.backup().totalBackupSize, + backupsFrequency = SignalStore.backup().backupFrequency + ) + } } } fun turnOffAndDeleteBackups() { - // TODO [message-backups] -- Delete. - SignalStore.backup().areBackupsEnabled = false - internalState.value = state.value.copy(snackbar = RemoteBackupsSettingsState.Snackbar.BACKUP_DELETED_AND_TURNED_OFF) + viewModelScope.launch { + requestDialog(RemoteBackupsSettingsState.Dialog.DELETING_BACKUP) + + withContext(Dispatchers.IO) { + BackupRepository.turnOffAndDeleteBackup() + } + + if (isActive) { + requestDialog(RemoteBackupsSettingsState.Dialog.BACKUP_DELETED) + delay(2000.milliseconds) + requestDialog(RemoteBackupsSettingsState.Dialog.NONE) + } + } } fun updateBackupProgress(backupEvent: BackupV2Event?) { - internalState.value = state.value.copy(backupProgress = backupEvent) + internalState.update { it.copy(backupProgress = backupEvent) } refreshBackupState() } private fun refreshBackupState() { - internalState.value = state.value.copy( - lastBackupTimestamp = SignalStore.backup().lastBackupTime, - backupSize = SignalStore.backup().totalBackupSize - ) + internalState.update { + it.copy( + lastBackupTimestamp = SignalStore.backup().lastBackupTime, + backupSize = SignalStore.backup().totalBackupSize + ) + } } fun onBackupNowClick() { - if (state.value.backupProgress == null || state.value.backupProgress?.type == BackupV2Event.Type.FINISHED) { + if (internalState.value.backupProgress == null || internalState.value.backupProgress?.type == BackupV2Event.Type.FINISHED) { BackupMessagesJob.enqueue() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt index 799d673196..44ae1c86dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt @@ -31,8 +31,6 @@ class PayPalRepository(private val donationsService: DonationsService) { private val TAG = Log.tag(PayPalRepository::class.java) } - private val recurringInAppPaymentRepository = RecurringInAppPaymentRepository(donationsService) - fun createOneTimePaymentIntent( amount: FiatMoney, badgeRecipient: RecipientId, @@ -88,7 +86,7 @@ class PayPalRepository(private val donationsService: DonationsService) { ) }.flatMap { serviceResponse -> if (retryOn409 && serviceResponse.status == 409) { - recurringInAppPaymentRepository.rotateSubscriberId(subscriberType).andThen(createPaymentMethod(subscriberType, retryOn409 = false)) + RecurringInAppPaymentRepository.rotateSubscriberId(subscriberType).andThen(createPaymentMethod(subscriberType, retryOn409 = false)) } else { serviceResponse.flattenResult() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt index fab5918079..1a48bb869e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/RecurringInAppPaymentRepository.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.components.settings.app.subscription +import androidx.annotation.CheckResult import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.subscription.LevelUpdate import org.thoughtcrime.securesms.subscription.LevelUpdateOperation import org.thoughtcrime.securesms.subscription.Subscription -import org.whispersystems.signalservice.api.services.DonationsService import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription import org.whispersystems.signalservice.api.subscriptions.IdempotencyKey import org.whispersystems.signalservice.api.subscriptions.SubscriberId @@ -39,7 +39,11 @@ import kotlin.time.Duration.Companion.milliseconds * Repository which can query for the user's active subscription as well as a list of available subscriptions, * in the currency indicated. */ -class RecurringInAppPaymentRepository(private val donationsService: DonationsService) { +object RecurringInAppPaymentRepository { + + private val TAG = Log.tag(RecurringInAppPaymentRepository::class.java) + + private val donationsService = AppDependencies.donationsService fun getActiveSubscription(type: InAppPaymentSubscriberRecord.Type): Single { val localSubscription = InAppPaymentsRepository.getSubscriber(type) @@ -129,29 +133,29 @@ class RecurringInAppPaymentRepository(private val donationsService: DonationsSer } } - fun cancelActiveSubscription(subscriberType: InAppPaymentSubscriberRecord.Type): Completable { + fun cancelActiveSubscriptionSync(subscriberType: InAppPaymentSubscriberRecord.Type) { Log.d(TAG, "Canceling active subscription...", true) - return Single - .fromCallable { - val localSubscriber = InAppPaymentsRepository.requireSubscriber(subscriberType) + val localSubscriber = InAppPaymentsRepository.requireSubscriber(subscriberType) - donationsService.cancelSubscription(localSubscriber.subscriberId) - } + val serviceResponse: ServiceResponse = donationsService.cancelSubscription(localSubscriber.subscriberId) + serviceResponse.resultOrThrow + + Log.d(TAG, "Cancelled active subscription.", true) + SignalStore.donationsValues().updateLocalStateForManualCancellation(subscriberType) + MultiDeviceSubscriptionSyncRequestJob.enqueue() + InAppPaymentsRepository.scheduleSyncForAccountRecordChange() + } + + @CheckResult + fun cancelActiveSubscription(subscriberType: InAppPaymentSubscriberRecord.Type): Completable { + return Completable + .fromAction { cancelActiveSubscriptionSync(subscriberType) } .subscribeOn(Schedulers.io()) - .flatMap(ServiceResponse::flattenResult) - .ignoreElement() - .doOnComplete { - Log.d(TAG, "Cancelled active subscription.", true) - SignalStore.donationsValues().updateLocalStateForManualCancellation(subscriberType) - MultiDeviceSubscriptionSyncRequestJob.enqueue() - InAppPaymentsRepository.scheduleSyncForAccountRecordChange() - } } fun cancelActiveSubscriptionIfNecessary(subscriberType: InAppPaymentSubscriberRecord.Type): Completable { return Single.fromCallable { InAppPaymentsRepository.getShouldCancelSubscriptionBeforeNextSubscribeAttempt(subscriberType) }.flatMapCompletable { if (it) { - Log.d(TAG, "Cancelling active subscription...", true) cancelActiveSubscription(subscriberType).doOnComplete { SignalStore.donationsValues().updateLocalStateForManualCancellation(subscriberType) MultiDeviceSubscriptionSyncRequestJob.enqueue() @@ -250,27 +254,23 @@ class RecurringInAppPaymentRepository(private val donationsService: DonationsSer getOrCreateLevelUpdateOperation(TAG, subscriptionLevel) } - companion object { - private val TAG = Log.tag(RecurringInAppPaymentRepository::class.java) + fun getOrCreateLevelUpdateOperation(tag: String, subscriptionLevel: String): LevelUpdateOperation { + Log.d(tag, "Retrieving level update operation for $subscriptionLevel") + val levelUpdateOperation = SignalStore.donationsValues().getLevelOperation(subscriptionLevel) + return if (levelUpdateOperation == null) { + val newOperation = LevelUpdateOperation( + idempotencyKey = IdempotencyKey.generate(), + level = subscriptionLevel + ) - fun getOrCreateLevelUpdateOperation(tag: String, subscriptionLevel: String): LevelUpdateOperation { - Log.d(tag, "Retrieving level update operation for $subscriptionLevel") - val levelUpdateOperation = SignalStore.donationsValues().getLevelOperation(subscriptionLevel) - return if (levelUpdateOperation == null) { - val newOperation = LevelUpdateOperation( - idempotencyKey = IdempotencyKey.generate(), - level = subscriptionLevel - ) - - SignalStore.donationsValues().setLevelOperation(newOperation) - LevelUpdate.updateProcessingState(true) - Log.d(tag, "Created a new operation for $subscriptionLevel") - newOperation - } else { - LevelUpdate.updateProcessingState(true) - Log.d(tag, "Reusing operation for $subscriptionLevel") - levelUpdateOperation - } + SignalStore.donationsValues().setLevelOperation(newOperation) + LevelUpdate.updateProcessingState(true) + Log.d(tag, "Created a new operation for $subscriptionLevel") + newOperation + } else { + LevelUpdate.updateProcessingState(true) + Log.d(tag, "Reusing operation for $subscriptionLevel") + levelUpdateOperation } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/StripeRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/StripeRepository.kt index 7e5e225eef..70fa147c5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/StripeRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/StripeRepository.kt @@ -51,7 +51,6 @@ class StripeRepository( private val googlePayApi = GooglePayApi(activity, StripeApi.Gateway(Environment.Donations.STRIPE_CONFIGURATION), Environment.Donations.GOOGLE_PAY_CONFIGURATION) private val stripeApi = StripeApi(Environment.Donations.STRIPE_CONFIGURATION, this, this, AppDependencies.okHttpClient) - private val recurringInAppPaymentRepository = RecurringInAppPaymentRepository(AppDependencies.donationsService) fun isGooglePayAvailable(): Completable { return googlePayApi.queryIsReadyToPay() @@ -169,7 +168,7 @@ class StripeRepository( } .flatMap { serviceResponse -> if (retryOn409 && serviceResponse.status == 409) { - recurringInAppPaymentRepository.rotateSubscriberId(subscriberType).andThen(createPaymentMethod(subscriberType, paymentSourceType, retryOn409 = false)) + RecurringInAppPaymentRepository.rotateSubscriberId(subscriberType).andThen(createPaymentMethod(subscriberType, paymentSourceType, retryOn409 = false)) } else { serviceResponse.flattenResult() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt index 2a8246b304..436e07988b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt @@ -52,7 +52,6 @@ import java.util.Optional */ class DonateToSignalViewModel( startType: InAppPaymentType, - private val subscriptionsRepository: RecurringInAppPaymentRepository, private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository ) : ViewModel() { @@ -75,7 +74,7 @@ class DonateToSignalViewModel( init { initializeOneTimeDonationState(oneTimeInAppPaymentRepository) - initializeMonthlyDonationState(subscriptionsRepository) + initializeMonthlyDonationState(RecurringInAppPaymentRepository) networkDisposable += InternetConnectionObserver .observe() @@ -91,7 +90,7 @@ class DonateToSignalViewModel( fun retryMonthlyDonationState() { if (!monthlyDonationDisposables.isDisposed && store.state.monthlyDonationState.donationStage == DonateToSignalState.DonationStage.FAILURE) { store.update { it.copy(monthlyDonationState = it.monthlyDonationState.copy(donationStage = DonateToSignalState.DonationStage.INIT)) } - initializeMonthlyDonationState(subscriptionsRepository) + initializeMonthlyDonationState(RecurringInAppPaymentRepository) } } @@ -181,7 +180,7 @@ class DonateToSignalViewModel( } fun refreshActiveSubscription() { - subscriptionsRepository + RecurringInAppPaymentRepository .getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION) .subscribeBy( onSuccess = { @@ -395,7 +394,7 @@ class DonateToSignalViewModel( val usd = PlatformCurrencyUtil.USD val newSubscriber = InAppPaymentsRepository.getSubscriber(usd, InAppPaymentSubscriberRecord.Type.DONATION) ?: InAppPaymentSubscriberRecord(SubscriberId.generate(), usd, InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN) InAppPaymentsRepository.setSubscriber(newSubscriber) - subscriptionsRepository.syncAccountRecord().subscribe() + RecurringInAppPaymentRepository.syncAccountRecord().subscribe() } } }, @@ -422,11 +421,10 @@ class DonateToSignalViewModel( class Factory( private val startType: InAppPaymentType, - private val subscriptionsRepository: RecurringInAppPaymentRepository = RecurringInAppPaymentRepository(AppDependencies.donationsService), private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService) ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(DonateToSignalViewModel(startType, subscriptionsRepository, oneTimeInAppPaymentRepository)) as T + return modelClass.cast(DonateToSignalViewModel(startType, oneTimeInAppPaymentRepository)) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressViewModel.kt index 36bc31056d..01ca77b9ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressViewModel.kt @@ -36,7 +36,6 @@ import org.whispersystems.signalservice.api.util.Preconditions class PayPalPaymentInProgressViewModel( private val payPalRepository: PayPalRepository, - private val recurringInAppPaymentRepository: RecurringInAppPaymentRepository, private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository ) : ViewModel() { @@ -86,7 +85,7 @@ class PayPalPaymentInProgressViewModel( Log.d(TAG, "Beginning subscription update...", true) store.update { DonationProcessorStage.PAYMENT_PIPELINE } - disposables += recurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType()).andThen(recurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, PaymentSourceType.PayPal)) + disposables += RecurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType()).andThen(RecurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, PaymentSourceType.PayPal)) .subscribeBy( onComplete = { Log.w(TAG, "Completed subscription update", true) @@ -104,12 +103,12 @@ class PayPalPaymentInProgressViewModel( Log.d(TAG, "Beginning cancellation...", true) store.update { DonationProcessorStage.CANCELLING } - disposables += recurringInAppPaymentRepository.cancelActiveSubscription(subscriberType).subscribeBy( + disposables += RecurringInAppPaymentRepository.cancelActiveSubscription(subscriberType).subscribeBy( onComplete = { Log.d(TAG, "Cancellation succeeded", true) SignalStore.donationsValues().updateLocalStateForManualCancellation(subscriberType) MultiDeviceSubscriptionSyncRequestJob.enqueue() - recurringInAppPaymentRepository.syncAccountRecord().subscribe() + RecurringInAppPaymentRepository.syncAccountRecord().subscribe() store.update { DonationProcessorStage.COMPLETE } }, onError = { throwable -> @@ -172,14 +171,14 @@ class PayPalPaymentInProgressViewModel( private fun proceedMonthly(inAppPayment: InAppPaymentTable.InAppPayment, routeToPaypalConfirmation: (PayPalCreatePaymentMethodResponse) -> Single) { Log.d(TAG, "Proceeding with monthly payment pipeline for InAppPayment::${inAppPayment.id} of type ${inAppPayment.type}...", true) - val setup = recurringInAppPaymentRepository.ensureSubscriberId(inAppPayment.type.requireSubscriberType()) - .andThen(recurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType())) + val setup = RecurringInAppPaymentRepository.ensureSubscriberId(inAppPayment.type.requireSubscriberType()) + .andThen(RecurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType())) .andThen(payPalRepository.createPaymentMethod(inAppPayment.type.requireSubscriberType())) .flatMap(routeToPaypalConfirmation) .flatMapCompletable { payPalRepository.setDefaultPaymentMethod(inAppPayment.type.requireSubscriberType(), it.paymentId) } .onErrorResumeNext { Completable.error(DonationError.getPaymentSetupError(DonationErrorSource.MONTHLY, it, PaymentSourceType.PayPal)) } - disposables += setup.andThen(recurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, PaymentSourceType.PayPal)) + disposables += setup.andThen(RecurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, PaymentSourceType.PayPal)) .subscribeBy( onError = { throwable -> Log.w(TAG, "Failure in monthly payment pipeline...", throwable, true) @@ -195,11 +194,10 @@ class PayPalPaymentInProgressViewModel( class Factory( private val payPalRepository: PayPalRepository = PayPalRepository(AppDependencies.donationsService), - private val recurringInAppPaymentRepository: RecurringInAppPaymentRepository = RecurringInAppPaymentRepository(AppDependencies.donationsService), private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService) ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(PayPalPaymentInProgressViewModel(payPalRepository, recurringInAppPaymentRepository, oneTimeInAppPaymentRepository)) as T + return modelClass.cast(PayPalPaymentInProgressViewModel(payPalRepository, oneTimeInAppPaymentRepository)) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/stripe/StripePaymentInProgressViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/stripe/StripePaymentInProgressViewModel.kt index bdc80099ee..652bd830e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/stripe/StripePaymentInProgressViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/stripe/StripePaymentInProgressViewModel.kt @@ -41,7 +41,6 @@ import org.whispersystems.signalservice.internal.push.exceptions.DonationProcess class StripePaymentInProgressViewModel( private val stripeRepository: StripeRepository, - private val recurringInAppPaymentRepository: RecurringInAppPaymentRepository, private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository ) : ViewModel() { @@ -144,18 +143,18 @@ class StripePaymentInProgressViewModel( } private fun proceedMonthly(inAppPayment: InAppPaymentTable.InAppPayment, paymentSourceProvider: PaymentSourceProvider, nextActionHandler: StripeNextActionHandler) { - val ensureSubscriberId: Completable = recurringInAppPaymentRepository.ensureSubscriberId(inAppPayment.type.requireSubscriberType()) + val ensureSubscriberId: Completable = RecurringInAppPaymentRepository.ensureSubscriberId(inAppPayment.type.requireSubscriberType()) val createAndConfirmSetupIntent: Single = paymentSourceProvider.paymentSource.flatMap { stripeRepository.createAndConfirmSetupIntent(inAppPayment.type, it, paymentSourceProvider.paymentSourceType as PaymentSourceType.Stripe) } - val setLevel: Completable = recurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, paymentSourceProvider.paymentSourceType) + val setLevel: Completable = RecurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, paymentSourceProvider.paymentSourceType) Log.d(TAG, "Starting subscription payment pipeline...", true) store.update { DonationProcessorStage.PAYMENT_PIPELINE } val setup: Completable = ensureSubscriberId - .andThen(recurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType())) + .andThen(RecurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType())) .andThen(createAndConfirmSetupIntent) .flatMap { secure3DSAction -> nextActionHandler.handle( @@ -255,7 +254,7 @@ class StripePaymentInProgressViewModel( Log.d(TAG, "Beginning cancellation...", true) store.update { DonationProcessorStage.CANCELLING } - disposables += recurringInAppPaymentRepository.cancelActiveSubscription(subscriberType).subscribeBy( + disposables += RecurringInAppPaymentRepository.cancelActiveSubscription(subscriberType).subscribeBy( onComplete = { Log.d(TAG, "Cancellation succeeded", true) store.update { DonationProcessorStage.COMPLETE } @@ -270,10 +269,10 @@ class StripePaymentInProgressViewModel( fun updateSubscription(inAppPayment: InAppPaymentTable.InAppPayment) { Log.d(TAG, "Beginning subscription update...", true) store.update { DonationProcessorStage.PAYMENT_PIPELINE } - disposables += recurringInAppPaymentRepository + disposables += RecurringInAppPaymentRepository .cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType()) - .andThen(recurringInAppPaymentRepository.getPaymentSourceTypeOfLatestSubscription(inAppPayment.type.requireSubscriberType())) - .flatMapCompletable { paymentSourceType -> recurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, paymentSourceType) } + .andThen(RecurringInAppPaymentRepository.getPaymentSourceTypeOfLatestSubscription(inAppPayment.type.requireSubscriberType())) + .flatMapCompletable { paymentSourceType -> RecurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, paymentSourceType) } .subscribeBy( onComplete = { Log.w(TAG, "Completed subscription update", true) @@ -304,11 +303,10 @@ class StripePaymentInProgressViewModel( class Factory( private val stripeRepository: StripeRepository, - private val recurringInAppPaymentRepository: RecurringInAppPaymentRepository = RecurringInAppPaymentRepository(AppDependencies.donationsService), private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService) ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(StripePaymentInProgressViewModel(stripeRepository, recurringInAppPaymentRepository, oneTimeInAppPaymentRepository)) as T + return modelClass.cast(StripePaymentInProgressViewModel(stripeRepository, oneTimeInAppPaymentRepository)) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt index 92036065fd..4cbb25084e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsFragment.kt @@ -20,14 +20,12 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon import org.thoughtcrime.securesms.components.settings.DSLSettingsText import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney -import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository import org.thoughtcrime.securesms.components.settings.app.subscription.completed.TerminalDonationDelegate import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle import org.thoughtcrime.securesms.database.model.databaseprotos.DonationErrorValue import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation -import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.help.HelpFragment import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.payments.FiatMoneyUtil @@ -66,11 +64,7 @@ class ManageDonationsFragment : ) } - private val viewModel: ManageDonationsViewModel by viewModels( - factoryProducer = { - ManageDonationsViewModel.Factory(RecurringInAppPaymentRepository(AppDependencies.donationsService)) - } - ) + private val viewModel: ManageDonationsViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycle.addObserver(TerminalDonationDelegate(childFragmentManager, viewLifecycleOwner)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt index fe2e3e6f10..2074772d86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.manage import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -25,9 +24,7 @@ import org.thoughtcrime.securesms.util.livedata.Store import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription import java.util.Optional -class ManageDonationsViewModel( - private val subscriptionsRepository: RecurringInAppPaymentRepository -) : ViewModel() { +class ManageDonationsViewModel : ViewModel() { private val store = Store(ManageDonationsState()) private val disposables = CompositeDisposable() @@ -65,7 +62,7 @@ class ManageDonationsViewModel( disposables.clear() val levelUpdateOperationEdges: Observable = LevelUpdate.isProcessing.distinctUntilChanged() - val activeSubscription: Single = subscriptionsRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION) + val activeSubscription: Single = RecurringInAppPaymentRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION) disposables += Single.fromCallable { InAppPaymentsRepository.getShouldCancelSubscriptionBeforeNextSubscribeAttempt(InAppPaymentSubscriberRecord.Type.DONATION) @@ -134,7 +131,7 @@ class ManageDonationsViewModel( } ) - disposables += subscriptionsRepository.getSubscriptions().subscribeBy( + disposables += RecurringInAppPaymentRepository.getSubscriptions().subscribeBy( onSuccess = { subs -> store.update { it.copy(availableSubscriptions = subs) } }, @@ -155,14 +152,6 @@ class ManageDonationsViewModel( } } - class Factory( - private val subscriptionsRepository: RecurringInAppPaymentRepository - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return modelClass.cast(ManageDonationsViewModel(subscriptionsRepository))!! - } - } - companion object { private val TAG = Log.tag(ManageDonationsViewModel::class.java) } diff --git a/app/src/main/res/drawable/symbol_check_light_24.xml b/app/src/main/res/drawable/symbol_check_light_24.xml new file mode 100644 index 0000000000..b025d8f539 --- /dev/null +++ b/app/src/main/res/drawable/symbol_check_light_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 35ac0f2540..b502f8f7d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7031,5 +7031,11 @@ OK + + + Deleting backup… + + Backup deleted + diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java index 8ea770b4ee..11ab1ee49f 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/ServiceResponse.java @@ -84,6 +84,18 @@ public final class ServiceResponse { } } + public Result getResultOrThrow() throws Throwable { + if (result.isPresent()) { + return result.get(); + } else if (applicationError.isPresent()) { + throw applicationError.get(); + } else if (executionError.isPresent()) { + throw executionError.get(); + } else { + throw new AssertionError("Should never get here"); + } + } + public static ServiceResponse forResult(T result, WebsocketResponse response) { return new ServiceResponse<>(result, response); }