mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-25 12:17:22 +00:00
Remove separate controllers and consolidate logic.
This commit is contained in:
committed by
Jeffrey Starke
parent
369085e162
commit
9b517a14cb
@@ -47,7 +47,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
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
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -61,7 +60,6 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -104,9 +102,8 @@ import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceExitActivity
|
||||
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
|
||||
import org.thoughtcrime.securesms.main.CallsNavHost
|
||||
import org.thoughtcrime.securesms.main.ChatsNavHost
|
||||
import org.thoughtcrime.securesms.main.EmptyDetailScreen
|
||||
import org.thoughtcrime.securesms.main.DetailsScreenNavHost
|
||||
import org.thoughtcrime.securesms.main.InsetsViewModelUpdater
|
||||
import org.thoughtcrime.securesms.main.MainBottomChrome
|
||||
import org.thoughtcrime.securesms.main.MainBottomChromeCallback
|
||||
import org.thoughtcrime.securesms.main.MainBottomChromeState
|
||||
@@ -125,7 +122,11 @@ import org.thoughtcrime.securesms.main.MainToolbarViewModel
|
||||
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder
|
||||
import org.thoughtcrime.securesms.main.NavigationBarSpacerCompat
|
||||
import org.thoughtcrime.securesms.main.SnackbarState
|
||||
import org.thoughtcrime.securesms.main.StoriesNavHost
|
||||
import org.thoughtcrime.securesms.main.callNavGraphBuilder
|
||||
import org.thoughtcrime.securesms.main.chatNavGraphBuilder
|
||||
import org.thoughtcrime.securesms.main.navigateToDetailLocation
|
||||
import org.thoughtcrime.securesms.main.rememberDetailNavHostController
|
||||
import org.thoughtcrime.securesms.main.storiesNavGraphBuilder
|
||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphone
|
||||
@@ -305,7 +306,6 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
val mainToolbarState by toolbarViewModel.state.collectAsStateWithLifecycle()
|
||||
val megaphone by mainNavigationViewModel.megaphone.collectAsStateWithLifecycle()
|
||||
val mainNavigationState by mainNavigationViewModel.mainNavigationState.collectAsStateWithLifecycle()
|
||||
val mainNavigationDetailLocation by mainNavigationViewModel.detailLocation.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(mainNavigationState.currentListLocation) {
|
||||
when (mainNavigationState.currentListLocation) {
|
||||
@@ -353,6 +353,35 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
)
|
||||
|
||||
val mutableInteractionSource = remember { MutableInteractionSource() }
|
||||
val mainNavigationDetailLocation by mainNavigationViewModel.detailLocation.collectAsStateWithLifecycle(mainNavigationViewModel.earlyNavigationDetailLocationRequested ?: MainNavigationDetailLocation.Empty)
|
||||
|
||||
val chatsNavHostController = rememberDetailNavHostController {
|
||||
chatNavGraphBuilder()
|
||||
}
|
||||
|
||||
val callsNavHostController = rememberDetailNavHostController {
|
||||
callNavGraphBuilder(it)
|
||||
}
|
||||
|
||||
val storiesNavHostController = rememberDetailNavHostController {
|
||||
storiesNavGraphBuilder()
|
||||
}
|
||||
|
||||
LaunchedEffect(mainNavigationDetailLocation) {
|
||||
mainNavigationViewModel.clearEarlyDetailLocation()
|
||||
when (mainNavigationDetailLocation) {
|
||||
is MainNavigationDetailLocation.Empty -> {
|
||||
when (mainNavigationState.currentListLocation) {
|
||||
MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> chatsNavHostController
|
||||
MainNavigationListLocation.CALLS -> callsNavHostController
|
||||
MainNavigationListLocation.STORIES -> storiesNavHostController
|
||||
}.navigateToDetailLocation(mainNavigationDetailLocation)
|
||||
}
|
||||
is MainNavigationDetailLocation.Chats -> chatsNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
|
||||
is MainNavigationDetailLocation.Calls -> callsNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
|
||||
is MainNavigationDetailLocation.Stories -> storiesNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(mainNavigationDetailLocation) {
|
||||
if (paneExpansionState.currentAnchor == listOnlyAnchor && wrappedNavigator.currentDestination?.pane == ThreePaneScaffoldRole.Primary) {
|
||||
@@ -366,6 +395,8 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
}
|
||||
|
||||
InsetsViewModelUpdater()
|
||||
|
||||
AppScaffold(
|
||||
navigator = wrappedNavigator,
|
||||
paneExpansionState = paneExpansionState,
|
||||
@@ -466,31 +497,24 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
},
|
||||
detailContent = {
|
||||
when (val location = mainNavigationDetailLocation) {
|
||||
MainNavigationDetailLocation.Empty -> {
|
||||
EmptyDetailScreen(contentLayoutData)
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Chats -> {
|
||||
ChatsNavHost(
|
||||
currentDestination = location,
|
||||
when (mainNavigationState.currentListLocation) {
|
||||
MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> {
|
||||
DetailsScreenNavHost(
|
||||
navHostController = chatsNavHostController,
|
||||
contentLayoutData = contentLayoutData
|
||||
)
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Calls -> {
|
||||
CallsNavHost(
|
||||
currentDestination = location,
|
||||
MainNavigationListLocation.CALLS -> {
|
||||
DetailsScreenNavHost(
|
||||
navHostController = callsNavHostController,
|
||||
contentLayoutData = contentLayoutData
|
||||
)
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Stories -> {
|
||||
val storiesNavigationController = rememberNavController()
|
||||
|
||||
StoriesNavHost(
|
||||
navHostController = storiesNavigationController,
|
||||
startDestination = location,
|
||||
MainNavigationListLocation.STORIES -> {
|
||||
DetailsScreenNavHost(
|
||||
navHostController = storiesNavHostController,
|
||||
contentLayoutData = contentLayoutData
|
||||
)
|
||||
}
|
||||
@@ -561,11 +585,11 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
)
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Calls.CallLinkDetails -> {
|
||||
is MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails -> {
|
||||
startActivity(CallLinkDetailsActivity.createIntent(this, detailLocation.callLinkRoomId))
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Calls.EditCallLinkName -> {
|
||||
is MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName -> {
|
||||
error("Unexpected subroute EditCallLinkName.")
|
||||
}
|
||||
|
||||
@@ -787,7 +811,6 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
|
||||
private fun handleConversationIntent(intent: Intent) {
|
||||
if (ConversationIntents.isConversationIntent(intent)) {
|
||||
Log.d(TAG, "Got conversation intent. Navigating to conversation.")
|
||||
mainNavigationViewModel.goTo(MainNavigationListLocation.CHATS)
|
||||
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Chats.Conversation(ConversationIntents.readArgsFromBundle(intent.extras!!)))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class CallLinkDetailsActivity : FragmentActivity() {
|
||||
private inner class Router : MainNavigationRouter {
|
||||
override fun goTo(location: MainNavigationDetailLocation) {
|
||||
when (location) {
|
||||
is MainNavigationDetailLocation.Calls.EditCallLinkName -> {
|
||||
is MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName -> {
|
||||
EditCallLinkNameDialogFragment().apply {
|
||||
arguments = bundleOf(EditCallLinkNameDialogFragment.ARG_NAME to viewModel.nameSnapshot)
|
||||
}.show(supportFragmentManager, null)
|
||||
|
||||
@@ -114,7 +114,7 @@ class DefaultCallLinkDetailsCallback(
|
||||
}
|
||||
|
||||
override fun onEditNameClicked() {
|
||||
router.goTo(MainNavigationDetailLocation.Calls.EditCallLinkName(callLinkRoomId = viewModel.recipientSnapshot!!.requireCallLinkRoomId()))
|
||||
router.goTo(MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName(callLinkRoomId = viewModel.recipientSnapshot!!.requireCallLinkRoomId()))
|
||||
}
|
||||
|
||||
override fun onShareClicked() {
|
||||
|
||||
@@ -320,7 +320,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
if (viewModel.selectionStateSnapshot.isNotEmpty(binding.recycler.adapter!!.itemCount)) {
|
||||
viewModel.toggleSelected(callLogRow.id)
|
||||
} else {
|
||||
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Calls.CallLinkDetails(callLogRow.record.roomId))
|
||||
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails(callLogRow.record.roomId))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.main.InsetsViewModel
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* A specialized [ConstraintLayout] that sets guidelines based on the window insets provided
|
||||
@@ -64,6 +66,7 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
|
||||
|
||||
private var insets: WindowInsetsCompat? = null
|
||||
private var windowTypes: Int = InsetAwareConstraintLayout.windowTypes
|
||||
private var verticalInsetOverride: InsetsViewModel.Insets = InsetsViewModel.Insets.Zero
|
||||
|
||||
private val windowInsetsListener = androidx.core.view.OnApplyWindowInsetsListener { _, insets ->
|
||||
this.insets = insets
|
||||
@@ -127,6 +130,22 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun applyInsets(insets: InsetsViewModel.Insets) {
|
||||
verticalInsetOverride = insets
|
||||
|
||||
if (this.insets != null) {
|
||||
applyInsets(this.insets!!.getInsets(windowTypes), this.insets!!.getInsets(keyboardType))
|
||||
}
|
||||
}
|
||||
|
||||
fun clearVerticalInsetOverride() {
|
||||
verticalInsetOverride = InsetsViewModel.Insets.Zero
|
||||
|
||||
if (this.insets != null) {
|
||||
applyInsets(this.insets!!.getInsets(windowTypes), this.insets!!.getInsets(keyboardType))
|
||||
}
|
||||
}
|
||||
|
||||
fun addKeyboardStateListener(listener: KeyboardStateListener) {
|
||||
keyboardStateListeners += listener
|
||||
}
|
||||
@@ -146,8 +165,8 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
|
||||
private fun applyInsets(windowInsets: Insets, keyboardInsets: Insets) {
|
||||
val isLtr = ViewUtil.isLtr(this)
|
||||
|
||||
val statusBar = windowInsets.top
|
||||
val navigationBar = windowInsets.bottom
|
||||
val statusBar = if (verticalInsetOverride == InsetsViewModel.Insets.Zero) windowInsets.top else verticalInsetOverride.statusBar.roundToInt()
|
||||
val navigationBar = if (verticalInsetOverride == InsetsViewModel.Insets.Zero) windowInsets.bottom else verticalInsetOverride.navBar.roundToInt()
|
||||
val parentStart = if (isLtr) windowInsets.left else windowInsets.right
|
||||
val parentEnd = if (isLtr) windowInsets.right else windowInsets.left
|
||||
|
||||
@@ -156,7 +175,9 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
|
||||
parentStartGuideline?.setGuidelineBegin(parentStart)
|
||||
parentEndGuideline?.setGuidelineEnd(parentEnd)
|
||||
|
||||
windowInsetsListeners.forEach { it.onApplyWindowInsets(statusBar, navigationBar, parentStart, parentEnd) }
|
||||
windowInsetsListeners.forEach {
|
||||
it.onApplyWindowInsets(statusBar, navigationBar, parentStart, parentEnd)
|
||||
}
|
||||
|
||||
if (keyboardInsets.bottom > 0) {
|
||||
setKeyboardHeight(keyboardInsets.bottom)
|
||||
|
||||
@@ -257,6 +257,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
|
||||
import org.thoughtcrime.securesms.longmessage.LongMessageFragment
|
||||
import org.thoughtcrime.securesms.main.InsetsViewModel
|
||||
import org.thoughtcrime.securesms.main.MainNavigationListLocation
|
||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
@@ -349,6 +350,7 @@ import org.thoughtcrime.securesms.util.visible
|
||||
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil
|
||||
import org.thoughtcrime.securesms.window.WindowSizeClass
|
||||
import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
@@ -483,6 +485,8 @@ class ConversationFragment :
|
||||
|
||||
private val shareDataTimestampViewModel: ShareDataTimestampViewModel by activityViewModels()
|
||||
|
||||
private val insetsViewModel: InsetsViewModel by activityViewModels()
|
||||
|
||||
private val inlineQueryController: InlineQueryResultsControllerV2 by lazy {
|
||||
InlineQueryResultsControllerV2(
|
||||
this,
|
||||
@@ -595,8 +599,21 @@ class ConversationFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.toolbar.isBackInvokedCallbackEnabled = false
|
||||
|
||||
binding.root.setApplyRootInsets(!resources.getWindowSizeClass().isSplitPane())
|
||||
binding.root.setUseWindowTypes(!resources.getWindowSizeClass().isSplitPane())
|
||||
if (WindowSizeClass.isLargeScreenSupportEnabled()) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
binding.root.clearVerticalInsetOverride()
|
||||
if (!resources.getWindowSizeClass().isSplitPane()) {
|
||||
insetsViewModel.insets.collect {
|
||||
binding.root.applyInsets(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.root.setApplyRootInsets(!WindowSizeClass.isLargeScreenSupportEnabled())
|
||||
binding.root.setUseWindowTypes(!WindowSizeClass.isLargeScreenSupportEnabled())
|
||||
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
|
||||
@@ -1601,6 +1618,7 @@ class ConversationFragment :
|
||||
composeText.setDraftText(data.text)
|
||||
inputPanel.clickOnComposeInput()
|
||||
}
|
||||
|
||||
is ShareOrDraftData.SetLocation -> attachmentManager.setLocation(data.location, MediaConstraints.getPushMediaConstraints())
|
||||
is ShareOrDraftData.SetEditMessage -> {
|
||||
composeText.setDraftText(data.draftText)
|
||||
@@ -3219,9 +3237,13 @@ class ConversationFragment :
|
||||
|
||||
override fun onItemLongClick(itemView: View, item: MultiselectPart) {
|
||||
Log.d(TAG, "onItemLongClick")
|
||||
if (actionMode != null) { return }
|
||||
if (actionMode != null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.getMessageRecord().isInMemoryMessageRecord) { return }
|
||||
if (item.getMessageRecord().isInMemoryMessageRecord) {
|
||||
return
|
||||
}
|
||||
|
||||
val messageRecord = item.getMessageRecord()
|
||||
val recipient = viewModel.recipientSnapshot ?: return
|
||||
|
||||
@@ -5,104 +5,43 @@
|
||||
|
||||
package org.thoughtcrime.securesms.main
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideInTransition
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideOutTransition
|
||||
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameScreen
|
||||
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsScreen
|
||||
import org.thoughtcrime.securesms.serialization.JsonSerializableNavType
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* A navigation host for the calls detail pane of [org.thoughtcrime.securesms.MainActivity].
|
||||
*
|
||||
* @param currentDestination The current calls destination to navigate to, containing routing information
|
||||
* @param contentLayoutData Layout configuration data for responsive UI rendering
|
||||
*/
|
||||
@Composable
|
||||
fun CallsNavHost(
|
||||
currentDestination: MainNavigationDetailLocation.Calls,
|
||||
contentLayoutData: MainContentLayoutData
|
||||
) {
|
||||
val navHostController = key(currentDestination.controllerKey) {
|
||||
rememberNavController()
|
||||
fun NavGraphBuilder.callNavGraphBuilder(navHostController: NavHostController) {
|
||||
composable<MainNavigationDetailLocation.Empty> {
|
||||
EmptyDetailScreen()
|
||||
}
|
||||
|
||||
val startDestination = remember(currentDestination.controllerKey) {
|
||||
MainNavigationDetailLocation.Calls.CallLinkDetails(currentDestination.controllerKey)
|
||||
}
|
||||
|
||||
LaunchedEffect(navHostController) {
|
||||
navHostController.enableOnBackPressed(true)
|
||||
}
|
||||
|
||||
LaunchedEffect(currentDestination) {
|
||||
if (currentDestination != startDestination) {
|
||||
navHostController.navigate(currentDestination)
|
||||
}
|
||||
}
|
||||
|
||||
val mainNavigationViewModel = viewModel<MainNavigationViewModel>(viewModelStoreOwner = LocalContext.current as ComponentActivity) {
|
||||
error("Should already exist.")
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navHostController,
|
||||
startDestination = startDestination,
|
||||
enterTransition = { navHostSlideInTransition { it } },
|
||||
exitTransition = { navHostSlideOutTransition { -it } },
|
||||
popEnterTransition = { navHostSlideInTransition { -it } },
|
||||
popExitTransition = { navHostSlideOutTransition { it } },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
composable<MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails>(
|
||||
typeMap = mapOf(
|
||||
typeOf<CallLinkRoomId>() to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
)
|
||||
) {
|
||||
composable<MainNavigationDetailLocation.Calls.CallLinkDetails>(
|
||||
typeMap = mapOf(
|
||||
typeOf<CallLinkRoomId>() to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
)
|
||||
) {
|
||||
val route = it.toRoute<MainNavigationDetailLocation.Calls.CallLinkDetails>()
|
||||
val route = it.toRoute<MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails>()
|
||||
|
||||
LaunchedEffect(route) {
|
||||
mainNavigationViewModel.goTo(route)
|
||||
}
|
||||
CallLinkDetailsScreen(roomId = route.callLinkRoomId)
|
||||
}
|
||||
|
||||
MainActivityDetailContainer(contentLayoutData) {
|
||||
CallLinkDetailsScreen(roomId = route.callLinkRoomId)
|
||||
}
|
||||
}
|
||||
composable<MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName>(
|
||||
typeMap = mapOf(
|
||||
typeOf<CallLinkRoomId>() to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
)
|
||||
) {
|
||||
val route = it.toRoute<MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName>()
|
||||
val parent = navHostController.previousBackStackEntry ?: return@composable
|
||||
|
||||
composable<MainNavigationDetailLocation.Calls.EditCallLinkName>(
|
||||
typeMap = mapOf(
|
||||
typeOf<CallLinkRoomId>() to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
)
|
||||
) {
|
||||
val parent = navHostController.getBackStackEntry(startDestination)
|
||||
val route = it.toRoute<MainNavigationDetailLocation.Calls.EditCallLinkName>()
|
||||
|
||||
LaunchedEffect(route) {
|
||||
mainNavigationViewModel.goTo(route)
|
||||
}
|
||||
|
||||
MainActivityDetailContainer(contentLayoutData) {
|
||||
CompositionLocalProvider(LocalViewModelStoreOwner provides parent) {
|
||||
EditCallLinkNameScreen(roomId = route.callLinkRoomId)
|
||||
}
|
||||
}
|
||||
CompositionLocalProvider(LocalViewModelStoreOwner provides parent) {
|
||||
EditCallLinkNameScreen(roomId = route.callLinkRoomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,95 +5,41 @@
|
||||
|
||||
package org.thoughtcrime.securesms.main
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.fragment.compose.AndroidFragment
|
||||
import androidx.fragment.compose.rememberFragmentState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideInTransition
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideOutTransition
|
||||
import org.thoughtcrime.securesms.conversation.ConversationArgs
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationFragment
|
||||
import org.thoughtcrime.securesms.serialization.JsonSerializableNavType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* A navigation host for the chats detail pane of [org.thoughtcrime.securesms.MainActivity].
|
||||
*
|
||||
* @param currentDestination The current calls destination to navigate to, containing routing information
|
||||
* @param contentLayoutData Layout configuration data for responsive UI rendering
|
||||
*/
|
||||
@Composable
|
||||
fun ChatsNavHost(
|
||||
currentDestination: MainNavigationDetailLocation.Chats,
|
||||
contentLayoutData: MainContentLayoutData
|
||||
) {
|
||||
val navHostController: NavHostController = key(currentDestination.controllerKey) {
|
||||
rememberNavController()
|
||||
fun NavGraphBuilder.chatNavGraphBuilder() {
|
||||
composable<MainNavigationDetailLocation.Empty> {
|
||||
EmptyDetailScreen()
|
||||
}
|
||||
|
||||
val startDestination = remember(currentDestination.controllerKey) {
|
||||
currentDestination as? MainNavigationDetailLocation.Chats.Conversation ?: error("Unsupported start destination.")
|
||||
}
|
||||
|
||||
LaunchedEffect(currentDestination) {
|
||||
if (currentDestination != startDestination) {
|
||||
navHostController.navigate(currentDestination)
|
||||
}
|
||||
}
|
||||
|
||||
val mainNavigationViewModel = viewModel<MainNavigationViewModel>(viewModelStoreOwner = LocalContext.current as ComponentActivity) {
|
||||
error("Should already exist.")
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navHostController,
|
||||
startDestination = startDestination,
|
||||
enterTransition = { navHostSlideInTransition { it } },
|
||||
exitTransition = { navHostSlideOutTransition { -it } },
|
||||
popEnterTransition = { navHostSlideInTransition { -it } },
|
||||
popExitTransition = { navHostSlideOutTransition { it } },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
composable<MainNavigationDetailLocation.Chats.Conversation>(
|
||||
typeMap = mapOf(
|
||||
typeOf<ConversationArgs>() to JsonSerializableNavType(ConversationArgs.serializer())
|
||||
)
|
||||
) {
|
||||
composable<MainNavigationDetailLocation.Chats.Conversation>(
|
||||
typeMap = mapOf(
|
||||
typeOf<ConversationArgs>() to JsonSerializableNavType(ConversationArgs.serializer())
|
||||
)
|
||||
) {
|
||||
val route = it.toRoute<MainNavigationDetailLocation.Chats.Conversation>()
|
||||
val fragmentState = key(route) { rememberFragmentState() }
|
||||
val context = LocalContext.current
|
||||
val route = it.toRoute<MainNavigationDetailLocation.Chats.Conversation>()
|
||||
val fragmentState = key(route) { rememberFragmentState() }
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(route) {
|
||||
mainNavigationViewModel.goTo(route)
|
||||
}
|
||||
|
||||
AndroidFragment(
|
||||
clazz = ConversationFragment::class.java,
|
||||
fragmentState = fragmentState,
|
||||
arguments = requireNotNull(ConversationIntents.createBuilderSync(context, route.conversationArgs).build().extras) { "Handed null Conversation intent arguments." },
|
||||
modifier = Modifier
|
||||
.padding(end = contentLayoutData.detailPaddingEnd)
|
||||
.clip(contentLayoutData.shape)
|
||||
.background(color = MaterialTheme.colorScheme.surface)
|
||||
.fillMaxSize()
|
||||
)
|
||||
}
|
||||
AndroidFragment(
|
||||
clazz = ConversationFragment::class.java,
|
||||
fragmentState = fragmentState,
|
||||
arguments = requireNotNull(ConversationIntents.createBuilderSync(context, route.conversationArgs).build().extras) { "Handed null Conversation intent arguments." },
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.main
|
||||
|
||||
import androidx.annotation.Px
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class InsetsViewModel : ViewModel() {
|
||||
private val internalInsets = MutableStateFlow(Insets.Zero)
|
||||
val insets: StateFlow<Insets> = internalInsets
|
||||
|
||||
fun updateInsets(insets: Insets) {
|
||||
internalInsets.update { insets }
|
||||
}
|
||||
|
||||
data class Insets(
|
||||
@param:Px val statusBar: Float,
|
||||
@param:Px val navBar: Float
|
||||
) {
|
||||
companion object {
|
||||
val Zero = Insets(0f, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InsetsViewModelUpdater(
|
||||
insetsViewModel: InsetsViewModel = viewModel { InsetsViewModel() }
|
||||
) {
|
||||
val statusBarInsets = WindowInsets.statusBars
|
||||
val navigationBarInsets = WindowInsets.navigationBars
|
||||
|
||||
val statusBarPadding = statusBarInsets.asPaddingValues()
|
||||
val navigationBarPadding = navigationBarInsets.asPaddingValues()
|
||||
val density = LocalDensity.current
|
||||
|
||||
LaunchedEffect(statusBarPadding, navigationBarPadding, density) {
|
||||
val statusBarPx = with(density) {
|
||||
(statusBarPadding.calculateTopPadding() + statusBarPadding.calculateBottomPadding()).toPx()
|
||||
}
|
||||
|
||||
val navBarPx = with(density) {
|
||||
(navigationBarPadding.calculateTopPadding() + navigationBarPadding.calculateBottomPadding()).toPx()
|
||||
}
|
||||
|
||||
insetsViewModel.updateInsets(
|
||||
InsetsViewModel.Insets(
|
||||
statusBar = statusBarPx,
|
||||
navBar = navBarPx
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,26 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.createGraph
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideInTransition
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideOutTransition
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
@Composable
|
||||
fun EmptyDetailScreen(
|
||||
contentLayoutData: MainContentLayoutData
|
||||
) {
|
||||
fun EmptyDetailScreen() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(end = contentLayoutData.detailPaddingEnd)
|
||||
.clip(contentLayoutData.shape)
|
||||
.background(color = MaterialTheme.colorScheme.surface)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
@@ -40,17 +45,46 @@ fun EmptyDetailScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainActivityDetailContainer(
|
||||
contentLayoutData: MainContentLayoutData,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Box(
|
||||
fun rememberDetailNavHostController(builder: NavGraphBuilder.(NavHostController) -> Unit): NavHostController {
|
||||
val navHostController = rememberNavController()
|
||||
val viewModelStore = LocalViewModelStoreOwner.current!!.viewModelStore
|
||||
|
||||
remember {
|
||||
val graph = navHostController.createGraph(
|
||||
startDestination = MainNavigationDetailLocation.Empty,
|
||||
builder = { builder(navHostController) }
|
||||
)
|
||||
|
||||
navHostController.setViewModelStore(viewModelStore)
|
||||
navHostController.setGraph(graph, null)
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
return navHostController
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToDetailLocation(location: MainNavigationDetailLocation) {
|
||||
navigate(location) {
|
||||
if (location.isContentRoot) {
|
||||
popUpTo(graph.id) { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DetailsScreenNavHost(navHostController: NavHostController, contentLayoutData: MainContentLayoutData) {
|
||||
NavHost(
|
||||
navController = navHostController,
|
||||
graph = navHostController.graph,
|
||||
enterTransition = { navHostSlideInTransition { it } },
|
||||
exitTransition = { navHostSlideOutTransition { -it } },
|
||||
popEnterTransition = { navHostSlideInTransition { -it } },
|
||||
popExitTransition = { navHostSlideOutTransition { it } },
|
||||
modifier = Modifier
|
||||
.padding(end = contentLayoutData.detailPaddingEnd)
|
||||
.clip(contentLayoutData.shape)
|
||||
.background(color = MaterialTheme.colorScheme.surface)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
content()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,17 +18,33 @@ import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
* Describes which content to display in the detail view.
|
||||
*/
|
||||
@Parcelize
|
||||
sealed interface MainNavigationDetailLocation : Parcelable {
|
||||
sealed class MainNavigationDetailLocation : Parcelable {
|
||||
|
||||
/**
|
||||
* Flag utilized internally to determine whether the given route is displayed at the root
|
||||
* of a task stack (or on top of Empty)
|
||||
*/
|
||||
@IgnoredOnParcel
|
||||
open val isContentRoot: Boolean = false
|
||||
|
||||
@Serializable
|
||||
data object Empty : MainNavigationDetailLocation
|
||||
data object Empty : MainNavigationDetailLocation() {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val isContentRoot: Boolean = true
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
sealed interface Chats : MainNavigationDetailLocation {
|
||||
sealed class Chats : MainNavigationDetailLocation() {
|
||||
|
||||
val controllerKey: RecipientId
|
||||
abstract val controllerKey: RecipientId
|
||||
|
||||
@Serializable
|
||||
data class Conversation(val conversationArgs: ConversationArgs) : Chats {
|
||||
data class Conversation(val conversationArgs: ConversationArgs) : Chats() {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val isContentRoot: Boolean = true
|
||||
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val controllerKey: RecipientId = conversationArgs.recipientId
|
||||
@@ -39,25 +55,33 @@ sealed interface MainNavigationDetailLocation : Parcelable {
|
||||
* Content which can be displayed while the user is navigating the Calls tab.
|
||||
*/
|
||||
@Parcelize
|
||||
sealed interface Calls : MainNavigationDetailLocation {
|
||||
sealed class Calls : MainNavigationDetailLocation() {
|
||||
|
||||
val controllerKey: CallLinkRoomId
|
||||
@Parcelize
|
||||
sealed class CallLinks : Calls() {
|
||||
|
||||
@Serializable
|
||||
data class CallLinkDetails(val callLinkRoomId: CallLinkRoomId) : Calls {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val controllerKey: CallLinkRoomId = callLinkRoomId
|
||||
}
|
||||
abstract val controllerKey: CallLinkRoomId
|
||||
|
||||
@Serializable
|
||||
data class EditCallLinkName(val callLinkRoomId: CallLinkRoomId) : Calls {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val controllerKey: CallLinkRoomId = callLinkRoomId
|
||||
@Serializable
|
||||
data class CallLinkDetails(val callLinkRoomId: CallLinkRoomId) : CallLinks() {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val isContentRoot: Boolean = true
|
||||
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val controllerKey: CallLinkRoomId = callLinkRoomId
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class EditCallLinkName(val callLinkRoomId: CallLinkRoomId) : CallLinks() {
|
||||
@Transient
|
||||
@IgnoredOnParcel
|
||||
override val controllerKey: CallLinkRoomId = callLinkRoomId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
sealed interface Stories : MainNavigationDetailLocation
|
||||
sealed class Stories : MainNavigationDetailLocation()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
@@ -32,8 +33,7 @@ import org.thoughtcrime.securesms.window.WindowSizeClass
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
class MainNavigationViewModel(
|
||||
initialListLocation: MainNavigationListLocation = MainNavigationListLocation.CHATS,
|
||||
initialDetailLocation: MainNavigationDetailLocation = MainNavigationDetailLocation.Empty
|
||||
initialListLocation: MainNavigationListLocation = MainNavigationListLocation.CHATS
|
||||
) : ViewModel(), MainNavigationRouter {
|
||||
private val megaphoneRepository = AppDependencies.megaphoneRepository
|
||||
|
||||
@@ -44,11 +44,9 @@ class MainNavigationViewModel(
|
||||
/**
|
||||
* The latest detail location that has been requested, for consumption by other components.
|
||||
*/
|
||||
private val internalDetailLocation = MutableStateFlow(initialDetailLocation)
|
||||
val detailLocation: StateFlow<MainNavigationDetailLocation> = internalDetailLocation
|
||||
private val internalDetailLocation = MutableSharedFlow<MainNavigationDetailLocation>()
|
||||
val detailLocation: SharedFlow<MainNavigationDetailLocation> = internalDetailLocation
|
||||
val detailLocationObservable: Observable<MainNavigationDetailLocation> = internalDetailLocation.asObservable()
|
||||
var latestConversationLocation: MainNavigationDetailLocation.Chats.Conversation? = null
|
||||
var latestCallsLocation: MainNavigationDetailLocation.Calls? = null
|
||||
|
||||
private val internalMegaphone = MutableStateFlow(Megaphone.NONE)
|
||||
val megaphone: StateFlow<Megaphone> = internalMegaphone
|
||||
@@ -71,7 +69,8 @@ class MainNavigationViewModel(
|
||||
val tabClickEvents: Observable<MainNavigationListLocation> = internalTabClickEvents.asObservable()
|
||||
|
||||
private var earlyNavigationListLocationRequested: MainNavigationListLocation? = null
|
||||
private var earlyNavigationDetailLocationRequested: MainNavigationDetailLocation? = null
|
||||
var earlyNavigationDetailLocationRequested: MainNavigationDetailLocation? = null
|
||||
private set
|
||||
|
||||
init {
|
||||
performStoreUpdate(MainNavigationRepository.getNumberOfUnreadMessages()) { unreadChats, state ->
|
||||
@@ -110,11 +109,13 @@ class MainNavigationViewModel(
|
||||
goTo(it)
|
||||
}
|
||||
|
||||
earlyNavigationDetailLocationRequested = null
|
||||
|
||||
return threePaneScaffoldNavigator
|
||||
}
|
||||
|
||||
fun clearEarlyDetailLocation() {
|
||||
earlyNavigationDetailLocationRequested = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the requested location. If the navigator is not present, this functionally sets our
|
||||
* "default" location to that specified, and we will route the user there when the navigator is set.
|
||||
@@ -130,8 +131,8 @@ class MainNavigationViewModel(
|
||||
return
|
||||
}
|
||||
|
||||
internalDetailLocation.update {
|
||||
location
|
||||
viewModelScope.launch {
|
||||
internalDetailLocation.emit(location)
|
||||
}
|
||||
|
||||
val focusedPane = when (location) {
|
||||
@@ -140,12 +141,10 @@ class MainNavigationViewModel(
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Chats.Conversation -> {
|
||||
latestConversationLocation = location
|
||||
ThreePaneScaffoldRole.Primary
|
||||
}
|
||||
|
||||
is MainNavigationDetailLocation.Calls -> {
|
||||
latestCallsLocation = location
|
||||
ThreePaneScaffoldRole.Primary
|
||||
}
|
||||
}
|
||||
@@ -175,22 +174,10 @@ class MainNavigationViewModel(
|
||||
}
|
||||
|
||||
when (location) {
|
||||
MainNavigationListLocation.CHATS -> {
|
||||
internalDetailLocation.update {
|
||||
latestConversationLocation ?: MainNavigationDetailLocation.Empty
|
||||
}
|
||||
}
|
||||
MainNavigationListLocation.CHATS -> Unit
|
||||
MainNavigationListLocation.ARCHIVE -> Unit
|
||||
MainNavigationListLocation.CALLS -> {
|
||||
internalDetailLocation.update {
|
||||
latestCallsLocation ?: MainNavigationDetailLocation.Empty
|
||||
}
|
||||
}
|
||||
MainNavigationListLocation.STORIES -> {
|
||||
internalDetailLocation.update {
|
||||
MainNavigationDetailLocation.Empty
|
||||
}
|
||||
}
|
||||
MainNavigationListLocation.CALLS -> Unit
|
||||
MainNavigationListLocation.STORIES -> Unit
|
||||
}
|
||||
|
||||
internalMainNavigationState.update {
|
||||
@@ -200,11 +187,6 @@ class MainNavigationViewModel(
|
||||
navigatorScope?.launch {
|
||||
val currentPane = navigator?.currentDestination?.pane ?: return@launch
|
||||
if (currentPane == ThreePaneScaffoldRole.Secondary) {
|
||||
val multiPane = navigator?.scaffoldDirective?.maxHorizontalPartitions == 2
|
||||
if (multiPane && location == MainNavigationListLocation.CHATS && latestConversationLocation != null) {
|
||||
navigator?.navigateTo(ThreePaneScaffoldRole.Primary)
|
||||
}
|
||||
|
||||
return@launch
|
||||
} else {
|
||||
navigator?.navigateBack()
|
||||
|
||||
@@ -5,31 +5,11 @@
|
||||
|
||||
package org.thoughtcrime.securesms.main
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideInTransition
|
||||
import org.signal.core.ui.compose.Animations.navHostSlideOutTransition
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
|
||||
/**
|
||||
* A navigation host for the stories detail pane of [org.thoughtcrime.securesms.MainActivity].
|
||||
*
|
||||
* @param currentDestination The current calls destination to navigate to, containing routing information
|
||||
* @param contentLayoutData Layout configuration data for responsive UI rendering
|
||||
*/
|
||||
@Composable
|
||||
fun StoriesNavHost(
|
||||
navHostController: NavHostController,
|
||||
startDestination: MainNavigationDetailLocation.Stories,
|
||||
contentLayoutData: MainContentLayoutData
|
||||
) {
|
||||
NavHost(
|
||||
navController = navHostController,
|
||||
startDestination = startDestination,
|
||||
enterTransition = { navHostSlideInTransition { it } },
|
||||
exitTransition = { navHostSlideOutTransition { -it } },
|
||||
popEnterTransition = { navHostSlideInTransition { -it } },
|
||||
popExitTransition = { navHostSlideOutTransition { it } }
|
||||
) {
|
||||
fun NavGraphBuilder.storiesNavGraphBuilder() {
|
||||
composable<MainNavigationDetailLocation.Empty> {
|
||||
EmptyDetailScreen()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user