Show conversation settings in the detail pane on large screens.

This commit is contained in:
jeffrey-signal
2026-03-24 15:30:56 -04:00
committed by Cody Henthorne
parent 9941b2d123
commit 4d301a4f66
5 changed files with 111 additions and 13 deletions

View File

@@ -30,6 +30,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import io.reactivex.rxjava3.kotlin.subscribeBy
import kotlinx.coroutines.launch
import org.signal.core.ui.getWindowSizeClass
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.permissions.Permissions
import org.signal.core.util.DimensionUnit
import org.signal.core.util.Result
@@ -117,6 +119,7 @@ import org.thoughtcrime.securesms.util.ExpirationUtil
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.WindowUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog
@@ -261,8 +264,25 @@ class ConversationSettingsFragment :
}
}
private fun goToConversationList() {
if (mainNavRouter != null) {
mainNavRouter?.goTo(MainNavigationDetailLocation.Empty)
} else {
startActivity(MainActivity.clearTopAndOpenDetail(requireContext(), MainNavigationDetailLocation.Empty))
}
}
override fun getMaterial3OnScrollHelper(toolbar: Toolbar?): Material3OnScrollHelper {
return object : Material3OnScrollHelper(activity = requireActivity(), views = listOf(toolbar!!), lifecycleOwner = viewLifecycleOwner) {
return object : Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(toolbar!!),
lifecycleOwner = viewLifecycleOwner,
setStatusBarColor = { color ->
if (!resources.getWindowSizeClass().isSplitPane() || activity is ConversationSettingsActivity) {
WindowUtil.setStatusBarColor(requireActivity().window, color)
}
}
) {
override val inactiveColorSet = ColorSet(
toolbarColorRes = CoreUiR.color.signal_colorBackground_0,
statusBarColorRes = CoreUiR.color.signal_colorBackground
@@ -972,7 +992,7 @@ class ConversationSettingsFragment :
icon = DSLSettingsIcon.from(R.drawable.symbol_archive_24),
onClick = {
viewModel.toggleArchive()
onToolbarNavigationClicked()
goToConversationList()
}
)
}
@@ -986,11 +1006,7 @@ class ConversationSettingsFragment :
lifecycleScope.launch {
viewModel.deleteChat()
progressDialog.dismissAllowingStateLoss()
if (mainNavRouter != null) {
mainNavRouter?.goTo(MainNavigationDetailLocation.Empty)
} else {
startActivity(MainActivity.clearTopAndOpenDetail(requireContext(), MainNavigationDetailLocation.Empty))
}
goToConversationList()
}
}
)
@@ -1059,7 +1075,7 @@ class ConversationSettingsFragment :
.onReportSpam()
.subscribeBy {
Toast.makeText(requireContext(), R.string.ConversationFragment_reported_as_spam, Toast.LENGTH_SHORT).show()
onToolbarNavigationClicked()
goToConversationList()
}
.addTo(lifecycleDisposable)
},
@@ -1073,7 +1089,7 @@ class ConversationSettingsFragment :
when (result) {
is Result.Success -> {
Toast.makeText(requireContext(), R.string.ConversationFragment_reported_as_spam_and_blocked, Toast.LENGTH_SHORT).show()
onToolbarNavigationClicked()
goToConversationList()
}
is Result.Failure -> {
@@ -1108,7 +1124,7 @@ class ConversationSettingsFragment :
.onReportSpam()
.subscribeBy {
Toast.makeText(requireContext(), R.string.ConversationFragment_reported_as_spam, Toast.LENGTH_SHORT).show()
onToolbarNavigationClicked()
goToConversationList()
}
.addTo(lifecycleDisposable)
},

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.conversation
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.navigation.fragment.NavHostFragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
class ConversationSettingsNavHostFragment : NavHostFragment() {
companion object {
suspend fun createArgs(recipientId: RecipientId): Bundle {
val recipient = withContext(Dispatchers.IO) { Recipient.resolved(recipientId) }
val args = if (recipient.isGroup) {
ConversationSettingsFragmentArgs.Builder(null, recipient.requireGroupId(), null)
} else {
ConversationSettingsFragmentArgs.Builder(recipientId, null, null)
}.build()
return bundleOf(DSLSettingsActivity.ARG_START_BUNDLE to args.toBundle())
}
}
override fun onCreate(savedInstanceState: Bundle?) {
val args = requireArguments().getBundle(DSLSettingsActivity.ARG_START_BUNDLE)
navController.setGraph(R.navigation.conversation_settings, args)
super.onCreate(savedInstanceState)
}
}

View File

@@ -4129,7 +4129,7 @@ class ConversationFragment :
override fun handleManageGroup() {
viewModel.recipientSnapshot?.let { recipient ->
navigateToConversationSettingsStandalone(recipient)
navigateTo(MainNavigationDetailLocation.Chats.ConversationSettings(recipient.id))
}
}
@@ -4166,7 +4166,7 @@ class ConversationFragment :
override fun handleConversationSettings() {
viewModel.recipientSnapshot?.let { recipient ->
if (!viewModel.hasMessageRequestState || recipient.isBlocked) {
navigateToConversationSettingsStandalone(recipient)
navigateTo(MainNavigationDetailLocation.Chats.ConversationSettings(recipient.id))
}
}
}
@@ -4235,6 +4235,7 @@ class ConversationFragment :
} else {
when (location) {
is MainNavigationDetailLocation.Chats.MessageDetails -> navigateToMessageDetailsStandalone(location)
is MainNavigationDetailLocation.Chats.ConversationSettings -> navigateToConversationSettingsStandalone(viewModel.recipientSnapshot!!)
is MainNavigationDetailLocation.Chats.Conversation -> error("ConversationFragment shouldn't navigate to another conversation - use the main navigation infrastructure instead.")
}
}

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.main
import android.os.Build
import android.os.Bundle
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
@@ -22,6 +23,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -47,6 +49,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.signal.core.ui.isSplitPane
import org.thoughtcrime.securesms.MainNavigator
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsNavHostFragment
import org.thoughtcrime.securesms.compose.FragmentBackHandler
import org.thoughtcrime.securesms.compose.FragmentBackPressedState
import org.thoughtcrime.securesms.conversation.ConversationArgs
@@ -162,12 +165,44 @@ fun NavGraphBuilder.chatNavGraphBuilder(
clazz = MessageDetailsFragment::class.java,
fragmentState = fragmentState,
arguments = MessageDetailsFragment.args(route.recipientId, route.messageId),
modifier = Modifier.fillMaxSize()
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.statusBarsPadding()
.navigationBarsPadding()
)
}
composable<MainNavigationDetailLocation.Chats.ConversationSettings>(
typeMap = mapOf(
typeOf<RecipientId>() to JsonSerializableNavType(RecipientId.serializer())
)
) { navBackStackEntry ->
val navigatorProvider = LocalContext.current as? MainNavigator.NavigatorProvider
val fragmentState = key(route) { rememberFragmentState() }
val route = navBackStackEntry.toRoute<MainNavigationDetailLocation.Chats.ConversationSettings>()
val arguments: Bundle? by produceState(null, route.recipientId) {
value = ConversationSettingsNavHostFragment.createArgs(route.recipientId)
}
LaunchedEffect(Unit) {
navigatorProvider?.onFirstRender()
}
arguments?.let { args ->
AndroidFragment(
clazz = ConversationSettingsNavHostFragment::class.java,
fragmentState = fragmentState,
arguments = args,
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.statusBarsPadding()
.navigationBarsPadding()
)
}
}
}
@Composable

View File

@@ -73,6 +73,13 @@ sealed class MainNavigationDetailLocation : Parcelable {
@IgnoredOnParcel
override val controllerKey: RecipientId = recipientId
}
@Serializable
data class ConversationSettings(val recipientId: RecipientId) : Chats() {
@Transient
@IgnoredOnParcel
override val controllerKey: RecipientId = recipientId
}
}
/**