Fix bad behavior when rotating device with message details open.

This commit is contained in:
Alex Hart
2026-02-23 10:54:21 -04:00
committed by GitHub
parent b5c666a1f4
commit cf9f98efc9
10 changed files with 147 additions and 100 deletions

View File

@@ -97,7 +97,6 @@ import org.signal.mediasend.MediaSendActivityContract
import org.thoughtcrime.securesms.backup.v2.ArchiveRestoreProgress
import org.thoughtcrime.securesms.backup.v2.ui.verify.VerifyBackupKeyActivity
import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar.show
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsActivity
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.calls.log.CallLogFragment
import org.thoughtcrime.securesms.calls.new.NewCallActivity
@@ -140,6 +139,7 @@ import org.thoughtcrime.securesms.main.MainContentLayoutData
import org.thoughtcrime.securesms.main.MainMegaphoneState
import org.thoughtcrime.securesms.main.MainNavigationBar
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
import org.thoughtcrime.securesms.main.MainNavigationDetailLocationEffect
import org.thoughtcrime.securesms.main.MainNavigationListLocation
import org.thoughtcrime.securesms.main.MainNavigationRail
import org.thoughtcrime.securesms.main.MainNavigationViewModel
@@ -156,7 +156,6 @@ import org.thoughtcrime.securesms.main.chatNavGraphBuilder
import org.thoughtcrime.securesms.main.navigateToDetailLocation
import org.thoughtcrime.securesms.main.rememberDetailNavHostController
import org.thoughtcrime.securesms.main.rememberFocusRequester
import org.thoughtcrime.securesms.main.rememberMainNavigationDetailLocation
import org.thoughtcrime.securesms.main.storiesNavGraphBuilder
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
@@ -447,7 +446,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
val chatNavGraphState = ChatNavGraphState.remember(windowSizeClass)
val mutableInteractionSource = remember { MutableInteractionSource() }
val mainNavigationDetailLocation by rememberMainNavigationDetailLocation(mainNavigationViewModel, chatNavGraphState::writeGraphicsLayerToBitmap)
MainNavigationDetailLocationEffect(mainNavigationViewModel, chatNavGraphState::writeGraphicsLayerToBitmap)
val chatsNavHostController = rememberDetailNavHostController(
onRequestFocus = rememberFocusRequester(
@@ -477,24 +476,28 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
storiesNavGraphBuilder()
}
LaunchedEffect(mainNavigationDetailLocation) {
LaunchedEffect(Unit) {
mainNavigationViewModel.clearEarlyDetailLocation()
when (mainNavigationDetailLocation) {
is MainNavigationDetailLocation.Empty -> {
when (mainNavigationState.currentListLocation) {
MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> chatsNavHostController
MainNavigationListLocation.CALLS -> callsNavHostController
MainNavigationListLocation.STORIES -> storiesNavHostController
}.navigateToDetailLocation(mainNavigationDetailLocation)
}
mainNavigationViewModel.detailLocation.collect { location ->
when (location) {
is MainNavigationDetailLocation.Empty -> {
when (mainNavigationState.currentListLocation) {
MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> chatsNavHostController
MainNavigationListLocation.CALLS -> callsNavHostController
MainNavigationListLocation.STORIES -> storiesNavHostController
}.navigateToDetailLocation(location)
}
is MainNavigationDetailLocation.Chats -> {
chatNavGraphState.writeGraphicsLayerToBitmap()
chatsNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
}
is MainNavigationDetailLocation.Chats -> {
if (location is MainNavigationDetailLocation.Chats.Conversation) {
chatNavGraphState.writeGraphicsLayerToBitmap()
}
chatsNavHostController.navigateToDetailLocation(location)
}
is MainNavigationDetailLocation.Calls -> callsNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
is MainNavigationDetailLocation.Stories -> storiesNavHostController.navigateToDetailLocation(mainNavigationDetailLocation)
is MainNavigationDetailLocation.Calls -> callsNavHostController.navigateToDetailLocation(location)
is MainNavigationDetailLocation.Stories -> storiesNavHostController.navigateToDetailLocation(location)
}
}
}
@@ -752,27 +755,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
val coroutine = rememberCoroutineScope()
return remember(scaffoldNavigator, coroutine) {
mainNavigationViewModel.wrapNavigator(coroutine, scaffoldNavigator) { detailLocation ->
when (detailLocation) {
is MainNavigationDetailLocation.Chats.Conversation -> {
startActivity(
ConversationIntents.createBuilderSync(this, detailLocation.conversationArgs.recipientId, detailLocation.conversationArgs.threadId)
.withArgs(detailLocation.conversationArgs)
.build()
)
}
is MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails -> {
startActivity(CallLinkDetailsActivity.createIntent(this, detailLocation.callLinkRoomId))
}
is MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName -> {
error("Unexpected subroute EditCallLinkName.")
}
MainNavigationDetailLocation.Empty -> Unit
}
}
mainNavigationViewModel.wrapNavigator(coroutine, scaffoldNavigator)
}
}

View File

@@ -47,7 +47,10 @@ public abstract class FullScreenDialogFragment extends DialogFragment {
@Override
public void onResume() {
super.onResume();
WindowUtil.initializeScreenshotSecurity(requireContext(), requireDialog().getWindow());
if (getShowsDialog()) {
WindowUtil.initializeScreenshotSecurity(requireContext(), requireDialog().getWindow());
}
}
protected void onNavigateUp() {

View File

@@ -6,6 +6,7 @@ import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import org.signal.core.ui.initializeScreenshotSecurity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.fragments.findListener
@@ -57,6 +58,11 @@ abstract class WrapperDialogFragment : DialogFragment(R.layout.fragment_containe
}
}
override fun onResume() {
super.onResume()
dialog?.window?.initializeScreenshotSecurity()
}
open fun onHandleBackPressed() {
dismissAllowingStateLoss()
}

View File

@@ -30,26 +30,28 @@ class CheckoutNavHostFragment : NavHostFragment() {
get() = requireArguments().getSerializableCompat(ARG_TYPE, InAppPaymentType::class.java)!!
override fun onCreate(savedInstanceState: Bundle?) {
if (savedInstanceState == null) {
val navGraph = navController.navInflater.inflate(R.navigation.checkout)
navGraph.setStartDestination(
when (inAppPaymentType) {
InAppPaymentType.UNKNOWN -> error("Unsupported start destination")
InAppPaymentType.ONE_TIME_GIFT -> R.id.giftFlowStartFragment
InAppPaymentType.ONE_TIME_DONATION, InAppPaymentType.RECURRING_DONATION -> R.id.donateToSignalFragment
InAppPaymentType.RECURRING_BACKUP -> error("Unsupported start destination")
}
)
val navGraph = navController.navInflater.inflate(R.navigation.checkout)
navGraph.setStartDestination(
when (inAppPaymentType) {
InAppPaymentType.UNKNOWN -> error("Unsupported start destination")
InAppPaymentType.ONE_TIME_GIFT -> R.id.giftFlowStartFragment
InAppPaymentType.ONE_TIME_DONATION, InAppPaymentType.RECURRING_DONATION -> R.id.donateToSignalFragment
InAppPaymentType.RECURRING_BACKUP -> error("Unsupported start destination")
}
)
val startBundle = when (inAppPaymentType) {
val startBundle = if (savedInstanceState == null) {
when (inAppPaymentType) {
InAppPaymentType.UNKNOWN -> error("Unknown payment type")
InAppPaymentType.ONE_TIME_GIFT, InAppPaymentType.RECURRING_BACKUP -> null
InAppPaymentType.ONE_TIME_DONATION, InAppPaymentType.RECURRING_DONATION -> DonateToSignalFragmentArgs.Builder(inAppPaymentType).build().toBundle()
}
navController.setGraph(navGraph, startBundle)
} else {
null
}
navController.setGraph(navGraph, startBundle)
super.onCreate(savedInstanceState)
}
}

View File

@@ -57,6 +57,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.doOnPreDraw
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentResultListener
import androidx.fragment.app.activityViewModels
@@ -279,6 +280,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
import org.thoughtcrime.securesms.longmessage.LongMessageFragment
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
import org.thoughtcrime.securesms.main.MainNavigationListLocation
import org.thoughtcrime.securesms.main.MainNavigationViewModel
import org.thoughtcrime.securesms.main.MainSnackbarHostKey
@@ -412,6 +414,7 @@ class ConversationFragment :
private const val ACTION_PINNED_SHORTCUT = "action_pinned_shortcut"
private const val SAVED_STATE_IS_SEARCH_REQUESTED = "is_search_requested"
private const val EMOJI_SEARCH_FRAGMENT_TAG = "EmojiSearchFragment"
private const val MESSAGE_DETAILS_TAG = "MessageDetailsFragment"
private const val SCROLL_HEADER_ANIMATION_DURATION: Long = 100L
private const val SCROLL_HEADER_CLOSE_DELAY: Long = SCROLL_HEADER_ANIMATION_DURATION * 4
@@ -780,6 +783,10 @@ class ConversationFragment :
}
keyboardEvents = null
if (!requireActivity().isChangingConfigurations) {
(requireActivity().supportFragmentManager.findFragmentByTag(MESSAGE_DETAILS_TAG) as? DialogFragment)?.dismissAllowingStateLoss()
}
super.onDestroyView()
if (pinnedShortcutReceiver != null) {
requireActivity().unregisterReceiver(pinnedShortcutReceiver)
@@ -2793,7 +2800,11 @@ class ConversationFragment :
private fun handleDisplayDetails(conversationMessage: ConversationMessage) {
val recipientSnapshot = viewModel.recipientSnapshot ?: return
MessageDetailsFragment.create(conversationMessage.messageRecord, recipientSnapshot.id).show(childFragmentManager, null)
if (requireActivity() is MainActivity) {
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Chats.MessageDetails(recipientSnapshot.id, conversationMessage.messageRecord.id))
} else {
MessageDetailsFragment.create(conversationMessage.messageRecord, recipientSnapshot.id).show(requireActivity().supportFragmentManager, MESSAGE_DETAILS_TAG)
}
}
private fun handleDeleteMessages(messageParts: Set<MultiselectPart>) {
@@ -3309,8 +3320,10 @@ class ConversationFragment :
.show(childFragmentManager)
} else if (messageRecord.hasFailedWithNetworkFailures()) {
ConversationDialogs.displayMessageCouldNotBeSentDialog(requireContext(), messageRecord)
} else if (requireActivity() is MainActivity) {
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Chats.MessageDetails(recipientId, messageRecord.id))
} else {
MessageDetailsFragment.create(messageRecord, recipientId).show(childFragmentManager, null)
MessageDetailsFragment.create(messageRecord, recipientId).show(requireActivity().supportFragmentManager, MESSAGE_DETAILS_TAG)
}
}

View File

@@ -13,6 +13,8 @@ import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -50,6 +52,8 @@ 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
import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.serialization.JsonSerializableNavType
import org.thoughtcrime.securesms.window.AppScaffoldAnimationDefaults
import org.thoughtcrime.securesms.window.AppScaffoldAnimationState
@@ -73,8 +77,8 @@ fun NavGraphBuilder.chatNavGraphBuilder(
val context = LocalContext.current
// Because it can take a long time to load content, we use a "fake" chat list image to delay displaying
// the fragment and prevent pop-in
var shouldDisplayFragment by remember { mutableStateOf(false) }
// the fragment and prevent pop-in. When there's no bitmap (e.g. returning from a sub-route), skip the animation.
var shouldDisplayFragment by remember { mutableStateOf(chatNavGraphState.chatBitmap == null) }
val transition: Transition<Boolean> = updateTransition(shouldDisplayFragment)
val bitmap = chatNavGraphState.chatBitmap
@@ -126,16 +130,42 @@ fun NavGraphBuilder.chatNavGraphBuilder(
fragment.viewLifecycleOwner.lifecycleScope.launch {
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
fragment.didFirstFrameRender.collectLatest {
shouldDisplayFragment = it
if (!it) {
delay(150.milliseconds)
shouldDisplayFragment = true
if (!shouldDisplayFragment) {
shouldDisplayFragment = it
if (!it) {
delay(150.milliseconds)
shouldDisplayFragment = true
}
}
}
}
}
}
}
composable<MainNavigationDetailLocation.Chats.MessageDetails>(
typeMap = mapOf(
typeOf<RecipientId>() to JsonSerializableNavType(RecipientId.serializer())
)
) { navBackStackEntry ->
val context = LocalContext.current
val route = navBackStackEntry.toRoute<MainNavigationDetailLocation.Chats.MessageDetails>()
val fragmentState = key(route) { rememberFragmentState() }
LaunchedEffect(Unit) {
(context as? MainNavigator.NavigatorProvider)?.onFirstRender()
}
AndroidFragment(
clazz = MessageDetailsFragment::class.java,
fragmentState = fragmentState,
arguments = MessageDetailsFragment.args(route.recipientId, route.messageId),
modifier = Modifier.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.statusBarsPadding()
.navigationBarsPadding()
)
}
}
@Composable

View File

@@ -17,7 +17,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -65,10 +64,10 @@ fun EmptyDetailScreen() {
* utilizing collectAsStateWithLifecycle. Then the latest value is remembered as a saveable using the default [MainNavigationDetailLocation.Saver]
*/
@Composable
fun rememberMainNavigationDetailLocation(
fun MainNavigationDetailLocationEffect(
mainNavigationViewModel: MainNavigationViewModel,
onWillFocusPrimary: suspend () -> Unit = {}
): State<MainNavigationDetailLocation> {
) {
val state = rememberSaveable(
stateSaver = MainNavigationDetailLocation.Saver(
mainNavigationViewModel.earlyNavigationDetailLocationRequested
@@ -84,7 +83,9 @@ fun rememberMainNavigationDetailLocation(
if (it == MainNavigationDetailLocation.Empty) {
ThreePaneScaffoldRole.Secondary
} else {
onWillFocusPrimary()
if (it.isContentRoot) {
onWillFocusPrimary()
}
ThreePaneScaffoldRole.Primary
}
)
@@ -93,8 +94,6 @@ fun rememberMainNavigationDetailLocation(
state.value = it
}
}
return state
}
@Composable
@@ -147,6 +146,7 @@ fun rememberDetailNavHostController(
fun NavHostController.navigateToDetailLocation(location: MainNavigationDetailLocation) {
navigate(location) {
launchSingleTop = true
if (location.isContentRoot) {
popUpTo(graph.id) { inclusive = true }
}

View File

@@ -65,6 +65,13 @@ sealed class MainNavigationDetailLocation : Parcelable {
@IgnoredOnParcel
override val controllerKey: RecipientId = conversationArgs.recipientId
}
@Serializable
data class MessageDetails(val recipientId: RecipientId, val messageId: Long) : Chats() {
@Transient
@IgnoredOnParcel
override val controllerKey: RecipientId = recipientId
}
}
/**

View File

@@ -66,7 +66,6 @@ class MainNavigationViewModel(
private var navigator: AppScaffoldNavigator<Any>? = null
private var navigatorScope: CoroutineScope? = null
private var goToLegacyDetailLocation: ((MainNavigationDetailLocation) -> Unit)? = null
private val internalDetailLocation = MutableSharedFlow<MainNavigationDetailLocation>()
val detailLocation: SharedFlow<MainNavigationDetailLocation> = internalDetailLocation
@@ -159,8 +158,7 @@ class MainNavigationViewModel(
* Sets the navigator on the view-model. This wraps the given navigator in our own delegating implementation
* such that we can react to navigateTo/Back signals and maintain proper state for internalDetailLocation.
*/
fun wrapNavigator(composeScope: CoroutineScope, threePaneScaffoldNavigator: ThreePaneScaffoldNavigator<Any>, goToLegacyDetailLocation: (MainNavigationDetailLocation) -> Unit): AppScaffoldNavigator<Any> {
this.goToLegacyDetailLocation = goToLegacyDetailLocation
fun wrapNavigator(composeScope: CoroutineScope, threePaneScaffoldNavigator: ThreePaneScaffoldNavigator<Any>): AppScaffoldNavigator<Any> {
this.navigatorScope = composeScope
this.navigator = Nav(threePaneScaffoldNavigator)

View File

@@ -1,12 +1,15 @@
package org.thoughtcrime.securesms.messagedetails
import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
@@ -15,7 +18,7 @@ import com.bumptech.glide.RequestManager
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.FullScreenDialogFragment
import org.thoughtcrime.securesms.components.WrapperDialogFragment
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
@@ -46,7 +49,7 @@ import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.fragments.requireListener
class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter.Callbacks {
class MessageDetailsFragment : Fragment(), MessageDetailsAdapter.Callbacks {
private lateinit var requestManager: RequestManager
private lateinit var viewModel: MessageDetailsViewModel
private lateinit var adapter: MessageDetailsAdapter
@@ -55,9 +58,16 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter
private fun getVoiceNoteMediaController() = requireListener<VoiceNoteMediaControllerOwner>().voiceNoteMediaController
override fun getTitle() = R.string.AndroidManifest__message_details
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.full_screen_dialog_fragment, container, false)
inflater.inflate(R.layout.message_details_fragment, view.findViewById(R.id.full_screen_dialog_content), true)
override fun getDialogLayoutResource() = R.layout.message_details_fragment
val toolbar: Toolbar = view.findViewById(R.id.full_screen_dialog_toolbar)
toolbar.setTitle(R.string.AndroidManifest__message_details)
toolbar.setNavigationOnClickListener { requireActivity().onBackPressedDispatcher.onBackPressed() }
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requestManager = Glide.with(this)
@@ -67,16 +77,6 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter
initializeVideoPlayer(view)
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
if (activity is Callback) {
(activity as Callback?)!!.onMessageDetailsFragmentDismissed()
} else if (parentFragment is Callback) {
(parentFragment as Callback?)!!.onMessageDetailsFragmentDismissed()
}
}
private fun initializeList(view: View) {
val list = view.findViewById<RecyclerView>(R.id.message_details_list)
val toolbarShadow = view.findViewById<View>(R.id.toolbar_shadow)
@@ -96,14 +96,14 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter
val factory = MessageDetailsViewModel.Factory(recipientId, messageId)
viewModel = ViewModelProvider(this, factory)[MessageDetailsViewModel::class.java]
viewModel.messageDetails.observe(this) { details: MessageDetails? ->
viewModel.messageDetails.observe(viewLifecycleOwner) { details: MessageDetails? ->
if (details == null) {
dismissAllowingStateLoss()
requireActivity().onBackPressedDispatcher.onBackPressed()
} else {
adapter.submitList(convertToRows(details))
}
}
viewModel.recipient.observe(this) { recipient: Recipient -> recyclerViewColorizer.setChatColors(recipient.chatColors) }
viewModel.recipient.observe(viewLifecycleOwner) { recipient: Recipient -> recyclerViewColorizer.setChatColors(recipient.chatColors) }
}
private fun initializeVideoPlayer(view: View) {
@@ -413,8 +413,12 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
interface Callback {
fun onMessageDetailsFragmentDismissed()
class Dialog : WrapperDialogFragment() {
override fun getWrappedFragment(): Fragment {
return MessageDetailsFragment().apply {
arguments = this@Dialog.requireArguments()
}
}
}
companion object {
@@ -422,16 +426,17 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter
private const val MESSAGE_ID_EXTRA = "message_id"
private const val RECIPIENT_EXTRA = "recipient_id"
fun create(message: MessageRecord, recipientId: RecipientId): DialogFragment {
val dialogFragment: DialogFragment = MessageDetailsFragment()
val args = Bundle()
fun args(recipientId: RecipientId, messageId: Long): Bundle {
return bundleOf(
MESSAGE_ID_EXTRA to messageId,
RECIPIENT_EXTRA to recipientId
)
}
args.putLong(MESSAGE_ID_EXTRA, message.id)
args.putParcelable(RECIPIENT_EXTRA, recipientId)
dialogFragment.arguments = args
return dialogFragment
fun create(message: MessageRecord, recipientId: RecipientId): Dialog {
return Dialog().apply {
arguments = args(recipientId, message.id)
}
}
}
}