From b11fabeb11e088602776639faa5f00baf390e79a Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 12 Dec 2022 15:05:56 +0300 Subject: [PATCH] DROID-99 Editor | Navigation | Fix double clicks / crashes (#2757) --- .../anytype/ui/base/NavigationFragment.kt | 14 ++++- .../anytype/ui/dashboard/DashboardFragment.kt | 59 ++++--------------- .../anytype/ui/editor/EditorFragment.kt | 36 +++++------ .../editor/sheets/ObjectMenuBaseFragment.kt | 9 +-- .../anytype/ui/sets/ObjectSetFragment.kt | 22 +++---- .../ui/settings/AccountAndDataFragment.kt | 11 ++-- .../anytype/ui/settings/AppearanceFragment.kt | 9 ++- .../ui/settings/MainSettingFragment.kt | 12 ++-- .../ui/settings/OtherSettingsFragment.kt | 18 +++--- .../core_ui/reactive/ViewClickedFlow.kt | 8 --- .../anytype/core_utils/ext/FlowExt.kt | 3 +- .../ui/BaseBottomSheetComposeFragment.kt | 44 +++++++++++++- .../core_utils/ui/BaseBottomSheetFragment.kt | 30 ++++++++++ .../anytype/core_utils/ui/BaseFragment.kt | 33 ++++++++++- .../presentation/sets/ObjectSetViewModel.kt | 2 +- 15 files changed, 184 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt index d46ce972f1..60ad3a27fb 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt @@ -6,6 +6,7 @@ import androidx.viewbinding.ViewBinding import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.core_utils.common.EventWrapper import com.anytypeio.anytype.core_utils.ui.BaseFragment +import com.anytypeio.anytype.core_utils.ui.getNavigationId import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.navigation.AppNavigation.Command import timber.log.Timber @@ -14,10 +15,14 @@ abstract class NavigationFragment( @LayoutRes private val layout: Int ) : BaseFragment(layout) { + private val currentNavigationId by lazy { getNavigationId() } + val navObserver = Observer> { event -> event.getContentIfNotHandled()?.let { try { - navigate(it) + if (currentNavigationId == getNavigationId()) { + throttle { navigate(it) } + } } catch (e: Exception) { Timber.e(e, "Navigation: $it") if (BuildConfig.DEBUG) { @@ -51,7 +56,10 @@ abstract class NavigationFragment( is Command.OpenSettings -> navigation.openSettings() is Command.OpenObject -> navigation.openDocument(command.id, command.editorSettings) is Command.OpenArchive -> navigation.openArchive(command.target) - is Command.OpenObjectSet -> navigation.openObjectSet(command.target, command.isPopUpToDashboard) + is Command.OpenObjectSet -> navigation.openObjectSet( + command.target, + command.isPopUpToDashboard + ) is Command.LaunchObjectSet -> navigation.launchObjectSet(command.target) is Command.LaunchDocument -> navigation.launchDocument(command.id) is Command.LaunchObjectFromSplash -> navigation.launchObjectFromSplash(command.target) @@ -79,4 +87,4 @@ abstract class NavigationFragment( else -> Timber.d("Nav command ignored: $command") } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt index 8539a48dbf..2119ea1ee2 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt @@ -17,12 +17,11 @@ import androidx.transition.TransitionSet import androidx.viewpager2.widget.ViewPager2 import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.ObjectWrapper -import com.anytypeio.anytype.core_ui.reactive.clicks +import com.anytypeio.anytype.core_ui.reactive.click import com.anytypeio.anytype.core_utils.ext.argOrNull import com.anytypeio.anytype.core_utils.ext.cancel import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.invisible -import com.anytypeio.anytype.core_utils.ext.throttleFirst import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ext.visible import com.anytypeio.anytype.core_utils.ui.ViewState @@ -38,8 +37,6 @@ import com.anytypeio.anytype.ui.base.ViewStateFragment import com.anytypeio.anytype.ui.editor.EditorFragment import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -442,51 +439,15 @@ class DashboardFragment : }) } - binding.btnAddDoc - .clicks() - .onEach { vm.onAddNewDocumentClicked() } - .launchIn(lifecycleScope) - - binding.btnSearch - .clicks() - .onEach { vm.onPageSearchClicked() } - .launchIn(lifecycleScope) - - binding.btnMarketplace - .clicks() - .onEach { toast(getString(R.string.coming_soon)) } - .launchIn(lifecycleScope) - - binding.ivSettings - .clicks() - .throttleFirst() - .onEach { vm.onSettingsClicked() } - .launchIn(lifecycleScope) - - binding.avatarContainer - .clicks() - .onEach { vm.onAvatarClicked() } - .launchIn(lifecycleScope) - - binding.tvCancel - .clicks() - .onEach { vm.onCancelSelectionClicked() } - .launchIn(lifecycleScope) - - binding.tvSelectAll - .clicks() - .onEach { vm.onSelectAllClicked() } - .launchIn(lifecycleScope) - - binding.tvRestore - .clicks() - .onEach { vm.onPutBackClicked() } - .launchIn(lifecycleScope) - - binding.tvDelete - .clicks() - .onEach { vm.onDeleteObjectsClicked() } - .launchIn(lifecycleScope) + click(binding.btnAddDoc) { vm.onAddNewDocumentClicked() } + click(binding.btnSearch) { vm.onPageSearchClicked() } + click(binding.btnMarketplace) { toast(getString(R.string.coming_soon)) } + click(binding.ivSettings) { vm.onSettingsClicked() } + click(binding.avatarContainer) { vm.onAvatarClicked() } + click(binding.tvCancel) { vm.onCancelSelectionClicked() } + click(binding.tvSelectAll) { vm.onSelectAllClicked() } + click(binding.tvRestore) { vm.onPutBackClicked() } + click(binding.tvDelete) { vm.onDeleteObjectsClicked() } } override fun inflateBinding( diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index 54b6a07ddd..44bd30eb39 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -132,10 +132,10 @@ import com.anytypeio.anytype.ui.linking.LinkToObjectOrWebPagesFragment import com.anytypeio.anytype.ui.linking.OnLinkToAction import com.anytypeio.anytype.ui.moving.MoveToFragment import com.anytypeio.anytype.ui.moving.OnMoveToAction +import com.anytypeio.anytype.ui.objects.appearance.ObjectAppearanceSettingFragment import com.anytypeio.anytype.ui.objects.types.pickers.DraftObjectSelectTypeFragment import com.anytypeio.anytype.ui.objects.types.pickers.ObjectSelectTypeFragment import com.anytypeio.anytype.ui.objects.types.pickers.OnObjectSelectTypeAction -import com.anytypeio.anytype.ui.objects.appearance.ObjectAppearanceSettingFragment import com.anytypeio.anytype.ui.relations.RelationAddBaseFragment.Companion.CTX_KEY import com.anytypeio.anytype.ui.relations.RelationAddResult import com.anytypeio.anytype.ui.relations.RelationAddToObjectBlockFragment @@ -886,7 +886,7 @@ open class EditorFragment : NavigationFragment(R.layout.f is Command.OpenTextBlockIconPicker -> { TextBlockIconPickerFragment.new( context = ctx, blockId = command.block - ).show(childFragmentManager, null) + ).showChildFragment() } Command.OpenDocumentEmojiIconPicker -> { hideSoftInput() @@ -901,7 +901,7 @@ open class EditorFragment : NavigationFragment(R.layout.f is Command.OpenBookmarkSetter -> { CreateBookmarkFragment.newInstance( target = command.target - ).show(childFragmentManager, null) + ).showChildFragment() } is Command.OpenGallery -> { pickerDelegate.openFilePicker(command.mimeType, null) @@ -936,7 +936,7 @@ open class EditorFragment : NavigationFragment(R.layout.f isProfile = false, fromName = getFrom() ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.OpenProfileMenu -> { hideKeyboard() @@ -963,7 +963,7 @@ open class EditorFragment : NavigationFragment(R.layout.f val fr = ObjectLayoutFragment.new(command.ctx).apply { onDismissListener = { vm.onLayoutDialogDismissed() } } - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.OpenFullScreenImage -> { val screen = FullScreenPictureFragment.new(command.target, command.url).apply { @@ -978,7 +978,7 @@ open class EditorFragment : NavigationFragment(R.layout.f } is Command.AlertDialog -> { if (childFragmentManager.findFragmentByTag(TAG_ALERT) == null) { - AlertUpdateAppFragment().show(childFragmentManager, TAG_ALERT) + AlertUpdateAppFragment().showChildFragment(TAG_ALERT) } else { // Do nothing } @@ -988,7 +988,7 @@ open class EditorFragment : NavigationFragment(R.layout.f } is Command.Dialog.SelectLanguage -> { SelectProgrammingLanguageFragment.new(command.target) - .show(childFragmentManager, null) + .showChildFragment() } is Command.OpenObjectRelationScreen.RelationAdd -> { hideKeyboard() @@ -998,7 +998,7 @@ open class EditorFragment : NavigationFragment(R.layout.f target = command.target, mode = RelationListFragment.MODE_ADD ) - .show(childFragmentManager, null) + .showChildFragment() } is Command.OpenObjectRelationScreen.RelationList -> { hideKeyboard() @@ -1023,7 +1023,7 @@ open class EditorFragment : NavigationFragment(R.layout.f targetObjectTypes = command.targetObjectTypes, isLocked = command.isLocked ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.OpenObjectRelationScreen.Value.Text -> { hideKeyboard() @@ -1034,7 +1034,7 @@ open class EditorFragment : NavigationFragment(R.layout.f objectId = command.target, isLocked = command.isLocked ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.OpenObjectRelationScreen.Value.Date -> { hideKeyboard() @@ -1044,7 +1044,7 @@ open class EditorFragment : NavigationFragment(R.layout.f relationId = command.relationId, relationKey = command.relationKey ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } Command.AddSlashWidgetTriggerToFocusedBlock -> { binding.recycler.addTextFromSelectedStart(text = "/") @@ -1054,14 +1054,14 @@ open class EditorFragment : NavigationFragment(R.layout.f val fr = DraftObjectSelectTypeFragment.newInstance( excludeTypes = command.excludedTypes ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.OpenObjectSelectTypeScreen -> { hideKeyboard() val fr = ObjectSelectTypeFragment.newInstance( excludeTypes = command.excludedTypes ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.OpenMoveToScreen -> { jobs += lifecycleScope.launch { @@ -1073,7 +1073,7 @@ open class EditorFragment : NavigationFragment(R.layout.f restorePosition = command.restorePosition, restoreBlock = command.restoreBlock ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } } is Command.OpenObjectSnackbar -> { @@ -1099,7 +1099,7 @@ open class EditorFragment : NavigationFragment(R.layout.f position = command.position, ignore = vm.context ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } } is Command.AddMentionWidgetTriggerToFocusedBlock -> { @@ -1125,7 +1125,7 @@ open class EditorFragment : NavigationFragment(R.layout.f rangeEnd = command.range.last, isWholeBlockMarkup = command.isWholeBlockMarkup ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.ShowKeyboard -> { binding.recycler.findFocus()?.focusAndShowKeyboard() @@ -1144,7 +1144,7 @@ open class EditorFragment : NavigationFragment(R.layout.f ctx = command.ctx, block = command.block ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is Command.ScrollToPosition -> { val lm = binding.recycler.layoutManager as LinearLayoutManager @@ -1162,7 +1162,7 @@ open class EditorFragment : NavigationFragment(R.layout.f hideKeyboard() } } - fr.show(childFragmentManager, null) + fr.showChildFragment() } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt index 6e1678c45b..9e825628bc 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt @@ -12,11 +12,12 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_ui.features.objects.ObjectActionAdapter import com.anytypeio.anytype.core_ui.layout.SpacingItemDecoration import com.anytypeio.anytype.core_ui.reactive.click -import com.anytypeio.anytype.core_ui.reactive.proceed import com.anytypeio.anytype.core_utils.ext.arg import com.anytypeio.anytype.core_utils.ext.shareFile +import com.anytypeio.anytype.core_utils.ext.throttleFirst import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment +import com.anytypeio.anytype.core_utils.ui.proceed import com.anytypeio.anytype.core_utils.ui.showActionableSnackBar import com.anytypeio.anytype.databinding.FragmentObjectMenuBinding import com.anytypeio.anytype.presentation.objects.ObjectIcon @@ -75,7 +76,7 @@ abstract class ObjectMenuBaseFragment : proceed(vm.actions) { actionAdapter.submitList(it) } proceed(vm.toasts) { toast(it) } proceed(vm.isDismissed) { isDismissed -> if (isDismissed) dismiss() } - proceed(vm.commands) { command -> execute(command) } + proceed(vm.commands.throttleFirst()) { command -> execute(command) } proceed(vm.options) { options -> renderOptions(options) } super.onStart() @@ -121,7 +122,7 @@ abstract class ObjectMenuBaseFragment : ObjectMenuViewModelBase.Command.OpenSetIcons -> openSetIcons() ObjectMenuViewModelBase.Command.OpenSetLayout -> toast(COMING_SOON_MSG) ObjectMenuViewModelBase.Command.OpenSetRelations -> toast(COMING_SOON_MSG) - ObjectMenuViewModelBase.Command.OpenLinkToChooser -> openLinkChooser(command) + ObjectMenuViewModelBase.Command.OpenLinkToChooser -> openLinkChooser() is ObjectMenuViewModelBase.Command.OpenSnackbar -> openSnackbar(command) is ObjectMenuViewModelBase.Command.ShareDebugTree -> shareFile(command.uri) } @@ -178,7 +179,7 @@ abstract class ObjectMenuBaseFragment : } - private fun openLinkChooser(command: ObjectMenuViewModelBase.Command) { + private fun openLinkChooser() { val fr = MoveToFragment.new( ctx = ctx, blocks = emptyList(), diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt index 15b2fceebc..49ecf436aa 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt @@ -594,7 +594,7 @@ open class ObjectSetFragment : flow = RelationTextValueFragment.FLOW_DATAVIEW, relationKey = command.relationKey ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.EditGridDateCell -> { //todo Relation as object, fix relationKey @@ -605,7 +605,7 @@ open class ObjectSetFragment : flow = RelationDateValueFragment.FLOW_DATAVIEW, relationKey = "" ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.EditRelationCell -> { findNavController().safeNavigate( @@ -628,25 +628,25 @@ open class ObjectSetFragment : ctx = command.ctx, viewer = command.viewer ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.CreateViewer -> { val fr = CreateDataViewViewerFragment.new( ctx = command.ctx, target = command.target ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.EditDataViewViewer -> { val fr = EditDataViewViewerFragment.new( ctx = command.ctx, viewer = command.viewer ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.ManageViewer -> { val fr = ManageViewerFragment.new(ctx = command.ctx, dataview = command.dataview) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.OpenSettings -> { val fr = ObjectSetSettingsFragment.new( @@ -654,7 +654,7 @@ open class ObjectSetFragment : dv = command.dv, viewer = command.viewer ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.SetNameForCreatedObject -> { findNavController().safeNavigate( @@ -712,11 +712,11 @@ open class ObjectSetFragment : val fr = ViewerFilterFragment.new( ctx = command.ctx ) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.ModifyViewerSorts -> { val fr = ViewerSortFragment.new(ctx) - fr.show(childFragmentManager, EMPTY_TAG) + fr.showChildFragment(EMPTY_TAG) } is ObjectSetCommand.Modal.OpenCoverActionMenu -> { findNavController().safeNavigate( @@ -735,11 +735,11 @@ open class ObjectSetFragment : val fr = DataViewSelectSourceFragment.newInstance( selectedTypes = command.selectedTypes ) - fr.show(childFragmentManager, null) + fr.showChildFragment() } is ObjectSetCommand.Modal.OpenEmptyDataViewSelectSourceScreen -> { val fr = EmptyDataViewSelectSourceFragment() - fr.show(childFragmentManager, null) + fr.showChildFragment() } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt index 2ec5ec3e83..88d2678bb0 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.core_utils.ext.shareFile @@ -39,11 +38,11 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { private val onKeychainPhraseClicked = { val bundle = bundleOf(KeychainPhraseDialog.ARG_SCREEN_TYPE to EventsDictionary.Type.screenSettings) - findNavController().navigate(R.id.keychainDialog, bundle) + safeNavigate(R.id.keychainDialog, bundle) } private val onLogoutClicked = { - findNavController().navigate(R.id.logoutWarningScreen) + safeNavigate(R.id.logoutWarningScreen) } override fun onCreateView( @@ -62,10 +61,10 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { MaterialTheme(typography = typography) { AccountAndDataScreen( onKeychainPhraseClicked = onKeychainPhraseClicked, - onClearFileCachedClicked = { proceedWithClearFileCacheWarning() }, - onDeleteAccountClicked = { proceedWithAccountDeletion() }, + onClearFileCachedClicked = { throttle { proceedWithClearFileCacheWarning() } }, + onDeleteAccountClicked = { throttle { proceedWithAccountDeletion() } }, onLogoutClicked = onLogoutClicked, - onDebugSyncReportClicked = { vm.onDebugSyncReportClicked() }, + onDebugSyncReportClicked = { throttle { vm.onDebugSyncReportClicked() } }, isLogoutInProgress = vm.isLoggingOut.collectAsState().value, isClearCacheInProgress = vm.isClearFileCacheInProgress.collectAsState().value, isDebugSyncReportInProgress = vm.isDebugSyncReportInProgress.collectAsState().value, diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/AppearanceFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/AppearanceFragment.kt index 79e839f7aa..a79ff841ae 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/AppearanceFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/AppearanceFragment.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment import com.anytypeio.anytype.di.common.componentManager @@ -25,7 +24,7 @@ class AppearanceFragment : BaseBottomSheetComposeFragment() { private val vm by viewModels { factory } private val onWallpaperClicked = { - findNavController().navigate(R.id.wallpaperSetScreen) + safeNavigate(R.id.wallpaperSetScreen) } override fun onCreateView( @@ -39,9 +38,9 @@ class AppearanceFragment : BaseBottomSheetComposeFragment() { MaterialTheme(typography = typography) { AppearanceScreen( onWallpaperClicked = onWallpaperClicked, - light = { vm.onLight() }, - dark = { vm.onDark() }, - system = { vm.onSystem() }, + light = { throttle { vm.onLight() } }, + dark = { throttle { vm.onDark() } }, + system = { throttle { vm.onSystem() } }, selectedMode = vm.selectedTheme.collectAsState().value ) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt index c9fc7e8ec8..01af7b81e9 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt @@ -12,17 +12,15 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Event import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Command +import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Event import com.anytypeio.anytype.ui.settings.system.SettingsActivity import com.anytypeio.anytype.ui_settings.main.MainSettingScreen -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject @@ -90,16 +88,16 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() { private fun processCommands(command: Command) { when (command) { Command.OpenAboutScreen -> { - findNavController().navigate(R.id.actionOpenAboutAppScreen) + safeNavigate(R.id.actionOpenAboutAppScreen) } Command.OpenAccountAndDataScreen -> { - findNavController().navigate(R.id.actionOpenAccountAndDataScreen) + safeNavigate(R.id.actionOpenAccountAndDataScreen) } Command.OpenAppearanceScreen -> { - findNavController().navigate(R.id.actionOpenAppearanceScreen) + safeNavigate(R.id.actionOpenAppearanceScreen) } Command.OpenPersonalizationScreen -> { - findNavController().navigate(R.id.actionOpenPersonalizationScreen) + safeNavigate(R.id.actionOpenPersonalizationScreen) } Command.OpenDebugScreen -> { startActivity(Intent(requireActivity(), SettingsActivity::class.java)) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/OtherSettingsFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/OtherSettingsFragment.kt index d9036ea5ad..53e396facf 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/OtherSettingsFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/OtherSettingsFragment.kt @@ -5,11 +5,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_utils.ext.subscribe import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment +import com.anytypeio.anytype.core_utils.ui.proceed import com.anytypeio.anytype.databinding.FragmentUserSettingsBinding import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.presentation.settings.OtherSettingsViewModel @@ -38,26 +37,23 @@ class OtherSettingsFragment : BaseBottomSheetFragment dismiss() + is OtherSettingsViewModel.Command.Exit -> throttle { dismiss() } is OtherSettingsViewModel.Command.NavigateToObjectTypesScreen -> { - val fr = AppDefaultObjectTypeFragment.newInstance( + AppDefaultObjectTypeFragment.newInstance( excludeTypes = command.excludeTypes - ) - fr.show(childFragmentManager, null) + ).showChildFragment() } is OtherSettingsViewModel.Command.Toast -> toast(command.msg) is OtherSettingsViewModel.Command.ShowClearCacheAlert -> { val dialog = ClearCacheAlertFragment.new() dialog.onClearAccepted = { vm.proceedWithClearCache() } - dialog.show(childFragmentManager, null) + dialog.showChildFragment() } } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/reactive/ViewClickedFlow.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/reactive/ViewClickedFlow.kt index 33ca21baca..0d972028ee 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/reactive/ViewClickedFlow.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/reactive/ViewClickedFlow.kt @@ -127,12 +127,4 @@ fun BaseBottomSheetFragment<*>.click( .throttleFirst() .onEach { action() } .launchIn(lifecycleScope) -} - -fun BaseFragment<*>.proceed(flow: Flow, body: suspend (T) -> Unit) { - jobs += flow.onEach { body(it) }.launchIn(lifecycleScope) -} - -fun BaseBottomSheetFragment<*>.proceed(flow: Flow, body: suspend (T) -> Unit) { - jobs += flow.onEach { body(it) }.launchIn(lifecycleScope) } \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FlowExt.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FlowExt.kt index 9031bae168..a06065459e 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FlowExt.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FlowExt.kt @@ -104,4 +104,5 @@ fun MutableList.cancel() { clear() } -const val DEFAULT_THROTTLE_DURATION = 1000L \ No newline at end of file +const val DEFAULT_THROTTLE_DURATION = 1000L +const val LONG_THROTTLE_DURATION = 2000L \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetComposeFragment.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetComposeFragment.kt index 3213ff00e9..dc0bd8dd89 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetComposeFragment.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetComposeFragment.kt @@ -2,13 +2,49 @@ package com.anytypeio.anytype.core_utils.ui import android.os.Bundle import android.view.View +import androidx.annotation.IdRes +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.core_utils.R +import com.anytypeio.anytype.core_utils.ext.LONG_THROTTLE_DURATION +import com.anytypeio.anytype.core_utils.ext.throttleFirst import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch abstract class BaseBottomSheetComposeFragment : BottomSheetDialogFragment() { - protected val jobs = mutableListOf() + val jobs = mutableListOf() + + private val currentNavigationId by lazy { getNavigationId() } + private val throttleFlow = MutableSharedFlow<() -> Unit>(0) + + protected fun safeNavigate( + @IdRes id: Int, + args: Bundle? = null + ) { + jobs += this.lifecycleScope.launch { + throttleFlow.emit { + if (currentNavigationId == getNavigationId()) { + findNavController().navigate(id, args) + } + } + } + } + + protected fun throttle(task: () -> Unit) { + jobs += this.lifecycleScope.launch { throttleFlow.emit { task() } } + } + + override fun onStart() { + super.onStart() + proceed(throttleFlow.throttleFirst(LONG_THROTTLE_DURATION)) { it() } + } override fun onStop() { super.onStop() @@ -35,4 +71,10 @@ abstract class BaseBottomSheetComposeFragment : BottomSheetDialogFragment() { abstract fun injectDependencies() abstract fun releaseDependencies() +} + +fun Fragment.getNavigationId() = findNavController().currentDestination?.id + +fun BaseBottomSheetComposeFragment.proceed(flow: Flow, body: suspend (T) -> Unit) { + jobs += flow.onEach { body(it) }.launchIn(lifecycleScope) } \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetFragment.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetFragment.kt index 2cdfa1b54f..99b3b8bd50 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetFragment.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseBottomSheetFragment.kt @@ -5,11 +5,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.anytypeio.anytype.core_utils.R +import com.anytypeio.anytype.core_utils.ext.LONG_THROTTLE_DURATION +import com.anytypeio.anytype.core_utils.ext.throttleFirst import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import com.google.android.material.R.id.design_bottom_sheet as BOTTOM_SHEET_ID abstract class BaseBottomSheetFragment( @@ -21,6 +30,12 @@ abstract class BaseBottomSheetFragment( val sheet: FrameLayout? get() = dialog?.findViewById(BOTTOM_SHEET_ID) + private val throttleFlow = MutableSharedFlow<() -> Unit>(0) + + protected fun throttle(task: () -> Unit) { + jobs += this.lifecycleScope.launch { throttleFlow.emit { task() } } + } + val jobs = mutableListOf() override fun onCreateView( @@ -42,6 +57,11 @@ abstract class BaseBottomSheetFragment( injectDependencies() } + override fun onStart() { + super.onStart() + proceed(throttleFlow.throttleFirst(LONG_THROTTLE_DURATION)) { it() } + } + override fun onStop() { super.onStop() jobs.apply { @@ -50,6 +70,12 @@ abstract class BaseBottomSheetFragment( } } + protected fun DialogFragment.showChildFragment(tag: String? = null) { + jobs += this@BaseBottomSheetFragment.lifecycleScope.launch { + throttleFlow.emit { show(this@BaseBottomSheetFragment.childFragmentManager, tag) } + } + } + override fun onDestroy() { super.onDestroy() if (fragmentScope) releaseDependencies() @@ -80,4 +106,8 @@ abstract class BaseBottomSheetFragment( } protected abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): T +} + +fun BaseBottomSheetFragment<*>.proceed(flow: Flow, body: suspend (T) -> Unit) { + jobs += flow.onEach { body(it) }.launchIn(lifecycleScope) } \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseFragment.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseFragment.kt index 60f75638d6..5b6e54ee82 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseFragment.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ui/BaseFragment.kt @@ -8,11 +8,20 @@ import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.anytypeio.anytype.core_utils.BuildConfig +import com.anytypeio.anytype.core_utils.ext.LONG_THROTTLE_DURATION +import com.anytypeio.anytype.core_utils.ext.throttleFirst import com.anytypeio.anytype.core_utils.insets.RootViewDeferringInsetsCallback import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch abstract class BaseFragment( @LayoutRes private val layout: Int, @@ -26,6 +35,7 @@ abstract class BaseFragment( val hasBinding get() = _binding != null val jobs = mutableListOf() + private val throttleFlow = MutableSharedFlow<() -> Unit>(0) abstract fun injectDependencies() abstract fun releaseDependencies() @@ -35,6 +45,15 @@ abstract class BaseFragment( injectDependencies() } + override fun onStart() { + super.onStart() + proceed(throttleFlow.throttleFirst(LONG_THROTTLE_DURATION)) { it() } + } + + protected fun throttle(task: () -> Unit) { + jobs += this.lifecycleScope.launch { throttleFlow.emit { task() } } + } + override fun onStop() { super.onStop() jobs.apply { @@ -59,7 +78,9 @@ abstract class BaseFragment( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (applyWindowRootInsets) { onApplyWindowRootInsets() } + if (applyWindowRootInsets) { + onApplyWindowRootInsets() + } } open fun onApplyWindowRootInsets() { @@ -73,10 +94,20 @@ abstract class BaseFragment( } } + protected fun DialogFragment.showChildFragment(tag: String? = null) { + jobs += this@BaseFragment.lifecycleScope.launch { + throttleFlow.emit { show(this@BaseFragment.childFragmentManager, tag) } + } + } + override fun onDestroyView() { super.onDestroyView() _binding = null } protected abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): T +} + +fun BaseFragment<*>.proceed(flow: Flow, body: suspend (T) -> Unit) { + jobs += flow.onEach { body(it) }.launchIn(lifecycleScope) } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt index 5d457cb886..8dfe198c50 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt @@ -910,7 +910,7 @@ class ObjectSetViewModel( } fun onViewerSettingsClicked() { - Timber.d("onViewerRelationsClicked, ") + Timber.d("onViewerSettingsClicked, ") if (isRestrictionPresent(DataViewRestriction.RELATION)) { toast(NOT_ALLOWED) } else {