diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0ddf065ec3..cce3a226fb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -190,9 +190,6 @@
android:screenOrientation="portrait" />
-
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt
deleted file mode 100644
index b8784a3d44..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesActivity.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.thoughtcrime.securesms.conversation.disappearingmessages
-
-import androidx.compose.runtime.Composable
-import androidx.core.content.IntentCompat
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import dagger.hilt.android.AndroidEntryPoint
-import network.loki.messenger.BuildConfig
-import org.session.libsession.messaging.messages.ExpirationConfiguration
-import org.session.libsession.utilities.Address
-import org.thoughtcrime.securesms.FullComposeScreenLockActivity
-import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.DisappearingMessagesScreen
-
-@AndroidEntryPoint
-class DisappearingMessagesActivity: FullComposeScreenLockActivity() {
-
- @Composable
- override fun ComposeContent() {
- val viewModel: DisappearingMessagesViewModel =
- hiltViewModel { factory ->
- factory.create(
- address = requireNotNull(
- IntentCompat.getParcelableExtra(intent, ARG_ADDRESS, Address::class.java)
- ) {
- "DisappearingMessagesActivity requires an Address to be passed in via the intent."
- },
- isNewConfigEnabled = ExpirationConfiguration.isNewConfigEnabled,
- showDebugOptions = BuildConfig.BUILD_TYPE != "release"
- )
- }
-
- DisappearingMessagesScreen(
- viewModel = viewModel,
- onBack = { finish() },
- )
- }
-
- companion object {
- const val ARG_ADDRESS = "address"
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt
index 1ba68f20a6..cb9a40ca23 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModel.kt
@@ -31,9 +31,9 @@ class DisappearingMessagesViewModel @AssistedInject constructor(
@Assisted private val address: Address,
@Assisted("isNewConfigEnabled") private val isNewConfigEnabled: Boolean,
@Assisted("showDebugOptions") private val showDebugOptions: Boolean,
+ @Assisted private val navigator: UINavigator,
@param:ApplicationContext private val context: Context,
private val disappearingMessages: DisappearingMessages,
- private val navigator: UINavigator,
private val recipientRepository: RecipientRepository,
) : ViewModel() {
@@ -96,7 +96,8 @@ class DisappearingMessagesViewModel @AssistedInject constructor(
fun create(
address: Address,
@Assisted("isNewConfigEnabled") isNewConfigEnabled: Boolean,
- @Assisted("showDebugOptions") showDebugOptions: Boolean
+ @Assisted("showDebugOptions") showDebugOptions: Boolean,
+ navigator: UINavigator
): DisappearingMessagesViewModel
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
index 68139a8268..f69b716d6c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
@@ -124,7 +124,6 @@ import org.thoughtcrime.securesms.audio.AudioRecorderHandle
import org.thoughtcrime.securesms.audio.recordAudio
import org.thoughtcrime.securesms.components.TypingStatusSender
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
-import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessagesActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener
import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ShowOpenUrlDialog
@@ -150,6 +149,7 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDel
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsActivity
+import org.thoughtcrime.securesms.conversation.v2.settings.ConversationSettingsDestination
import org.thoughtcrime.securesms.conversation.v2.settings.notification.NotificationSettingsActivity
import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
@@ -692,9 +692,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
}
is ConversationUiEvent.ShowDisappearingMessages -> {
- val intent = Intent(this@ConversationActivityV2, DisappearingMessagesActivity::class.java).apply {
- putExtra(DisappearingMessagesActivity.ARG_ADDRESS, event.address)
- }
+ val intent = ConversationSettingsActivity.createIntent(
+ context = this@ConversationActivityV2,
+ address = event.address,
+ startDestination = ConversationSettingsDestination.RouteDisappearingMessages
+ )
startActivity(intent)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
index 89c980c0c5..21113c8bc3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
@@ -1293,7 +1293,7 @@ class ConversationViewModel @AssistedInject constructor(
}
}
- _uiEvents.tryEmit(ConversationUiEvent.ShowDisappearingMessages(convo.address))
+ _uiEvents.tryEmit(ConversationUiEvent.ShowDisappearingMessages(address))
}
}
@@ -1455,7 +1455,7 @@ data class UiMessage(val id: Long, val message: String)
sealed interface ConversationUiEvent {
data class NavigateToConversation(val address: Address.Conversable) : ConversationUiEvent
- data class ShowDisappearingMessages(val address: Address) : ConversationUiEvent
+ data class ShowDisappearingMessages(val address: Address.Conversable) : ConversationUiEvent
data class ShowNotificationSettings(val address: Address) : ConversationUiEvent
data class ShowGroupMembers(val groupAddress: Address.Group) : ConversationUiEvent
data class ShowConversationSettings(val threadAddress: Address.Conversable) : ConversationUiEvent
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt
index 9e6058c79a..fedeadd188 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsActivity.kt
@@ -7,32 +7,39 @@ import androidx.core.content.IntentCompat
import dagger.hilt.android.AndroidEntryPoint
import org.session.libsession.utilities.Address
import org.thoughtcrime.securesms.FullComposeScreenLockActivity
-import org.thoughtcrime.securesms.ui.UINavigator
-import javax.inject.Inject
@AndroidEntryPoint
class ConversationSettingsActivity: FullComposeScreenLockActivity() {
companion object {
- const val THREAD_ADDRESS = "conversation_settings_thread_address"
+ private const val THREAD_ADDRESS = "conversation_settings_thread_address"
+ private const val EXTRA_START_DESTINATION = "start_destination"
- fun createIntent(context: Context, address: Address.Conversable): Intent {
+ fun createIntent(
+ context: Context,
+ address: Address.Conversable,
+ startDestination: ConversationSettingsDestination = ConversationSettingsDestination.RouteConversationSettings
+ ): Intent {
return Intent(context, ConversationSettingsActivity::class.java).apply {
putExtra(THREAD_ADDRESS, address)
+ putExtra(EXTRA_START_DESTINATION, startDestination)
}
}
}
- @Inject
- lateinit var navigator: UINavigator
-
@Composable
override fun ComposeContent() {
+ val startDestination = IntentCompat.getParcelableExtra(
+ intent,
+ EXTRA_START_DESTINATION,
+ ConversationSettingsDestination::class.java
+ ) ?: ConversationSettingsDestination.RouteConversationSettings
+
ConversationSettingsNavHost(
address = requireNotNull(IntentCompat.getParcelableExtra(intent, THREAD_ADDRESS, Address.Conversable::class.java)) {
"ConversationSettingsActivity requires an Address to be passed in the intent."
},
- navigator = navigator,
+ startDestination = startDestination,
returnResult = { code, value ->
setResult(RESULT_OK, Intent().putExtra(code, value))
finish()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt
index 02c288e96b..5a540d1d0c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsNavHost.kt
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.conversation.v2.settings
import android.annotation.SuppressLint
+import android.os.Parcelable
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.runtime.Composable
@@ -14,6 +15,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
+import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import network.loki.messenger.BuildConfig
import org.session.libsession.messaging.messages.ExpirationConfiguration
@@ -39,11 +41,13 @@ import org.thoughtcrime.securesms.ui.UINavigator
import org.thoughtcrime.securesms.ui.horizontalSlideComposable
// Destinations
-sealed interface ConversationSettingsDestination {
+sealed interface ConversationSettingsDestination: Parcelable {
@Serializable
+ @Parcelize
data object RouteConversationSettings: ConversationSettingsDestination
@Serializable
+ @Parcelize
data class RouteGroupMembers private constructor(
private val address: String
): ConversationSettingsDestination {
@@ -53,6 +57,7 @@ sealed interface ConversationSettingsDestination {
}
@Serializable
+ @Parcelize
data class RouteManageMembers private constructor(
private val address: String
): ConversationSettingsDestination {
@@ -62,6 +67,7 @@ sealed interface ConversationSettingsDestination {
}
@Serializable
+ @Parcelize
data class RouteInviteToGroup private constructor(
private val address: String,
val excludingAccountIDs: List
@@ -73,15 +79,19 @@ sealed interface ConversationSettingsDestination {
}
@Serializable
+ @Parcelize
data object RouteDisappearingMessages: ConversationSettingsDestination
@Serializable
+ @Parcelize
data object RouteAllMedia: ConversationSettingsDestination
@Serializable
+ @Parcelize
data object RouteNotifications: ConversationSettingsDestination
@Serializable
+ @Parcelize
data class RouteInviteToCommunity(
val communityUrl: String
): ConversationSettingsDestination
@@ -92,12 +102,21 @@ sealed interface ConversationSettingsDestination {
@Composable
fun ConversationSettingsNavHost(
address: Address.Conversable,
- navigator: UINavigator,
+ startDestination: ConversationSettingsDestination = RouteConversationSettings,
returnResult: (String, Boolean) -> Unit,
onBack: () -> Unit
){
SharedTransitionLayout {
val navController = rememberNavController()
+ val navigator: UINavigator = remember { UINavigator() }
+
+ val handleBack: () -> Unit = {
+ if (navController.previousBackStackEntry != null) {
+ navController.navigateUp()
+ } else {
+ onBack() // Finish activity if at root
+ }
+ }
ObserveAsEvents(flow = navigator.navigationActions) { action ->
when (action) {
@@ -107,7 +126,7 @@ fun ConversationSettingsNavHost(
action.navOptions(this)
}
- NavigationAction.NavigateUp -> navController.navigateUp()
+ NavigationAction.NavigateUp -> handleBack()
is NavigationAction.NavigateToIntent -> {
navController.context.startActivity(action.intent)
@@ -119,12 +138,12 @@ fun ConversationSettingsNavHost(
}
}
- NavHost(navController = navController, startDestination = RouteConversationSettings) {
+ NavHost(navController = navController, startDestination = startDestination) {
// Conversation Settings
horizontalSlideComposable {
val viewModel =
hiltViewModel { factory ->
- factory.create(address)
+ factory.create(address, navigator)
}
val lifecycleOwner = LocalLifecycleOwner.current
@@ -154,7 +173,7 @@ fun ConversationSettingsNavHost(
GroupMembersScreen(
viewModel = viewModel,
onBack = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
},
)
}
@@ -178,7 +197,7 @@ fun ConversationSettingsNavHost(
)
},
onBack = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
},
)
}
@@ -208,10 +227,10 @@ fun ConversationSettingsNavHost(
//send invites from the manage group screen
editGroupViewModel.onContactSelected(viewModel.currentSelected)
- navController.popBackStack()
+ handleBack()
},
onBack = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
},
banner = {
GroupMinimumVersionBanner()
@@ -244,7 +263,7 @@ fun ConversationSettingsNavHost(
viewModel.clearSelection()
},
onBack = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
},
)
}
@@ -256,14 +275,15 @@ fun ConversationSettingsNavHost(
factory.create(
address = address,
isNewConfigEnabled = ExpirationConfiguration.isNewConfigEnabled,
- showDebugOptions = BuildConfig.BUILD_TYPE != "release"
+ showDebugOptions = BuildConfig.BUILD_TYPE != "release",
+ navigator = navigator
)
}
DisappearingMessagesScreen(
viewModel = viewModel,
onBack = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
},
)
}
@@ -278,7 +298,7 @@ fun ConversationSettingsNavHost(
MediaOverviewScreen(
viewModel = viewModel,
onClose = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
},
)
}
@@ -293,7 +313,7 @@ fun ConversationSettingsNavHost(
NotificationSettingsScreen(
viewModel = viewModel,
onBack = dropUnlessResumed {
- navController.popBackStack()
+ handleBack()
}
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt
index de86d4cec5..0d98c5c126 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt
@@ -72,13 +72,13 @@ import org.thoughtcrime.securesms.util.AvatarUtils
@HiltViewModel(assistedFactory = ConversationSettingsViewModel.Factory::class)
class ConversationSettingsViewModel @AssistedInject constructor(
@Assisted private val address: Address.Conversable,
+ @Assisted private val navigator: UINavigator,
@param:ApplicationContext private val context: Context,
private val avatarUtils: AvatarUtils,
private val repository: ConversationRepository,
private val configFactory: ConfigFactoryProtocol,
private val storage: StorageProtocol,
private val conversationRepository: ConversationRepository,
- private val navigator: UINavigator,
private val groupManagerV2: GroupManagerV2,
private val groupManager: GroupManagerV2,
private val openGroupManager: OpenGroupManager,
@@ -1385,7 +1385,10 @@ class ConversationSettingsViewModel @AssistedInject constructor(
@AssistedFactory
interface Factory {
- fun create(address: Address.Conversable): ConversationSettingsViewModel
+ fun create(
+ address: Address.Conversable,
+ navigator: UINavigator
+ ): ConversationSettingsViewModel
}
data class UIState(
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 70d252701e..e10d248e0f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientRepository.kt
@@ -47,8 +47,10 @@ import org.session.libsignal.utilities.AccountId
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.model.NotifyType
import org.thoughtcrime.securesms.database.model.RecipientSettings
+import org.thoughtcrime.securesms.debugmenu.DebugMenuViewModel
import org.thoughtcrime.securesms.dependencies.ManagerScope
import org.thoughtcrime.securesms.groups.GroupMemberComparator
+import org.thoughtcrime.securesms.pro.SubscriptionType
import org.thoughtcrime.securesms.util.DateUtils.Companion.secondsToInstant
import java.lang.ref.WeakReference
import java.time.Duration
@@ -147,7 +149,10 @@ class RecipientRepository @Inject constructor(
value = createLocalRecipient(address, recipientData)
changeSource = merge(
configFactory.userConfigsChanged(onlyConfigTypes = EnumSet.of(UserConfigType.USER_PROFILE)),
- TextSecurePreferences.events.filter { it == TextSecurePreferences.SET_FORCE_CURRENT_USER_PRO }
+ TextSecurePreferences.events.filter {
+ it == TextSecurePreferences.SET_FORCE_CURRENT_USER_PRO
+ || it == TextSecurePreferences.DEBUG_SUBSCRIPTION_STATUS
+ }
)
}
@@ -407,7 +412,11 @@ class RecipientRepository @Inject constructor(
expiryMode = configs.userProfile.getNtsExpiry(),
priority = configs.userProfile.getNtsPriority(),
proStatus = if (preferences.forceCurrentUserAsPro()) {
- ProStatus.Pro()
+ // take into account the fact that we can be expired via the debug menu - which is no longer pro
+ if(preferences.getDebugSubscriptionType() == DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED
+ || preferences.getDebugSubscriptionType() == DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_APPLE
+ || preferences.getDebugSubscriptionType() == DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_EARLIER) ProStatus.None
+ else ProStatus.Pro()
} else {
// TODO: Get pro status from config
ProStatus.None
diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
index ad8a6100e5..6be7bb2d63 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt
@@ -277,28 +277,28 @@ fun DebugMenu(
)
}
)
+ }
+ }
- Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
- Text(
- modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing),
- text = "Pro Data Status",
- style = LocalType.current.base
- )
- DropDown(
- modifier = Modifier.fillMaxWidth()
- .padding(top = LocalDimensions.current.xxsSpacing),
- selectedText = uiState.selectedDebugProPlanStatus.label,
- values = uiState.debugProPlanStatus.map { it.label },
- onValueSelected = { selection ->
- sendCommand(
- DebugMenuViewModel.Commands.SetDebugProPlanStatus(
- uiState.debugProPlanStatus.first { it.label == selection }
- )
- )
- }
+ Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
+ Text(
+ modifier = Modifier.padding(top = LocalDimensions.current.xxsSpacing),
+ text = "Pro Data Status",
+ style = LocalType.current.base
+ )
+ DropDown(
+ modifier = Modifier.fillMaxWidth()
+ .padding(top = LocalDimensions.current.xxsSpacing),
+ selectedText = uiState.selectedDebugProPlanStatus.label,
+ values = uiState.debugProPlanStatus.map { it.label },
+ onValueSelected = { selection ->
+ sendCommand(
+ DebugMenuViewModel.Commands.SetDebugProPlanStatus(
+ uiState.debugProPlanStatus.first { it.label == selection }
+ )
)
}
- }
+ )
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
DebugSwitchRow(
diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt
index 5fc01a9cdd..8688a484ba 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenuViewModel.kt
@@ -87,9 +87,11 @@ class DebugMenuViewModel @Inject constructor(
debugSubscriptionStatuses = setOf(
DebugSubscriptionStatus.AUTO_GOOGLE,
DebugSubscriptionStatus.EXPIRING_GOOGLE,
+ DebugSubscriptionStatus.EXPIRING_GOOGLE_LATER,
DebugSubscriptionStatus.AUTO_APPLE,
DebugSubscriptionStatus.EXPIRING_APPLE,
DebugSubscriptionStatus.EXPIRED,
+ DebugSubscriptionStatus.EXPIRED_EARLIER,
DebugSubscriptionStatus.EXPIRED_APPLE,
),
selectedDebugSubscriptionStatus = textSecurePreferences.getDebugSubscriptionType() ?: DebugSubscriptionStatus.AUTO_GOOGLE,
@@ -445,11 +447,13 @@ class DebugMenuViewModel @Inject constructor(
enum class DebugSubscriptionStatus(val label: String) {
AUTO_GOOGLE("Auto Renewing (Google, 3 months)"),
- EXPIRING_GOOGLE("Expiring/Cancelled (Google, 12 months)"),
+ EXPIRING_GOOGLE("Expiring/Cancelled (Expires in 14 days, Google, 12 months)"),
+ EXPIRING_GOOGLE_LATER("Expiring/Cancelled (Expires in 40 days, Google, 12 months)"),
AUTO_APPLE("Auto Renewing (Apple, 1 months)"),
- EXPIRING_APPLE("Expiring/Cancelled (Apple, 1 months)"),
- EXPIRED("Expired (Google)"),
- EXPIRED_APPLE("Expired (Apple)"),
+ EXPIRING_APPLE("Expiring/Cancelled (Expires in 14 days, Apple, 1 months)"),
+ EXPIRED("Expired (Expired 2 days ago, Google)"),
+ EXPIRED_EARLIER("Expired (Expired 60 days ago, Google)"),
+ EXPIRED_APPLE("Expired (Expired 2 days ago, Apple)"),
}
enum class DebugProPlanStatus(val label: String){
diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt
index 3318d50bf1..33f91e5a8f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt
@@ -135,7 +135,6 @@ class HomeActivity : ScreenLockActionBarActivity(),
@Inject lateinit var proStatusManager: ProStatusManager
@Inject lateinit var recipientRepository: RecipientRepository
@Inject lateinit var avatarUtils: AvatarUtils
- @Inject lateinit var startConversationNavigator: UINavigator
private val globalSearchViewModel by viewModels()
private val homeViewModel by viewModels()
@@ -294,7 +293,6 @@ class HomeActivity : ScreenLockActionBarActivity(),
val dialogsState by homeViewModel.dialogsState.collectAsState()
HomeDialogs(
dialogsState = dialogsState,
- startConversationNavigator = startConversationNavigator,
sendCommand = homeViewModel::onCommand
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt
index 45ab8a9a8d..31e7c693e7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDialogs.kt
@@ -2,12 +2,18 @@ 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
@@ -32,7 +38,6 @@ import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme
@Composable
fun HomeDialogs(
dialogsState: HomeViewModel.DialogsState,
- startConversationNavigator: UINavigator,
sendCommand: (HomeViewModel.Commands) -> Unit
) {
SessionMaterialTheme {
@@ -61,14 +66,24 @@ fun HomeDialogs(
if(dialogsState.showStartConversationSheet != null){
StartConversationSheet(
accountId = dialogsState.showStartConversationSheet.accountId,
- navigator = startConversationNavigator,
onDismissRequest = {
sendCommand(HomeViewModel.Commands.HideStartConversationSheet)
}
)
}
- if(dialogsState.proExpiringCTA != null){
+ // we need a delay before displaying this.
+ // Setting the delay in the VM does not account for render and it seems to appear immediately
+ var showExpiring by remember { mutableStateOf(false) }
+ LaunchedEffect(dialogsState.proExpiringCTA) {
+ showExpiring = false
+ if (dialogsState.proExpiringCTA != null) {
+ delay(1500)
+ showExpiring = true
+ }
+ }
+
+ if(showExpiring && dialogsState.proExpiringCTA != null){
val context = LocalContext.current
AnimatedSessionProCTA(
heroImageBg = R.drawable.cta_hero_generic_bg,
@@ -82,9 +97,9 @@ fun HomeDialogs(
.format()
.toString(),
features = listOf(
- CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)),
CTAFeature.Icon(stringResource(R.string.proFeatureListLongerMessages)),
CTAFeature.Icon(stringResource(R.string.proFeatureListPinnedConversations)),
+ CTAFeature.Icon(stringResource(R.string.proFeatureListAnimatedDisplayPicture)),
),
positiveButtonText = stringResource(R.string.update),
negativeButtonText = stringResource(R.string.close),
@@ -98,7 +113,18 @@ fun HomeDialogs(
)
}
- if(dialogsState.proExpiredCTA){
+ // we need a delay before displaying this.
+ // Setting the delay in the VM does not account for render and it seems to appear immediately
+ var showExpired by remember { mutableStateOf(false) }
+ LaunchedEffect(dialogsState.proExpiredCTA) {
+ showExpired = false
+ if (dialogsState.proExpiredCTA) {
+ delay(1500)
+ showExpired = true
+ }
+ }
+
+ if (showExpired && dialogsState.proExpiredCTA) {
val context = LocalContext.current
AnimatedSessionProCTA(
heroImageBg = R.drawable.cta_hero_generic_bg,
@@ -112,9 +138,9 @@ fun HomeDialogs(
.format()
.toString(),
features = listOf(
- CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)),
CTAFeature.Icon(stringResource(R.string.proFeatureListLongerMessages)),
CTAFeature.Icon(stringResource(R.string.proFeatureListPinnedConversations)),
+ CTAFeature.Icon(stringResource(R.string.proFeatureListAnimatedDisplayPicture)),
),
positiveButtonText = stringResource(R.string.renew),
negativeButtonText = stringResource(R.string.cancel),
diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt
index 4ecd116b9f..6906b3c7ed 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt
@@ -9,7 +9,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -50,7 +49,6 @@ import org.thoughtcrime.securesms.util.UserProfileModalData
import org.thoughtcrime.securesms.util.UserProfileUtils
import org.thoughtcrime.securesms.webrtc.CallManager
import org.thoughtcrime.securesms.webrtc.data.State
-import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
import javax.inject.Inject
@@ -172,7 +170,6 @@ class HomeViewModel @Inject constructor(
val validUntil = subscription.type.proStatus.validUntil ?: return@collect
if (validUntil.isBefore(now.plus(7, ChronoUnit.DAYS))) {
- delay(2000)
prefs.setHasSeenProExpiring()
_dialogsState.update { state ->
state.copy(
@@ -191,7 +188,6 @@ class HomeViewModel @Inject constructor(
// Check if now is within 30 days after expiry
if (now.isBefore(validUntil.plus(30, ChronoUnit.DAYS))) {
- delay(2000)
prefs.setHasSeenProExpired()
_dialogsState.update { state ->
state.copy(proExpiredCTA = true)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/startconversation/StartConversationSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/startconversation/StartConversationSheet.kt
index 6a9c7cc4f0..fc3652f386 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/home/startconversation/StartConversationSheet.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/home/startconversation/StartConversationSheet.kt
@@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -48,7 +49,6 @@ import org.thoughtcrime.securesms.ui.theme.PreviewTheme
fun StartConversationSheet(
modifier: Modifier = Modifier,
accountId: String,
- navigator: UINavigator,
onDismissRequest: () -> Unit,
){
val sheetState = rememberModalBottomSheetState(
@@ -71,7 +71,6 @@ fun StartConversationSheet(
) {
StartConversationNavHost(
accountId = accountId,
- navigator = navigator,
onClose = {
scope.launch {
sheetState.hide()
@@ -107,11 +106,12 @@ sealed interface StartConversationDestination {
@Composable
fun StartConversationNavHost(
accountId: String,
- navigator: UINavigator,
onClose: () -> Unit
){
SharedTransitionLayout {
val navController = rememberNavController()
+ val navigator: UINavigator =
+ remember { UINavigator() }
ObserveAsEvents(flow = navigator.navigationActions) { action ->
when (action) {
@@ -235,7 +235,6 @@ fun PreviewStartConversationSheet(){
StartConversationSheet(
accountId = "",
onDismissRequest = {},
- navigator = UINavigator()
)
}
}
\ No newline at end of file
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 62cbb09cbc..693d109e32 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt
@@ -561,15 +561,7 @@ fun Buttons(
modifier = Modifier.qaTag(R.string.qa_settings_item_pro),
colors = accentTextButtonColors()
) {
- // there is a special case when we have a subscription error or loading
- // but also no pro account
- if(subscriptionState.refreshState !is State.Success &&
- subscriptionState.type is SubscriptionType.NeverSubscribed
- ){
- sendCommand(ShowProErrorOrLoading)
- } else {
- activity?.push()
- }
+ activity?.push()
}
Divider()
@@ -1079,7 +1071,7 @@ private fun SettingsScreenPreview() {
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
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 d272784213..1c609572be 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt
@@ -585,53 +585,6 @@ class SettingsViewModel @Inject constructor(
showUrlDialog( "https://session.foundation/donate#app")
}
- is Commands.ShowProErrorOrLoading -> {
- when(_uiState.value.subscriptionState.refreshState){
- // if we are in a loading or refresh state we should show a dialog instead
- is State.Loading -> {
- _uiState.update {
- it.copy(
- showSimpleDialog = SimpleDialogData(
- title = Phrase.from(context.getText(R.string.proStatusLoading))
- .put(PRO_KEY, NonTranslatableStringConstants.PRO)
- .format().toString(),
- message = Phrase.from(context.getText(R.string.proStatusLoadingDescription))
- .put(PRO_KEY, NonTranslatableStringConstants.PRO)
- .format(),
- positiveText = context.getString(R.string.okay),
- positiveStyleDanger = false,
- )
- )
- }
- }
-
- is State.Error -> {
- _uiState.update {
- it.copy(
- showSimpleDialog = SimpleDialogData(
- title = Phrase.from(context.getText(R.string.proStatusError))
- .put(PRO_KEY, NonTranslatableStringConstants.PRO)
- .format().toString(),
- message = Phrase.from(context.getText(R.string.proStatusRefreshNetworkError))
- .put(PRO_KEY, NonTranslatableStringConstants.PRO)
- .format(),
- positiveText = context.getString(R.string.retry),
- negativeText = context.getString(R.string.helpSupport),
- positiveStyleDanger = false,
- showXIcon = true,
- onPositive = { refreshSubscriptionData() },
- onNegative = {
- showUrlDialog(ProStatusManager.URL_PRO_SUPPORT)
- }
- )
- )
- }
- }
-
- else -> {}
- }
- }
-
is Commands.HideSimpleDialog -> {
_uiState.update { it.copy(showSimpleDialog = null) }
}
@@ -718,8 +671,6 @@ class SettingsViewModel @Inject constructor(
data object OnDonateClicked: Commands
- data object ShowProErrorOrLoading: Commands
-
data class ClearData(val clearNetwork: Boolean): Commands
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/BaseProSettingsScreens.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/BaseProSettingsScreens.kt
index 71cf886ad2..ec7c688348 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/BaseProSettingsScreens.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/BaseProSettingsScreens.kt
@@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.preferences.prosettings
import androidx.annotation.DrawableRes
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
@@ -27,6 +29,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
@@ -60,26 +63,29 @@ import org.thoughtcrime.securesms.ui.components.inlineContentMap
@Composable
fun BaseProSettingsScreen(
disabled: Boolean,
+ hideHomeAppBar: Boolean = false,
onBack: () -> Unit,
onHeaderClick: (() -> Unit)? = null,
extraHeaderContent: @Composable (() -> Unit)? = null,
content: @Composable () -> Unit
){
Scaffold(
- topBar = {
- BackAppBar(
- title = "",
- backgroundColor = Color.Transparent,
- onBack = onBack,
- )
- },
+ topBar = if(!hideHomeAppBar){{
+ BackAppBar(
+ title = "",
+ backgroundColor = Color.Transparent,
+ onBack = onBack,
+ )
+ }} else {{}},
contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal),
) { paddings ->
Column(
modifier = Modifier
.fillMaxSize()
- .padding(top = paddings.calculateTopPadding() - LocalDimensions.current.appBarHeight)
+ .padding(top =
+ (paddings.calculateTopPadding() - LocalDimensions.current.appBarHeight)
+ .coerceAtLeast(0.dp))
.consumeWindowInsets(paddings)
.padding(
horizontal = LocalDimensions.current.spacing,
@@ -204,6 +210,7 @@ fun BaseNonOriginatingProSettingsScreen(
headerTitle: CharSequence?,
contentTitle: String?,
contentDescription: CharSequence?,
+ contentClick: (() -> Unit)? = null,
linkCellsInfo: String?,
linkCells: List = emptyList(),
) {
@@ -226,6 +233,15 @@ fun BaseNonOriginatingProSettingsScreen(
if (contentDescription != null) {
Spacer(Modifier.height(LocalDimensions.current.xxxsSpacing))
Text(
+ modifier = Modifier.then(
+ // make the component clickable is there is an action
+ if (contentClick != null) Modifier.clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = contentClick
+ )
+ else Modifier
+ ),
text = annotatedStringResource(contentDescription),
style = LocalType.current.base,
color = LocalColors.current.text,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt
index 792c8957bb..e8f190426f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/CancelPlanNonOriginating.kt
@@ -89,7 +89,7 @@ private fun PreviewUpdatePlan(
val context = LocalContext.current
CancelPlanNonOriginating (
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt
index e2246b8281..5e8c6c0b13 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/PlanConfirmationScreen.kt
@@ -160,7 +160,7 @@ private fun PreviewPlanConfirmation(
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsActivity.kt
index da7e5cbd20..710d638c2f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsActivity.kt
@@ -3,17 +3,13 @@ package org.thoughtcrime.securesms.preferences.prosettings
import android.content.Context
import android.content.Intent
import androidx.compose.runtime.Composable
+import androidx.core.content.IntentCompat
import dagger.hilt.android.AndroidEntryPoint
import org.thoughtcrime.securesms.FullComposeScreenLockActivity
-import org.thoughtcrime.securesms.ui.UINavigator
-import javax.inject.Inject
@AndroidEntryPoint
class ProSettingsActivity: FullComposeScreenLockActivity() {
- @Inject
- lateinit var navigator: UINavigator
-
companion object {
private const val EXTRA_START_DESTINATION = "start_destination"
@@ -29,12 +25,14 @@ class ProSettingsActivity: FullComposeScreenLockActivity() {
@Composable
override fun ComposeContent() {
- val startDestination = intent.getParcelableExtra(
- EXTRA_START_DESTINATION
+ val startDestination = IntentCompat.getParcelableExtra(
+ intent,
+ EXTRA_START_DESTINATION,
+ ProSettingsDestination::class.java
) ?: ProSettingsDestination.Home
ProSettingsNavHost(
- navigator = navigator,
+ hideHomeAppBar = false,
startDestination = startDestination,
onBack = this::finish
)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt
index 6cd615cbf8..d22f35c78c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsHomeScreen.kt
@@ -99,12 +99,14 @@ import java.time.Instant
@Composable
fun ProSettingsHomeScreen(
viewModel: ProSettingsViewModel,
+ hideHomeAppBar: Boolean,
onBack: () -> Unit,
) {
val data by viewModel.proSettingsUIState.collectAsState()
ProSettingsHome(
data = data,
+ hideHomeAppBar = hideHomeAppBar,
sendCommand = viewModel::onCommand,
onBack = onBack,
)
@@ -114,6 +116,7 @@ fun ProSettingsHomeScreen(
@Composable
fun ProSettingsHome(
data: ProSettingsViewModel.ProSettingsState,
+ hideHomeAppBar: Boolean,
sendCommand: (ProSettingsViewModel.Commands) -> Unit,
onBack: () -> Unit,
) {
@@ -122,11 +125,13 @@ fun ProSettingsHome(
BaseProSettingsScreen(
disabled = subscriptionType is SubscriptionType.Expired,
+ hideHomeAppBar = hideHomeAppBar,
onBack = onBack,
onHeaderClick = {
// 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 = {
@@ -142,7 +147,6 @@ fun ProSettingsHome(
when(subscriptionType){
is SubscriptionType.Active -> R.string.proStatusLoadingSubtitle
else -> R.string.checkingProStatus
- //todo PRO will need to handle never subscribed here
}))
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format().toString(),
@@ -189,6 +193,10 @@ fun ProSettingsHome(
) {
// Header for non-pro users
if(subscriptionType is SubscriptionType.NeverSubscribed) {
+ if(data.subscriptionState.refreshState !is State.Success){
+ Spacer(Modifier.height(LocalDimensions.current.smallSpacing))
+ }
+
Text(
text = Phrase.from(context.getText(R.string.proFullestPotential))
.put(APP_NAME_KEY, stringResource(R.string.app_name))
@@ -203,6 +211,7 @@ fun ProSettingsHome(
AccentFillButtonRect(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.theContinue),
+ enabled = data.subscriptionState.refreshState is State.Success,
onClick = { sendCommand(GoToChoosePlan) }
)
}
@@ -631,7 +640,8 @@ fun ProFeatures(
// Longer messages
ProFeatureItem(
title = stringResource(R.string.proLongerMessages),
- subtitle = annotatedStringResource(R.string.proLongerMessagesDescription),
+ subtitle = if(data is SubscriptionType.Active) annotatedStringResource(R.string.proLongerMessagesDescription)
+ else annotatedStringResource(R.string.nonProLongerMessagesDescription),
icon = R.drawable.ic_message_square,
iconGradientStart = primaryBlue,
iconGradientEnd = primaryPurple,
@@ -816,21 +826,20 @@ fun ProManage(
is SubscriptionType.Expired -> {
// the details depend on the loading/error state
- val renewIcon: @Composable BoxScope.() -> Unit = {
+ fun renewIcon(color: Color): @Composable BoxScope.() -> Unit = {
Icon(
modifier = Modifier.align(Alignment.Center)
.size(LocalDimensions.current.iconMedium)
.qaTag(R.string.qa_action_item_icon),
painter = painterResource(id = R.drawable.ic_circle_plus),
contentDescription = null,
- tint = LocalColors.current.text
+ tint = color
)
}
val (subtitle, subColor, icon) = when(subscriptionRefreshState){
is State.Loading -> Triple Unit>(
- //todo PRO need the ellipsis version of this string
- Phrase.from(LocalContext.current, R.string.checkingProStatus)
+ Phrase.from(LocalContext.current, R.string.checkingProStatusEllipsis)
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format().toString(),
LocalColors.current.text,
@@ -841,12 +850,12 @@ fun ProManage(
Phrase.from(LocalContext.current, R.string.errorCheckingProStatus)
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format().toString(),
- LocalColors.current.warning, renewIcon
+ LocalColors.current.warning, renewIcon(LocalColors.current.text)
)
is State.Success<*> -> Triple Unit>(
null,
- LocalColors.current.text, renewIcon
+ LocalColors.current.text, renewIcon(LocalColors.current.accent)
)
}
@@ -856,6 +865,8 @@ fun ProManage(
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format().toString()
),
+ titleColor = if(subscriptionRefreshState is State.Success ) LocalColors.current.accent
+ else LocalColors.current.text,
subtitle = if(subtitle == null) null else annotatedStringResource(subtitle),
subtitleColor = subColor,
endContent = {
@@ -906,7 +917,7 @@ fun PreviewProSettingsPro(
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -917,6 +928,7 @@ fun PreviewProSettingsPro(
refreshState = State.Success(Unit),
),
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
@@ -939,7 +951,7 @@ fun PreviewProSettingsProLoading(
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -950,6 +962,7 @@ fun PreviewProSettingsProLoading(
refreshState = State.Loading,
),
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
@@ -972,7 +985,7 @@ fun PreviewProSettingsProError(
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -983,6 +996,7 @@ fun PreviewProSettingsProError(
refreshState = State.Error(Exception()),
),
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
@@ -1001,7 +1015,7 @@ fun PreviewProSettingsExpired(
type = SubscriptionType.Expired(
expiredAt = Instant.now() - Duration.ofDays(14),
SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -1011,6 +1025,7 @@ fun PreviewProSettingsExpired(
refreshState = State.Success(Unit),
)
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
@@ -1029,7 +1044,7 @@ fun PreviewProSettingsExpiredLoading(
type = SubscriptionType.Expired(
expiredAt = Instant.now() - Duration.ofDays(14),
SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -1039,6 +1054,7 @@ fun PreviewProSettingsExpiredLoading(
refreshState = State.Loading,
)
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
@@ -1057,7 +1073,7 @@ fun PreviewProSettingsExpiredError(
type = SubscriptionType.Expired(
expiredAt = Instant.now() - Duration.ofDays(14),
SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -1067,6 +1083,7 @@ fun PreviewProSettingsExpiredError(
refreshState = State.Error(Exception()),
)
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
@@ -1086,6 +1103,7 @@ fun PreviewProSettingsNonPro(
refreshState = State.Success(Unit),
)
),
+ hideHomeAppBar = false,
sendCommand = {},
onBack = {},
)
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 efb35fe9f2..3123ee4292 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
@@ -7,11 +7,13 @@ import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.remember
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
+import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
-import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsDestination.CancelSubscription
@@ -53,26 +55,25 @@ sealed interface ProSettingsDestination: Parcelable {
data object RefundSubscription: ProSettingsDestination
}
+@Serializable object ProSettingsGraph
+
@SuppressLint("RestrictedApi")
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun ProSettingsNavHost(
- navigator: UINavigator,
startDestination: ProSettingsDestination = Home,
+ hideHomeAppBar: Boolean,
onBack: () -> Unit
){
SharedTransitionLayout {
val navController = rememberNavController()
- val scope = rememberCoroutineScope()
-
- // all screens within the Pro Flow can share the same VM
- val viewModel = hiltViewModel()
-
- val dialogsState by viewModel.dialogState.collectAsState()
+ val navigator: UINavigator = remember {
+ UINavigator()
+ }
val handleBack: () -> Unit = {
if (navController.previousBackStackEntry != null) {
- scope.launch { navigator.navigateUp() }
+ navController.navigateUp()
} else {
onBack() // Finish activity if at root
}
@@ -87,7 +88,7 @@ fun ProSettingsNavHost(
action.navOptions(this)
}
- NavigationAction.NavigateUp -> navController.navigateUp()
+ NavigationAction.NavigateUp -> handleBack()
is NavigationAction.NavigateToIntent -> {
navController.context.startActivity(action.intent)
@@ -97,58 +98,84 @@ fun ProSettingsNavHost(
}
}
- NavHost(navController = navController, startDestination = startDestination) {
- // Home
- horizontalSlideComposable {
- ProSettingsHomeScreen(
- viewModel = viewModel,
- onBack = onBack,
- )
- }
+ NavHost(navController = navController, startDestination = ProSettingsGraph) {
+ navigation(startDestination = startDestination) {
+ // Home
+ horizontalSlideComposable { entry ->
+ val viewModel = navController.proGraphViewModel(entry, navigator)
+ ProSettingsHomeScreen(
+ viewModel = viewModel,
+ hideHomeAppBar = hideHomeAppBar,
+ onBack = onBack,
+ )
+ }
- // Subscription plan selection
- horizontalSlideComposable {
- UpdatePlanScreen(
- viewModel = viewModel,
- onBack = handleBack,
- )
- }
- horizontalSlideComposable {
- GetOrRenewPlanScreen(
- viewModel = viewModel,
- onBack = handleBack,
- )
- }
+ // Subscription plan selection
+ horizontalSlideComposable { entry ->
+ val viewModel = navController.proGraphViewModel(entry, navigator)
+ UpdatePlanScreen(
+ viewModel = viewModel,
+ onBack = handleBack,
+ )
+ }
+ horizontalSlideComposable { entry ->
+ val viewModel = navController.proGraphViewModel(entry, navigator)
+ GetOrRenewPlanScreen(
+ viewModel = viewModel,
+ onBack = handleBack,
+ )
+ }
- // Subscription plan confirmation
- horizontalSlideComposable {
- PlanConfirmationScreen(
- viewModel = viewModel,
- onBack = handleBack,
- )
- }
+ // Subscription plan confirmation
+ horizontalSlideComposable { entry ->
+ val viewModel = navController.proGraphViewModel(entry, navigator)
+ PlanConfirmationScreen(
+ viewModel = viewModel,
+ onBack = handleBack,
+ )
+ }
- // Refund
- horizontalSlideComposable {
- RefundPlanScreen(
- viewModel = viewModel,
- onBack = handleBack,
- )
- }
+ // Refund
+ horizontalSlideComposable { entry ->
+ val viewModel = navController.proGraphViewModel(entry, navigator)
+ RefundPlanScreen(
+ viewModel = viewModel,
+ onBack = handleBack,
+ )
+ }
- // Cancellation
- horizontalSlideComposable {
- CancelPlanScreen(
- viewModel = viewModel,
- onBack = handleBack,
- )
+ // Cancellation
+ horizontalSlideComposable { entry ->
+ val viewModel = navController.proGraphViewModel(entry, navigator)
+ CancelPlanScreen(
+ viewModel = viewModel,
+ onBack = handleBack,
+ )
+ }
}
}
// Dialogs
- ProSettingsDialogs(
- dialogsState = dialogsState,
- sendCommand = viewModel::onCommand,
- )
+ // the composable need to wait until the graph has been rendered
+ val graphReady = remember(navController.currentBackStackEntryAsState().value) {
+ runCatching { navController.getBackStackEntry(ProSettingsGraph) }.getOrNull()
+ }
+ graphReady?.let { entry ->
+ val vm = navController.proGraphViewModel(entry, navigator)
+ val dialogsState by vm.dialogState.collectAsState()
+ ProSettingsDialogs(dialogsState = dialogsState, sendCommand = vm::onCommand)
+ }
}
+}
+
+@Composable
+fun NavController.proGraphViewModel(
+ entry: androidx.navigation.NavBackStackEntry,
+ navigator: UINavigator
+): ProSettingsViewModel {
+ val graphEntry = remember(entry) { getBackStackEntry(ProSettingsGraph) }
+ return hiltViewModel<
+ ProSettingsViewModel,
+ ProSettingsViewModel.Factory
+ >(graphEntry) { factory -> factory.create(navigator) }
}
\ No newline at end of file
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 3464985b08..982a7afb3b 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
@@ -8,6 +8,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavOptionsBuilder
import com.squareup.phrase.Phrase
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -31,6 +34,7 @@ import org.session.libsession.utilities.StringSubstitutionConstants.PRO_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.SELECTED_PLAN_LENGTH_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.SELECTED_PLAN_LENGTH_SINGULAR_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY
+import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.Commands.ShowOpenUrlDialog
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.pro.SubscriptionState
@@ -48,15 +52,20 @@ import javax.inject.Inject
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
-@HiltViewModel
-class ProSettingsViewModel @Inject constructor(
- @ApplicationContext private val context: Context,
- private val navigator: UINavigator,
+@HiltViewModel(assistedFactory = ProSettingsViewModel.Factory::class)
+class ProSettingsViewModel @AssistedInject constructor(
+ @Assisted private val navigator: UINavigator,
+ @param:ApplicationContext private val context: Context,
private val proStatusManager: ProStatusManager,
private val subscriptionCoordinator: SubscriptionCoordinator,
private val dateUtils: DateUtils
) : ViewModel() {
+ @AssistedFactory
+ interface Factory {
+ fun create(navigator: UINavigator): ProSettingsViewModel
+ }
+
private val _proSettingsUIState: MutableStateFlow = MutableStateFlow(ProSettingsState())
val proSettingsUIState: StateFlow = _proSettingsUIState
@@ -284,6 +293,7 @@ class ProSettingsViewModel @Inject constructor(
.put(PRO_KEY, NonTranslatableStringConstants.PRO)
.format().toString() to
Phrase.from(context.getText(R.string.proStatusRenewError))
+ .put(PRO_KEY, NonTranslatableStringConstants.PRO)
.put(APP_NAME_KEY, context.getString(R.string.app_name))
.format()
//todo PRO will need to handle never subscribed here
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt
index 80ae0b0f84..11b77926b7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/RefundPlanNonOriginating.kt
@@ -100,7 +100,7 @@ private fun PreviewUpdatePlan(
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt
index bc16f1a34d..6096804e94 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/chooseplan/ChoosePlanNoBilling.kt
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsViewModel.C
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.pro.SubscriptionDetails
import org.thoughtcrime.securesms.pro.SubscriptionType
+import org.thoughtcrime.securesms.ui.OpenURLAlertDialog
import org.thoughtcrime.securesms.ui.components.iconExternalLink
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
@@ -203,6 +204,9 @@ fun ChoosePlanNoBilling(
},
contentTitle = contentTitle,
contentDescription = contentDescription,
+ contentClick = {
+ sendCommand(ShowOpenUrlDialog("https://getsession.org/pro-roadmap"))
+ },
linkCellsInfo = cellsInfo,
linkCells = cells
)
@@ -220,7 +224,7 @@ private fun PreviewNonOrigExpiredUpdatePlan(
subscription = SubscriptionType.Expired(
expiredAt = Instant.now() - Duration.ofDays(14),
SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
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 8cb7861a4c..ab0947d8ed 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
@@ -132,7 +132,7 @@ private fun PreviewUpdatePlan(
),
duration = ProSubscriptionDuration.THREE_MONTHS,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
diff --git a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt
index ba363f5d9d..a71d352678 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/pro/ProStatusManager.kt
@@ -46,7 +46,10 @@ class ProStatusManager @Inject constructor(
(TextSecurePreferences.events.filter { it == TextSecurePreferences.DEBUG_PRO_PLAN_STATUS } as Flow<*>)
.onStart { emit(Unit) }
.map { prefs.getDebugProPlanStatus() },
- ){ selfRecipient, debugSubscription, debugProPlanStatus ->
+ (TextSecurePreferences.events.filter { it == TextSecurePreferences.SET_FORCE_CURRENT_USER_PRO } as Flow<*>)
+ .onStart { emit(Unit) }
+ .map { prefs.forceCurrentUserAsPro() },
+ ){ selfRecipient, debugSubscription, debugProPlanStatus, forceCurrentUserAsPro ->
//todo PRO implement properly
val subscriptionState = debugSubscription ?: DebugMenuViewModel.DebugSubscriptionStatus.AUTO_GOOGLE
@@ -56,7 +59,8 @@ class ProStatusManager @Inject constructor(
else -> State.Success(Unit)
}
- if(selfRecipient.proStatus is ProStatus.None){
+ if(!forceCurrentUserAsPro){
+ //todo PRO this is where we should get the real state
SubscriptionState(
type = SubscriptionType.NeverSubscribed,
refreshState = proDataStatus
@@ -96,6 +100,22 @@ class ProStatusManager @Inject constructor(
)
)
+ DebugMenuViewModel.DebugSubscriptionStatus.EXPIRING_GOOGLE_LATER -> SubscriptionType.Active.Expiring(
+ proStatus = ProStatus.Pro(
+ visible = true,
+ validUntil = Instant.now() + Duration.ofDays(40),
+ ),
+ duration = ProSubscriptionDuration.TWELVE_MONTHS,
+ subscriptionDetails = SubscriptionDetails(
+ device = "Android",
+ store = "Google Play Store",
+ platform = "Google",
+ platformAccount = "Google account",
+ subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY",
+ refundUrl = "https://getsession.org/android-refund",
+ )
+ )
+
DebugMenuViewModel.DebugSubscriptionStatus.AUTO_APPLE -> SubscriptionType.Active.AutoRenewing(
proStatus = ProStatus.Pro(
visible = true,
@@ -103,7 +123,7 @@ class ProStatusManager @Inject constructor(
),
duration = ProSubscriptionDuration.ONE_MONTH,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -119,7 +139,7 @@ class ProStatusManager @Inject constructor(
),
duration = ProSubscriptionDuration.ONE_MONTH,
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
@@ -139,10 +159,21 @@ class ProStatusManager @Inject constructor(
refundUrl = "https://getsession.org/android-refund",
)
)
+ DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_EARLIER -> SubscriptionType.Expired(
+ expiredAt = Instant.now() - Duration.ofDays(60),
+ subscriptionDetails = SubscriptionDetails(
+ device = "Android",
+ store = "Google Play Store",
+ platform = "Google",
+ platformAccount = "Google account",
+ subscriptionUrl = "https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY",
+ refundUrl = "https://getsession.org/android-refund",
+ )
+ )
DebugMenuViewModel.DebugSubscriptionStatus.EXPIRED_APPLE -> SubscriptionType.Expired(
expiredAt = Instant.now() - Duration.ofDays(14),
subscriptionDetails = SubscriptionDetails(
- device = "iPhone",
+ device = "iOS",
store = "Apple App Store",
platform = "Apple",
platformAccount = "Apple Account",
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt
index e0290112aa..ea1077065e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt
@@ -151,7 +151,7 @@ fun AlertDialogContent(
Icon(
painter = painterResource(id = R.drawable.ic_x),
tint = LocalColors.current.text,
- contentDescription = "back"
+ contentDescription = stringResource(R.string.close)
)
}
}
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 c19697a5d9..7dfd639686 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt
@@ -15,29 +15,38 @@ 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.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
+import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -51,6 +60,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
@@ -62,17 +72,22 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideSubcomposition
import com.bumptech.glide.integration.compose.RequestState
import com.squareup.phrase.Phrase
+import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsession.utilities.NonTranslatableStringConstants
import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.APP_PRO_KEY
+import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsDestination
+import org.thoughtcrime.securesms.preferences.prosettings.ProSettingsNavHost
import org.thoughtcrime.securesms.ui.components.AccentFillButtonRect
import org.thoughtcrime.securesms.ui.components.Avatar
+import org.thoughtcrime.securesms.ui.components.BaseBottomSheet
import org.thoughtcrime.securesms.ui.components.FillButtonRect
import org.thoughtcrime.securesms.ui.components.QrImage
import org.thoughtcrime.securesms.ui.components.TertiaryFillButtonRect
@@ -205,6 +220,10 @@ private fun PreviewProBadgeText(
}
}
+/**
+ * This composable comprises of the CTA itself
+ * and the bottom sheet with the whole pro settings content
+ */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SessionProCTA(
@@ -212,100 +231,164 @@ fun SessionProCTA(
textContent: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
title: String = stringResource(R.string.upgradeTo),
+ titleColor: Color = LocalColors.current.text,
badgeAtStart: Boolean = false,
disabled: Boolean = false,
features: List = emptyList(),
positiveButtonText: String? = stringResource(R.string.theContinue),
negativeButtonText: String? = stringResource(R.string.cancel),
- onUpgrade: () -> Unit = {},
+ onUpgrade: (() -> Unit)? = null,
onCancel: () -> Unit = {},
){
- BasicAlertDialog(
- modifier = modifier,
- onDismissRequest = onCancel,
- content = {
- DialogBg {
- Column(modifier = Modifier.fillMaxWidth()) {
- // hero image
- BottomFadingEdgeBox(
- fadingEdgeHeight = 70.dp,
- fadingColor = LocalColors.current.backgroundSecondary,
- content = { _ ->
- content()
- },
- )
+ val sheetState = rememberModalBottomSheetState(
+ skipPartiallyExpanded = true
+ )
+ val scope = rememberCoroutineScope()
- // content
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(LocalDimensions.current.smallSpacing)
- ) {
- // title
- ProBadgeText(
- modifier = Modifier.align(Alignment.CenterHorizontally),
- text = title,
- badgeAtStart = badgeAtStart,
- badgeColors = if(disabled) proBadgeColorDisabled() else proBadgeColorStandard(),
- )
+ // 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) }
- Spacer(Modifier.height(LocalDimensions.current.contentSpacing))
+ // default handling of the upgrade button
+ val defaultUpgrade: () -> Unit = {
+ showProSheet = true
+ showDialog = false
+ }
- // main message
- textContent()
+ if(showDialog) {
+ BasicAlertDialog(
+ modifier = modifier,
+ onDismissRequest = onCancel,
+ content = {
+ DialogBg {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ // hero image
+ BottomFadingEdgeBox(
+ fadingEdgeHeight = 70.dp,
+ fadingColor = LocalColors.current.backgroundSecondary,
+ content = { _ ->
+ content()
+ },
+ )
- Spacer(Modifier.height(LocalDimensions.current.contentSpacing))
+ // content
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(LocalDimensions.current.smallSpacing)
+ ) {
+ // title
+ ProBadgeText(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ text = title,
+ textStyle = LocalType.current.h5.copy(color = titleColor),
+ badgeAtStart = badgeAtStart,
+ badgeColors = if (disabled) proBadgeColorDisabled() else proBadgeColorStandard(),
+ )
- // features
- if(features.isNotEmpty()) {
- features.forEachIndexed { index, feature ->
- ProCTAFeature(data = feature)
- if (index < features.size - 1) {
- Spacer(Modifier.height(LocalDimensions.current.xsSpacing))
- }
- }
+ Spacer(Modifier.height(LocalDimensions.current.contentSpacing))
+
+ // main message
+ textContent()
Spacer(Modifier.height(LocalDimensions.current.contentSpacing))
- }
- // buttons
- Row(
- Modifier.height(IntrinsicSize.Min)
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(
- LocalDimensions.current.xsSpacing,
- Alignment.CenterHorizontally
- ),
- ) {
- positiveButtonText?.let {
- AccentFillButtonRect(
- modifier = Modifier.then(
- if(negativeButtonText != null)
- Modifier.weight(1f)
- else Modifier
- ).shimmerOverlay(),
- text = it,
- onClick = onUpgrade
- )
+ // features
+ if (features.isNotEmpty()) {
+ features.forEachIndexed { index, feature ->
+ ProCTAFeature(data = feature)
+ if (index < features.size - 1) {
+ Spacer(Modifier.height(LocalDimensions.current.xsSpacing))
+ }
+ }
+
+ Spacer(Modifier.height(LocalDimensions.current.contentSpacing))
}
- negativeButtonText?.let {
- TertiaryFillButtonRect(
- modifier = Modifier.then(
- if(positiveButtonText != null)
- Modifier.weight(1f)
- else Modifier
- ),
- text = it,
- onClick = onCancel
- )
+ // buttons
+ Row(
+ Modifier.height(IntrinsicSize.Min)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(
+ LocalDimensions.current.xsSpacing,
+ Alignment.CenterHorizontally
+ ),
+ ) {
+ positiveButtonText?.let {
+ AccentFillButtonRect(
+ modifier = Modifier.then(
+ if (negativeButtonText != null)
+ Modifier.weight(1f)
+ else Modifier
+ ).shimmerOverlay(),
+ text = it,
+ onClick = onUpgrade ?: defaultUpgrade
+ )
+ }
+
+ negativeButtonText?.let {
+ TertiaryFillButtonRect(
+ modifier = Modifier.then(
+ if (positiveButtonText != null)
+ Modifier.weight(1f)
+ else Modifier
+ ),
+ text = it,
+ onClick = onCancel
+ )
+ }
}
}
}
}
}
+ )
+ }
+
+ if(showProSheet) {
+ val dismissSheet: () -> Unit = {
+ scope.launch {
+ sheetState.hide()
+ onCancel()
+ }
+ }
+
+ BaseBottomSheet(
+ modifier = modifier,
+ sheetState = sheetState,
+ dragHandle = null,
+ onDismissRequest = dismissSheet
+ ) {
+ BoxWithConstraints(modifier = modifier) {
+ val topInset = WindowInsets.safeDrawing.asPaddingValues().calculateTopPadding()
+ val targetHeight =
+ (this.maxHeight - topInset) * 0.94f // sheet should take up 94% of the height, without the status bar
+ Box(
+ modifier = Modifier.height(targetHeight),
+ contentAlignment = Alignment.TopCenter
+ ) {
+ ProSettingsNavHost(
+ startDestination = ProSettingsDestination.Home,
+ hideHomeAppBar = true,
+ onBack = dismissSheet
+ )
+
+ IconButton(
+ onClick = dismissSheet,
+ modifier = Modifier.align(Alignment.TopEnd)
+ .padding(10.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_x),
+ tint = LocalColors.current.text,
+ contentDescription = stringResource(R.string.close)
+ )
+ }
+ }
+ }
}
- )
+ }
}
sealed interface CTAFeature {
@@ -332,7 +415,7 @@ fun SimpleSessionProCTA(
features: List = emptyList(),
positiveButtonText: String? = stringResource(R.string.theContinue),
negativeButtonText: String? = stringResource(R.string.cancel),
- onUpgrade: () -> Unit = {},
+ onUpgrade: (() -> Unit)? = null,
onCancel: () -> Unit = {},
){
SessionProCTA(
@@ -385,7 +468,7 @@ fun AnimatedSessionProCTA(
features: List = emptyList(),
positiveButtonText: String? = stringResource(R.string.theContinue),
negativeButtonText: String? = stringResource(R.string.cancel),
- onUpgrade: () -> Unit = {},
+ onUpgrade: (() -> Unit)? = null,
onCancel: () -> Unit = {},
){
SessionProCTA(
@@ -413,6 +496,7 @@ fun AnimatedSessionProCTA(
positiveButtonText = positiveButtonText,
negativeButtonText = negativeButtonText,
title = title,
+ titleColor = if(disabled) LocalColors.current.disabled else LocalColors.current.text,
badgeAtStart = badgeAtStart,
disabled = disabled
)
@@ -465,7 +549,6 @@ fun CTAAnimatedImages(
@Composable
fun GenericProCTA(
onDismissRequest: () -> Unit,
- onPostAction: (() -> Unit)? = null // a function for optional code once an action has been taken
){
val context = LocalContext.current
AnimatedSessionProCTA(
@@ -481,11 +564,6 @@ fun GenericProCTA(
CTAFeature.Icon(stringResource(R.string.proFeatureListLongerMessages)),
CTAFeature.RainbowIcon(stringResource(R.string.proFeatureListLoadsMore)),
),
- onUpgrade = {
- onDismissRequest()
- onPostAction?.invoke()
- //todo PRO go to screen once it exists
- },
onCancel = {
onDismissRequest()
}
@@ -508,10 +586,6 @@ fun LongMessageProCTA(
CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)),
CTAFeature.RainbowIcon(stringResource(R.string.proFeatureListLoadsMore)),
),
- onUpgrade = {
- onDismissRequest()
- //todo PRO go to screen once it exists
- },
onCancel = {
onDismissRequest()
}
@@ -535,10 +609,6 @@ fun AnimatedProfilePicProCTA(
CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)),
CTAFeature.RainbowIcon(stringResource(R.string.proFeatureListLoadsMore)),
),
- onUpgrade = {
- onDismissRequest()
- //todo PRO go to screen once it exists
- },
onCancel = {
onDismissRequest()
}
@@ -574,10 +644,6 @@ fun PinProCTA(
CTAFeature.Icon(stringResource(R.string.proFeatureListLargerGroups)),
CTAFeature.RainbowIcon(stringResource(R.string.proFeatureListLoadsMore)),
),
- onUpgrade = {
- onDismissRequest()
- //todo PRO go to screen once it exists
- },
onCancel = {
onDismissRequest()
}
@@ -953,22 +1019,25 @@ fun SessionProSettingsHeader(
Spacer(Modifier.height(LocalDimensions.current.xsSpacing))
- Row(
- modifier = Modifier.height(LocalDimensions.current.smallSpacing)
- ) {
- Image(
- painter = painterResource(R.drawable.ic_session),
- contentDescription = null,
- colorFilter = ColorFilter.tint(LocalColors.current.text)
- )
+ // Force the row to remain in LTR to preserve the image+icon order
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
+ Row(
+ modifier = Modifier.height(LocalDimensions.current.smallSpacing)
+ ) {
+ Image(
+ painter = painterResource(R.drawable.ic_session),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(LocalColors.current.text)
+ )
- Spacer(Modifier.width(LocalDimensions.current.xxxsSpacing))
+ Spacer(Modifier.width(LocalDimensions.current.xxxsSpacing))
- ProBadge(
- colors = proBadgeColorStandard().copy(
- backgroundColor = color
+ ProBadge(
+ colors = proBadgeColorStandard().copy(
+ backgroundColor = color
+ )
)
- )
+ }
}
extraContent?.let{
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/UINavigator.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/UINavigator.kt
index 599873491d..6fdcf5ce90 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/UINavigator.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/UINavigator.kt
@@ -5,10 +5,10 @@ import androidx.navigation.NavOptionsBuilder
import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
+import org.session.libsignal.utilities.Log
import javax.inject.Inject
-@ActivityRetainedScoped
-class UINavigator @Inject constructor() {
+class UINavigator () {
private val _navigationActions = Channel>()
val navigationActions = _navigationActions.receiveAsFlow()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/BottomSheets.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/BottomSheets.kt
index 1cfb7701cb..8100afa6f0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/BottomSheets.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/BottomSheets.kt
@@ -62,8 +62,8 @@ fun BaseBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
shape = RoundedCornerShape(
- topStart = LocalDimensions.current.xsSpacing,
- topEnd = LocalDimensions.current.xsSpacing
+ topStart = LocalDimensions.current.contentSpacing,
+ topEnd = LocalDimensions.current.contentSpacing
),
dragHandle = dragHandle,
containerColor = LocalColors.current.backgroundSecondary
diff --git a/app/src/main/res/drawable/ic_circle_help.xml b/app/src/main/res/drawable/ic_circle_help.xml
index 3b3b13768b..1fba16ac74 100644
--- a/app/src/main/res/drawable/ic_circle_help.xml
+++ b/app/src/main/res/drawable/ic_circle_help.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/app/src/main/res/drawable/ic_question_custom.xml b/app/src/main/res/drawable/ic_question_custom.xml
index 148e732324..1f9cfc20e0 100644
--- a/app/src/main/res/drawable/ic_question_custom.xml
+++ b/app/src/main/res/drawable/ic_question_custom.xml
@@ -1,4 +1,4 @@
-
+