From 246bb44e21db48b7deb7cfba84d74bb5e6a8c514 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 17 Nov 2025 16:52:24 +1100 Subject: [PATCH 1/7] Version bump and new debug button to copy pro master key --- app/build.gradle.kts | 2 +- .../securesms/debugmenu/DebugMenu.kt | 8 ++++++++ .../securesms/debugmenu/DebugMenuViewModel.kt | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7ef76a3438..e0b86be788 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,7 +26,7 @@ configurations.configureEach { exclude(module = "commons-logging") } -val canonicalVersionCode = 430 +val canonicalVersionCode = 432 val canonicalVersionName = "1.30.0" val postFixSize = 10 diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt index 3249da0ef1..1212c0c5b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt @@ -504,6 +504,14 @@ fun DebugMenu( sendCommand(Copy07PrefixedBlindedPublicKey) } ) + + SlimFillButtonRect ( + text = "Copy Pro Master Key", + modifier = Modifier.fillMaxWidth(), + onClick = { + sendCommand(DebugMenuViewModel.Commands.CopyProMasterKey) + } + ) } Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt index b1acc94ba5..e2fcd316fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt @@ -202,6 +202,21 @@ class DebugMenuViewModel @AssistedInject constructor( } } + is Commands.CopyProMasterKey -> { + val proKey = loginStateRepository.loggedInState.value?.seeded?.proMasterPrivateKey?.toHexString() + val clip = ClipData.newPlainText("Pro Master Key", proKey) + clipboardManager.setPrimaryClip(ClipData(clip)) + + // Show a toast if the version is below Android 13 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + Toast.makeText( + context, + "Copied Pro Master Key to clipboard", + Toast.LENGTH_SHORT + ).show() + } + } + is Commands.HideMessageRequest -> { textSecurePreferences.setHasHiddenMessageRequests(command.hide) _uiState.value = _uiState.value.copy(hideMessageRequests = command.hide) @@ -590,6 +605,7 @@ class DebugMenuViewModel @AssistedInject constructor( object ScheduleTokenNotification : Commands() object Copy07PrefixedBlindedPublicKey : Commands() object CopyAccountId : Commands() + object CopyProMasterKey : Commands() data class HideMessageRequest(val hide: Boolean) : Commands() data class HideNoteToSelf(val hide: Boolean) : Commands() data class ForceCurrentUserAsPro(val set: Boolean) : Commands() From 48889d8f4775cce77c8c043fcc1955a948777e65 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 17 Nov 2025 16:52:49 +1100 Subject: [PATCH 2/7] Wrong behaviour for retrying server error - need to change logic --- .../securesms/preferences/prosettings/ProSettingsViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index a033fd2a18..7816f2e159 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -139,7 +139,8 @@ class ProSettingsViewModel @AssistedInject constructor( positiveStyleDanger = false, showXIcon = true, onPositive = { - getPlanFromProvider() // retry getting the plan from provider + //todo PRO I shouldn't get the plan from the provider again. I should really only try to redo the backend call + //getPlanFromProvider() // retry getting the plan from provider }, onNegative = { onCommand(ShowOpenUrlDialog(ProStatusManager.URL_PRO_SUPPORT)) From 45f44e50e85443036f3296c38bc4610ae1144b42 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 17 Nov 2025 16:53:20 +1100 Subject: [PATCH 3/7] Hooking up our payment with the server api --- .../securesms/pro/ProStatusManager.kt | 95 +++++++++++++------ .../securesms/pro/api/AddProPayment.kt | 13 +-- .../securesms/pro/api/ApiRequest.kt | 18 +--- .../securesms/pro/api/GenerateProProof.kt | 2 +- .../securesms/pro/api/GetProDetails.kt | 4 +- .../securesms/pro/api/GetProRevocations.kt | 2 +- .../securesms/pro/api/ProApiExecutor.kt | 2 +- .../pro/subscription/SubscriptionManager.kt | 4 +- .../PlayStoreSubscriptionManager.kt | 5 +- 9 files changed, 85 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index cc17f676f0..20273a4b79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -4,6 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -18,14 +19,22 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout import org.session.libsession.messaging.messages.visible.VisibleMessage +import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.auth.LoginStateRepository import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.debugmenu.DebugLogGroup import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel import org.thoughtcrime.securesms.dependencies.ManagerScope import org.thoughtcrime.securesms.dependencies.OnAppStartupComponent +import org.thoughtcrime.securesms.pro.api.AddPaymentErrorStatus +import org.thoughtcrime.securesms.pro.api.AddProPaymentRequest +import org.thoughtcrime.securesms.pro.api.ProApiExecutor +import org.thoughtcrime.securesms.pro.api.ProApiResponse +import org.thoughtcrime.securesms.pro.db.ProDatabase import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.pro.subscription.SubscriptionManager import org.thoughtcrime.securesms.util.State @@ -40,7 +49,10 @@ class ProStatusManager @Inject constructor( private val prefs: TextSecurePreferences, recipientRepository: RecipientRepository, @param:ManagerScope private val scope: CoroutineScope, - loginStateRepository: LoginStateRepository, + private val apiExecutor: ProApiExecutor, + private val loginState: LoginStateRepository, + private val proDatabase: ProDatabase, + private val snodeClock: SnodeClock, ) : OnAppStartupComponent { val subscriptionState: StateFlow = combine( @@ -267,43 +279,64 @@ class ProStatusManager @Inject constructor( return emptySet() } - suspend fun appProPaymentToBackend() { + @OptIn(ExperimentalCoroutinesApi::class) + suspend fun appProPaymentToBackend(orderId: String, paymentId: String) { // max 3 attempts as per PRD val maxAttempts = 3 + // no point in going further if we have no key data + val keyData = loginState.loggedInState.value ?: throw Exception() + for (attempt in 1..maxAttempts) { try { - // 5s timeout as per PRD - withTimeout(5_000L) { - //todo PRO call AddProPaymentRequest in libsession - /** - * Here are the errors from the back end that we will need to be aware of - * UnknownPayment: retryable > increment counter and try again - * Error, ParseError: is non retryable - throw PaymentServerException - * Success, AlreadyRedeemed - all good - * - * - * /// Payment was claimed and the pro proof was successfully generated - * Success = SESSION_PRO_BACKEND_ADD_PRO_PAYMENT_RESPONSE_STATUS_SUCCESS, - * - * /// Backend encountered an error when attempting to claim the payment - * Error = SESSION_PRO_BACKEND_ADD_PRO_PAYMENT_RESPONSE_STATUS_ERROR, - * - * /// Request JSON failed to be parsed correctly, payload was malformed or missing values - * ParseError = SESSION_PRO_BACKEND_ADD_PRO_PAYMENT_RESPONSE_STATUS_PARSE_ERROR, - * - * /// Payment is already claimed - * AlreadyRedeemed = SESSION_PRO_BACKEND_ADD_PRO_PAYMENT_RESPONSE_STATUS_ALREADY_REDEEMED, - * - * /// Payment transaction attempted to claim a payment that the backend does not have. Either the - * /// payment doesn't exist or the backend has not witnessed the payment from the provider yet. - * UnknownPayment = SESSION_PRO_BACKEND_ADD_PRO_PAYMENT_RESPONSE_STATUS_UNKNOWN_PAYMENT, - */ - - } + // 5s timeout as per PRD + val paymentResponse = withTimeout(5_000L) { + apiExecutor.executeRequest( + request = AddProPaymentRequest( + googlePaymentToken = paymentId, + googleOrderId = orderId, + masterPrivateKey = keyData.seeded.proMasterPrivateKey, + rotatingPrivateKey = proDatabase.ensureValidRotatingKeys(snodeClock.currentTime()).ed25519PrivKey + ) + ) + } + + when (paymentResponse) { + is ProApiResponse.Success -> { + Log.d(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' successful") + // Payment was successfully claimed - save it to the database + proDatabase.updateCurrentProProof(paymentResponse.data) + } + + is ProApiResponse.Failure -> { + // Handle payment failure + Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' failure: $paymentResponse") + when (paymentResponse.status) { + // unknown payment is retryable - throw a generic exception here to go through our retries + AddPaymentErrorStatus.UnknownPayment -> { + throw Exception() + } + + // nothing to do if already redeemed + AddPaymentErrorStatus.AlreadyRedeemed -> { + return + } + + // non retryable error - throw our custom exception + AddPaymentErrorStatus.GenericError -> { + throw SubscriptionManager.PaymentServerException() + } + } + } + } } catch (e: CancellationException) { throw e - } catch (_: Exception) { + } catch (e: SubscriptionManager.PaymentServerException){ + // rethrow this error directly without retrying + Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' PaymentServerException caught and rethrown") + throw e + }catch (e: Exception) { + Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' exception", e) // If not the last attempt, backoff a little and retry if (attempt < maxAttempts) { // small incremental backoff before retry diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt index f50cf3440b..dde76a0d73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt @@ -3,13 +3,14 @@ package org.thoughtcrime.securesms.pro.api import kotlinx.serialization.DeserializationStrategy import network.loki.messenger.libsession_util.pro.BackendRequests import network.loki.messenger.libsession_util.pro.ProProof +import org.session.libsignal.utilities.Log class AddProPaymentRequest( private val googlePaymentToken: String, private val googleOrderId: String, private val masterPrivateKey: ByteArray, private val rotatingPrivateKey: ByteArray, -) : ApiRequest { +) : ApiRequest { override val endpoint: String get() = "add_pro_payment" @@ -24,9 +25,10 @@ class AddProPaymentRequest( ) } - override fun convertStatus(status: Int): AddPaymentStatus { - return AddPaymentStatus.entries.firstOrNull { it.apiValue == status } - ?: AddPaymentStatus.GenericError + override fun convertErrorStatus(status: Int): AddPaymentErrorStatus { + Log.d("", "*** convertErrorStatus: $status") + return AddPaymentErrorStatus.entries.firstOrNull { it.apiValue == status } + ?: AddPaymentErrorStatus.GenericError } override val responseDeserializer: DeserializationStrategy @@ -34,8 +36,7 @@ class AddProPaymentRequest( } -enum class AddPaymentStatus(val apiValue: Int) { - Success(0), +enum class AddPaymentErrorStatus(val apiValue: Int) { GenericError(1), AlreadyRedeemed(2), UnknownPayment(3), diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/ApiRequest.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/ApiRequest.kt index 28bd00428c..a6ffb030b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/ApiRequest.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/ApiRequest.kt @@ -1,26 +1,14 @@ package org.thoughtcrime.securesms.pro.api import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.decodeFromStream -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.snode.utilities.await /** * Represents a generic API request to the Pro backend. * - * @param Status The type of the status returned by the API. + * @param ErrorStatus The type of error status returned by the API. * @param Res The type of the expected response. */ -interface ApiRequest { +interface ApiRequest { /** * The endpoint (path) for this API request, e.g. "v1/pro/payments" */ @@ -28,7 +16,7 @@ interface ApiRequest { val responseDeserializer: DeserializationStrategy - fun convertStatus(status: Int): Status + fun convertErrorStatus(status: Int): ErrorStatus fun buildJsonBody(): String } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt index 4b0eadb32a..399353c082 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GenerateProProof.kt @@ -29,7 +29,7 @@ class GenerateProProofRequest @AssistedInject constructor( override val responseDeserializer: DeserializationStrategy get() = ProProof.serializer() - override fun convertStatus(status: Int): GetProProofStatus = status + override fun convertErrorStatus(status: Int): GetProProofStatus = status @AssistedFactory interface Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt index 10f7f5bb02..a5b77a2b31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt @@ -31,7 +31,7 @@ class GetProDetailsRequest @AssistedInject constructor( override val responseDeserializer: DeserializationStrategy get() = ProDetails.serializer() - override fun convertStatus(status: Int): Int = status + override fun convertErrorStatus(status: Int): Int = status @AssistedFactory interface Factory { @@ -79,7 +79,7 @@ class ProDetails( val expiry: Instant? = null, @SerialName("grace_duration_ms") - val graceDurationMs: Long, + val graceDurationMs: Long? = null, @SerialName("platform_refund_expiry_unix_ts_ms") @Serializable(with = InstantAsMillisSerializer::class) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProRevocations.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProRevocations.kt index 2616e51258..a800df5fd5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProRevocations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProRevocations.kt @@ -17,7 +17,7 @@ class GetProRevocationRequest @AssistedInject constructor( override val responseDeserializer: DeserializationStrategy get() = ProRevocations.serializer() - override fun convertStatus(status: Int): Int = status + override fun convertErrorStatus(status: Int): Int = status override val endpoint: String get() = "get_pro_revocations" diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/ProApiExecutor.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/ProApiExecutor.kt index 3056715138..c0560ac600 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/ProApiExecutor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/ProApiExecutor.kt @@ -80,7 +80,7 @@ class ProApiExecutor @Inject constructor( ProApiResponse.Success(data) } else { ProApiResponse.Failure( - status = request.convertStatus(rawResp.status), + status = request.convertErrorStatus(rawResp.status), errors = rawResp.errors.orEmpty() ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt index f365e1ca3d..fea97d821f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt @@ -70,11 +70,11 @@ abstract class SubscriptionManager( /** * Function called when a purchased has been made successfully from the subscription api */ - protected fun onPurchaseSuccessful(){ + protected fun onPurchaseSuccessful(orderId: String, paymentId: String){ // we need to tie our purchase with the back end scope.launch { try { - proStatusManager.appProPaymentToBackend() + proStatusManager.appProPaymentToBackend(orderId, paymentId) _purchaseEvents.emit(PurchaseEvent.Success) } catch (e: Exception) { when (e) { diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt index c2a2a7eb27..00169f5106 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt @@ -80,7 +80,10 @@ class PlayStoreSubscriptionManager @Inject constructor( Log.d(DebugLogGroup.PRO_SUBSCRIPTION.label, "Billing callback. We have a purchase [${it.orderId}]. Acknowledged? ${it.isAcknowledged}") - onPurchaseSuccessful() + onPurchaseSuccessful( + orderId = it.orderId ?: "", + paymentId = it.purchaseToken + ) } } else { Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Purchase failed or cancelled: $result") From 37cd39c4a539da638ff1ab66dcd3bebc4c972f20 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Tue, 18 Nov 2025 10:32:13 +1100 Subject: [PATCH 4/7] More logs and proper status mapping --- .../java/org/thoughtcrime/securesms/pro/ProStatusManager.kt | 1 + .../org/thoughtcrime/securesms/pro/api/AddProPayment.kt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index 20273a4b79..6974bb9892 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -347,6 +347,7 @@ class ProStatusManager @Inject constructor( } // All attempts failed - throw our custom exception + Log.w(DebugLogGroup.PRO_SUBSCRIPTION.label, "Backend 'add pro payment' - Al retries attempted, throwing our custom `PaymentServerException`") throw SubscriptionManager.PaymentServerException() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt index dde76a0d73..9918ee7f35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/AddProPayment.kt @@ -26,7 +26,7 @@ class AddProPaymentRequest( } override fun convertErrorStatus(status: Int): AddPaymentErrorStatus { - Log.d("", "*** convertErrorStatus: $status") + Log.w("", "AddProPayment: convertErrorStatus: $status") return AddPaymentErrorStatus.entries.firstOrNull { it.apiValue == status } ?: AddPaymentErrorStatus.GenericError } @@ -38,6 +38,6 @@ class AddProPaymentRequest( enum class AddPaymentErrorStatus(val apiValue: Int) { GenericError(1), - AlreadyRedeemed(2), - UnknownPayment(3), + AlreadyRedeemed(100), + UnknownPayment(101), } From a2d2fdc138b2fc25fac6a750003208ee09a49e3e Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Tue, 18 Nov 2025 13:52:08 +1100 Subject: [PATCH 5/7] Mapping server data to presentation models --- .../securesms/InputbarViewModel.kt | 2 +- .../v2/MessageDetailsViewModel.kt | 2 +- .../settings/ConversationSettingsViewModel.kt | 6 +- .../securesms/debugmenu/DebugLogger.kt | 5 +- .../securesms/home/HomeViewModel.kt | 4 +- .../securesms/preferences/SettingsScreen.kt | 18 +- .../preferences/SettingsViewModel.kt | 10 +- .../prosettings/PlanConfirmationScreen.kt | 12 +- .../prosettings/ProSettingsHomeScreen.kt | 38 ++-- .../prosettings/ProSettingsViewModel.kt | 36 +-- .../securesms/pro/ProDataMapper.kt | 63 ++++++ .../thoughtcrime/securesms/pro/ProStatus.kt | 4 +- .../securesms/pro/ProStatusManager.kt | 211 +++++++++--------- .../securesms/pro/api/GetProDetails.kt | 24 +- .../securesms/util/UserProfileUtils.kt | 2 +- 15 files changed, 259 insertions(+), 178 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt index ebd701fd57..f1eb2e8304 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/InputbarViewModel.kt @@ -93,7 +93,7 @@ abstract class InputbarViewModel( fun showSessionProCTA(){ _inputBarStateDialogsState.update { - it.copy(sessionProCharLimitCTA = CharLimitCTAData(proStatusManager.subscriptionState.value.type)) + it.copy(sessionProCharLimitCTA = CharLimitCTAData(proStatusManager.proDataState.value.type)) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 35705c5c39..e70e6058c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -280,7 +280,7 @@ class MessageDetailsViewModel @AssistedInject constructor( is Commands.ShowProBadgeCTA -> { val features = state.value.proFeatures _dialogState.update { - val proSubscription = proStatusManager.subscriptionState.value.type + val proSubscription = proStatusManager.proDataState.value.type it.copy( proBadgeCTA = when{ features.size > 1 -> ProBadgeCTA.Generic(proSubscription) // always show the generic cta when there are more than 1 feature diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 90d10b1e53..756862d7cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -719,7 +719,7 @@ class ConversationSettingsViewModel @AssistedInject constructor( _dialogState.update { it.copy(pinCTA = PinProCTA( overTheLimit = totalPins > maxPins, - proSubscription = proStatusManager.subscriptionState.value.type + proSubscription = proStatusManager.proDataState.value.type )) } } else { @@ -1237,8 +1237,8 @@ class ConversationSettingsViewModel @AssistedInject constructor( is Commands.ShowProBadgeCTA -> { _dialogState.update { it.copy( - proBadgeCTA = if(recipient?.isGroupV2Recipient == true) ProBadgeCTA.Group(proStatusManager.subscriptionState.value.type) - else ProBadgeCTA.Generic(proStatusManager.subscriptionState.value.type) + proBadgeCTA = if(recipient?.isGroupV2Recipient == true) ProBadgeCTA.Group(proStatusManager.proDataState.value.type) + else ProBadgeCTA.Generic(proStatusManager.proDataState.value.type) ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugLogger.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugLogger.kt index e1a0421c1a..8197d585fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugLogger.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugLogger.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.launch import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.dependencies.ManagerScope +import org.thoughtcrime.securesms.ui.theme.primaryBlue import org.thoughtcrime.securesms.ui.theme.primaryGreen import org.thoughtcrime.securesms.ui.theme.primaryOrange import org.thoughtcrime.securesms.util.DateUtils @@ -127,5 +128,7 @@ data class DebugLogData( ) enum class DebugLogGroup(val label: String, val color: Color){ - AVATAR("Avatar", primaryOrange), PRO_SUBSCRIPTION("Pro Subscription", primaryGreen) + AVATAR("Avatar", primaryOrange), + PRO_SUBSCRIPTION("Pro Subscription", primaryGreen), + PRO_DATA("Pro Data", primaryBlue) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index 8c64aacf7c..416a0fb402 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -159,7 +159,7 @@ class HomeViewModel @Inject constructor( init { // observe subscription status viewModelScope.launch { - proStatusManager.subscriptionState.collect { subscription -> + proStatusManager.proDataState.collect { subscription -> // show a CTA (only once per install) when // - subscription is expiring in less than 7 days // - subscription expired less than 30 days ago @@ -262,7 +262,7 @@ class HomeViewModel @Inject constructor( it.copy( pinCTA = PinProCTA( overTheLimit = totalPins > maxPins, - proSubscription = proStatusManager.subscriptionState.value.type + proSubscription = proStatusManager.proDataState.value.type ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt index b7d6eeec87..638df937e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt @@ -79,7 +79,7 @@ import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.* import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsActivity import org.thoughtcrime.securesms.pro.SubscriptionDetails -import org.thoughtcrime.securesms.pro.SubscriptionState +import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity @@ -266,11 +266,11 @@ fun Settings( }, text = uiState.username, iconSize = 53.sp to 24.sp, - content = if(uiState.subscriptionState.type !is ProStatus.NeverSubscribed){{ // if we are pro or expired + content = if(uiState.proDataState.type !is ProStatus.NeverSubscribed){{ // if we are pro or expired ProBadge( modifier = Modifier.padding(start = 4.dp) .qaTag(stringResource(R.string.qa_pro_badge_icon)), - colors = if(uiState.subscriptionState.type is ProStatus.Active) + colors = if(uiState.proDataState.type is ProStatus.Active) proBadgeColorStandard() else proBadgeColorDisabled() ) @@ -302,7 +302,7 @@ fun Settings( recoveryHidden = uiState.recoveryHidden, hasPaths = uiState.hasPath, postPro = uiState.isPostPro, - subscriptionState = uiState.subscriptionState, + proDataState = uiState.proDataState, sendCommand = sendCommand ) @@ -384,7 +384,7 @@ fun Settings( if(uiState.showAvatarDialog) { AvatarDialog( state = uiState.avatarDialogState, - isPro = uiState.subscriptionState.type is ProStatus.Active, + isPro = uiState.proDataState.type is ProStatus.Active, isPostPro = uiState.isPostPro, sendCommand = sendCommand, startAvatarSelection = startAvatarSelection @@ -394,7 +394,7 @@ fun Settings( // Animated avatar CTA if(uiState.showAnimatedProCTA){ AnimatedProCTA( - proSubscription = uiState.subscriptionState.type, + proSubscription = uiState.proDataState.type, sendCommand = sendCommand ) } @@ -481,7 +481,7 @@ fun Buttons( recoveryHidden: Boolean, hasPaths: Boolean, postPro: Boolean, - subscriptionState: SubscriptionState, + proDataState: ProDataState, sendCommand: (SettingsViewModel.Commands) -> Unit, ) { Column( @@ -526,7 +526,7 @@ fun Buttons( if(postPro){ ItemButton( text = annotatedStringResource( - when (subscriptionState.type) { + when (proDataState.type) { is ProStatus.Active -> Phrase.from( LocalContext.current, R.string.sessionProBeta @@ -1062,7 +1062,7 @@ private fun SettingsScreenPreview() { ) ), isPostPro = true, - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index 6c0400e3fd..bd491e3091 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -52,7 +52,7 @@ import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.pro.ProStatusManager -import org.thoughtcrime.securesms.pro.SubscriptionState +import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.getDefaultSubscriptionStateData import org.thoughtcrime.securesms.reviews.InAppReviewManager import org.thoughtcrime.securesms.ui.SimpleDialogData @@ -96,7 +96,7 @@ class SettingsViewModel @Inject constructor( version = getVersionNumber(), recoveryHidden = prefs.getHidePassword(), isPostPro = proStatusManager.isPostPro(), - subscriptionState = getDefaultSubscriptionStateData(), + proDataState = getDefaultSubscriptionStateData(), )) val uiState: StateFlow get() = _uiState @@ -116,8 +116,8 @@ class SettingsViewModel @Inject constructor( // observe subscription status viewModelScope.launch { - proStatusManager.subscriptionState.collect { state -> - _uiState.update { it.copy(subscriptionState = state) } + proStatusManager.proDataState.collect { state -> + _uiState.update { it.copy(proDataState = state) } } } @@ -651,7 +651,7 @@ class SettingsViewModel @Inject constructor( val usernameDialog: UsernameDialogData? = null, val showSimpleDialog: SimpleDialogData? = null, val isPostPro: Boolean, - val subscriptionState: SubscriptionState, + val proDataState: ProDataState, ) sealed interface Commands { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt index 88514a2058..0df7dbabb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt @@ -38,7 +38,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.DATE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NETWORK_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY import org.thoughtcrime.securesms.pro.SubscriptionDetails -import org.thoughtcrime.securesms.pro.SubscriptionState +import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.ui.SessionProSettingsHeader @@ -115,7 +115,7 @@ fun PlanConfirmation( Spacer(Modifier.height(LocalDimensions.current.xsSpacing)) - val description = when (proData.subscriptionState.type) { + val description = when (proData.proDataState.type) { is ProStatus.Active -> { Phrase.from(context.getText(R.string.proAllSetDescription)) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) @@ -150,7 +150,7 @@ fun PlanConfirmation( Spacer(Modifier.height(LocalDimensions.current.spacing)) - val buttonLabel = when (proData.subscriptionState.type) { + val buttonLabel = when (proData.proDataState.type) { is ProStatus.Active -> stringResource(R.string.theReturn) else -> { @@ -185,7 +185,7 @@ private fun PreviewPlanConfirmationActive( PlanConfirmation( proData = ProSettingsViewModel.ProSettingsState( subscriptionExpiryDate = "20th June 2026", - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, @@ -216,7 +216,7 @@ private fun PreviewPlanConfirmationExpired( PreviewTheme(colors) { PlanConfirmation( proData = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), SubscriptionDetails( @@ -245,7 +245,7 @@ private fun PreviewPlanConfirmationNeverSub( PreviewTheme(colors) { PlanConfirmation( proData = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.NeverSubscribed, refreshState = State.Success(Unit), showProBadge = true, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt index 2742b8c4a1..451e51ff4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt @@ -63,7 +63,7 @@ import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.C import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.pro.SubscriptionDetails -import org.thoughtcrime.securesms.pro.SubscriptionState +import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.ui.ActionRowItem @@ -125,7 +125,7 @@ fun ProSettingsHome( sendCommand: (ProSettingsViewModel.Commands) -> Unit, onBack: () -> Unit, ) { - val subscriptionType = data.subscriptionState.type + val subscriptionType = data.proDataState.type val context = LocalContext.current val expiredInMainScreen = subscriptionType is ProStatus.Expired && !inSheet @@ -137,13 +137,13 @@ fun ProSettingsHome( onBack = onBack, onHeaderClick = { // add a click handling if the subscription state is loading or errored - if(data.subscriptionState.refreshState !is State.Success<*>){ + if(data.proDataState.refreshState !is State.Success<*>){ sendCommand(OnHeaderClicked(inSheet)) } else null }, extraHeaderContent = { // display extra content if the subscription state is loading or errored - when(data.subscriptionState.refreshState){ + when(data.proDataState.refreshState){ is State.Loading -> { Row( verticalAlignment = Alignment.CenterVertically, @@ -199,7 +199,7 @@ fun ProSettingsHome( ) { // Header for non-pro users or expired users in sheet mode if(subscriptionType is ProStatus.NeverSubscribed || expiredInSheet) { - if(data.subscriptionState.refreshState !is State.Success){ + if(data.proDataState.refreshState !is State.Success){ Spacer(Modifier.height(LocalDimensions.current.contentSpacing)) } @@ -219,7 +219,7 @@ fun ProSettingsHome( Spacer(Modifier.height(LocalDimensions.current.spacing)) Box { - val enableButon = data.subscriptionState.refreshState is State.Success + val enableButon = data.proDataState.refreshState is State.Success AccentFillButtonRect( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.theContinue), @@ -261,8 +261,8 @@ fun ProSettingsHome( if(subscriptionType is ProStatus.Active){ Spacer(Modifier.height(LocalDimensions.current.smallSpacing)) ProSettings( - showProBadge = data.subscriptionState.showProBadge, - subscriptionRefreshState = data.subscriptionState.refreshState, + showProBadge = data.proDataState.showProBadge, + subscriptionRefreshState = data.proDataState.refreshState, inSheet = inSheet, expiry = data.subscriptionExpiryLabel, sendCommand = sendCommand, @@ -274,7 +274,7 @@ fun ProSettingsHome( Spacer(Modifier.height(LocalDimensions.current.spacing)) ProManage( data = subscriptionType, - subscriptionRefreshState = data.subscriptionState.refreshState, + subscriptionRefreshState = data.proDataState.refreshState, inSheet = inSheet, sendCommand = sendCommand, ) @@ -292,7 +292,7 @@ fun ProSettingsHome( if(!inSheet){ ProSettingsFooter( proStatus = subscriptionType, - subscriptionRefreshState = data.subscriptionState.refreshState, + subscriptionRefreshState = data.proDataState.refreshState, inSheet = inSheet, sendCommand = sendCommand ) @@ -973,7 +973,7 @@ fun PreviewProSettingsPro( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, @@ -1005,7 +1005,7 @@ fun PreviewProSettingsProLoading( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, @@ -1037,7 +1037,7 @@ fun PreviewProSettingsProError( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, @@ -1069,7 +1069,7 @@ fun PreviewProSettingsExpired( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), SubscriptionDetails( @@ -1099,7 +1099,7 @@ fun PreviewProSettingsExpiredInSheet( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), SubscriptionDetails( @@ -1129,7 +1129,7 @@ fun PreviewProSettingsExpiredLoading( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), SubscriptionDetails( @@ -1159,7 +1159,7 @@ fun PreviewProSettingsExpiredError( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), SubscriptionDetails( @@ -1189,7 +1189,7 @@ fun PreviewProSettingsNonPro( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.NeverSubscribed, refreshState = State.Success(Unit), showProBadge = true, @@ -1210,7 +1210,7 @@ fun PreviewProSettingsNonProInSheet( PreviewTheme(colors) { ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( - subscriptionState = SubscriptionState( + proDataState = ProDataState( type = ProStatus.NeverSubscribed, refreshState = State.Success(Unit), showProBadge = true, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index 7816f2e159..7bdab4a8a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -36,7 +36,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.SELECTED_PLA import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog import org.thoughtcrime.securesms.pro.ProStatusManager -import org.thoughtcrime.securesms.pro.SubscriptionState +import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.getDefaultSubscriptionStateData import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration @@ -84,7 +84,7 @@ class ProSettingsViewModel @AssistedInject constructor( init { // observe subscription status viewModelScope.launch { - proStatusManager.subscriptionState.collect { + proStatusManager.proDataState.collect { generateState(it) } } @@ -120,7 +120,7 @@ class ProSettingsViewModel @AssistedInject constructor( // this is a special case of failure. We should display a custom dialog and allow the user to retry _dialogState.update { val action = context.getString( - when(_proSettingsUIState.value.subscriptionState.type) { + when(_proSettingsUIState.value.proDataState.type) { is ProStatus.Active -> R.string.proUpdatingAction is ProStatus.Expired -> R.string.proRenewingAction else -> R.string.proUpgradingAction @@ -158,14 +158,14 @@ class ProSettingsViewModel @AssistedInject constructor( } } - private fun generateState(subscriptionState: SubscriptionState){ + private fun generateState(proDataState: ProDataState){ //todo PRO need to properly calculate this - val subType = subscriptionState.type + val subType = proDataState.type _proSettingsUIState.update { ProSettingsState( - subscriptionState = subscriptionState, + proDataState = proDataState, subscriptionExpiryLabel = when(subType){ is ProStatus.Active.AutoRenewing -> Phrase.from(context, R.string.proAutoRenewTime) @@ -207,10 +207,10 @@ class ProSettingsViewModel @AssistedInject constructor( } is Commands.GoToChoosePlan -> { - when(_proSettingsUIState.value.subscriptionState.refreshState){ + when(_proSettingsUIState.value.proDataState.refreshState){ // if we are in a loading or refresh state we should show a dialog instead is State.Loading -> { - val state = _proSettingsUIState.value.subscriptionState.type + val state = _proSettingsUIState.value.proDataState.type val (title, message) = when{ state is ProStatus.Active -> Phrase.from(context.getText(R.string.proAccessLoading)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) @@ -246,7 +246,7 @@ class ProSettingsViewModel @AssistedInject constructor( } is State.Error -> { - val state = _proSettingsUIState.value.subscriptionState.type + val state = _proSettingsUIState.value.proDataState.type val (title, message) = when{ state is ProStatus.Active -> Phrase.from(context.getText(R.string.proAccessError)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) @@ -295,7 +295,7 @@ class ProSettingsViewModel @AssistedInject constructor( } Commands.GoToRefund -> { - val sub = _proSettingsUIState.value.subscriptionState.type + val sub = _proSettingsUIState.value.proDataState.type if(sub !is ProStatus.Active) return _refundPlanState.update { State.Loading } @@ -316,7 +316,7 @@ class ProSettingsViewModel @AssistedInject constructor( } Commands.GoToCancel -> { - val sub = _proSettingsUIState.value.subscriptionState.type + val sub = _proSettingsUIState.value.proDataState.type if(sub !is ProStatus.Active) return // calculate state @@ -345,7 +345,7 @@ class ProSettingsViewModel @AssistedInject constructor( } Commands.OpenSubscriptionPage -> { - val subUrl = (_proSettingsUIState.value.subscriptionState.type as? ProStatus.Active) + val subUrl = (_proSettingsUIState.value.proDataState.type as? ProStatus.Active) ?.subscriptionDetails?.subscriptionUrl if(!subUrl.isNullOrEmpty()){ viewModelScope.launch { @@ -389,7 +389,7 @@ class ProSettingsViewModel @AssistedInject constructor( } Commands.GetProPlan -> { - val currentSubscription = _proSettingsUIState.value.subscriptionState.type + val currentSubscription = _proSettingsUIState.value.proDataState.type val selectedPlan = getSelectedPlan() ?: return if(currentSubscription is ProStatus.Active){ @@ -453,10 +453,10 @@ class ProSettingsViewModel @AssistedInject constructor( } is Commands.OnHeaderClicked -> { - when(_proSettingsUIState.value.subscriptionState.refreshState){ + when(_proSettingsUIState.value.proDataState.refreshState){ // if we are in a loading or refresh state we should show a dialog instead is State.Loading -> { - val state = _proSettingsUIState.value.subscriptionState.type + val state = _proSettingsUIState.value.proDataState.type val (title, message) = when{ state is ProStatus.Active -> Phrase.from(context.getText(R.string.proStatusLoading)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) @@ -492,7 +492,7 @@ class ProSettingsViewModel @AssistedInject constructor( is State.Error -> { _dialogState.update { - val state = _proSettingsUIState.value.subscriptionState.type + val state = _proSettingsUIState.value.proDataState.type val (title, message) = when{ state is ProStatus.Active -> Phrase.from(context.getText(R.string.proStatusError)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) @@ -579,7 +579,7 @@ class ProSettingsViewModel @AssistedInject constructor( // while the user is on the page we need to calculate the "choose plan" data viewModelScope.launch { - val subType = _proSettingsUIState.value.subscriptionState.type + val subType = _proSettingsUIState.value.proDataState.type // first check if the user has a valid subscription and billing val hasBillingCapacity = subscriptionCoordinator.getCurrentManager().supportsBilling.value @@ -784,7 +784,7 @@ class ProSettingsViewModel @AssistedInject constructor( } data class ProSettingsState( - val subscriptionState: SubscriptionState = getDefaultSubscriptionStateData(), + val proDataState: ProDataState = getDefaultSubscriptionStateData(), val proStats: State = State.Loading, val subscriptionExpiryLabel: CharSequence = "", // eg: "Pro auto renewing in 3 days" val subscriptionExpiryDate: CharSequence = "", // eg: "May 21st, 2025" diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt new file mode 100644 index 0000000000..399db3e81b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt @@ -0,0 +1,63 @@ +package org.thoughtcrime.securesms.pro + +import org.thoughtcrime.securesms.pro.api.ServerPlanDuration +import org.thoughtcrime.securesms.pro.api.ProDetails +import org.thoughtcrime.securesms.pro.api.ProDetails.Companion.SERVER_PLAN_DURATION_12_MONTH +import org.thoughtcrime.securesms.pro.api.ProDetails.Companion.SERVER_PLAN_DURATION_3_MONTH +import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration + +fun ProDetails.toProStatus(): ProStatus { + return when (status) { + ProDetails.DETAILS_STATUS_ACTIVE -> { + if (autoRenewing == true) { + ProStatus.Active.AutoRenewing( + validUntil = expiry!!, + duration = paymentItems.first().planDuration.toSubscriptionDuration(), + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "", + refundUrl = "", + ) + ) + } else { + ProStatus.Active.Expiring( + validUntil = expiry!!, + duration = paymentItems.first().planDuration.toSubscriptionDuration(), + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "", + refundUrl = "", + ) + ) + } + } + + ProDetails.DETAILS_STATUS_EXPIRED -> ProStatus.Expired( + expiredAt = expiry!!, + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "", + refundUrl = "", + ) + ) + + else -> ProStatus.NeverSubscribed + } +} + +fun ServerPlanDuration.toSubscriptionDuration(): ProSubscriptionDuration { + return when(this){ + SERVER_PLAN_DURATION_12_MONTH -> ProSubscriptionDuration.TWELVE_MONTHS + SERVER_PLAN_DURATION_3_MONTH -> ProSubscriptionDuration.THREE_MONTHS + else -> ProSubscriptionDuration.ONE_MONTH + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt index 008edc44b9..a310a789b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt @@ -32,7 +32,7 @@ sealed interface ProStatus{ ): ProStatus } -data class SubscriptionState( +data class ProDataState( val type: ProStatus, val showProBadge: Boolean, val refreshState: State, @@ -61,7 +61,7 @@ data class SubscriptionDetails( } } -fun getDefaultSubscriptionStateData() = SubscriptionState( +fun getDefaultSubscriptionStateData() = ProDataState( type = ProStatus.NeverSubscribed, refreshState = State.Loading, showProBadge = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index 6974bb9892..e442dc7149 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -55,7 +55,7 @@ class ProStatusManager @Inject constructor( private val snodeClock: SnodeClock, ) : OnAppStartupComponent { - val subscriptionState: StateFlow = combine( + val proDataState: StateFlow = combine( recipientRepository.observeSelf(), (TextSecurePreferences.events.filter { it == TextSecurePreferences.DEBUG_SUBSCRIPTION_STATUS } as Flow<*>) .onStart { emit(Unit) } @@ -67,128 +67,131 @@ class ProStatusManager @Inject constructor( .onStart { emit(Unit) } .map { prefs.forceCurrentUserAsPro() }, ){ selfRecipient, debugSubscription, debugProPlanStatus, forceCurrentUserAsPro -> - //todo PRO implement properly - - val subscriptionState = debugSubscription ?: DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE - val proDataStatus = when(debugProPlanStatus){ + val proDataRefreshState = when(debugProPlanStatus){ DebugMenuViewModel.DebugProPlanStatus.LOADING -> State.Loading DebugMenuViewModel.DebugProPlanStatus.ERROR -> State.Error(Exception()) else -> State.Success(Unit) } if(!forceCurrentUserAsPro){ + Log.d(DebugLogGroup.PRO_DATA.label, "ProStatusManager: Getting REAL Pro data state") //todo PRO this is where we should get the real state - SubscriptionState( + ProDataState( type = ProStatus.NeverSubscribed, showProBadge = selfRecipient.shouldShowProBadge, - refreshState = proDataStatus + refreshState = proDataRefreshState ) - } - else SubscriptionState( - type = when(subscriptionState){ - DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE -> ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", + }// debug data + else { + Log.d(DebugLogGroup.PRO_DATA.label, "ProStatusManager: Getting DEBUG Pro data state") + val subscriptionState = debugSubscription ?: DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE + + ProDataState( + type = when(subscriptionState){ + DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE -> ProStatus.Active.AutoRenewing( + validUntil = Instant.now() + Duration.ofDays(14), + duration = ProSubscriptionDuration.THREE_MONTHS, + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", + refundUrl = "https://getsession.org/android-refund", + ) ) - ) - - DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE -> ProStatus.Active.Expiring( - validUntil = Instant.now() + Duration.ofDays(2), - duration = ProSubscriptionDuration.TWELVE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", + + DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE -> ProStatus.Active.Expiring( + validUntil = Instant.now() + Duration.ofDays(2), + duration = ProSubscriptionDuration.TWELVE_MONTHS, + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", + refundUrl = "https://getsession.org/android-refund", + ) ) - ) - - DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE_LATER -> ProStatus.Active.Expiring( - validUntil = Instant.now() + Duration.ofDays(40), - duration = ProSubscriptionDuration.TWELVE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", + + DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE_LATER -> ProStatus.Active.Expiring( + validUntil = Instant.now() + Duration.ofDays(40), + duration = ProSubscriptionDuration.TWELVE_MONTHS, + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", + refundUrl = "https://getsession.org/android-refund", + ) ) - ) - - DebugMenuViewModel.DebugSubscriptionStatus.AUTO_APPLE -> ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.ONE_MONTH, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://support.apple.com/118223", + + DebugMenuViewModel.DebugSubscriptionStatus.AUTO_APPLE -> ProStatus.Active.AutoRenewing( + validUntil = Instant.now() + Duration.ofDays(14), + duration = ProSubscriptionDuration.ONE_MONTH, + subscriptionDetails = SubscriptionDetails( + device = "iOS", + store = "Apple App Store", + platform = "Apple", + platformAccount = "Apple Account", + subscriptionUrl = "https://www.apple.com/account/subscriptions", + refundUrl = "https://support.apple.com/118223", + ) ) - ) - - DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_APPLE -> ProStatus.Active.Expiring( - validUntil = Instant.now() + Duration.ofDays(2), - duration = ProSubscriptionDuration.ONE_MONTH, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://support.apple.com/118223", + + DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_APPLE -> ProStatus.Active.Expiring( + validUntil = Instant.now() + Duration.ofDays(2), + duration = ProSubscriptionDuration.ONE_MONTH, + subscriptionDetails = SubscriptionDetails( + device = "iOS", + store = "Apple App Store", + platform = "Apple", + platformAccount = "Apple Account", + subscriptionUrl = "https://www.apple.com/account/subscriptions", + refundUrl = "https://support.apple.com/118223", + ) ) - ) - - DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED -> ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", + + DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED -> ProStatus.Expired( + expiredAt = Instant.now() - Duration.ofDays(14), + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", + refundUrl = "https://getsession.org/android-refund", + ) ) - ) - DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_EARLIER -> ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(60), - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", + DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_EARLIER -> ProStatus.Expired( + expiredAt = Instant.now() - Duration.ofDays(60), + subscriptionDetails = SubscriptionDetails( + device = "Android", + store = "Google Play Store", + platform = "Google", + platformAccount = "Google account", + subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", + refundUrl = "https://getsession.org/android-refund", + ) ) - ) - DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_APPLE -> ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://support.apple.com/118223", + DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_APPLE -> ProStatus.Expired( + expiredAt = Instant.now() - Duration.ofDays(14), + subscriptionDetails = SubscriptionDetails( + device = "iOS", + store = "Apple App Store", + platform = "Apple", + platformAccount = "Apple Account", + subscriptionUrl = "https://www.apple.com/account/subscriptions", + refundUrl = "https://support.apple.com/118223", + ) ) - ) - }, + }, - refreshState = proDataStatus, - showProBadge = selfRecipient.shouldShowProBadge, - ) + refreshState = proDataRefreshState, + showProBadge = selfRecipient.shouldShowProBadge, + ) + } }.stateIn(scope, SharingStarted.Eagerly, initialValue = getDefaultSubscriptionStateData() diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt index a5b77a2b31..df386213dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/api/GetProDetails.kt @@ -39,11 +39,12 @@ class GetProDetailsRequest @AssistedInject constructor( } } -typealias ProDetailsStatus = Int +typealias ServerProDetailsStatus = Int +typealias ServerPlanDuration = Int @Serializable class ProDetails( - val status: ProDetailsStatus, + val status: ServerProDetailsStatus, @SerialName("auto_renewing") val autoRenewing: Boolean? = null, @@ -66,10 +67,17 @@ class ProDetails( val version: Int, ) { + init { + check((status != DETAILS_STATUS_ACTIVE && status != DETAILS_STATUS_EXPIRED) || expiry != null) { "Expiry must not be null for state other than 'never subscribed'" } + check((status != DETAILS_STATUS_ACTIVE && status != DETAILS_STATUS_EXPIRED) || paymentItems.isNotEmpty()) { "Can't have no payment items for state other than 'never subscribed'" } + } @Serializable data class Item( - val status: ProDetailsStatus, + @SerialName("plan") + val planDuration: ServerPlanDuration, + + val status: Int, // Payment status [Redeemed, Revoked, Expired] - we do not use this status in the clients @SerialName("payment_provider") val paymentProvider: PaymentProvider, @@ -114,8 +122,12 @@ class ProDetails( ) companion object { - const val DETAILS_STATUS_NEVER_BEEN_PRO: ProDetailsStatus = 0 - const val DETAILS_STATUS_ACTIVE: ProDetailsStatus = 1 - const val DETAILS_STATUS_EXPIRED: ProDetailsStatus = 2 + const val DETAILS_STATUS_NEVER_BEEN_PRO: ServerProDetailsStatus = 0 + const val DETAILS_STATUS_ACTIVE: ServerProDetailsStatus = 1 + const val DETAILS_STATUS_EXPIRED: ServerProDetailsStatus = 2 + + const val SERVER_PLAN_DURATION_1_MONTH: ServerPlanDuration = 1 + const val SERVER_PLAN_DURATION_3_MONTH: ServerPlanDuration = 2 + const val SERVER_PLAN_DURATION_12_MONTH: ServerPlanDuration = 3 } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UserProfileUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/UserProfileUtils.kt index f388053c21..c4df000fbf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/UserProfileUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/UserProfileUtils.kt @@ -143,7 +143,7 @@ class UserProfileUtils @AssistedInject constructor( UserProfileModalCommands.ShowProCTA -> { _userProfileModalData.update { _userProfileModalData.value?.copy( - showProCTA = GenericCTAData(proStatusManager.subscriptionState.value.type) + showProCTA = GenericCTAData(proStatusManager.proDataState.value.type) ) } } From 1612e9b738535ccfd45ff5e4d8a79d4ae51af47f Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Tue, 18 Nov 2025 16:14:28 +1100 Subject: [PATCH 6/7] Using the back end data. Unifying compose previews --- .../securesms/preferences/SettingsScreen.kt | 37 +++---- .../prosettings/CancelPlanNonOriginating.kt | 17 ++-- .../prosettings/CancelPlanScreen.kt | 5 +- .../prosettings/PlanConfirmationScreen.kt | 30 +----- .../prosettings/ProSettingsHomeScreen.kt | 91 ++---------------- .../prosettings/ProSettingsViewModel.kt | 21 ++-- .../prosettings/RefundPlanNonOriginating.kt | 20 +--- .../prosettings/RefundPlanScreen.kt | 34 +------ .../chooseplan/ChoosePlanHomeScreen.kt | 1 + .../chooseplan/ChoosePlanNoBilling.kt | 21 +--- .../chooseplan/ChoosePlanNonOriginating.kt | 21 +--- .../securesms/pro/ProDataMapper.kt | 96 +++++++++++++------ .../thoughtcrime/securesms/pro/ProStatus.kt | 41 +++----- .../securesms/pro/ProStatusManager.kt | 80 ++++------------ .../subscription/NoOpSubscriptionManager.kt | 6 -- .../pro/subscription/SubscriptionManager.kt | 7 -- .../PlayStoreSubscriptionManager.kt | 17 ---- gradle/libs.versions.toml | 2 +- 18 files changed, 174 insertions(+), 373 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt index 638df937e4..891ba0a687 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt @@ -75,13 +75,29 @@ import org.thoughtcrime.securesms.home.PathActivity import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.TempAvatar import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.UserAvatar -import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.* +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ClearData +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideAnimatedProCTA +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideAvatarPickerOptions +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideClearDataDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideSimpleDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideUrlDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.HideUsernameDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.OnAvatarDialogDismissed +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.OnDonateClicked +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.RemoveAvatar +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.SaveAvatar +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.SetUsername +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowAnimatedProCTA +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowAvatarDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowClearDataDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowUrlDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.ShowUsernameDialog +import org.thoughtcrime.securesms.preferences.SettingsViewModel.Commands.UpdateUsername import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsActivity -import org.thoughtcrime.securesms.pro.SubscriptionDetails import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus -import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity import org.thoughtcrime.securesms.tokenpage.TokenPageActivity import org.thoughtcrime.securesms.ui.AccountIdHeader @@ -131,8 +147,6 @@ import org.thoughtcrime.securesms.util.AvatarUIData import org.thoughtcrime.securesms.util.AvatarUIElement import org.thoughtcrime.securesms.util.State import org.thoughtcrime.securesms.util.push -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalSharedTransitionApi::class) @Composable @@ -1063,18 +1077,7 @@ private fun SettingsScreenPreview() { ), isPostPro = true, proDataState = ProDataState( - type = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + type = previewAutoRenewingApple, refreshState = State.Success(Unit), showProBadge = true ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt index e8f190426f..d803089f9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import com.squareup.phrase.Phrase import network.loki.messenger.R +import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata import org.session.libsession.utilities.NonTranslatableStringConstants import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY @@ -17,7 +18,8 @@ import org.session.libsession.utilities.StringSubstitutionConstants.PLATFORM_ACC import org.session.libsession.utilities.StringSubstitutionConstants.PLATFORM_KEY import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog -import org.thoughtcrime.securesms.pro.SubscriptionDetails +import org.thoughtcrime.securesms.pro.getPlatformDisplayName +import org.thoughtcrime.securesms.pro.previewAppleMetaData import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors @@ -25,7 +27,7 @@ import org.thoughtcrime.securesms.ui.theme.ThemeColors @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable fun CancelPlanNonOriginating( - subscriptionDetails: SubscriptionDetails, + subscriptionDetails: PaymentProviderMetadata, sendCommand: (ProSettingsViewModel.Commands) -> Unit, onBack: () -> Unit, ){ @@ -42,7 +44,7 @@ fun CancelPlanNonOriginating( .format().toString(), dangerButton = true, onButtonClick = { - sendCommand(ShowOpenUrlDialog(subscriptionDetails.subscriptionUrl)) + sendCommand(ShowOpenUrlDialog(subscriptionDetails.cancelSubscriptionUrl)) }, contentTitle = stringResource(R.string.proCancellation), contentDescription = Phrase.from(context.getText(R.string.proCancellationDescription)) @@ -88,14 +90,7 @@ private fun PreviewUpdatePlan( PreviewTheme(colors) { val context = LocalContext.current CancelPlanNonOriginating ( - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ), + subscriptionDetails = previewAppleMetaData, sendCommand = {}, onBack = {}, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt index 9a03c95a64..0ee0fb1eca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt @@ -19,7 +19,8 @@ import network.loki.messenger.R import org.session.libsession.utilities.NonTranslatableStringConstants import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY -import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.OpenSubscriptionPage +import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.OpenCancelSubscriptionPage +import org.thoughtcrime.securesms.pro.isFromAnotherPlatform import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -81,7 +82,7 @@ fun CancelPlan( .format().toString(), dangerButton = true, onButtonClick = { - sendCommand(OpenSubscriptionPage) + sendCommand(OpenCancelSubscriptionPage) }, title = Phrase.from(context.getText(R.string.proCancelSorry)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt index 0df7dbabb1..c8d596e956 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt @@ -37,10 +37,10 @@ import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DATE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NETWORK_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY -import org.thoughtcrime.securesms.pro.SubscriptionDetails import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus -import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple +import org.thoughtcrime.securesms.pro.previewExpiredApple import org.thoughtcrime.securesms.ui.SessionProSettingsHeader import org.thoughtcrime.securesms.ui.components.AccentFillButtonRect import org.thoughtcrime.securesms.ui.components.annotatedStringResource @@ -52,8 +52,6 @@ import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors import org.thoughtcrime.securesms.util.State -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalSharedTransitionApi::class) @@ -186,18 +184,7 @@ private fun PreviewPlanConfirmationActive( proData = ProSettingsViewModel.ProSettingsState( subscriptionExpiryDate = "20th June 2026", proDataState = ProDataState( - type = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + type = previewAutoRenewingApple, refreshState = State.Success(Unit), showProBadge = false, ), @@ -217,16 +204,7 @@ private fun PreviewPlanConfirmationExpired( PlanConfirmation( proData = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - )), + type = previewExpiredApple, refreshState = State.Success(Unit), showProBadge = true, ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt index 451e51ff4c..73b74cbb39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt @@ -61,11 +61,11 @@ import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.C import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.OnProStatsClicked import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.SetShowProBadge import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog -import org.thoughtcrime.securesms.pro.ProStatusManager -import org.thoughtcrime.securesms.pro.SubscriptionDetails import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus -import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.pro.ProStatusManager +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple +import org.thoughtcrime.securesms.pro.previewExpiredApple import org.thoughtcrime.securesms.ui.ActionRowItem import org.thoughtcrime.securesms.ui.CategoryCell import org.thoughtcrime.securesms.ui.Divider @@ -96,8 +96,6 @@ import org.thoughtcrime.securesms.ui.theme.primaryRed import org.thoughtcrime.securesms.ui.theme.primaryYellow import org.thoughtcrime.securesms.util.NumberUtil import org.thoughtcrime.securesms.util.State -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalSharedTransitionApi::class) @@ -974,18 +972,7 @@ fun PreviewProSettingsPro( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + type = previewAutoRenewingApple, refreshState = State.Success(Unit), showProBadge = true, ), @@ -1006,18 +993,7 @@ fun PreviewProSettingsProLoading( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + type = previewAutoRenewingApple, refreshState = State.Loading, showProBadge = true, ), @@ -1038,18 +1014,7 @@ fun PreviewProSettingsProError( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + type = previewAutoRenewingApple, refreshState = State.Error(Exception()), showProBadge = true, ), @@ -1070,16 +1035,7 @@ fun PreviewProSettingsExpired( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - )), + type = previewExpiredApple, refreshState = State.Success(Unit), showProBadge = true, ) @@ -1100,16 +1056,7 @@ fun PreviewProSettingsExpiredInSheet( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - )), + type = previewExpiredApple, refreshState = State.Success(Unit), showProBadge = true, ) @@ -1130,16 +1077,7 @@ fun PreviewProSettingsExpiredLoading( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - )), + type = previewExpiredApple, refreshState = State.Loading, showProBadge = true, ) @@ -1160,16 +1098,7 @@ fun PreviewProSettingsExpiredError( ProSettingsHome( data = ProSettingsViewModel.ProSettingsState( proDataState = ProDataState( - type = ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - )), + type = previewExpiredApple, refreshState = State.Error(Exception()), showProBadge = true, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index 7bdab4a8a6..8446783226 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -34,11 +34,13 @@ import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY import org.session.libsession.utilities.StringSubstitutionConstants.SELECTED_PLAN_LENGTH_KEY import org.session.libsession.utilities.StringSubstitutionConstants.SELECTED_PLAN_LENGTH_SINGULAR_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY +import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog -import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProStatus +import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.pro.getDefaultSubscriptionStateData +import org.thoughtcrime.securesms.pro.isFromAnotherPlatform import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.pro.subscription.SubscriptionCoordinator import org.thoughtcrime.securesms.pro.subscription.SubscriptionManager @@ -58,7 +60,8 @@ class ProSettingsViewModel @AssistedInject constructor( @param:ApplicationContext private val context: Context, private val proStatusManager: ProStatusManager, private val subscriptionCoordinator: SubscriptionCoordinator, - private val dateUtils: DateUtils + private val dateUtils: DateUtils, + private val prefs: TextSecurePreferences, ) : ViewModel() { @AssistedFactory @@ -302,13 +305,15 @@ class ProSettingsViewModel @AssistedInject constructor( navigateTo(ProSettingsDestination.RefundSubscription) viewModelScope.launch { - val subManager = subscriptionCoordinator.getCurrentManager() _refundPlanState.update { + val isQuickRefund = if(prefs.getDebugIsWithinQuickRefund() && prefs.forceCurrentUserAsPro()) true // debug mode + else sub.isWithinQuickRefundWindow() + State.Success( RefundPlanState( proStatus = sub, - isQuickRefund = subManager.isWithinQuickRefundWindow(), - quickRefundUrl = subManager.quickRefundUrl + isQuickRefund = isQuickRefund, + quickRefundUrl = sub.subscriptionDetails.refundUrl ) ) } @@ -344,9 +349,9 @@ class ProSettingsViewModel @AssistedInject constructor( } } - Commands.OpenSubscriptionPage -> { + Commands.OpenCancelSubscriptionPage -> { val subUrl = (_proSettingsUIState.value.proDataState.type as? ProStatus.Active) - ?.subscriptionDetails?.subscriptionUrl + ?.subscriptionDetails?.cancelSubscriptionUrl if(!subUrl.isNullOrEmpty()){ viewModelScope.launch { navigator.navigateToIntent( @@ -771,7 +776,7 @@ class ProSettingsViewModel @AssistedInject constructor( object GoToCancel: Commands object OnPostPlanConfirmation: Commands - object OpenSubscriptionPage: Commands + object OpenCancelSubscriptionPage: Commands data class SetShowProBadge(val show: Boolean): Commands diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt index b95cc48f36..6fb71f483c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt @@ -19,13 +19,10 @@ import org.session.libsession.utilities.StringSubstitutionConstants.PLATFORM_STO import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog import org.thoughtcrime.securesms.pro.ProStatus -import org.thoughtcrime.securesms.pro.SubscriptionDetails -import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable @@ -45,7 +42,7 @@ fun RefundPlanNonOriginating( .format().toString(), dangerButton = true, onButtonClick = { - sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.refundUrl)) + sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.refundSupportUrl)) }, contentTitle = Phrase.from(context.getText(R.string.proRefunding)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) @@ -92,18 +89,7 @@ private fun PreviewUpdatePlan( PreviewTheme(colors) { val context = LocalContext.current RefundPlanNonOriginating ( - subscription = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + subscription = previewAutoRenewingApple, sendCommand = {}, onBack = {}, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt index 4011193e37..48ada584ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt @@ -22,8 +22,8 @@ import org.session.libsession.utilities.StringSubstitutionConstants.PLATFORM_KEY import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog import org.thoughtcrime.securesms.pro.ProStatus -import org.thoughtcrime.securesms.pro.SubscriptionDetails -import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.pro.isFromAnotherPlatform +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions @@ -32,8 +32,6 @@ import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors import org.thoughtcrime.securesms.ui.theme.bold -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalSharedTransitionApi::class) @@ -95,7 +93,7 @@ fun RefundPlan( if(isQuickRefund && !quickRefundUrl.isNullOrEmpty()){ sendCommand(ShowOpenUrlDialog(quickRefundUrl)) } else { - sendCommand(ShowOpenUrlDialog(data.subscriptionDetails.refundUrl)) + sendCommand(ShowOpenUrlDialog(data.subscriptionDetails.refundSupportUrl)) } }, title = stringResource(R.string.proRefundDescription), @@ -158,18 +156,7 @@ private fun PreviewRefundPlan( ) { PreviewTheme(colors) { RefundPlan( - data = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) - ), + data = previewAutoRenewingApple, isQuickRefund = false, quickRefundUrl = "", sendCommand = {}, @@ -185,18 +172,7 @@ private fun PreviewQuickRefundPlan( ) { PreviewTheme(colors) { RefundPlan( - data = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) - ), + data = previewAutoRenewingApple, isQuickRefund = true, quickRefundUrl = "", sendCommand = {}, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt index 30dd0ff6a8..74c799872d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.getValue import org.thoughtcrime.securesms.preferences.prosettings.BaseStateProScreen import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel import org.thoughtcrime.securesms.pro.ProStatus +import org.thoughtcrime.securesms.pro.isFromAnotherPlatform @OptIn(ExperimentalSharedTransitionApi::class) @Composable diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt index 5c1f13f5c7..098600ef3e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt @@ -24,15 +24,14 @@ import org.thoughtcrime.securesms.preferences.prosettings.BaseNonOriginatingProS import org.thoughtcrime.securesms.preferences.prosettings.NonOriginatingLinkCellData import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog -import org.thoughtcrime.securesms.pro.ProStatusManager -import org.thoughtcrime.securesms.pro.SubscriptionDetails import org.thoughtcrime.securesms.pro.ProStatus +import org.thoughtcrime.securesms.pro.ProStatusManager +import org.thoughtcrime.securesms.pro.getPlatformDisplayName +import org.thoughtcrime.securesms.pro.previewExpiredApple import org.thoughtcrime.securesms.ui.components.iconExternalLink import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable @@ -198,7 +197,7 @@ fun ChoosePlanNoBilling( dangerButton = false, onButtonClick = { if(subscription is ProStatus.Expired) { - sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.subscriptionUrl)) + sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.updateSubscriptionUrl)) } }, contentTitle = contentTitle, @@ -220,17 +219,7 @@ private fun PreviewNonOrigExpiredUpdatePlan( PreviewTheme(colors) { val context = LocalContext.current ChoosePlanNoBilling ( - subscription = ProStatus.Expired( - expiredAt = Instant.now() - Duration.ofDays(14), - SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + subscription = previewExpiredApple, sendCommand = {}, onBack = {}, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt index be88a097bf..1a96c9ed14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt @@ -24,15 +24,13 @@ import org.thoughtcrime.securesms.preferences.prosettings.NonOriginatingLinkCell import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog import org.thoughtcrime.securesms.pro.ProStatus -import org.thoughtcrime.securesms.pro.SubscriptionDetails -import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.pro.getPlatformDisplayName +import org.thoughtcrime.securesms.pro.previewAutoRenewingApple import org.thoughtcrime.securesms.pro.subscription.expiryFromNow import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors import org.thoughtcrime.securesms.util.DateUtils -import java.time.Duration -import java.time.Instant @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable @@ -73,7 +71,7 @@ fun ChoosePlanNonOriginating( .format().toString(), dangerButton = false, onButtonClick = { - sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.subscriptionUrl)) + sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.updateSubscriptionUrl)) }, contentTitle = Phrase.from(LocalContext.current, R.string.updateAccess) .put(PRO_KEY, NonTranslatableStringConstants.PRO) @@ -124,18 +122,7 @@ private fun PreviewUpdatePlan( PreviewTheme(colors) { val context = LocalContext.current ChoosePlanNonOriginating ( - subscription = ProStatus.Active.AutoRenewing( - validUntil = Instant.now() + Duration.ofDays(14), - duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://www.apple.com/account/subscriptions", - ) - ), + subscription = previewAutoRenewingApple, sendCommand = {}, onBack = {}, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt index 399db3e81b..71446741c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt @@ -1,63 +1,105 @@ package org.thoughtcrime.securesms.pro +import network.loki.messenger.libsession_util.pro.BackendRequests +import network.loki.messenger.libsession_util.pro.BackendRequests.PAYMENT_PROVIDER_APP_STORE +import network.loki.messenger.libsession_util.pro.BackendRequests.PAYMENT_PROVIDER_GOOGLE_PLAY +import network.loki.messenger.libsession_util.pro.PaymentProvider +import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata import org.thoughtcrime.securesms.pro.api.ServerPlanDuration import org.thoughtcrime.securesms.pro.api.ProDetails import org.thoughtcrime.securesms.pro.api.ProDetails.Companion.SERVER_PLAN_DURATION_12_MONTH import org.thoughtcrime.securesms.pro.api.ProDetails.Companion.SERVER_PLAN_DURATION_3_MONTH import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import java.time.Duration +import java.time.Instant fun ProDetails.toProStatus(): ProStatus { return when (status) { ProDetails.DETAILS_STATUS_ACTIVE -> { + val paymentItem = paymentItems.first() + if (autoRenewing == true) { ProStatus.Active.AutoRenewing( validUntil = expiry!!, - duration = paymentItems.first().planDuration.toSubscriptionDuration(), - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "", - refundUrl = "", - ) + duration = paymentItem.planDuration.toSubscriptionDuration(), + subscriptionDetails = paymentItem.paymentProvider.getMetadata(), + quickRefundExpiry = paymentItem.platformExpiry ) } else { ProStatus.Active.Expiring( validUntil = expiry!!, - duration = paymentItems.first().planDuration.toSubscriptionDuration(), - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "", - refundUrl = "", - ) + duration = paymentItem.planDuration.toSubscriptionDuration(), + subscriptionDetails = paymentItem.paymentProvider.getMetadata(), + quickRefundExpiry = paymentItem.platformExpiry ) } } ProDetails.DETAILS_STATUS_EXPIRED -> ProStatus.Expired( expiredAt = expiry!!, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "", - refundUrl = "", - ) + subscriptionDetails = paymentItems.first().paymentProvider.getMetadata() ) else -> ProStatus.NeverSubscribed } } +fun PaymentProvider.getMetadata(): PaymentProviderMetadata{ + return when(this){ + PAYMENT_PROVIDER_APP_STORE -> BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!! + else -> BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! + } +} + fun ServerPlanDuration.toSubscriptionDuration(): ProSubscriptionDuration { return when(this){ SERVER_PLAN_DURATION_12_MONTH -> ProSubscriptionDuration.TWELVE_MONTHS SERVER_PLAN_DURATION_3_MONTH -> ProSubscriptionDuration.THREE_MONTHS else -> ProSubscriptionDuration.ONE_MONTH } -} \ No newline at end of file +} + +fun PaymentProviderMetadata.isFromAnotherPlatform(): Boolean { + return platform.trim().lowercase() != "google" +} + +/** + * Some UI cases require a special display name for the platform. + */ +fun PaymentProviderMetadata.getPlatformDisplayName(): String { + return when(platform.trim().lowercase()){ + "google" -> store + else -> platform + } +} + + +/** + * Preview Data - Reusable data for composable previews + */ + +val previewAppleMetaData = PaymentProviderMetadata( + device = "iOS", + store = "Apple App Store", + platform = "Apple", + platformAccount = "Apple Account", + updateSubscriptionUrl = "https://www.apple.com/account/subscriptions", + cancelSubscriptionUrl = "https://www.apple.com/account/subscriptions", + refundUrl = "https://www.apple.com/account/subscriptions", + refundSupportUrl = "https://www.apple.com/account/subscriptions", + refundAfterPlatformDeadlineUrl = "https://www.apple.com/account/subscriptions" +) + +val previewAutoRenewingApple = ProStatus.Active.AutoRenewing( + validUntil = Instant.now() + Duration.ofDays(14), + duration = ProSubscriptionDuration.THREE_MONTHS, + subscriptionDetails = previewAppleMetaData, + quickRefundExpiry = Instant.now() + Duration.ofDays(14) +) + +val previewExpiredApple = ProStatus.Expired( + expiredAt = Instant.now() - Duration.ofDays(14), + subscriptionDetails = previewAppleMetaData +) + + diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt index a310a789b2..1cddfdf690 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.pro +import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata +import org.thoughtcrime.securesms.pro.api.ProDetails import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.util.State import java.time.Instant @@ -10,25 +12,33 @@ sealed interface ProStatus{ sealed interface Active: ProStatus{ val validUntil: Instant val duration: ProSubscriptionDuration - val subscriptionDetails: SubscriptionDetails + val subscriptionDetails: PaymentProviderMetadata + val quickRefundExpiry: Instant? data class AutoRenewing( override val validUntil: Instant, override val duration: ProSubscriptionDuration, - override val subscriptionDetails: SubscriptionDetails + override val subscriptionDetails: PaymentProviderMetadata, + override val quickRefundExpiry: Instant? ): Active data class Expiring( override val validUntil: Instant, override val duration: ProSubscriptionDuration, - override val subscriptionDetails: SubscriptionDetails + override val subscriptionDetails: PaymentProviderMetadata, + override val quickRefundExpiry: Instant? ): Active + fun isWithinQuickRefundWindow(): Boolean { + return quickRefundExpiry != null && quickRefundExpiry!!.isAfter(Instant.now()) + } + + } data class Expired( val expiredAt: Instant, - val subscriptionDetails: SubscriptionDetails + val subscriptionDetails: PaymentProviderMetadata ): ProStatus } @@ -38,29 +48,6 @@ data class ProDataState( val refreshState: State, ) -data class SubscriptionDetails( - val device: String, - val store: String, - val platform: String, - val platformAccount: String, - val subscriptionUrl: String, - val refundUrl: String, -){ - fun isFromAnotherPlatform(): Boolean { - return platform.trim().lowercase() != "google" - } - - /** - * Some UI cases require a special display name for the platform. - */ - fun getPlatformDisplayName(): String { - return when(platform.trim().lowercase()){ - "google" -> store - else -> platform - } - } -} - fun getDefaultSubscriptionStateData() = ProDataState( type = ProStatus.NeverSubscribed, refreshState = State.Loading, diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index e442dc7149..84716a332a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -18,6 +18,9 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout +import network.loki.messenger.libsession_util.pro.BackendRequests +import network.loki.messenger.libsession_util.pro.BackendRequests.PAYMENT_PROVIDER_APP_STORE +import network.loki.messenger.libsession_util.pro.BackendRequests.PAYMENT_PROVIDER_GOOGLE_PLAY import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.TextSecurePreferences @@ -91,100 +94,49 @@ class ProStatusManager @Inject constructor( DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE -> ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, + quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE -> ProStatus.Active.Expiring( validUntil = Instant.now() + Duration.ofDays(2), duration = ProSubscriptionDuration.TWELVE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, + quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE_LATER -> ProStatus.Active.Expiring( validUntil = Instant.now() + Duration.ofDays(40), duration = ProSubscriptionDuration.TWELVE_MONTHS, - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, + quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.AUTO_APPLE -> ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.ONE_MONTH, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://support.apple.com/118223", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!!, + quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_APPLE -> ProStatus.Active.Expiring( validUntil = Instant.now() + Duration.ofDays(2), duration = ProSubscriptionDuration.ONE_MONTH, - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://support.apple.com/118223", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!!, + quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED -> ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_EARLIER -> ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(60), - subscriptionDetails = SubscriptionDetails( - device = "Android", - store = "Google Play Store", - platform = "Google", - platformAccount = "Google account", - subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY", - refundUrl = "https://getsession.org/android-refund", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_APPLE -> ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = SubscriptionDetails( - device = "iOS", - store = "Apple App Store", - platform = "Apple", - platformAccount = "Apple Account", - subscriptionUrl = "https://www.apple.com/account/subscriptions", - refundUrl = "https://support.apple.com/118223", - ) + subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!! ) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/NoOpSubscriptionManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/NoOpSubscriptionManager.kt index 91de7bf5f0..7c91139d3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/NoOpSubscriptionManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/NoOpSubscriptionManager.kt @@ -25,8 +25,6 @@ class NoOpSubscriptionManager @Inject constructor( override val supportsBilling = MutableStateFlow(false) - override val quickRefundUrl = null - override suspend fun purchasePlan(subscriptionDuration: ProSubscriptionDuration): Result { return Result.success(Unit) } @@ -37,10 +35,6 @@ class NoOpSubscriptionManager @Inject constructor( return false } - override suspend fun isWithinQuickRefundWindow(): Boolean { - return false - } - override suspend fun getSubscriptionPrices(): List { return emptyList() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt index fea97d821f..ea0d54469d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/SubscriptionManager.kt @@ -26,9 +26,6 @@ abstract class SubscriptionManager( abstract val supportsBilling: StateFlow - // Optional. Some store can have a platform specific refund window and url - abstract val quickRefundUrl: String? - abstract val availablePlans: List sealed interface PurchaseEvent { @@ -50,10 +47,6 @@ abstract class SubscriptionManager( abstract suspend fun purchasePlan(subscriptionDuration: ProSubscriptionDuration): Result - /** - * Returns true if a provider has a quick refunds and the current time since purchase is within that window - */ - abstract suspend fun isWithinQuickRefundWindow(): Boolean /** * Checks whether there is a valid subscription for the current user within this subscriber's billing API diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt index 00169f5106..89f202b9f2 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/pro/subscription/PlayStoreSubscriptionManager.kt @@ -68,8 +68,6 @@ class PlayStoreSubscriptionManager @Inject constructor( } .stateIn(scope, SharingStarted.Eagerly, false) - override val quickRefundUrl = "https://support.google.com/googleplay/workflow/9813244" - private val billingClient by lazy { BillingClient.newBuilder(application) .setListener { result, purchases -> @@ -267,21 +265,6 @@ class PlayStoreSubscriptionManager @Inject constructor( else getExistingSubscription() != null } - override suspend fun isWithinQuickRefundWindow(): Boolean { - if(prefs.getDebugIsWithinQuickRefund() && prefs.forceCurrentUserAsPro()) return true // debug mode - - val purchaseTimeMillis = getExistingSubscription()?.purchaseTime ?: return false - - val now = Instant.now() - val purchaseInstant = Instant.ofEpochMilli(purchaseTimeMillis) - - // Google Play allows refunds within 48 hours of purchase - val refundWindowHours = 48 - val refundDeadline = purchaseInstant.plus(refundWindowHours.toLong(), ChronoUnit.HOURS) - - return now.isBefore(refundDeadline) - } - @Throws(Exception::class) override suspend fun getSubscriptionPrices(): List { val result = getProductDetails() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e1e2707e0..e4355a56af 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ kotlinVersion = "2.2.20" kryoVersion = "5.6.2" kspVersion = "2.3.0" legacySupportV13Version = "1.0.0" -libsessionUtilAndroidVersion = "1.0.9-12-ga1e5a23" +libsessionUtilAndroidVersion = "1.0.9-15-g01bceb9" media3ExoplayerVersion = "1.8.0" mockitoCoreVersion = "5.20.0" navVersion = "2.9.5" From 074b9af433320a72dbadf72732396171ec6b04dc Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Wed, 19 Nov 2025 08:20:19 +1100 Subject: [PATCH 7/7] PR feedback: Renamed property --- .../prosettings/CancelPlanNonOriginating.kt | 22 +++++++++---------- .../prosettings/CancelPlanScreen.kt | 4 ++-- .../prosettings/ProSettingsViewModel.kt | 6 ++--- .../prosettings/RefundPlanNonOriginating.kt | 20 ++++++++--------- .../prosettings/RefundPlanScreen.kt | 8 +++---- .../chooseplan/ChoosePlanHomeScreen.kt | 2 +- .../chooseplan/ChoosePlanNoBilling.kt | 10 ++++----- .../chooseplan/ChoosePlanNonOriginating.kt | 16 +++++++------- .../securesms/pro/ProDataMapper.kt | 10 ++++----- .../thoughtcrime/securesms/pro/ProStatus.kt | 9 ++++---- .../securesms/pro/ProStatusManager.kt | 16 +++++++------- 11 files changed, 61 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt index d803089f9e..0cee7d8c4d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt @@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.ui.theme.ThemeColors @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable fun CancelPlanNonOriginating( - subscriptionDetails: PaymentProviderMetadata, + providerData: PaymentProviderMetadata, sendCommand: (ProSettingsViewModel.Commands) -> Unit, onBack: () -> Unit, ){ @@ -40,28 +40,28 @@ fun CancelPlanNonOriginating( .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format().toString(), buttonText = Phrase.from(context.getText(R.string.openPlatformWebsite)) - .put(PLATFORM_KEY, subscriptionDetails.getPlatformDisplayName()) + .put(PLATFORM_KEY, providerData.getPlatformDisplayName()) .format().toString(), dangerButton = true, onButtonClick = { - sendCommand(ShowOpenUrlDialog(subscriptionDetails.cancelSubscriptionUrl)) + sendCommand(ShowOpenUrlDialog(providerData.cancelSubscriptionUrl)) }, contentTitle = stringResource(R.string.proCancellation), contentDescription = Phrase.from(context.getText(R.string.proCancellationDescription)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) - .put(PLATFORM_ACCOUNT_KEY, subscriptionDetails.platformAccount) + .put(PLATFORM_ACCOUNT_KEY, providerData.platformAccount) .format(), linkCellsInfo = stringResource(R.string.proCancellationOptions), linkCells = listOf( NonOriginatingLinkCellData( title = Phrase.from(context.getText(R.string.onDevice)) - .put(DEVICE_TYPE_KEY, subscriptionDetails.device) + .put(DEVICE_TYPE_KEY, providerData.device) .format(), info = Phrase.from(context.getText(R.string.onDeviceCancelDescription)) .put(APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME) - .put(DEVICE_TYPE_KEY, subscriptionDetails.device) - .put(PLATFORM_ACCOUNT_KEY, subscriptionDetails.platformAccount) + .put(DEVICE_TYPE_KEY, providerData.device) + .put(PLATFORM_ACCOUNT_KEY, providerData.platformAccount) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), @@ -69,11 +69,11 @@ fun CancelPlanNonOriginating( ), NonOriginatingLinkCellData( title = Phrase.from(context.getText(R.string.onPlatformWebsite)) - .put(PLATFORM_KEY, subscriptionDetails.getPlatformDisplayName()) + .put(PLATFORM_KEY, providerData.getPlatformDisplayName()) .format(), info = Phrase.from(context.getText(R.string.requestRefundPlatformWebsite)) - .put(PLATFORM_KEY, subscriptionDetails.getPlatformDisplayName()) - .put(PLATFORM_ACCOUNT_KEY, subscriptionDetails.platformAccount) + .put(PLATFORM_KEY, providerData.getPlatformDisplayName()) + .put(PLATFORM_ACCOUNT_KEY, providerData.platformAccount) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), iconRes = R.drawable.ic_globe @@ -90,7 +90,7 @@ private fun PreviewUpdatePlan( PreviewTheme(colors) { val context = LocalContext.current CancelPlanNonOriginating ( - subscriptionDetails = previewAppleMetaData, + providerData = previewAppleMetaData, sendCommand = {}, onBack = {}, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt index 0ee0fb1eca..8e1349854a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanScreen.kt @@ -49,10 +49,10 @@ fun CancelPlanScreen( when { // there is an active subscription but from a different platform or from the // same platform but a different account - activePlan.subscriptionDetails.isFromAnotherPlatform() + activePlan.providerData.isFromAnotherPlatform() || !planData.hasValidSubscription -> CancelPlanNonOriginating( - subscriptionDetails = activePlan.subscriptionDetails, + providerData = activePlan.providerData, sendCommand = viewModel::onCommand, onBack = onBack, ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index 8446783226..2a254640cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -313,7 +313,7 @@ class ProSettingsViewModel @AssistedInject constructor( RefundPlanState( proStatus = sub, isQuickRefund = isQuickRefund, - quickRefundUrl = sub.subscriptionDetails.refundUrl + quickRefundUrl = sub.providerData.refundUrl ) ) } @@ -351,7 +351,7 @@ class ProSettingsViewModel @AssistedInject constructor( Commands.OpenCancelSubscriptionPage -> { val subUrl = (_proSettingsUIState.value.proDataState.type as? ProStatus.Active) - ?.subscriptionDetails?.cancelSubscriptionUrl + ?.providerData?.cancelSubscriptionUrl if(!subUrl.isNullOrEmpty()){ viewModelScope.launch { navigator.navigateToIntent( @@ -595,7 +595,7 @@ class ProSettingsViewModel @AssistedInject constructor( // or the user is pro but non originating val noPriceNeeded = !hasBillingCapacity || (subType is ProStatus.Active && !hasValidSub) - || (subType is ProStatus.Active && subType.subscriptionDetails.isFromAnotherPlatform()) + || (subType is ProStatus.Active && subType.providerData.isFromAnotherPlatform()) val plans = if(noPriceNeeded) emptyList() else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt index 6fb71f483c..d2793ee74e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt @@ -38,41 +38,41 @@ fun RefundPlanNonOriginating( onBack = onBack, headerTitle = stringResource(R.string.proRefundDescription), buttonText = Phrase.from(context.getText(R.string.openPlatformWebsite)) - .put(PLATFORM_KEY, subscription.subscriptionDetails.platform) + .put(PLATFORM_KEY, subscription.providerData.platform) .format().toString(), dangerButton = true, onButtonClick = { - sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.refundSupportUrl)) + sendCommand(ShowOpenUrlDialog(subscription.providerData.refundSupportUrl)) }, contentTitle = Phrase.from(context.getText(R.string.proRefunding)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format().toString(), contentDescription = Phrase.from(context.getText(R.string.proPlanPlatformRefund)) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) - .put(PLATFORM_STORE_KEY, subscription.subscriptionDetails.store) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(PLATFORM_STORE_KEY, subscription.providerData.store) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .format(), linkCellsInfo = stringResource(R.string.refundRequestOptions), linkCells = listOf( NonOriginatingLinkCellData( title = Phrase.from(context.getText(R.string.onDevice)) - .put(DEVICE_TYPE_KEY, subscription.subscriptionDetails.device) + .put(DEVICE_TYPE_KEY, subscription.providerData.device) .format(), info = Phrase.from(context.getText(R.string.proRefundAccountDevice)) .put(APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME) - .put(DEVICE_TYPE_KEY, subscription.subscriptionDetails.device) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(DEVICE_TYPE_KEY, subscription.providerData.device) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) .format(), iconRes = R.drawable.ic_smartphone ), NonOriginatingLinkCellData( title = Phrase.from(context.getText(R.string.onPlatformWebsite)) - .put(PLATFORM_KEY, subscription.subscriptionDetails.platform) + .put(PLATFORM_KEY, subscription.providerData.platform) .format(), info = Phrase.from(context.getText(R.string.requestRefundPlatformWebsite)) - .put(PLATFORM_KEY, subscription.subscriptionDetails.platform) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(PLATFORM_KEY, subscription.providerData.platform) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), iconRes = R.drawable.ic_globe diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt index 48ada584ff..3738b73461 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanScreen.kt @@ -51,7 +51,7 @@ fun RefundPlanScreen( // there are different UI depending on the state when { // there is an active subscription but from a different platform - activePlan.subscriptionDetails.isFromAnotherPlatform() -> + activePlan.providerData.isFromAnotherPlatform() -> RefundPlanNonOriginating( subscription = activePlan, sendCommand = viewModel::onCommand, @@ -85,7 +85,7 @@ fun RefundPlan( disabled = true, onBack = onBack, buttonText = if(isQuickRefund) Phrase.from(context.getText(R.string.openPlatformWebsite)) - .put(PLATFORM_KEY, data.subscriptionDetails.platform) + .put(PLATFORM_KEY, data.providerData.platform) .format().toString() else stringResource(R.string.requestRefund), dangerButton = true, @@ -93,7 +93,7 @@ fun RefundPlan( if(isQuickRefund && !quickRefundUrl.isNullOrEmpty()){ sendCommand(ShowOpenUrlDialog(quickRefundUrl)) } else { - sendCommand(ShowOpenUrlDialog(data.subscriptionDetails.refundSupportUrl)) + sendCommand(ShowOpenUrlDialog(data.providerData.refundSupportUrl)) } }, title = stringResource(R.string.proRefundDescription), @@ -113,7 +113,7 @@ fun RefundPlan( text = annotatedStringResource( if(isQuickRefund) Phrase.from(context.getText(R.string.proRefundRequestStorePolicies)) - .put(PLATFORM_KEY, data.subscriptionDetails.platform) + .put(PLATFORM_KEY, data.providerData.platform) .put(APP_NAME_KEY, context.getString(R.string.app_name)) .format() else Phrase.from(context.getText(R.string.proRefundRequestSessionSupport)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt index 74c799872d..4e8d1d3922 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanHomeScreen.kt @@ -29,7 +29,7 @@ fun ChoosePlanHomeScreen( // there is an active subscription but from a different platform or from the // same platform but a different account // or we have no billing APIs - subscription.subscriptionDetails.isFromAnotherPlatform() + subscription.providerData.isFromAnotherPlatform() || !planData.hasValidSubscription || !planData.hasBillingCapacity -> ChoosePlanNonOriginating( diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt index 098600ef3e..0c1a4be12b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt @@ -172,11 +172,11 @@ fun ChoosePlanNoBilling( add( NonOriginatingLinkCellData( title = Phrase.from(context.getText(R.string.onPlatformStoreWebsite)) - .put(PLATFORM_STORE_KEY, subscription.subscriptionDetails.getPlatformDisplayName()) + .put(PLATFORM_STORE_KEY, subscription.providerData.getPlatformDisplayName()) .format(), info = Phrase.from(context.getText(R.string.proAccessRenewPlatformWebsite)) - .put(PLATFORM_KEY, subscription.subscriptionDetails.getPlatformDisplayName()) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(PLATFORM_KEY, subscription.providerData.getPlatformDisplayName()) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), iconRes = R.drawable.ic_globe @@ -191,13 +191,13 @@ fun ChoosePlanNoBilling( onBack = onBack, headerTitle = headerTitle, buttonText = if(subscription is ProStatus.Expired) Phrase.from(context.getText(R.string.openPlatformWebsite)) - .put(PLATFORM_KEY, subscription.subscriptionDetails.getPlatformDisplayName()) + .put(PLATFORM_KEY, subscription.providerData.getPlatformDisplayName()) .format().toString() else null, dangerButton = false, onButtonClick = { if(subscription is ProStatus.Expired) { - sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.updateSubscriptionUrl)) + sendCommand(ShowOpenUrlDialog(subscription.providerData.updateSubscriptionUrl)) } }, contentTitle = contentTitle, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt index 1a96c9ed14..e262b7a669 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNonOriginating.kt @@ -41,7 +41,7 @@ fun ChoosePlanNonOriginating( ){ val context = LocalContext.current - val platformOverride = subscription.subscriptionDetails.getPlatformDisplayName() + val platformOverride = subscription.providerData.getPlatformDisplayName() val headerTitle = when(subscription) { is ProStatus.Active.Expiring -> Phrase.from(context.getText(R.string.proAccessExpireDate)) @@ -71,15 +71,15 @@ fun ChoosePlanNonOriginating( .format().toString(), dangerButton = false, onButtonClick = { - sendCommand(ShowOpenUrlDialog(subscription.subscriptionDetails.updateSubscriptionUrl)) + sendCommand(ShowOpenUrlDialog(subscription.providerData.updateSubscriptionUrl)) }, contentTitle = Phrase.from(LocalContext.current, R.string.updateAccess) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format().toString(), contentDescription = Phrase.from(context.getText(R.string.proAccessSignUp)) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) - .put(PLATFORM_STORE_KEY, subscription.subscriptionDetails.store) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(PLATFORM_STORE_KEY, subscription.providerData.store) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), linkCellsInfo = Phrase.from(context.getText(R.string.updateAccessTwo)) @@ -88,12 +88,12 @@ fun ChoosePlanNonOriginating( linkCells = listOf( NonOriginatingLinkCellData( title = Phrase.from(context.getText(R.string.onDevice)) - .put(DEVICE_TYPE_KEY, subscription.subscriptionDetails.device) + .put(DEVICE_TYPE_KEY, subscription.providerData.device) .format(), info = Phrase.from(context.getText(R.string.onDeviceDescription)) .put(APP_NAME_KEY, NonTranslatableStringConstants.APP_NAME) - .put(DEVICE_TYPE_KEY, subscription.subscriptionDetails.device) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(DEVICE_TYPE_KEY, subscription.providerData.device) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), @@ -104,7 +104,7 @@ fun ChoosePlanNonOriginating( .put(PLATFORM_KEY, platformOverride) .format(), info = Phrase.from(context.getText(R.string.viaStoreWebsiteDescription)) - .put(PLATFORM_ACCOUNT_KEY, subscription.subscriptionDetails.platformAccount) + .put(PLATFORM_ACCOUNT_KEY, subscription.providerData.platformAccount) .put(PLATFORM_STORE_KEY, platformOverride) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .format(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt index 71446741c7..c7e16f622e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProDataMapper.kt @@ -22,14 +22,14 @@ fun ProDetails.toProStatus(): ProStatus { ProStatus.Active.AutoRenewing( validUntil = expiry!!, duration = paymentItem.planDuration.toSubscriptionDuration(), - subscriptionDetails = paymentItem.paymentProvider.getMetadata(), + providerData = paymentItem.paymentProvider.getMetadata(), quickRefundExpiry = paymentItem.platformExpiry ) } else { ProStatus.Active.Expiring( validUntil = expiry!!, duration = paymentItem.planDuration.toSubscriptionDuration(), - subscriptionDetails = paymentItem.paymentProvider.getMetadata(), + providerData = paymentItem.paymentProvider.getMetadata(), quickRefundExpiry = paymentItem.platformExpiry ) } @@ -37,7 +37,7 @@ fun ProDetails.toProStatus(): ProStatus { ProDetails.DETAILS_STATUS_EXPIRED -> ProStatus.Expired( expiredAt = expiry!!, - subscriptionDetails = paymentItems.first().paymentProvider.getMetadata() + providerData = paymentItems.first().paymentProvider.getMetadata() ) else -> ProStatus.NeverSubscribed @@ -93,13 +93,13 @@ val previewAppleMetaData = PaymentProviderMetadata( val previewAutoRenewingApple = ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = previewAppleMetaData, + providerData = previewAppleMetaData, quickRefundExpiry = Instant.now() + Duration.ofDays(14) ) val previewExpiredApple = ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = previewAppleMetaData + providerData = previewAppleMetaData ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt index 1cddfdf690..5060e5e96a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.pro import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata -import org.thoughtcrime.securesms.pro.api.ProDetails import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration import org.thoughtcrime.securesms.util.State import java.time.Instant @@ -12,20 +11,20 @@ sealed interface ProStatus{ sealed interface Active: ProStatus{ val validUntil: Instant val duration: ProSubscriptionDuration - val subscriptionDetails: PaymentProviderMetadata + val providerData: PaymentProviderMetadata val quickRefundExpiry: Instant? data class AutoRenewing( override val validUntil: Instant, override val duration: ProSubscriptionDuration, - override val subscriptionDetails: PaymentProviderMetadata, + override val providerData: PaymentProviderMetadata, override val quickRefundExpiry: Instant? ): Active data class Expiring( override val validUntil: Instant, override val duration: ProSubscriptionDuration, - override val subscriptionDetails: PaymentProviderMetadata, + override val providerData: PaymentProviderMetadata, override val quickRefundExpiry: Instant? ): Active @@ -38,7 +37,7 @@ sealed interface ProStatus{ data class Expired( val expiredAt: Instant, - val subscriptionDetails: PaymentProviderMetadata + val providerData: PaymentProviderMetadata ): ProStatus } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt index 84716a332a..55c2cbb49f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt @@ -94,49 +94,49 @@ class ProStatusManager @Inject constructor( DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE -> ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.THREE_MONTHS, - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE -> ProStatus.Active.Expiring( validUntil = Instant.now() + Duration.ofDays(2), duration = ProSubscriptionDuration.TWELVE_MONTHS, - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE_LATER -> ProStatus.Active.Expiring( validUntil = Instant.now() + Duration.ofDays(40), duration = ProSubscriptionDuration.TWELVE_MONTHS, - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!!, quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.AUTO_APPLE -> ProStatus.Active.AutoRenewing( validUntil = Instant.now() + Duration.ofDays(14), duration = ProSubscriptionDuration.ONE_MONTH, - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!!, + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!!, quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_APPLE -> ProStatus.Active.Expiring( validUntil = Instant.now() + Duration.ofDays(2), duration = ProSubscriptionDuration.ONE_MONTH, - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!!, + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!!, quickRefundExpiry = Instant.now() + Duration.ofDays(7) ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED -> ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_EARLIER -> ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(60), - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_GOOGLE_PLAY)!! ) DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_APPLE -> ProStatus.Expired( expiredAt = Instant.now() - Duration.ofDays(14), - subscriptionDetails = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!! + providerData = BackendRequests.getPaymentProviderMetadata(PAYMENT_PROVIDER_APP_STORE)!! ) },