diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt index 08f5691b5e..fe55d4ff19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt @@ -37,6 +37,7 @@ import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState @@ -62,6 +63,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.RecyclerView +import androidx.window.core.layout.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest @@ -163,7 +166,8 @@ import org.thoughtcrime.securesms.window.AppPaneDragHandle import org.thoughtcrime.securesms.window.AppScaffold import org.thoughtcrime.securesms.window.AppScaffoldAnimationStateFactory import org.thoughtcrime.securesms.window.AppScaffoldNavigator -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.Navigation +import org.thoughtcrime.securesms.window.isSplitPane import org.thoughtcrime.securesms.window.rememberThreePaneScaffoldNavigatorDelegate import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState @@ -339,17 +343,23 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner ) } - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val contentLayoutData = MainContentLayoutData.rememberContentLayoutData(mainToolbarState.mode) MainContainer { val wrappedNavigator = rememberNavigator(windowSizeClass, contentLayoutData, maxWidth) val listPaneWidth = contentLayoutData.rememberDefaultPanePreferredWidth(maxWidth) + val navigation = Navigation.rememberNavigation() val anchors = remember(contentLayoutData, mainToolbarState) { val halfPartitionWidth = contentLayoutData.partitionWidth / 2 - val detailOffset = if (mainToolbarState.mode == MainToolbarMode.SEARCH) 0.dp else 80.dp + val detailOffset = when { + mainToolbarState.mode == MainToolbarMode.SEARCH -> 0.dp + navigation == Navigation.BAR -> 0.dp + else -> 80.dp + } + val detailOnlyAnchor = PaneExpansionAnchor.Offset.fromStart(detailOffset + contentLayoutData.listPaddingStart + halfPartitionWidth) val detailAndListAnchor = PaneExpansionAnchor.Offset.fromStart(listPaneWidth + halfPartitionWidth) val listOnlyAnchor = PaneExpansionAnchor.Offset.fromEnd(contentLayoutData.detailPaddingEnd - halfPartitionWidth) @@ -519,7 +529,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner } }, secondaryContent = { - val listContainerColor = if (windowSizeClass.isMedium()) { + val listContainerColor = if (windowSizeClass.isSplitPane() && windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.MEDIUM) { SignalTheme.colors.colorSurface1 } else { MaterialTheme.colorScheme.surface @@ -699,10 +709,10 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner @Composable private fun MainContainer(content: @Composable BoxWithConstraintsScope.() -> Unit) { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(this)) { - val backgroundColor = if (windowSizeClass.isCompact()) { + val backgroundColor = if (!windowSizeClass.isSplitPane()) { MaterialTheme.colorScheme.surface } else { SignalTheme.colors.colorSurface1 diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/links/EditCallLinkNameDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/links/EditCallLinkNameDialogFragment.kt index 377c91fc8f..53cb5e8dec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/links/EditCallLinkNameDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/links/EditCallLinkNameDialogFragment.kt @@ -16,6 +16,7 @@ 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 @@ -47,7 +48,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsViewModel import org.thoughtcrime.securesms.compose.ComposeDialogFragment import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isSplitPane class EditCallLinkNameDialogFragment : ComposeDialogFragment() { @@ -109,7 +110,7 @@ fun EditCallLinkNameScreen( onNavigationClick = { backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() }, - showNavigationIcon = !WindowSizeClass.rememberWindowSizeClass().isSplitPane() + showNavigationIcon = !currentWindowAdaptiveInfo().windowSizeClass.isSplitPane() ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsScreen.kt index 26b4ab1dc3..1c63a6cb27 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/links/details/CallLinkDetailsScreen.kt @@ -14,6 +14,7 @@ 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 @@ -55,7 +56,7 @@ import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState import org.thoughtcrime.securesms.sharing.v2.ShareActivity import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.Util -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isSplitPane import java.time.Instant @Composable @@ -84,7 +85,7 @@ fun CallLinkDetailsScreen( state = state, showAlreadyInACall = showAlreadyInACall, callback = callback, - showNavigationIcon = !WindowSizeClass.rememberWindowSizeClass().isSplitPane() + showNavigationIcon = !currentWindowAdaptiveInfo().windowSizeClass.isSplitPane() ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt index f1d0ac6640..2199c2ad00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt @@ -61,7 +61,8 @@ import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.doAfterNextLayout import org.thoughtcrime.securesms.util.fragments.requireListener import org.thoughtcrime.securesms.util.visible -import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass +import org.thoughtcrime.securesms.window.getWindowSizeClass +import org.thoughtcrime.securesms.window.isSplitPane import java.util.Objects /** @@ -184,7 +185,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal } } - if (resources.getWindowSizeClass().isCompact()) { + if (!resources.getWindowSizeClass().isSplitPane()) { ViewUtil.setBottomMargin(binding.bottomActionBar, ViewUtil.getNavigationBarHeight(binding.bottomActionBar)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/FixedRoundedCornerBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/FixedRoundedCornerBottomSheetDialogFragment.kt index d6d1b4d365..87ba71eef8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/FixedRoundedCornerBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/FixedRoundedCornerBottomSheetDialogFragment.kt @@ -19,8 +19,9 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.util.ThemeUtil import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.WindowUtil -import org.thoughtcrime.securesms.window.WindowSizeClass -import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass +import org.thoughtcrime.securesms.window.getWindowSizeClass +import org.thoughtcrime.securesms.window.isLargeScreenSupportEnabled +import org.thoughtcrime.securesms.window.isSplitPane import com.google.android.material.R as MaterialR /** @@ -32,7 +33,7 @@ abstract class FixedRoundedCornerBottomSheetDialogFragment : BottomSheetDialogFr * Sheet corner radius in DP */ protected val cornerRadius: Int by lazy { - if (WindowSizeClass.isLargeScreenSupportEnabled() && resources.getWindowSizeClass().isSplitPane()) { + if (isLargeScreenSupportEnabled() && resources.getWindowSizeClass().isSplitPane()) { 32 } else { 18 diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt index a09a6edc28..9c0e0dfe72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InsetAwareConstraintLayout.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.components import android.content.Context +import android.content.res.Configuration import android.util.AttributeSet import android.view.View import androidx.constraintlayout.widget.ConstraintLayout @@ -15,7 +16,6 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.main.VerticalInsets import org.thoughtcrime.securesms.util.ViewUtil -import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass import kotlin.math.roundToInt /** @@ -241,7 +241,7 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor( } private fun isLandscape(): Boolean { - return resources.getWindowSizeClass().isLandscape() + return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE } private val Guideline?.guidelineEnd: Int diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt b/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt index 639cdce1b2..7965d18c62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt @@ -8,10 +8,12 @@ package org.thoughtcrime.securesms.components.compose import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import org.thoughtcrime.securesms.window.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass +import org.thoughtcrime.securesms.window.isAtLeast /** * Displays the screen title for split-pane UIs on tablets and foldable devices. @@ -21,7 +23,7 @@ fun ScreenTitlePane( title: String, modifier: Modifier = Modifier ) { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass Text( text = title, @@ -29,7 +31,7 @@ fun ScreenTitlePane( color = MaterialTheme.colorScheme.onSurface, modifier = modifier .padding( - start = if (windowSizeClass.isExtended()) 80.dp else 20.dp, + start = if (windowSizeClass.windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED)) 80.dp else 20.dp, end = 20.dp, bottom = 12.dp ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index 1344df9f30..2a02503bc6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -185,11 +185,11 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter switchPref( isEnabled = state.largeScreenUi, - title = DSLSettingsText.from("Force split pane UI on landscape phones."), + title = DSLSettingsText.from("Force split pane UI on phones."), summary = DSLSettingsText.from("This setting requires split pane UI to be enabled."), - isChecked = state.forceSplitPaneOnCompactLandscape, + isChecked = state.forceSplitPane, onClick = { - viewModel.setForceSplitPaneOnCompactLandscape(!state.forceSplitPaneOnCompactLandscape) + viewModel.setForceSplitPane(!state.forceSplitPane) } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt index 022b5bcb60..15e0611251 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt @@ -32,5 +32,5 @@ data class InternalSettingsState( val hevcEncoding: Boolean, val newCallingUi: Boolean, val largeScreenUi: Boolean, - val forceSplitPaneOnCompactLandscape: Boolean + val forceSplitPane: Boolean ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt index a5f0fca2ff..eb86cfc5cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt @@ -198,7 +198,7 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito hevcEncoding = SignalStore.internal.hevcEncoding, newCallingUi = SignalStore.internal.newCallingUi, largeScreenUi = SignalStore.internal.largeScreenUi, - forceSplitPaneOnCompactLandscape = SignalStore.internal.forceSplitPaneOnCompactLandscape + forceSplitPane = SignalStore.internal.forceSplitPane ) fun onClearOnboardingState() { @@ -219,8 +219,8 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito refresh() } - fun setForceSplitPaneOnCompactLandscape(forceSplitPaneOnCompactLandscape: Boolean) { - SignalStore.internal.forceSplitPaneOnCompactLandscape = forceSplitPaneOnCompactLandscape + fun setForceSplitPane(forceSplitPane: Boolean) { + SignalStore.internal.forceSplitPane = forceSplitPane refresh() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt index 0df8514d90..f8f7e19212 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt @@ -37,6 +37,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -63,6 +64,8 @@ import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.signal.core.ui.compose.BottomSheets @@ -76,7 +79,6 @@ import org.thoughtcrime.securesms.events.CallParticipant import org.thoughtcrime.securesms.events.GroupCallReactionEvent import org.thoughtcrime.securesms.events.WebRtcViewModel import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.window.WindowSizeClass import kotlin.math.max import kotlin.math.round import kotlin.time.Duration.Companion.seconds @@ -484,7 +486,7 @@ private fun TinyLocalVideoRenderer( if (LocalInspectionMode.current) { Text( - "Test ${WindowSizeClass.rememberWindowSizeClass()}", + "Test ${currentWindowAdaptiveInfo().windowSizeClass}", modifier = modifier .padding(padding) .height(height) @@ -553,27 +555,28 @@ private fun SmallMoveableLocalVideoRenderer( @Composable private fun rememberTinyPortraitSize(): SelfPictureInPictureDimensions { val smallWidth = dimensionResource(R.dimen.call_screen_overflow_item_size) - val windowClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - val smallSize = when (windowClass) { - WindowSizeClass.COMPACT_PORTRAIT -> DpSize(40.dp, smallWidth) - WindowSizeClass.COMPACT_LANDSCAPE -> DpSize(smallWidth, 40.dp) - WindowSizeClass.EXTENDED_PORTRAIT, WindowSizeClass.EXTENDED_LANDSCAPE -> DpSize(124.dp, 217.dp) + val smallSize = when { + windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT && !isLandscape -> DpSize(40.dp, smallWidth) + windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT && isLandscape -> DpSize(smallWidth, 40.dp) + windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED -> DpSize(124.dp, 217.dp) else -> DpSize(smallWidth, smallWidth) } - val expandedSize = when (windowClass) { - WindowSizeClass.COMPACT_PORTRAIT -> DpSize(180.dp, 320.dp) - WindowSizeClass.COMPACT_LANDSCAPE -> DpSize(320.dp, 180.dp) + val expandedSize = when { + windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT && !isLandscape -> DpSize(180.dp, 320.dp) + windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT && isLandscape -> DpSize(320.dp, 180.dp) else -> DpSize(smallWidth, smallWidth) } - val padding = when (windowClass) { - WindowSizeClass.COMPACT_PORTRAIT -> PaddingValues(vertical = 16.dp) + val padding = when { + windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT && !isLandscape -> PaddingValues(vertical = 16.dp) else -> PaddingValues(16.dp) } - return remember(windowClass) { + return remember(windowSizeClass) { SelfPictureInPictureDimensions(smallSize, expandedSize, padding) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivity.kt index 670e10ec82..14fa8fde9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivity.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -64,7 +65,8 @@ import org.thoughtcrime.securesms.recipients.ui.findby.FindByActivity import org.thoughtcrime.securesms.recipients.ui.findby.FindByMode import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.window.AppScaffold -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.detailPaneMaxContentWidth +import org.thoughtcrime.securesms.window.isSplitPane import org.thoughtcrime.securesms.window.rememberAppScaffoldNavigator /** @@ -200,8 +202,8 @@ private fun NewConversationScreenUi( uiState: NewConversationUiState, callbacks: UiCallbacks ) { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() - val isSplitPane = windowSizeClass.isSplitPane(forceSplitPaneOnCompactLandscape = uiState.forceSplitPaneOnCompactLandscape) + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = uiState.forceSplitPaneOnCompactLandscape) val snackbarHostState = remember { SnackbarHostState() } AppScaffold( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt index a5ed39be71..ecc2106159 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt @@ -161,7 +161,7 @@ class NewConversationViewModel : ViewModel() { } data class NewConversationUiState( - val forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape, + val forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPane, val searchQuery: String = "", val isLookingUpRecipient: Boolean = false, val isRefreshingContacts: Boolean = false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/inlinequery/InlineQueryResultsControllerV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/inlinequery/InlineQueryResultsControllerV2.kt index 7068811feb..2a88f2150b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/inlinequery/InlineQueryResultsControllerV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/inlinequery/InlineQueryResultsControllerV2.kt @@ -6,6 +6,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.commit import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowSizeClass import io.reactivex.rxjava3.kotlin.subscribeBy import org.signal.core.util.DimensionUnit import org.signal.core.util.concurrent.LifecycleDisposable @@ -15,7 +17,6 @@ import org.thoughtcrime.securesms.components.ComposeText import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerFragmentV2 import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel import org.thoughtcrime.securesms.util.doOnEachLayout -import org.thoughtcrime.securesms.window.WindowSizeClass /** * Controller for inline search results. @@ -71,7 +72,7 @@ class InlineQueryResultsControllerV2( } fun onWindowSizeClassChanged(windowSizeClass: WindowSizeClass) { - this.shouldHideForWindowSizeClass = windowSizeClass == WindowSizeClass.COMPACT_LANDSCAPE + this.shouldHideForWindowSizeClass = windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT if (shouldHideForWindowSizeClass) { dismiss() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivity.kt index 2834d1169f..823d9938c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivity.kt @@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.jobs.ConversationShortcutUpdateJob import org.thoughtcrime.securesms.util.ConfigurationUtil import org.thoughtcrime.securesms.util.Debouncer import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isLargeScreenSupportEnabled import java.util.concurrent.TimeUnit /** @@ -54,7 +54,7 @@ open class ConversationActivity : PassphraseRequiredActivity(), VoiceNoteMediaCo } override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { - if (!ActivityCompat.isLaunchedFromBubble(this) && WindowSizeClass.isLargeScreenSupportEnabled()) { + if (!ActivityCompat.isLaunchedFromBubble(this) && isLargeScreenSupportEnabled()) { startActivity( MainActivity.clearTop(this).apply { action = ConversationIntents.ACTION diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index e19bcd0d69..68ff3f67d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -361,8 +361,9 @@ 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 org.thoughtcrime.securesms.window.getWindowSizeClass +import org.thoughtcrime.securesms.window.isLargeScreenSupportEnabled +import org.thoughtcrime.securesms.window.isSplitPane import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId @@ -615,8 +616,8 @@ class ConversationFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.toolbar.isBackInvokedCallbackEnabled = false - binding.root.setApplyRootInsets(!WindowSizeClass.isLargeScreenSupportEnabled()) - binding.root.setUseWindowTypes(!WindowSizeClass.isLargeScreenSupportEnabled()) + binding.root.setApplyRootInsets(!isLargeScreenSupportEnabled()) + binding.root.setUseWindowTypes(!isLargeScreenSupportEnabled()) disposables.bindTo(viewLifecycleOwner) @@ -699,7 +700,7 @@ class ConversationFragment : override fun onResume() { super.onResume() - if (!WindowSizeClass.isLargeScreenSupportEnabled()) { + if (!isLargeScreenSupportEnabled()) { WindowUtil.setLightNavigationBarFromTheme(requireActivity()) WindowUtil.setLightStatusBarFromTheme(requireActivity()) } @@ -1455,7 +1456,7 @@ class ConversationFragment : } private fun presentNavigationIconForNormal() { - if (WindowSizeClass.isLargeScreenSupportEnabled()) { + if (isLargeScreenSupportEnabled()) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { mainNavigationViewModel.isFullScreenPane.collect { isFullScreenPane -> @@ -3483,7 +3484,7 @@ class ConversationFragment : getVoiceNoteMediaController().resumePlayback(selectedConversationModel.audioUri, messageRecord.id) } - if (!WindowSizeClass.isLargeScreenSupportEnabled()) { + if (!isLargeScreenSupportEnabled()) { WindowUtil.setLightStatusBarFromTheme(requireActivity()) WindowUtil.setLightNavigationBarFromTheme(requireActivity()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index b4e3e834ce..7d617d9924 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -53,6 +53,7 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; +import androidx.window.core.layout.WindowHeightSizeClass; import com.airbnb.lottie.SimpleColorFilter; import com.annimon.stream.Stream; @@ -159,7 +160,6 @@ import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; -import org.thoughtcrime.securesms.window.WindowSizeClass; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; import java.lang.ref.WeakReference; @@ -179,6 +179,9 @@ import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; import kotlin.Unit; +import static org.thoughtcrime.securesms.window.WindowSizeClassExtensionsKt.getWindowSizeClass; +import static org.thoughtcrime.securesms.window.WindowSizeClassExtensionsKt.isSplitPane; + public class ConversationListFragment extends MainFragment implements ConversationListAdapter.OnConversationClickListener, ClearFilterViewHolder.OnClearFilterClickListener, @@ -311,7 +314,7 @@ public class ConversationListFragment extends MainFragment implements Conversati searchAdapter = contactSearchMediator.getAdapter(); - if (WindowSizeClass.Companion.getWindowSizeClass(getResources()).isCompact()) { + if (getWindowSizeClass(getResources()).getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT) { ViewUtil.setBottomMargin(bottomActionBar, ViewUtil.getNavigationBarHeight(bottomActionBar)); } @@ -411,7 +414,7 @@ public class ConversationListFragment extends MainFragment implements Conversati } })); - if (WindowSizeClass.Companion.getWindowSizeClass(getResources()).isSplitPane()) { + if (isSplitPane(getWindowSizeClass(getResources()))) { lifecycleDisposable.add(mainNavigationViewModel.getObservableActiveChatThreadId() .subscribeOn(AndroidSchedulers.mainThread()) .subscribe(defaultAdapter::setActiveThreadId)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragmentExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragmentExtensions.kt index ef72f06639..73268bafec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragmentExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragmentExtensions.kt @@ -16,7 +16,8 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus import org.thoughtcrime.securesms.main.MainNavigationDetailLocation -import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass +import org.thoughtcrime.securesms.window.getWindowSizeClass +import org.thoughtcrime.securesms.window.isSplitPane /** * When the user searches for a conversation and then enters a message, we should clear @@ -35,7 +36,7 @@ fun Fragment.listenToEventBusWhileResumed( detailLocation .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED) .collectLatest { - if (resources.getWindowSizeClass().isCompact()) { + if (!resources.getWindowSizeClass().isSplitPane()) { when (it) { is MainNavigationDetailLocation.Chats.Conversation -> unsubscribe() MainNavigationDetailLocation.Empty -> subscribe() diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt index 32f6635d1e..39a4859a90 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.kt @@ -33,6 +33,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -68,7 +69,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.ui.findby.FindByActivity import org.thoughtcrime.securesms.recipients.ui.findby.FindByMode import org.thoughtcrime.securesms.window.AppScaffold -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.detailPaneMaxContentWidth +import org.thoughtcrime.securesms.window.isSplitPane import org.thoughtcrime.securesms.window.rememberAppScaffoldNavigator import java.text.NumberFormat @@ -162,8 +164,8 @@ private fun CreateGroupScreenUi( uiState: CreateGroupUiState, callbacks: UiCallbacks ) { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() - val isSplitPane = windowSizeClass.isSplitPane(forceSplitPaneOnCompactLandscape = uiState.forceSplitPaneOnCompactLandscape) + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val isSplitPane = windowSizeClass.isSplitPane(forceSplitPane = uiState.forceSplitPane) val snackbarHostState = remember { SnackbarHostState() } val titleText = if (uiState.newSelections.isNotEmpty()) { @@ -352,7 +354,7 @@ private fun CreateGroupScreenPreview() { Previews.Preview { CreateGroupScreenUi( uiState = CreateGroupUiState( - forceSplitPaneOnCompactLandscape = false, + forceSplitPane = false, selectionLimits = SelectionLimits.NO_LIMITS ), callbacks = UiCallbacks.Empty diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupViewModel.kt index 8e95ba2001..8786f1a0a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupViewModel.kt @@ -168,7 +168,7 @@ class CreateGroupViewModel : ViewModel() { } data class CreateGroupUiState( - val forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape, + val forceSplitPane: Boolean = SignalStore.internal.forceSplitPane, val searchQuery: String = "", val selectionLimits: SelectionLimits = RemoteConfig.groupLimits.excludingSelf(), val newSelections: List = emptyList(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt index b9e286fb85..f20e74c8eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt @@ -51,7 +51,7 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal /** * Force split-pane mode on compact landscape */ - var forceSplitPaneOnCompactLandscape by booleanValue(FORCE_SPLIT_PANE_ON_COMPACT_LANDSCAPE, false).falseForExternalUsers() + var forceSplitPane by booleanValue(FORCE_SPLIT_PANE_ON_COMPACT_LANDSCAPE, false).falseForExternalUsers() /** * Members will not be added directly to a GV2 even if they could be. diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java index 03b60cfc16..54a18f62da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java @@ -34,13 +34,14 @@ import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.VersionTracker; -import org.thoughtcrime.securesms.window.WindowSizeClass; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import java.util.Arrays; import java.util.LinkedList; import java.util.Locale; +import static org.thoughtcrime.securesms.window.WindowSizeClassExtensionsKt.getWindowSizeClass; + public class LogSectionSystemInfo implements LogSection { @Override @@ -62,7 +63,7 @@ public class LogSectionSystemInfo implements LogSection { builder.append("Screen : ").append(getScreenResolution(context)).append(", ") .append(ScreenDensity.get(context)).append(", ") .append(getScreenRefreshRate(context)).append("\n"); - builder.append("WindowSizeClass : ").append(WindowSizeClass.Companion.getWindowSizeClass(context.getResources())).append("\n"); + builder.append("WindowSizeClass : ").append(getWindowSizeClass(context.getResources())).append("\n"); builder.append("Font Scale : ").append(context.getResources().getConfiguration().fontScale).append("\n"); builder.append("Animation Scale : ").append(ContextUtil.getAnimationScale(context)).append("\n"); builder.append("Android : ").append(Build.VERSION.RELEASE).append(", API ") diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/ChatsNavHost.kt b/app/src/main/java/org/thoughtcrime/securesms/main/ChatsNavHost.kt index 20e005d8a4..8dda3996f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/ChatsNavHost.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/ChatsNavHost.kt @@ -39,6 +39,7 @@ 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 @@ -49,7 +50,8 @@ import org.thoughtcrime.securesms.conversation.v2.ConversationFragment import org.thoughtcrime.securesms.serialization.JsonSerializableNavType import org.thoughtcrime.securesms.window.AppScaffoldAnimationDefaults import org.thoughtcrime.securesms.window.AppScaffoldAnimationState -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isLargeScreenSupportEnabled +import org.thoughtcrime.securesms.window.isSplitPane import kotlin.reflect.typeOf import kotlin.time.Duration.Companion.milliseconds @@ -205,7 +207,7 @@ class ChatNavGraphState private constructor( private var hasWrittenToGraphicsLayer: Boolean by mutableStateOf(false) suspend fun writeGraphicsLayerToBitmap() { - if (WindowSizeClass.isLargeScreenSupportEnabled() && !windowSizeClass.isSplitPane() && hasWrittenToGraphicsLayer) { + if (isLargeScreenSupportEnabled() && !windowSizeClass.isSplitPane() && hasWrittenToGraphicsLayer) { chatBitmap = graphicsLayer.toImageBitmap() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt index 87fc588b0c..49fd3be542 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt @@ -15,12 +15,12 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.SnackbarDuration 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.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Dialogs import org.signal.core.ui.compose.Previews @@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.megaphone.Megaphone import org.thoughtcrime.securesms.megaphone.MegaphoneActionController import org.thoughtcrime.securesms.megaphone.Megaphones -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isSplitPane data class SnackbarState( val message: String, @@ -77,14 +77,14 @@ fun MainBottomChrome( megaphoneActionController: MegaphoneActionController, modifier: Modifier = Modifier ) { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass Column( modifier = modifier .fillMaxWidth() .animateContentSize() ) { - if (state.mainToolbarMode == MainToolbarMode.FULL && windowSizeClass.isCompact()) { + if (state.mainToolbarMode == MainToolbarMode.FULL && !windowSizeClass.isSplitPane()) { Box( contentAlignment = Alignment.CenterEnd, modifier = Modifier.fillMaxWidth() @@ -104,7 +104,7 @@ fun MainBottomChrome( ) } - val snackBarModifier = if (windowSizeClass.isCompact() && state.mainToolbarMode == MainToolbarMode.BASIC) { + val snackBarModifier = if (!windowSizeClass.isSplitPane() && state.mainToolbarMode == MainToolbarMode.BASIC) { Modifier.navigationBarsPadding() } else { Modifier diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt index 81a3f0a8e3..4d31c7c580 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt @@ -6,6 +6,7 @@ package org.thoughtcrime.securesms.main import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember @@ -13,7 +14,9 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import org.thoughtcrime.securesms.window.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass +import org.thoughtcrime.securesms.window.isAtLeast +import org.thoughtcrime.securesms.window.isSplitPane private val MEDIUM_CONTENT_CORNERS = 18.dp private val EXTENDED_CONTENT_CORNERS = 14.dp @@ -42,7 +45,7 @@ data class MainContentLayoutData( */ @Composable fun hasDragHandle(): Boolean { - return !WindowSizeClass.rememberWindowSizeClass().isCompact() + return currentWindowAdaptiveInfo().windowSizeClass.isSplitPane() } /** @@ -50,12 +53,12 @@ data class MainContentLayoutData( */ @Composable fun rememberDefaultPanePreferredWidth(maxWidth: Dp): Dp { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass return remember(maxWidth, windowSizeClass) { when { !windowSizeClass.isSplitPane() -> maxWidth - windowSizeClass.isExtended() -> 416.dp + windowSizeClass.windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED) -> 416.dp else -> (maxWidth - extraPadding) / 2f } } @@ -67,23 +70,23 @@ data class MainContentLayoutData( */ @Composable fun rememberContentLayoutData(mode: MainToolbarMode): MainContentLayoutData { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass return remember(windowSizeClass, mode) { MainContentLayoutData( shape = when { !windowSizeClass.isSplitPane() -> RectangleShape - windowSizeClass.isExtended() -> RoundedCornerShape(EXTENDED_CONTENT_CORNERS) + windowSizeClass.windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED) -> RoundedCornerShape(EXTENDED_CONTENT_CORNERS) else -> RoundedCornerShape(MEDIUM_CONTENT_CORNERS) }, navigationBarShape = when { !windowSizeClass.isSplitPane() -> RectangleShape - windowSizeClass.isExtended() -> RoundedCornerShape(0.dp, 0.dp, EXTENDED_CONTENT_CORNERS, EXTENDED_CONTENT_CORNERS) + windowSizeClass.windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED) -> RoundedCornerShape(0.dp, 0.dp, EXTENDED_CONTENT_CORNERS, EXTENDED_CONTENT_CORNERS) else -> RoundedCornerShape(0.dp, 0.dp, MEDIUM_CONTENT_CORNERS, MEDIUM_CONTENT_CORNERS) }, partitionWidth = when { !windowSizeClass.isSplitPane() -> 0.dp - windowSizeClass.isExtended() -> 24.dp + windowSizeClass.windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED) -> 24.dp else -> 13.dp }, listPaddingStart = when { @@ -97,7 +100,7 @@ data class MainContentLayoutData( }, detailPaddingEnd = when { !windowSizeClass.isSplitPane() -> 0.dp - windowSizeClass.isExtended() -> 24.dp + windowSizeClass.windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED) -> 24.dp else -> 12.dp } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainMegaphoneContainer.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainMegaphoneContainer.kt index 0369716645..f3fa4c634f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainMegaphoneContainer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainMegaphoneContainer.kt @@ -8,17 +8,18 @@ package org.thoughtcrime.securesms.main import android.app.Activity import android.content.Intent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.fragment.app.DialogFragment +import androidx.window.core.layout.WindowHeightSizeClass import org.signal.core.ui.compose.DayNightPreviews import org.signal.core.ui.compose.Previews import org.thoughtcrime.securesms.megaphone.Megaphone import org.thoughtcrime.securesms.megaphone.MegaphoneActionController import org.thoughtcrime.securesms.megaphone.MegaphoneComponent import org.thoughtcrime.securesms.megaphone.Megaphones -import org.thoughtcrime.securesms.window.WindowSizeClass data class MainMegaphoneState( val megaphone: Megaphone = Megaphone.NONE, @@ -44,9 +45,9 @@ fun MainMegaphoneContainer( controller: MegaphoneActionController, onMegaphoneVisible: (Megaphone) -> Unit ) { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val visible = remember(windowSizeClass, state) { - !(state.megaphone == Megaphone.NONE || state.mainToolbarMode != MainToolbarMode.FULL || windowSizeClass == WindowSizeClass.COMPACT_LANDSCAPE) + !(state.megaphone == Megaphone.NONE || state.mainToolbarMode != MainToolbarMode.FULL || windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT) } AnimatedVisibility(visible = visible) { @@ -57,7 +58,7 @@ fun MainMegaphoneContainer( } LaunchedEffect(state, windowSizeClass) { - if (state.megaphone == Megaphone.NONE || state.mainToolbarMode == MainToolbarMode.BASIC || windowSizeClass == WindowSizeClass.COMPACT_LANDSCAPE) { + if (state.megaphone == Megaphone.NONE || state.mainToolbarMode == MainToolbarMode.BASIC || windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT) { return@LaunchedEffect } diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt index d20c273cd7..6d6f527cae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainNavigationViewModel.kt @@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.megaphone.Megaphones import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.window.AppScaffoldNavigator -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isLargeScreenSupportEnabled import java.util.Optional @OptIn(ExperimentalMaterial3AdaptiveApi::class) @@ -198,7 +198,7 @@ class MainNavigationViewModel( override fun goTo(location: MainNavigationDetailLocation) { lockPaneToSecondary = false - if (!WindowSizeClass.isLargeScreenSupportEnabled()) { + if (!isLargeScreenSupportEnabled()) { goToLegacyDetailLocation?.invoke(location) return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/VerticalInsets.kt b/app/src/main/java/org/thoughtcrime/securesms/main/VerticalInsets.kt index adf2ee4af5..360c5bc411 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/VerticalInsets.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/VerticalInsets.kt @@ -12,6 +12,7 @@ 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.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf @@ -20,7 +21,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import kotlinx.parcelize.Parcelize -import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.isSplitPane @Parcelize data class VerticalInsets( @@ -41,7 +42,7 @@ fun rememberVerticalInsets(): State { val navigationBarPadding = navigationBarInsets.asPaddingValues() val density = LocalDensity.current - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val insets = rememberSaveable { mutableStateOf(VerticalInsets.Zero) } val updated = remember(statusBarInsets, navigationBarInsets, windowSizeClass) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt index 61e6e25ed5..b91b9145e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt @@ -5,8 +5,6 @@ package org.thoughtcrime.securesms.window -import android.content.res.Configuration -import android.content.res.Resources import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.foundation.background @@ -16,7 +14,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -42,23 +39,17 @@ import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.layout -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import androidx.window.core.ExperimentalWindowCoreApi import androidx.window.core.layout.WindowHeightSizeClass -import androidx.window.core.layout.WindowWidthSizeClass import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Previews -import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.main.MainFloatingActionButtonsCallback import org.thoughtcrime.securesms.main.MainNavigationBar import org.thoughtcrime.securesms.main.MainNavigationRail import org.thoughtcrime.securesms.main.MainNavigationState -import org.thoughtcrime.securesms.util.RemoteConfig import kotlin.math.max enum class Navigation { @@ -68,141 +59,15 @@ enum class Navigation { companion object { @Composable fun rememberNavigation(): Navigation { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - return remember(windowSizeClass) { windowSizeClass.navigation } - } - } -} - -/** - * Describes the size of screen we are displaying, and what components should be displayed. - * - * Screens should utilize this class by convention instead of calling [currentWindowAdaptiveInfo] - * themselves, as this class includes checks with [RemoteConfig] to ensure we're allowed to display - * content in different screen sizes. - * - * https://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes - */ -enum class WindowSizeClass( - val navigation: Navigation -) { - COMPACT_PORTRAIT(Navigation.BAR), - COMPACT_LANDSCAPE(Navigation.BAR), - MEDIUM_PORTRAIT(Navigation.RAIL), - MEDIUM_LANDSCAPE(Navigation.RAIL), - EXTENDED_PORTRAIT(Navigation.RAIL), - EXTENDED_LANDSCAPE(Navigation.RAIL); - - val listPaneDefaultPreferredWidth: Dp - get() = if (isExtended()) 416.dp else 316.dp - - val detailPaneMaxContentWidth: Dp = 624.dp - val horizontalPartitionDefaultSpacerSize: Dp = 12.dp - - fun isCompact(): Boolean = this == COMPACT_PORTRAIT || this == COMPACT_LANDSCAPE - fun isMedium(): Boolean = this == MEDIUM_PORTRAIT || this == MEDIUM_LANDSCAPE - fun isExtended(): Boolean = this == EXTENDED_PORTRAIT || this == EXTENDED_LANDSCAPE - - fun isLandscape(): Boolean = this == COMPACT_LANDSCAPE || this == MEDIUM_LANDSCAPE || this == EXTENDED_LANDSCAPE - fun isPortrait(): Boolean = !isLandscape() - - @JvmOverloads - fun isSplitPane( - forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape - ): Boolean { - return if (isLargeScreenSupportEnabled() && forceSplitPaneOnCompactLandscape) { - this != COMPACT_PORTRAIT - } else { - this.navigation != Navigation.BAR - } - } - - companion object { - @OptIn(ExperimentalWindowCoreApi::class) - fun Resources.getWindowSizeClass(): WindowSizeClass { - val orientation = configuration.orientation - - if (isForcedCompact()) { - return getCompactSizeClassForOrientation(orientation) - } - - val windowSizeClass = androidx.window.core.layout.WindowSizeClass.compute( - displayMetrics.widthPixels, - displayMetrics.heightPixels, - displayMetrics.density - ) - - return getSizeClassForOrientationAndSystemSizeClass(orientation, windowSizeClass) - } - - fun isLargeScreenSupportEnabled(): Boolean { - return RemoteConfig.largeScreenUi && SignalStore.internal.largeScreenUi - } - - fun isForcedCompact(): Boolean { - return !isLargeScreenSupportEnabled() - } - - @Composable - fun checkForcedCompact(): Boolean { - return !LocalInspectionMode.current && isForcedCompact() - } - - @Composable - fun rememberWindowSizeClass(forceCompact: Boolean = checkForcedCompact()): WindowSizeClass { - val orientation = LocalConfiguration.current.orientation - - if (forceCompact) { - return remember(orientation) { - getCompactSizeClassForOrientation(orientation) + return remember(windowSizeClass) { + if (windowSizeClass.isSplitPane() && windowSizeClass.windowHeightSizeClass.isAtLeast(WindowHeightSizeClass.MEDIUM)) { + RAIL + } else { + BAR } } - - val wsc = currentWindowAdaptiveInfo().windowSizeClass - - return remember(orientation, wsc) { - getSizeClassForOrientationAndSystemSizeClass(orientation, wsc) - } - } - - private fun getCompactSizeClassForOrientation(orientation: Int): WindowSizeClass { - return when (orientation) { - Configuration.ORIENTATION_PORTRAIT, Configuration.ORIENTATION_UNDEFINED, Configuration.ORIENTATION_SQUARE -> { - COMPACT_PORTRAIT - } - - Configuration.ORIENTATION_LANDSCAPE -> COMPACT_LANDSCAPE - else -> error("Unexpected orientation: $orientation") - } - } - - private fun getSizeClassForOrientationAndSystemSizeClass(orientation: Int, windowSizeClass: androidx.window.core.layout.WindowSizeClass): WindowSizeClass { - if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT || windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT) { - return getCompactSizeClassForOrientation(orientation) - } - - return when (orientation) { - Configuration.ORIENTATION_PORTRAIT, Configuration.ORIENTATION_UNDEFINED, Configuration.ORIENTATION_SQUARE -> { - when (windowSizeClass.windowWidthSizeClass) { - WindowWidthSizeClass.COMPACT -> COMPACT_PORTRAIT - WindowWidthSizeClass.MEDIUM -> MEDIUM_PORTRAIT - WindowWidthSizeClass.EXPANDED -> EXTENDED_PORTRAIT - else -> error("Unsupported.") - } - } - - Configuration.ORIENTATION_LANDSCAPE -> { - when (windowSizeClass.windowWidthSizeClass) { - WindowWidthSizeClass.COMPACT -> COMPACT_LANDSCAPE - WindowWidthSizeClass.MEDIUM -> MEDIUM_LANDSCAPE - WindowWidthSizeClass.EXPANDED -> EXTENDED_LANDSCAPE - else -> error("Unsupported.") - } - } - - else -> error("Unexpected orientation: $orientation") - } } } } @@ -240,8 +105,8 @@ fun AppScaffold( contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, animatorFactory: AppScaffoldAnimationStateFactory = AppScaffoldAnimationStateFactory.Default ) { - val isForcedCompact = WindowSizeClass.checkForcedCompact() - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val isForcedCompact = !LocalInspectionMode.current && !isLargeScreenSupportEnabled() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass if (isForcedCompact) { ListAndNavigation( @@ -249,7 +114,6 @@ fun AppScaffold( listContent = secondaryContent, navRailContent = navRailContent, bottomNavContent = bottomNavContent, - windowSizeClass = windowSizeClass, contentWindowInsets = contentWindowInsets, modifier = modifier ) @@ -314,7 +178,6 @@ fun AppScaffold( listContent = secondaryContent, navRailContent = navRailContent, bottomNavContent = bottomNavContent, - windowSizeClass = windowSizeClass, contentWindowInsets = WindowInsets() // parent scaffold already applies the necessary insets ) } @@ -380,10 +243,11 @@ private fun ListAndNavigation( navRailContent: @Composable () -> Unit, bottomNavContent: @Composable () -> Unit, snackbarHost: @Composable () -> Unit = {}, - windowSizeClass: WindowSizeClass, contentWindowInsets: WindowInsets, modifier: Modifier = Modifier ) { + val navigation = Navigation.rememberNavigation() + Scaffold( containerColor = Color.Transparent, topBar = topBarContent, @@ -394,9 +258,8 @@ private fun ListAndNavigation( Row( modifier = Modifier .padding(paddingValues) - .then(if (windowSizeClass.isLandscape()) Modifier.displayCutoutPadding() else Modifier) ) { - if (windowSizeClass.navigation == Navigation.RAIL) { + if (navigation == Navigation.RAIL) { navRailContent() } @@ -405,7 +268,7 @@ private fun ListAndNavigation( listContent() } - if (windowSizeClass.navigation == Navigation.BAR) { + if (navigation == Navigation.BAR) { bottomNavContent() } } @@ -418,11 +281,11 @@ private fun ListAndNavigation( @Composable private fun AppScaffoldPreview() { Previews.Preview { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass AppScaffold( navigator = rememberAppScaffoldNavigator( - isSplitPane = windowSizeClass.navigation != Navigation.BAR, + isSplitPane = windowSizeClass.isSplitPane(), defaultPanePreferredWidth = 416.dp, horizontalPartitionSpacerSize = 16.dp ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt index e4b0e5f315..c057d54094 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.Dp +import androidx.window.core.layout.WindowSizeClass import org.thoughtcrime.securesms.keyvalue.SignalStore /** @@ -94,9 +95,9 @@ open class AppScaffoldNavigator @RememberInComposition constructor(private va @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun rememberAppScaffoldNavigator( - windowSizeClass: WindowSizeClass = WindowSizeClass.rememberWindowSizeClass(), + windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass, isSplitPane: Boolean = windowSizeClass.isSplitPane( - forceSplitPaneOnCompactLandscape = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPaneOnCompactLandscape + forceSplitPane = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPane ), horizontalPartitionSpacerSize: Dp = windowSizeClass.horizontalPartitionDefaultSpacerSize, defaultPanePreferredWidth: Dp = windowSizeClass.listPaneDefaultPreferredWidth diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt index 05e886acce..d8e09e02c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -29,7 +30,7 @@ import org.thoughtcrime.securesms.R @Composable private fun AppScaffoldWithTopBarPreview() { Previews.Preview { - val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass AppScaffold( navigator = rememberAppScaffoldNavigator(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/WindowSizeClassExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/window/WindowSizeClassExtensions.kt new file mode 100644 index 0000000000..75e91e52d0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/window/WindowSizeClassExtensions.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.window + +import android.content.res.Resources +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.window.core.ExperimentalWindowCoreApi +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowSizeClass +import androidx.window.core.layout.WindowWidthSizeClass +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.RemoteConfig + +val WindowSizeClass.listPaneDefaultPreferredWidth: Dp get() = if (windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED)) 416.dp else 316.dp +val WindowSizeClass.horizontalPartitionDefaultSpacerSize: Dp get() = 12.dp +val WindowSizeClass.detailPaneMaxContentWidth: Dp get() = 624.dp + +fun WindowHeightSizeClass.isAtLeast(other: WindowHeightSizeClass): Boolean { + return hashCode() >= other.hashCode() +} + +fun WindowWidthSizeClass.isAtLeast(other: WindowWidthSizeClass): Boolean { + return hashCode() >= other.hashCode() +} + +/** + * Global check for large screen support, can be inlined after production release. + */ +fun isLargeScreenSupportEnabled(): Boolean { + return RemoteConfig.largeScreenUi && SignalStore.internal.largeScreenUi +} + +@OptIn(ExperimentalWindowCoreApi::class) +fun Resources.getWindowSizeClass(): WindowSizeClass { + return WindowSizeClass.compute(displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.density) +} + +/** + * Split Pane is enabled as long as the width size class is MEDIUM or greater + */ +@JvmOverloads +fun WindowSizeClass.isSplitPane( + forceSplitPane: Boolean = SignalStore.internal.forceSplitPane +): Boolean { + if (forceSplitPane) { + return true + } + + return windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.MEDIUM) +}