Modify heuristic for split-pane determination.

This commit is contained in:
Alex Hart
2026-04-22 09:30:14 -03:00
parent 454fe86dda
commit 6031fc9113
16 changed files with 75 additions and 59 deletions

View File

@@ -61,6 +61,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.fragment.app.DialogFragment
@@ -73,7 +74,6 @@ import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import androidx.window.core.layout.WindowSizeClass
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.subjects.PublishSubject
import io.reactivex.rxjava3.subjects.Subject
@@ -88,6 +88,7 @@ import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.permissions.Permissions
import org.signal.core.ui.rememberIsSplitPane
import org.signal.core.util.Util
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.getParcelableCompat
@@ -429,11 +430,12 @@ class MainActivity :
)
}
val isSplitPane = LocalResources.current.rememberIsSplitPane()
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val contentLayoutData = MainContentLayoutData.rememberContentLayoutData(mainToolbarState.mode)
MainContainer {
val wrappedNavigator = rememberNavigator(windowSizeClass, contentLayoutData, maxWidth)
val wrappedNavigator = rememberNavigator(isSplitPane, contentLayoutData, maxWidth)
val listPaneWidth = contentLayoutData.rememberDefaultPanePreferredWidth(maxWidth)
val navigationType = NavigationType.rememberNavigationType()
@@ -478,7 +480,7 @@ class MainActivity :
}
}
val chatNavGraphState = ChatNavGraphState.remember(windowSizeClass)
val chatNavGraphState = ChatNavGraphState.remember(isSplitPane)
val mutableInteractionSource = remember { MutableInteractionSource() }
MainNavigationDetailLocationEffect(mainNavigationViewModel, chatNavGraphState::writeGraphicsLayerToBitmap)
@@ -624,7 +626,7 @@ class MainActivity :
onDestinationSelected = mainNavigationCallback
)
if (!windowSizeClass.isSplitPane()) {
if (!LocalResources.current.rememberIsSplitPane()) {
Spacer(Modifier.navigationBarsPadding())
}
}
@@ -640,7 +642,7 @@ class MainActivity :
}
},
secondaryContent = {
val listContainerColor = if (windowSizeClass.isSplitPane()) {
val listContainerColor = if (isSplitPane) {
SignalTheme.colors.colorSurface1
} else {
MaterialTheme.colorScheme.surface
@@ -781,12 +783,12 @@ class MainActivity :
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
private fun rememberNavigator(
windowSizeClass: WindowSizeClass,
isSplitPane: Boolean,
contentLayoutData: MainContentLayoutData,
maxWidth: Dp
): AppScaffoldNavigator<Any> {
val scaffoldNavigator = rememberThreePaneScaffoldNavigatorDelegate(
isSplitPane = windowSizeClass.isSplitPane(),
isSplitPane = isSplitPane,
horizontalPartitionSpacerSize = contentLayoutData.partitionWidth,
defaultPanePreferredWidth = contentLayoutData.rememberDefaultPanePreferredWidth(maxWidth)
)
@@ -800,18 +802,18 @@ class MainActivity :
@Composable
private fun MainContainer(content: @Composable BoxWithConstraintsScope.() -> Unit) {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val isSplitPane = LocalResources.current.rememberIsSplitPane()
CompositionLocalProvider(LocalSnackbarStateConsumerRegistry provides mainNavigationViewModel.snackbarRegistry) {
SignalTheme {
val backgroundColor = if (!windowSizeClass.isSplitPane()) {
val backgroundColor = if (!isSplitPane) {
MaterialTheme.colorScheme.surface
} else {
SignalTheme.colors.colorSurface1
}
val modifier = when {
windowSizeClass.isSplitPane() -> {
isSplitPane -> {
Modifier
.systemBarsPadding()
.displayCutoutPadding()

View File

@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -27,6 +26,7 @@ import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
@@ -43,7 +43,7 @@ import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeDialogFragment
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.rememberIsSplitPane
import org.signal.core.util.BreakIteratorCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsViewModel
@@ -109,7 +109,7 @@ fun EditCallLinkNameScreen(
onNavigationClick = {
backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed()
},
showNavigationIcon = !currentWindowAdaptiveInfo().windowSizeClass.isSplitPane()
showNavigationIcon = !LocalResources.current.rememberIsSplitPane()
)
}

View File

@@ -15,12 +15,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.app.ShareCompat
@@ -37,7 +37,7 @@ import org.signal.core.ui.compose.Rows
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.rememberIsSplitPane
import org.signal.core.util.Util
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.ringrtc.CallLinkState.Restrictions
@@ -83,7 +83,7 @@ fun CallLinkDetailsScreen(
state = state,
showAlreadyInACall = showAlreadyInACall,
callback = callback,
showNavigationIcon = !currentWindowAdaptiveInfo().windowSizeClass.isSplitPane()
showNavigationIcon = !LocalResources.current.rememberIsSplitPane()
)
}

View File

@@ -23,7 +23,6 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
import kotlinx.coroutines.launch
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.getWindowSizeClass
import org.signal.core.ui.isSplitPane
import org.signal.core.util.DimensionUnit
import org.signal.core.util.concurrent.LifecycleDisposable
@@ -133,7 +132,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
val filteredCount = callLogAdapter.submitCallRows(
data,
selected,
activeCallLogRowId = activeRowId.orNull().takeIf { resources.getWindowSizeClass().isSplitPane() },
activeCallLogRowId = activeRowId.orNull().takeIf { resources.isSplitPane() },
viewModel.callLogPeekHelper.localDeviceCallRecipientId,
scrollToPositionDelegate::notifyListCommitted
)
@@ -187,7 +186,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
}
}
if (!resources.getWindowSizeClass().isSplitPane()) {
if (!resources.isSplitPane()) {
ViewUtil.setBottomMargin(binding.bottomActionBar, ViewUtil.getNavigationBarHeight(binding.bottomActionBar))
}

View File

@@ -30,7 +30,6 @@ 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
@@ -277,7 +276,7 @@ class ConversationSettingsFragment :
views = listOf(toolbar!!),
lifecycleOwner = viewLifecycleOwner,
setStatusBarColor = { color ->
if (!resources.getWindowSizeClass().isSplitPane() || activity is ConversationSettingsActivity) {
if (!resources.isSplitPane() || activity is ConversationSettingsActivity) {
WindowUtil.setStatusBarColor(requireActivity().window, color)
}
}

View File

@@ -666,7 +666,7 @@ class ConversationFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.resetBackPressedState()
binding.toolbar.isBackInvokedCallbackEnabled = false
binding.root.setUseWindowTypes(args.conversationScreenType == ConversationScreenType.NORMAL && !resources.getWindowSizeClass().isSplitPane())
binding.root.setUseWindowTypes(args.conversationScreenType == ConversationScreenType.NORMAL && !resources.isSplitPane())
if (args.conversationScreenType == ConversationScreenType.BUBBLE) {
binding.root.setNavigationBarInsetOverride(0)
view.post {
@@ -1763,7 +1763,7 @@ class ConversationFragment :
}
private fun updateNavigationIconForNormal(isFullScreenPane: Boolean) {
if (!resources.getWindowSizeClass().isSplitPane() || isFullScreenPane) {
if (!resources.isSplitPane() || isFullScreenPane) {
binding.toolbar.setNavigationIcon(CoreUiR.drawable.symbol_arrow_start_24)
binding.toolbar.navigationIcon?.setTint(
ContextCompat.getColor(
@@ -4359,7 +4359,7 @@ class ConversationFragment :
*/
private fun navigateTo(location: MainNavigationDetailLocation.Chats) {
val router = mainNavRouter
if (router != null && resources.getWindowSizeClass().isSplitPane()) {
if (router != null && resources.isSplitPane()) {
router.goTo(location)
} else {
when (location) {

View File

@@ -459,7 +459,7 @@ public class ConversationListFragment extends MainFragment implements Conversati
}
}));
if (isSplitPane(getWindowSizeClass(getResources()))) {
if (isSplitPane(getResources())) {
lifecycleDisposable.add(mainNavigationViewModel.getObservableActiveChatThreadId()
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(defaultAdapter::setActiveThreadId));

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.signal.core.ui.getWindowSizeClass
import org.signal.core.ui.isSplitPane
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
@@ -36,7 +35,7 @@ fun Fragment.listenToEventBusWhileResumed(
detailLocation
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED)
.collectLatest {
if (!resources.getWindowSizeClass().isSplitPane()) {
if (!resources.isSplitPane()) {
when (it) {
is MainNavigationDetailLocation.Chats.Conversation -> unsubscribe()
MainNavigationDetailLocation.Empty -> subscribe()

View File

@@ -43,11 +43,9 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import androidx.window.core.layout.WindowSizeClass
import kotlinx.coroutines.delay
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
@@ -247,17 +245,17 @@ private fun Transition<Boolean>.chatAnimationState(hasFake: Boolean): AppScaffol
*/
@Stable
class ChatNavGraphState private constructor(
val windowSizeClass: WindowSizeClass,
val isSplitPane: Boolean,
val graphicsLayer: GraphicsLayer
) {
companion object {
@Composable
fun remember(windowSizeClass: WindowSizeClass): ChatNavGraphState {
fun remember(isSplitPane: Boolean): ChatNavGraphState {
val graphicsLayer = rememberGraphicsLayer()
return remember(windowSizeClass) {
return remember(isSplitPane) {
ChatNavGraphState(
windowSizeClass,
isSplitPane,
graphicsLayer
)
}
@@ -271,7 +269,7 @@ class ChatNavGraphState private constructor(
suspend fun writeGraphicsLayerToBitmap() {
// toImageBitmap() uses LayerSnapshot which has format compatibility issues on Android 7 and below
if (Build.VERSION.SDK_INT >= 26 && !windowSizeClass.isSplitPane() && hasWrittenToGraphicsLayer) {
if (Build.VERSION.SDK_INT >= 26 && !isSplitPane && hasWrittenToGraphicsLayer) {
chatBitmap = graphicsLayer.toImageBitmap()
}
}

View File

@@ -13,18 +13,19 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalResources
import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.compose.showSnackbar
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.rememberIsSplitPane
import org.thoughtcrime.securesms.components.snackbars.SnackbarHostKey
import org.thoughtcrime.securesms.components.snackbars.rememberSnackbarState
import org.thoughtcrime.securesms.megaphone.Megaphone
@@ -64,7 +65,7 @@ fun MainBottomChrome(
megaphoneActionController: MegaphoneActionController,
modifier: Modifier = Modifier
) {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val isSplitPane = LocalResources.current.rememberIsSplitPane()
val navigationType = NavigationType.rememberNavigationType()
Column(
@@ -92,7 +93,7 @@ fun MainBottomChrome(
)
}
if (windowSizeClass.isSplitPane()) {
if (isSplitPane) {
return@Column
}

View File

@@ -18,7 +18,7 @@ import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowSizeClass
import org.signal.core.ui.WindowBreakpoint
import org.signal.core.ui.getWindowBreakpoint
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.rememberIsSplitPane
private val MEDIUM_CONTENT_CORNERS = 18.dp
private val EXTENDED_CONTENT_CORNERS = 14.dp
@@ -47,7 +47,7 @@ data class MainContentLayoutData(
*/
@Composable
fun hasDragHandle(): Boolean {
return currentWindowAdaptiveInfo().windowSizeClass.isSplitPane()
return LocalResources.current.rememberIsSplitPane()
}
/**
@@ -55,11 +55,12 @@ data class MainContentLayoutData(
*/
@Composable
fun rememberDefaultPanePreferredWidth(maxWidth: Dp): Dp {
val isSplitPane = LocalResources.current.rememberIsSplitPane()
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
return remember(maxWidth, windowSizeClass) {
when {
!windowSizeClass.isSplitPane() -> maxWidth
!isSplitPane -> maxWidth
windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND) -> 416.dp
else -> (maxWidth - extraPadding) / 2f
}
@@ -75,9 +76,9 @@ data class MainContentLayoutData(
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val resources = LocalResources.current
val breakpoint = resources.getWindowBreakpoint()
val isSplitPane = resources.rememberIsSplitPane()
return remember(windowSizeClass, mode, breakpoint) {
val isSplitPane = windowSizeClass.isSplitPane()
return remember(windowSizeClass, mode, breakpoint, isSplitPane) {
val isLargeWindowSize = breakpoint == WindowBreakpoint.LARGE
MainContentLayoutData(

View File

@@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.AllDevicePreviews
@@ -34,6 +35,7 @@ import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.detailPaneMaxContentWidth
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.rememberIsSplitPane
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.compose.ScreenTitlePane
import org.thoughtcrime.securesms.window.AppScaffold
@@ -53,8 +55,8 @@ fun RecipientPickerScaffold(
primaryContent: @Composable () -> Unit,
floatingActionButton: (@Composable () -> Unit)? = null
) {
val isSplitPane = LocalResources.current.rememberIsSplitPane(forceSplitPane)
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = forceSplitPane)
AppScaffold(
topBarContent = {

View File

@@ -49,6 +49,7 @@ import org.signal.core.ui.compose.Previews
import org.signal.core.ui.getWindowBreakpoint
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.isWidthExpanded
import org.signal.core.ui.rememberIsSplitPane
import org.thoughtcrime.securesms.main.MainFloatingActionButtonsCallback
import org.thoughtcrime.securesms.main.MainNavigationBar
import org.thoughtcrime.securesms.main.MainNavigationRail
@@ -274,10 +275,11 @@ private fun ListAndNavigation(
private fun AppScaffoldPreview() {
Previews.Preview {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val isSplitPane = LocalResources.current.rememberIsSplitPane(false)
AppScaffold(
navigator = rememberAppScaffoldNavigator(
isSplitPane = windowSizeClass.isSplitPane(false),
isSplitPane = isSplitPane,
defaultPanePreferredWidth = 416.dp,
horizontalPartitionSpacerSize = 16.dp
),

View File

@@ -20,11 +20,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.unit.Dp
import androidx.window.core.layout.WindowSizeClass
import org.signal.core.ui.horizontalPartitionDefaultSpacerSize
import org.signal.core.ui.isSplitPane
import org.signal.core.ui.listPaneDefaultPreferredWidth
import org.signal.core.ui.rememberIsSplitPane
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
@@ -99,7 +101,7 @@ open class AppScaffoldNavigator<T> @RememberInComposition constructor(private va
@Composable
fun rememberAppScaffoldNavigator(
windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
isSplitPane: Boolean = windowSizeClass.isSplitPane(
isSplitPane: Boolean = LocalResources.current.rememberIsSplitPane(
forceSplitPane = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPane
),
horizontalPartitionSpacerSize: Dp = windowSizeClass.horizontalPartitionDefaultSpacerSize,