Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,60 @@ interface TextSecurePreferences {
fun setDebugProPlanStatus(status: DebugMenuViewModel.DebugProPlanStatus?)
fun getDebugForceNoBilling(): Boolean
fun setDebugForceNoBilling(hasBilling: Boolean)
fun getDebugIsWithinQuickRefund(): Boolean
fun setDebugIsWithinQuickRefund(isWithin: Boolean)
/**
* Mocked quick-refund window: `true` forces it open, `false` forces it closed, `null` for no override
* so the fixture's own window stands.
*
* Tri-state because every debug fixture sets the window open; a plain boolean defaulting to false
* would close it for every existing test. Applied by moving `quickRefundExpiry`, which is the single
* representation of the window — see `ProStatusManager.withMockedQuickRefundWindow`.
*/
fun getDebugQuickRefundWindowOverride(): Boolean?
fun setDebugQuickRefundWindowOverride(isWithin: Boolean?)

/**
* Mocked refund-pending override: `true` forces a refund in progress, `false` forces none, `null`
* means no override so the real state governs.
*
* Tri-state as [getDebugProAccessOverride] is: the real state is a synced config flag another device
* can have set, so `false` and `null` differ. Orthogonal to [getDebugSubscriptionType] so it composes
* with any fixture.
*/
fun getDebugRefundInProgressOverride(): Boolean?
fun setDebugRefundInProgressOverride(refunding: Boolean?)

/**
* Mocked originating payment provider (a `BackendRequests.PAYMENT_PROVIDER_*` slug), or `null` for no
* override so the fixture's own provider stands.
*
* This is Android's equivalent of iOS's `mockCurrentUserSessionProOriginatingPlatform`: the provider
* slug is what every "was this bought elsewhere" decision reads, via
* `PaymentProviderMetadata.isFromAnotherPlatform`.
*/
fun getDebugOriginatingProvider(): String?
fun setDebugOriginatingProvider(providerSlug: String?)

/**
* Mocked auto-renewing override: `true` forces a renewing plan, `false` forces one that runs to its
* end date, `null` for no override.
*
* The flag the "Pro auto-renewing in {time}" line, the renewal-unsuccessful state and the Cancel Pro
* Access action all read. Tri-state like the others so "force not renewing" and "do not mock" stay
* distinguishable.
*/
fun getDebugAutoRenewingOverride(): Boolean?
fun setDebugAutoRenewingOverride(autoRenewing: Boolean?)

/**
* Mocked originating-account override: `true` forces the store account currently signed in to be the
* one that bought the subscription, `false` forces a different one, `null` for no override.
*
* Overrides `SubscriptionManager.hasValidSubscription()`, which is what the screens read as "same
* platform but a different account" — iOS spells the same fact
* `mockCurrentUserOriginatingAccount`.
*/
fun getDebugOriginatingAccountOverride(): Boolean?
fun setDebugOriginatingAccountOverride(isOriginating: Boolean?)

fun setSubscriptionProvider(provider: String)
fun getSubscriptionProvider(): String?
Expand Down Expand Up @@ -399,6 +451,10 @@ interface TextSecurePreferences {
const val DEBUG_PRO_PLAN_STATUS = "debug_pro_plan_status"
const val DEBUG_FORCE_NO_BILLING = "debug_pro_has_billing"
const val DEBUG_WITHIN_QUICK_REFUND = "debug_within_quick_refund"
const val DEBUG_PRO_REFUND_IN_PROGRESS = "debug_pro_refund_in_progress"
const val DEBUG_PRO_ORIGINATING_PROVIDER = "debug_pro_originating_provider"
const val DEBUG_PRO_AUTO_RENEWING = "debug_pro_auto_renewing"
const val DEBUG_PRO_ORIGINATING_ACCOUNT = "debug_pro_originating_account"

const val SUBSCRIPTION_PROVIDER = "session_subscription_provider"
const val DEBUG_AVATAR_REUPLOAD = "debug_avatar_reupload"
Expand Down Expand Up @@ -1298,13 +1354,44 @@ class AppTextSecurePreferences @Inject constructor(
_events.tryEmit(TextSecurePreferences.DEBUG_FORCE_NO_BILLING)
}

override fun getDebugIsWithinQuickRefund(): Boolean {
return getBooleanPreference(TextSecurePreferences.DEBUG_WITHIN_QUICK_REFUND, false)
override fun getDebugOriginatingAccountOverride(): Boolean? =
getStringPreference(TextSecurePreferences.DEBUG_PRO_ORIGINATING_ACCOUNT, null)?.toBooleanStrictOrNull()

override fun setDebugOriginatingAccountOverride(isOriginating: Boolean?) {
setStringPreference(TextSecurePreferences.DEBUG_PRO_ORIGINATING_ACCOUNT, isOriginating?.toString())
_events.tryEmit(TextSecurePreferences.DEBUG_PRO_ORIGINATING_ACCOUNT)
}

override fun setDebugIsWithinQuickRefund(isWithin: Boolean) {
setBooleanPreference(TextSecurePreferences.DEBUG_WITHIN_QUICK_REFUND, isWithin)
_events.tryEmit(TextSecurePreferences.DEBUG_FORCE_NO_BILLING)
override fun getDebugAutoRenewingOverride(): Boolean? =
getStringPreference(TextSecurePreferences.DEBUG_PRO_AUTO_RENEWING, null)?.toBooleanStrictOrNull()

override fun setDebugAutoRenewingOverride(autoRenewing: Boolean?) {
setStringPreference(TextSecurePreferences.DEBUG_PRO_AUTO_RENEWING, autoRenewing?.toString())
_events.tryEmit(TextSecurePreferences.DEBUG_PRO_AUTO_RENEWING)
}

override fun getDebugOriginatingProvider(): String? =
getStringPreference(TextSecurePreferences.DEBUG_PRO_ORIGINATING_PROVIDER, null)

override fun setDebugOriginatingProvider(providerSlug: String?) {
setStringPreference(TextSecurePreferences.DEBUG_PRO_ORIGINATING_PROVIDER, providerSlug)
_events.tryEmit(TextSecurePreferences.DEBUG_PRO_ORIGINATING_PROVIDER)
}

override fun getDebugRefundInProgressOverride(): Boolean? =
getStringPreference(TextSecurePreferences.DEBUG_PRO_REFUND_IN_PROGRESS, null)?.toBooleanStrictOrNull()

override fun setDebugRefundInProgressOverride(refunding: Boolean?) {
setStringPreference(TextSecurePreferences.DEBUG_PRO_REFUND_IN_PROGRESS, refunding?.toString())
_events.tryEmit(TextSecurePreferences.DEBUG_PRO_REFUND_IN_PROGRESS)
}

override fun getDebugQuickRefundWindowOverride(): Boolean? =
getStringPreference(TextSecurePreferences.DEBUG_WITHIN_QUICK_REFUND, null)?.toBooleanStrictOrNull()

override fun setDebugQuickRefundWindowOverride(isWithin: Boolean?) {
setStringPreference(TextSecurePreferences.DEBUG_WITHIN_QUICK_REFUND, isWithin?.toString())
_events.tryEmit(TextSecurePreferences.DEBUG_WITHIN_QUICK_REFUND)
}

override fun getSubscriptionProvider(): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,13 +683,21 @@ class RecipientRepository @Inject constructor(
configFactory.withUserConfigs { configs ->
val pro = configs.userProfile.getProConfig()

if (prefs.forceCurrentUserAsPro()) {
// Honours the tri-state access override first, then the legacy flag. Without this
// the two levers disagree: a `proProof=none` fixture would still be given Pro data
// here whenever the legacy flag happened to be set, and a `proProof=valid` one
// would not be given any unless it was.
if (prefs.getDebugProAccessOverride() ?: prefs.forceCurrentUserAsPro()) {
proDataContext?.addProData(
RecipientSettings.ProData(
showProBadge = configs.userProfile.getProFeatures().contains(
ProProfileFeature.PRO_BADGE
),
expiry = Instant.now().plusSeconds(3600),
// The mocked access expiry when one was set, so this and the Pro state
// report the same date. It used to hard-code an hour from now, which made
// it a second and silently disagreeing source for the same fact.
expiry = prefs.getDebugProAccessExpiry()
?: Instant.now().plusSeconds(3600),
revocationTag = "a1b2c3d4",
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class DebugMenuViewModel @AssistedInject constructor(
.flatMap { it.availablePlans.asSequence().map { plan -> DebugProPlan(it, plan) } }
.toList(),
forceNoBilling = textSecurePreferences.getDebugForceNoBilling(),
withinQuickRefund = textSecurePreferences.getDebugIsWithinQuickRefund(),
withinQuickRefund = textSecurePreferences.getDebugQuickRefundWindowOverride() == true,
availableAltFileServers = TEST_FILE_SERVERS,
alternativeFileServer = textSecurePreferences.alternativeFileServer,
showToastForGroups = getDebugGroupToastPref(),
Expand Down Expand Up @@ -342,7 +342,7 @@ class DebugMenuViewModel @AssistedInject constructor(
}

is Commands.WithinQuickRefund -> {
textSecurePreferences.setDebugIsWithinQuickRefund(command.set)
textSecurePreferences.setDebugQuickRefundWindowOverride(command.set)
_uiState.update {
it.copy(withinQuickRefund = command.set)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.preferences.prosettings

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand Down Expand Up @@ -46,6 +47,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.qaTag
import org.thoughtcrime.securesms.ui.Cell
import org.thoughtcrime.securesms.ui.dialog.DialogBg
import org.thoughtcrime.securesms.ui.SessionProSettingsHeader
Expand Down Expand Up @@ -75,6 +77,8 @@ fun BaseProSettingsScreen(
onBack: () -> Unit,
onHeaderClick: (() -> Unit)? = null,
extraHeaderContent: @Composable (() -> Unit)? = null,
/** Identifies WHICH store-flow screen this is, so a spec can assert the destination a state opens. */
@StringRes screenQaTag: Int? = null,
content: @Composable LazyItemScope.() -> Unit
){
// Calculate scroll fraction
Expand Down Expand Up @@ -123,7 +127,8 @@ fun BaseProSettingsScreen(
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.consumeWindowInsets(paddings),
.consumeWindowInsets(paddings)
.qaTag(screenQaTag),
state = listState,
contentPadding = safeInsetsPadding,
horizontalAlignment = CenterHorizontally
Expand Down Expand Up @@ -153,17 +158,20 @@ fun BaseCellButtonProSettingsScreen(
dangerButton: Boolean,
onButtonClick: () -> Unit,
title: CharSequence? = null,
@StringRes screenQaTag: Int? = null,
content: @Composable LazyItemScope.() -> Unit
) {
BaseProSettingsScreen(
disabled = disabled,
onBack = onBack,
screenQaTag = screenQaTag,
) {
Spacer(Modifier.height(LocalDimensions.current.spacing))

if(!title.isNullOrEmpty()) {
Text(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth()
.qaTag(R.string.qa_pro_screen_header),
text = annotatedStringResource(title),
textAlign = TextAlign.Center,
style = LocalType.current.base,
Expand All @@ -189,14 +197,16 @@ fun BaseCellButtonProSettingsScreen(
if (dangerButton) {
DangerFillButtonRect(
modifier = Modifier.fillMaxWidth()
.widthIn(max = LocalDimensions.current.maxContentWidth),
.widthIn(max = LocalDimensions.current.maxContentWidth)
.qaTag(R.string.qa_pro_screen_action),
text = buttonText,
onClick = onButtonClick
)
} else {
AccentFillButtonRect(
modifier = Modifier.fillMaxWidth()
.widthIn(max = LocalDimensions.current.maxContentWidth),
.widthIn(max = LocalDimensions.current.maxContentWidth)
.qaTag(R.string.qa_pro_screen_action),
text = buttonText,
onClick = onButtonClick
)
Expand Down Expand Up @@ -245,6 +255,7 @@ fun BaseNonOriginatingProSettingsScreen(
contentClick: (() -> Unit)? = null,
linkCellsInfo: String?,
linkCells: List<NonOriginatingLinkCellData> = emptyList(),
@StringRes screenQaTag: Int? = null,
) {
BaseCellButtonProSettingsScreen(
disabled = disabled,
Expand All @@ -253,9 +264,11 @@ fun BaseNonOriginatingProSettingsScreen(
dangerButton = dangerButton,
onButtonClick = onButtonClick,
title = headerTitle,
screenQaTag = screenQaTag,
){
if (contentTitle != null) {
Text(
modifier = Modifier.qaTag(R.string.qa_pro_screen_title),
text = contentTitle,
style = LocalType.current.h7,
color = LocalColors.current.text,
Expand All @@ -265,7 +278,7 @@ fun BaseNonOriginatingProSettingsScreen(
if (contentDescription != null) {
Spacer(Modifier.height(LocalDimensions.current.xxxsSpacing))
Text(
modifier = Modifier.then(
modifier = Modifier.qaTag(R.string.qa_pro_screen_description).then(
// make the component clickable is there is an action
if (contentClick != null) Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
Expand Down Expand Up @@ -313,6 +326,7 @@ fun NonOriginatingLinkCell(
) {
Row(
modifier = Modifier.fillMaxWidth()
.qaTag(data.qaTag)
.padding(LocalDimensions.current.smallSpacing),
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
Expand Down Expand Up @@ -348,6 +362,7 @@ fun NonOriginatingLinkCell(
modifier = Modifier.weight(1f)
) {
Text(
modifier = Modifier.qaTag(data.titleQaTag),
text = annotatedStringResource(data.title),
style = LocalType.current.base.bold(),
color = LocalColors.current.text,
Expand All @@ -356,6 +371,7 @@ fun NonOriginatingLinkCell(
Spacer(Modifier.height(LocalDimensions.current.xxxsSpacing))

Text(
modifier = Modifier.qaTag(data.descriptionQaTag),
text = annotatedStringResource(data.info),
style = LocalType.current.base,
color = LocalColors.current.text,
Expand All @@ -370,7 +386,16 @@ data class NonOriginatingLinkCellData(
val title: CharSequence,
val info: CharSequence,
@DrawableRes val iconRes: Int,
val onClick: (() -> Unit)? = null
val onClick: (() -> Unit)? = null,
/**
* QA ids for this option's cell, title and description.
*
* Named per option rather than generically like the rest of the screen, because two of these are
* shown at once so there is no single "the description" to address.
*/
@StringRes val qaTag: Int? = null,
@StringRes val titleQaTag: Int? = null,
@StringRes val descriptionQaTag: Int? = null,
)

@Preview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fun CancelPlanNonOriginating(
val context = LocalContext.current

BaseNonOriginatingProSettingsScreen(
screenQaTag = R.string.qa_pro_screen_cancel_plan_non_originating,
disabled = true,
onBack = onBack,
headerTitle = Phrase.from(context.getText(R.string.proCancelSorry))
Expand Down Expand Up @@ -58,7 +59,10 @@ fun CancelPlanNonOriginating(
.put(DEVICE_TYPE_KEY, providerData.device)
.put(PLATFORM_ACCOUNT_KEY, providerData.platformAccount)
.format(),
iconRes = R.drawable.ic_smartphone
iconRes = R.drawable.ic_smartphone,
qaTag = R.string.qa_pro_link_cell_device,
titleQaTag = R.string.qa_pro_link_cell_device_title,
descriptionQaTag = R.string.qa_pro_link_cell_device_description,
),
NonOriginatingLinkCellData(
title = Phrase.from(context.getText(R.string.onPlatformWebsite))
Expand All @@ -68,7 +72,10 @@ fun CancelPlanNonOriginating(
.put(PLATFORM_STORE_KEY, providerData.store)
.put(PLATFORM_ACCOUNT_KEY, providerData.platformAccount)
.format(),
iconRes = R.drawable.ic_globe
iconRes = R.drawable.ic_globe,
qaTag = R.string.qa_pro_link_cell_website,
titleQaTag = R.string.qa_pro_link_cell_website_title,
descriptionQaTag = R.string.qa_pro_link_cell_website_description,
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ fun CancelPlan(
}

BaseCellButtonProSettingsScreen(
screenQaTag = R.string.qa_pro_screen_cancel_plan,
disabled = true,
onBack = onBack,
buttonText = Phrase.from(context.getText(R.string.cancelAccess))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.pro.ProDataState
import org.thoughtcrime.securesms.pro.ProStatus
import org.thoughtcrime.securesms.pro.previewAutoRenewingApple
import org.thoughtcrime.securesms.pro.previewExpiredApple
import org.thoughtcrime.securesms.ui.qaTag
import org.thoughtcrime.securesms.ui.SessionProSettingsHeader
import org.thoughtcrime.securesms.ui.components.AccentFillButtonRect
import org.thoughtcrime.securesms.ui.components.annotatedStringResource
Expand Down Expand Up @@ -90,6 +91,7 @@ fun PlanConfirmation(
modifier = Modifier
.fillMaxSize()
.consumeWindowInsets(paddings)
.qaTag(R.string.qa_pro_screen_plan_confirmation)
.padding(
horizontal = LocalDimensions.current.spacing,
)
Expand All @@ -105,7 +107,8 @@ fun PlanConfirmation(
Spacer(Modifier.height(LocalDimensions.current.spacing))

Text(
modifier = Modifier.align(CenterHorizontally),
modifier = Modifier.align(CenterHorizontally)
.qaTag(R.string.qa_pro_screen_title),
text = stringResource(R.string.proAllSet),
style = LocalType.current.h6,
color = LocalColors.current.text,
Expand Down Expand Up @@ -133,7 +136,8 @@ fun PlanConfirmation(

Text(
modifier = Modifier.align(CenterHorizontally)
.safeContentWidth(),
.safeContentWidth()
.qaTag(R.string.qa_pro_screen_description),
text = annotatedStringResource(description),
textAlign = TextAlign.Center,
style = LocalType.current.base,
Expand All @@ -154,7 +158,8 @@ fun PlanConfirmation(

AccentFillButtonRect(
modifier = Modifier.fillMaxWidth()
.widthIn(max = LocalDimensions.current.maxContentWidth),
.widthIn(max = LocalDimensions.current.maxContentWidth)
.qaTag(R.string.qa_pro_screen_action),
text = buttonLabel,
onClick = {
sendCommand(ProSettingsViewModel.Commands.OnPostPlanConfirmation)
Expand Down
Loading
Loading