Utilize snapshotFlow to fix insets.

This commit is contained in:
Alex Hart
2025-10-09 14:35:07 -03:00
committed by Cody Henthorne
parent b49074a786
commit 971bcf4f41
5 changed files with 58 additions and 86 deletions

View File

@@ -102,7 +102,6 @@ import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
import org.thoughtcrime.securesms.main.DetailsScreenNavHost import org.thoughtcrime.securesms.main.DetailsScreenNavHost
import org.thoughtcrime.securesms.main.InsetsViewModelUpdater
import org.thoughtcrime.securesms.main.MainBottomChrome import org.thoughtcrime.securesms.main.MainBottomChrome
import org.thoughtcrime.securesms.main.MainBottomChromeCallback import org.thoughtcrime.securesms.main.MainBottomChromeCallback
import org.thoughtcrime.securesms.main.MainBottomChromeState import org.thoughtcrime.securesms.main.MainBottomChromeState
@@ -415,8 +414,6 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
} }
} }
InsetsViewModelUpdater()
AppScaffold( AppScaffold(
navigator = wrappedNavigator, navigator = wrappedNavigator,
paneExpansionState = paneExpansionState, paneExpansionState = paneExpansionState,

View File

@@ -13,7 +13,7 @@ import androidx.core.view.WindowInsetsCompat
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.main.InsetsViewModel import org.thoughtcrime.securesms.main.VerticalInsets
import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -66,7 +66,7 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
private var insets: WindowInsetsCompat? = null private var insets: WindowInsetsCompat? = null
private var windowTypes: Int = InsetAwareConstraintLayout.windowTypes private var windowTypes: Int = InsetAwareConstraintLayout.windowTypes
private var verticalInsetOverride: InsetsViewModel.Insets = InsetsViewModel.Insets.Zero private var verticalInsetOverride: VerticalInsets = VerticalInsets.Zero
private val windowInsetsListener = androidx.core.view.OnApplyWindowInsetsListener { _, insets -> private val windowInsetsListener = androidx.core.view.OnApplyWindowInsetsListener { _, insets ->
this.insets = insets this.insets = insets
@@ -130,7 +130,7 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
} }
} }
fun applyInsets(insets: InsetsViewModel.Insets) { fun applyInsets(insets: VerticalInsets) {
verticalInsetOverride = insets verticalInsetOverride = insets
if (this.insets != null) { if (this.insets != null) {
@@ -138,14 +138,6 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
} }
} }
fun clearVerticalInsetOverride() {
verticalInsetOverride = InsetsViewModel.Insets.Zero
if (this.insets != null) {
applyInsets(this.insets!!.getInsets(windowTypes), this.insets!!.getInsets(keyboardType))
}
}
fun addKeyboardStateListener(listener: KeyboardStateListener) { fun addKeyboardStateListener(listener: KeyboardStateListener) {
keyboardStateListeners += listener keyboardStateListeners += listener
} }
@@ -165,8 +157,8 @@ open class InsetAwareConstraintLayout @JvmOverloads constructor(
private fun applyInsets(windowInsets: Insets, keyboardInsets: Insets) { private fun applyInsets(windowInsets: Insets, keyboardInsets: Insets) {
val isLtr = ViewUtil.isLtr(this) val isLtr = ViewUtil.isLtr(this)
val statusBar = if (verticalInsetOverride == InsetsViewModel.Insets.Zero) windowInsets.top else verticalInsetOverride.statusBar.roundToInt() val statusBar = if (verticalInsetOverride == VerticalInsets.Zero) windowInsets.top else verticalInsetOverride.statusBar.roundToInt()
val navigationBar = if (verticalInsetOverride == InsetsViewModel.Insets.Zero) windowInsets.bottom else verticalInsetOverride.navBar.roundToInt() val navigationBar = if (verticalInsetOverride == VerticalInsets.Zero) windowInsets.bottom else verticalInsetOverride.navBar.roundToInt()
val parentStart = if (isLtr) windowInsets.left else windowInsets.right val parentStart = if (isLtr) windowInsets.left else windowInsets.right
val parentEnd = if (isLtr) windowInsets.right else windowInsets.left val parentEnd = if (isLtr) windowInsets.right else windowInsets.left

View File

@@ -258,8 +258,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2 import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
import org.thoughtcrime.securesms.longmessage.LongMessageFragment import org.thoughtcrime.securesms.longmessage.LongMessageFragment
import org.thoughtcrime.securesms.main.InsetsViewModel
import org.thoughtcrime.securesms.main.MainNavigationListLocation import org.thoughtcrime.securesms.main.MainNavigationListLocation
import org.thoughtcrime.securesms.main.VerticalInsets
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity
@@ -490,8 +490,6 @@ class ConversationFragment :
private val shareDataTimestampViewModel: ShareDataTimestampViewModel by activityViewModels() private val shareDataTimestampViewModel: ShareDataTimestampViewModel by activityViewModels()
private val insetsViewModel: InsetsViewModel by activityViewModels()
private val inlineQueryController: InlineQueryResultsControllerV2 by lazy { private val inlineQueryController: InlineQueryResultsControllerV2 by lazy {
InlineQueryResultsControllerV2( InlineQueryResultsControllerV2(
this, this,
@@ -601,22 +599,13 @@ class ConversationFragment :
SignalLocalMetrics.ConversationOpen.start() SignalLocalMetrics.ConversationOpen.start()
} }
fun applyRootInsets(insets: VerticalInsets) {
binding.root.applyInsets(insets)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.toolbar.isBackInvokedCallbackEnabled = false binding.toolbar.isBackInvokedCallbackEnabled = false
if (WindowSizeClass.isLargeScreenSupportEnabled()) {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
binding.root.clearVerticalInsetOverride()
if (!resources.getWindowSizeClass().isSplitPane()) {
insetsViewModel.insets.collect {
binding.root.applyInsets(it)
}
}
}
}
}
binding.root.setApplyRootInsets(!WindowSizeClass.isLargeScreenSupportEnabled()) binding.root.setApplyRootInsets(!WindowSizeClass.isLargeScreenSupportEnabled())
binding.root.setUseWindowTypes(!WindowSizeClass.isLargeScreenSupportEnabled()) binding.root.setUseWindowTypes(!WindowSizeClass.isLargeScreenSupportEnabled())

View File

@@ -6,14 +6,22 @@
package org.thoughtcrime.securesms.main package org.thoughtcrime.securesms.main
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.fragment.compose.AndroidFragment import androidx.fragment.compose.AndroidFragment
import androidx.fragment.compose.rememberFragmentState import androidx.fragment.compose.rememberFragmentState
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.toRoute import androidx.navigation.toRoute
import kotlinx.coroutines.launch
import org.thoughtcrime.securesms.conversation.ConversationArgs import org.thoughtcrime.securesms.conversation.ConversationArgs
import org.thoughtcrime.securesms.conversation.ConversationIntents import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.v2.ConversationFragment import org.thoughtcrime.securesms.conversation.v2.ConversationFragment
@@ -29,10 +37,12 @@ fun NavGraphBuilder.chatNavGraphBuilder() {
typeMap = mapOf( typeMap = mapOf(
typeOf<ConversationArgs>() to JsonSerializableNavType(ConversationArgs.serializer()) typeOf<ConversationArgs>() to JsonSerializableNavType(ConversationArgs.serializer())
) )
) { ) { navBackStackEntry ->
val route = it.toRoute<MainNavigationDetailLocation.Chats.Conversation>() val route = navBackStackEntry.toRoute<MainNavigationDetailLocation.Chats.Conversation>()
val fragmentState = key(route) { rememberFragmentState() } val fragmentState = key(route) { rememberFragmentState() }
val context = LocalContext.current val context = LocalContext.current
val insets by rememberVerticalInsets()
val insetFlow = remember { snapshotFlow { insets } }
AndroidFragment( AndroidFragment(
clazz = ConversationFragment::class.java, clazz = ConversationFragment::class.java,
@@ -40,6 +50,14 @@ fun NavGraphBuilder.chatNavGraphBuilder() {
arguments = requireNotNull(ConversationIntents.createBuilderSync(context, route.conversationArgs).build().extras) { "Handed null Conversation intent arguments." }, arguments = requireNotNull(ConversationIntents.createBuilderSync(context, route.conversationArgs).build().extras) { "Handed null Conversation intent arguments." },
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) ) { fragment ->
fragment.viewLifecycleOwner.lifecycleScope.launch {
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
insetFlow.collect {
fragment.applyRootInsets(insets)
}
}
}
}
} }
} }

View File

@@ -12,38 +12,24 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Density
import androidx.lifecycle.ViewModel import org.thoughtcrime.securesms.window.WindowSizeClass
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
class InsetsViewModel : ViewModel() { data class VerticalInsets(
private val internalInsets = MutableStateFlow(Insets.Zero) @param:Px val statusBar: Float,
val insets: StateFlow<Insets> = internalInsets @param:Px val navBar: Float
) {
fun updateInsets(insets: Insets) { companion object {
internalInsets.update { insets } val Zero = VerticalInsets(0f, 0f)
}
data class Insets(
@param:Px val statusBar: Float,
@param:Px val navBar: Float
) {
companion object {
val Zero = Insets(0f, 0f)
}
} }
} }
@Composable @Composable
fun InsetsViewModelUpdater( fun rememberVerticalInsets(): State<VerticalInsets> {
insetsViewModel: InsetsViewModel = viewModel { InsetsViewModel() }
) {
val statusBarInsets = WindowInsets.statusBars val statusBarInsets = WindowInsets.statusBars
val navigationBarInsets = WindowInsets.navigationBars val navigationBarInsets = WindowInsets.navigationBars
@@ -51,35 +37,27 @@ fun InsetsViewModelUpdater(
val navigationBarPadding = navigationBarInsets.asPaddingValues() val navigationBarPadding = navigationBarInsets.asPaddingValues()
val density = LocalDensity.current val density = LocalDensity.current
LaunchedEffect( val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
statusBarPadding,
navigationBarPadding, val insets = remember { mutableStateOf(VerticalInsets.Zero) }
density val updated = remember(statusBarInsets, navigationBarInsets, windowSizeClass) {
) { insets.value = if (windowSizeClass.isSplitPane()) {
calculateAndUpdateInsets( VerticalInsets.Zero
density, } else {
insetsViewModel, calculateAndUpdateInsets(density, statusBarPadding, navigationBarPadding)
statusBarPadding, }
navigationBarPadding
) 0
} }
SideEffect { return insets
calculateAndUpdateInsets(
density,
insetsViewModel,
statusBarPadding,
navigationBarPadding
)
}
} }
private fun calculateAndUpdateInsets( private fun calculateAndUpdateInsets(
density: Density, density: Density,
insetsViewModel: InsetsViewModel,
statusBarPadding: PaddingValues, statusBarPadding: PaddingValues,
navigationBarPadding: PaddingValues navigationBarPadding: PaddingValues
) { ): VerticalInsets {
val statusBarPx = with(density) { val statusBarPx = with(density) {
(statusBarPadding.calculateTopPadding() + statusBarPadding.calculateBottomPadding()).toPx() (statusBarPadding.calculateTopPadding() + statusBarPadding.calculateBottomPadding()).toPx()
} }
@@ -88,10 +66,8 @@ private fun calculateAndUpdateInsets(
(navigationBarPadding.calculateTopPadding() + navigationBarPadding.calculateBottomPadding()).toPx() (navigationBarPadding.calculateTopPadding() + navigationBarPadding.calculateBottomPadding()).toPx()
} }
insetsViewModel.updateInsets( return VerticalInsets(
InsetsViewModel.Insets( statusBar = statusBarPx,
statusBar = statusBarPx, navBar = navBarPx
navBar = navBarPx
)
) )
} }