mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Remove ConversationTabs* and migrate to MainActivity.
This commit is contained in:
committed by
Cody Henthorne
parent
462fcdce16
commit
54191433e0
@@ -84,8 +84,10 @@ import org.thoughtcrime.securesms.main.MainBottomChrome
|
||||
import org.thoughtcrime.securesms.main.MainBottomChromeCallback
|
||||
import org.thoughtcrime.securesms.main.MainBottomChromeState
|
||||
import org.thoughtcrime.securesms.main.MainMegaphoneState
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.main.MainNavigationBar
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
|
||||
import org.thoughtcrime.securesms.main.MainNavigationListLocation
|
||||
import org.thoughtcrime.securesms.main.MainNavigationRail
|
||||
import org.thoughtcrime.securesms.main.MainNavigationViewModel
|
||||
import org.thoughtcrime.securesms.main.MainToolbar
|
||||
import org.thoughtcrime.securesms.main.MainToolbarCallback
|
||||
@@ -104,9 +106,6 @@ import org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.stories.settings.StorySettingsActivity
|
||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository
|
||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsFragment
|
||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver
|
||||
import org.thoughtcrime.securesms.util.AppStartup
|
||||
import org.thoughtcrime.securesms.util.CachedInflater
|
||||
@@ -131,23 +130,23 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun clearTopAndOpenTab(context: Context, startingTab: MainNavigationDestination): Intent {
|
||||
fun clearTopAndOpenTab(context: Context, startingTab: MainNavigationListLocation): Intent {
|
||||
return clearTop(context).putExtra(KEY_STARTING_TAB, startingTab)
|
||||
}
|
||||
}
|
||||
|
||||
private val dynamicTheme = DynamicNoActionBarTheme()
|
||||
private val navigator = MainNavigator(this)
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
private lateinit var mediaController: VoiceNoteMediaController
|
||||
private lateinit var navigator: MainNavigator
|
||||
|
||||
override val voiceNoteMediaController: VoiceNoteMediaController
|
||||
get() = mediaController
|
||||
|
||||
private val conversationListTabsViewModel: ConversationListTabsViewModel by viewModel {
|
||||
val startingTab = intent.extras?.getSerializableCompat(KEY_STARTING_TAB, MainNavigationDestination::class.java)
|
||||
ConversationListTabsViewModel(startingTab ?: MainNavigationDestination.CHATS, ConversationListTabRepository())
|
||||
private val mainNavigationViewModel: MainNavigationViewModel by viewModel {
|
||||
val startingTab = intent.extras?.getSerializableCompat(KEY_STARTING_TAB, MainNavigationListLocation::class.java)
|
||||
MainNavigationViewModel(startingTab ?: MainNavigationListLocation.CHATS)
|
||||
}
|
||||
|
||||
private val vitalsViewModel: VitalsViewModel by viewModel {
|
||||
@@ -169,6 +168,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
|
||||
private val mainBottomChromeCallback = BottomChromeCallback()
|
||||
private val megaphoneActionController = MainMegaphoneActionController()
|
||||
private val mainNavigationCallback = MainNavigationCallback()
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
return motionEventRelay.offer(ev) || super.dispatchTouchEvent(ev)
|
||||
@@ -186,20 +186,21 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
)
|
||||
|
||||
conversationListTabsViewModel
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
navigator = MainNavigator(this, mainNavigationViewModel)
|
||||
|
||||
AppForegroundObserver.addListener(object : AppForegroundObserver.Listener {
|
||||
override fun onForeground() {
|
||||
navigator.viewModel.getNextMegaphone()
|
||||
mainNavigationViewModel.getNextMegaphone()
|
||||
}
|
||||
})
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
navigator.viewModel.navigationEvents.collectLatest {
|
||||
mainNavigationViewModel.navigationEvents.collectLatest {
|
||||
when (it) {
|
||||
MainNavigationViewModel.NavigationEvent.STORY_CAMERA_FIRST -> {
|
||||
mainBottomChromeCallback.onCameraClick(MainNavigationDestination.STORIES)
|
||||
mainBottomChromeCallback.onCameraClick(MainNavigationListLocation.STORIES)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,12 +208,16 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
setContent {
|
||||
val navState = rememberFragmentState()
|
||||
val listHostState = rememberFragmentState()
|
||||
val detailLocation by navigator.viewModel.detailLocation.collectAsStateWithLifecycle(MainNavigationDetailLocation.Empty)
|
||||
val snackbar by navigator.viewModel.snackbar.collectAsStateWithLifecycle()
|
||||
val detailLocation by mainNavigationViewModel.detailLocation.collectAsStateWithLifecycle(MainNavigationDetailLocation.Empty)
|
||||
val snackbar by mainNavigationViewModel.snackbar.collectAsStateWithLifecycle()
|
||||
val mainToolbarState by toolbarViewModel.state.collectAsStateWithLifecycle()
|
||||
val megaphone by navigator.viewModel.megaphone.collectAsStateWithLifecycle()
|
||||
val megaphone by mainNavigationViewModel.megaphone.collectAsStateWithLifecycle()
|
||||
val mainNavigationState by mainNavigationViewModel.mainNavigationState.collectAsStateWithLifecycle()
|
||||
|
||||
val isNavigationVisible = remember(mainToolbarState.mode) {
|
||||
mainToolbarState.mode == MainToolbarMode.FULL
|
||||
}
|
||||
|
||||
val mainBottomChromeState = remember(mainToolbarState.destination, snackbar, mainToolbarState.mode, megaphone) {
|
||||
MainBottomChromeState(
|
||||
@@ -251,16 +256,20 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
AppScaffold(
|
||||
navigator = scaffoldNavigator,
|
||||
bottomNavContent = {
|
||||
AndroidFragment(
|
||||
clazz = ConversationListTabsFragment::class.java,
|
||||
fragmentState = navState
|
||||
)
|
||||
if (isNavigationVisible) {
|
||||
MainNavigationBar(
|
||||
state = mainNavigationState,
|
||||
onDestinationSelected = mainNavigationCallback
|
||||
)
|
||||
}
|
||||
},
|
||||
navRailContent = {
|
||||
AndroidFragment(
|
||||
clazz = ConversationListTabsFragment::class.java,
|
||||
fragmentState = navState
|
||||
)
|
||||
if (isNavigationVisible) {
|
||||
MainNavigationRail(
|
||||
state = mainNavigationState,
|
||||
onDestinationSelected = mainNavigationCallback
|
||||
)
|
||||
}
|
||||
},
|
||||
listContent = {
|
||||
val listContainerColor = if (windowSizeClass.isMedium()) {
|
||||
@@ -376,14 +385,14 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
handleDeepLinkIntent(intent)
|
||||
|
||||
val extras = intent.extras ?: return
|
||||
val startingTab = extras.getSerializableCompat(KEY_STARTING_TAB, MainNavigationDestination::class.java)
|
||||
val startingTab = extras.getSerializableCompat(KEY_STARTING_TAB, MainNavigationListLocation::class.java)
|
||||
|
||||
when (startingTab) {
|
||||
MainNavigationDestination.CHATS -> conversationListTabsViewModel.onChatsSelected()
|
||||
MainNavigationDestination.CALLS -> conversationListTabsViewModel.onCallsSelected()
|
||||
MainNavigationDestination.STORIES -> {
|
||||
MainNavigationListLocation.CHATS -> mainNavigationViewModel.onChatsSelected()
|
||||
MainNavigationListLocation.CALLS -> mainNavigationViewModel.onCallsSelected()
|
||||
MainNavigationListLocation.STORIES -> {
|
||||
if (Stories.isFeatureEnabled()) {
|
||||
conversationListTabsViewModel.onStoriesSelected()
|
||||
mainNavigationViewModel.onStoriesSelected()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,6 +431,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
vitalsViewModel.checkSlowNotificationHeuristics()
|
||||
mainNavigationViewModel.refreshNavigationBarState()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@@ -440,13 +450,13 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
if (resultCode == RESULT_OK && requestCode == CreateSvrPinActivity.REQUEST_NEW_PIN) {
|
||||
getNavigator().getViewModel().setSnackbar(SnackbarState(message = getString(R.string.ConfirmKbsPinFragment__pin_created)))
|
||||
getNavigator().getViewModel().onMegaphoneCompleted(Megaphones.Event.PINS_FOR_ALL)
|
||||
mainNavigationViewModel.setSnackbar(SnackbarState(message = getString(R.string.ConfirmKbsPinFragment__pin_created)))
|
||||
mainNavigationViewModel.onMegaphoneCompleted(Megaphones.Event.PINS_FOR_ALL)
|
||||
}
|
||||
|
||||
if (resultCode == RESULT_OK && requestCode == UsernameEditFragment.REQUEST_CODE) {
|
||||
val snackbarString = getString(R.string.ConversationListFragment_username_recovered_toast, SignalStore.account.username)
|
||||
getNavigator().getViewModel().setSnackbar(
|
||||
mainNavigationViewModel.setSnackbar(
|
||||
SnackbarState(
|
||||
message = snackbarString
|
||||
)
|
||||
@@ -605,12 +615,12 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
startActivity(NewCallActivity.createIntent(this@MainActivity))
|
||||
}
|
||||
|
||||
override fun onCameraClick(destination: MainNavigationDestination) {
|
||||
override fun onCameraClick(destination: MainNavigationListLocation) {
|
||||
val onGranted = {
|
||||
startActivity(
|
||||
MediaSelectionActivity.camera(
|
||||
context = this@MainActivity,
|
||||
isStory = destination == MainNavigationDestination.STORIES
|
||||
isStory = destination == MainNavigationListLocation.STORIES
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -636,11 +646,11 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
override fun onMegaphoneVisible(megaphone: Megaphone) {
|
||||
navigator.viewModel.onMegaphoneVisible(megaphone)
|
||||
mainNavigationViewModel.onMegaphoneVisible(megaphone)
|
||||
}
|
||||
|
||||
override fun onSnackbarDismissed() {
|
||||
navigator.viewModel.setSnackbar(null)
|
||||
mainNavigationViewModel.setSnackbar(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,7 +664,7 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
override fun onMegaphoneToastRequested(string: String) {
|
||||
getNavigator().viewModel.setSnackbar(
|
||||
mainNavigationViewModel.setSnackbar(
|
||||
SnackbarState(
|
||||
message = string
|
||||
)
|
||||
@@ -666,15 +676,25 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
|
||||
override fun onMegaphoneSnooze(event: Megaphones.Event) {
|
||||
getNavigator().viewModel.onMegaphoneSnoozed(event)
|
||||
mainNavigationViewModel.onMegaphoneSnoozed(event)
|
||||
}
|
||||
|
||||
override fun onMegaphoneCompleted(event: Megaphones.Event) {
|
||||
getNavigator().viewModel.onMegaphoneCompleted(event)
|
||||
mainNavigationViewModel.onMegaphoneCompleted(event)
|
||||
}
|
||||
|
||||
override fun onMegaphoneDialogFragmentRequested(dialogFragment: DialogFragment) {
|
||||
dialogFragment.show(supportFragmentManager, "megaphone_dialog")
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MainNavigationCallback : (MainNavigationListLocation) -> Unit {
|
||||
override fun invoke(location: MainNavigationListLocation) {
|
||||
when (location) {
|
||||
MainNavigationListLocation.CHATS -> mainNavigationViewModel.onChatsSelected()
|
||||
MainNavigationListLocation.CALLS -> mainNavigationViewModel.onCallsSelected()
|
||||
MainNavigationListLocation.STORIES -> mainNavigationViewModel.onStoriesSelected()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ package org.thoughtcrime.securesms;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
|
||||
@@ -25,25 +23,16 @@ public class MainNavigator {
|
||||
|
||||
private final AppCompatActivity activity;
|
||||
private final LifecycleDisposable lifecycleDisposable;
|
||||
private final MainNavigationViewModel viewModel;
|
||||
|
||||
private MainNavigationViewModel viewModel;
|
||||
|
||||
public MainNavigator(@NonNull AppCompatActivity activity) {
|
||||
public MainNavigator(@NonNull AppCompatActivity activity, @NonNull MainNavigationViewModel viewModel) {
|
||||
this.activity = activity;
|
||||
this.lifecycleDisposable = new LifecycleDisposable();
|
||||
this.viewModel = viewModel;
|
||||
|
||||
lifecycleDisposable.bindTo(activity);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public @NonNull MainNavigationViewModel getViewModel() {
|
||||
if (viewModel == null) {
|
||||
viewModel = new ViewModelProvider(activity).get(MainNavigationViewModel.class);
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
public static MainNavigator get(@NonNull Activity activity) {
|
||||
if (!(activity instanceof MainActivity)) {
|
||||
throw new IllegalArgumentException("Activity must be an instance of MainActivity!");
|
||||
|
||||
@@ -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.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
@@ -44,14 +43,13 @@ import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterLerp
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterPullState
|
||||
import org.thoughtcrime.securesms.databinding.CallLogFragmentBinding
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.main.MainNavigationListLocation
|
||||
import org.thoughtcrime.securesms.main.MainNavigationViewModel
|
||||
import org.thoughtcrime.securesms.main.MainToolbarMode
|
||||
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.stories.tabs.ConversationListTabsViewModel
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.doAfterNextLayout
|
||||
@@ -84,7 +82,6 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
private lateinit var signalBottomActionBarController: SignalBottomActionBarController
|
||||
|
||||
private val viewModel: CallLogViewModel by activityViewModels()
|
||||
private val tabsViewModel: ConversationListTabsViewModel by viewModels(ownerProducer = { requireActivity() })
|
||||
private val mainToolbarViewModel: MainToolbarViewModel by activityViewModels()
|
||||
private val mainNavigationViewModel: MainNavigationViewModel by activityViewModels()
|
||||
|
||||
@@ -171,7 +168,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (!closeSearchIfOpen()) {
|
||||
tabsViewModel.onChatsSelected()
|
||||
mainNavigationViewModel.onChatsSelected()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,8 +193,8 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
}
|
||||
|
||||
private fun initializeTapToScrollToTop(scrollToPositionDelegate: ScrollToPositionDelegate) {
|
||||
disposables += tabsViewModel.tabClickEvents
|
||||
.filter { it == MainNavigationDestination.CALLS }
|
||||
disposables += mainNavigationViewModel.tabClickEvents
|
||||
.filter { it == MainNavigationListLocation.CALLS }
|
||||
.subscribeBy(onNext = {
|
||||
scrollToPositionDelegate.resetScrollPosition()
|
||||
})
|
||||
|
||||
@@ -255,7 +255,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
|
||||
import org.thoughtcrime.securesms.longmessage.LongMessageFragment
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.main.MainNavigationListLocation
|
||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity
|
||||
@@ -3055,7 +3055,7 @@ class ConversationFragment :
|
||||
} else if ("username_edit" == action) {
|
||||
startActivity(EditProfileActivity.getIntentForUsernameEdit(requireContext()))
|
||||
} else if ("calls_tab" == action) {
|
||||
startActivity(MainActivity.clearTopAndOpenTab(requireContext(), MainNavigationDestination.CALLS))
|
||||
startActivity(MainActivity.clearTopAndOpenTab(requireContext(), MainNavigationListLocation.CALLS))
|
||||
} else if ("chat_folder" == action) {
|
||||
startActivity(AppSettingsActivity.chatFolders(requireContext()))
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
getNavigator().getViewModel().setSnackbar(new SnackbarState(
|
||||
mainNavigationViewModel.setSnackbar(new SnackbarState(
|
||||
getResources().getQuantityString(R.plurals.ConversationListFragment_moved_conversations_to_inbox, 1, 1),
|
||||
new SnackbarState.ActionState(
|
||||
getString(R.string.ConversationListFragment_undo),
|
||||
|
||||
@@ -132,7 +132,8 @@ import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.AccountValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination;
|
||||
import org.thoughtcrime.securesms.main.MainNavigationListLocation;
|
||||
import org.thoughtcrime.securesms.main.MainNavigationViewModel;
|
||||
import org.thoughtcrime.securesms.main.MainToolbarMode;
|
||||
import org.thoughtcrime.securesms.main.MainToolbarViewModel;
|
||||
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder;
|
||||
@@ -146,7 +147,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.search.MessageResult;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
@@ -225,13 +225,14 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
protected ConversationListArchiveItemDecoration archiveDecoration;
|
||||
protected ConversationListItemAnimator itemAnimator;
|
||||
private Stopwatch startupStopwatch;
|
||||
private ConversationListTabsViewModel conversationListTabsViewModel;
|
||||
private ContactSearchMediator contactSearchMediator;
|
||||
private MainToolbarViewModel mainToolbarViewModel;
|
||||
private ChatListBackHandler chatListBackHandler;
|
||||
|
||||
private BannerManager bannerManager;
|
||||
|
||||
protected MainNavigationViewModel mainNavigationViewModel;
|
||||
|
||||
public static ConversationListFragment newInstance() {
|
||||
return new ConversationListFragment();
|
||||
}
|
||||
@@ -250,8 +251,9 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
startupStopwatch = new Stopwatch("startup");
|
||||
mainToolbarViewModel = new ViewModelProvider(getActivity()).get(MainToolbarViewModel.class);
|
||||
startupStopwatch = new Stopwatch("startup");
|
||||
mainToolbarViewModel = new ViewModelProvider(requireActivity()).get(MainToolbarViewModel.class);
|
||||
mainNavigationViewModel = new ViewModelProvider(requireActivity()).get(MainNavigationViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -390,19 +392,17 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
chatListBackHandler = new ChatListBackHandler(false);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), chatListBackHandler);
|
||||
|
||||
conversationListTabsViewModel = new ViewModelProvider(requireActivity()).get(ConversationListTabsViewModel.class);
|
||||
|
||||
lifecycleDisposable.bindTo(getViewLifecycleOwner());
|
||||
lifecycleDisposable.add(conversationListTabsViewModel.getTabClickEvents().filter(tab -> tab == MainNavigationDestination.CHATS)
|
||||
.subscribe(unused -> {
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) list.getLayoutManager();
|
||||
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
|
||||
if (firstVisibleItemPosition <= LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD) {
|
||||
list.smoothScrollToPosition(0);
|
||||
} else {
|
||||
list.scrollToPosition(0);
|
||||
}
|
||||
}));
|
||||
lifecycleDisposable.add(mainNavigationViewModel.getTabClickEvents().filter(tab -> tab == MainNavigationListLocation.CHATS)
|
||||
.subscribe(unused -> {
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) list.getLayoutManager();
|
||||
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
|
||||
if (firstVisibleItemPosition <= LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD) {
|
||||
list.smoothScrollToPosition(0);
|
||||
} else {
|
||||
list.scrollToPosition(0);
|
||||
}
|
||||
}));
|
||||
|
||||
requireCallback().bindScrollHelper(list, getViewLifecycleOwner(), chatFolderList, color -> {
|
||||
for (int i = 0; i < chatFolderList.getChildCount(); i++) {
|
||||
@@ -953,7 +953,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
.subscribe(() -> {
|
||||
endActionModeIfActive();
|
||||
|
||||
getNavigator().getViewModel().setSnackbar(new SnackbarState(
|
||||
mainNavigationViewModel.setSnackbar(new SnackbarState(
|
||||
snackBarTitle,
|
||||
new SnackbarState.ActionState(
|
||||
getString(R.string.ConversationListFragment_undo),
|
||||
@@ -1042,7 +1042,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
.toList());
|
||||
|
||||
if (toPin.size() + viewModel.getPinnedCount() > MAXIMUM_PINNED_CONVERSATIONS) {
|
||||
getNavigator().getViewModel().setSnackbar(new SnackbarState(
|
||||
mainNavigationViewModel.setSnackbar(new SnackbarState(
|
||||
getString(R.string.conversation_list__you_can_only_pin_up_to_d_chats, MAXIMUM_PINNED_CONVERSATIONS),
|
||||
null,
|
||||
false,
|
||||
@@ -1407,7 +1407,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(pinnedThreadIds -> {
|
||||
getNavigator().getViewModel().setSnackbar(new SnackbarState(
|
||||
mainNavigationViewModel.setSnackbar(new SnackbarState(
|
||||
getResources().getQuantityString(R.plurals.ConversationListFragment_conversations_archived, 1, 1),
|
||||
new SnackbarState.ActionState(
|
||||
getString(R.string.ConversationListFragment_undo),
|
||||
|
||||
@@ -5,13 +5,18 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
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 io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
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
|
||||
@@ -21,8 +26,6 @@ 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.stories.tabs.ConversationListTabsState
|
||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
import org.thoughtcrime.securesms.util.TopToastPopup
|
||||
@@ -35,13 +38,13 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
private val TAG = Log.tag(MainActivityListHostFragment::class.java)
|
||||
}
|
||||
|
||||
private val conversationListTabsViewModel: ConversationListTabsViewModel by viewModels(ownerProducer = { requireActivity() })
|
||||
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)
|
||||
@@ -50,18 +53,30 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
toolbarViewModel.setHasUnreadPayments(unread.isPresent)
|
||||
}
|
||||
|
||||
disposables += conversationListTabsViewModel.state.subscribeBy { state ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disposables += conversationListTabsViewModel.getNotificationProfiles().subscribeBy { profiles ->
|
||||
updateNotificationProfileStatus(profiles)
|
||||
launch {
|
||||
mainNavigationViewModel.getNotificationProfiles().collectLatest { profiles ->
|
||||
withContext(Dispatchers.Main) {
|
||||
updateNotificationProfileStatus(profiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +84,11 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
return requireView().findViewById<View>(R.id.fragment_container).findNavController()
|
||||
}
|
||||
|
||||
private fun goToStateFromConversationList(state: ConversationListTabsState, navController: NavController) {
|
||||
if (state.tab == MainNavigationDestination.CHATS) {
|
||||
private fun goToStateFromConversationList(state: MainNavigationState, navController: NavController) {
|
||||
if (state.selectedDestination == MainNavigationListLocation.CHATS) {
|
||||
return
|
||||
} else {
|
||||
val destination = if (state.tab == MainNavigationDestination.STORIES) {
|
||||
val destination = if (state.selectedDestination == MainNavigationListLocation.STORIES) {
|
||||
R.id.action_conversationListFragment_to_storiesLandingFragment
|
||||
} else {
|
||||
R.id.action_conversationListFragment_to_callLogFragment
|
||||
@@ -87,19 +102,19 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToStateFromCalling(state: ConversationListTabsState, navController: NavController) {
|
||||
when (state.tab) {
|
||||
MainNavigationDestination.CALLS -> return
|
||||
MainNavigationDestination.CHATS -> navController.popBackStack(R.id.conversationListFragment, false)
|
||||
MainNavigationDestination.STORIES -> navController.navigate(R.id.action_callLogFragment_to_storiesLandingFragment)
|
||||
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: ConversationListTabsState, navController: NavController) {
|
||||
when (state.tab) {
|
||||
MainNavigationDestination.STORIES -> return
|
||||
MainNavigationDestination.CHATS -> navController.popBackStack(R.id.conversationListFragment, false)
|
||||
MainNavigationDestination.CALLS -> navController.navigate(R.id.action_storiesLandingFragment_to_callLogFragment)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +127,7 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
.findNavController()
|
||||
.addOnDestinationChangedListener(destinationChangedListener)
|
||||
|
||||
if (conversationListTabsViewModel.isMultiSelectOpen()) {
|
||||
if (toolbarViewModel.state.value.mode == MainToolbarMode.ACTION_MODE) {
|
||||
presentToolbarForMultiselect()
|
||||
}
|
||||
}
|
||||
@@ -126,19 +141,19 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
}
|
||||
|
||||
private fun presentToolbarForConversationListFragment() {
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationDestination.CHATS)
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CHATS)
|
||||
}
|
||||
|
||||
private fun presentToolbarForConversationListArchiveFragment() {
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.BASIC, destination = MainNavigationDestination.CHATS)
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.BASIC, destination = MainNavigationListLocation.CHATS)
|
||||
}
|
||||
|
||||
private fun presentToolbarForStoriesLandingFragment() {
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationDestination.STORIES)
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.STORIES)
|
||||
}
|
||||
|
||||
private fun presentToolbarForCallLogFragment() {
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationDestination.CALLS)
|
||||
toolbarViewModel.setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CALLS)
|
||||
}
|
||||
|
||||
private fun presentToolbarForMultiselect() {
|
||||
@@ -152,7 +167,6 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
|
||||
override fun onMultiSelectStarted() {
|
||||
presentToolbarForMultiselect()
|
||||
conversationListTabsViewModel.onMultiSelectStarted()
|
||||
}
|
||||
|
||||
override fun onMultiSelectFinished() {
|
||||
@@ -160,8 +174,6 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
if (currentDestination != null) {
|
||||
presentToolbarForDestination(currentDestination)
|
||||
}
|
||||
|
||||
conversationListTabsViewModel.onMultiSelectFinished()
|
||||
}
|
||||
|
||||
override fun updateProxyStatus(state: WebSocketConnectionState) {
|
||||
@@ -218,22 +230,18 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||
private fun presentToolbarForDestination(destination: NavDestination) {
|
||||
when (destination.id) {
|
||||
R.id.conversationListFragment -> {
|
||||
conversationListTabsViewModel.isShowingArchived(false)
|
||||
presentToolbarForConversationListFragment()
|
||||
}
|
||||
|
||||
R.id.conversationListArchiveFragment -> {
|
||||
conversationListTabsViewModel.isShowingArchived(true)
|
||||
presentToolbarForConversationListArchiveFragment()
|
||||
}
|
||||
|
||||
R.id.storiesLandingFragment -> {
|
||||
conversationListTabsViewModel.isShowingArchived(false)
|
||||
presentToolbarForStoriesLandingFragment()
|
||||
}
|
||||
|
||||
R.id.callLogFragment -> {
|
||||
conversationListTabsViewModel.isShowingArchived(false)
|
||||
presentToolbarForCallLogFragment()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,21 +46,21 @@ data class SnackbarState(
|
||||
interface MainBottomChromeCallback {
|
||||
fun onNewChatClick()
|
||||
fun onNewCallClick()
|
||||
fun onCameraClick(destination: MainNavigationDestination)
|
||||
fun onCameraClick(destination: MainNavigationListLocation)
|
||||
fun onMegaphoneVisible(megaphone: Megaphone)
|
||||
fun onSnackbarDismissed()
|
||||
|
||||
object Empty : MainBottomChromeCallback {
|
||||
override fun onNewChatClick() = Unit
|
||||
override fun onNewCallClick() = Unit
|
||||
override fun onCameraClick(destination: MainNavigationDestination) = Unit
|
||||
override fun onCameraClick(destination: MainNavigationListLocation) = Unit
|
||||
override fun onMegaphoneVisible(megaphone: Megaphone) = Unit
|
||||
override fun onSnackbarDismissed() = Unit
|
||||
}
|
||||
}
|
||||
|
||||
data class MainBottomChromeState(
|
||||
val destination: MainNavigationDestination = MainNavigationDestination.CHATS,
|
||||
val destination: MainNavigationListLocation = MainNavigationListLocation.CHATS,
|
||||
val megaphoneState: MainMegaphoneState = MainMegaphoneState(),
|
||||
val snackbarState: SnackbarState? = null,
|
||||
val mainToolbarMode: MainToolbarMode = MainToolbarMode.FULL
|
||||
|
||||
@@ -46,9 +46,9 @@ private val ACTION_BUTTON_SPACING = 16.dp
|
||||
|
||||
@Composable
|
||||
fun MainFloatingActionButtons(
|
||||
destination: MainNavigationDestination,
|
||||
destination: MainNavigationListLocation,
|
||||
onNewChatClick: () -> Unit = {},
|
||||
onCameraClick: (MainNavigationDestination) -> Unit = {},
|
||||
onCameraClick: (MainNavigationListLocation) -> Unit = {},
|
||||
onNewCallClick: () -> Unit = {}
|
||||
) {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
@@ -67,7 +67,7 @@ fun MainFloatingActionButtons(
|
||||
.height(boxHeightDp)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = destination == MainNavigationDestination.CHATS,
|
||||
visible = destination == MainNavigationListLocation.CHATS,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
enter = slideInVertically(initialOffsetY = { boxHeightPx - it }),
|
||||
exit = slideOutVertically(targetOffsetY = { boxHeightPx - it })
|
||||
@@ -79,7 +79,7 @@ fun MainFloatingActionButtons(
|
||||
containerColor = SignalTheme.colors.colorSurface1
|
||||
),
|
||||
onClick = {
|
||||
onCameraClick(MainNavigationDestination.CHATS)
|
||||
onCameraClick(MainNavigationListLocation.CHATS)
|
||||
},
|
||||
shadowElevation = elevation
|
||||
)
|
||||
@@ -100,16 +100,16 @@ fun MainFloatingActionButtons(
|
||||
|
||||
@Composable
|
||||
private fun PrimaryActionButton(
|
||||
destination: MainNavigationDestination,
|
||||
destination: MainNavigationListLocation,
|
||||
onNewChatClick: () -> Unit = {},
|
||||
onCameraClick: (MainNavigationDestination) -> Unit = {},
|
||||
onCameraClick: (MainNavigationListLocation) -> Unit = {},
|
||||
onNewCallClick: () -> Unit = {}
|
||||
) {
|
||||
val onClick = remember(destination) {
|
||||
when (destination) {
|
||||
MainNavigationDestination.CHATS -> onNewChatClick
|
||||
MainNavigationDestination.CALLS -> onNewCallClick
|
||||
MainNavigationDestination.STORIES -> {
|
||||
MainNavigationListLocation.CHATS -> onNewChatClick
|
||||
MainNavigationListLocation.CALLS -> onNewCallClick
|
||||
MainNavigationListLocation.STORIES -> {
|
||||
{ onCameraClick(destination) }
|
||||
}
|
||||
}
|
||||
@@ -120,9 +120,9 @@ private fun PrimaryActionButton(
|
||||
icon = {
|
||||
AnimatedContent(destination) { targetState ->
|
||||
val icon = when (targetState) {
|
||||
MainNavigationDestination.CHATS -> R.drawable.symbol_edit_24
|
||||
MainNavigationDestination.CALLS -> R.drawable.symbol_phone_plus_24
|
||||
MainNavigationDestination.STORIES -> R.drawable.symbol_camera_24
|
||||
MainNavigationListLocation.CHATS -> R.drawable.symbol_edit_24
|
||||
MainNavigationListLocation.CALLS -> R.drawable.symbol_phone_plus_24
|
||||
MainNavigationListLocation.STORIES -> R.drawable.symbol_camera_24
|
||||
}
|
||||
|
||||
Icon(
|
||||
@@ -179,14 +179,14 @@ private fun MainFloatingActionButton(
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun MainFloatingActionButtonsPreview() {
|
||||
var destination by remember { mutableStateOf(MainNavigationDestination.CHATS) }
|
||||
var destination by remember { mutableStateOf(MainNavigationListLocation.CHATS) }
|
||||
|
||||
Previews.Preview {
|
||||
MainFloatingActionButtons(
|
||||
destination = destination,
|
||||
onCameraClick = { destination = MainNavigationDestination.CALLS },
|
||||
onNewChatClick = { destination = MainNavigationDestination.STORIES },
|
||||
onNewCallClick = { destination = MainNavigationDestination.CHATS }
|
||||
onCameraClick = { destination = MainNavigationListLocation.CALLS },
|
||||
onNewChatClick = { destination = MainNavigationListLocation.STORIES },
|
||||
onNewCallClick = { destination = MainNavigationListLocation.CHATS }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ import org.thoughtcrime.securesms.R
|
||||
|
||||
private val LOTTIE_SIZE = 28.dp
|
||||
|
||||
enum class MainNavigationDestination(
|
||||
enum class MainNavigationListLocation(
|
||||
@StringRes val label: Int,
|
||||
@RawRes val icon: Int,
|
||||
@StringRes val contentDescription: Int = label
|
||||
@@ -90,7 +90,7 @@ data class MainNavigationState(
|
||||
val storiesCount: Int = 0,
|
||||
val storyFailure: Boolean = false,
|
||||
val isStoriesFeatureEnabled: Boolean = true,
|
||||
val selectedDestination: MainNavigationDestination = MainNavigationDestination.CHATS,
|
||||
val selectedDestination: MainNavigationListLocation = MainNavigationListLocation.CHATS,
|
||||
val compact: Boolean = false
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ data class MainNavigationState(
|
||||
@Composable
|
||||
fun MainNavigationBar(
|
||||
state: MainNavigationState,
|
||||
onDestinationSelected: (MainNavigationDestination) -> Unit
|
||||
onDestinationSelected: (MainNavigationListLocation) -> Unit
|
||||
) {
|
||||
Column(modifier = Modifier.background(color = SignalTheme.colors.colorSurface2)) {
|
||||
NavigationBar(
|
||||
@@ -111,18 +111,18 @@ fun MainNavigationBar(
|
||||
) {
|
||||
val entries = remember(state.isStoriesFeatureEnabled) {
|
||||
if (state.isStoriesFeatureEnabled) {
|
||||
MainNavigationDestination.entries
|
||||
MainNavigationListLocation.entries
|
||||
} else {
|
||||
MainNavigationDestination.entries.filterNot { it == MainNavigationDestination.STORIES }
|
||||
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.STORIES }
|
||||
}
|
||||
}
|
||||
|
||||
entries.forEach { destination ->
|
||||
|
||||
val badgeCount = when (destination) {
|
||||
MainNavigationDestination.CHATS -> state.chatsCount
|
||||
MainNavigationDestination.CALLS -> state.callsCount
|
||||
MainNavigationDestination.STORIES -> state.storiesCount
|
||||
MainNavigationListLocation.CHATS -> state.chatsCount
|
||||
MainNavigationListLocation.CALLS -> state.callsCount
|
||||
MainNavigationListLocation.STORIES -> state.storiesCount
|
||||
}
|
||||
|
||||
val selected = state.selectedDestination == destination
|
||||
@@ -214,7 +214,7 @@ private fun Modifier.drawNavigationBarBadge(count: Int, compact: Boolean): Modif
|
||||
@Composable
|
||||
fun MainNavigationRail(
|
||||
state: MainNavigationState,
|
||||
onDestinationSelected: (MainNavigationDestination) -> Unit
|
||||
onDestinationSelected: (MainNavigationListLocation) -> Unit
|
||||
) {
|
||||
NavigationRail(
|
||||
containerColor = SignalTheme.colors.colorSurface1,
|
||||
@@ -260,9 +260,9 @@ fun MainNavigationRail(
|
||||
) {
|
||||
val entries = remember(state.isStoriesFeatureEnabled) {
|
||||
if (state.isStoriesFeatureEnabled) {
|
||||
MainNavigationDestination.entries
|
||||
MainNavigationListLocation.entries
|
||||
} else {
|
||||
MainNavigationDestination.entries.filterNot { it == MainNavigationDestination.STORIES }
|
||||
MainNavigationListLocation.entries.filterNot { it == MainNavigationListLocation.STORIES }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ fun MainNavigationRail(
|
||||
|
||||
Box {
|
||||
NavigationRailItem(
|
||||
modifier = Modifier.padding(bottom = if (MainNavigationDestination.entries.lastIndex == idx) 0.dp else 16.dp),
|
||||
modifier = Modifier.padding(bottom = if (MainNavigationListLocation.entries.lastIndex == idx) 0.dp else 16.dp),
|
||||
icon = {
|
||||
NavigationDestinationIcon(
|
||||
destination = destination,
|
||||
@@ -299,13 +299,13 @@ fun MainNavigationRail(
|
||||
@Composable
|
||||
private fun BoxScope.NavigationRailCountIndicator(
|
||||
state: MainNavigationState,
|
||||
destination: MainNavigationDestination
|
||||
destination: MainNavigationListLocation
|
||||
) {
|
||||
val count = remember(state, destination) {
|
||||
when (destination) {
|
||||
MainNavigationDestination.CHATS -> state.chatsCount
|
||||
MainNavigationDestination.CALLS -> state.callsCount
|
||||
MainNavigationDestination.STORIES -> state.storiesCount
|
||||
MainNavigationListLocation.CHATS -> state.chatsCount
|
||||
MainNavigationListLocation.CALLS -> state.callsCount
|
||||
MainNavigationListLocation.STORIES -> state.storiesCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ private fun BoxScope.NavigationRailCountIndicator(
|
||||
|
||||
@Composable
|
||||
private fun NavigationDestinationIcon(
|
||||
destination: MainNavigationDestination,
|
||||
destination: MainNavigationListLocation,
|
||||
selected: Boolean
|
||||
) {
|
||||
val dynamicProperties = rememberLottieDynamicProperties(
|
||||
@@ -358,7 +358,7 @@ private fun NavigationDestinationIcon(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NavigationDestinationLabel(destination: MainNavigationDestination) {
|
||||
private fun NavigationDestinationLabel(destination: MainNavigationListLocation) {
|
||||
Text(stringResource(destination.label))
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ private fun formatCount(count: Int): String {
|
||||
@Composable
|
||||
private fun MainNavigationRailPreview() {
|
||||
Previews.Preview {
|
||||
var selected by remember { mutableStateOf(MainNavigationDestination.CHATS) }
|
||||
var selected by remember { mutableStateOf(MainNavigationListLocation.CHATS) }
|
||||
|
||||
MainNavigationRail(
|
||||
state = MainNavigationState(
|
||||
@@ -392,7 +392,7 @@ private fun MainNavigationRailPreview() {
|
||||
@Composable
|
||||
private fun MainNavigationBarPreview() {
|
||||
Previews.Preview {
|
||||
var selected by remember { mutableStateOf(MainNavigationDestination.CHATS) }
|
||||
var selected by remember { mutableStateOf(MainNavigationListLocation.CHATS) }
|
||||
|
||||
MainNavigationBar(
|
||||
state = MainNavigationState(
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package org.thoughtcrime.securesms.stories.tabs
|
||||
package org.thoughtcrime.securesms.main
|
||||
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.reactive.asFlow
|
||||
import org.thoughtcrime.securesms.database.RxDatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
class ConversationListTabRepository {
|
||||
object MainNavigationRepository {
|
||||
|
||||
fun getNumberOfUnreadMessages(): Flowable<Long> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.threads.getUnreadMessageCount() }
|
||||
fun getNumberOfUnreadMessages(): Flow<Long> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.threads.getUnreadMessageCount() }.asFlow()
|
||||
}
|
||||
|
||||
fun getNumberOfUnseenStories(): Flowable<Long> {
|
||||
fun getNumberOfUnseenStories(): Flow<Long> {
|
||||
return RxDatabaseObserver.conversationList.map {
|
||||
SignalDatabase
|
||||
.messages
|
||||
@@ -20,14 +21,14 @@ class ConversationListTabRepository {
|
||||
.filterNot { it.shouldHideStory }
|
||||
.size
|
||||
.toLong()
|
||||
}
|
||||
}.asFlow()
|
||||
}
|
||||
|
||||
fun getHasFailedOutgoingStories(): Flowable<Boolean> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.messages.hasFailedOutgoingStory() }
|
||||
fun getHasFailedOutgoingStories(): Flow<Boolean> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.messages.hasFailedOutgoingStory() }.asFlow()
|
||||
}
|
||||
|
||||
fun getNumberOfUnseenCalls(): Flowable<Long> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.calls.getUnreadMissedCallCount() }
|
||||
fun getNumberOfUnseenCalls(): Flow<Long> {
|
||||
return RxDatabaseObserver.conversationList.map { SignalDatabase.calls.getUnreadMissedCallCount() }.asFlow()
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,27 @@ package org.thoughtcrime.securesms.main
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.reactive.asFlow
|
||||
import kotlinx.coroutines.rx3.asObservable
|
||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.NotificationProfilesRepository
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphone
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
|
||||
class MainNavigationViewModel : ViewModel() {
|
||||
class MainNavigationViewModel(initialListLocation: MainNavigationListLocation = MainNavigationListLocation.CHATS) : ViewModel() {
|
||||
private val megaphoneRepository = AppDependencies.megaphoneRepository
|
||||
|
||||
private val detailLocationFlow = MutableSharedFlow<MainNavigationDetailLocation>()
|
||||
@@ -33,6 +42,35 @@ class MainNavigationViewModel : ViewModel() {
|
||||
private val internalNavigationEvents = MutableSharedFlow<NavigationEvent>()
|
||||
val navigationEvents: Flow<NavigationEvent> = internalNavigationEvents
|
||||
|
||||
private val notificationProfilesRepository: NotificationProfilesRepository = NotificationProfilesRepository()
|
||||
|
||||
private val internalMainNavigationState = MutableStateFlow(MainNavigationState(selectedDestination = initialListLocation))
|
||||
val mainNavigationState: StateFlow<MainNavigationState> = internalMainNavigationState
|
||||
|
||||
/**
|
||||
* This is Rx because these are still accessed from Java.
|
||||
*/
|
||||
private val internalTabClickEvents: MutableSharedFlow<MainNavigationListLocation> = MutableSharedFlow()
|
||||
val tabClickEvents: Observable<MainNavigationListLocation> = internalTabClickEvents.filter { Stories.isFeatureEnabled() }.asObservable()
|
||||
|
||||
init {
|
||||
performStoreUpdate(MainNavigationRepository.getNumberOfUnreadMessages()) { unreadChats, state ->
|
||||
state.copy(chatsCount = unreadChats.toInt())
|
||||
}
|
||||
|
||||
performStoreUpdate(MainNavigationRepository.getNumberOfUnseenCalls()) { unseenCalls, state ->
|
||||
state.copy(callsCount = unseenCalls.toInt())
|
||||
}
|
||||
|
||||
performStoreUpdate(MainNavigationRepository.getNumberOfUnseenStories()) { unseenStories, state ->
|
||||
state.copy(storiesCount = unseenStories.toInt())
|
||||
}
|
||||
|
||||
performStoreUpdate(MainNavigationRepository.getHasFailedOutgoingStories()) { hasFailedStories, state ->
|
||||
state.copy(storyFailure = hasFailedStories)
|
||||
}
|
||||
}
|
||||
|
||||
fun goTo(location: MainNavigationDetailLocation) {
|
||||
viewModelScope.launch {
|
||||
detailLocationFlow.emit(location)
|
||||
@@ -69,6 +107,43 @@ class MainNavigationViewModel : ViewModel() {
|
||||
megaphoneRepository.markVisible(visible.event)
|
||||
}
|
||||
|
||||
fun refreshNavigationBarState() {
|
||||
internalMainNavigationState.update { it.copy(compact = SignalStore.settings.useCompactNavigationBar, isStoriesFeatureEnabled = Stories.isFeatureEnabled()) }
|
||||
}
|
||||
|
||||
fun getNotificationProfiles(): Flow<List<NotificationProfile>> {
|
||||
return notificationProfilesRepository.getProfiles().asFlow()
|
||||
}
|
||||
|
||||
fun onChatsSelected() {
|
||||
internalTabClickEvents.tryEmit(MainNavigationListLocation.CHATS)
|
||||
internalMainNavigationState.update {
|
||||
it.copy(selectedDestination = MainNavigationListLocation.CHATS)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCallsSelected() {
|
||||
internalTabClickEvents.tryEmit(MainNavigationListLocation.CALLS)
|
||||
internalMainNavigationState.update {
|
||||
it.copy(selectedDestination = MainNavigationListLocation.CALLS)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStoriesSelected() {
|
||||
internalTabClickEvents.tryEmit(MainNavigationListLocation.STORIES)
|
||||
internalMainNavigationState.update {
|
||||
it.copy(selectedDestination = MainNavigationListLocation.STORIES)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Any> performStoreUpdate(flow: Flow<T>, fn: (T, MainNavigationState) -> MainNavigationState) {
|
||||
viewModelScope.launch {
|
||||
flow.collectLatest { item ->
|
||||
internalMainNavigationState.update { state -> fn(item, state) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class NavigationEvent {
|
||||
STORY_CAMERA_FIRST
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ data class MainToolbarState(
|
||||
val toolbarColor: Color? = null,
|
||||
val self: Recipient = Recipient.self(),
|
||||
val mode: MainToolbarMode = MainToolbarMode.FULL,
|
||||
val destination: MainNavigationDestination = MainNavigationDestination.CHATS,
|
||||
val destination: MainNavigationListLocation = MainNavigationListLocation.CHATS,
|
||||
val chatFilter: ConversationFilter = ConversationFilter.OFF,
|
||||
val callFilter: CallLogFilter = CallLogFilter.ALL,
|
||||
val hasUnreadPayments: Boolean = false,
|
||||
@@ -373,9 +373,9 @@ private fun PrimaryToolbar(
|
||||
controller = controller
|
||||
) {
|
||||
when (state.destination) {
|
||||
MainNavigationDestination.CHATS -> ChatDropdownItems(state, callback, dismiss)
|
||||
MainNavigationDestination.CALLS -> CallDropdownItems(state.callFilter, callback, dismiss)
|
||||
MainNavigationDestination.STORIES -> StoryDropDownItems(callback, dismiss)
|
||||
MainNavigationListLocation.CHATS -> ChatDropdownItems(state, callback, dismiss)
|
||||
MainNavigationListLocation.CALLS -> CallDropdownItems(state.callFilter, callback, dismiss)
|
||||
MainNavigationListLocation.STORIES -> StoryDropDownItems(callback, dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -671,7 +671,7 @@ private fun FullMainToolbarPreview() {
|
||||
state = MainToolbarState(
|
||||
self = Recipient(isResolving = false),
|
||||
mode = mode,
|
||||
destination = MainNavigationDestination.CHATS,
|
||||
destination = MainNavigationListLocation.CHATS,
|
||||
hasEnabledNotificationProfile = true,
|
||||
proxyState = MainToolbarState.ProxyState.CONNECTED,
|
||||
hasFailedBackups = true
|
||||
|
||||
@@ -63,7 +63,7 @@ class MainToolbarViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun setToolbarMode(mode: MainToolbarMode, destination: MainNavigationDestination? = null) {
|
||||
fun setToolbarMode(mode: MainToolbarMode, destination: MainNavigationListLocation? = null) {
|
||||
val previousMode = internalStateFlow.value.mode
|
||||
|
||||
internalStateFlow.update {
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.main.MainNavigationListLocation
|
||||
import org.thoughtcrime.securesms.main.MainNavigationViewModel
|
||||
import org.thoughtcrime.securesms.main.MainToolbarMode
|
||||
import org.thoughtcrime.securesms.main.MainToolbarViewModel
|
||||
@@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.stories.StoryViewerArgs
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
|
||||
import org.thoughtcrime.securesms.stories.my.MyStoriesActivity
|
||||
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel
|
||||
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
@@ -73,7 +72,6 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||
}
|
||||
)
|
||||
|
||||
private val tabsViewModel: ConversationListTabsViewModel by viewModels(ownerProducer = { requireActivity() })
|
||||
private val mainToolbarViewModel: MainToolbarViewModel by activityViewModels()
|
||||
private val mainNavigationViewModel: MainNavigationViewModel by activityViewModels()
|
||||
|
||||
@@ -156,14 +154,14 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (!closeSearchIfOpen()) {
|
||||
tabsViewModel.onChatsSelected()
|
||||
mainNavigationViewModel.onChatsSelected()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
lifecycleDisposable += tabsViewModel.tabClickEvents
|
||||
.filter { it == MainNavigationDestination.STORIES }
|
||||
lifecycleDisposable += mainNavigationViewModel.tabClickEvents
|
||||
.filter { it == MainNavigationListLocation.STORIES }
|
||||
.subscribeBy(onNext = {
|
||||
val layoutManager = recyclerView?.layoutManager as? LinearLayoutManager ?: return@subscribeBy
|
||||
if (layoutManager.findFirstVisibleItemPosition() <= LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD) {
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
package org.thoughtcrime.securesms.stories.tabs
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rxjava3.subscribeAsState
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.main.MainNavigationBar
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.main.MainNavigationRail
|
||||
import org.thoughtcrime.securesms.main.MainNavigationState
|
||||
import org.thoughtcrime.securesms.main.MainToolbarMode
|
||||
import org.thoughtcrime.securesms.main.MainToolbarViewModel
|
||||
import org.thoughtcrime.securesms.window.Navigation
|
||||
import org.thoughtcrime.securesms.window.WindowSizeClass
|
||||
|
||||
/**
|
||||
* Displays the "Chats" and "Stories" tab to a user.
|
||||
*/
|
||||
class ConversationListTabsFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel: ConversationListTabsViewModel by viewModels(ownerProducer = { requireActivity() })
|
||||
private val mainToolbarViewModel: MainToolbarViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
mainToolbarViewModel.state.map { it.mode }.collectLatest {
|
||||
when (it) {
|
||||
MainToolbarMode.ACTION_MODE -> {
|
||||
viewModel.onMultiSelectStarted()
|
||||
viewModel.onSearchClosed()
|
||||
}
|
||||
MainToolbarMode.FULL -> {
|
||||
viewModel.onMultiSelectFinished()
|
||||
viewModel.onSearchClosed()
|
||||
viewModel.isShowingArchived(false)
|
||||
}
|
||||
MainToolbarMode.BASIC -> {
|
||||
viewModel.onMultiSelectFinished()
|
||||
viewModel.onSearchClosed()
|
||||
viewModel.isShowingArchived(true)
|
||||
}
|
||||
MainToolbarMode.SEARCH -> {
|
||||
viewModel.onMultiSelectFinished()
|
||||
viewModel.onSearchOpened()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.subscribeAsState(ConversationListTabsState())
|
||||
|
||||
val navState = remember(state) {
|
||||
MainNavigationState(
|
||||
chatsCount = state.unreadMessagesCount.toInt(),
|
||||
callsCount = state.unreadCallsCount.toInt(),
|
||||
storiesCount = state.unreadStoriesCount.toInt(),
|
||||
storyFailure = state.hasFailedStory,
|
||||
selectedDestination = when (state.tab) {
|
||||
MainNavigationDestination.CHATS -> MainNavigationDestination.CHATS
|
||||
MainNavigationDestination.CALLS -> MainNavigationDestination.CALLS
|
||||
MainNavigationDestination.STORIES -> MainNavigationDestination.STORIES
|
||||
},
|
||||
compact = state.compact,
|
||||
isStoriesFeatureEnabled = state.isStoriesFeatureEnabled
|
||||
)
|
||||
}
|
||||
|
||||
if (state.visibilityState.isVisible()) {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val onDestinationSelected: (MainNavigationDestination) -> Unit = remember {
|
||||
{
|
||||
when (it) {
|
||||
MainNavigationDestination.CHATS -> viewModel.onChatsSelected()
|
||||
MainNavigationDestination.CALLS -> viewModel.onCallsSelected()
|
||||
MainNavigationDestination.STORIES -> viewModel.onStoriesSelected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (windowSizeClass.navigation == Navigation.BAR) {
|
||||
MainNavigationBar(
|
||||
state = navState,
|
||||
onDestinationSelected = onDestinationSelected
|
||||
)
|
||||
} else {
|
||||
MainNavigationRail(
|
||||
state = navState,
|
||||
onDestinationSelected = onDestinationSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.refreshNavigationBarState()
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.thoughtcrime.securesms.stories.tabs
|
||||
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
|
||||
data class ConversationListTabsState(
|
||||
val tab: MainNavigationDestination = MainNavigationDestination.CHATS,
|
||||
val prevTab: MainNavigationDestination = if (tab == MainNavigationDestination.CHATS) MainNavigationDestination.STORIES else MainNavigationDestination.CHATS,
|
||||
val unreadMessagesCount: Long = 0L,
|
||||
val unreadCallsCount: Long = 0L,
|
||||
val unreadStoriesCount: Long = 0L,
|
||||
val hasFailedStory: Boolean = false,
|
||||
val visibilityState: VisibilityState = VisibilityState(),
|
||||
val compact: Boolean = SignalStore.settings.useCompactNavigationBar,
|
||||
val isStoriesFeatureEnabled: Boolean = Stories.isFeatureEnabled()
|
||||
) {
|
||||
data class VisibilityState(
|
||||
val isSearchOpen: Boolean = false,
|
||||
val isMultiSelectOpen: Boolean = false,
|
||||
val isShowingArchived: Boolean = false
|
||||
) {
|
||||
fun isVisible(): Boolean {
|
||||
return !isSearchOpen && !isMultiSelectOpen && !isShowingArchived
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package org.thoughtcrime.securesms.stories.tabs
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.NotificationProfilesRepository
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.main.MainNavigationDestination
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
class ConversationListTabsViewModel(startingTab: MainNavigationDestination, repository: ConversationListTabRepository) : ViewModel() {
|
||||
|
||||
private val notificationProfilesRepository: NotificationProfilesRepository = NotificationProfilesRepository()
|
||||
|
||||
private val store = RxStore(ConversationListTabsState(tab = startingTab))
|
||||
|
||||
val stateSnapshot: ConversationListTabsState
|
||||
get() = store.state
|
||||
|
||||
val state: Flowable<ConversationListTabsState> = store.stateFlowable.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread())
|
||||
val disposables = CompositeDisposable()
|
||||
|
||||
private val internalTabClickEvents: Subject<MainNavigationDestination> = PublishSubject.create()
|
||||
val tabClickEvents: Observable<MainNavigationDestination> = internalTabClickEvents.filter { Stories.isFeatureEnabled() }
|
||||
|
||||
init {
|
||||
disposables += performStoreUpdate(repository.getNumberOfUnreadMessages()) { unreadChats, state ->
|
||||
state.copy(unreadMessagesCount = unreadChats)
|
||||
}
|
||||
|
||||
disposables += performStoreUpdate(repository.getNumberOfUnseenCalls()) { unseenCalls, state ->
|
||||
state.copy(unreadCallsCount = unseenCalls)
|
||||
}
|
||||
|
||||
disposables += performStoreUpdate(repository.getNumberOfUnseenStories()) { unseenStories, state ->
|
||||
state.copy(unreadStoriesCount = unseenStories)
|
||||
}
|
||||
|
||||
disposables += performStoreUpdate(repository.getHasFailedOutgoingStories()) { hasFailedStories, state ->
|
||||
state.copy(hasFailedStory = hasFailedStories)
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshNavigationBarState() {
|
||||
store.update { it.copy(compact = SignalStore.settings.useCompactNavigationBar, isStoriesFeatureEnabled = Stories.isFeatureEnabled()) }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
fun getNotificationProfiles(): Flowable<List<NotificationProfile>> {
|
||||
return notificationProfilesRepository.getProfiles()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun onChatsSelected() {
|
||||
internalTabClickEvents.onNext(MainNavigationDestination.CHATS)
|
||||
performStoreUpdate { it.copy(tab = MainNavigationDestination.CHATS) }
|
||||
}
|
||||
|
||||
fun onCallsSelected() {
|
||||
internalTabClickEvents.onNext(MainNavigationDestination.CALLS)
|
||||
performStoreUpdate { it.copy(tab = MainNavigationDestination.CALLS) }
|
||||
}
|
||||
|
||||
fun onStoriesSelected() {
|
||||
internalTabClickEvents.onNext(MainNavigationDestination.STORIES)
|
||||
performStoreUpdate { it.copy(tab = MainNavigationDestination.STORIES) }
|
||||
}
|
||||
|
||||
fun onSearchOpened() {
|
||||
performStoreUpdate { it.copy(visibilityState = it.visibilityState.copy(isSearchOpen = true)) }
|
||||
}
|
||||
|
||||
fun onSearchClosed() {
|
||||
performStoreUpdate { it.copy(visibilityState = it.visibilityState.copy(isSearchOpen = false)) }
|
||||
}
|
||||
|
||||
fun onMultiSelectStarted() {
|
||||
performStoreUpdate { it.copy(visibilityState = it.visibilityState.copy(isMultiSelectOpen = true)) }
|
||||
}
|
||||
|
||||
fun isMultiSelectOpen(): Boolean {
|
||||
return store.state.visibilityState.isMultiSelectOpen
|
||||
}
|
||||
|
||||
fun onMultiSelectFinished() {
|
||||
performStoreUpdate { it.copy(visibilityState = it.visibilityState.copy(isMultiSelectOpen = false)) }
|
||||
}
|
||||
|
||||
fun isShowingArchived(isShowingArchived: Boolean) {
|
||||
performStoreUpdate { it.copy(visibilityState = it.visibilityState.copy(isShowingArchived = isShowingArchived)) }
|
||||
}
|
||||
|
||||
private fun performStoreUpdate(fn: (ConversationListTabsState) -> ConversationListTabsState) {
|
||||
store.update {
|
||||
fn(it.copy(prevTab = it.tab))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Any> performStoreUpdate(flowable: Flowable<T>, fn: (T, ConversationListTabsState) -> ConversationListTabsState): Disposable {
|
||||
return store.update(flowable) { t, state ->
|
||||
fn(t, state.copy(prevTab = state.tab))
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val startingTab: MainNavigationDestination?, private val repository: ConversationListTabRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
val tab = if (startingTab == null || (startingTab == MainNavigationDestination.STORIES && !Stories.isFeatureEnabled())) {
|
||||
MainNavigationDestination.CHATS
|
||||
} else {
|
||||
startingTab
|
||||
}
|
||||
return modelClass.cast(ConversationListTabsViewModel(tab, repository)) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user