Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ec660d1
Making sure we do not show the loader if the purchase fails right away
ThomasSession Oct 27, 2025
32b457f
UI Update to match designs
ThomasSession Oct 28, 2025
e7a2c1b
More state and UI handling to match designs
ThomasSession Oct 28, 2025
0f5a606
Merge branch 'dev' into feature/more-pro-states
ThomasSession Oct 28, 2025
9fc05e7
Merge branch 'dev' into feature/more-pro-states
ThomasSession Oct 29, 2025
be0d710
giving subscription state to CTA to display dynamic content
ThomasSession Oct 29, 2025
9a594d6
Merge branch 'dev' into feature/more-pro-states
ThomasSession Oct 30, 2025
054e01e
Rely on subscription state instead of simple boolean for Pro status
ThomasSession Oct 30, 2025
fa92657
Renaming subscription methods in preparation for price calculation
ThomasSession Oct 30, 2025
150ca71
Added State management to the choose plan and cancel data
ThomasSession Oct 30, 2025
760a693
New debug toggle for quick refunds
ThomasSession Oct 30, 2025
63c3d0c
Making sure refund also handle its data within a State
ThomasSession Oct 31, 2025
e6134d7
Adding price calculation and formatting
ThomasSession Oct 31, 2025
556cbd1
Formatting total to match
ThomasSession Oct 31, 2025
8d9e34f
Do not apply debug setting if we are not forcing the user as pro
ThomasSession Oct 31, 2025
85ada7d
Fixing old component to use crossfade for better transition
ThomasSession Oct 31, 2025
0e3a987
Removing suffix for QA + PR feedback
ThomasSession Nov 3, 2025
2e77128
PR feedback
ThomasSession Nov 3, 2025
61c05e5
Merge branch 'dev' into feature/more-pro-states
ThomasSession Nov 3, 2025
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
3 changes: 1 addition & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,12 @@ android {
matchingFallbacks += "release"

signingConfig = signingConfigs.getByName("debug")
applicationIdSuffix = ".$name"

devNetDefaultOn(false)
enablePermissiveNetworkSecurityConfig(true)

setAlternativeAppName("Session QA")
setAuthorityPostfix(".qa")
setAuthorityPostfix("")
}

