mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Remove MainListHostFragment and rescope list vms to the activity.
This commit is contained in:
@@ -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,7 +224,12 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
})
|
||||
|
||||
UnreadPaymentsLiveData().observe(this) { unread ->
|
||||
toolbarViewModel.setHasUnreadPayments(unread.isPresent)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mainNavigationViewModel.navigationEvents.collectLatest {
|
||||
when (it) {
|
||||
@@ -214,15 +241,32 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
) {
|
||||
when (val destination = mainNavigationState.selectedDestination) {
|
||||
MainNavigationListLocation.CHATS -> {
|
||||
val state = key(destination) { rememberFragmentState() }
|
||||
AndroidFragment(
|
||||
clazz = MainActivityListHostFragment::class.java,
|
||||
fragmentState = listHostState,
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -937,6 +937,7 @@ class ConversationFragment :
|
||||
firstRender = false
|
||||
binding.conversationItemRecycler.doAfterNextLayout {
|
||||
SignalLocalMetrics.ConversationOpen.onRenderFinished()
|
||||
(requireActivity() as? MainActivity)?.onFirstRender()
|
||||
doAfterFirstRender()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user