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.kotlin.subscribeBy
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.greenrobot.eventbus.EventBus
|
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.VoiceNoteMediaControllerOwner
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
|
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
|
||||||
import org.thoughtcrime.securesms.components.voice.VoiceNotePlayerView
|
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.contacts.paged.ContactSearchKey.RecipientSearchKey
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact
|
import org.thoughtcrime.securesms.contactshare.Contact
|
||||||
import org.thoughtcrime.securesms.contactshare.ContactUtil
|
import org.thoughtcrime.securesms.contactshare.ContactUtil
|
||||||
@@ -393,7 +397,8 @@ class ConversationFragment :
|
|||||||
SafetyNumberBottomSheet.Callbacks,
|
SafetyNumberBottomSheet.Callbacks,
|
||||||
EnableCallNotificationSettingsDialog.Callback,
|
EnableCallNotificationSettingsDialog.Callback,
|
||||||
MultiselectForwardBottomSheet.Callback,
|
MultiselectForwardBottomSheet.Callback,
|
||||||
DoubleTapEditEducationSheet.Callback {
|
DoubleTapEditEducationSheet.Callback,
|
||||||
|
FragmentBackPressedInfoProvider {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(ConversationFragment::class.java)
|
private val TAG = Log.tag(ConversationFragment::class.java)
|
||||||
@@ -935,6 +940,18 @@ class ConversationFragment :
|
|||||||
|
|
||||||
override fun onDismissForwardSheet() = Unit
|
override fun onDismissForwardSheet() = Unit
|
||||||
|
|
||||||
|
override fun getFragmentBackPressedInfo(): Flow<FragmentBackPressedInfo> {
|
||||||
|
return viewModel.backPressedState.map {
|
||||||
|
if (it.shouldHandleBackPressed()) {
|
||||||
|
FragmentBackPressedInfo.Enabled({
|
||||||
|
BackPressedCallback().handleOnBackPressed()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
FragmentBackPressedInfo.Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
private fun startActionMode() {
|
private fun startActionMode() {
|
||||||
@@ -1034,13 +1051,15 @@ class ConversationFragment :
|
|||||||
activity?.supportStartPostponedEnterTransition()
|
activity?.supportStartPostponedEnterTransition()
|
||||||
internalDidFirstFrameRender.update { true }
|
internalDidFirstFrameRender.update { true }
|
||||||
|
|
||||||
val backPressedDelegate = BackPressedDelegate()
|
if (requireActivity() is ConversationActivity) {
|
||||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedDelegate)
|
val backPressedCallback = BackPressedCallback()
|
||||||
|
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedCallback)
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
viewModel.backPressedState.collectLatest {
|
viewModel.backPressedState.collectLatest {
|
||||||
backPressedDelegate.isEnabled = it.shouldHandleBackPressed()
|
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() {
|
override fun handleOnBackPressed() {
|
||||||
Log.d(TAG, "onBackPressed()")
|
Log.d(TAG, "onBackPressed()")
|
||||||
val state = viewModel.backPressedState.value
|
val state = viewModel.backPressedState.value
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.thoughtcrime.securesms.MainNavigator
|
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.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
|
||||||
@@ -106,6 +108,9 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val backPressedState = remember { FragmentBackPressedState() }
|
||||||
|
FragmentBackHandler(backPressedState)
|
||||||
|
|
||||||
AndroidFragment(
|
AndroidFragment(
|
||||||
clazz = ConversationFragment::class.java,
|
clazz = ConversationFragment::class.java,
|
||||||
fragmentState = fragmentState,
|
fragmentState = fragmentState,
|
||||||
@@ -127,6 +132,8 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backPressedState.attach(fragment)
|
||||||
|
|
||||||
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
||||||
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
fragment.didFirstFrameRender.collectLatest {
|
fragment.didFirstFrameRender.collectLatest {
|
||||||
|
|||||||
Reference in New Issue
Block a user