Remove MainListHostFragment and rescope list vms to the activity.

This commit is contained in:
Alex Hart
2025-05-05 11:54:19 -03:00
committed by Michelle Tang
parent bc94a92f68
commit 6d04c8ba42
20 changed files with 293 additions and 423 deletions

View File

@@ -13,6 +13,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.Toast
import androidx.activity.SystemBarStyle
@@ -37,11 +38,11 @@ import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -50,18 +51,24 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.compose.AndroidFragment
import androidx.fragment.compose.rememberFragmentState
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.getSerializableCompat
import org.signal.core.util.logging.Log
import org.signal.donations.StripeApi
import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar.show
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.calls.log.CallLogFragment
import org.thoughtcrime.securesms.calls.new.NewCallActivity
import org.thoughtcrime.securesms.components.ConnectivityWarningBottomSheet
import org.thoughtcrime.securesms.components.DebugLogsPromptDialogFragment
@@ -76,14 +83,16 @@ import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.v2.ConversationFragment
import org.thoughtcrime.securesms.conversation.v2.MotionEventRelay
import org.thoughtcrime.securesms.conversation.v2.ShareDataTimestampViewModel
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment
import org.thoughtcrime.securesms.conversationlist.RelinkDevicesReminderBottomSheetFragment
import org.thoughtcrime.securesms.conversationlist.RestoreCompleteBottomSheetDialog
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceExitActivity
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
import org.thoughtcrime.securesms.main.MainActivityListHostFragment
import org.thoughtcrime.securesms.main.MainBottomChrome
import org.thoughtcrime.securesms.main.MainBottomChromeCallback
import org.thoughtcrime.securesms.main.MainBottomChromeState
@@ -97,7 +106,9 @@ import org.thoughtcrime.securesms.main.MainNavigationViewModel
import org.thoughtcrime.securesms.main.MainToolbar
import org.thoughtcrime.securesms.main.MainToolbarCallback
import org.thoughtcrime.securesms.main.MainToolbarMode
import org.thoughtcrime.securesms.main.MainToolbarState
import org.thoughtcrime.securesms.main.MainToolbarViewModel
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder
import org.thoughtcrime.securesms.main.NavigationBarSpacerCompat
import org.thoughtcrime.securesms.main.SnackbarState
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
@@ -107,25 +118,35 @@ import org.thoughtcrime.securesms.megaphone.MegaphoneActionController
import org.thoughtcrime.securesms.megaphone.Megaphones
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.notifications.VitalsViewModel
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.stories.landing.StoriesLandingFragment
import org.thoughtcrime.securesms.stories.settings.StorySettingsActivity
import org.thoughtcrime.securesms.util.AppForegroundObserver
import org.thoughtcrime.securesms.util.AppStartup
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.CachedInflater
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.SplashScreenUtil
import org.thoughtcrime.securesms.util.TopToastPopup
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.viewModel
import org.thoughtcrime.securesms.window.AppScaffold
import org.thoughtcrime.securesms.window.WindowSizeClass
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner, MainNavigator.NavigatorProvider {
class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner, MainNavigator.NavigatorProvider, Material3OnScrollHelperBinder, ConversationListFragment.Callback, CallLogFragment.Callback {
companion object {
private val TAG = Log.tag(MainActivity::class)
private const val KEY_STARTING_TAB = "STARTING_TAB"
const val RESULT_CONFIG_CHANGED = Activity.RESULT_FIRST_USER + 901
@@ -172,6 +193,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
private val motionEventRelay: MotionEventRelay by viewModels()
private var onFirstRender = false
private var previousTopToastPopup: TopToastPopup? = null
private val mainBottomChromeCallback = BottomChromeCallback()
private val megaphoneActionController = MainMegaphoneActionController()
@@ -202,27 +224,49 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
}
})
UnreadPaymentsLiveData().observe(this) { unread ->
toolbarViewModel.setHasUnreadPayments(unread.isPresent)
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mainNavigationViewModel.navigationEvents.collectLatest {
when (it) {
MainNavigationViewModel.NavigationEvent.STORY_CAMERA_FIRST -> {
mainBottomChromeCallback.onCameraClick(MainNavigationListLocation.STORIES)
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mainNavigationViewModel.navigationEvents.collectLatest {
when (it) {
MainNavigationViewModel.NavigationEvent.STORY_CAMERA_FIRST -> {
mainBottomChromeCallback.onCameraClick(MainNavigationListLocation.STORIES)
}
}
}
}
}
launch {
mainNavigationViewModel.getNotificationProfiles().collectLatest { profiles ->
withContext(Dispatchers.Main) {
updateNotificationProfileStatus(profiles)
}
}
}
}
shareDataTimestampViewModel.setTimestampFromActivityCreation(savedInstanceState, intent)
setContent {
val listHostState = rememberFragmentState()
val snackbar by mainNavigationViewModel.snackbar.collectAsStateWithLifecycle()
val mainToolbarState by toolbarViewModel.state.collectAsStateWithLifecycle()
val megaphone by mainNavigationViewModel.megaphone.collectAsStateWithLifecycle()
val mainNavigationState by mainNavigationViewModel.mainNavigationState.collectAsStateWithLifecycle()
LaunchedEffect(mainNavigationState.selectedDestination) {
when (mainNavigationState.selectedDestination) {
MainNavigationListLocation.CHATS -> toolbarViewModel.presentToolbarForConversationListFragment()
MainNavigationListLocation.ARCHIVE -> toolbarViewModel.presentToolbarForConversationListArchiveFragment()
MainNavigationListLocation.CALLS -> toolbarViewModel.presentToolbarForCallLogFragment()
MainNavigationListLocation.STORIES -> toolbarViewModel.presentToolbarForStoriesLandingFragment()
}
}
val isNavigationVisible = remember(mainToolbarState.mode) {
mainToolbarState.mode == MainToolbarMode.FULL
}
@@ -296,11 +340,40 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
Box(
modifier = Modifier.weight(1f)
) {
AndroidFragment(
clazz = MainActivityListHostFragment::class.java,
fragmentState = listHostState,
modifier = Modifier.fillMaxSize()
)
when (val destination = mainNavigationState.selectedDestination) {
MainNavigationListLocation.CHATS -> {
val state = key(destination) { rememberFragmentState() }
AndroidFragment(
clazz = ConversationListFragment::class.java,
fragmentState = state,
modifier = Modifier.fillMaxSize()
)
}
MainNavigationListLocation.ARCHIVE -> {
val state = key(destination) { rememberFragmentState() }
AndroidFragment(
clazz = ConversationListArchiveFragment::class.java,
fragmentState = state,
modifier = Modifier.fillMaxSize()
)
}
MainNavigationListLocation.CALLS -> {
val state = key(destination) { rememberFragmentState() }
AndroidFragment(
clazz = CallLogFragment::class.java,
fragmentState = state,
modifier = Modifier.fillMaxSize()
)
}
MainNavigationListLocation.STORIES -> {
val state = key(destination) { rememberFragmentState() }
AndroidFragment(
clazz = StoriesLandingFragment::class.java,
fragmentState = state,
modifier = Modifier.fillMaxSize()
)
}
}
MainBottomChrome(
state = mainBottomChromeState,
@@ -433,6 +506,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
when (startingTab) {
MainNavigationListLocation.CHATS -> mainNavigationViewModel.onChatsSelected()
MainNavigationListLocation.ARCHIVE -> mainNavigationViewModel.onArchiveSelected()
MainNavigationListLocation.CALLS -> mainNavigationViewModel.onCallsSelected()
MainNavigationListLocation.STORIES -> {
if (Stories.isFeatureEnabled()) {
@@ -453,6 +527,8 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
super.onResume()
dynamicTheme.onResume(this)
toolbarViewModel.refresh()
if (SignalStore.misc.shouldShowLinkedDevicesReminder) {
SignalStore.misc.shouldShowLinkedDevicesReminder = false
RelinkDevicesReminderBottomSheetFragment.show(supportFragmentManager)
@@ -516,6 +592,56 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
return navigator
}
override fun bindScrollHelper(recyclerView: RecyclerView, lifecycleOwner: LifecycleOwner) {
Material3OnScrollHelper(
activity = this,
views = listOf(),
viewStubs = listOf(),
onSetToolbarColor = {
toolbarViewModel.setToolbarColor(it)
},
setStatusBarColor = {},
lifecycleOwner = lifecycleOwner
).attach(recyclerView)
}
override fun bindScrollHelper(recyclerView: RecyclerView, lifecycleOwner: LifecycleOwner, chatFolders: RecyclerView, setChatFolder: (Int) -> Unit) {
Material3OnScrollHelper(
activity = this,
views = listOf(chatFolders),
viewStubs = listOf(),
setStatusBarColor = {},
onSetToolbarColor = {
toolbarViewModel.setToolbarColor(it)
},
lifecycleOwner = lifecycleOwner,
setChatFolderColor = setChatFolder
).attach(recyclerView)
}
override fun updateProxyStatus(state: WebSocketConnectionState) {
if (SignalStore.proxy.isProxyEnabled) {
val proxyState: MainToolbarState.ProxyState = when (state) {
WebSocketConnectionState.CONNECTING, WebSocketConnectionState.DISCONNECTING, WebSocketConnectionState.DISCONNECTED -> MainToolbarState.ProxyState.CONNECTING
WebSocketConnectionState.CONNECTED -> MainToolbarState.ProxyState.CONNECTED
WebSocketConnectionState.AUTHENTICATION_FAILED, WebSocketConnectionState.FAILED, WebSocketConnectionState.REMOTE_DEPRECATED -> MainToolbarState.ProxyState.FAILED
else -> MainToolbarState.ProxyState.NONE
}
toolbarViewModel.setProxyState(proxyState = proxyState)
} else {
toolbarViewModel.setProxyState(proxyState = MainToolbarState.ProxyState.NONE)
}
}
override fun onMultiSelectStarted() {
toolbarViewModel.presentToolbarForMultiselect()
}
override fun onMultiSelectFinished() {
toolbarViewModel.presentToolbarForCurrentDestination()
}
private fun handleDeepLinkIntent(intent: Intent) {
handleConversationIntent(intent)
handleGroupLinkInIntent(intent)
@@ -578,6 +704,44 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
}
}
private fun updateNotificationProfileStatus(notificationProfiles: List<NotificationProfile>) {
val activeProfile = NotificationProfiles.getActiveProfile(notificationProfiles)
if (activeProfile != null) {
if (activeProfile.id != SignalStore.notificationProfile.lastProfilePopup) {
val view = findViewById<ViewGroup>(android.R.id.content)
view.postDelayed({
try {
var fragmentView = view ?: return@postDelayed
SignalStore.notificationProfile.lastProfilePopup = activeProfile.id
SignalStore.notificationProfile.lastProfilePopupTime = System.currentTimeMillis()
if (previousTopToastPopup?.isShowing == true) {
previousTopToastPopup?.dismiss()
}
val fragment = supportFragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
if (fragment != null && fragment.isAdded && fragment.view != null) {
fragmentView = fragment.requireView() as ViewGroup
}
previousTopToastPopup = TopToastPopup.show(fragmentView, R.drawable.ic_moon_16, getString(R.string.ConversationListFragment__s_on, activeProfile.name))
} catch (e: Exception) {
Log.w(TAG, "Unable to show toast popup", e)
}
}, 500L)
}
toolbarViewModel.setNotificationProfileEnabled(true)
} else {
toolbarViewModel.setNotificationProfileEnabled(false)
}
if (!SignalStore.notificationProfile.hasSeenTooltip && Util.hasItems(notificationProfiles)) {
toolbarViewModel.setShowNotificationProfilesTooltip(true)
}
}
inner class ToolbarCallback : MainToolbarCallback {
override fun onNewGroupClick() {
@@ -744,6 +908,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
MainNavigationListLocation.CHATS -> mainNavigationViewModel.onChatsSelected()
MainNavigationListLocation.CALLS -> mainNavigationViewModel.onCallsSelected()
MainNavigationListLocation.STORIES -> mainNavigationViewModel.onStoriesSelected()
MainNavigationListLocation.ARCHIVE -> mainNavigationViewModel.onArchiveSelected()
}
}
}

View File

@@ -9,13 +9,18 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.map
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.rx3.asFlow
import org.thoughtcrime.securesms.components.AvatarImageView
import org.thoughtcrime.securesms.database.model.ProfileAvatarFileDetails
import org.thoughtcrime.securesms.profiles.AvatarHelper
@@ -36,11 +41,21 @@ fun AvatarImage(
)
} else {
val context = LocalContext.current
val state = recipient.live().liveData.map { AvatarImageState(NameUtil.getAbbreviation(it.getDisplayName(context)), it, AvatarHelper.getAvatarFileDetails(context, it.id)) }.observeAsState().value ?: return
var state: AvatarImageState by remember {
mutableStateOf(AvatarImageState(null, recipient, ProfileAvatarFileDetails.NO_DETAILS))
}
LaunchedEffect(recipient.id) {
Recipient.observable(recipient.id).asFlow()
.collectLatest {
state = AvatarImageState(NameUtil.getAbbreviation(it.getDisplayName(context)), it, AvatarHelper.getAvatarFileDetails(context, it.id))
}
}
AndroidView(
factory = {
AvatarImageView(context).apply {
initialize(context, null)
this.contentDescription = contentDescription
}
},

View File

@@ -34,7 +34,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import androidx.navigation.fragment.navArgs
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.util.BreakIteratorCompat
@@ -45,11 +44,11 @@ class EditCallLinkNameDialogFragment : ComposeDialogFragment() {
companion object {
const val RESULT_KEY = "edit_call_link_name"
private const val MAX_CHARACTER_COUNT = 32
const val ARG_NAME = "name"
}
private val args: EditCallLinkNameDialogFragmentArgs by navArgs()
private val argName: String
get() = requireArguments().getString(ARG_NAME)!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -69,8 +68,8 @@ class EditCallLinkNameDialogFragment : ComposeDialogFragment() {
var callName by remember {
mutableStateOf(
TextFieldValue(
text = args.name,
selection = TextRange(args.name.length)
text = argName,
selection = TextRange(argName.length)
)
)
}

View File

@@ -33,9 +33,9 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.app.ShareCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
@@ -127,9 +127,9 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
private fun onAddACallNameClicked() {
val snapshot = viewModel.callLink.value
findNavController().navigate(
CreateCallLinkBottomSheetDialogFragmentDirections.actionCreateCallLinkBottomSheetToEditCallLinkNameDialogFragment(snapshot.state.name)
)
EditCallLinkNameDialogFragment().apply {
arguments = bundleOf(EditCallLinkNameDialogFragment.ARG_NAME to snapshot.state.name)
}.show(parentFragmentManager, null)
}
private fun onJoinClicked() {

View File

@@ -13,7 +13,6 @@ import androidx.compose.material3.SnackbarDuration
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -26,6 +25,7 @@ import org.signal.core.util.concurrent.addTo
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.MainNavigator
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.calls.links.create.CreateCallLinkBottomSheetDialogFragment
import org.thoughtcrime.securesms.calls.links.details.CallLinkDetailsActivity
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
@@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.main.MainToolbarViewModel
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder
import org.thoughtcrime.securesms.main.SnackbarState
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.doAfterNextLayout
@@ -293,7 +294,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
}
override fun onCreateACallLinkClicked() {
findNavController().navigate(R.id.createCallLinkBottomSheet)
CreateCallLinkBottomSheetDialogFragment().show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
override fun onCallClicked(callLogRow: CallLogRow.Call) {

View File

@@ -9,12 +9,12 @@ import android.content.ActivityNotFoundException
import android.content.Intent
import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.os.bundleOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.BaseActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.calls.links.CallLinks
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment
import org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragmentArgs
import org.thoughtcrime.securesms.components.webrtc.controls.CallInfoView
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel
import org.thoughtcrime.securesms.dependencies.AppDependencies
@@ -44,7 +44,7 @@ class CallInfoCallbacks(
override fun onEditNameClicked(name: String) {
EditCallLinkNameDialogFragment().apply {
arguments = EditCallLinkNameDialogFragmentArgs.Builder(name).build().toBundle()
arguments = bundleOf(EditCallLinkNameDialogFragment.ARG_NAME to name)
}.show(activity.supportFragmentManager, null)
}

View File

@@ -937,6 +937,7 @@ class ConversationFragment :
firstRender = false
binding.conversationItemRecycler.doAfterNextLayout {
SignalLocalMetrics.ConversationOpen.onRenderFinished()
(requireActivity() as? MainActivity)?.onFirstRender()
doAfterFirstRender()
}
}

View File

@@ -32,7 +32,7 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.SignalPreview
@@ -59,7 +59,7 @@ class AddToFolderBottomSheet private constructor(private val onDismissListener:
OTHER(3)
}
private val viewModel: ConversationListViewModel by viewModels(
private val viewModel: ConversationListViewModel.UnarchivedConversationListViewModel by activityViewModels(
factoryProducer = {
ConversationListViewModel.Factory(isArchived = false)
}

View File

@@ -72,6 +72,8 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
this.onConversationClickListener = onConversationClickListener;
this.onClearFilterClicked = onClearFilterClicked;
this.onFolderSettingsClicked = onFolderSettingsClicked;
setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
}
@Override

View File

@@ -53,7 +53,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -643,8 +642,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
@Override
public void onShowArchiveClick() {
if (viewModel.currentSelectedConversations().isEmpty()) {
NavHostFragment.findNavController(this)
.navigate(ConversationListFragmentDirections.actionConversationListFragmentToConversationListArchiveFragment());
mainNavigationViewModel.goTo(MainNavigationListLocation.ARCHIVE);
}
}
@@ -707,7 +705,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
} else if (event instanceof MainToolbarViewModel.Event.Chats.ClearFilter) {
onClearFilterClick();
} else if (event instanceof MainToolbarViewModel.Event.Chats.CloseArchive) {
NavHostFragment.findNavController(this).popBackStack(R.id.conversationListFragment, false);
mainNavigationViewModel.goTo(MainNavigationListLocation.CHATS);
}
})
);
@@ -855,7 +853,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
private void initializeViewModel() {
viewModel = new ViewModelProvider(this, new ConversationListViewModel.Factory(isArchived())).get(ConversationListViewModel.class);
Class<? extends ConversationListViewModel> viewModelClass = isArchived() ? ConversationListViewModel.ArchivedConversationListViewModel.class : ConversationListViewModel.UnarchivedConversationListViewModel.class;
viewModel = new ViewModelProvider(requireActivity(), new ConversationListViewModel.Factory(isArchived())).get(viewModelClass);
lifecycleDisposable.add(viewModel.getConversationsState().subscribe(this::onConversationListChanged));
lifecycleDisposable.add(viewModel.getHasNoConversations().subscribe(this::updateEmptyState));
@@ -1389,7 +1388,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
protected Callback requireCallback() {
return ((Callback) getParentFragment().getParentFragment());
return ((Callback) requireActivity());
}
protected @PluralsRes int getArchivedSnackbarTitleRes() {

View File

@@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.rx.RxStore
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import java.util.concurrent.TimeUnit
class ConversationListViewModel(
sealed class ConversationListViewModel(
private val isArchived: Boolean,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
@@ -317,13 +317,20 @@ class ConversationListViewModel(
val pinnedCount: Int = 0
)
class UnarchivedConversationListViewModel(savedStateHandle: SavedStateHandle) : ConversationListViewModel(isArchived = false, savedStateHandle = savedStateHandle)
class ArchivedConversationListViewModel(savedStateHandle: SavedStateHandle) : ConversationListViewModel(isArchived = true, savedStateHandle = savedStateHandle)
class Factory(
private val isArchived: Boolean
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val savedStateHandle = extras.createSavedStateHandle()
return modelClass.cast(ConversationListViewModel(isArchived, savedStateHandle))!!
return if (isArchived) {
ArchivedConversationListViewModel(savedStateHandle) as T
} else {
UnarchivedConversationListViewModel(savedStateHandle) as T
}
}
}
}

View File

@@ -1,282 +0,0 @@
package org.thoughtcrime.securesms.main
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.calls.log.CallLogFragment
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment
import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfiles
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
import org.thoughtcrime.securesms.util.TopToastPopup
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_fragment), ConversationListFragment.Callback, Material3OnScrollHelperBinder, CallLogFragment.Callback {
companion object {
private val TAG = Log.tag(MainActivityListHostFragment::class.java)
}
private val disposables: LifecycleDisposable = LifecycleDisposable()
private var previousTopToastPopup: TopToastPopup? = null
private val destinationChangedListener = DestinationChangedListener()
private val toolbarViewModel: MainToolbarViewModel by activityViewModels()
private val mainNavigationViewModel: MainNavigationViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
UnreadPaymentsLiveData().observe(viewLifecycleOwner) { unread ->
toolbarViewModel.setHasUnreadPayments(unread.isPresent)
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
launch {
mainNavigationViewModel.mainNavigationState.collectLatest { state ->
withContext(Dispatchers.Main) {
val controller: NavController = getChildNavController()
when (controller.currentDestination?.id) {
R.id.conversationListFragment -> goToStateFromConversationList(state, controller)
R.id.conversationListArchiveFragment -> Unit
R.id.storiesLandingFragment -> goToStateFromStories(state, controller)
R.id.callLogFragment -> goToStateFromCalling(state, controller)
}
}
}
}
launch {
mainNavigationViewModel.getNotificationProfiles().collectLatest { profiles ->
withContext(Dispatchers.Main) {
updateNotificationProfileStatus(profiles)
}
}
}
}
}
}
private fun getChildNavController(): NavController {
return requireView().findViewById<View>(R.id.fragment_container).findNavController()
}
private fun goToStateFromConversationList(state: MainNavigationState, navController: NavController) {
if (state.selectedDestination == MainNavigationListLocation.CHATS) {
return
} else {
val destination = if (state.selectedDestination == MainNavigationListLocation.STORIES) {
R.id.action_conversationListFragment_to_storiesLandingFragment
} else {
R.id.action_conversationListFragment_to_callLogFragment
}
navController.navigate(
destination,
null,
null
)
}
}
private fun goToStateFromCalling(state: MainNavigationState, navController: NavController) {
when (state.selectedDestination) {
MainNavigationListLocation.CALLS -> return
MainNavigationListLocation.CHATS -> navController.popBackStack(R.id.conversationListFragment, false)
MainNavigationListLocation.STORIES -> navController.navigate(R.id.action_callLogFragment_to_storiesLandingFragment)
}
}
private fun goToStateFromStories(state: MainNavigationState, navController: NavController) {
when (state.selectedDestination) {
MainNavigationListLocation.STORIES -> return
MainNavigationListLocation.CHATS -> navController.popBackStack(R.id.conversationListFragment, false)
MainNavigationListLocation.CALLS -> navController.navigate(R.id.action_storiesLandingFragment_to_callLogFragment)
}
}
override fun onResume() {
super.onResume()
toolbarViewModel.refresh()
requireView()
.findViewById<View>(R.id.fragment_container)
.findNavController()
.addOnDestinationChangedListener(destinationChangedListener)
if (toolbarViewModel.state.value.mode == MainToolbarMode.ACTION_MODE) {
presentToolbarForMultiselect()
}
}
override fun onPause() {
super.onPause()
requireView()
.findViewById<View>(R.id.fragment_container)
.findNavController()
.removeOnDestinationChangedListener(destinationChangedListener)
}
private fun presentToolbarForConversationListFragment() {
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CHATS, overwriteSearchMode = false)
}
private fun presentToolbarForConversationListArchiveFragment() {
toolbarViewModel.setToolbarMode(MainToolbarMode.BASIC, destination = MainNavigationListLocation.CHATS)
}
private fun presentToolbarForStoriesLandingFragment() {
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.STORIES)
}
private fun presentToolbarForCallLogFragment() {
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CALLS)
}
private fun presentToolbarForMultiselect() {
toolbarViewModel.setToolbarMode(MainToolbarMode.ACTION_MODE)
}
override fun onDestroyView() {
previousTopToastPopup = null
super.onDestroyView()
}
override fun onMultiSelectStarted() {
presentToolbarForMultiselect()
}
override fun onMultiSelectFinished() {
val currentDestination: NavDestination? = requireView().findViewById<View>(R.id.fragment_container).findNavController().currentDestination
if (currentDestination != null) {
presentToolbarForDestination(currentDestination)
}
}
override fun updateProxyStatus(state: WebSocketConnectionState) {
if (SignalStore.proxy.isProxyEnabled) {
val proxyState: MainToolbarState.ProxyState = when (state) {
WebSocketConnectionState.CONNECTING, WebSocketConnectionState.DISCONNECTING, WebSocketConnectionState.DISCONNECTED -> MainToolbarState.ProxyState.CONNECTING
WebSocketConnectionState.CONNECTED -> MainToolbarState.ProxyState.CONNECTED
WebSocketConnectionState.AUTHENTICATION_FAILED, WebSocketConnectionState.FAILED, WebSocketConnectionState.REMOTE_DEPRECATED -> MainToolbarState.ProxyState.FAILED
else -> MainToolbarState.ProxyState.NONE
}
toolbarViewModel.setProxyState(proxyState = proxyState)
} else {
toolbarViewModel.setProxyState(proxyState = MainToolbarState.ProxyState.NONE)
}
}
private fun updateNotificationProfileStatus(notificationProfiles: List<NotificationProfile>) {
val activeProfile = NotificationProfiles.getActiveProfile(notificationProfiles)
if (activeProfile != null) {
if (activeProfile.id != SignalStore.notificationProfile.lastProfilePopup) {
view?.postDelayed({
try {
var fragmentView = view as? ViewGroup ?: return@postDelayed
SignalStore.notificationProfile.lastProfilePopup = activeProfile.id
SignalStore.notificationProfile.lastProfilePopupTime = System.currentTimeMillis()
if (previousTopToastPopup?.isShowing == true) {
previousTopToastPopup?.dismiss()
}
val fragment = parentFragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
if (fragment != null && fragment.isAdded && fragment.view != null) {
fragmentView = fragment.requireView() as ViewGroup
}
previousTopToastPopup = TopToastPopup.show(fragmentView, R.drawable.ic_moon_16, getString(R.string.ConversationListFragment__s_on, activeProfile.name))
} catch (e: Exception) {
Log.w(TAG, "Unable to show toast popup", e)
}
}, 500L)
}
toolbarViewModel.setNotificationProfileEnabled(true)
} else {
toolbarViewModel.setNotificationProfileEnabled(false)
}
if (!SignalStore.notificationProfile.hasSeenTooltip && Util.hasItems(notificationProfiles)) {
toolbarViewModel.setShowNotificationProfilesTooltip(true)
}
}
private fun presentToolbarForDestination(destination: NavDestination) {
when (destination.id) {
R.id.conversationListFragment -> {
presentToolbarForConversationListFragment()
}
R.id.conversationListArchiveFragment -> {
presentToolbarForConversationListArchiveFragment()
}
R.id.storiesLandingFragment -> {
presentToolbarForStoriesLandingFragment()
}
R.id.callLogFragment -> {
presentToolbarForCallLogFragment()
}
}
}
private inner class DestinationChangedListener : NavController.OnDestinationChangedListener {
override fun onDestinationChanged(controller: NavController, destination: NavDestination, arguments: Bundle?) {
presentToolbarForDestination(destination)
}
}
override fun bindScrollHelper(recyclerView: RecyclerView, lifecycleOwner: LifecycleOwner) {
Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(),
viewStubs = listOf(),
onSetToolbarColor = {
toolbarViewModel.setToolbarColor(it)
},
setStatusBarColor = {},
lifecycleOwner = lifecycleOwner
).attach(recyclerView)
}
override fun bindScrollHelper(recyclerView: RecyclerView, lifecycleOwner: LifecycleOwner, chatFolders: RecyclerView, setChatFolder: (Int) -> Unit) {
Material3OnScrollHelper(
activity = requireActivity(),
views = listOf(chatFolders),
viewStubs = listOf(),
setStatusBarColor = {},
onSetToolbarColor = {
toolbarViewModel.setToolbarColor(it)
},
lifecycleOwner = lifecycleOwner,
setChatFolderColor = setChatFolder
).attach(recyclerView)
}
}

View File

@@ -170,6 +170,7 @@ private fun PrimaryActionButton(
) {
val onClick = remember(destination) {
when (destination) {
MainNavigationListLocation.ARCHIVE -> error("Not supported")
MainNavigationListLocation.CHATS -> onNewChatClick
MainNavigationListLocation.CALLS -> onNewCallClick
MainNavigationListLocation.STORIES -> {
@@ -184,6 +185,7 @@ private fun PrimaryActionButton(
icon = {
AnimatedContent(destination) { targetState ->
val (icon, contentDescriptionId) = when (targetState) {
MainNavigationListLocation.ARCHIVE -> error("Not supported")
MainNavigationListLocation.CHATS -> R.drawable.symbol_edit_24 to R.string.conversation_list_fragment__fab_content_description
MainNavigationListLocation.CALLS -> R.drawable.symbol_phone_plus_24 to R.string.CallLogFragment__start_a_new_call
MainNavigationListLocation.STORIES -> R.drawable.symbol_camera_24 to R.string.conversation_list_fragment__open_camera_description

View File

@@ -68,6 +68,10 @@ enum class MainNavigationListLocation(
label = R.string.ConversationListTabs__chats,
icon = R.raw.chats_28
),
ARCHIVE(
label = R.string.ConversationListTabs__chats,
icon = R.raw.chats_28
),
CALLS(
label = R.string.ConversationListTabs__calls,
icon = R.raw.calls_28
@@ -104,15 +108,16 @@ fun MainNavigationBar(
) {
val entries = remember(state.isStoriesFeatureEnabled) {
if (state.isStoriesFeatureEnabled) {
MainNavigationListLocation.entries
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.ARCHIVE }
} else {
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.STORIES }
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.STORIES || it == MainNavigationListLocation.ARCHIVE }
}
}
entries.forEach { destination ->
val badgeCount = when (destination) {
MainNavigationListLocation.ARCHIVE -> error("Not supported")
MainNavigationListLocation.CHATS -> state.chatsCount
MainNavigationListLocation.CALLS -> state.callsCount
MainNavigationListLocation.STORIES -> state.storiesCount
@@ -219,9 +224,9 @@ fun MainNavigationRail(
) {
val entries = remember(state.isStoriesFeatureEnabled) {
if (state.isStoriesFeatureEnabled) {
MainNavigationListLocation.entries
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.ARCHIVE }
} else {
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.STORIES }
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.STORIES || it == MainNavigationListLocation.ARCHIVE }
}
}
@@ -262,6 +267,7 @@ private fun BoxScope.NavigationRailCountIndicator(
) {
val count = remember(state, destination) {
when (destination) {
MainNavigationListLocation.ARCHIVE -> error("Not supported")
MainNavigationListLocation.CHATS -> state.chatsCount
MainNavigationListLocation.CALLS -> state.callsCount
MainNavigationListLocation.STORIES -> state.storiesCount

View File

@@ -95,6 +95,15 @@ class MainNavigationViewModel(
val wrapped = LegacyNavigator(composeScope, threePaneScaffoldNavigator, goToLegacyDetailLocation)
this.navigator = wrapped
if (previous != null) {
val destination = previous.currentDestination?.contentKey ?: return wrapped
if (destination is MainNavigationListLocation) {
goTo(destination)
}
} else {
goTo(mainNavigationState.value.selectedDestination)
}
if (previous != null) {
val destination = previous.currentDestination?.contentKey ?: return wrapped
if (destination is MainNavigationDetailLocation) {
@@ -123,6 +132,12 @@ class MainNavigationViewModel(
}
}
fun goTo(location: MainNavigationListLocation) {
internalMainNavigationState.update {
it.copy(selectedDestination = location)
}
}
fun goToCameraFirstStoryCapture() {
viewModelScope.launch {
internalNavigationEvents.emit(NavigationEvent.STORY_CAMERA_FIRST)
@@ -168,6 +183,13 @@ class MainNavigationViewModel(
}
}
fun onArchiveSelected() {
internalTabClickEvents.tryEmit(MainNavigationListLocation.ARCHIVE)
internalMainNavigationState.update {
it.copy(selectedDestination = MainNavigationListLocation.ARCHIVE)
}
}
fun onCallsSelected() {
internalTabClickEvents.tryEmit(MainNavigationListLocation.CALLS)
internalMainNavigationState.update {

View File

@@ -395,6 +395,7 @@ private fun PrimaryToolbar(
controller = controller
) {
when (state.destination) {
MainNavigationListLocation.ARCHIVE -> Unit
MainNavigationListLocation.CHATS -> ChatDropdownItems(state, callback, dismiss)
MainNavigationListLocation.CALLS -> CallDropdownItems(state.callFilter, callback, dismiss)
MainNavigationListLocation.STORIES -> StoryDropDownItems(callback, dismiss)

View File

@@ -73,6 +73,33 @@ class MainToolbarViewModel : ViewModel() {
}
}
fun presentToolbarForConversationListFragment() {
setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CHATS, overwriteSearchMode = false)
}
fun presentToolbarForConversationListArchiveFragment() {
setToolbarMode(MainToolbarMode.BASIC, destination = MainNavigationListLocation.CHATS)
}
fun presentToolbarForStoriesLandingFragment() {
setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.STORIES)
}
fun presentToolbarForCallLogFragment() {
setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CALLS)
}
fun presentToolbarForMultiselect() {
setToolbarMode(MainToolbarMode.ACTION_MODE)
}
fun presentToolbarForCurrentDestination() {
when (state.value.destination) {
MainNavigationListLocation.ARCHIVE -> setToolbarMode(MainToolbarMode.BASIC)
else -> setToolbarMode(MainToolbarMode.FULL)
}
}
@JvmOverloads
fun setToolbarMode(
mode: MainToolbarMode,

View File

@@ -9,7 +9,6 @@ import androidx.compose.ui.platform.ComposeView
import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
@@ -66,7 +65,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
private val lifecycleDisposable = LifecycleDisposable()
private val viewModel: StoriesLandingViewModel by viewModels(
private val viewModel: StoriesLandingViewModel by activityViewModels(
factoryProducer = {
StoriesLandingViewModel.Factory(StoriesLandingRepository(requireContext()))
}

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:viewBindingIgnore="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:navGraph="@navigation/main_activity_list" />
</LinearLayout>

View File

@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_activity_list"
app:startDestination="@id/conversationListFragment">
<fragment
android:id="@+id/conversationListFragment"
android:name="org.thoughtcrime.securesms.conversationlist.ConversationListFragment"
android:label="conversation_list_fragment">
<action
android:id="@+id/action_conversationListFragment_to_conversationListArchiveFragment"
app:destination="@id/conversationListArchiveFragment"
app:enterAnim="@anim/slide_from_end"
app:exitAnim="@anim/slide_to_start"
app:popEnterAnim="@anim/slide_from_start"
app:popExitAnim="@anim/slide_to_end" />
<action
android:id="@+id/action_conversationListFragment_to_storiesLandingFragment"
app:destination="@id/storiesLandingFragment" />
<action
android:id="@+id/action_conversationListFragment_to_callLogFragment"
app:destination="@id/callLogFragment" />
</fragment>
<fragment
android:id="@+id/conversationListArchiveFragment"
android:name="org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment"
android:label="conversation_list_archive_fragment" />
<fragment
android:id="@+id/storiesLandingFragment"
android:name="org.thoughtcrime.securesms.stories.landing.StoriesLandingFragment"
android:label="stories_landing_fragment">
<action
android:id="@+id/action_storiesLandingFragment_to_callLogFragment"
app:destination="@id/callLogFragment" />
</fragment>
<fragment
android:id="@+id/callLogFragment"
android:name="org.thoughtcrime.securesms.calls.log.CallLogFragment"
android:label="call_log_fragment">
<action
android:id="@+id/action_callLogFragment_to_storiesLandingFragment"
app:destination="@id/storiesLandingFragment" />
<action
android:id="@+id/action_callLogFragment_to_createCallLinkBottomSheet"
app:destination="@id/createCallLinkBottomSheet" />
</fragment>
<dialog
android:id="@+id/createCallLinkBottomSheet"
android:name="org.thoughtcrime.securesms.calls.links.create.CreateCallLinkBottomSheetDialogFragment"
android:label="create_call_link_bottom_sheet">
<action
android:id="@+id/action_createCallLinkBottomSheet_to_editCallLinkNameDialogFragment"
app:destination="@id/editCallLinkNameDialogFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_close_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</dialog>
<dialog
android:id="@+id/editCallLinkNameDialogFragment"
android:name="org.thoughtcrime.securesms.calls.links.EditCallLinkNameDialogFragment"
android:label="edit_call_link_name_fragment">
<argument
android:name="name"
app:argType="string"
app:nullable="false" />
</dialog>
</navigation>