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 @@ -1789,6 +1789,7 @@ class AppTextSecurePreferences @Inject constructor(

override fun setDebugForceNoBilling(hasBilling: Boolean) {
setBooleanPreference(TextSecurePreferences.DEBUG_FORCE_NO_BILLING, hasBilling)
_events.tryEmit(TextSecurePreferences.DEBUG_FORCE_NO_BILLING)
}

override fun getSubscriptionProvider(): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
Expand Down Expand Up @@ -293,7 +294,7 @@ class HomeActivity : ScreenLockActionBarActivity(),
binding.dialogs.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setThemedContent {
val dialogsState by homeViewModel.dialogsState.collectAsState()
val dialogsState by homeViewModel.dialogsState.collectAsStateWithLifecycle()
HomeDialogs(
dialogsState = dialogsState,
sendCommand = homeViewModel::onCommand
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ class HomeViewModel @Inject constructor(
val validUntil = subscription.type.proStatus.validUntil ?: return@collect

if (validUntil.isBefore(now.plus(7, ChronoUnit.DAYS))) {
prefs.setHasSeenProExpiring()
_dialogsState.update { state ->
state.copy(
proExpiringCTA = ProExpiringCTA(
Expand All @@ -188,7 +187,7 @@ class HomeViewModel @Inject constructor(

// Check if now is within 30 days after expiry
if (now.isBefore(validUntil.plus(30, ChronoUnit.DAYS))) {
prefs.setHasSeenProExpired()

_dialogsState.update { state ->
state.copy(proExpiredCTA = true)
}
Expand Down Expand Up @@ -299,10 +298,12 @@ class HomeViewModel @Inject constructor(
}

is Commands.HideExpiringCTADialog -> {
prefs.setHasSeenProExpiring()
_dialogsState.update { it.copy(proExpiringCTA = null) }
}

is Commands.HideExpiredCTADialog -> {
prefs.setHasSeenProExpired()
_dialogsState.update { it.copy(proExpiredCTA = false) }
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.thoughtcrime.securesms.preferences.prosettings

import androidx.annotation.DrawableRes
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
Expand All @@ -20,41 +22,42 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
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.Cell
import org.thoughtcrime.securesms.ui.DialogBg
import org.thoughtcrime.securesms.ui.SessionProSettingsHeader
import org.thoughtcrime.securesms.ui.components.AccentFillButtonRect
import org.thoughtcrime.securesms.ui.components.BackAppBar
import org.thoughtcrime.securesms.ui.components.DangerFillButtonRect
import org.thoughtcrime.securesms.ui.components.annotatedStringResource
import org.thoughtcrime.securesms.ui.components.inlineContentMap
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
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 network.loki.messenger.R
import org.thoughtcrime.securesms.ui.DialogBg
import org.thoughtcrime.securesms.ui.components.inlineContentMap

/**
* Base structure used in most Pro Settings screen
Expand All @@ -69,42 +72,63 @@ fun BaseProSettingsScreen(
extraHeaderContent: @Composable (() -> Unit)? = null,
content: @Composable () -> Unit
){
// We need the app bar to start as transparent and slowly go opaque as we scroll
val lazyListState = rememberLazyListState()
// Calculate scroll fraction
val density = LocalDensity.current
val thresholdPx = remember(density) { with(density) { 28.dp.toPx() } } // amount before the appbar gets fully opaque

// raw fraction 0..1 derived from scrolling
val rawFraction by remember {
derivedStateOf {
when {
lazyListState.layoutInfo.totalItemsCount == 0 -> 0f
lazyListState.firstVisibleItemIndex > 0 -> 1f
else -> (lazyListState.firstVisibleItemScrollOffset / thresholdPx).coerceIn(0f, 1f)
}
}
}

// easing + smoothing of fraction
val easedFraction = remember(rawFraction) {
FastOutSlowInEasing.transform(rawFraction)
}

// setting the appbar's bg alpha based on scroll
val backgroundColor = LocalColors.current.background.copy(alpha = easedFraction)

Scaffold(
topBar = if(!hideHomeAppBar){{
BackAppBar(
title = "",
backgroundColor = Color.Transparent,
backgroundColor = backgroundColor,
onBack = onBack,
)
}} else {{}},
contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal),
) { paddings ->

Column(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top =
(paddings.calculateTopPadding() - LocalDimensions.current.appBarHeight)
.coerceAtLeast(0.dp))
.consumeWindowInsets(paddings)
.padding(
horizontal = LocalDimensions.current.spacing,
)
.verticalScroll(rememberScrollState()),
.padding(horizontal = LocalDimensions.current.spacing),
state = lazyListState,
contentPadding = PaddingValues(
top = (paddings.calculateTopPadding() - LocalDimensions.current.appBarHeight)
.coerceAtLeast(0.dp) + 46.dp,
bottom = paddings.calculateBottomPadding() + LocalDimensions.current.spacing
),
horizontalAlignment = CenterHorizontally
) {
Spacer(Modifier.height(46.dp))

SessionProSettingsHeader(
disabled = disabled,
onClick = onHeaderClick,
extraContent = extraHeaderContent
)

content()
item {
SessionProSettingsHeader(
disabled = disabled,
onClick = onHeaderClick,
extraContent = extraHeaderContent
)
}

Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
item { content() }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import com.squareup.phrase.Phrase
import network.loki.messenger.R
import org.session.libsession.utilities.NonTranslatableStringConstants
import org.session.libsession.utilities.NonTranslatableStringConstants.NETWORK_NAME
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.session.libsession.utilities.recipients.ProStatus
import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.GoToProSettings
Expand Down Expand Up @@ -110,29 +112,56 @@ fun PlanConfirmation(

Spacer(Modifier.height(LocalDimensions.current.xsSpacing))

val description = when (proData.subscriptionState.type) {
is SubscriptionType.Active -> {
Phrase.from(context.getText(R.string.proAllSetDescription))
.put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO)
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.put(DATE_KEY, proData.subscriptionExpiryDate)
.format()
}

is SubscriptionType.NeverSubscribed -> {
Phrase.from(context.getText(R.string.proUpgraded))
.put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO)
.put(NETWORK_NAME_KEY, NETWORK_NAME)
.format()
}

is SubscriptionType.Expired -> {
Phrase.from(context.getText(R.string.proPlanRenewSupport))
.put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO)
.put(NETWORK_NAME_KEY, NETWORK_NAME)
.format()
}
}

Text(
modifier = Modifier.align(CenterHorizontally)
.safeContentWidth(),
//todo PRO the text below can change if the user was renewing vs expiring and/or/auto-renew
text = annotatedStringResource(
Phrase.from(context.getText(R.string.proAllSetDescription))
.put(APP_PRO_KEY, NonTranslatableStringConstants.APP_PRO)
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.put(DATE_KEY, proData.subscriptionExpiryDate)
.format()
),
text = annotatedStringResource(description),
textAlign = TextAlign.Center,
style = LocalType.current.base,
color = LocalColors.current.text,
)

Spacer(Modifier.height(LocalDimensions.current.spacing))

//todo PRO the button text can change if the user was renewing vs expiring and/or/auto-renew
val buttonLabel = when (proData.subscriptionState.type) {
is SubscriptionType.Active -> stringResource(R.string.theReturn)

else -> {
Phrase.from(context.getText(R.string.proStartUsing))
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format()
.toString()
}
}

AccentFillButtonRect(
modifier = Modifier.fillMaxWidth()
.widthIn(max = LocalDimensions.current.maxContentWidth),
text = stringResource(R.string.theReturn),
text = buttonLabel,
onClick = {
sendCommand(GoToProSettings)
}
Expand All @@ -146,12 +175,13 @@ fun PlanConfirmation(

@Preview
@Composable
private fun PreviewPlanConfirmation(
private fun PreviewPlanConfirmationActive(
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
PlanConfirmation(
proData = ProSettingsViewModel.ProSettingsState(
subscriptionExpiryDate = "20th June 2026",
subscriptionState = SubscriptionState(
type = SubscriptionType.Active.AutoRenewing(
proStatus = ProStatus.Pro(
Expand All @@ -176,4 +206,51 @@ private fun PreviewPlanConfirmation(
}
}

@Preview
@Composable
private fun PreviewPlanConfirmationExpired(
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
PlanConfirmation(
proData = ProSettingsViewModel.ProSettingsState(
subscriptionState = SubscriptionState(
type = SubscriptionType.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",
)),
refreshState = State.Success(Unit),),
),
sendCommand = {},
onBack = {},
)
}
}

@Preview
@Composable
private fun PreviewPlanConfirmationNeverSub(
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
PlanConfirmation(
proData = ProSettingsViewModel.ProSettingsState(
subscriptionState = SubscriptionState(
type = SubscriptionType.NeverSubscribed,
refreshState = State.Success(Unit),),
),
sendCommand = {},
onBack = {},
)
}
}




Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ fun ProSettingsHome(
// add a click handling if the subscription state is loading or errored
if(data.subscriptionState.refreshState !is State.Success<*>){
sendCommand(OnHeaderClicked)
//todo PRO double check if KEE is ok to not have two different dialogs for the header vs the action button. If yes then I need to simplify the logic, if not I need to fix the never-subscribed case
} else null
},
extraHeaderContent = {
Expand Down Expand Up @@ -169,7 +168,6 @@ fun ProSettingsHome(
when(subscriptionType){
is SubscriptionType.Active -> R.string.proErrorRefreshingStatus
else -> R.string.errorCheckingProStatus
//todo PRO will need to handle never subscribed here
}))
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format().toString(),
Expand Down
Loading