From 8076d126f0817c39282356fbe37545009f3ad7dd Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Mon, 25 Oct 2021 15:29:55 +0300 Subject: [PATCH] Editor | Feature | Note as main type (#1852) * update proto * add layout note * add title note * send default type on pageCreate * title can be null * fixes * test * drawables * object layout screen * footer adapter * mapping * get layouts use case * fix * update createPage use case * default page type on dashboard * default page type on editor * fix tests * notes layout logic * fix * hide on text change * fix * fix * fix test * fix * fix * fix test * fix * ci off * fix * pr fix * fix * fix * fixes * fix * fix * fix * fix test * ci off --- .../analytics/base/EventsDictionary.kt | 1 + .../anytype/di/feature/DashboardDi.kt | 13 +- .../anytypeio/anytype/di/feature/EditorDI.kt | 14 +- .../anytypeio/anytype/di/feature/SplashDi.kt | 21 +- .../anytype/di/feature/UserSettingsDI.kt | 18 +- .../actions/BlockActionToolbarFactory.kt | 1 + .../anytype/core_models/ext/BlockExt.kt | 11 +- .../core_ui/features/editor/BlockAdapter.kt | 10 + .../editor/holders/other/TitleNoteHolder.kt | 6 + .../main/res/layout/item_block_note_title.xml | 5 + .../auth/repo/block/BlockDataRepository.kt | 5 +- .../data/auth/repo/block/BlockDataStore.kt | 2 +- .../data/auth/repo/block/BlockRemote.kt | 2 +- .../auth/repo/block/BlockRemoteDataStore.kt | 5 +- .../domain/block/repo/BlockRepository.kt | 2 +- ...ultPageType.kt => GetDefaultEditorType.kt} | 4 +- ...ultPageType.kt => SetDefaultEditorType.kt} | 4 +- .../anytype/domain/page/CreatePage.kt | 12 +- .../anytype/domain/ext/BlockExtensionTest.kt | 12 +- .../middleware/block/BlockMiddleware.kt | 4 +- .../middleware/interactor/Middleware.kt | 3 +- .../dashboard/HomeDashboardViewModel.kt | 68 +++-- .../HomeDashboardViewModelFactory.kt | 3 + .../anytype/presentation/editor/Editor.kt | 1 - .../presentation/editor/EditorViewModel.kt | 109 ++++---- .../editor/EditorViewModelFactory.kt | 7 +- .../presentation/editor/editor/Store.kt | 9 - .../editor/editor/model/BlockView.kt | 18 ++ .../presentation/editor/editor/model/Types.kt | 1 + .../editor/render/DefaultBlockViewRenderer.kt | 9 + .../relations/ObjectSetRenderMapper.kt | 4 +- .../presentation/sets/ObjectSetViewState.kt | 2 +- .../settings/UserSettingsViewModel.kt | 20 +- .../presentation/splash/SplashViewModel.kt | 14 +- .../splash/SplashViewModelFactory.kt | 12 +- .../dashboard/DashboardTestSetup.kt | 5 + .../dashboard/HomeDashboardViewModelTest.kt | 15 +- .../editor/EditorViewModelTest.kt | 54 +--- .../editor/editor/EditorNoteLayoutTest.kt | 151 +++++++++++ .../EditorObjectTypeChangeWidgetTest.kt | 253 ++++++++++++++++++ .../editor/EditorPresentationTestSetup.kt | 13 +- .../splash/SplashViewModelTest.kt | 27 +- 42 files changed, 732 insertions(+), 218 deletions(-) create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/TitleNoteHolder.kt create mode 100644 core-ui/src/main/res/layout/item_block_note_title.xml rename domain/src/main/java/com/anytypeio/anytype/domain/launch/{GetDefaultPageType.kt => GetDefaultEditorType.kt} (82%) rename domain/src/main/java/com/anytypeio/anytype/domain/launch/{SetDefaultPageType.kt => SetDefaultEditorType.kt} (76%) create mode 100644 presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt create mode 100644 presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt diff --git a/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt b/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt index b62f5acb83..9881dc4bd6 100644 --- a/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt +++ b/analytics/src/main/java/com/anytypeio/anytype/analytics/base/EventsDictionary.kt @@ -60,6 +60,7 @@ object EventsDictionary { const val PROP_STYLE = "style" const val PROP_TYPE = "objectType" + const val PROP_IS_DRAFT = "isDraft" const val PROP_LAYOUT = "layout" const val PROP_ACCOUNT_ID = "accountId" const val PROP_RELATION_FORMAT = "relationFormat" diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt index a257c291c7..9dcb1b3276 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt @@ -10,11 +10,12 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.GetConfig import com.anytypeio.anytype.domain.config.GetDebugSettings import com.anytypeio.anytype.domain.config.InfrastructureRepository +import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.EventChannel import com.anytypeio.anytype.domain.event.interactor.InterceptEvents -import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived @@ -61,6 +62,7 @@ object HomeDashboardModule { getDebugSettings: GetDebugSettings, analytics: Analytics, searchObjects: SearchObjects, + getDefaultEditorType: GetDefaultEditorType, urlBuilder: UrlBuilder, setObjectListIsArchived: SetObjectListIsArchived, deleteObjects: DeleteObjects @@ -78,7 +80,8 @@ object HomeDashboardModule { analytics = analytics, urlBuilder = urlBuilder, setObjectListIsArchived = setObjectListIsArchived, - deleteObjects = deleteObjects + deleteObjects = deleteObjects, + getDefaultEditorType = getDefaultEditorType ) @JvmStatic @@ -178,6 +181,12 @@ object HomeDashboardModule { repo = repo ) + @JvmStatic + @Provides + @PerScreen + fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType = + GetDefaultEditorType(repo) + @JvmStatic @Provides @PerScreen 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 d2a7020327..ba325106ba 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 @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.clipboard.Clipboard import com.anytypeio.anytype.domain.clipboard.Copy import com.anytypeio.anytype.domain.clipboard.Paste +import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey @@ -28,6 +29,7 @@ import com.anytypeio.anytype.domain.download.Downloader import com.anytypeio.anytype.domain.event.interactor.EventChannel import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.page.* @@ -144,7 +146,8 @@ object EditorSessionModule { updateDetail: UpdateDetail, getCompatibleObjectTypes: GetCompatibleObjectTypes, objectTypesProvider: ObjectTypesProvider, - searchObjects: SearchObjects + searchObjects: SearchObjects, + getDefaultEditorType: GetDefaultEditorType ): EditorViewModelFactory = EditorViewModelFactory( openPage = openPage, closeObject = closePage, @@ -167,7 +170,8 @@ object EditorSessionModule { updateDetail = updateDetail, getCompatibleObjectTypes = getCompatibleObjectTypes, objectTypesProvider = objectTypesProvider, - searchObjects = searchObjects + searchObjects = searchObjects, + getDefaultEditorType = getDefaultEditorType ) @JvmStatic @@ -726,4 +730,10 @@ object EditorUseCaseModule { fun searchObjects( repo: BlockRepository ): SearchObjects = SearchObjects(repo = repo) + + @JvmStatic + @Provides + @PerScreen + fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType = + GetDefaultEditorType(repo) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt index 5601418c8b..be79f85724 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt @@ -13,11 +13,10 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.FlavourConfigProvider import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.device.PathProvider -import com.anytypeio.anytype.domain.launch.GetDefaultPageType -import com.anytypeio.anytype.domain.launch.SetDefaultPageType +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType +import com.anytypeio.anytype.domain.launch.SetDefaultEditorType import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory import com.anytypeio.anytype.ui.splash.SplashFragment -import com.squareup.wire.get import dagger.Module import dagger.Provides import dagger.Subcomponent @@ -54,8 +53,8 @@ object SplashModule { analytics: Analytics, storeObjectTypes: StoreObjectTypes, getLastOpenedObject: GetLastOpenedObject, - getDefaultPageType: GetDefaultPageType, - setDefaultPageType: SetDefaultPageType + getDefaultEditorType: GetDefaultEditorType, + setDefaultEditorType: SetDefaultEditorType ): SplashViewModelFactory = SplashViewModelFactory( checkAuthorizationStatus = checkAuthorizationStatus, launchAccount = launchAccount, @@ -63,8 +62,8 @@ object SplashModule { analytics = analytics, storeObjectTypes = storeObjectTypes, getLastOpenedObject = getLastOpenedObject, - setDefaultPageType = setDefaultPageType, - getDefaultPageType = getDefaultPageType + setDefaultEditorType = setDefaultEditorType, + getDefaultEditorType = getDefaultEditorType ) @JvmStatic @@ -123,12 +122,12 @@ object SplashModule { @JvmStatic @PerScreen @Provides - fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultPageType = - GetDefaultPageType(repo) + fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType = + GetDefaultEditorType(repo) @JvmStatic @PerScreen @Provides - fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultPageType = - SetDefaultPageType(repo) + fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultEditorType = + SetDefaultEditorType(repo) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/UserSettingsDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/UserSettingsDI.kt index 13a87a88e6..27b3d01ed5 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/UserSettingsDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/UserSettingsDI.kt @@ -3,8 +3,8 @@ package com.anytypeio.anytype.di.feature import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.domain.config.UserSettingsRepository -import com.anytypeio.anytype.domain.launch.GetDefaultPageType -import com.anytypeio.anytype.domain.launch.SetDefaultPageType +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType +import com.anytypeio.anytype.domain.launch.SetDefaultEditorType import com.anytypeio.anytype.presentation.settings.UserSettingsViewModel import com.anytypeio.anytype.ui.settings.UserSettingsFragment import dagger.Module @@ -30,22 +30,22 @@ object UserSettingsModule { @JvmStatic @PerScreen @Provides - fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultPageType = - GetDefaultPageType(repo) + fun provideGetDefaultPageType(repo: UserSettingsRepository): GetDefaultEditorType = + GetDefaultEditorType(repo) @JvmStatic @PerScreen @Provides - fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultPageType = - SetDefaultPageType(repo) + fun provideSetDefaultPageType(repo: UserSettingsRepository): SetDefaultEditorType = + SetDefaultEditorType(repo) @JvmStatic @Provides @PerScreen fun provideUserSettingsFabric( - getDefaultPageType: GetDefaultPageType, - setDefaultPageType: SetDefaultPageType, + getDefaultEditorType: GetDefaultEditorType, + setDefaultEditorType: SetDefaultEditorType, analytics: Analytics ): UserSettingsViewModel.Factory = - UserSettingsViewModel.Factory(getDefaultPageType, setDefaultPageType, analytics) + UserSettingsViewModel.Factory(getDefaultEditorType, setDefaultEditorType, analytics) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/actions/BlockActionToolbarFactory.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/actions/BlockActionToolbarFactory.kt index cebc9f8f40..b45907610f 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/actions/BlockActionToolbarFactory.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/modals/actions/BlockActionToolbarFactory.kt @@ -47,6 +47,7 @@ object BlockActionToolbarFactory { is BlockView.Description -> TODO() is BlockView.FeaturedRelation -> TODO() is BlockView.Unsupported -> TODO() + is BlockView.TitleNote -> TODO() } fun newInstance( diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/ext/BlockExt.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/ext/BlockExt.kt index 199c6d7800..39bb8abc60 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/ext/BlockExt.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/ext/BlockExt.kt @@ -52,16 +52,15 @@ fun List.parents(selection: Iterable) : List { /** * Finds title block for a [Document] - * @return title block - * @throws NoSuchElementException if there was no title block in this document. + * @return title block or null if there's no title present */ -fun Document.title(): Block { - val header = first { block -> +fun Document.title(): Block? { + val header = firstOrNull { block -> val cnt = block.content cnt is Content.Layout && cnt.type == Content.Layout.Type.HEADER - } + } ?: return null val children = filter { header.children.contains(it.id) } - return children.first { child -> + return children.firstOrNull { child -> val cnt = child.content cnt is Content.Text && cnt.style == Content.Text.Style.TITLE } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt index e578863fea..26b826d7f6 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/BlockAdapter.kt @@ -76,6 +76,7 @@ import com.anytypeio.anytype.presentation.editor.editor.mention.MentionEvent import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_DESCRIPTION import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_FEATURED_RELATION +import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_NOTE_TITLE import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_RELATION_CHECKBOX import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_RELATION_DEFAULT import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_RELATION_FILE @@ -251,6 +252,15 @@ class BlockAdapter( } } } + HOLDER_NOTE_TITLE -> { + TitleNoteHolder( + view = inflater.inflate( + R.layout.item_block_note_title, + parent, + false + ) + ) + } HOLDER_HEADER_ONE -> { HeaderOne( view = inflater.inflate( diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/TitleNoteHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/TitleNoteHolder.kt new file mode 100644 index 0000000000..4398afc151 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/TitleNoteHolder.kt @@ -0,0 +1,6 @@ +package com.anytypeio.anytype.core_ui.features.editor.holders.other + +import android.view.View +import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder + +class TitleNoteHolder(view: View) : BlockViewHolder(view) \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_block_note_title.xml b/core-ui/src/main/res/layout/item_block_note_title.xml new file mode 100644 index 0000000000..b4063653a5 --- /dev/null +++ b/core-ui/src/main/res/layout/item_block_note_title.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index 9381ddd619..f43ef58935 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -42,10 +42,11 @@ class BlockDataRepository( command: Command.UpdateAlignment ): Payload = factory.remote.updateAlignment(command) - override suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?) = factory.remote.createPage( + override suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?) = factory.remote.createPage( ctx = ctx, emoji = emoji, - isDraft = isDraft + isDraft = isDraft, + type = type ) override suspend fun closePage(id: String) { diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt index 53f159f897..7e79955188 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt @@ -26,7 +26,7 @@ interface BlockDataStore { suspend fun move(command: Command.Move): Payload suspend fun unlink(command: Command.Unlink): Payload suspend fun getConfig(): Config - suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id + suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id suspend fun openPage(id: String): Payload suspend fun openObjectSet(id: String): Payload suspend fun openProfile(id: String): Payload diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index c051c8b24f..209806687d 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -23,7 +23,7 @@ interface BlockRemote { suspend fun updateCheckbox(command: Command.UpdateCheckbox): Payload suspend fun move(command: Command.Move): Payload suspend fun getConfig(): Config - suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id + suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id suspend fun createPage(command: Command.CreateNewDocument): String suspend fun openPage(id: String): Payload suspend fun openProfile(id: String): Payload diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt index 5b328eb937..87b8b14470 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt @@ -18,8 +18,9 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore { override suspend fun createPage( ctx: Id?, emoji: String?, - isDraft: Boolean? - ): Id = remote.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft) + isDraft: Boolean?, + type: String? + ): Id = remote.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft, type = type) override suspend fun openPage(id: String): Payload = remote.openPage(id) override suspend fun openProfile(id: String): Payload = remote.openProfile(id) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index 707b378ac8..c48301589a 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -69,7 +69,7 @@ interface BlockRepository { suspend fun getConfig(): Config - suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id + suspend fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id suspend fun openPage(id: String): Result diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/launch/GetDefaultPageType.kt b/domain/src/main/java/com/anytypeio/anytype/domain/launch/GetDefaultEditorType.kt similarity index 82% rename from domain/src/main/java/com/anytypeio/anytype/domain/launch/GetDefaultPageType.kt rename to domain/src/main/java/com/anytypeio/anytype/domain/launch/GetDefaultEditorType.kt index c7b108d865..23b58e4214 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/launch/GetDefaultPageType.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/launch/GetDefaultEditorType.kt @@ -3,9 +3,9 @@ package com.anytypeio.anytype.domain.launch import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.config.UserSettingsRepository -class GetDefaultPageType( +class GetDefaultEditorType( private val userSettingsRepository: UserSettingsRepository -) : BaseUseCase() { +) : BaseUseCase() { override suspend fun run(params: Unit) = safe { Response(userSettingsRepository.getDefaultPageType()) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/launch/SetDefaultPageType.kt b/domain/src/main/java/com/anytypeio/anytype/domain/launch/SetDefaultEditorType.kt similarity index 76% rename from domain/src/main/java/com/anytypeio/anytype/domain/launch/SetDefaultPageType.kt rename to domain/src/main/java/com/anytypeio/anytype/domain/launch/SetDefaultEditorType.kt index 0ce44c31a4..20bc83d4e2 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/launch/SetDefaultPageType.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/launch/SetDefaultEditorType.kt @@ -3,8 +3,8 @@ package com.anytypeio.anytype.domain.launch import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.config.UserSettingsRepository -class SetDefaultPageType(private val repo: UserSettingsRepository) : - BaseUseCase() { +class SetDefaultEditorType(private val repo: UserSettingsRepository) : + BaseUseCase() { override suspend fun run(params: Params) = safe { repo.setDefaultPageType(params.type) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/page/CreatePage.kt b/domain/src/main/java/com/anytypeio/anytype/domain/page/CreatePage.kt index 1a534b7c97..716f86c4b5 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/page/CreatePage.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/page/CreatePage.kt @@ -16,12 +16,20 @@ class CreatePage( repo.createPage( ctx = params.ctx, emoji = null, - isDraft = params.isDraft + isDraft = params.isDraft, + type = params.type ) } /** * @property [ctx] context (parent) for this new page. + * @property [type] type of created object + * @property [isDraft] should this object be in Draft state */ - data class Params(val ctx: Id?, val isDraft: Boolean?) + data class Params( + val ctx: Id?, + val type: String?, + val emoji: String?, + val isDraft: Boolean? + ) } \ No newline at end of file diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt index fd41c2ada4..2c0406f13f 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/ext/BlockExtensionTest.kt @@ -947,8 +947,8 @@ class BlockExtensionTest { assertEquals(expected = expected, actual = result) } - @Test(expected = NoSuchElementException::class) - fun `should throw NoSuchElementException when title block is not present in header childs`() { + @Test + fun `should return null when title block is not present in header childs`() { val root = MockDataFactory.randomUuid() @@ -982,10 +982,12 @@ class BlockExtensionTest { val document = listOf(page, header, a) val result = document.title() + + assertNull(result) } - @Test(expected = NoSuchElementException::class) - fun `should throw NoSuchElementException when header is not present`() { + @Test + fun `should return null when header is not present`() { val root = MockDataFactory.randomUuid() @@ -1010,5 +1012,7 @@ class BlockExtensionTest { val document = listOf(page, a) val result = document.title() + + assertNull(result) } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index 51dd6be000..cf9046502c 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -23,8 +23,8 @@ class BlockMiddleware( } override suspend fun createPage( - ctx: Id?, emoji: String?, isDraft: Boolean? - ): String = middleware.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft) + ctx: Id?, emoji: String?, isDraft: Boolean?, type: String? + ): String = middleware.createPage(ctx = ctx, emoji = emoji, isDraft = isDraft, type = type) override suspend fun createPage(command: Command.CreateNewDocument): String = middleware.createPage(command) diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 2b9e6d0a16..a11338b88a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -183,11 +183,12 @@ class Middleware( } @Throws(Exception::class) - fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?): Id { + fun createPage(ctx: Id?, emoji: String?, isDraft: Boolean?, type: String?): Id { val details: MutableMap = mutableMapOf() emoji?.let { details[iconEmojiKey] = it} isDraft?.let { details[isDraftKey] = it } + type?.let { details[typeKey] = it } val request = Rpc.Block.CreatePage.Request( contextId = ctx.orEmpty(), diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt index 343fdfdb01..c21fec0f53 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt @@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.EventsDictionary.PAGE_CREATE +import com.anytypeio.anytype.analytics.base.EventsDictionary.PROP_IS_DRAFT +import com.anytypeio.anytype.analytics.base.EventsDictionary.PROP_TYPE import com.anytypeio.anytype.analytics.base.EventsDictionary.SCREEN_DASHBOARD import com.anytypeio.anytype.analytics.base.EventsDictionary.SCREEN_PROFILE import com.anytypeio.anytype.analytics.base.EventsDictionary.TAB_ARCHIVE @@ -27,6 +29,7 @@ import com.anytypeio.anytype.domain.dashboard.interactor.CloseDashboard import com.anytypeio.anytype.domain.dashboard.interactor.OpenDashboard import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived @@ -58,6 +61,7 @@ class HomeDashboardViewModel( private val getDebugSettings: GetDebugSettings, private val analytics: Analytics, private val searchObjects: SearchObjects, + private val getDefaultEditorType: GetDefaultEditorType, private val urlBuilder: UrlBuilder, private val setObjectListIsArchived: SetObjectListIsArchived, private val deleteObjects: DeleteObjects @@ -181,25 +185,7 @@ class HomeDashboardViewModel( } fun onAddNewDocumentClicked() { - val startTime = System.currentTimeMillis() - createPage.invoke(viewModelScope, CreatePage.Params(ctx = null, isDraft = true)) { result -> - result.either( - fnL = { e -> Timber.e(e, "Error while creating a new page") }, - fnR = { id -> - val middle = System.currentTimeMillis() - viewModelScope.sendEvent( - analytics = analytics, - startTime = startTime, - middleTime = middle, - renderTime = middle, - eventName = PAGE_CREATE, - props = Props.empty() - ) - machine.onEvents(listOf(Machine.Event.OnFinishedCreatingPage)) - proceedWithOpeningDocument(id) - } - ) - } + proceedWithGettingDefaultPageType() } /** @@ -652,12 +638,54 @@ class HomeDashboardViewModel( ObjectType.Layout.PROFILE, ObjectType.Layout.FILE, ObjectType.Layout.IMAGE, - ObjectType.Layout.SET + ObjectType.Layout.SET, + ObjectType.Layout.NOTE ) } enum class TAB { FAVOURITE, RECENT, INBOX, SETS, ARCHIVE } + //region CREATE PAGE + private fun proceedWithGettingDefaultPageType() { + viewModelScope.launch { + getDefaultEditorType.invoke(Unit).proceed( + failure = { Timber.e(it, "Error while getting default page type") }, + success = { response -> proceedWithCreatePage(type = response.type) } + ) + } + } + + private suspend fun proceedWithCreatePage(type: String?) { + val startTime = System.currentTimeMillis() + val isDraft = true + val params = CreatePage.Params( + ctx = null, + isDraft = isDraft, + type = type, + emoji = null + ) + createPage.invoke(viewModelScope, params) { result -> + result.either( + fnL = { e -> Timber.e(e, "Error while creating a new page") }, + fnR = { id -> + val middle = System.currentTimeMillis() + val props = Props(mapOf(PROP_TYPE to type, PROP_IS_DRAFT to isDraft)) + viewModelScope.sendEvent( + analytics = analytics, + startTime = startTime, + middleTime = middle, + renderTime = middle, + eventName = PAGE_CREATE, + props = props + ) + machine.onEvents(listOf(Machine.Event.OnFinishedCreatingPage)) + proceedWithOpeningDocument(id) + } + ) + } + } + //endregion + enum class Mode { DEFAULT, SELECTION } sealed class Alert { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt index fd3ca47435..762a26536f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelFactory.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.config.GetDebugSettings import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived @@ -27,6 +28,7 @@ class HomeDashboardViewModelFactory( private val getDebugSettings: GetDebugSettings, private val analytics: Analytics, private val searchObjects: SearchObjects, + private val getDefaultEditorType: GetDefaultEditorType, private val urlBuilder: UrlBuilder, private val setObjectListIsArchived: SetObjectListIsArchived, private val deleteObjects: DeleteObjects @@ -47,6 +49,7 @@ class HomeDashboardViewModelFactory( analytics = analytics, searchObjects = searchObjects, urlBuilder = urlBuilder, + getDefaultEditorType = getDefaultEditorType, deleteObjects = deleteObjects, setObjectListIsArchived = setObjectListIsArchived ) as T diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt index 51bdb4c735..0fe176b69c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/Editor.kt @@ -33,7 +33,6 @@ interface Editor { val objectTypes: Store.ObjectTypes = Store.ObjectTypes() val textSelection: Store = Store.TextSelection() val objectRestrictions: Store.ObjectRestrictions = Store.ObjectRestrictions() - val objectIsDraft: Store.ObjectIsDraft = Store.ObjectIsDraft() } class Proxer( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index 844887295f..93bf7fd28e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -43,6 +43,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.editor.Editor import com.anytypeio.anytype.domain.error.Error import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.page.* @@ -135,6 +136,7 @@ class EditorViewModel( private val getCompatibleObjectTypes: GetCompatibleObjectTypes, private val objectTypesProvider: ObjectTypesProvider, private val searchObjects: SearchObjects, + private val getDefaultEditorType: GetDefaultEditorType ) : ViewStateViewModel(), SupportNavigation>, SupportCommand, @@ -285,8 +287,8 @@ class EditorViewModel( orchestrator.stores.relations.update(event.relations) orchestrator.stores.objectTypes.update(event.objectTypes) orchestrator.stores.objectRestrictions.update(event.objectRestrictions) - orchestrator.stores.objectIsDraft.set(event.details, context) - showObjectTypesWidget() + val objectType = event.details.details[context]?.type?.firstOrNull() + proceedWithShowingObjectTypesWidget(objectType, event.blocks) } if (event is Event.Command.Details) { orchestrator.stores.details.apply { update(current().process(event)) } @@ -604,6 +606,7 @@ class EditorViewModel( } } + //TODO need refactoring, logic must depend on Object Layouts private fun onStartFocusing(payload: Payload) { val event = payload.events.find { it is Event.Command.ShowObject } if (event is Event.Command.ShowObject) { @@ -616,17 +619,27 @@ class EditorViewModel( if (content is Content.Layout && content.type == Content.Layout.Type.HEADER) { try { val title = event.blocks.title() - if (title.content().text.isEmpty()) { + if (title != null && title.content().text.isEmpty()) { val focus = Editor.Focus(id = title.id, cursor = Editor.Cursor.End) viewModelScope.launch { orchestrator.stores.focus.update(focus) } } else { - Timber.d("Skipping initial focusing. Title is not empty.") + Timber.d("Skipping initial focusing. Title is not empty or is null") } } catch (e: Throwable) { Timber.e(e, "Error while initial focusing") } } } + root.children.size == 2 -> { + val layout = event.details.details[root.id]?.layout + if (layout == ObjectType.Layout.NOTE.code.toDouble()) { + val block = event.blocks.firstOrNull { it.content is Content.Text } + if (block != null && block.content().text.isEmpty()) { + val focus = Editor.Focus(id = block.id, cursor = Editor.Cursor.End) + viewModelScope.launch { orchestrator.stores.focus.update(focus) } + } + } + } else -> Timber.d("Skipping initial focusing, document is not empty.") } } @@ -782,7 +795,6 @@ class EditorViewModel( ) viewModelScope.launch { orchestrator.stores.views.update(new) } viewModelScope.launch { orchestrator.proxies.changes.send(update) } - viewModelScope.launch { checkObjectIsDraft() } } fun onDescriptionBlockTextChanged(view: BlockView.Description) { @@ -819,6 +831,7 @@ class EditorViewModel( viewModelScope.launch { store.update(new) } viewModelScope.launch { orchestrator.proxies.changes.send(update) } + if (isObjectTypesWidgetVisible) hideObjectTypesWidget() } fun onSelectionChanged(id: String, selection: IntRange) { @@ -3476,31 +3489,6 @@ class EditorViewModel( } } - fun onPlusButtonPressed() { - Timber.d("onPlusButtonPressed, ") - val startTime = System.currentTimeMillis() - createPage( - scope = viewModelScope, - params = CreatePage.Params(ctx = null, isDraft = true) - ) { result -> - result.either( - fnL = { Timber.e(it, "Error while creating a new page on home dashboard") }, - fnR = { id -> - val middle = System.currentTimeMillis() - viewModelScope.sendEvent( - analytics = analytics, - startTime = startTime, - middleTime = middle, - renderTime = middle, - eventName = PAGE_CREATE, - props = Props.empty() - ) - proceedWithOpeningPage(id) - } - ) - } - } - fun onProceedWithFilePath(filePath: String?) { Timber.d("onProceedWithFilePath, filePath:[$filePath]") if (filePath == null) { @@ -4646,6 +4634,7 @@ class EditorViewModel( Timber.d("onKeyPressedEvent, event:[$event]") when (event) { is KeyPressedEvent.OnTitleBlockEnterKeyEvent -> { + if (isObjectTypesWidgetVisible) hideObjectTypesWidget() proceedWithTitleEnterClicked( title = event.target, text = event.text, @@ -5146,6 +5135,9 @@ class EditorViewModel( //endregion //region OBJECT TYPES WIDGET + private val isObjectTypesWidgetVisible : Boolean get() = + controlPanelViewState.value?.objectTypesToolbar?.isVisible ?: false + fun onObjectTypesWidgetItemClicked(id: Id) { Timber.d("onObjectTypesWidgetItemClicked, id:[$id]") controlPanelInteractor.onEvent( @@ -5175,42 +5167,57 @@ class EditorViewModel( ) } - private fun showObjectTypesWidget() { + private fun proceedWithShowingObjectTypesWidget(objectType: String?, blocks: List) { val restrictions = orchestrator.stores.objectRestrictions.current() if (restrictions.contains(ObjectRestriction.TYPE_CHANGE)) { _toasts.offer(NOT_ALLOWED_FOR_OBJECT) Timber.d("No interaction allowed with this object type") return } - val isDraft = orchestrator.stores.objectIsDraft.current() - if (isDraft) { - val smartBlockType = getObjectSmartBlockType() - viewModelScope.launch { - getCompatibleObjectTypes.invoke( - GetCompatibleObjectTypes.Params(smartBlockType) - ).proceed( - failure = { Timber.e(it, "Error while getting object types") }, - success = { objectTypes -> - val views = listOf(ObjectTypeView.Search()) + objectTypes.toObjectTypeView() - controlPanelInteractor.onEvent( - ControlPanelMachine.Event.ObjectTypesWidgetEvent.Show(views) - ) + when (objectType) { + ObjectType.NOTE_URL -> { + val root = blocks.find { it.id == context } ?: return + if (root.children.size == 2) { + val lastBlock = blocks.find { it.id == root.children.last() } + if (lastBlock != null && lastBlock.content is Content.Text) { + if (lastBlock.content().text.isEmpty()) { + proceedWithGettingObjectTypesForObjectTypeWidget() + } } - ) + } + } + else -> { + val root = blocks.find { it.id == context } ?: return + if (root.children.size == 1) { + val title = blocks.title() ?: return + if (title.content().text.isEmpty()) { + proceedWithGettingObjectTypesForObjectTypeWidget() + } + } } } } - private suspend fun checkObjectIsDraft() { - val isDraft = orchestrator.stores.objectIsDraft.current() - if (isDraft) { - orchestrator.stores.objectIsDraft.set(state = false) - controlPanelInteractor.onEvent( - ControlPanelMachine.Event.ObjectTypesWidgetEvent.Hide + private fun proceedWithGettingObjectTypesForObjectTypeWidget() { + val smartBlockType = getObjectSmartBlockType() + val params = GetCompatibleObjectTypes.Params(smartBlockType) + viewModelScope.launch { + getCompatibleObjectTypes.invoke(params).proceed( + failure = { Timber.e(it, "Error while getting object types") }, + success = { objectTypes -> + val views = listOf(ObjectTypeView.Search()) + objectTypes.toObjectTypeView() + controlPanelInteractor.onEvent( + ControlPanelMachine.Event.ObjectTypesWidgetEvent.Show(views) + ) + } ) } } + private fun hideObjectTypesWidget() { + controlPanelInteractor.onEvent(ControlPanelMachine.Event.ObjectTypesWidgetEvent.Hide) + } + private fun getObjectSmartBlockType(): SmartBlockType { val block = blocks.firstOrNull { it.id == context } return if (block?.content is Content.Smart) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt index bac2eeff69..77292cc2a9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.page.* @@ -45,7 +46,8 @@ open class EditorViewModelFactory( private val updateDetail: UpdateDetail, private val getCompatibleObjectTypes: GetCompatibleObjectTypes, private val objectTypesProvider: ObjectTypesProvider, - private val searchObjects: SearchObjects + private val searchObjects: SearchObjects, + private val getDefaultEditorType: GetDefaultEditorType ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -72,7 +74,8 @@ open class EditorViewModelFactory( updateDetail = updateDetail, getCompatibleObjectTypes = getCompatibleObjectTypes, objectTypesProvider = objectTypesProvider, - searchObjects = searchObjects + searchObjects = searchObjects, + getDefaultEditorType = getDefaultEditorType ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Store.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Store.kt index bb7f6e32d4..9b5099dcc8 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Store.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Store.kt @@ -76,14 +76,5 @@ interface Store { class Relations : State>(emptyList()) class ObjectTypes : State>(emptyList()) class ObjectRestrictions : State>(emptyList()) - class ObjectIsDraft : State(false) { - suspend fun set(state: Boolean) { - super.update(state) - } - suspend fun set(details: Block.Details, ctx: Id) { - val isDraft = details.details[ctx]?.isDraft ?: false - super.update(isDraft) - } - } class TextSelection : State(Editor.TextSelection.empty()) } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt index bd16496b86..03b2667286 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt @@ -24,6 +24,7 @@ import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HEADE import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HEADER_THREE import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HEADER_TWO import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_HIGHLIGHT +import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_NOTE_TITLE import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_NUMBERED import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_OBJECT_TYPE import com.anytypeio.anytype.presentation.editor.editor.model.Types.HOLDER_PAGE @@ -540,7 +541,24 @@ sealed class BlockView : ViewType, Parcelable { ) : Title() { override fun getViewType() = HOLDER_ARCHIVE_TITLE } + } + + + /** + * UI-model for a note-layout title block. + * In fact it's just an empty space, because NOTE LAYOUT doesn't have title + * @property id block's id + */ + @Parcelize + data class TitleNote( + override val id: String + ) : BlockView() { + override fun getViewType() = HOLDER_NOTE_TITLE + + companion object { + const val INTERNAL_ID = "HOLDER_NOTE_TITLE" + } } /** diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/Types.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/Types.kt index ec2b481cf3..5ac4b2bb67 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/Types.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/Types.kt @@ -6,6 +6,7 @@ object Types { const val HOLDER_PROFILE_TITLE = 35 const val HOLDER_ARCHIVE_TITLE = 36 const val HOLDER_TODO_TITLE = 48 + const val HOLDER_NOTE_TITLE = 50 const val HOLDER_HEADER_ONE = 2 const val HOLDER_HEADER_TWO = 3 const val HOLDER_HEADER_THREE = 4 diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt index d6fa766762..d6a1e4abe9 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt @@ -53,6 +53,9 @@ class DefaultBlockViewRenderer( ) ) } + if (isLayoutNote(root, details)) { + result.add(BlockView.TitleNote(id = BlockView.TitleNote.INTERNAL_ID)) + } } } @@ -472,6 +475,7 @@ class DefaultBlockViewRenderer( relations = relations, details = details ) + if (featured.relations.isNotEmpty()) { result.add(featured) } @@ -1350,4 +1354,9 @@ class DefaultBlockViewRenderer( is Cursor.Range -> cursor.range.first } } + + private fun isLayoutNote(root: Block, details: Block.Details): Boolean { + val layoutCode = details.details[root.id]?.layout?.toInt() + return layoutCode == ObjectType.Layout.NOTE.code + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt index 5c6cef0df0..dbb4dd943f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt @@ -112,8 +112,8 @@ fun ObjectSet.render( fun ObjectSet.title( ctx: Id, urlBuilder: UrlBuilder -): BlockView.Title.Basic { - val title = blocks.title() +): BlockView.Title.Basic? { + val title = blocks.title() ?: return null val objectDetails = details[ctx] diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewState.kt index 53bc3fa63a..bdda49ce57 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewState.kt @@ -4,6 +4,6 @@ import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.sets.model.Viewer data class ObjectSetViewState( - val title: BlockView.Title.Basic, + val title: BlockView.Title.Basic?, val viewer: Viewer ) \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/UserSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/UserSettingsViewModel.kt index 725dfadcce..7559c90373 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/UserSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/UserSettingsViewModel.kt @@ -5,15 +5,15 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.ObjectType -import com.anytypeio.anytype.domain.launch.GetDefaultPageType -import com.anytypeio.anytype.domain.launch.SetDefaultPageType +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType +import com.anytypeio.anytype.domain.launch.SetDefaultEditorType import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import timber.log.Timber class UserSettingsViewModel( - private val getDefaultPageType: GetDefaultPageType, - private val setDefaultPageType: SetDefaultPageType, + private val getDefaultEditorType: GetDefaultEditorType, + private val setDefaultEditorType: SetDefaultEditorType, private val analytics: Analytics ) : ViewModel() { @@ -21,7 +21,7 @@ class UserSettingsViewModel( init { viewModelScope.launch { - getDefaultPageType.invoke(Unit).proceed( + getDefaultEditorType.invoke(Unit).proceed( failure = { Timber.e(it, "Error while getting user settings") }, success = { response -> if (response.type == ObjectType.NOTE_URL) { @@ -44,7 +44,7 @@ class UserSettingsViewModel( private fun proceedWithUpdateType(type: String) { viewModelScope.launch { - setDefaultPageType.invoke(SetDefaultPageType.Params(type)).process( + setDefaultEditorType.invoke(SetDefaultEditorType.Params(type)).process( failure = { Timber.e(it, "Error while setting default object type") commands.emit(Command.Exit) @@ -62,16 +62,16 @@ class UserSettingsViewModel( } class Factory( - private val getDefaultPageType: GetDefaultPageType, - private val setDefaultPageType: SetDefaultPageType, + private val getDefaultEditorType: GetDefaultEditorType, + private val setDefaultEditorType: SetDefaultEditorType, private val analytics: Analytics ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( modelClass: Class ): T = UserSettingsViewModel( - getDefaultPageType = getDefaultPageType, - setDefaultPageType = setDefaultPageType, + getDefaultEditorType = getDefaultEditorType, + setDefaultEditorType = setDefaultEditorType, analytics = analytics ) as T } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt index efbc859e71..4ea3ccd161 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt @@ -21,8 +21,8 @@ import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.auth.model.AuthStatus import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes -import com.anytypeio.anytype.domain.launch.GetDefaultPageType -import com.anytypeio.anytype.domain.launch.SetDefaultPageType +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType +import com.anytypeio.anytype.domain.launch.SetDefaultEditorType import com.anytypeio.anytype.presentation.objects.SupportedLayouts import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @@ -40,8 +40,8 @@ class SplashViewModel( private val launchAccount: LaunchAccount, private val storeObjectTypes: StoreObjectTypes, private val getLastOpenedObject: GetLastOpenedObject, - private val getDefaultPageType: GetDefaultPageType, - private val setDefaultPageType: SetDefaultPageType + private val getDefaultEditorType: GetDefaultEditorType, + private val setDefaultEditorType: SetDefaultEditorType ) : ViewModel() { val commands = MutableSharedFlow(replay = 0) @@ -52,7 +52,7 @@ class SplashViewModel( private fun proceedWithUserSettings() { viewModelScope.launch { - getDefaultPageType.invoke(Unit).process( + getDefaultEditorType.invoke(Unit).process( failure = { Timber.e(it, "Error while getting default page type") checkAuthorizationStatus() @@ -77,9 +77,9 @@ class SplashViewModel( DEFAULT_TYPE_UPDATE } viewModelScope.launch { - val params = SetDefaultPageType.Params(defaultType) + val params = SetDefaultEditorType.Params(defaultType) Timber.d("Start to update Default Page Type:${params.type}") - setDefaultPageType.invoke(params).process( + setDefaultEditorType.invoke(params).process( failure = { Timber.e(it, "Error while setting default page type") checkAuthorizationStatus() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt index b3045e4422..97d3c309bc 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt @@ -8,8 +8,8 @@ import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes -import com.anytypeio.anytype.domain.launch.GetDefaultPageType -import com.anytypeio.anytype.domain.launch.SetDefaultPageType +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType +import com.anytypeio.anytype.domain.launch.SetDefaultEditorType /** * Created by Konstantin Ivanov @@ -23,8 +23,8 @@ class SplashViewModelFactory( private val analytics: Analytics, private val storeObjectTypes: StoreObjectTypes, private val getLastOpenedObject: GetLastOpenedObject, - private val getDefaultPageType: GetDefaultPageType, - private val setDefaultPageType: SetDefaultPageType + private val getDefaultEditorType: GetDefaultEditorType, + private val setDefaultEditorType: SetDefaultEditorType ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -36,7 +36,7 @@ class SplashViewModelFactory( analytics = analytics, storeObjectTypes = storeObjectTypes, getLastOpenedObject = getLastOpenedObject, - getDefaultPageType = getDefaultPageType, - setDefaultPageType = setDefaultPageType + getDefaultEditorType = getDefaultEditorType, + setDefaultEditorType = setDefaultEditorType ) as T } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt index a89fa79d33..6f0c741abf 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/DashboardTestSetup.kt @@ -18,6 +18,7 @@ import com.anytypeio.anytype.domain.config.GetDebugSettings import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived @@ -82,6 +83,9 @@ open class DashboardTestSetup { @Mock lateinit var objectTypesProvider: ObjectTypesProvider + @Mock + lateinit var getDefaultEditorType: GetDefaultEditorType + lateinit var vm: HomeDashboardViewModel val builder: UrlBuilder get() = UrlBuilder(gateway) @@ -108,6 +112,7 @@ open class DashboardTestSetup { analytics = analytics, searchObjects = searchObjects, urlBuilder = builder, + getDefaultEditorType = getDefaultEditorType, setObjectListIsArchived = setObjectListIsArchived, deleteObjects = deleteObjects ) 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 ac21993ecf..d1c9cc5a01 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 @@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.config.GetDebugSettings import com.anytypeio.anytype.domain.dashboard.interactor.* import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived @@ -83,6 +84,9 @@ class HomeDashboardViewModelTest { @Mock lateinit var objectTypesProvider: ObjectTypesProvider + @Mock + lateinit var getDefaultEditorType: GetDefaultEditorType + private lateinit var vm: HomeDashboardViewModel private val config = Config( @@ -116,7 +120,8 @@ class HomeDashboardViewModelTest { searchObjects = searchObjects, deleteObjects = deleteObjects, setObjectListIsArchived = setObjectListIsArchived, - urlBuilder = builder + urlBuilder = builder, + getDefaultEditorType = getDefaultEditorType ) } @@ -305,6 +310,7 @@ class HomeDashboardViewModelTest { fun `should start creating page when requested from UI`() { stubObserveEvents() + stubGetDefaultObjectType(null) vm = buildViewModel() @@ -322,6 +328,7 @@ class HomeDashboardViewModelTest { stubGetEditorSettings() stubCloseDashboard() stubCreatePage(id) + stubGetDefaultObjectType(null) vm = buildViewModel() @@ -384,4 +391,10 @@ class HomeDashboardViewModelTest { onBlocking { invoke(any()) } doReturn Either.Right(DebugSettings(true)) } } + + private fun stubGetDefaultObjectType(type: String?) { + getDefaultEditorType.stub { + onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type)) + } + } } \ No newline at end of file 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 4629bbfad7..eb46eb7d09 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 @@ -21,6 +21,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey import com.anytypeio.anytype.domain.download.DownloadFile import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.page.* @@ -214,6 +215,9 @@ open class EditorViewModelTest { @Mock lateinit var objectTypesProvider: ObjectTypesProvider + @Mock + lateinit var getDefaultEditorType: GetDefaultEditorType + private lateinit var updateDetail: UpdateDetail lateinit var vm: EditorViewModel @@ -2406,49 +2410,6 @@ open class EditorViewModelTest { coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION) } - @Test - fun `should proceed with creating a new page with is draft true on on-plus-button-clicked event`() { - - val root = MockDataFactory.randomUuid() - val child = MockDataFactory.randomUuid() - - val page = MockBlockFactory.makeOnePageWithOneTextBlock( - root = root, - child = child - ) - - val flow: Flow> = flow { - delay(100) - emit( - listOf( - Event.Command.ShowObject( - root = root, - blocks = page, - context = root - ) - ) - ) - } - - stubObserveEvents(flow) - stubOpenPage() - buildViewModel() - - vm.onStart(root) - - coroutineTestRule.advanceTime(100) - - // TESTING - - vm.onPlusButtonPressed() - - verify(createPage, times(1)).invoke( - scope = any(), - params = eq(CreatePage.Params(null, true)), - onResult = any() - ) - } - @Test fun `should start downloading file`() { @@ -3885,6 +3846,12 @@ open class EditorViewModelTest { } } + private fun stubGetDefaultObjectType(type: String?) { + getDefaultEditorType.stub { + onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type)) + } + } + fun buildViewModel(urlBuilder: UrlBuilder = builder) { val storage = Editor.Storage() @@ -3915,6 +3882,7 @@ open class EditorViewModelTest { createDocument = createDocument, createNewDocument = createNewDocument, analytics = analytics, + getDefaultEditorType = getDefaultEditorType, orchestrator = Orchestrator( createBlock = createBlock, replaceBlock = replaceBlock, diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt new file mode 100644 index 0000000000..7a9c6ae0b0 --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorNoteLayoutTest.kt @@ -0,0 +1,151 @@ +package com.anytypeio.anytype.presentation.editor.editor + +import MockDataFactory +import android.util.Log +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.anytypeio.anytype.core_models.* +import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory +import com.anytypeio.anytype.presentation.editor.EditorViewModel +import com.anytypeio.anytype.presentation.editor.editor.model.BlockView +import com.anytypeio.anytype.presentation.relations.DocumentRelationView +import com.anytypeio.anytype.presentation.util.CoroutinesTestRule +import com.jraska.livedata.test +import net.lachlanmckee.timberjunit.TimberTestRule +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.MockitoAnnotations + +class EditorNoteLayoutTest : EditorPresentationTestSetup() { + + @get:Rule + val timberTestRule: TimberTestRule = TimberTestRule.builder() + .minPriority(Log.DEBUG) + .showThread(true) + .showTimestamp(false) + .onlyLogWhenTestFails(true) + .build() + + @get:Rule + val rule = InstantTaskExecutorRule() + + @get:Rule + val coroutineTestRule = CoroutinesTestRule() + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + } + + @After + fun after() { + coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION) + } + + @Test + fun `should render note title block with featured relations block`() { + + val featuredBlock = Block( + id = "featuredRelations", + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.FeaturedRelations + ) + + val header = Block( + id = "header", + content = Block.Content.Layout( + type = Block.Content.Layout.Type.HEADER + ), + fields = Block.Fields.empty(), + children = listOf(featuredBlock.id) + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id) + ) + + val doc = listOf(page, header, featuredBlock) + + val objectTypeId = "objectTypeId" + val objectTypeName = "objectTypeName" + val objectTypeDescription = "objectTypeDesc" + + val r1 = MockTypicalDocumentFactory.relation("Ad") + val r2 = MockTypicalDocumentFactory.relation("De") + val r3 = MockTypicalDocumentFactory.relation("HJ") + val relationObjectType = Relation( + key = Block.Fields.TYPE_KEY, + name = "Object Type", + format = Relation.Format.OBJECT, + source = Relation.Source.DERIVED + ) + + + val value1 = MockDataFactory.randomString() + val value2 = MockDataFactory.randomString() + val value3 = MockDataFactory.randomString() + val objectFields = Block.Fields( + mapOf( + r1.key to value1, + r2.key to value2, + r3.key to value3, + relationObjectType.key to objectTypeId, + Relations.FEATURED_RELATIONS to listOf(relationObjectType.key), + Relations.LAYOUT to ObjectType.Layout.NOTE.code.toDouble() + ) + ) + + val objectTypeFields = Block.Fields( + mapOf( + Block.Fields.NAME_KEY to objectTypeName, + Block.Fields.DESCRIPTION_KEY to objectTypeDescription + ) + ) + val customDetails = Block.Details( + mapOf( + root to objectFields, + objectTypeId to objectTypeFields + ) + ) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubGetDefaultObjectType(null) + stubOpenDocument( + document = doc, + details = customDetails, + relations = listOf(r1, r2, r3, relationObjectType) + ) + + val vm = buildViewModel() + + vm.onStart(root) + + val expected = listOf( + BlockView.TitleNote( + id = BlockView.TitleNote.INTERNAL_ID + ), + BlockView.FeaturedRelation( + id = featuredBlock.id, + relations = listOf( + DocumentRelationView.ObjectType( + relationId = relationObjectType.key, + name = objectTypeName, + value = null, + isFeatured = true, + type = objectTypeId + ) + ) + ) + ) + + vm.state.test().assertValue(ViewState.Success(expected)) + } + +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt new file mode 100644 index 0000000000..67aa09c401 --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorObjectTypeChangeWidgetTest.kt @@ -0,0 +1,253 @@ +package com.anytypeio.anytype.presentation.editor.editor + +import MockDataFactory +import android.util.Log +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.core_models.SmartBlockType +import com.anytypeio.anytype.presentation.editor.EditorViewModel +import com.anytypeio.anytype.presentation.util.CoroutinesTestRule +import net.lachlanmckee.timberjunit.TimberTestRule +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.MockitoAnnotations +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class EditorObjectTypeChangeWidgetTest : EditorPresentationTestSetup() { + + @get:Rule + val timberTestRule: TimberTestRule = TimberTestRule.builder() + .minPriority(Log.DEBUG) + .showThread(true) + .showTimestamp(false) + .onlyLogWhenTestFails(true) + .build() + + @get:Rule + val rule = InstantTaskExecutorRule() + + @get:Rule + val coroutineTestRule = CoroutinesTestRule() + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + } + + @After + fun after() { + coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION) + } + + @Test + fun `should show widget on note object with one empty text block`() { + + val paragraph = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = Block.Content.Text.Style.P + ) + ) + + val featuredBlock = Block( + id = "featuredRelations", + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.FeaturedRelations + ) + + val header = Block( + id = "header", + content = Block.Content.Layout( + type = Block.Content.Layout.Type.HEADER + ), + fields = Block.Fields.empty(), + children = listOf(featuredBlock.id) + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id, paragraph.id) + ) + + val doc = listOf(page, header, paragraph, featuredBlock) + + val objectDetails = Block.Fields( + mapOf( + "type" to ObjectType.NOTE_URL, + "layout" to ObjectType.Layout.NOTE.code.toDouble() + ) + ) + + val detailsList = Block.Details(details = mapOf(root to objectDetails)) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubGetDefaultObjectType(type = ObjectType.NOTE_URL) + stubOpenDocument( + document = doc, + details = detailsList + ) + + val vm = buildViewModel() + + vm.onStart(root) + + val state = vm.controlPanelViewState.value + + val objectTypesWidget = state?.objectTypesToolbar + + assertNotNull(objectTypesWidget) + assertTrue(objectTypesWidget.isVisible) + } + + @Test + fun `should not show widget on note object with one not empty text block`() { + + val paragraph = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Text( + text = "F", + marks = emptyList(), + style = Block.Content.Text.Style.P + ) + ) + + val featuredBlock = Block( + id = "featuredRelations", + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.FeaturedRelations + ) + + val header = Block( + id = "header", + content = Block.Content.Layout( + type = Block.Content.Layout.Type.HEADER + ), + fields = Block.Fields.empty(), + children = listOf(featuredBlock.id) + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id, paragraph.id) + ) + + val doc = listOf(page, header, paragraph, featuredBlock) + + val objectDetails = Block.Fields( + mapOf( + "type" to ObjectType.NOTE_URL, + "layout" to ObjectType.Layout.NOTE.code.toDouble() + ) + ) + + val detailsList = Block.Details(details = mapOf(root to objectDetails)) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubGetDefaultObjectType(type = ObjectType.NOTE_URL) + stubOpenDocument( + document = doc, + details = detailsList + ) + + val vm = buildViewModel() + + vm.onStart(root) + + val state = vm.controlPanelViewState.value + + val objectTypesWidget = state?.objectTypesToolbar + + assertNotNull(objectTypesWidget) + assertFalse(objectTypesWidget.isVisible) + } + + @Test + fun `should show widget on note object layout with one empty text block and default not note type`() { + + val paragraph = Block( + id = MockDataFactory.randomUuid(), + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.Text( + text = "", + marks = emptyList(), + style = Block.Content.Text.Style.P + ) + ) + + val featuredBlock = Block( + id = "featuredRelations", + fields = Block.Fields.empty(), + children = emptyList(), + content = Block.Content.FeaturedRelations + ) + + val header = Block( + id = "header", + content = Block.Content.Layout( + type = Block.Content.Layout.Type.HEADER + ), + fields = Block.Fields.empty(), + children = listOf(featuredBlock.id) + ) + + val page = Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(SmartBlockType.PAGE), + children = listOf(header.id, paragraph.id) + ) + + val doc = listOf(page, header, paragraph, featuredBlock) + + val objectDetails = Block.Fields( + mapOf( + "type" to ObjectType.NOTE_URL, + "layout" to ObjectType.Layout.NOTE.code.toDouble() + ) + ) + + val detailsList = Block.Details(details = mapOf(root to objectDetails)) + + stubInterceptEvents() + stubInterceptThreadStatus() + stubGetObjectTypes(objectTypes = listOf()) + stubGetDefaultObjectType(type = ObjectType.PAGE_URL) + stubOpenDocument( + document = doc, + details = detailsList + ) + + val vm = buildViewModel() + + vm.onStart(root) + + val state = vm.controlPanelViewState.value + + val objectTypesWidget = state?.objectTypesToolbar + + assertNotNull(objectTypesWidget) + assertTrue(objectTypesWidget.isVisible) + } +} \ No newline at end of file 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 47580f9407..95b6f5cf7e 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 @@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey import com.anytypeio.anytype.domain.download.DownloadFile import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.page.* @@ -184,6 +185,9 @@ open class EditorPresentationTestSetup { @Mock lateinit var searchObjects: SearchObjects + @Mock + lateinit var getDefaultEditorType: GetDefaultEditorType + private val builder: UrlBuilder get() = UrlBuilder(gateway) private lateinit var updateDetail: UpdateDetail @@ -266,7 +270,8 @@ open class EditorPresentationTestSetup { updateDetail = updateDetail, getCompatibleObjectTypes = getCompatibleObjectTypes, objectTypesProvider = objectTypesProvider, - searchObjects = searchObjects + searchObjects = searchObjects, + getDefaultEditorType = getDefaultEditorType ) } @@ -514,4 +519,10 @@ open class EditorPresentationTestSetup { onBlocking { invoke(any()) } doReturn Either.Right(listOf()) } } + + fun stubGetDefaultObjectType(type: String?) { + getDefaultEditorType.stub { + onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type)) + } + } } \ No newline at end of file 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 5ce45f5461..68a8270387 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 @@ -1,6 +1,5 @@ package com.anytypeio.anytype.presentation.splash -import MockDataFactory import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.ObjectType @@ -15,8 +14,8 @@ import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.UserSettingsRepository -import com.anytypeio.anytype.domain.launch.GetDefaultPageType -import com.anytypeio.anytype.domain.launch.SetDefaultPageType +import com.anytypeio.anytype.domain.launch.GetDefaultEditorType +import com.anytypeio.anytype.domain.launch.SetDefaultEditorType import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import kotlinx.coroutines.runBlocking import org.junit.Before @@ -62,10 +61,10 @@ class SplashViewModelTest { private lateinit var getLastOpenedObject: GetLastOpenedObject @Mock - private lateinit var setDefaultPageType: SetDefaultPageType + private lateinit var setDefaultEditorType: SetDefaultEditorType @Mock - private lateinit var getDefaultPageType: GetDefaultPageType + private lateinit var getDefaultEditorType: GetDefaultEditorType lateinit var vm: SplashViewModel @@ -90,8 +89,8 @@ class SplashViewModelTest { analytics = analytics, storeObjectTypes = storeObjectTypes, getLastOpenedObject = getLastOpenedObject, - setDefaultPageType = setDefaultPageType, - getDefaultPageType = getDefaultPageType + setDefaultEditorType = setDefaultEditorType, + getDefaultEditorType = getDefaultEditorType ) } @@ -125,14 +124,14 @@ class SplashViewModelTest { stubLaunchWallet() stubLaunchAccount() stubGetLastOpenedObject() - getDefaultPageType.stub { + getDefaultEditorType.stub { onBlocking { invoke(Unit) } doReturn Either.Left(Exception("error")) } initViewModel() runBlocking { - verify(getDefaultPageType, times(1)).invoke(any()) + verify(getDefaultEditorType, times(1)).invoke(any()) verify(checkAuthorizationStatus, times(1)).invoke(any()) } } @@ -151,7 +150,7 @@ class SplashViewModelTest { initViewModel() runBlocking { - verify(getDefaultPageType, times(1)).invoke(any()) + verify(getDefaultEditorType, times(1)).invoke(any()) verify(checkAuthorizationStatus, times(1)).invoke(any()) } } @@ -312,14 +311,14 @@ class SplashViewModelTest { } private fun stubGetDefaultObjectType(type: String?) { - getDefaultPageType.stub { - onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultPageType.Response(type)) + getDefaultEditorType.stub { + onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type)) } } private fun stubSetDefaultObjectType(type: String) { - setDefaultPageType.stub { - onBlocking { invoke(SetDefaultPageType.Params(type)) } doReturn Either.Right(Unit) + setDefaultEditorType.stub { + onBlocking { invoke(SetDefaultEditorType.Params(type)) } doReturn Either.Right(Unit) } } } \ No newline at end of file