From a1a3b4f9656bf8650f8bacee74b5b2e45641d3ad Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:24:51 +0200 Subject: [PATCH] DROID-1593 Editor | Feature | InternalFlags, selectTemplate, selectType (#261) --- .../features/editor/base/EditorTestSetup.kt | 7 +- .../anytypeio/anytype/di/feature/EditorDI.kt | 15 +- .../ui/templates/TemplateSelectFragment.kt | 34 +-- .../anytypeio/anytype/core_models/Command.kt | 2 + .../auth/repo/block/BlockDataRepository.kt | 4 + .../data/auth/repo/block/BlockRemote.kt | 2 + .../domain/block/repo/BlockRepository.kt | 1 + .../domain/object/SetObjectInternalFlags.kt | 28 +++ .../middleware/block/BlockMiddleware.kt | 4 + .../middleware/interactor/Middleware.kt | 14 ++ .../middleware/service/MiddlewareService.kt | 3 + .../MiddlewareServiceImplementation.kt | 13 ++ .../presentation/editor/EditorViewModel.kt | 153 +++++++++---- .../editor/EditorViewModelFactory.kt | 7 +- .../templates/TemplateSelectViewModel.kt | 5 +- .../editor/EditorViewModelTest.kt | 6 +- .../editor/editor/EditorInternalFlagsTest.kt | 204 ++++++++++++++++++ .../editor/EditorPresentationTestSetup.kt | 19 +- 18 files changed, 442 insertions(+), 79 deletions(-) create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/object/SetObjectInternalFlags.kt 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 83586d78cd..5a23ebc672 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 @@ -60,6 +60,7 @@ import com.anytypeio.anytype.domain.launch.GetDefaultPageType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations @@ -269,6 +270,9 @@ open class EditorTestSetup { @Mock lateinit var fileLimitsEventChannel: FileLimitsEventChannel + @Mock + lateinit var setObjectInternalFlags: SetObjectInternalFlags + lateinit var interceptFileLimitEvents: InterceptFileLimitEvents lateinit var addRelationToObject: AddRelationToObject @@ -466,7 +470,8 @@ open class EditorTestSetup { getObjectTypes = getObjectTypes, objectToCollection = objectToCollection, interceptFileLimitEvents = interceptFileLimitEvents, - addRelationToObject = addRelationToObject + addRelationToObject = addRelationToObject, + setObjectInternalFlags = setObjectInternalFlags ) } 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 66bc16a48f..02dfcec075 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 @@ -56,6 +56,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.DuplicateObject +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.SetObjectIsArchived import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes @@ -233,6 +234,14 @@ object EditorSessionModule { dispatchers: AppCoroutineDispatchers ) : InterceptFileLimitEvents = InterceptFileLimitEvents(channel, dispatchers) + @JvmStatic + @Provides + @PerScreen + fun provideSetObjectInternalFlags( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ) : SetObjectInternalFlags = SetObjectInternalFlags(repo, dispatchers) + @JvmStatic @Provides fun providePageViewModelFactory( @@ -271,7 +280,8 @@ object EditorSessionModule { getObjectTypes: GetObjectTypes, objectToCollection: ConvertObjectToCollection, interceptFileLimitEvents: InterceptFileLimitEvents, - addRelationToObject: AddRelationToObject + addRelationToObject: AddRelationToObject, + setObjectInternalFlags: SetObjectInternalFlags ): EditorViewModelFactory = EditorViewModelFactory( openPage = openPage, closeObject = closePage, @@ -308,7 +318,8 @@ object EditorSessionModule { getObjectTypes = getObjectTypes, objectToCollection = objectToCollection, interceptFileLimitEvents = interceptFileLimitEvents, - addRelationToObject = addRelationToObject + addRelationToObject = addRelationToObject, + setObjectInternalFlags = setObjectInternalFlags ) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/templates/TemplateSelectFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/templates/TemplateSelectFragment.kt index 38927dc197..49cfa5926e 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/templates/TemplateSelectFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/templates/TemplateSelectFragment.kt @@ -5,9 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.Id @@ -20,7 +18,6 @@ import com.anytypeio.anytype.databinding.FragmentTemplateSelectBinding import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.presentation.templates.TemplateSelectViewModel import com.google.android.material.tabs.TabLayoutMediator -import kotlinx.coroutines.launch import javax.inject.Inject class TemplateSelectFragment : @@ -40,9 +37,15 @@ class TemplateSelectFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupViewPagerAndTabs() - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - setupClickEventHandlers() + with(lifecycleScope) { + subscribe(binding.btnCancel.clicks()) { + vm.onCancelButtonClicked() + } + subscribe(binding.btnUseTemplate.clicks()) { + vm.onUseTemplateButtonPressed( + currentItem = binding.templateViewPager.currentItem, + ctx = ctx + ) } } } @@ -53,11 +56,6 @@ class TemplateSelectFragment : TabLayoutMediator(binding.tabs, binding.templateViewPager) { _, _ -> }.attach() } - private suspend fun setupClickEventHandlers() { - setupUseTemplateClicks() - setupCancelClicks() - } - override fun onStart() { jobs += lifecycleScope.subscribe(vm.viewState) { render(it) } jobs += lifecycleScope.subscribe(vm.isDismissed) { if (it) exit() } @@ -67,7 +65,6 @@ class TemplateSelectFragment : private fun render(viewState: TemplateSelectViewModel.ViewState) { when (viewState) { - TemplateSelectViewModel.ViewState.ErrorGettingType -> TODO() TemplateSelectViewModel.ViewState.Init -> { binding.tvTemplateCountOrTutorial.text = null binding.btnCancel.isEnabled = true @@ -86,19 +83,6 @@ class TemplateSelectFragment : } } - private suspend fun setupUseTemplateClicks() { - binding.btnUseTemplate.clicks().collect { - vm.onUseTemplateButtonPressed( - currentItem = binding.templateViewPager.currentItem, - ctx = ctx - ) - } - } - - private suspend fun setupCancelClicks() { - binding.btnCancel.clicks().collect { exit() } - } - private fun exit() { findNavController().popBackStack() } diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index 763b97102f..d5fc48c9d9 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -421,4 +421,6 @@ sealed class Command { data class AddObjectToCollection(val ctx: Id, val afterId: Id, val ids: List) data class SetQueryToSet(val ctx: Id, val query: String) + + data class SetInternalFlags(val ctx: Id, val flags: List) } \ 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 25514b5b1b..f358dd992c 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 @@ -841,4 +841,8 @@ class BlockDataRepository( override suspend fun fileSpaceUsage(): FileLimits { return remote.fileSpaceUsage() } + + override suspend fun setInternalFlags(command: Command.SetInternalFlags): Payload { + return remote.setInternalFlags(command) + } } \ No newline at end of file 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 1c3f5493ac..420fba3314 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 @@ -360,4 +360,6 @@ interface BlockRemote { suspend fun addObjectToCollection(command: Command.AddObjectToCollection): Payload suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload suspend fun fileSpaceUsage(): FileLimits + + suspend fun setInternalFlags(command: Command.SetInternalFlags): Payload } \ No newline at end of file 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 2835ad3230..2b28adedfe 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 @@ -412,4 +412,5 @@ interface BlockRepository { suspend fun addObjectToCollection(command: Command.AddObjectToCollection): Payload suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload suspend fun fileSpaceUsage(): FileLimits + suspend fun setInternalFlags(command: Command.SetInternalFlags): Payload } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/object/SetObjectInternalFlags.kt b/domain/src/main/java/com/anytypeio/anytype/domain/object/SetObjectInternalFlags.kt new file mode 100644 index 0000000000..f52721f556 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/object/SetObjectInternalFlags.kt @@ -0,0 +1,28 @@ +package com.anytypeio.anytype.domain.`object` + +import com.anytypeio.anytype.core_models.Command +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.InternalFlags +import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import com.anytypeio.anytype.domain.block.repo.BlockRepository + +class SetObjectInternalFlags( + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params): Payload { + val command = Command.SetInternalFlags( + ctx = params.ctx, + flags = params.flags + ) + return repo.setInternalFlags(command) + } + + data class Params( + val ctx: Id, + val flags: List + ) +} \ 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 69bc2094a3..7a965a9c78 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 @@ -792,4 +792,8 @@ class BlockMiddleware( override suspend fun fileSpaceUsage(): FileLimits { return middleware.fileSpaceUsage() } + + override suspend fun setInternalFlags(command: Command.SetInternalFlags): Payload { + return middleware.setInternalFlags(command) + } } \ No newline at end of file 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 ec1b69df8a..537c578c81 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 @@ -2185,6 +2185,20 @@ class Middleware @Inject constructor( return response.event.toPayload() } + @Throws(Exception::class) + fun setInternalFlags( + command: Command.SetInternalFlags + ): Payload { + val request = Rpc.Object.SetInternalFlags.Request( + contextId = command.ctx, + internalFlags = command.flags.toMiddlewareModel() + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.setInternalFlags(request) + if (BuildConfig.DEBUG) logResponse(response) + return response.event.toPayload() + } + fun addObjectToCollection(command: Command.AddObjectToCollection): Payload { val request = Rpc.ObjectCollection.Add.Request( contextId = command.ctx, diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 741e074016..489057c2d4 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -143,6 +143,9 @@ interface MiddlewareService { @Throws(Exception::class) fun setObjectSource(request: Rpc.Object.SetSource.Request): Rpc.Object.SetSource.Response + @Throws(Exception::class) + fun setInternalFlags(request: Rpc.Object.SetInternalFlags.Request): Rpc.Object.SetInternalFlags.Response + //endregion //region OBJECT'S RELATIONS command 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 13e01fcab4..20f366eb80 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 @@ -1611,4 +1611,17 @@ class MiddlewareServiceImplementation @Inject constructor( return response } } + + override fun setInternalFlags(request: Rpc.Object.SetInternalFlags.Request): Rpc.Object.SetInternalFlags.Response { + val encoded = Service.objectSetInternalFlags( + Rpc.Object.SetInternalFlags.Request.ADAPTER.encode(request) + ) + val response = Rpc.Object.SetInternalFlags.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Object.SetInternalFlags.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } } \ No newline at end of file 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 d8a0481a18..17b3f2233e 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 @@ -73,6 +73,7 @@ import com.anytypeio.anytype.domain.launch.GetDefaultPageType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations @@ -94,6 +95,7 @@ import com.anytypeio.anytype.presentation.common.Delegator import com.anytypeio.anytype.presentation.common.StateReducer import com.anytypeio.anytype.presentation.common.SupportCommand import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Interactor +import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Event.ObjectTypesWidgetEvent import com.anytypeio.anytype.presentation.editor.Editor.Restore import com.anytypeio.anytype.presentation.editor.editor.Command import com.anytypeio.anytype.presentation.editor.editor.Intent @@ -286,7 +288,8 @@ class EditorViewModel( private val workspaceManager: WorkspaceManager, private val getObjectTypes: GetObjectTypes, private val interceptFileLimitEvents: InterceptFileLimitEvents, - private val addRelationToObject: AddRelationToObject + private val addRelationToObject: AddRelationToObject, + private val setObjectInternalFlags: SetObjectInternalFlags ) : ViewStateViewModel(), PickerListener, SupportNavigation>, @@ -1212,9 +1215,7 @@ class EditorViewModel( ) viewModelScope.launch { orchestrator.stores.views.update(new) } viewModelScope.launch { orchestrator.proxies.changes.send(update) } - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() } fun onDescriptionBlockTextChanged(view: BlockView.Description) { @@ -1229,9 +1230,7 @@ class EditorViewModel( ) viewModelScope.launch { orchestrator.stores.views.update(new) } viewModelScope.launch { orchestrator.proxies.changes.send(update) } - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() } fun onTextBlockTextChanged(view: BlockView.Text) { @@ -1257,9 +1256,7 @@ class EditorViewModel( } viewModelScope.launch { orchestrator.proxies.changes.send(update) } - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() } fun onSelectionChanged(id: String, selection: IntRange) { @@ -1438,9 +1435,7 @@ class EditorViewModel( ) { Timber.d("onEndLineEnterClicked, id:[$id] text:[$text] marks:[$marks]") - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() val target = blocks.first { it.id == id } @@ -2908,9 +2903,7 @@ class EditorViewModel( fun onOutsideClicked() { Timber.d("onOutsideClicked, ") - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() if (mode is EditorMode.Styling) { onExitBlockStyleToolbarClicked() @@ -4289,11 +4282,9 @@ class EditorViewModel( analytics = analytics, objType = storeOfObjectTypes.get(type) ) - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() if (applyTemplate) { - proceedWithTemplateSelection(type) + proceedWithCheckingInternalFlagShouldSelectTemplate(objTypeId = type) } } } @@ -5353,9 +5344,7 @@ class EditorViewModel( Timber.d("onKeyPressedEvent, event:[$event]") when (event) { is KeyPressedEvent.OnTitleBlockEnterKeyEvent -> { - if (isObjectTypesWidgetVisible) { - proceedWithHidingObjectTypeWidget() - } + sendHideObjectTypeWidgetEvent() proceedWithTitleEnterClicked( title = event.target, text = event.text, @@ -5883,28 +5872,17 @@ class EditorViewModel( fun onObjectTypesWidgetDoneClicked() { Timber.d("onObjectTypesWidgetDoneClicked, ") - proceedWithHidingObjectTypeWidget() - val details = orchestrator.stores.details.current() - val wrapper = ObjectWrapper.Basic(details.details[context]?.map ?: emptyMap()) - if (wrapper.internalFlags.contains(InternalFlags.ShouldSelectTemplate)) { - if (wrapper.type.isNotEmpty()) { - proceedWithTemplateSelection(typeId = wrapper.type.first()) - } - } + sendHideObjectTypeWidgetEvent() + proceedWithCheckingInternalFlagShouldSelectTemplate() } private fun proceedWithShowingObjectTypesWidget() { val restrictions = orchestrator.stores.objectRestrictions.current() if (restrictions.contains(ObjectRestriction.TYPE_CHANGE)) { + Timber.d("proceedWithShowingObjectTypesWidget, type change is restricted") return } - val details = orchestrator.stores.details.current() - val objectDetails = ObjectWrapper.Basic(details.details[context]?.map ?: emptyMap()) - - val internalFlags = objectDetails.internalFlags - if (internalFlags.contains(InternalFlags.ShouldSelectType)) { - proceedWithGettingObjectTypesForObjectTypeWidget() - } + proceedWithCheckingInternalFlagShouldSelectType() } private fun proceedWithGettingObjectTypesForObjectTypeWidget() { @@ -5959,10 +5937,6 @@ class EditorViewModel( ) } - private fun proceedWithHidingObjectTypeWidget() { - controlPanelInteractor.onEvent(ControlPanelMachine.Event.ObjectTypesWidgetEvent.Hide) - } - private fun proceedWithOpeningSelectingObjectTypeScreen() { val excludeTypes = orchestrator.stores.details.current().details[context]?.type val command = if (isObjectTypesWidgetVisible) { @@ -5976,6 +5950,10 @@ class EditorViewModel( } dispatch(command) } + + private fun sendHideObjectTypeWidgetEvent() { + if (isObjectTypesWidgetVisible) controlPanelInteractor.onEvent(ObjectTypesWidgetEvent.Hide) + } //endregion //region OBJECT APPEARANCE SETTING @@ -6186,23 +6164,28 @@ class EditorViewModel( viewModelScope.launch { onEvent(SelectTemplateEvent.OnSkipped) } } - private fun proceedWithTemplateSelection(typeId: Id) { + private fun proceedWithStartTemplateEvent(objTypeId: Id) { viewModelScope.launch { - val objType = storeOfObjectTypes.get(typeId) + val objType = storeOfObjectTypes.get(objTypeId) if (objType != null) { onEvent( SelectTemplateEvent.OnStart( ctx = context, - type = typeId, + type = objTypeId, typeName = objType.name.orEmpty() ) ) } else { - Timber.e("Error while getting object type from storeOfObjectTypes by id: $typeId") + Timber.e("Error while getting object type from storeOfObjectTypes by id: $objTypeId") } } } + private fun getObjectTypeFromDetails(): Id? { + val details = orchestrator.stores.details.current() + val wrapper = ObjectWrapper.Basic(details.details[context]?.map ?: emptyMap()) + return wrapper.getProperType() + } //endregion //region SIMPLE TABLES @@ -6892,6 +6875,84 @@ class EditorViewModel( } } //endregion + + //region INTERNAL FLAGS + private fun proceedWithCheckingInternalFlagShouldSelectType() { + val internalFlags = getInternalFlagsFromDetails() + if (internalFlags.contains(InternalFlags.ShouldSelectType)) { + //We use this flag to show object type widget and then we don't need it anymore + proceedWithGettingObjectTypesForObjectTypeWidget() + proceedWithOptOutTypeInternalFlag() + } else { + Timber.d("Object doesn't have internal flag: ShouldSelectType") + } + } + + private fun proceedWithCheckingInternalFlagShouldSelectTemplate(objTypeId: Id? = null) { + val internalFlags = getInternalFlagsFromDetails() + if (internalFlags.contains(InternalFlags.ShouldSelectTemplate)) { + //We use this flag to show template widget and then we don't need it anymore + val properObjTypeId = objTypeId ?: getObjectTypeFromDetails() ?: return + proceedWithStartTemplateEvent(objTypeId = properObjTypeId) + proceedWithOptOutTemplateInternalFlag() + } else { + Timber.d("Object doesn't have internal flag: ShouldSelectTemplate") + } + } + + private fun proceedWithOptOutTypeInternalFlag() { + val internalFlags = getInternalFlagsFromDetails() + if (!internalFlags.contains(InternalFlags.ShouldSelectType)) return + val flagsWithoutType = filterOutInternalFlags( + flags = internalFlags, + out = InternalFlags.ShouldSelectType + ) + updateFlagsAndProceed( + flags = flagsWithoutType, + action = this::sendHideObjectTypeWidgetEvent + ) + } + + private fun proceedWithOptOutTemplateInternalFlag() { + val internalFlags = getInternalFlagsFromDetails() + if (!internalFlags.contains(InternalFlags.ShouldSelectTemplate)) return + val flagsWithoutTemplate = filterOutInternalFlags( + flags = internalFlags, + out = InternalFlags.ShouldSelectTemplate + ) + updateFlagsAndProceed(flags = flagsWithoutTemplate) + } + + private fun getInternalFlagsFromDetails(): List { + val details = orchestrator.stores.details.current() + val obj = ObjectWrapper.Basic(details.details[context]?.map ?: emptyMap()) + return obj.internalFlags + } + + private fun filterOutInternalFlags(flags: List, out: InternalFlags): List { + return flags.filter { it != out } + } + + private fun updateFlagsAndProceed(flags: List, action: () -> Unit = {}) { + viewModelScope.launch { + val params = SetObjectInternalFlags.Params( + ctx = context, + flags = flags + ) + setObjectInternalFlags.async(params).fold( + onSuccess = { + dispatcher.send(it) + Timber.d("Internal flags updated") + action.invoke() + }, + onFailure = { + Timber.e(it, "Error while updating internal flags") + action.invoke() + } + ) + } + } + //endregion } private const val NO_POSITION = -1 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 cd539f2c47..860ec65028 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 @@ -18,6 +18,7 @@ import com.anytypeio.anytype.domain.launch.GetDefaultPageType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations @@ -79,7 +80,8 @@ open class EditorViewModelFactory( private val getObjectTypes: GetObjectTypes, private val objectToCollection: ConvertObjectToCollection, private val interceptFileLimitEvents: InterceptFileLimitEvents, - private val addRelationToObject: AddRelationToObject + private val addRelationToObject: AddRelationToObject, + private val setObjectInternalFlags: SetObjectInternalFlags ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -120,7 +122,8 @@ open class EditorViewModelFactory( workspaceManager = workspaceManager, getObjectTypes = getObjectTypes, interceptFileLimitEvents = interceptFileLimitEvents, - addRelationToObject = addRelationToObject + addRelationToObject = addRelationToObject, + setObjectInternalFlags = setObjectInternalFlags ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateSelectViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateSelectViewModel.kt index 1b13ed76e7..213aeeb7ca 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateSelectViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/templates/TemplateSelectViewModel.kt @@ -127,6 +127,10 @@ class TemplateSelectViewModel( } } + fun onCancelButtonClicked() { + isDismissed.value = true + } + class Factory @Inject constructor( private val applyTemplate: ApplyTemplate, private val getTemplates: GetTemplates, @@ -149,7 +153,6 @@ class TemplateSelectViewModel( ) : ViewState() object Init : ViewState() - object ErrorGettingType : ViewState() } } \ 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 28e78146f4..2b050d0826 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 @@ -61,6 +61,7 @@ import com.anytypeio.anytype.domain.launch.GetDefaultPageType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations @@ -352,6 +353,7 @@ open class EditorViewModelTest { private lateinit var objectToSet: ConvertObjectToSet private lateinit var clearBlockContent: ClearBlockContent private lateinit var clearBlockStyle: ClearBlockStyle + private lateinit var setObjectInternalFlags: SetObjectInternalFlags val root = MockDataFactory.randomUuid() @@ -3812,6 +3814,7 @@ open class EditorViewModelTest { clearBlockContent = ClearBlockContent(repo) clearBlockStyle = ClearBlockStyle(repo) interceptFileLimitEvents = InterceptFileLimitEvents(fileLimitsEventChannel, dispatchers) + setObjectInternalFlags = SetObjectInternalFlags(repo, dispatchers) workspaceManager = WorkspaceManager.DefaultWorkspaceManager() runBlocking { @@ -3904,7 +3907,8 @@ open class EditorViewModelTest { workspaceManager = workspaceManager, getObjectTypes = getObjectTypes, interceptFileLimitEvents = interceptFileLimitEvents, - addRelationToObject = addRelationToObject + addRelationToObject = addRelationToObject, + setObjectInternalFlags = setObjectInternalFlags ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt index d7325102fd..561eb02b1d 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorInternalFlagsTest.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.StubHeader import com.anytypeio.anytype.core_models.StubSmartBlock import com.anytypeio.anytype.core_models.StubTitle +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule import kotlin.test.assertEquals import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -19,6 +20,10 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.MockitoAnnotations +import org.mockito.kotlin.times +import org.mockito.kotlin.verifyBlocking +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions @ExperimentalCoroutinesApi class EditorInternalFlagsTest : EditorPresentationTestSetup() { @@ -99,4 +104,203 @@ class EditorInternalFlagsTest : EditorPresentationTestSetup() { assertEquals(expected = expectedFlags, actual = actualFlags) } + + @Test + fun `should remove type flag on show object event with type flag in details`() = runTest { + val title = StubTitle() + val header = StubHeader(children = listOf(title.id)) + val page = StubSmartBlock(id = root, children = listOf(header.id)) + val document = listOf(page, header, title) + stubInterceptEvents() + + val detailsList = Block.Details( + details = mapOf( + root to Block.Fields( + mapOf( + Relations.TYPE to ObjectTypeIds.PAGE, + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.INTERNAL_FLAGS to listOf( + InternalFlags.ShouldSelectTemplate.code.toDouble(), + InternalFlags.ShouldEmptyDelete.code.toDouble(), + InternalFlags.ShouldSelectType.code.toDouble(), + ) + ) + ) + ) + ) + stubOpenDocument(document = document, details = detailsList) + stubGetObjectTypes(types = emptyList()) + stubGetDefaultObjectType() + + val vm = buildViewModel() + + stubFileLimitEvents() + stubSetInternalFlags() + + vm.onStart(root) + + advanceUntilIdle() + + verifyBlocking(setObjectInternalFlags, times(1)) { + async( + params = SetObjectInternalFlags.Params( + ctx = root, + flags = listOf( + InternalFlags.ShouldSelectTemplate, + InternalFlags.ShouldEmptyDelete + ) + ) + ) + } + + coroutineTestRule.advanceTime(100) + } + + @Test + fun `should not remove type flag on show object event without type flag in details`() = runTest { + val title = StubTitle() + val header = StubHeader(children = listOf(title.id)) + val page = StubSmartBlock(id = root, children = listOf(header.id)) + val document = listOf(page, header, title) + stubInterceptEvents() + + val detailsList = Block.Details( + details = mapOf( + root to Block.Fields( + mapOf( + Relations.TYPE to ObjectTypeIds.PAGE, + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.INTERNAL_FLAGS to listOf( + InternalFlags.ShouldSelectTemplate.code.toDouble(), + InternalFlags.ShouldEmptyDelete.code.toDouble() + ) + ) + ) + ) + ) + stubOpenDocument(document = document, details = detailsList) + stubGetObjectTypes(types = emptyList()) + stubGetDefaultObjectType() + + val vm = buildViewModel() + + stubFileLimitEvents() + stubSetInternalFlags() + + vm.onStart(root) + + advanceUntilIdle() + + verifyNoInteractions(setObjectInternalFlags) + + coroutineTestRule.advanceTime(100) + } + + @Test + fun `should remove template flag on start template selection widget`() = runTest { + val title = StubTitle() + val header = StubHeader(children = listOf(title.id)) + val page = StubSmartBlock(id = root, children = listOf(header.id)) + val document = listOf(page, header, title) + stubInterceptEvents() + + val detailsList = Block.Details( + details = mapOf( + root to Block.Fields( + mapOf( + Relations.TYPE to ObjectTypeIds.PAGE, + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.INTERNAL_FLAGS to listOf( + InternalFlags.ShouldSelectTemplate.code.toDouble(), + InternalFlags.ShouldEmptyDelete.code.toDouble(), + ) + ) + ) + ) + ) + stubOpenDocument(document = document, details = detailsList) + stubGetObjectTypes(types = emptyList()) + stubGetDefaultObjectType() + + val vm = buildViewModel() + + stubFileLimitEvents() + stubSetInternalFlags() + + vm.onStart(root) + + advanceUntilIdle() + vm.onObjectTypesWidgetDoneClicked() + + advanceUntilIdle() + + verifyBlocking(setObjectInternalFlags, times(1)) { + async( + params = SetObjectInternalFlags.Params( + ctx = root, + flags = listOf( + InternalFlags.ShouldEmptyDelete + ) + ) + ) + } + + coroutineTestRule.advanceTime(100) + } + + @Test + fun `should not remove template flag on start template selection widget when flag isn't present`() = runTest { + val title = StubTitle() + val header = StubHeader(children = listOf(title.id)) + val page = StubSmartBlock(id = root, children = listOf(header.id)) + val document = listOf(page, header, title) + stubInterceptEvents() + + val detailsList = Block.Details( + details = mapOf( + root to Block.Fields( + mapOf( + Relations.TYPE to ObjectTypeIds.PAGE, + Relations.LAYOUT to ObjectType.Layout.BASIC.code.toDouble(), + Relations.INTERNAL_FLAGS to listOf( + InternalFlags.ShouldSelectType.code.toDouble(), + InternalFlags.ShouldEmptyDelete.code.toDouble(), + ) + ) + ) + ) + ) + stubOpenDocument(document = document, details = detailsList) + stubGetObjectTypes(types = emptyList()) + stubGetDefaultObjectType() + + val vm = buildViewModel() + + stubFileLimitEvents() + stubSetInternalFlags() + + vm.onStart(root) + + advanceUntilIdle() + vm.onObjectTypesWidgetDoneClicked() + + advanceUntilIdle() + + verifyBlocking(setObjectInternalFlags, times(1)) { + async( + params = SetObjectInternalFlags.Params( + ctx = root, + flags = listOf( + InternalFlags.ShouldEmptyDelete + ) + ) + ) + } + + advanceUntilIdle() + + verifyNoMoreInteractions(setObjectInternalFlags) + + coroutineTestRule.advanceTime(100) + } } \ 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 83b58728ae..b998548059 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 @@ -50,6 +50,7 @@ import com.anytypeio.anytype.domain.launch.GetDefaultPageType import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet +import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations @@ -346,6 +347,9 @@ open class EditorPresentationTestSetup { lateinit var fileLimitsEventChannel: FileLimitsEventChannel lateinit var interceptFileLimitEvents: InterceptFileLimitEvents + @Mock + lateinit var setObjectInternalFlags: SetObjectInternalFlags + open fun buildViewModel(urlBuilder: UrlBuilder = builder): EditorViewModel { val storage = Editor.Storage() @@ -467,7 +471,8 @@ open class EditorPresentationTestSetup { workspaceManager = workspaceManager, getObjectTypes = getObjectTypes, interceptFileLimitEvents = interceptFileLimitEvents, - addRelationToObject = addRelationToObject + addRelationToObject = addRelationToObject, + setObjectInternalFlags = setObjectInternalFlags ) } @@ -746,4 +751,16 @@ open class EditorPresentationTestSetup { } doReturn Resultat.success(types) } } + + fun stubFileLimitEvents() { + interceptFileLimitEvents.stub { + onBlocking { run(Unit) } doReturn emptyFlow() + } + } + + fun stubSetInternalFlags() { + setObjectInternalFlags.stub { + onBlocking { async(any()) } doReturn Resultat.success(Payload(root, emptyList())) + } + } } \ No newline at end of file