mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 11:08:31 +00:00
Add BackHandler compatibility layer.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.compose
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.annotation.RememberInComposition
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Allows us to support view based fragments hosted in a compose-based activity having their
|
||||
* own back handling.
|
||||
*/
|
||||
@Stable
|
||||
class FragmentBackPressedState @RememberInComposition constructor() {
|
||||
var info by mutableStateOf<FragmentBackPressedInfo?>(null)
|
||||
|
||||
fun attach(fragment: Fragment) {
|
||||
if (fragment is FragmentBackPressedInfoProvider) {
|
||||
with(fragment) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(state = Lifecycle.State.CREATED) {
|
||||
getFragmentBackPressedInfo().collect {
|
||||
info = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the current back-pressed state, produced by a [Fragment]
|
||||
*/
|
||||
sealed interface FragmentBackPressedInfo {
|
||||
object Disabled : FragmentBackPressedInfo
|
||||
data class Enabled(val callback: () -> Unit) : FragmentBackPressedInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragment should implement this interface.
|
||||
*/
|
||||
interface FragmentBackPressedInfoProvider {
|
||||
fun getFragmentBackPressedInfo(): Flow<FragmentBackPressedInfo>
|
||||
}
|
||||
|
||||
/**
|
||||
* BackHandler for interop with legacy style fragments.
|
||||
* Don't forget to call [FragmentBackPressedState.attach]!
|
||||
*/
|
||||
@Composable
|
||||
fun FragmentBackHandler(state: FragmentBackPressedState) {
|
||||
val info = state.info
|
||||
val enabled = when (info) {
|
||||
FragmentBackPressedInfo.Disabled -> false
|
||||
is FragmentBackPressedInfo.Enabled -> true
|
||||
null -> false
|
||||
}
|
||||
|
||||
BackHandler(enabled = enabled) {
|
||||
if (info is FragmentBackPressedInfo.Enabled) {
|
||||
info.callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,11 +86,13 @@ import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@@ -153,6 +155,8 @@ import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
|
||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlayerView
|
||||
import org.thoughtcrime.securesms.compose.FragmentBackPressedInfo
|
||||
import org.thoughtcrime.securesms.compose.FragmentBackPressedInfoProvider
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.contactshare.ContactUtil
|
||||
@@ -393,7 +397,8 @@ class ConversationFragment :
|
||||
SafetyNumberBottomSheet.Callbacks,
|
||||
EnableCallNotificationSettingsDialog.Callback,
|
||||
MultiselectForwardBottomSheet.Callback,
|
||||
DoubleTapEditEducationSheet.Callback {
|
||||
DoubleTapEditEducationSheet.Callback,
|
||||
FragmentBackPressedInfoProvider {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationFragment::class.java)
|
||||
@@ -935,6 +940,18 @@ class ConversationFragment :
|
||||
|
||||
override fun onDismissForwardSheet() = Unit
|
||||
|
||||
override fun getFragmentBackPressedInfo(): Flow<FragmentBackPressedInfo> {
|
||||
return viewModel.backPressedState.map {
|
||||
if (it.shouldHandleBackPressed()) {
|
||||
FragmentBackPressedInfo.Enabled({
|
||||
BackPressedCallback().handleOnBackPressed()
|
||||
})
|
||||
} else {
|
||||
FragmentBackPressedInfo.Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
private fun startActionMode() {
|
||||
@@ -1034,13 +1051,15 @@ class ConversationFragment :
|
||||
activity?.supportStartPostponedEnterTransition()
|
||||
internalDidFirstFrameRender.update { true }
|
||||
|
||||
val backPressedDelegate = BackPressedDelegate()
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedDelegate)
|
||||
if (requireActivity() is ConversationActivity) {
|
||||
val backPressedCallback = BackPressedCallback()
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedCallback)
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel.backPressedState.collectLatest {
|
||||
backPressedDelegate.isEnabled = it.shouldHandleBackPressed()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel.backPressedState.collectLatest {
|
||||
backPressedCallback.isEnabled = it.shouldHandleBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2489,7 +2508,7 @@ class ConversationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private inner class BackPressedDelegate : OnBackPressedCallback(false) {
|
||||
private inner class BackPressedCallback : OnBackPressedCallback(false) {
|
||||
override fun handleOnBackPressed() {
|
||||
Log.d(TAG, "onBackPressed()")
|
||||
val state = viewModel.backPressedState.value
|
||||
|
||||
@@ -44,6 +44,8 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.MainNavigator
|
||||
import org.thoughtcrime.securesms.compose.FragmentBackHandler
|
||||
import org.thoughtcrime.securesms.compose.FragmentBackPressedState
|
||||
import org.thoughtcrime.securesms.conversation.ConversationArgs
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationFragment
|
||||
@@ -106,6 +108,9 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
)
|
||||
}
|
||||
|
||||
val backPressedState = remember { FragmentBackPressedState() }
|
||||
FragmentBackHandler(backPressedState)
|
||||
|
||||
AndroidFragment(
|
||||
clazz = ConversationFragment::class.java,
|
||||
fragmentState = fragmentState,
|
||||
@@ -127,6 +132,8 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
backPressedState.attach(fragment)
|
||||
|
||||
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
||||
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
fragment.didFirstFrameRender.collectLatest {
|
||||
|
||||
Reference in New Issue
Block a user