create("automaticQa") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ interface TextSecurePreferences {
fun setDebugProPlanStatus(status: DebugMenuViewModel.DebugProPlanStatus?)
fun getDebugForceNoBilling(): Boolean
fun setDebugForceNoBilling(hasBilling: Boolean)
fun getDebugIsWithinQuickRefund(): Boolean
fun setDebugIsWithinQuickRefund(isWithin: Boolean)

fun setSubscriptionProvider(provider: String)
fun getSubscriptionProvider(): String?
Expand Down Expand Up @@ -388,6 +390,7 @@ interface TextSecurePreferences {
const val DEBUG_SUBSCRIPTION_STATUS = "debug_subscription_status"
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 SUBSCRIPTION_PROVIDER = "session_subscription_provider"
const val DEBUG_AVATAR_REUPLOAD = "debug_avatar_reupload"
Expand Down Expand Up @@ -1799,6 +1802,15 @@ 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 setDebugIsWithinQuickRefund(isWithin: Boolean) {
setBooleanPreference(TextSecurePreferences.DEBUG_WITHIN_QUICK_REFUND, isWithin)
_events.tryEmit(TextSecurePreferences.DEBUG_FORCE_NO_BILLING)
}

override fun getSubscriptionProvider(): String? {
return getStringPreference(TextSecurePreferences.SUBSCRIPTION_PROVIDER, null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ fun InputBarDialogs(
}

// Pro CTA
if (inputBarDialogsState.sessionProCharLimitCTA) {
if (inputBarDialogsState.sessionProCharLimitCTA != null) {
LongMessageProCTA(
proSubscription = inputBarDialogsState.sessionProCharLimitCTA.proSubscription,
onDismissRequest = {sendCommand(InputbarViewModel.Commands.HideSessionProCTA)}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.session.libsession.utilities.recipients.isPro
import org.session.libsession.utilities.recipients.shouldShowProBadge
import org.thoughtcrime.securesms.database.RecipientRepository
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.pro.SubscriptionType
import org.thoughtcrime.securesms.ui.SimpleDialogData
import org.thoughtcrime.securesms.util.NumberUtil

Expand Down Expand Up @@ -94,7 +95,7 @@ abstract class InputbarViewModel(

fun showSessionProCTA(){
_inputBarStateDialogsState.update {
it.copy(sessionProCharLimitCTA = true)
it.copy(sessionProCharLimitCTA = CharLimitCTAData(proStatusManager.subscriptionState.value.type))
}
}

Expand Down Expand Up @@ -165,7 +166,7 @@ abstract class InputbarViewModel(

is Commands.HideSessionProCTA -> {
_inputBarStateDialogsState.update {
it.copy(sessionProCharLimitCTA = false)
it.copy(sessionProCharLimitCTA = null)
}
}
}
Expand Down Expand Up @@ -195,7 +196,11 @@ abstract class InputbarViewModel(

data class InputBarDialogsState(
val showSimpleDialog: SimpleDialogData? = null,
val sessionProCharLimitCTA: Boolean = false
val sessionProCharLimitCTA: CharLimitCTAData? = null
)

data class CharLimitCTAData(
val proSubscription: SubscriptionType
)

sealed interface Commands {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -706,13 +706,22 @@ fun MessageDetailDialogs(
if(state.proBadgeCTA != null){
when(state.proBadgeCTA){
is ProBadgeCTA.Generic ->
GenericProCTA(onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)})
GenericProCTA(
proSubscription = state.proBadgeCTA.proSubscription,
onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)}
)

is ProBadgeCTA.LongMessage ->
LongMessageProCTA(onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)})
LongMessageProCTA(
proSubscription = state.proBadgeCTA.proSubscription,
onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)}
)

is ProBadgeCTA.AnimatedProfile ->
AnimatedProfilePicProCTA(onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)})
AnimatedProfilePicProCTA(
proSubscription = state.proBadgeCTA.proSubscription,
onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)}
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.pro.ProStatusManager.MessageProFeature.AnimatedAvatar
import org.thoughtcrime.securesms.pro.ProStatusManager.MessageProFeature.LongMessage
import org.thoughtcrime.securesms.pro.SubscriptionType
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.TitledText
import org.thoughtcrime.securesms.util.AvatarUIData
Expand Down Expand Up @@ -283,13 +284,14 @@ class MessageDetailsViewModel @AssistedInject constructor(
is Commands.ShowProBadgeCTA -> {
val features = state.value.proFeatures
_dialogState.update {
val proSubscription = proStatusManager.subscriptionState.value.type
it.copy(
proBadgeCTA = when{
features.size > 1 -> ProBadgeCTA.Generic // always show the generic cta when there are more than 1 feature
features.size > 1 -> ProBadgeCTA.Generic(proSubscription) // always show the generic cta when there are more than 1 feature

features.contains(LongMessage) -> ProBadgeCTA.LongMessage
features.contains(AnimatedAvatar) -> ProBadgeCTA.AnimatedProfile
else -> ProBadgeCTA.Generic
features.contains(LongMessage) -> ProBadgeCTA.LongMessage(proSubscription)
features.contains(AnimatedAvatar) -> ProBadgeCTA.AnimatedProfile(proSubscription)
else -> ProBadgeCTA.Generic(proSubscription)
}
)
}
Expand Down Expand Up @@ -369,10 +371,10 @@ data class MessageDetailsState(
val canDelete: Boolean get() = !readOnly
}

sealed interface ProBadgeCTA {
data object Generic: ProBadgeCTA
data object LongMessage: ProBadgeCTA
data object AnimatedProfile: ProBadgeCTA
sealed class ProBadgeCTA(open val proSubscription: SubscriptionType) {
data class Generic(override val proSubscription: SubscriptionType): ProBadgeCTA(proSubscription)
data class LongMessage(override val proSubscription: SubscriptionType): ProBadgeCTA(proSubscription)
data class AnimatedProfile(override val proSubscription: SubscriptionType): ProBadgeCTA(proSubscription)
}

data class DialogsState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsV
import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsViewModel.Commands.UpdateGroupDescription
import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsViewModel.Commands.UpdateGroupName
import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsViewModel.Commands.UpdateNickname
import org.thoughtcrime.securesms.pro.SubscriptionType
import org.thoughtcrime.securesms.ui.AlertDialog
import org.thoughtcrime.securesms.ui.CTAImage
import org.thoughtcrime.securesms.ui.DialogButtonData
Expand Down Expand Up @@ -232,6 +233,7 @@ fun ConversationSettingsDialogs(
// pin CTA
if(dialogsState.pinCTA != null){
PinProCTA(
proSubscription = dialogsState.pinCTA.proSubscription,
overTheLimit = dialogsState.pinCTA.overTheLimit,
onDismissRequest = {
sendCommand(HidePinCTADialog)
Expand All @@ -242,6 +244,7 @@ fun ConversationSettingsDialogs(
when(dialogsState.proBadgeCTA){
is ConversationSettingsViewModel.ProBadgeCTA.Generic -> {
GenericProCTA(
proSubscription = dialogsState.proBadgeCTA.proSubscription,
onDismissRequest = {
sendCommand(HideProBadgeCTA)
}
Expand Down Expand Up @@ -438,7 +441,7 @@ fun PreviewCTAGroupDialog() {
PreviewTheme {
ConversationSettingsDialogs(
dialogsState = ConversationSettingsViewModel.DialogsState(
proBadgeCTA = ConversationSettingsViewModel.ProBadgeCTA.Group
proBadgeCTA = ConversationSettingsViewModel.ProBadgeCTA.Group(SubscriptionType.NeverSubscribed)
),
sendCommand = {}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory.Companion.MAX_NAME_
import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.home.HomeActivity
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.pro.SubscriptionType
import org.thoughtcrime.securesms.repository.ConversationRepository
import org.thoughtcrime.securesms.ui.SimpleDialogData
import org.thoughtcrime.securesms.ui.UINavigator
Expand Down Expand Up @@ -719,7 +720,10 @@ class ConversationSettingsViewModel @AssistedInject constructor(
if(totalPins >= maxPins){
// the user has reached the pin limit, show the CTA
_dialogState.update {
it.copy(pinCTA = PinProCTA(overTheLimit = totalPins > maxPins))
it.copy(pinCTA = PinProCTA(
overTheLimit = totalPins > maxPins,
proSubscription = proStatusManager.subscriptionState.value.type
))
}
} else {
viewModelScope.launch {
Expand Down Expand Up @@ -1236,8 +1240,8 @@ class ConversationSettingsViewModel @AssistedInject constructor(
is Commands.ShowProBadgeCTA -> {
_dialogState.update {
it.copy(
proBadgeCTA = if(recipient?.isGroupV2Recipient == true) ProBadgeCTA.Group
else ProBadgeCTA.Generic
proBadgeCTA = if(recipient?.isGroupV2Recipient == true) ProBadgeCTA.Group(proStatusManager.subscriptionState.value.type)
else ProBadgeCTA.Generic(proStatusManager.subscriptionState.value.type)
)
}
}
Expand Down Expand Up @@ -1441,12 +1445,13 @@ class ConversationSettingsViewModel @AssistedInject constructor(
)

data class PinProCTA(
val overTheLimit: Boolean
val overTheLimit: Boolean,
val proSubscription: SubscriptionType
)

sealed interface ProBadgeCTA {
data object Generic: ProBadgeCTA
data object Group: ProBadgeCTA
sealed class ProBadgeCTA(open val proSubscription: SubscriptionType) {
data class Generic(override val proSubscription: SubscriptionType): ProBadgeCTA(proSubscription)
data class Group(override val proSubscription: SubscriptionType): ProBadgeCTA(proSubscription)
}

data class NicknameDialogData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,15 @@ fun DebugMenu(
)
}
)

Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
DebugSwitchRow(
text = "Is Within Quick Refund Window",
checked = uiState.withinQuickRefund,
onCheckedChange = {
sendCommand(DebugMenuViewModel.Commands.WithinQuickRefund(it))
}
)
}
}

Expand Down Expand Up @@ -831,6 +840,7 @@ fun PreviewDebugMenu() {
selectedDebugProPlanStatus = DebugMenuViewModel.DebugProPlanStatus.NORMAL,
debugProPlans = emptyList(),
forceNoBilling = false,
withinQuickRefund = true,
forceDeterministicEncryption = false,
debugAvatarReupload = true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class DebugMenuViewModel @Inject constructor(
.flatMap { it.availablePlans.asSequence().map { plan -> DebugProPlan(it, plan) } }
.toList(),
forceNoBilling = textSecurePreferences.getDebugForceNoBilling(),
withinQuickRefund = textSecurePreferences.getDebugIsWithinQuickRefund(),
availableAltFileServers = TEST_FILE_SERVERS,
alternativeFileServer = textSecurePreferences.alternativeFileServer,
)
Expand Down Expand Up @@ -285,6 +286,13 @@ class DebugMenuViewModel @Inject constructor(
}
}

is Commands.WithinQuickRefund -> {
textSecurePreferences.setDebugIsWithinQuickRefund(command.set)
_uiState.update {
it.copy(withinQuickRefund = command.set)
}
}

is Commands.ForcePostPro -> {
textSecurePreferences.setForcePostPro(command.set)
_uiState.update {
Expand Down Expand Up @@ -333,7 +341,9 @@ class DebugMenuViewModel @Inject constructor(
}

is Commands.PurchaseDebugPlan -> {
command.plan.apply { manager.purchasePlan(plan) }
viewModelScope.launch {
command.plan.apply { manager.purchasePlan(plan) }
}
}

is Commands.ToggleDeterministicEncryption -> {
Expand Down Expand Up @@ -469,6 +479,7 @@ class DebugMenuViewModel @Inject constructor(
val selectedDebugProPlanStatus: DebugProPlanStatus,
val debugProPlans: List<DebugProPlan>,
val forceNoBilling: Boolean,
val withinQuickRefund: Boolean,
val alternativeFileServer: FileServer? = null,
val availableAltFileServers: List<FileServer> = emptyList(),
)
Expand Down Expand Up @@ -509,6 +520,7 @@ class DebugMenuViewModel @Inject constructor(
data class ForceOtherUsersAsPro(val set: Boolean) : Commands()
data class ForceIncomingMessagesAsPro(val set: Boolean) : Commands()
data class ForceNoBilling(val set: Boolean) : Commands()
data class WithinQuickRefund(val set: Boolean) : Commands()
data class ForcePostPro(val set: Boolean) : Commands()
data class ForceShortTTl(val set: Boolean) : Commands()
data class SetMessageProFeature(val feature: ProStatusManager.MessageProFeature, val set: Boolean) : Commands()
Expand Down
14 changes: 3 additions & 11 deletions app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
package org.thoughtcrime.securesms.home

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.squareup.phrase.Phrase
import kotlinx.coroutines.delay
import network.loki.messenger.R
import org.session.libsession.utilities.NonTranslatableStringConstants
import org.session.libsession.utilities.NonTranslatableStringConstants.PRO
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
import org.thoughtcrime.securesms.home.HomeViewModel.Commands.HandleUserProfileCommand
import org.thoughtcrime.securesms.home.HomeViewModel.Commands.HidePinCTADialog
import org.thoughtcrime.securesms.home.HomeViewModel.Commands.HideUserProfileModal
import org.thoughtcrime.securesms.home.startconversation.StartConversationDestination
import org.thoughtcrime.securesms.home.startconversation.StartConversationSheet
import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsDestination
import org.thoughtcrime.securesms.ui.AnimatedSessionProCTA
import org.thoughtcrime.securesms.ui.CTAFeature
import org.thoughtcrime.securesms.ui.PinProCTA
import org.thoughtcrime.securesms.ui.UINavigator
import org.thoughtcrime.securesms.ui.UserProfileModal
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme

@Composable
Expand All @@ -45,6 +36,7 @@ fun HomeDialogs(
if(dialogsState.pinCTA != null){
PinProCTA(
overTheLimit = dialogsState.pinCTA.overTheLimit,
proSubscription = dialogsState.pinCTA.proSubscription,
onDismissRequest = {
sendCommand(HidePinCTADialog)
}
Expand Down Expand Up @@ -105,7 +97,7 @@ fun HomeDialogs(
negativeButtonText = stringResource(R.string.close),
onUpgrade = {
sendCommand(HomeViewModel.Commands.HideExpiringCTADialog)
sendCommand(HomeViewModel.Commands.GotoProSettings(ProSettingsDestination.UpdatePlan))
sendCommand(HomeViewModel.Commands.GotoProSettings(ProSettingsDestination.ChoosePlan))
},
onCancel = {
sendCommand(HomeViewModel.Commands.HideExpiringCTADialog)
Expand Down Expand Up @@ -146,7 +138,7 @@ fun HomeDialogs(
negativeButtonText = stringResource(R.string.cancel),
onUpgrade = {
sendCommand(HomeViewModel.Commands.HideExpiredCTADialog)
sendCommand(HomeViewModel.Commands.GotoProSettings(ProSettingsDestination.GetOrRenewPlan))
sendCommand(HomeViewModel.Commands.GotoProSettings(ProSettingsDestination.ChoosePlan))
},
onCancel = {
sendCommand(HomeViewModel.Commands.HideExpiredCTADialog)
Expand Down
Loading