From e2ac0e60760d45ba35a4d69eb864c4e33ac4dbcf Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 30 Nov 2022 23:17:39 +0300 Subject: [PATCH] DROID-449 App | Tech | Add debug tree menu (#2730) DROID-449 App | Tech | Add debug tree menu --- .../features/editor/CreateBlockTesting.kt | 8 +- .../features/editor/ListBlockTesting.kt | 4 +- .../features/editor/MentionUpdateTesting.kt | 4 +- .../features/editor/base/EditorTestSetup.kt | 21 +- .../anytypeio/anytype/di/feature/EditorDI.kt | 29 ++- .../anytype/di/feature/ObjectMenuDI.kt | 23 +- .../editor/sheets/ObjectMenuBaseFragment.kt | 234 +++++++++--------- .../ui/settings/AccountAndDataFragment.kt | 25 +- .../ui/settings/MainSettingFragment.kt | 18 +- .../main/res/layout/fragment_object_menu.xml | 25 +- app/src/main/res/values/strings.xml | 2 + core-ui/build.gradle | 1 + .../core_ui/reactive/ViewClickedFlow.kt | 66 ++++- .../drawable/ic_object_menu_diagnostics.xml | 16 ++ core-ui/src/main/res/values/colors.xml | 2 + .../core_utils/ext/AndroidExtension.kt | 20 ++ .../core_utils/ui/BaseBottomSheetFragment.kt | 2 +- .../anytype/core_utils/ui/BaseFragment.kt | 2 +- .../MiddlewareServiceImplementation.kt | 2 +- .../editor/editor/Orchestrator.kt | 5 +- .../objects/menu/ObjectMenuOptionsProvider.kt | 2 + .../menu/ObjectMenuOptionsProviderImpl.kt | 6 + .../objects/menu/ObjectMenuViewModel.kt | 30 +++ .../objects/menu/ObjectMenuViewModelBase.kt | 30 ++- .../settings/MainSettingsViewModel.kt | 16 +- .../downloader/DebugTreeShareDownloader.kt | 14 ++ .../downloader/DocumentFileShareDownloader.kt | 16 ++ .../downloader/MiddlewareShareDownloader.kt | 26 +- .../dashboard/HomeDashboardViewModelTest.kt | 4 +- .../editor/EditorViewModelTest.kt | 23 +- .../editor/editor/EditorMentionTest.kt | 76 +++--- .../editor/EditorPresentationTestSetup.kt | 67 ++--- .../editor/editor/EditorTitleAddBlockTest.kt | 19 +- .../menu/ObjectMenuOptionsProviderImplTest.kt | 15 +- .../sets/main/ObjectSetNavigationTest.kt | 5 +- .../sets/main/ObjectSetViewModelTestSetup.kt | 4 +- .../splash/SplashViewModelTest.kt | 53 ++-- .../anytype/test_utils/ValueClassAnswer.kt | 8 - .../ui_settings/main/MainSettingScreen.kt | 12 +- .../src/main/res/drawable/ic_debug.xml | 21 ++ ui-settings/src/main/res/values/strings.xml | 1 + 41 files changed, 598 insertions(+), 359 deletions(-) create mode 100644 core-ui/src/main/res/drawable/ic_object_menu_diagnostics.xml create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DebugTreeShareDownloader.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DocumentFileShareDownloader.kt delete mode 100644 test/utils/src/main/java/com/anytypeio/anytype/test_utils/ValueClassAnswer.kt create mode 100644 ui-settings/src/main/res/drawable/ic_debug.xml diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/CreateBlockTesting.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/CreateBlockTesting.kt index 881369873b..b90bd3d7a4 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/CreateBlockTesting.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/CreateBlockTesting.kt @@ -16,7 +16,7 @@ import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget -import com.anytypeio.anytype.domain.base.Either +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.UpdateTextStyle import com.anytypeio.anytype.features.editor.base.EditorTestSetup @@ -24,7 +24,6 @@ import com.anytypeio.anytype.features.editor.base.TestEditorFragment import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent import com.anytypeio.anytype.presentation.MockBlockFactory import com.anytypeio.anytype.presentation.editor.EditorViewModel -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.anytypeio.anytype.test_utils.utils.TestUtils import com.anytypeio.anytype.ui.editor.EditorFragment import com.anytypeio.anytype.utils.CoroutinesTestRule @@ -33,7 +32,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.stub import org.mockito.kotlin.times @@ -183,7 +181,7 @@ class CreateBlockTesting : EditorTestSetup() { // Check results - verifyBlocking(createBlock, times(1)) { invoke(params) } + verifyBlocking(createBlock, times(1)) { asFlow(params) } Espresso.onView( TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId) @@ -326,7 +324,7 @@ class CreateBlockTesting : EditorTestSetup() { createBlock.stub { onBlocking { execute(params) - } doAnswer ValueClassAnswer( + } doReturn Resultat.success( Pair(new.id, Payload(context = root, events = events)) ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/ListBlockTesting.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/ListBlockTesting.kt index 855f1c817c..a20c1ccb3b 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/ListBlockTesting.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/ListBlockTesting.kt @@ -18,6 +18,7 @@ import com.anytypeio.anytype.core_models.SmartBlockType import com.anytypeio.anytype.core_models.ext.content import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget import com.anytypeio.anytype.domain.base.Either +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.block.interactor.UpdateTextStyle import com.anytypeio.anytype.features.editor.base.EditorTestSetup @@ -25,7 +26,6 @@ import com.anytypeio.anytype.features.editor.base.TestEditorFragment import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent import com.anytypeio.anytype.presentation.MockBlockFactory import com.anytypeio.anytype.presentation.editor.EditorViewModel -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.anytypeio.anytype.test_utils.utils.TestUtils import com.anytypeio.anytype.ui.editor.EditorFragment import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule @@ -367,7 +367,7 @@ class ListBlockTesting : EditorTestSetup() { createBlock.stub { onBlocking { execute(params) - } doAnswer ValueClassAnswer( + } doReturn Resultat.success( Pair(new.id, Payload(context = root, events = events)) ) } diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/MentionUpdateTesting.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/MentionUpdateTesting.kt index bd51dda0f6..3c1b022797 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/MentionUpdateTesting.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/MentionUpdateTesting.kt @@ -12,12 +12,12 @@ import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.features.editor.base.EditorTestSetup import com.anytypeio.anytype.features.editor.base.TestEditorFragment import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent import com.anytypeio.anytype.presentation.editor.EditorViewModel import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.anytypeio.anytype.test_utils.utils.checkHasText import com.anytypeio.anytype.test_utils.utils.onItemView import com.anytypeio.anytype.test_utils.utils.rVMatcher @@ -114,7 +114,7 @@ class MentionUpdateTesting : EditorTestSetup() { stubInterceptThreadStatus() stubUpdateText() openPage.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer( + onBlocking { execute(any()) } doReturn Resultat.success( Result.Success( Payload( context = root, diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt index 017d67bfbd..e383fa728b 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.UpdateDivider import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.ClearBlockStyle @@ -103,9 +104,8 @@ import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher -import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader +import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.StandardTestDispatcher @@ -140,24 +140,31 @@ open class EditorTestSetup { lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory @Mock - lateinit var middlewareShareDownloader: MiddlewareShareDownloader + lateinit var documentFileShareDownloader: DocumentFileShareDownloader @Mock lateinit var openPage: OpenPage + @Mock lateinit var closePage: CloseBlock + @Mock lateinit var updateText: UpdateText + @Mock lateinit var createBlock: CreateBlock + @Mock lateinit var interceptEvents: InterceptEvents + @Mock lateinit var updateCheckbox: UpdateCheckbox + @Mock lateinit var unlinkBlocks: UnlinkBlocks lateinit var getSearchObjects: SearchObjects + @Mock lateinit var duplicateBlock: DuplicateBlock @@ -341,7 +348,7 @@ open class EditorTestSetup { applyTemplate = applyTemplate ) - featureToggles = DefaultFeatureToggles() + featureToggles = mock() TestEditorFragment.testViewModelFactory = EditorViewModelFactory( @@ -375,7 +382,7 @@ open class EditorTestSetup { duplicateBlock = duplicateBlock, updateAlignment = updateAlignment, downloadFile = downloadFile, - middlewareShareDownloader = middlewareShareDownloader, + documentFileShareDownloader = documentFileShareDownloader, mergeBlocks = mergeBlocks, updateTextColor = updateTextColor, replaceBlock = replaceBlock, @@ -455,7 +462,7 @@ open class EditorTestSetup { relations: List = emptyList() ) { openPage.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer( + onBlocking { execute(any()) } doReturn Resultat.success( Result.Success( Payload( context = root, @@ -479,7 +486,7 @@ open class EditorTestSetup { events: List ) { createBlock.stub { - onBlocking { execute(params) } doAnswer ValueClassAnswer( + onBlocking { execute(params) } doReturn Resultat.success( Pair( MockDataFactory.randomUuid(), Payload(context = root, events = events) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt index 462102f80b..cc894e35d3 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt @@ -123,7 +123,8 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvide import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher -import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader +import com.anytypeio.anytype.presentation.util.downloader.DebugTreeShareDownloader +import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider import com.anytypeio.anytype.providers.DefaultUriFileProvider @@ -409,7 +410,7 @@ object EditorSessionModule { setRelationKey: SetRelationKey, analytics: Analytics, updateBlocksMark: UpdateBlocksMark, - middlewareShareDownloader: MiddlewareShareDownloader, + documentFileShareDownloader: DocumentFileShareDownloader, clearBlockContent: ClearBlockContent, clearBlockStyle: ClearBlockStyle ): Orchestrator = Orchestrator( @@ -431,7 +432,7 @@ object EditorSessionModule { updateDivider = updateDivider, memory = memory, downloadFile = downloadFile, - middlewareShareDownloader = middlewareShareDownloader, + documentFileShareDownloader = documentFileShareDownloader, turnIntoDocument = turnIntoDocument, textInteractor = Interactor.TextInteractor( proxies = proxer, @@ -1034,17 +1035,25 @@ object EditorUseCaseModule { @JvmStatic @Provides @PerScreen - fun providesMiddlewareShareDownloader( + fun providesDocumentFileShareDownloader( repo: BlockRepository, context: Context, fileProvider: UriFileProvider - ): MiddlewareShareDownloader = MiddlewareShareDownloader( + ): DocumentFileShareDownloader = DocumentFileShareDownloader( + repo = repo, + context = context.applicationContext, + uriFileProvider = fileProvider + ) + + @JvmStatic + @Provides + @PerScreen + fun providesDebugTreeShareDownloader( + repo: BlockRepository, + context: Context, + fileProvider: UriFileProvider + ): DebugTreeShareDownloader = DebugTreeShareDownloader( repo = repo, - dispatchers = AppCoroutineDispatchers( - io = Dispatchers.IO, - computation = Dispatchers.Default, - main = Dispatchers.Main - ), context = context.applicationContext, uriFileProvider = fileProvider ) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt index 1327c2c67b..1a45036fc8 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.feature import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_utils.di.scope.PerDialog +import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.domain.`object`.DuplicateObject import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.block.interactor.CreateBlock @@ -23,6 +24,7 @@ import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModel import com.anytypeio.anytype.presentation.objects.menu.ObjectSetMenuViewModel import com.anytypeio.anytype.presentation.sets.ObjectSet import com.anytypeio.anytype.presentation.util.Dispatcher +import com.anytypeio.anytype.presentation.util.downloader.DebugTreeShareDownloader import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuFragment import com.anytypeio.anytype.ui.sets.ObjectSetMenuFragment import dagger.Module @@ -92,6 +94,7 @@ object ObjectMenuModule { fun provideViewModelFactory( setObjectIsArchived: SetObjectIsArchived, duplicateObject: DuplicateObject, + debugTreeShareDownloader: DebugTreeShareDownloader, addToFavorite: AddToFavorite, removeFromFavorite: RemoveFromFavorite, addBackLinkToObject: AddBackLinkToObject, @@ -100,10 +103,12 @@ object ObjectMenuModule { analytics: Analytics, dispatcher: Dispatcher, updateFields: UpdateFields, + featureToggles: FeatureToggles, delegator: Delegator ): ObjectMenuViewModel.Factory = ObjectMenuViewModel.Factory( setObjectIsArchived = setObjectIsArchived, duplicateObject = duplicateObject, + debugTreeShareDownloader = debugTreeShareDownloader, addToFavorite = addToFavorite, removeFromFavorite = removeFromFavorite, addBackLinkToObject = addBackLinkToObject, @@ -113,14 +118,15 @@ object ObjectMenuModule { dispatcher = dispatcher, updateFields = updateFields, delegator = delegator, - menuOptionsProvider = createMenuOptionsProvider(storage) + menuOptionsProvider = createMenuOptionsProvider(storage, featureToggles) ) @JvmStatic - private fun createMenuOptionsProvider(storage: Editor.Storage) = + private fun createMenuOptionsProvider(storage: Editor.Storage, featureToggles: FeatureToggles) = ObjectMenuOptionsProviderImpl( details = storage.details.stream().map { it.details }, - restrictions = storage.objectRestrictions.stream() + restrictions = storage.objectRestrictions.stream(), + featureToggles = featureToggles ) } @@ -140,6 +146,7 @@ object ObjectSetMenuModule { urlBuilder: UrlBuilder, analytics: Analytics, state: StateFlow, + featureToggles: FeatureToggles, dispatcher: Dispatcher ): ObjectSetMenuViewModel.Factory = ObjectSetMenuViewModel.Factory( setObjectIsArchived = setObjectIsArchived, @@ -152,7 +159,7 @@ object ObjectSetMenuModule { analytics = analytics, state = state, dispatcher = dispatcher, - menuOptionsProvider = createMenuOptionsProvider(state) + menuOptionsProvider = createMenuOptionsProvider(state, featureToggles) ) @JvmStatic @@ -173,9 +180,13 @@ object ObjectSetMenuModule { ) @JvmStatic - private fun createMenuOptionsProvider(state: StateFlow) = + private fun createMenuOptionsProvider( + state: StateFlow, + featureToggles: FeatureToggles + ) = ObjectMenuOptionsProviderImpl( details = state.map { it.details }, - restrictions = state.map { it.objectRestrictions } + restrictions = state.map { it.objectRestrictions }, + featureToggles = featureToggles ) } \ No newline at end of file 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 be3bf6feb1..25fbf5b56b 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 @@ -5,16 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.anytypeio.anytype.R 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.clicks +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.subscribe +import com.anytypeio.anytype.core_utils.ext.shareFile import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment import com.anytypeio.anytype.core_utils.ui.showActionableSnackBar @@ -29,10 +29,9 @@ import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase import com.anytypeio.anytype.ui.moving.MoveToFragment import com.anytypeio.anytype.ui.moving.OnMoveToAction import com.anytypeio.anytype.ui.relations.RelationListFragment -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment(), +abstract class ObjectMenuBaseFragment : + BaseBottomSheetFragment(), OnMoveToAction { protected val ctx get() = arg(CTX_KEY) @@ -53,30 +52,12 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment if (isDismissed) dismiss() } - jobs += subscribe(vm.commands) { command -> execute(command) } - jobs += subscribe(vm.options) { options -> renderOptions(options) } - } + 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.options) { options -> renderOptions(options) } + super.onStart() vm.onStart( ctx = ctx, @@ -110,11 +90,13 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment { - findNavController().navigate( - R.id.objectCoverScreen, - bundleOf(SelectCoverObjectFragment.CTX_KEY to ctx) - ) - } - ObjectMenuViewModelBase.Command.OpenObjectIcons -> { - findNavController().navigate( - R.id.objectIconPickerScreen, - bundleOf( - IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx, - ) - ) - } - ObjectMenuViewModelBase.Command.OpenObjectLayout -> { - val fr = ObjectLayoutFragment.new(ctx) - fr.show(childFragmentManager, null) - } - ObjectMenuViewModelBase.Command.OpenObjectRelations -> { - findNavController().navigate( - R.id.objectRelationListScreen, - bundleOf( - RelationListFragment.ARG_CTX to ctx, - RelationListFragment.ARG_TARGET to null, - RelationListFragment.ARG_LOCKED to isLocked, - RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST - ) - ) - } - ObjectMenuViewModelBase.Command.OpenSetCover -> { - findNavController().navigate( - R.id.objectSetCoverScreen, - bundleOf(SelectCoverObjectSetFragment.CTX_KEY to ctx) - ) - } - ObjectMenuViewModelBase.Command.OpenSetIcons -> { - findNavController().navigate( - R.id.objectSetIconPickerScreen, - bundleOf( - IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx, - ) - ) - } - ObjectMenuViewModelBase.Command.OpenSetLayout -> { - toast(COMING_SOON_MSG) - } - ObjectMenuViewModelBase.Command.OpenSetRelations -> { - toast(COMING_SOON_MSG) - } - ObjectMenuViewModelBase.Command.OpenLinkToChooser -> { - val fr = MoveToFragment.new( - ctx = ctx, - blocks = emptyList(), - restorePosition = null, - restoreBlock = null, - title = getString(R.string.link_to) - ) - fr.show(childFragmentManager, null) - } - is ObjectMenuViewModelBase.Command.OpenSnackbar -> { - binding.root.postDelayed({ - dialog?.window - ?.decorView - ?.showActionableSnackBar( - command.currentObjectName, - command.targetObjectName, - command.icon, - binding.anchor - ) { - vm.proceedWithOpeningPage(command.id) - } - }, 300L) - } + ObjectMenuViewModelBase.Command.OpenObjectCover -> openObjectCover() + ObjectMenuViewModelBase.Command.OpenObjectIcons -> openObjectIcons() + ObjectMenuViewModelBase.Command.OpenObjectLayout -> openObjectLayout() + ObjectMenuViewModelBase.Command.OpenObjectRelations -> openObjectRelations() + ObjectMenuViewModelBase.Command.OpenSetCover -> openSetCover() + ObjectMenuViewModelBase.Command.OpenSetIcons -> openSetIcons() + ObjectMenuViewModelBase.Command.OpenSetLayout -> toast(COMING_SOON_MSG) + ObjectMenuViewModelBase.Command.OpenSetRelations -> toast(COMING_SOON_MSG) + ObjectMenuViewModelBase.Command.OpenLinkToChooser -> openLinkChooser(command) + is ObjectMenuViewModelBase.Command.OpenSnackbar -> openSnackbar(command) + is ObjectMenuViewModelBase.Command.ShareDebugTree -> shareFile(command.uri) } } + private fun openObjectCover() { + findNavController().navigate( + R.id.objectCoverScreen, + bundleOf(SelectCoverObjectFragment.CTX_KEY to ctx) + ) + } + + private fun openObjectIcons() { + findNavController().navigate( + R.id.objectIconPickerScreen, + bundleOf( + IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx, + ) + ) + } + + private fun openObjectLayout() { + val fr = ObjectLayoutFragment.new(ctx) + fr.show(childFragmentManager, null) + } + + private fun openObjectRelations() { + findNavController().navigate( + R.id.objectRelationListScreen, + bundleOf( + RelationListFragment.ARG_CTX to ctx, + RelationListFragment.ARG_TARGET to null, + RelationListFragment.ARG_LOCKED to isLocked, + RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST + ) + ) + } + + private fun openSetCover() { + findNavController().navigate( + R.id.objectSetCoverScreen, + bundleOf(SelectCoverObjectSetFragment.CTX_KEY to ctx) + ) + } + + + private fun openSetIcons() { + findNavController().navigate( + R.id.objectSetIconPickerScreen, + bundleOf( + IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx, + ) + ) + } + + + private fun openLinkChooser(command: ObjectMenuViewModelBase.Command) { + val fr = MoveToFragment.new( + ctx = ctx, + blocks = emptyList(), + restorePosition = null, + restoreBlock = null, + title = getString(R.string.link_to) + ) + fr.show(childFragmentManager, null) + } + + private fun openSnackbar(command: ObjectMenuViewModelBase.Command.OpenSnackbar) { + binding.root.postDelayed({ + dialog?.window + ?.decorView + ?.showActionableSnackBar( + command.currentObjectName, + command.targetObjectName, + command.icon, + binding.anchor + ) { + vm.proceedWithOpeningPage(command.id) + } + }, 300L) + } + + override fun onMoveTo( target: Id, blocks: List, @@ -243,4 +243,6 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment { factory } private val onAccountAndDataClicked = { @@ -46,6 +52,10 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() { vm.onOptionClicked(Event.OnAppearanceClicked) } + private val onDebugClicked = { + vm.onOptionClicked(Event.OnDebugClicked) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -59,7 +69,9 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() { onAccountAndDataClicked = onAccountAndDataClicked, onAboutAppClicked = onAboutAppClicked, onAppearanceClicked = onAppearanceClicked, - onPersonalizationClicked = onPersonalizationClicked + onDebugClicked = onDebugClicked, + onPersonalizationClicked = onPersonalizationClicked, + showDebugMenu = featureToggles.isDebug ) } } @@ -89,12 +101,16 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() { Command.OpenPersonalizationScreen -> { findNavController().navigate(R.id.actionOpenPersonalizationScreen) } + Command.OpenDebugScreen -> { + startActivity(Intent(requireActivity(), SettingsActivity::class.java)) + } } } override fun injectDependencies() { componentManager().mainSettingsComponent.get().inject(this) } + override fun releaseDependencies() { componentManager().mainSettingsComponent.release() } diff --git a/app/src/main/res/layout/fragment_object_menu.xml b/app/src/main/res/layout/fragment_object_menu.xml index bb59fc08fb..6a2cbd7c50 100644 --- a/app/src/main/res/layout/fragment_object_menu.xml +++ b/app/src/main/res/layout/fragment_object_menu.xml @@ -137,6 +137,29 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/optionHistory" /> + + + + + app:layout_constraintTop_toBottomOf="@+id/objectDiagnosticsDivider"> List of related objects All version of object History + Object Debug + Diagnostics Provide name for new object Please give us a moment. We’re almost there… Soon diff --git a/core-ui/build.gradle b/core-ui/build.gradle index e398ab310d..bddd5f2895 100644 --- a/core-ui/build.gradle +++ b/core-ui/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation applicationDependencies.kotlin implementation applicationDependencies.coroutinesAndroid implementation applicationDependencies.androidxCore + implementation applicationDependencies.lifecycleRuntime implementation applicationDependencies.design implementation applicationDependencies.recyclerView 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 a5ed417678..33ca21baca 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 @@ -1,16 +1,27 @@ package com.anytypeio.anytype.core_ui.reactive import android.os.Looper +import android.os.Parcelable import android.text.Editable import android.text.TextWatcher import android.view.MotionEvent import android.view.View import android.widget.EditText import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.anytypeio.anytype.core_utils.ext.throttleFirst +import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment +import com.anytypeio.anytype.core_utils.ui.BaseFragment +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach fun View.clicks(): Flow = callbackFlow { checkMainThread() @@ -42,20 +53,21 @@ fun EditText.focusChanges(): Flow = callbackFlow { }.conflate() -fun View.touches(handled: (MotionEvent) -> Boolean = { true }): Flow = callbackFlow { - checkMainThread() - val listener = View.OnTouchListener { _, event -> - performClick() - if (handled(event)) { - trySend(event) - true - } else { - false +fun View.touches(handled: (MotionEvent) -> Boolean = { true }): Flow = + callbackFlow { + checkMainThread() + val listener = View.OnTouchListener { _, event -> + performClick() + if (handled(event)) { + trySend(event) + true + } else { + false + } } - } - setOnTouchListener(listener) - awaitClose { setOnTouchListener(null) } -}.conflate() + setOnTouchListener(listener) + awaitClose { setOnTouchListener(null) } + }.conflate() fun EditText.afterTextChanges(): Flow = callbackFlow { checkMainThread() @@ -95,4 +107,32 @@ fun TextView.editorActionEvents( fun checkMainThread() = check(Looper.myLooper() == Looper.getMainLooper()) { "Expected to be called on the main thread but was " + Thread.currentThread().name +} + +fun BaseFragment<*>.click( + view: View, + action: () -> Unit +) { + jobs += view.clicks() + .throttleFirst() + .onEach { action() } + .launchIn(lifecycleScope) +} + +fun BaseBottomSheetFragment<*>.click( + view: View, + action: () -> Unit +) { + jobs += view.clicks() + .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-ui/src/main/res/drawable/ic_object_menu_diagnostics.xml b/core-ui/src/main/res/drawable/ic_object_menu_diagnostics.xml new file mode 100644 index 0000000000..64c5af7fc8 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_object_menu_diagnostics.xml @@ -0,0 +1,16 @@ + + + + diff --git a/core-ui/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml index 3ee84031db..bff9eba07d 100644 --- a/core-ui/src/main/res/values/colors.xml +++ b/core-ui/src/main/res/values/colors.xml @@ -21,6 +21,8 @@ #ECD91B #DFDDD0 + #0FC8BA + #2C2B27 #ACA996 diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt index e287b412d0..6acac1be11 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.core_utils.ext import android.Manifest import android.app.Activity +import android.content.ActivityNotFoundException import android.content.ClipboardManager import android.content.Context import android.content.Intent @@ -353,4 +354,23 @@ fun NavController.safeNavigate( if (currentDestinationId == currentDestination?.id) { navigate(id, args) } +} + +fun Fragment.shareFile(uri: Uri) { + try { + val shareIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + putExtra(Intent.EXTRA_STREAM, uri) + type = requireContext().contentResolver.getType(uri) + } + startActivity(shareIntent) + } catch (e: Exception) { + if (e is ActivityNotFoundException) { + toast("No application found to open the selected file") + } else { + toast("Could not open file: ${e.message}") + } + Timber.e(e, "Error while opening file") + } } \ 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 ee773d31a1..2cdfa1b54f 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 @@ -21,7 +21,7 @@ abstract class BaseBottomSheetFragment( val sheet: FrameLayout? get() = dialog?.findViewById(BOTTOM_SHEET_ID) - protected val jobs = mutableListOf() + val jobs = mutableListOf() override fun onCreateView( inflater: LayoutInflater, 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 b9a93a6b62..60f75638d6 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 @@ -25,7 +25,7 @@ abstract class BaseFragment( val binding: T get() = _binding!! val hasBinding get() = _binding != null - protected val jobs = mutableListOf() + val jobs = mutableListOf() abstract fun injectDependencies() abstract fun releaseDependencies() diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 1707d91724..701e2f85aa 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -581,7 +581,7 @@ class MiddlewareServiceImplementation @Inject constructor( } override fun debugTree(request: Rpc.Debug.Tree.Request): Rpc.Debug.Tree.Response { - val encoded = Service.debugSync(Rpc.Debug.Tree.Request.ADAPTER.encode(request)) + val encoded = Service.debugTree(Rpc.Debug.Tree.Request.ADAPTER.encode(request)) val response = Rpc.Debug.Tree.Response.ADAPTER.decode(encoded) val error = response.error if (error != null && error.code != Rpc.Debug.Tree.Response.Error.Code.NULL) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt index 99b9104d73..13e3c1c7a6 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt @@ -51,6 +51,7 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsReorderBlockEve import com.anytypeio.anytype.presentation.extension.sendAnalyticsSplitBlockEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsUndoEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsUploadMediaEvent +import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader import timber.log.Timber @@ -68,7 +69,7 @@ class Orchestrator( private val turnIntoStyle: TurnIntoStyle, private val updateCheckbox: UpdateCheckbox, private val downloadFile: DownloadFile, - private val middlewareShareDownloader: MiddlewareShareDownloader, + private val documentFileShareDownloader: DocumentFileShareDownloader, val updateText: UpdateText, private val updateAlignment: UpdateAlignment, private val uploadBlock: UploadBlock, @@ -442,7 +443,7 @@ class Orchestrator( ) } is Intent.Media.ShareFile -> { - middlewareShareDownloader.execute( + documentFileShareDownloader.execute( params = MiddlewareShareDownloader.Params( hash = intent.hash, name = intent.name diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt index 249616353a..f7350755f9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt @@ -10,6 +10,7 @@ interface ObjectMenuOptionsProvider { val hasCover: Boolean, val hasLayout: Boolean, val hasRelations: Boolean, + val hasDiagnosticsVisibility: Boolean, ) { val hasHistory: Boolean = false companion object { @@ -18,6 +19,7 @@ interface ObjectMenuOptionsProvider { hasCover = true, hasLayout = true, hasRelations = true, + hasDiagnosticsVisibility = true ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt index 36dad18281..8e0b12b704 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt @@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction +import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider.Options import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -15,6 +16,7 @@ import timber.log.Timber class ObjectMenuOptionsProviderImpl( private val details: Flow>, private val restrictions: Flow>, + private val featureToggles: FeatureToggles, ) : ObjectMenuOptionsProvider { private fun observeLayout(ctx: Id): Flow = details @@ -58,12 +60,14 @@ class ObjectMenuOptionsProviderImpl( hasIcon = hasIcon, hasCover = hasCover, hasLayout = hasLayout, + hasDiagnosticsVisibility = featureToggles.isDebug, ) ObjectType.Layout.TODO -> Options( hasIcon = false, hasCover = hasCover, hasLayout = hasLayout, hasRelations = true, + hasDiagnosticsVisibility = featureToggles.isDebug, ) ObjectType.Layout.NOTE -> Options( @@ -71,6 +75,7 @@ class ObjectMenuOptionsProviderImpl( hasCover = false, hasLayout = hasLayout, hasRelations = true, + hasDiagnosticsVisibility = featureToggles.isDebug, ) } } else { @@ -79,6 +84,7 @@ class ObjectMenuOptionsProviderImpl( hasIcon = hasIcon, hasCover = hasCover, hasLayout = hasLayout, + hasDiagnosticsVisibility = featureToggles.isDebug, ) } return options diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt index 2d7a6f2366..537385369b 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.domain.`object`.DuplicateObject +import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.block.interactor.UpdateFields import com.anytypeio.anytype.domain.dashboard.interactor.AddToFavorite import com.anytypeio.anytype.domain.dashboard.interactor.RemoveFromFavorite @@ -22,6 +23,8 @@ import com.anytypeio.anytype.presentation.common.Delegator import com.anytypeio.anytype.presentation.editor.Editor import com.anytypeio.anytype.presentation.objects.ObjectAction import com.anytypeio.anytype.presentation.util.Dispatcher +import com.anytypeio.anytype.presentation.util.downloader.DebugTreeShareDownloader +import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader import kotlinx.coroutines.launch import timber.log.Timber @@ -35,6 +38,7 @@ class ObjectMenuViewModel( dispatcher: Dispatcher, menuOptionsProvider: ObjectMenuOptionsProvider, duplicateObject: DuplicateObject, + private val debugTreeShareDownloader: DebugTreeShareDownloader, private val storage: Editor.Storage, private val analytics: Analytics, private val updateFields: UpdateFields @@ -88,6 +92,30 @@ class ObjectMenuViewModel( add(ObjectAction.SEARCH_ON_PAGE) } + override fun onDiagnosticsClicked(ctx: Id) { + jobs += viewModelScope.launch { + debugTreeShareDownloader.stream( + MiddlewareShareDownloader.Params(hash = ctx, name = "$ctx.zip") + ).collect { result -> + result.fold( + onSuccess = { uri -> + commands.emit(Command.ShareDebugTree(uri)) + }, + onLoading = { + sendToast( + "Do not go away from this menu and don't turn the screen off. " + + "Tree diagnostic is started to collect." + ) + }, + onFailure = { + sendToast("Error while collecting tree diagnostics") + Timber.e(it, "Error while adding link from object to object") + } + ) + } + } + } + override fun onIconClicked(ctx: Id) { viewModelScope.launch { if (objectRestrictions.contains(ObjectRestriction.DETAILS)) { @@ -255,6 +283,7 @@ class ObjectMenuViewModel( class Factory( private val setObjectIsArchived: SetObjectIsArchived, private val duplicateObject: DuplicateObject, + private val debugTreeShareDownloader: DebugTreeShareDownloader, private val addToFavorite: AddToFavorite, private val removeFromFavorite: RemoveFromFavorite, private val addBackLinkToObject: AddBackLinkToObject, @@ -270,6 +299,7 @@ class ObjectMenuViewModel( return ObjectMenuViewModel( setObjectIsArchived = setObjectIsArchived, duplicateObject = duplicateObject, + debugTreeShareDownloader = debugTreeShareDownloader, addToFavorite = addToFavorite, removeFromFavorite = removeFromFavorite, addBackLinkToObject = addBackLinkToObject, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt index 7c01b1b63f..9beccf4aa4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.presentation.objects.menu +import android.net.Uri import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.Id @@ -42,7 +43,7 @@ abstract class ObjectMenuViewModelBase( private val duplicateObject: DuplicateObject ) : BaseViewModel() { - private val jobs = mutableListOf() + protected val jobs = mutableListOf() val isDismissed = MutableStateFlow(false) val isObjectArchived = MutableStateFlow(false) val commands = MutableSharedFlow(replay = 0) @@ -54,6 +55,7 @@ abstract class ObjectMenuViewModelBase( hasCover = false, hasLayout = false, hasRelations = false, + hasDiagnosticsVisibility = false ) ) val options: Flow = _options @@ -100,7 +102,7 @@ abstract class ObjectMenuViewModelBase( ): List protected fun proceedWithRemovingFromFavorites(ctx: Id) { - viewModelScope.launch { + jobs += viewModelScope.launch { removeFromFavorite( RemoveFromFavorite.Params( target = ctx @@ -119,7 +121,7 @@ abstract class ObjectMenuViewModelBase( } protected fun proceedWithAddingToFavorites(ctx: Id) { - viewModelScope.launch { + jobs += viewModelScope.launch { addToFavorite( AddToFavorite.Params( target = ctx @@ -138,7 +140,7 @@ abstract class ObjectMenuViewModelBase( } fun proceedWithUpdatingArchivedStatus(ctx: Id, isArchived: Boolean) { - viewModelScope.launch { + jobs += viewModelScope.launch { setObjectIsArchived( SetObjectIsArchived.Params( context = ctx, @@ -169,11 +171,14 @@ abstract class ObjectMenuViewModelBase( ).fold( onSuccess = { obj -> sendAnalyticsObjectLinkToEvent(analytics) - commands.emit(Command.OpenSnackbar( - id = addTo, - currentObjectName = fromName, - targetObjectName = obj.getProperName(), - icon = ObjectIcon.from(obj, obj.layout, urlBuilder))) + commands.emit( + Command.OpenSnackbar( + id = addTo, + currentObjectName = fromName, + targetObjectName = obj.getProperName(), + icon = ObjectIcon.from(obj, obj.layout, urlBuilder) + ) + ) }, onFailure = { Timber.e(it, "Error while adding link from object to object") @@ -207,6 +212,10 @@ abstract class ObjectMenuViewModelBase( } } + open fun onDiagnosticsClicked(ctx: Id) { + throw IllegalStateException("You should not call diagnostics for sets") + } + sealed class Command { object OpenObjectIcons : Command() object OpenSetIcons : Command() @@ -217,7 +226,8 @@ abstract class ObjectMenuViewModelBase( object OpenObjectRelations : Command() object OpenSetRelations : Command() object OpenLinkToChooser : Command() - class OpenSnackbar( + data class ShareDebugTree(val uri: Uri) : Command() + data class OpenSnackbar( val id: Id, val currentObjectName: String?, val targetObjectName: String?, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt index 1537251908..86d580a80f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt @@ -38,6 +38,7 @@ class MainSettingsViewModel( Event.OnAccountAndDataClicked -> commands.emit(Command.OpenAccountAndDataScreen) Event.OnAppearanceClicked -> commands.emit(Command.OpenAppearanceScreen) Event.OnPersonalizationClicked -> commands.emit(Command.OpenPersonalizationScreen) + Event.OnDebugClicked -> commands.emit(Command.OpenDebugScreen) } } @@ -67,6 +68,7 @@ class MainSettingsViewModel( eventName = EventsDictionary.personalisationSettingsShow ) } + Event.OnDebugClicked -> {} } } @@ -83,15 +85,17 @@ class MainSettingsViewModel( sealed class Event { object OnAboutClicked : Event() - object OnAppearanceClicked: Event() - object OnAccountAndDataClicked: Event() - object OnPersonalizationClicked: Event() + object OnAppearanceClicked : Event() + object OnAccountAndDataClicked : Event() + object OnPersonalizationClicked : Event() + object OnDebugClicked : Event() } sealed class Command { object OpenAboutScreen : Command() - object OpenAppearanceScreen: Command() - object OpenAccountAndDataScreen: Command() - object OpenPersonalizationScreen: Command() + object OpenAppearanceScreen : Command() + object OpenAccountAndDataScreen : Command() + object OpenPersonalizationScreen : Command() + object OpenDebugScreen : Command() } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DebugTreeShareDownloader.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DebugTreeShareDownloader.kt new file mode 100644 index 0000000000..673b891d4e --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DebugTreeShareDownloader.kt @@ -0,0 +1,14 @@ +package com.anytypeio.anytype.presentation.util.downloader + +import android.content.Context +import com.anytypeio.anytype.domain.block.repo.BlockRepository + +class DebugTreeShareDownloader( + private val repo: BlockRepository, + context: Context, + uriFileProvider: UriFileProvider +) : MiddlewareShareDownloader(context, uriFileProvider) { + + override suspend fun downloadFile(hash: String, path: String) = + repo.debugTree(objectId = hash, path = path) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DocumentFileShareDownloader.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DocumentFileShareDownloader.kt new file mode 100644 index 0000000000..a94d86c888 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/DocumentFileShareDownloader.kt @@ -0,0 +1,16 @@ +package com.anytypeio.anytype.presentation.util.downloader + +import android.content.Context +import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.domain.block.repo.BlockRepository + +class DocumentFileShareDownloader( + private val repo: BlockRepository, + context: Context, + uriFileProvider: UriFileProvider +) : MiddlewareShareDownloader(context, uriFileProvider) { + + override suspend fun downloadFile(hash: String, path: String) = repo.downloadFile( + Command.DownloadFile(hash = hash, path = path) + ) +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt index edb520f6cb..0bc7f9ba0c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt @@ -2,17 +2,11 @@ package com.anytypeio.anytype.presentation.util.downloader import android.content.Context import android.net.Uri -import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Hash -import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.ResultInteractor -import com.anytypeio.anytype.domain.block.repo.BlockRepository -import kotlinx.coroutines.withContext import java.io.File -class MiddlewareShareDownloader( - private val repo: BlockRepository, - private val dispatchers: AppCoroutineDispatchers, +abstract class MiddlewareShareDownloader( private val context: Context, private val uriFileProvider: UriFileProvider ) : ResultInteractor() { @@ -22,7 +16,9 @@ class MiddlewareShareDownloader( val name: String ) - override suspend fun doWork(params: Params) = withContext(dispatchers.io) { + abstract suspend fun downloadFile(hash: String, path: String): String + + override suspend fun doWork(params: Params): Uri { val cacheDir = context.cacheDir require(cacheDir != null) { "Impossible to cache files!" } @@ -38,17 +34,11 @@ class MiddlewareShareDownloader( if (tempDir.exists()) tempDir.deleteRecursively() tempDir.mkdirs() - val tempResult = File( - repo.downloadFile( - Command.DownloadFile( - hash = params.hash, - path = tempFileFolderPath - ) - ) - ) + val tempResult = File(downloadFile(params.hash, tempFileFolderPath)) tempResult.renameTo(resultFile) } - uriFileProvider.getUriForFile(resultFile) + return uriFileProvider.getUriForFile(resultFile) } -} \ No newline at end of file +} + diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt index c83b8663d3..961e6399f2 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.core_models.ext.getChildrenIdsList import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.auth.interactor.GetProfile import com.anytypeio.anytype.domain.base.Either +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.config.DebugSettings import com.anytypeio.anytype.domain.config.Gateway @@ -35,7 +36,6 @@ import com.anytypeio.anytype.presentation.MockBlockFactory.link import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.jraska.livedata.test import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -372,7 +372,7 @@ class HomeDashboardViewModelTest { private fun givenDelegateId(id: String) { createNewObject.stub { - onBlocking { execute(Unit) } doAnswer ValueClassAnswer(id) + onBlocking { execute(Unit) } doReturn Resultat.success(id) } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt index ab5dbe6655..373c9408f7 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt @@ -25,6 +25,7 @@ import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.UpdateDivider import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.ClearBlockStyle @@ -117,9 +118,9 @@ import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.util.TXT +import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.jraska.livedata.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -233,7 +234,7 @@ open class EditorViewModelTest { lateinit var downloadFile: DownloadFile @Mock - lateinit var middlewareShareDownloader: MiddlewareShareDownloader + lateinit var documentFileShareDownloader: DocumentFileShareDownloader @Mock lateinit var uploadBlock: UploadBlock @@ -2665,7 +2666,7 @@ open class EditorViewModelTest { vm.startSharingFile(id = file.id) runTest { - verify(middlewareShareDownloader, times(1)).execute( + verify(documentFileShareDownloader, times(1)).execute( params = eq( MiddlewareShareDownloader.Params( name = file.content().name.orEmpty(), @@ -3743,7 +3744,7 @@ open class EditorViewModelTest { objectRestrictions: List = emptyList() ) { openPage.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer( + onBlocking { execute(any()) } doReturn Resultat.success( Result.Success( Payload( context = root, @@ -3768,7 +3769,7 @@ open class EditorViewModelTest { ) { closePage.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer(Unit) + onBlocking { execute(any()) } doReturn Resultat.success(Unit) } exception?.let { @@ -3797,7 +3798,7 @@ open class EditorViewModelTest { events: List = emptyList() ) { openPage.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer( + onBlocking { execute(any()) } doReturn Resultat.success( Result.Success( Payload( context = context, @@ -3857,7 +3858,7 @@ open class EditorViewModelTest { private fun stubCreateBlock(root: String) { createBlock.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer( + onBlocking { execute(any()) } doReturn Resultat.success( Pair( MockDataFactory.randomString(), Payload( context = root, @@ -3881,8 +3882,8 @@ open class EditorViewModelTest { } private fun givenSharedFile() { - middlewareShareDownloader.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer(Uri.EMPTY) + documentFileShareDownloader.stub { + onBlocking { execute(any()) } doReturn Resultat.success(Uri.EMPTY) } } @@ -3967,7 +3968,7 @@ open class EditorViewModelTest { updateTextColor = updateTextColor, duplicateBlock = duplicateBlock, downloadFile = downloadFile, - middlewareShareDownloader = middlewareShareDownloader, + documentFileShareDownloader = documentFileShareDownloader, undo = undo, redo = redo, updateText = updateText, @@ -4523,7 +4524,7 @@ open class EditorViewModelTest { private fun givenDelegateId(id: String) { createNewObject.stub { - onBlocking { execute(Unit) } doAnswer ValueClassAnswer(id) + onBlocking { execute(Unit) } doReturn Resultat.success(id) } } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt index 78593a4579..3b522e55b9 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorMentionTest.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.ext.content import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider import com.anytypeio.anytype.domain.page.CreateNewDocument @@ -23,7 +24,6 @@ import com.anytypeio.anytype.presentation.editor.render.parseThemeBackgroundColo import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.presentation.util.TXT import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.jraska.livedata.test import kotlinx.coroutines.runBlocking import net.lachlanmckee.timberjunit.TimberTestRule @@ -875,29 +875,28 @@ class EditorMentionTest : EditorPresentationTestSetup() { val params = InterceptEvents.Params(context = root) openPage.stub { - onBlocking { execute(any()) } doAnswer - ValueClassAnswer( - Result.Success( - Payload( + onBlocking { execute(any()) } doReturn Resultat.success( + Result.Success( + Payload( + context = root, + events = listOf( + Event.Command.ShowObject( context = root, - events = listOf( - Event.Command.ShowObject( - context = root, - root = root, - details = Block.Details(), - relations = emptyList(), - blocks = document, - objectRestrictions = emptyList() - ), - Event.Command.Details.Amend( - context = root, - target = mentionTarget, - details = mapOf(Block.Fields.NAME_KEY to "Foob") - ) - ) + root = root, + details = Block.Details(), + relations = emptyList(), + blocks = document, + objectRestrictions = emptyList() + ), + Event.Command.Details.Amend( + context = root, + target = mentionTarget, + details = mapOf(Block.Fields.NAME_KEY to "Foob") ) ) ) + ) + ) } stubInterceptEvents() stubSearchObjects() @@ -1023,29 +1022,28 @@ class EditorMentionTest : EditorPresentationTestSetup() { val params = InterceptEvents.Params(context = root) openPage.stub { - onBlocking { execute(any()) } doAnswer - ValueClassAnswer( - Result.Success( - Payload( + onBlocking { execute(any()) } doReturn Resultat.success( + Result.Success( + Payload( + context = root, + events = listOf( + Event.Command.ShowObject( context = root, - events = listOf( - Event.Command.ShowObject( - context = root, - root = root, - details = Block.Details(), - relations = emptyList(), - blocks = document, - objectRestrictions = emptyList() - ), - Event.Command.Details.Amend( - context = root, - target = mentionTarget, - details = mapOf(Block.Fields.NAME_KEY to "") - ) - ) + root = root, + details = Block.Details(), + relations = emptyList(), + blocks = document, + objectRestrictions = emptyList() + ), + Event.Command.Details.Amend( + context = root, + target = mentionTarget, + details = mapOf(Block.Fields.NAME_KEY to "") ) ) ) + ) + ) } stubInterceptEvents() stubSearchObjects() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt index 7c0376a67d..7e82898c02 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt @@ -14,6 +14,7 @@ import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.UpdateDivider import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent import com.anytypeio.anytype.domain.block.interactor.ClearBlockStyle @@ -99,9 +100,8 @@ import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher -import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader +import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf @@ -183,7 +183,7 @@ open class EditorPresentationTestSetup { lateinit var downloadFile: DownloadFile @Mock - lateinit var middlewareShareDownloader: MiddlewareShareDownloader + lateinit var documentFileShareDownloader: DocumentFileShareDownloader @Mock lateinit var uploadBlock: UploadBlock @@ -296,22 +296,31 @@ open class EditorPresentationTestSetup { @Mock lateinit var createTableColumn: CreateTableColumn + @Mock lateinit var createTableRow: CreateTableRow + @Mock lateinit var deleteTableColumn: DeleteTableColumn + @Mock lateinit var deleteTableRow: DeleteTableRow + @Mock lateinit var duplicateTableRow: DuplicateTableRow + @Mock lateinit var duplicateTableColumn: DuplicateTableColumn + @Mock lateinit var fillTableColumn: FillTableColumn + @Mock lateinit var moveTableRow: MoveTableRow + @Mock lateinit var moveTableColumn: MoveTableColumn + @Mock lateinit var setTableRowHeader: SetTableRowHeader @@ -347,7 +356,7 @@ open class EditorPresentationTestSetup { updateTextColor = updateTextColor, duplicateBlock = duplicateBlock, downloadFile = downloadFile, - middlewareShareDownloader = middlewareShareDownloader, + documentFileShareDownloader = documentFileShareDownloader, undo = undo, redo = redo, updateText = updateText, @@ -445,25 +454,24 @@ open class EditorPresentationTestSetup { relationLinks: List = emptyList() ) { openPage.stub { - onBlocking { execute(any()) } doAnswer - ValueClassAnswer( - Result.Success( - Payload( + onBlocking { execute(any()) } doReturn Resultat.success( + Result.Success( + Payload( + context = root, + events = listOf( + Event.Command.ShowObject( context = root, - events = listOf( - Event.Command.ShowObject( - context = root, - root = root, - details = details, - relations = relations, - relationLinks = relationLinks, - blocks = document, - objectRestrictions = objectRestrictions - ) - ) + root = root, + details = details, + relations = relations, + relationLinks = relationLinks, + blocks = document, + objectRestrictions = objectRestrictions ) ) ) + ) + ) } } @@ -547,15 +555,14 @@ open class EditorPresentationTestSetup { fun stubCreateBlock(root: String) { createBlock.stub { - onBlocking { execute(any()) } doAnswer - ValueClassAnswer( - Pair( - MockDataFactory.randomString(), Payload( - context = root, - events = listOf() - ) - ) + onBlocking { execute(any()) } doReturn Resultat.success( + Pair( + MockDataFactory.randomString(), Payload( + context = root, + events = listOf() ) + ) + ) } } @@ -620,7 +627,7 @@ open class EditorPresentationTestSetup { fun stubClosePage() { closePage.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer(Unit) + onBlocking { execute(any()) } doReturn Resultat.success(Unit) } } @@ -671,7 +678,7 @@ open class EditorPresentationTestSetup { } } - fun stubSearchObjects(objects : List = emptyList()) { + fun stubSearchObjects(objects: List = emptyList()) { searchObjects.stub { onBlocking { invoke(any()) } doReturn Either.Right(objects) } @@ -679,7 +686,7 @@ open class EditorPresentationTestSetup { fun stubGetDefaultObjectType(type: String? = null, name: String? = null) { getDefaultEditorType.stub { - onBlocking { execute(Unit) } doAnswer ValueClassAnswer( + onBlocking { execute(Unit) } doReturn Resultat.success( GetDefaultEditorType.Response( type, name diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt index 2490b8df66..576d5bfb0f 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorTitleAddBlockTest.kt @@ -5,12 +5,12 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.domain.base.Either +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.CreateBlock import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.page.CreateDocument import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import org.junit.Before import org.junit.Rule import org.junit.Test @@ -541,16 +541,15 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() { params: CreateBlock.Params ) { createBlock.stub { - onBlocking { execute(params) } doAnswer - ValueClassAnswer( - Pair( - MockDataFactory.randomUuid(), - Payload( - context = root, - events = emptyList() - ) - ) + onBlocking { execute(params) } doReturn Resultat.success( + Pair( + MockDataFactory.randomUuid(), + Payload( + context = root, + events = emptyList() ) + ) + ) } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt index 682ab97e7f..42680d4fc9 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test +import org.mockito.kotlin.mock import kotlin.test.assertEquals @ExperimentalCoroutinesApi @@ -18,7 +19,7 @@ class ObjectMenuOptionsProviderImplTest { private val objectId: String = "objectId" private val details = MutableStateFlow>(mapOf()) private val restrictions = MutableStateFlow>(emptyList()) - private val provider = ObjectMenuOptionsProviderImpl(details, restrictions) + private val provider = ObjectMenuOptionsProviderImpl(details, restrictions, mock()) @Test fun `when layout note - options are layout, relations, history`() { @@ -30,6 +31,7 @@ class ObjectMenuOptionsProviderImplTest { hasCover = false, hasLayout = true, hasRelations = true, + hasDiagnosticsVisibility = false ) assertOptions( @@ -47,6 +49,7 @@ class ObjectMenuOptionsProviderImplTest { hasCover = true, hasLayout = true, hasRelations = true, + hasDiagnosticsVisibility = false ) assertOptions( @@ -61,7 +64,7 @@ class ObjectMenuOptionsProviderImplTest { ) assertOptions( - expected = ObjectMenuOptionsProvider.Options.ALL + expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasDiagnosticsVisibility = false) ) } @@ -73,7 +76,7 @@ class ObjectMenuOptionsProviderImplTest { ) assertOptions( - expected = ObjectMenuOptionsProvider.Options.ALL + expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasDiagnosticsVisibility = false) ) } @@ -85,7 +88,10 @@ class ObjectMenuOptionsProviderImplTest { restrictions.value = listOf(ObjectRestriction.LAYOUT_CHANGE) assertOptions( - expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasLayout = false) + expected = ObjectMenuOptionsProvider.Options.ALL.copy( + hasLayout = false, + hasDiagnosticsVisibility = false + ) ) } @@ -102,6 +108,7 @@ class ObjectMenuOptionsProviderImplTest { hasCover = false, hasLayout = false, hasRelations = true, + hasDiagnosticsVisibility = false ) ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt index 074c4641ea..9e9d62e18f 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetNavigationTest.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.SearchResult import com.anytypeio.anytype.core_models.StubRelationObject import com.anytypeio.anytype.core_models.StubTitle import com.anytypeio.anytype.core_models.ext.content +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.objects.SupportedLayouts import com.anytypeio.anytype.presentation.relations.ObjectSetConfig @@ -24,7 +25,6 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.sets.model.Viewer import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.jraska.livedata.test import kotlinx.coroutines.test.runTest import org.junit.After @@ -33,6 +33,7 @@ import org.junit.Rule import org.junit.Test import org.mockito.MockitoAnnotations import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn import org.mockito.kotlin.stub import org.mockito.kotlin.times import org.mockito.kotlin.verifyBlocking @@ -596,7 +597,7 @@ class ObjectSetNavigationTest : ObjectSetViewModelTestSetup() { private fun givenDelegateId(id: String) { createNewObject.stub { - onBlocking { execute(Unit) } doAnswer ValueClassAnswer(id) + onBlocking { execute(Unit) } doReturn Resultat.success(id) } } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt index 9657c8a2e4..891bdcdea6 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.base.Result +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.interactor.UpdateText import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.Gateway @@ -52,7 +53,6 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetSession import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf @@ -244,7 +244,7 @@ open class ObjectSetViewModelTestSetup { fun stubCloseBlock() { closeBlock.stub { - onBlocking { execute(any()) } doAnswer ValueClassAnswer(Unit) + onBlocking { execute(any()) } doReturn Resultat.success(Unit) } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt index d3bbeb03a2..975fba7df1 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.auth.model.AuthStatus import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.Either +import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.launch.SetDefaultEditorType @@ -19,7 +20,6 @@ import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.test_utils.MockDataFactory -import com.anytypeio.anytype.test_utils.ValueClassAnswer import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Before @@ -31,6 +31,7 @@ import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow import org.mockito.kotlin.eq import org.mockito.kotlin.stub import org.mockito.kotlin.times @@ -143,7 +144,7 @@ class SplashViewModelTest { stubLaunchAccount() stubGetLastOpenedObject() getDefaultEditorType.stub { - onBlocking { execute(Unit) } doAnswer ValueClassAnswer (Exception("error")) + onBlocking { execute(Unit) } doThrow Exception("error") } initViewModel() @@ -231,35 +232,37 @@ class SplashViewModelTest { } @Test - fun `should fallback to default object type if default object type contains deprecated prefix id`() = runTest { - stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED)) - stubLaunchWallet() - stubLaunchAccount() - stubGetLastOpenedObject() - stubGetDefaultObjectType(type = ObjectTypeIds.MARKETPLACE_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid()) + fun `should fallback to default object type if default object type contains deprecated prefix id`() = + runTest { + stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED)) + stubLaunchWallet() + stubLaunchAccount() + stubGetLastOpenedObject() + stubGetDefaultObjectType(type = ObjectTypeIds.MARKETPLACE_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid()) - initViewModel() + initViewModel() - verify(setDefaultEditorType, times(1)).invoke( - SetDefaultEditorType.Params( - SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.first, - SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.second + verify(setDefaultEditorType, times(1)).invoke( + SetDefaultEditorType.Params( + SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.first, + SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.second + ) ) - ) - } + } @Test - fun `should not fallback to default object type if default object type does not contain deprecated prefix id`() = runTest { - stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED)) - stubLaunchWallet() - stubLaunchAccount() - stubGetLastOpenedObject() - stubGetDefaultObjectType(type = ObjectTypeIds.DEFAULT_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid()) + fun `should not fallback to default object type if default object type does not contain deprecated prefix id`() = + runTest { + stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED)) + stubLaunchWallet() + stubLaunchAccount() + stubGetLastOpenedObject() + stubGetDefaultObjectType(type = ObjectTypeIds.DEFAULT_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid()) - initViewModel() + initViewModel() - verifyNoInteractions(setDefaultEditorType) - } + verifyNoInteractions(setDefaultEditorType) + } //Todo can't mock Amplitude // @Test @@ -361,7 +364,7 @@ class SplashViewModelTest { private fun stubGetDefaultObjectType(type: String? = null, name: String? = null) { getDefaultEditorType.stub { - onBlocking { execute(Unit) } doAnswer ValueClassAnswer( + onBlocking { execute(Unit) } doReturn Resultat.success( GetDefaultEditorType.Response( type, name diff --git a/test/utils/src/main/java/com/anytypeio/anytype/test_utils/ValueClassAnswer.kt b/test/utils/src/main/java/com/anytypeio/anytype/test_utils/ValueClassAnswer.kt deleted file mode 100644 index a9277275ae..0000000000 --- a/test/utils/src/main/java/com/anytypeio/anytype/test_utils/ValueClassAnswer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.anytypeio.anytype.test_utils - -import org.mockito.invocation.InvocationOnMock -import org.mockito.stubbing.Answer - -class ValueClassAnswer(private val value: Any) : Answer { - override fun answer(invocation: InvocationOnMock?): Any = value -} \ No newline at end of file diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt index 0244016837..b538d7223f 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt @@ -18,8 +18,10 @@ import com.anytypeio.anytype.ui_settings.R fun MainSettingScreen( onAccountAndDataClicked: () -> Unit, onAboutAppClicked: () -> Unit, + onDebugClicked: () -> Unit, onPersonalizationClicked: () -> Unit, - onAppearanceClicked: () -> Unit + onAppearanceClicked: () -> Unit, + showDebugMenu: Boolean ) { Column { Box( @@ -51,6 +53,14 @@ fun MainSettingScreen( onClick = onAboutAppClicked ) Divider() + if (showDebugMenu) { + Option( + image = R.drawable.ic_debug, + text = stringResource(R.string.debug), + onClick = onDebugClicked + ) + Divider() + } Box(modifier = Modifier.height(16.dp)) } } \ No newline at end of file diff --git a/ui-settings/src/main/res/drawable/ic_debug.xml b/ui-settings/src/main/res/drawable/ic_debug.xml new file mode 100644 index 0000000000..28f73b0afb --- /dev/null +++ b/ui-settings/src/main/res/drawable/ic_debug.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/ui-settings/src/main/res/values/strings.xml b/ui-settings/src/main/res/values/strings.xml index cb8b7e0c0e..5a555be6d8 100644 --- a/ui-settings/src/main/res/values/strings.xml +++ b/ui-settings/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ Recovery phrase Clear file cache Debug Sync Report + Debug Account Reset account Delete account