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()
}
}
}