Skip to content
Merged
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 @@ -133,6 +133,15 @@ class MessageSender @Inject constructor(
}
}

// Pre-launch no proof is ever attached, so a rotating-key signature would vouch for nothing
private fun proRotatingEd25519PrivKey(): ByteArray? {
if (!proStatusManager.get().isPostPro()) return null

return configFactory.withUserConfigs { configs ->
configs.userProfile.getProConfig()
}?.rotatingPrivateKey?.data
}

private fun buildProto(msg: Message): SessionProtos.Content {
try {
val builder = SessionProtos.Content.newBuilder()
Expand Down Expand Up @@ -207,9 +216,7 @@ class MessageSender @Inject constructor(
throw Error.InvalidMessage()
}

val proRotatingEd25519PrivKey = configFactory.withUserConfigs { configs ->
configs.userProfile.getProConfig()
}?.rotatingPrivateKey?.data
val proRotatingEd25519PrivKey = proRotatingEd25519PrivKey()

val messagePlaintext = buildProto(message).toByteArray()

Expand Down Expand Up @@ -391,9 +398,7 @@ class MessageSender @Inject constructor(
}
val plaintext = SessionProtocol.encodeForCommunity(
plaintext = content.toByteArray(),
proRotatingEd25519PrivKey = configFactory.withUserConfigs { configs ->
configs.userProfile.getProConfig()
}?.rotatingPrivateKey?.data,
proRotatingEd25519PrivKey = proRotatingEd25519PrivKey(),
)

val openGroupMessage = OpenGroupMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.SEND_WIT
import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_CURRENT_USER_PRO
import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_INCOMING_MESSAGE_PRO
import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_OTHER_USERS_PRO
import org.session.libsession.utilities.TextSecurePreferences.Companion.SET_FORCE_POST_PRO
import org.session.libsession.utilities.TextSecurePreferences.Companion.SHOWN_CALL_NOTIFICATION
import org.session.libsession.utilities.TextSecurePreferences.Companion.SHOWN_CALL_WARNING
import org.session.libsession.utilities.TextSecurePreferences.Companion.SHOW_DONATION_CTA_FROM_POSITIVE_REVIEW
Expand Down Expand Up @@ -158,11 +159,14 @@ interface TextSecurePreferences {
fun setForceOtherUsersAsPro(isPro: Boolean)
fun forceIncomingMessagesAsPro(): Boolean
fun setForceIncomingMessagesAsPro(isPro: Boolean)
fun forcePostPro(): Boolean
fun setForcePostPro(postPro: Boolean)
fun hasSeenProExpiring(): Boolean
fun setHasSeenProExpiring()
fun hasSeenProExpired(): Boolean
fun setHasSeenProExpired()
fun clearProExpiryView()
fun watchPostProStatus(): StateFlow<Boolean>
fun hasSeenSlowModeCallWarning(): Boolean
fun setHasSeenSlowModeCallWarning(value: Boolean)
fun setShownCallWarning(): Boolean
Expand Down Expand Up @@ -394,6 +398,7 @@ interface TextSecurePreferences {
const val SET_FORCE_PRO_REVOCATION_REFRESH = "pref_force_pro_revocation_refresh"
const val SET_FORCE_OTHER_USERS_PRO = "pref_force_other_users_pro"
const val SET_FORCE_INCOMING_MESSAGE_PRO = "pref_force_incoming_message_pro"
const val SET_FORCE_POST_PRO = "pref_force_post_pro"
const val HAS_SEEN_PRO_EXPIRING = "has_seen_pro_expiring"
const val HAS_SEEN_PRO_EXPIRED = "has_seen_pro_expired"
const val SHOWN_SLOW_MODE_CALL_WARNING = "has_seen_slow_mode_call_warning"
Expand Down Expand Up @@ -669,6 +674,9 @@ class AppTextSecurePreferences @Inject constructor(
@param:ApplicationContext private val context: Context,
private val json: Json,
): TextSecurePreferences {
// The master gate for Session Pro. Off, this account can neither use nor buy Pro, while other people's Pro
// (badges, longer messages, animated avatars) is still honoured.
private val postProLaunchState = MutableStateFlow(getBooleanPreference(SET_FORCE_POST_PRO, false))
private val hiddenPasswordState = MutableStateFlow(getBooleanPreference(HIDE_PASSWORD, false))

override var migratedToGroupV2Config: Boolean
Expand Down Expand Up @@ -1127,6 +1135,16 @@ class AppTextSecurePreferences @Inject constructor(
setBooleanPreference(SET_FORCE_INCOMING_MESSAGE_PRO, isPro)
}

override fun forcePostPro(): Boolean {
return postProLaunchState.value
}

override fun setForcePostPro(postPro: Boolean) {
setBooleanPreference(SET_FORCE_POST_PRO, postPro)
postProLaunchState.update { postPro }
_events.tryEmit(SET_FORCE_POST_PRO)
}

override fun hasSeenProExpiring(): Boolean {
return getBooleanPreference(HAS_SEEN_PRO_EXPIRING, false)
}
Expand All @@ -1148,6 +1166,10 @@ class AppTextSecurePreferences @Inject constructor(
setBooleanPreference(HAS_SEEN_PRO_EXPIRING, false)
}

override fun watchPostProStatus(): StateFlow<Boolean> {
return postProLaunchState
}

override fun getFingerprintKeyGenerated(): Boolean {
return getBooleanPreference(TextSecurePreferences.FINGERPRINT_KEY_GENERATED, false)
}
Expand Down Expand Up @@ -1251,6 +1273,7 @@ class AppTextSecurePreferences @Inject constructor(
* Clear all prefs and reset our observables
*/
override fun clearAll() {
postProLaunchState.update { false }
hiddenPasswordState.update { false }

getDefaultSharedPreferences(context).edit(commit = true) { clear() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ abstract class InputbarViewModel(
// subscriber whose proof has not arrived is correctly held to the standard limit by
// ACCESS above, and inviting them to buy what they already pay for is a different
// question with a different answer.
showProBadge = proStatusManager.proDataState.value.type !is ProStatus.Active
showProBadge = proStatusManager.isPostPro() &&
proStatusManager.proDataState.value.type !is ProStatus.Active
)
} else {
null
Expand Down Expand Up @@ -113,7 +114,7 @@ abstract class InputbarViewModel(
// that is ACCESS, correctly refusing. But offering them "upgrade to Pro" is inviting them to
// buy something they are already paying for. They get "message too long" instead, and the
// upsell is reserved for users whose plan says they are not subscribed.
if(proStatusManager.proDataState.value.type is ProStatus.Active){
if(!proStatusManager.isPostPro() || proStatusManager.proDataState.value.type is ProStatus.Active){
showMessageTooLongSendDialog()
} else {
showSessionProCTA()
Expand All @@ -129,7 +130,7 @@ abstract class InputbarViewModel(
// Same split as [validateMessageLength]: this chooses which explanation to show, so it is DISPLAY.
// `handleCharLimitTappedForRegularUser` is the upsell, and a subscriber with no usable proof must
// not be upsold their own plan.
if(proStatusManager.proDataState.value.type is ProStatus.Active){
if(!proStatusManager.isPostPro() || proStatusManager.proDataState.value.type is ProStatus.Active){
handleCharLimitTappedForProUser()
} else {
handleCharLimitTappedForRegularUser()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ class RecipientRepository @Inject constructor(
}
}

changeSources?.add(proStatusManager.get().postProLaunchStatus.drop(1))

return updatedValue to changeSources?.let { merge(*it.toTypedArray()) }
}

Expand Down Expand Up @@ -701,7 +703,8 @@ class RecipientRepository @Inject constructor(
revocationTag = "a1b2c3d4",
)
)
} else if (pro != null) {
} else if (pro != null && proStatusManager.get().isPostPro()) {
// Pre-launch our own proof, even one synced from another device, is no entitlement here
proDataContext?.addProData(
RecipientSettings.ProData(
showProBadge = configs.userProfile.getProFeatures().contains(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,15 @@ fun DebugMenu(

}

Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
DebugSwitchRow(
text = "Set app as post Pro launch",
checked = uiState.forcePostPro,
onCheckedChange = {
sendCommand(DebugMenuViewModel.Commands.ForcePostPro(it))
}
)

Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
DebugSwitchRow(
text = "Set other users as Pro",
Expand Down Expand Up @@ -944,6 +953,7 @@ fun PreviewDebugMenu() {
forceCurrentUserAsPro = true,
forceIncomingMessagesAsPro = true,
forceOtherUsersAsPro = false,
forcePostPro = false,
forceShortTTl = false,
messageProFeature = setOf(ProMessageFeature.HIGHER_CHARACTER_LIMIT),
dbInspectorState = DebugMenuViewModel.DatabaseInspectorState.STARTED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class DebugMenuViewModel @AssistedInject constructor(
forceCurrentUserAsPro = textSecurePreferences.forceCurrentUserAsPro(),
forceOtherUsersAsPro = textSecurePreferences.forceOtherUsersAsPro(),
forceIncomingMessagesAsPro = textSecurePreferences.forceIncomingMessagesAsPro(),
forcePostPro = textSecurePreferences.forcePostPro(),
forceShortTTl = textSecurePreferences.forcedShortTTL(),
debugAvatarReupload = textSecurePreferences.debugAvatarReupload,
messageProFeature = textSecurePreferences.getDebugMessageFeatures(),
Expand Down Expand Up @@ -347,6 +348,13 @@ class DebugMenuViewModel @AssistedInject constructor(
}
}

is Commands.ForcePostPro -> {
textSecurePreferences.setForcePostPro(command.set)
_uiState.update {
it.copy(forcePostPro = command.set)
}
}

is Commands.ForceShortTTl -> {
textSecurePreferences.setForcedShortTTL(command.set)
_uiState.update {
Expand Down Expand Up @@ -616,6 +624,7 @@ class DebugMenuViewModel @AssistedInject constructor(
val forceOtherUsersAsPro: Boolean,
val forceIncomingMessagesAsPro: Boolean,
val messageProFeature: Set<ProFeature>,
val forcePostPro: Boolean,
val forceShortTTl: Boolean,
val forceDeprecationState: LegacyGroupDeprecationManager.DeprecationState?,
val debugAvatarReupload: Boolean,
Expand Down Expand Up @@ -711,6 +720,7 @@ class DebugMenuViewModel @AssistedInject constructor(
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: ProFeature, val set: Boolean) : Commands()
data class ShowDeprecationChangeDialog(val state: LegacyGroupDeprecationManager.DeprecationState?) : Commands()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ fun Settings(
Buttons(
recoveryHidden = uiState.recoveryHidden,
pathStatus = uiState.pathStatus,
postPro = uiState.isPostPro,
proDataState = uiState.proDataState,
sendCommand = sendCommand
)
Expand Down Expand Up @@ -397,6 +398,7 @@ fun Settings(
AvatarDialog(
state = uiState.avatarDialogState,
isPro = uiState.proDataState.type is ProStatus.Active,
isPostPro = uiState.isPostPro,
sendCommand = sendCommand,
startAvatarSelection = startAvatarSelection
)
Expand Down Expand Up @@ -535,6 +537,7 @@ fun Settings(
fun Buttons(
recoveryHidden: Boolean,
pathStatus: PathStatus,
postPro: Boolean,
proDataState: ProDataState,
sendCommand: (SettingsViewModel.Commands) -> Unit,
) {
Expand Down Expand Up @@ -577,46 +580,48 @@ fun Buttons(

Cell {
Column {
ItemButton(
text = annotatedStringResource(
when (proDataState.type) {
is ProStatus.Active -> Phrase.from(
LocalContext.current,
R.string.sessionProBeta
)
.format().toString()

is ProStatus.NeverSubscribed -> Phrase.from(
LocalContext.current,
R.string.upgradeSession
)
.format().toString()

is ProStatus.Expired -> Phrase.from(
LocalContext.current,
R.string.proRenewBeta
if(postPro){
ItemButton(
text = annotatedStringResource(
when (proDataState.type) {
is ProStatus.Active -> Phrase.from(
LocalContext.current,
R.string.sessionProBeta
)
.format().toString()

is ProStatus.NeverSubscribed -> Phrase.from(
LocalContext.current,
R.string.upgradeSession
)
.format().toString()

is ProStatus.Expired -> Phrase.from(
LocalContext.current,
R.string.proRenewBeta
)
.format().toString()
}
),
icon = {
Image(
modifier = Modifier.size(LocalDimensions.current.iconLargeAvatar)
.align(Alignment.Center),
painter = painterResource(R.drawable.ic_pro_badge),
contentDescription = null,
)
.format().toString()
}
),
icon = {
Image(
modifier = Modifier.size(LocalDimensions.current.iconLargeAvatar)
.align(Alignment.Center),
painter = painterResource(R.drawable.ic_pro_badge),
contentDescription = null,
)
},
modifier = Modifier.qaTag(R.string.qa_settings_item_pro),
// The row id above is the tap target and carries no text; this one is on the
// label, so a test can read WHICH of the three states the row is showing.
textQaTag = R.string.qa_settings_item_pro_title,
colors = accentTextButtonColors()
) {
activity?.push<ProSettingsActivity>()
}
},
modifier = Modifier.qaTag(R.string.qa_settings_item_pro),
// The row id above is the tap target and carries no text; this one is on the
// label, so a test can read WHICH of the three states the row is showing.
textQaTag = R.string.qa_settings_item_pro_title,
colors = accentTextButtonColors()
) {
activity?.push<ProSettingsActivity>()
}

Divider()
Divider()
}

// Invite a friend
ItemButton(
Expand Down Expand Up @@ -923,6 +928,7 @@ fun AvatarOption(
fun AvatarDialog(
state: SettingsViewModel.AvatarDialogState,
isPro: Boolean,
isPostPro: Boolean,
sendCommand: (SettingsViewModel.Commands) -> Unit,
startAvatarSelection: () -> Unit,
){
Expand All @@ -935,20 +941,22 @@ fun AvatarDialog(
// custom content that has the displayed images

// animated Pro title
ProBadgeText(
modifier = Modifier
.padding(
top = LocalDimensions.current.xxxsSpacing,
bottom = LocalDimensions.current.xsSpacing,
)
.clickable {
sendCommand(ShowAnimatedProCTA)
},
text = stringResource(if(isPro) R.string.proAnimatedDisplayPictureModalDescription
else R.string.proAnimatedDisplayPicturesNonProModalDescription),
textStyle = LocalType.current.base.copy(color = LocalColors.current.textSecondary),
badgeAtStart = isPro
)
if(isPostPro){
ProBadgeText(
modifier = Modifier
.padding(
top = LocalDimensions.current.xxxsSpacing,
bottom = LocalDimensions.current.xsSpacing,
)
.clickable {
sendCommand(ShowAnimatedProCTA)
},
text = stringResource(if(isPro) R.string.proAnimatedDisplayPictureModalDescription
else R.string.proAnimatedDisplayPicturesNonProModalDescription),
textStyle = LocalType.current.base.copy(color = LocalColors.current.textSecondary),
badgeAtStart = isPro
)
}

// main container that control the overall size and adds the rounded bg
Box(
Expand Down Expand Up @@ -1073,6 +1081,7 @@ private fun SettingsScreenPreview() {
refreshState = State.Success(Unit),
showProBadge = true
),
isPostPro = true,
username = "Atreyu",
accountID = "053d30141d0d35d9c4b30a8f8880f8464e221ee71a8aff9f0dcefb1e60145cea5144",
pathStatus = PathStatus.READY,
Expand All @@ -1097,6 +1106,7 @@ fun PreviewAvatarDialog(
AvatarDialog(
state = SettingsViewModel.AvatarDialogState.NoAvatar,
isPro = false,
isPostPro = false,
sendCommand = {},
startAvatarSelection = {}
)
Expand Down
Loading
Loading