diff --git a/app/src/main/java/org/session/libsession/network/SnodeClock.kt b/app/src/main/java/org/session/libsession/network/SnodeClock.kt index ac1815b5c6..d4f248978d 100644 --- a/app/src/main/java/org/session/libsession/network/SnodeClock.kt +++ b/app/src/main/java/org/session/libsession/network/SnodeClock.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.withTimeout import org.session.libsession.network.snode.SnodeDirectory import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.dependencies.ManagerScope +import org.thoughtcrime.securesms.dependencies.OnAppStartupComponent import java.util.Date import javax.inject.Inject import javax.inject.Singleton @@ -32,7 +33,7 @@ class SnodeClock @Inject constructor( @param:ManagerScope private val scope: CoroutineScope, private val snodeDirectory: SnodeDirectory, private val snodeClient: Lazy, -) { +): OnAppStartupComponent { private val instantState = MutableStateFlow(null) @@ -46,6 +47,12 @@ class SnodeClock @Inject constructor( // 10 Minutes in milliseconds private val minSyncIntervalMs = 10 * 60 * 1000L + override fun onPostAppStarted() { + scope.launch { + resyncClock() + } + } + /** * Resync by querying 3 random snodes and setting time to the median of their adjusted times. * * Rules: diff --git a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt index 26506c7ca9..1691a7342f 100644 --- a/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt +++ b/app/src/main/java/org/session/libsession/utilities/recipients/RecipientData.kt @@ -176,7 +176,6 @@ sealed interface RecipientData { val expiryMode: ExpiryMode, val members: List, val description: String?, - override val proData: ProData?, override val firstMember: Recipient?, // Used primarily to assemble the profile picture for the group. override val secondMember: Recipient?, // Used primarily to assemble the profile picture for the group. ) : RecipientData, GroupLike { @@ -186,6 +185,7 @@ sealed interface RecipientData { val kicked: Boolean get() = groupInfo.kicked val destroyed: Boolean get() = groupInfo.destroyed val shouldPoll: Boolean get() = groupInfo.shouldPoll + override val proData: ProData? get() = null //todo LARGE GROUP hiding group pro status until we enable large groups override val profileUpdatedAt: Instant? get() = null @@ -198,7 +198,8 @@ sealed interface RecipientData { return hasAdmin(user) } - override fun setProData(proData: ProData): Group = copy(proData = proData) + //todo LARGE GROUP hiding group pro status until we enable large groups + override fun setProData(proData: ProData): Group = this //copy(proData = proData) } data class LegacyGroup( diff --git a/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt index 91d720e821..ee95964ebb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/InputBarDialogs.kt @@ -3,18 +3,10 @@ package org.thoughtcrime.securesms import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.squareup.phrase.Phrase -import network.loki.messenger.R -import org.session.libsession.utilities.NonTranslatableStringConstants -import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.CTAFeature import org.thoughtcrime.securesms.ui.DialogButtonData import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.LongMessageProCTA -import org.thoughtcrime.securesms.ui.SimpleSessionProCTA import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 430fc96f3c..341abe63ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader +import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.ui.AnimatedProfilePicProCTA import org.thoughtcrime.securesms.ui.CTAFeature @@ -720,7 +721,7 @@ fun MessageDetailDialogs( is ProBadgeCTA.AnimatedProfile -> AnimatedProfilePicProCTA( - proSubscription = state.proBadgeCTA.proSubscription, + expired = state.proBadgeCTA.proSubscription is ProStatus.Expired, onDismissRequest = {sendCommand(Commands.HideProBadgeCTA)} ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt index 69ede234b5..2d12cf48e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt @@ -660,7 +660,8 @@ class RecipientRepository @Inject constructor( avatar = configs.groupInfo.getProfilePic().toRemoteFile(), expiryMode = configs.groupInfo.expiryMode, name = configs.groupInfo.getName() ?: groupInfo.name, - proData = null, // final ProData will be calculated later + //todo LARGE GROUP hiding group pro status until we enable large groups + //proData = null, // final ProData will be calculated later description = configs.groupInfo.getDescription(), members = configs.groupMembers.all() .asSequence() diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/OnAppStartupComponents.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/OnAppStartupComponents.kt index 2022368c42..6ac07309f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/OnAppStartupComponents.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/OnAppStartupComponents.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.dependencies import org.session.libsession.messaging.notifications.TokenFetcher import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPollerManager +import org.session.libsession.network.SnodeClock import org.thoughtcrime.securesms.auth.AuthAwareComponentsHandler import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.disguise.AppDisguiseManager @@ -43,6 +44,7 @@ class OnAppStartupComponents private constructor( emojiIndexLoader: EmojiIndexLoader, subscriptionCoordinator: SubscriptionCoordinator, authAwareHandler: AuthAwareComponentsHandler, + snodeClock: SnodeClock, subscriptionManagers: Set<@JvmSuppressWildcards SubscriptionManager>, ): this( components = listOf( @@ -62,6 +64,7 @@ class OnAppStartupComponents private constructor( emojiIndexLoader, subscriptionCoordinator, authAwareHandler, + snodeClock ) + subscriptionManagers ) } 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 3c4285a069..acadb2c13f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt @@ -408,11 +408,52 @@ fun Settings( } // Animated avatar CTA - if(uiState.showAnimatedProCTA){ - AnimatedProCTA( - proSubscription = uiState.proDataState.type, - sendCommand = sendCommand - ) + when(uiState.avatarCTAState){ + is SettingsViewModel.AvatarCTAState.Pro -> { + SessionProCTA ( + title = stringResource(R.string.proActivated), + badgeAtStart = true, + textContent = { + ProBadgeText( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = stringResource(R.string.proAlreadyPurchased), + textStyle = LocalType.current.base.copy(color = LocalColors.current.textSecondary) + ) + + Spacer(Modifier.height(2.dp)) + + // main message + Text( + modifier = Modifier + .qaTag(R.string.qa_cta_body) + .align(Alignment.CenterHorizontally), + text = stringResource(R.string.proAnimatedDisplayPicture), + textAlign = TextAlign.Center, + style = LocalType.current.base.copy( + color = LocalColors.current.textSecondary + ) + ) + }, + content = { + CTAAnimatedImages( + heroImageBg = R.drawable.cta_hero_animated_bg, + heroImageAnimatedFg = R.drawable.cta_hero_animated_fg, + ) + }, + positiveButtonText = null, + negativeButtonText = stringResource(R.string.close), + onCancel = { sendCommand(HideAnimatedProCTA) } + ) + } + + is SettingsViewModel.AvatarCTAState.NonPro -> { + AnimatedProfilePicProCTA( + expired = uiState.avatarCTAState.expired, + onDismissRequest = { sendCommand(HideAnimatedProCTA) }, + ) + } + + else -> {} } // donate confirmation @@ -1014,54 +1055,6 @@ fun AvatarDialog( ) } -@Composable -fun AnimatedProCTA( - proSubscription: ProStatus, - sendCommand: (SettingsViewModel.Commands) -> Unit, -){ - if(proSubscription is ProStatus.Active) { - SessionProCTA ( - title = stringResource(R.string.proActivated), - badgeAtStart = true, - textContent = { - ProBadgeText( - modifier = Modifier.align(Alignment.CenterHorizontally), - text = stringResource(R.string.proAlreadyPurchased), - textStyle = LocalType.current.base.copy(color = LocalColors.current.textSecondary) - ) - - Spacer(Modifier.height(2.dp)) - - // main message - Text( - modifier = Modifier - .qaTag(R.string.qa_cta_body) - .align(Alignment.CenterHorizontally), - text = stringResource(R.string.proAnimatedDisplayPicture), - textAlign = TextAlign.Center, - style = LocalType.current.base.copy( - color = LocalColors.current.textSecondary - ) - ) - }, - content = { - CTAAnimatedImages( - heroImageBg = R.drawable.cta_hero_animated_bg, - heroImageAnimatedFg = R.drawable.cta_hero_animated_fg, - ) - }, - positiveButtonText = null, - negativeButtonText = stringResource(R.string.close), - onCancel = { sendCommand(HideAnimatedProCTA) } - ) - } else { - AnimatedProfilePicProCTA( - proSubscription = proSubscription, - onDismissRequest = { sendCommand(HideAnimatedProCTA) }, - ) - } -} - @OptIn(ExperimentalSharedTransitionApi::class) @SuppressLint("UnusedContentLambdaTargetStateParameter") @Preview @@ -1077,7 +1070,7 @@ private fun SettingsScreenPreview() { showAvatarDialog = false, showAvatarPickerOptionCamera = false, showAvatarPickerOptions = false, - showAnimatedProCTA = false, + avatarCTAState = SettingsViewModel.AvatarCTAState.Hidden, avatarData = AvatarUIData( listOf( AvatarUIElement( 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 6b5ad4b652..523e0cfb7c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.pro.ProDataState import org.thoughtcrime.securesms.pro.ProDetailsRepository +import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.pro.getDefaultSubscriptionStateData import org.thoughtcrime.securesms.reviews.InAppReviewManager @@ -375,11 +376,21 @@ class SettingsViewModel @Inject constructor( && AnimatedImageUtils.isAnimated(rawImageData) private fun showAnimatedProCTA() { - _uiState.update { it.copy(showAnimatedProCTA = true) } + // show the right CTA based on pro state + _uiState.update { + it.copy( + avatarCTAState = + if(it.proDataState.type is ProStatus.Active) AvatarCTAState.Pro + else AvatarCTAState.NonPro( + expired = it.proDataState.type is ProStatus.Expired + )) + } } private fun hideAnimatedProCTA() { - _uiState.update { it.copy(showAnimatedProCTA = false) } + _uiState.update { it.copy( + avatarCTAState = AvatarCTAState.Hidden + ) } } fun showAvatarDialog() { @@ -672,13 +683,19 @@ class SettingsViewModel @Inject constructor( val showAvatarDialog: Boolean = false, val showAvatarPickerOptionCamera: Boolean = false, val showAvatarPickerOptions: Boolean = false, - val showAnimatedProCTA: Boolean = false, + val avatarCTAState: AvatarCTAState = AvatarCTAState.Hidden, val usernameDialog: UsernameDialogData? = null, val showSimpleDialog: SimpleDialogData? = null, val isPostPro: Boolean, val proDataState: ProDataState, ) + sealed interface AvatarCTAState { + data object Hidden : AvatarCTAState + data object Pro : AvatarCTAState + data class NonPro(val expired: Boolean) : AvatarCTAState + } + sealed interface Commands { data object ShowClearDataDialog: Commands data object HideClearDataDialog: Commands diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt index 33fe94fe89..85c4f8ee2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsNavHost.kt @@ -98,7 +98,7 @@ fun ProSettingsNavHost( // handle the custom case of dealing with the post "choose plan confirmation"screen ProNavHostCustomActions.ON_POST_PLAN_CONFIRMATION, ProNavHostCustomActions.ON_POST_CANCELLATION -> { - // we get here where we either hit back or hit the "ok" button on the plan confirmation screen + // we get here when we either hit back or hit the "ok" button on the plan confirmation screen // if we are in a sheet we need to close it if (inSheet) { 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 3fbbcbd4b9..6ee3a7407a 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 @@ -58,7 +58,6 @@ 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 -import org.thoughtcrime.securesms.pro.subscription.expiryFromNow import org.thoughtcrime.securesms.ui.SimpleDialogData import org.thoughtcrime.securesms.ui.UINavigator import org.thoughtcrime.securesms.util.CurrencyFormatter @@ -227,7 +226,7 @@ class ProSettingsViewModel @AssistedInject constructor( else -> "" }, subscriptionExpiryDate = when(subType){ - is ProStatus.Active -> subType.duration.expiryFromNow(now) + is ProStatus.Active -> subType.validUntilFormatted() else -> "" }, ) @@ -526,7 +525,7 @@ class ProSettingsViewModel @AssistedInject constructor( val selectedPlan = getSelectedPlan() ?: return if(currentSubscription is ProStatus.Active){ - val newSubscriptionExpiryString = selectedPlan.durationType.expiryFromNow() + val newSubscriptionExpiryString = currentSubscription.validUntilFormatted() val currentSubscriptionDuration = DateUtils.getLocalisedTimeDuration( context = context, @@ -551,14 +550,14 @@ class ProSettingsViewModel @AssistedInject constructor( .put(PRO_KEY, NonTranslatableStringConstants.PRO) .put(DATE_KEY, newSubscriptionExpiryString) .put(CURRENT_PLAN_LENGTH_KEY, currentSubscriptionDuration) - .put(SELECTED_PLAN_LENGTH_KEY, selectedSubscriptionDuration) + .put(SELECTED_PLAN_LENGTH_KEY, selectedSubscriptionDuration.lowercase()) // for this string below, we want to remove the 's' at the end if there is one: 12 Months becomes 12 Month .put(SELECTED_PLAN_LENGTH_SINGULAR_KEY, selectedSubscriptionDuration.removeSuffix("s")) .format() else Phrase.from(context.getText(R.string.proUpdateAccessExpireDescription)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) .put(DATE_KEY, newSubscriptionExpiryString) - .put(SELECTED_PLAN_LENGTH_KEY, selectedSubscriptionDuration) + .put(SELECTED_PLAN_LENGTH_KEY, selectedSubscriptionDuration.lowercase()) .format(), positiveText = context.getString(R.string.update), negativeText = context.getString(R.string.cancel), diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlan.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlan.kt index a18972c7c5..043465be42 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlan.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlan.kt @@ -68,7 +68,6 @@ import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.P import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.ProPlanBadge import org.thoughtcrime.securesms.pro.ProStatus import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration -import org.thoughtcrime.securesms.pro.subscription.expiryFromNow import org.thoughtcrime.securesms.ui.LoadingArcOr import org.thoughtcrime.securesms.ui.SpeechBubbleTooltip import org.thoughtcrime.securesms.ui.components.AccentFillButtonRect @@ -111,7 +110,7 @@ fun ChoosePlan( val title = when (planData.proStatus) { is ProStatus.Active.Expiring -> Phrase.from(context.getText(R.string.proAccessActivatedNotAuto)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) - .put(DATE_KEY, planData.proStatus.duration.expiryFromNow()) + .put(DATE_KEY, planData.proStatus.validUntilFormatted()) .format() is ProStatus.Active.AutoRenewing -> Phrase.from(context.getText(R.string.proAccessActivatesAuto)) @@ -123,7 +122,7 @@ fun ChoosePlan( unit = MeasureUnit.MONTH ) ) - .put(DATE_KEY, planData.proStatus.duration.expiryFromNow()) + .put(DATE_KEY, planData.proStatus.validUntilFormatted()) .format() else -> 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 e262b7a669..9a000b1eac 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 @@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.C import org.thoughtcrime.securesms.pro.ProStatus 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 @@ -46,7 +45,7 @@ fun ChoosePlanNonOriginating( val headerTitle = when(subscription) { is ProStatus.Active.Expiring -> Phrase.from(context.getText(R.string.proAccessExpireDate)) .put(PRO_KEY, NonTranslatableStringConstants.PRO) - .put(DATE_KEY, subscription.duration.expiryFromNow()) + .put(DATE_KEY, subscription.validUntilFormatted()) .format() is ProStatus.Active.AutoRenewing -> Phrase.from(context.getText(R.string.proAccessActivatedAutoShort)) @@ -56,7 +55,7 @@ fun ChoosePlanNonOriginating( amount = subscription.duration.duration.months, unit = MeasureUnit.MONTH )) - .put(DATE_KEY, subscription.duration.expiryFromNow()) + .put(DATE_KEY, subscription.validUntilFormatted()) .format() else -> "" 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 edb76c4a27..a8e04795a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatus.kt @@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.pro import network.loki.messenger.libsession_util.protocol.PaymentProviderMetadata import org.thoughtcrime.securesms.pro.subscription.ProSubscriptionDuration +import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.State import java.time.Instant +import java.time.ZoneId sealed interface ProStatus{ data object NeverSubscribed: ProStatus @@ -34,6 +36,12 @@ sealed interface ProStatus{ fun isWithinQuickRefundWindow(): Boolean { return quickRefundExpiry != null && quickRefundExpiry!!.isAfter(Instant.now()) } + + fun validUntilFormatted(): String { + return DateUtils.getLocaleFormattedDate( + validUntil.toEpochMilli(), "MMMM d, yyyy" + ) + } } data class Expired( diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/ProSubscriptionDuration.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/ProSubscriptionDuration.kt index 4d52f85262..e0b2af4b08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/ProSubscriptionDuration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pro/subscription/ProSubscriptionDuration.kt @@ -1,9 +1,6 @@ package org.thoughtcrime.securesms.pro.subscription -import org.thoughtcrime.securesms.util.DateUtils -import java.time.Instant import java.time.Period -import java.time.ZoneId enum class ProSubscriptionDuration(val duration: Period, val id: String) { ONE_MONTH(Period.ofMonths(1), "session-pro-1-month"), @@ -13,19 +10,3 @@ enum class ProSubscriptionDuration(val duration: Period, val id: String) { fun ProSubscriptionDuration.getById(id: String): ProSubscriptionDuration? = ProSubscriptionDuration.entries.find { it.id == id } - -private const val proSettingsDateFormat = "MMMM d, yyyy" - -fun ProSubscriptionDuration.expiryFromNow(now: Instant = Instant.now()): String { - // It's important to convert the Instant to a ZonedDateTime as adding - // a Period like Month depends on the timezone (daylight savings etc). - val newSubscriptionExpiryDate = now - .atZone(ZoneId.systemDefault()) - .plus(duration) - .toInstant() - .toEpochMilli() - - return DateUtils.getLocaleFormattedDate( - newSubscriptionExpiryDate, proSettingsDateFormat - ) -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt index 735c418938..3988fe5bca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt @@ -49,6 +49,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.retain.retain import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -253,8 +254,8 @@ fun SessionProCTA( // We should avoid internal state in a composable but having the bottom sheet // here avoids re-defining the sheet in multiple places in the app - var showDialog by remember { mutableStateOf(true) } - var showProSheet by remember { mutableStateOf(false) } + var showDialog by retain { mutableStateOf(true) } + var showProSheet by retain { mutableStateOf(false) } // default handling of the upgrade button val defaultUpgrade: () -> Unit = { @@ -640,10 +641,9 @@ fun LongMessageProCTA( // Reusable animated profile pic Pro CTA @Composable fun AnimatedProfilePicProCTA( - proSubscription: ProStatus, + expired: Boolean, onDismissRequest: () -> Unit, ){ - val expired = proSubscription is ProStatus.Expired val context = LocalContext.current AnimatedSessionProCTA( diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt index c415d06cb6..d1ba385308 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt @@ -297,7 +297,12 @@ class DateUtils @Inject constructor( fun getLocalisedTimeDuration(context: Context, amount: Int, unit: MeasureUnit): String { val locale = context.resources.configuration.locales[0] val format = MeasureFormat.getInstance(locale, MeasureFormat.FormatWidth.WIDE) - return format.format(Measure(amount, unit)) + val rawString = format.format(Measure(amount, unit)) + + // capitalise duration + return rawString.replace(Regex("""(\d+\p{Z}+)(\p{L})""")) { + it.groupValues[1] + it.groupValues[2].uppercase(locale) + } } // Format a given timestamp with a specific pattern diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt index 1c6b3b18df..15fa3a619f 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt @@ -107,7 +107,8 @@ class DisappearingMessagesViewModelTest : BaseViewModelTest() { destroyed = false, joinedAtSecs = System.currentTimeMillis() / 1000L, ), - proData = null, + //todo LARGE GROUP hiding group pro status until we enable large groups + //proData = null, members = listOf(), description = null, firstMember = Recipient( @@ -182,7 +183,8 @@ class DisappearingMessagesViewModelTest : BaseViewModelTest() { destroyed = false, joinedAtSecs = System.currentTimeMillis() / 1000L, ), - proData = null, + //todo LARGE GROUP hiding group pro status until we enable large groups + //proData = null, members = listOf(), description = null, firstMember = Recipient(