diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt index 72b11cb683..062355949d 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectMenuDI.kt @@ -25,6 +25,7 @@ import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.OpenPage import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations +import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.domain.templates.CreateTemplateFromObject import com.anytypeio.anytype.domain.widgets.CreateWidget @@ -49,11 +50,12 @@ import com.anytypeio.anytype.ui.sets.ObjectSetMenuFragment import dagger.Module import dagger.Provides import dagger.Subcomponent +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf @Subcomponent(modules = [ObjectMenuModuleBase::class, ObjectMenuModule::class]) @@ -128,7 +130,9 @@ object ObjectMenuModule { getSpaceInviteLink: GetSpaceInviteLink, addToFeaturedRelations: AddToFeaturedRelations, removeFromFeaturedRelations: RemoveFromFeaturedRelations, - userPermissionProvider: UserPermissionProvider + userPermissionProvider: UserPermissionProvider, + deleteRelationFromObject: DeleteRelationFromObject, + objectMenuOptionsProvider: ObjectMenuOptionsProvider ): ObjectMenuViewModel.Factory = ObjectMenuViewModel.Factory( setObjectIsArchived = setObjectIsArchived, duplicateObject = duplicateObject, @@ -140,7 +144,7 @@ object ObjectMenuModule { dispatcher = dispatcher, updateFields = updateFields, delegator = delegator, - menuOptionsProvider = createMenuOptionsProvider(storage), + menuOptionsProvider = objectMenuOptionsProvider, addObjectToCollection = addObjectToCollection, createTemplateFromObject = createTemplateFromObject, setObjectDetails = setObjectDetails, @@ -156,7 +160,19 @@ object ObjectMenuModule { spaceViewSubscriptionContainer = spaceViewSubscriptionContainer, addToFeaturedRelations = addToFeaturedRelations, removeFromFeaturedRelations = removeFromFeaturedRelations, - userPermissionProvider = userPermissionProvider + userPermissionProvider = userPermissionProvider, + deleteRelationFromObject = deleteRelationFromObject + ) + + @JvmStatic + @Provides + @PerDialog + fun provideDeleteRelationFromObject( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): DeleteRelationFromObject = DeleteRelationFromObject( + repo = repo, + dispatchers = dispatchers ) @JvmStatic @@ -170,11 +186,15 @@ object ObjectMenuModule { dispatchers ) + @Provides + @PerDialog @JvmStatic - private fun createMenuOptionsProvider(storage: Editor.Storage): ObjectMenuOptionsProvider = + fun provideMenuOptionsProvider( + storage: Editor.Storage + ): ObjectMenuOptionsProvider = ObjectMenuOptionsProviderImpl( objectViewDetailsFlow = storage.details.stream(), - restrictions = storage.objectRestrictions.stream() + hasObjectLayoutConflict = storage.hasLayoutOrRelationConflict.stream() ) @JvmStatic @@ -240,6 +260,13 @@ object ObjectMenuModule { @Module object ObjectSetMenuModule { + @JvmStatic + @Provides + @PerDialog + fun provideUpdateFieldsUseCase( + repo: BlockRepository + ): UpdateFields = UpdateFields(repo) + @JvmStatic @Provides @PerDialog @@ -250,6 +277,7 @@ object ObjectSetMenuModule { urlBuilder: UrlBuilder, analytics: Analytics, state: MutableStateFlow, + updateFields: UpdateFields, featureToggles: FeatureToggles, dispatcher: Dispatcher, addObjectToCollection: AddObjectToCollection, @@ -266,7 +294,10 @@ object ObjectSetMenuModule { spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer, addToFeaturedRelations: AddToFeaturedRelations, removeFromFeaturedRelations: RemoveFromFeaturedRelations, - userPermissionProvider: UserPermissionProvider + userPermissionProvider: UserPermissionProvider, + deleteRelationFromObject: DeleteRelationFromObject, + setObjectDetails: SetObjectDetails, + objectMenuOptionsProvider: ObjectMenuOptionsProvider ): ObjectSetMenuViewModel.Factory = ObjectSetMenuViewModel.Factory( setObjectListIsArchived = setObjectIsArchived, addBackLinkToObject = addBackLinkToObject, @@ -276,7 +307,7 @@ object ObjectSetMenuModule { analytics = analytics, objectState = state, dispatcher = dispatcher, - menuOptionsProvider = createMenuOptionsProvider(state), + menuOptionsProvider = objectMenuOptionsProvider, addObjectToCollection = addObjectToCollection, debugGoroutinesShareDownloader = debugGoroutinesShareDownloader, createWidget = createWidget, @@ -290,7 +321,21 @@ object ObjectSetMenuModule { spaceViewSubscriptionContainer = spaceViewSubscriptionContainer, addToFeaturedRelations = addToFeaturedRelations, removeFromFeaturedRelations = removeFromFeaturedRelations, - userPermissionProvider = userPermissionProvider + userPermissionProvider = userPermissionProvider, + deleteRelationFromObject = deleteRelationFromObject, + updateFields = updateFields, + setObjectDetails = setObjectDetails + ) + + @JvmStatic + @Provides + @PerDialog + fun provideDeleteRelationFromObject( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): DeleteRelationFromObject = DeleteRelationFromObject( + repo = repo, + dispatchers = dispatchers ) @JvmStatic @@ -345,22 +390,50 @@ object ObjectSetMenuModule { dispatchers: AppCoroutineDispatchers ): SetObjectListIsFavorite = SetObjectListIsFavorite(repo = repo, dispatchers = dispatchers) + @OptIn(ExperimentalCoroutinesApi::class) @JvmStatic - private fun createMenuOptionsProvider( - state: StateFlow, + @Provides + @PerDialog + fun provideMenuOptionsProvider( + state: MutableStateFlow, ): ObjectMenuOptionsProvider { - return when (val currentState = state.value) { - is ObjectState.DataView -> ObjectMenuOptionsProviderImpl( - objectViewDetailsFlow = state.map { currentState.details }.distinctUntilChanged(), - restrictions = state.map { currentState.objectRestrictions }.distinctUntilChanged(), - ) - else -> ObjectMenuOptionsProviderImpl( - objectViewDetailsFlow = emptyFlow(), - restrictions = emptyFlow(), - ) - } + val objectViewDetailsFlow = state + .flatMapLatest { currentState -> + if (currentState is ObjectState.DataView) { + flowOf(currentState.details) + } else { + emptyFlow() + } + } + .distinctUntilChanged() + + val hasObjectLayoutConflictFlow = state + .flatMapLatest { currentState -> + if (currentState is ObjectState.DataView) { + flowOf(currentState.hasObjectLayoutConflict) + } else { + emptyFlow() + } + } + .distinctUntilChanged() + + return ObjectMenuOptionsProviderImpl( + objectViewDetailsFlow = objectViewDetailsFlow, + hasObjectLayoutConflict = hasObjectLayoutConflictFlow + ) } + @JvmStatic + @Provides + @PerDialog + fun provideSetObjectDetails( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): SetObjectDetails = SetObjectDetails( + repo, + dispatchers + ) + @JvmStatic @Provides @PerDialog diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt index 83f28f6106..d0c1342b52 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectRelationListDI.kt @@ -106,8 +106,11 @@ object ObjectRelationListModule { @JvmStatic @Provides @PerModal - fun deleteRelationFromObject(repo: BlockRepository): DeleteRelationFromObject = - DeleteRelationFromObject(repo) + fun deleteRelationFromObject( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): DeleteRelationFromObject = + DeleteRelationFromObject(repo, dispatchers) @JvmStatic @Provides diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt index ef3c7b3c83..b32f41ef93 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/sheets/ObjectMenuBaseFragment.kt @@ -87,7 +87,12 @@ abstract class ObjectMenuBaseFragment : setContent { ConflictScreen( showScreen = vm.showLayoutConflictScreen.collectAsStateWithLifecycle().value, - onResetClick = { }, + onResetClick = { + vm.onResetToDefaultLayout( + ctx = ctx, + space = space + ) + }, onDismiss = { vm.onHideConflictScreen() } ) } @@ -133,6 +138,7 @@ abstract class ObjectMenuBaseFragment : val relationsVisibility = options.hasRelations.toVisibility() val historyVisibility = options.hasHistory.toVisibility() val objectDiagnosticsVisibility = options.hasDiagnosticsVisibility.toVisibility() + val objectLayoutConflictVisibility = options.hasObjectLayoutConflict.toVisibility() if (options.hasDescriptionShow) { binding.optionDescription.setAction(setAsHide = false) @@ -150,6 +156,7 @@ abstract class ObjectMenuBaseFragment : binding.historyDivider.visibility = historyVisibility binding.objectDiagnostics.visibility = objectDiagnosticsVisibility binding.objectDiagnosticsDivider.visibility = objectDiagnosticsVisibility + binding.objectLayoutConflict.visibility = objectLayoutConflictVisibility } private fun execute(command: ObjectMenuViewModelBase.Command) { diff --git a/app/src/main/res/layout/fragment_object_menu.xml b/app/src/main/res/layout/fragment_object_menu.xml index 658669995e..26c1fd69eb 100644 --- a/app/src/main/res/layout/fragment_object_menu.xml +++ b/app/src/main/res/layout/fragment_object_menu.xml @@ -173,7 +173,7 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:background="@drawable/default_ripple" - android:visibility="visible" + android:visibility="gone" app:icon="@drawable/ic_attention_24" app:showArrow="false" app:title="@string/object_conflict_menu_item_title" diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 8794c892c5..aa39f269ae 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -161,6 +161,9 @@ android:id="@+id/actionExitToSpaceWidgets" app:popUpTo="@+id/homeScreen" app:popUpToInclusive="false" /> + ): Payload { + return remote.deleteRelationFromObject(ctx = ctx, relations = relations) } override suspend fun debugSpace(space: SpaceId): String = remote.debugSpace(space) 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 ddba8f6881..89d51fa7f8 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 @@ -192,7 +192,7 @@ interface BlockRemote { suspend fun cancelObjectSearchSubscription(subscriptions: List) suspend fun addRelationToObject(ctx: Id, relation: Key): Payload? - suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload + suspend fun deleteRelationFromObject(ctx: Id, relations: List): Payload suspend fun debugSpace(space: SpaceId): String 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 f893fa119a..9f2e7a254b 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 @@ -232,7 +232,7 @@ interface BlockRepository { suspend fun cancelObjectSearchSubscription(subscriptions: List) suspend fun addRelationToObject(ctx: Id, relation: Key): Payload? - suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload + suspend fun deleteRelationFromObject(ctx: Id, relations: List): Payload suspend fun debugSpace(space: SpaceId): String diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/relations/DeleteRelationFromObject.kt b/domain/src/main/java/com/anytypeio/anytype/domain/relations/DeleteRelationFromObject.kt index 28e707dc03..1bc7b8f2fc 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/relations/DeleteRelationFromObject.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/relations/DeleteRelationFromObject.kt @@ -3,29 +3,31 @@ package com.anytypeio.anytype.domain.relations import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.Payload -import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor import com.anytypeio.anytype.domain.block.repo.BlockRepository /** * Use-case for deleting a relation from an object. */ class DeleteRelationFromObject( - private val repo: BlockRepository -) : BaseUseCase() { + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { - override suspend fun run(params: Params) = safe { - repo.deleteRelationFromObject( + override suspend fun doWork(params: Params): Payload { + return repo.deleteRelationFromObject( ctx = params.ctx, - relation = params.relation + relations = params.relations ) } /** * @param ctx id of the object - * @param relation relation key + * @param relations relation keys */ class Params( val ctx: Id, - val relation: Key + val relations: 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 853e052a91..578f6704d5 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 @@ -420,10 +420,10 @@ class BlockMiddleware( override suspend fun deleteRelationFromObject( ctx: Id, - relation: Id + relations: List ): Payload = middleware.objectRelationDelete( ctx = ctx, - relation = relation + relations = relations ) override suspend fun debugSpace(space: SpaceId): String = middleware.debugSpaceSummary(space) 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 1eaa9d2af9..7aff5bc7aa 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 @@ -1171,10 +1171,10 @@ class Middleware @Inject constructor( } @Throws(Exception::class) - fun objectRelationDelete(ctx: Id, relation: Key): Payload { + fun objectRelationDelete(ctx: Id, relations: List): Payload { val request = Rpc.ObjectRelation.Delete.Request( contextId = ctx, - relationKeys = listOf(relation) + relationKeys = relations ) logRequestIfDebug(request) val (response, time) = measureTimedValue { service.objectRelationDelete(request) } 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 773be45d2d..0474e3651e 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 @@ -270,6 +270,7 @@ import com.anytypeio.anytype.presentation.navigation.leftButtonClickAnalytics import com.anytypeio.anytype.presentation.objects.getCreateObjectParams import com.anytypeio.anytype.presentation.objects.getObjectTypeViewsForSBPage import com.anytypeio.anytype.presentation.objects.getProperType +import com.anytypeio.anytype.presentation.objects.hasLayoutConflict import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed import com.anytypeio.anytype.presentation.objects.toViews import com.anytypeio.anytype.presentation.relations.ObjectRelationView @@ -837,24 +838,13 @@ class EditorViewModel( newBlocks: List ) { - if (currentObject == null) { - orchestrator.stores.hasLayoutOrRelationConflict.update(false) - return - } - - val featuredBlock = newBlocks.firstOrNull { it is BlockView.FeaturedRelation } - val hasFeaturedPropertiesConflict = - (featuredBlock as? BlockView.FeaturedRelation)?.hasFeaturePropertiesConflict == true - val currentObjectType = storeOfObjectTypes.getTypeOfObject(currentObject) - - val objectLayout = currentObject.layout - val hasObjectLayoutConflict = objectLayout != null - && objectLayout != currentObjectType?.recommendedLayout - - orchestrator.stores.hasLayoutOrRelationConflict.update( - hasObjectLayoutConflict - || hasFeaturedPropertiesConflict + val hasConflict = hasLayoutConflict( + currentObject = currentObject, + blocks = newBlocks, + storeOfObjectTypes = storeOfObjectTypes ) + + orchestrator.stores.hasLayoutOrRelationConflict.update(hasConflict) } private fun refreshTableToolbar() { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/FeaturedPropertiesExt.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/FeaturedPropertiesExt.kt index abe2f42000..61b45c9a60 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/FeaturedPropertiesExt.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/FeaturedPropertiesExt.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.permissions.toObjectPermissionsForTypes import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations +import com.anytypeio.anytype.domain.objects.getTypeOfObject import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.extension.getObject @@ -265,4 +266,28 @@ private suspend fun ObjectWrapper.Relation.toView( ) } } +} + +suspend fun hasLayoutConflict( + blocks: List, + currentObject: ObjectWrapper.Basic?, + storeOfObjectTypes: StoreOfObjectTypes, +): Boolean { + + val featuredBlock = blocks.firstOrNull { it is BlockView.FeaturedRelation } + + if (currentObject == null) { + return false + } + + val hasFeaturedPropertiesConflict = + (featuredBlock as? BlockView.FeaturedRelation)?.hasFeaturePropertiesConflict == true + val currentObjectType = storeOfObjectTypes.getTypeOfObject(currentObject) + + val objectLayout = currentObject.layout + val hasObjectLayoutConflict = + objectLayout != null && currentObjectType != null && currentObjectType.isValid + && objectLayout != currentObjectType.recommendedLayout + + return hasObjectLayoutConflict || hasFeaturedPropertiesConflict } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt index 9863c8bd98..671a6fe1a0 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProvider.kt @@ -11,7 +11,8 @@ interface ObjectMenuOptionsProvider { val hasRelations: Boolean, val hasDiagnosticsVisibility: Boolean, val hasHistory: Boolean, - val hasDescriptionShow: Boolean + val hasDescriptionShow: Boolean, + val hasObjectLayoutConflict: Boolean ) { companion object { val ALL = Options( @@ -20,7 +21,8 @@ interface ObjectMenuOptionsProvider { hasRelations = true, hasDiagnosticsVisibility = true, hasHistory = true, - hasDescriptionShow = true + hasDescriptionShow = true, + hasObjectLayoutConflict = false ) val NONE = Options( hasIcon = false, @@ -28,7 +30,8 @@ interface ObjectMenuOptionsProvider { hasRelations = false, hasDiagnosticsVisibility = false, hasHistory = false, - hasDescriptionShow = false + hasDescriptionShow = false, + hasObjectLayoutConflict = false ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt index 2b3eb3d30d..9e1332f86a 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImpl.kt @@ -2,10 +2,9 @@ package com.anytypeio.anytype.presentation.objects.menu import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType -import com.anytypeio.anytype.core_models.SupportedLayouts -import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.core_models.ObjectViewDetails import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.SupportedLayouts import com.anytypeio.anytype.presentation.extension.getObject import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider.Options import kotlinx.coroutines.flow.Flow @@ -16,7 +15,7 @@ import timber.log.Timber class ObjectMenuOptionsProviderImpl( private val objectViewDetailsFlow: Flow, - private val restrictions: Flow> + private val hasObjectLayoutConflict: Flow ) : ObjectMenuOptionsProvider { private fun observeLayout(ctx: Id): Flow = objectViewDetailsFlow @@ -41,16 +40,20 @@ class ObjectMenuOptionsProviderImpl( return@map featuredRelations?.any { it == Relations.DESCRIPTION } == true } + private fun observeHasObjectLayoutConflict(): Flow = hasObjectLayoutConflict + override fun provide(ctx: Id, isLocked: Boolean, isReadOnly: Boolean): Flow { return combine( observeLayout(ctx), - observeFeatureFieldsContainsDescription(ctx) - ) { layout, featuredContainsDescription -> + observeFeatureFieldsContainsDescription(ctx), + observeHasObjectLayoutConflict() + ) { layout, featuredContainsDescription, hasObjectLayoutConflict -> createOptions( layout = layout, isLocked = isLocked, isReadOnly = isReadOnly, - featuredContainsDescription = featuredContainsDescription + featuredContainsDescription = featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) } } @@ -59,7 +62,8 @@ class ObjectMenuOptionsProviderImpl( layout: ObjectType.Layout?, isLocked: Boolean, isReadOnly: Boolean, - featuredContainsDescription: Boolean + featuredContainsDescription: Boolean, + hasObjectLayoutConflict: Boolean ): Options { val hasIcon = !isLocked && !isReadOnly val hasCover = !isLocked && !isReadOnly @@ -71,7 +75,8 @@ class ObjectMenuOptionsProviderImpl( hasDiagnosticsVisibility = true, hasHistory = false, hasRelations = false, - hasDescriptionShow = !featuredContainsDescription + hasDescriptionShow = !featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) in SupportedLayouts.systemLayouts -> Options.NONE @@ -81,7 +86,8 @@ class ObjectMenuOptionsProviderImpl( hasCover = false, hasDiagnosticsVisibility = true, hasHistory = false, - hasDescriptionShow = !featuredContainsDescription + hasDescriptionShow = !featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) } @@ -92,7 +98,8 @@ class ObjectMenuOptionsProviderImpl( hasCover = hasCover, hasDiagnosticsVisibility = true, hasHistory = !isLocked && !isReadOnly, - hasDescriptionShow = !featuredContainsDescription + hasDescriptionShow = !featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) } @@ -103,7 +110,8 @@ class ObjectMenuOptionsProviderImpl( hasCover = hasCover, hasDiagnosticsVisibility = true, hasHistory = !isLocked && !isReadOnly, - hasDescriptionShow = !featuredContainsDescription + hasDescriptionShow = !featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) ObjectType.Layout.TODO -> Options( @@ -112,7 +120,8 @@ class ObjectMenuOptionsProviderImpl( hasRelations = true, hasDiagnosticsVisibility = true, hasHistory = !isLocked && !isReadOnly, - hasDescriptionShow = !featuredContainsDescription + hasDescriptionShow = !featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) ObjectType.Layout.NOTE -> Options( @@ -121,17 +130,20 @@ class ObjectMenuOptionsProviderImpl( hasRelations = true, hasDiagnosticsVisibility = true, hasHistory = !isLocked && !isReadOnly, - hasDescriptionShow = !featuredContainsDescription + hasDescriptionShow = !featuredContainsDescription, + hasObjectLayoutConflict = hasObjectLayoutConflict ) else -> Options.NONE.copy( - hasDiagnosticsVisibility = true + hasDiagnosticsVisibility = true, + hasObjectLayoutConflict = hasObjectLayoutConflict ) } } else { // unknown layout Options.NONE.copy( - hasDiagnosticsVisibility = true + hasDiagnosticsVisibility = true, + hasObjectLayoutConflict = hasObjectLayoutConflict ) } return options diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt index 3c04ef7c3e..cb909dc383 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModel.kt @@ -40,6 +40,7 @@ import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations +import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.presentation.extension.getObject import com.anytypeio.anytype.presentation.extension.getTypeObject @@ -80,7 +81,8 @@ class ObjectMenuViewModel( private val getSpaceInviteLink: GetSpaceInviteLink, private val addToFeaturedRelations: AddToFeaturedRelations, private val removeFromFeaturedRelations: RemoveFromFeaturedRelations, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val deleteRelationFromObject: DeleteRelationFromObject ) : ObjectMenuViewModelBase( setObjectIsArchived = setObjectIsArchived, addBackLinkToObject = addBackLinkToObject, @@ -521,6 +523,79 @@ class ObjectMenuViewModel( return doc.fields.isLocked ?: false } + override fun onResetToDefaultLayout( + ctx: Id, + space: Id + ) { + + showLayoutConflictScreen.value = false + + val currentObject = storage.details.current().getObject(ctx) + val featuredRelations = currentObject?.featuredRelations ?: emptyList() + + viewModelScope.launch { + val params = DeleteRelationFromObject.Params( + ctx = ctx, + relations = listOf( + Relations.LEGACY_LAYOUT, + Relations.LAYOUT_ALIGN + ) + ) + deleteRelationFromObject.async( + params = params + ).fold( + onSuccess = { payload -> + dispatcher.send(payload) + }, + onFailure = { + Timber.e(it, "Error while resetting layout to default") + } + ) + } + + viewModelScope.launch{ + + val rootBlockFields = storage.document.get().find { it.id == ctx }?.fields + + val params = UpdateFields.Params( + context = ctx, + fields = listOf(Pair(ctx, rootBlockFields?.copy( + map = rootBlockFields.map.toMutableMap().apply { + put("width", null) + } + ) ?: Block.Fields.empty())) + ) + updateFields(params = params + ).process( + success = { payload -> + dispatcher.send(payload) + }, + failure = { + Timber.e(it, "Error while resetting layout to default") + } + ) + } + + viewModelScope.launch { + val params = SetObjectDetails.Params( + ctx = ctx, + details = mapOf( + Relations.FEATURED_RELATIONS to featuredRelations.filter { + it == Relations.DESCRIPTION + }.map { it } + ) + ) + setObjectDetails.async(params).fold( + onSuccess = { + dispatcher.send(it) + }, + onFailure = { + Timber.e(it, "Error while resetting layout to default") + } + ) + } + } + @Suppress("UNCHECKED_CAST") class Factory @Inject constructor( private val duplicateObject: DuplicateObject, @@ -549,7 +624,8 @@ class ObjectMenuViewModel( private val spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer, private val addToFeaturedRelations: AddToFeaturedRelations, private val removeFromFeaturedRelations: RemoveFromFeaturedRelations, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val deleteRelationFromObject: DeleteRelationFromObject ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return ObjectMenuViewModel( @@ -579,7 +655,8 @@ class ObjectMenuViewModel( spaceViewSubscriptionContainer = spaceViewSubscriptionContainer, addToFeaturedRelations = addToFeaturedRelations, removeFromFeaturedRelations = removeFromFeaturedRelations, - userPermissionProvider = userPermissionProvider + userPermissionProvider = userPermissionProvider, + deleteRelationFromObject = deleteRelationFromObject ) as T } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt index 08685fdd54..6f42d54a69 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuViewModelBase.kt @@ -86,7 +86,8 @@ abstract class ObjectMenuViewModelBase( hasRelations = false, hasDiagnosticsVisibility = false, hasHistory = false, - hasDescriptionShow = false + hasDescriptionShow = false, + hasObjectLayoutConflict = false ) ) val options: Flow = _options @@ -95,6 +96,7 @@ abstract class ObjectMenuViewModelBase( abstract fun onCoverClicked(ctx: Id, space: Id) abstract fun onDescriptionClicked(ctx: Id, space: Id) abstract fun onRelationsClicked() + abstract fun onResetToDefaultLayout(ctx: Id, space: Id) val showLayoutConflictScreen = MutableStateFlow(false) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt index 5d9c314e03..54ec0fa6ab 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/objects/menu/ObjectSetMenuViewModel.kt @@ -4,11 +4,14 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.block.interactor.UpdateFields import com.anytypeio.anytype.domain.collections.AddObjectToCollection import com.anytypeio.anytype.domain.dashboard.interactor.SetObjectListIsFavorite import com.anytypeio.anytype.domain.misc.DeepLinkResolver @@ -17,10 +20,12 @@ import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.`object`.DuplicateObject +import com.anytypeio.anytype.domain.`object`.SetObjectDetails import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.AddBackLinkToObject import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations +import com.anytypeio.anytype.domain.relations.DeleteRelationFromObject import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.domain.widgets.CreateWidget import com.anytypeio.anytype.domain.workspace.SpaceManager @@ -62,7 +67,10 @@ class ObjectSetMenuViewModel( getSpaceInviteLink: GetSpaceInviteLink, private val addToFeaturedRelations: AddToFeaturedRelations, private val removeFromFeaturedRelations: RemoveFromFeaturedRelations, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val deleteRelationFromObject: DeleteRelationFromObject, + private val updateFields: UpdateFields, + private val setObjectDetails: SetObjectDetails ) : ObjectMenuViewModelBase( setObjectIsArchived = setObjectIsArchived, addBackLinkToObject = addBackLinkToObject, @@ -113,7 +121,10 @@ class ObjectSetMenuViewModel( private val getSpaceInviteLink: GetSpaceInviteLink, private val addToFeaturedRelations: AddToFeaturedRelations, private val removeFromFeaturedRelations: RemoveFromFeaturedRelations, - private val userPermissionProvider: UserPermissionProvider + private val userPermissionProvider: UserPermissionProvider, + private val deleteRelationFromObject: DeleteRelationFromObject, + private val updateFields: UpdateFields, + private val setObjectDetails: SetObjectDetails ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return ObjectSetMenuViewModel( @@ -139,7 +150,10 @@ class ObjectSetMenuViewModel( getSpaceInviteLink = getSpaceInviteLink, addToFeaturedRelations = addToFeaturedRelations, removeFromFeaturedRelations = removeFromFeaturedRelations, - userPermissionProvider = userPermissionProvider + userPermissionProvider = userPermissionProvider, + deleteRelationFromObject = deleteRelationFromObject, + updateFields = updateFields, + setObjectDetails = setObjectDetails ) as T } } @@ -308,4 +322,32 @@ class ObjectSetMenuViewModel( } } } + + override fun onResetToDefaultLayout( + ctx: Id, + space: Id + ) { + showLayoutConflictScreen.value = false + + val state = objectState.value.dataViewState() ?: return + + val currentObject = state.details.getObject(ctx) + val featuredRelations = currentObject?.featuredRelations ?: emptyList() + + viewModelScope.launch { + val featuredWithoutConflict = featuredRelations.filter { key -> key == Relations.DESCRIPTION } + val params = SetObjectDetails.Params( + ctx = ctx, + details = mapOf(Relations.FEATURED_RELATIONS to featuredWithoutConflict) + ) + setObjectDetails.async(params).fold( + onSuccess = { + dispatcher.send(it) + }, + onFailure = { + Timber.e(it, "Error while resetting layout to default") + } + ) + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt index a620b70474..50504f2546 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/RelationListViewModel.kt @@ -475,14 +475,13 @@ class RelationListViewModel( fun onDeleteClicked(ctx: Id, view: ObjectRelationView) { viewModelScope.launch { - deleteRelationFromObject( - DeleteRelationFromObject.Params( - ctx = ctx, - relation = view.key - ) - ).process( - failure = { Timber.e(it, "Error while deleting relation") }, - success = { + val params = DeleteRelationFromObject.Params( + ctx = ctx, + relations = listOf(view.key) + ) + deleteRelationFromObject.async(params).fold( + onFailure = { Timber.e(it, "Error while deleting relation") }, + onSuccess = { dispatcher.send(it) analytics.sendAnalyticsRelationEvent( eventName = EventsDictionary.relationDelete, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt index 083f15fd42..9921a50aa1 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt @@ -12,6 +12,7 @@ import com.anytypeio.anytype.analytics.props.Props.Companion.OBJ_TYPE_CUSTOM import com.anytypeio.anytype.core_models.DVViewer import com.anytypeio.anytype.core_models.DVViewerCardSize import com.anytypeio.anytype.core_models.DVViewerType +import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.ObjectType @@ -80,6 +81,7 @@ import com.anytypeio.anytype.presentation.extension.getObject import com.anytypeio.anytype.presentation.navigation.NavPanelState import com.anytypeio.anytype.presentation.navigation.leftButtonClickAnalytics import com.anytypeio.anytype.presentation.objects.getCreateObjectParams +import com.anytypeio.anytype.presentation.objects.hasLayoutConflict import com.anytypeio.anytype.presentation.objects.isCreateObjectAllowed import com.anytypeio.anytype.presentation.objects.isTemplatesAllowed import com.anytypeio.anytype.presentation.objects.toFeaturedPropertiesViews @@ -245,7 +247,7 @@ class ObjectSetViewModel( state to permission } .collectLatest { (state, permission) -> - featured.value = toFeaturedPropertiesViews( + val featuredBlock = toFeaturedPropertiesViews( objectId = vmParams.ctx, urlBuilder = urlBuilder, fieldParser = fieldParser, @@ -255,12 +257,14 @@ class ObjectSetViewModel( details = state.details, participantCanEdit = permission?.isOwnerOrEditor() == true ) + featured.value = featuredBlock _header.value = state.header( ctx = vmParams.ctx, urlBuilder = urlBuilder, coverImageHashProvider = coverImageHashProvider, isReadOnlyMode = permission == SpaceMemberPermissions.NO_PERMISSIONS || permission == SpaceMemberPermissions.READER ) + updateLayoutConflictState(featuredBlock = featuredBlock) } } @@ -418,6 +422,33 @@ class ObjectSetViewModel( ) } + private suspend fun updateLayoutConflictState( + featuredBlock: BlockView.FeaturedRelation?, + ) { + val state = stateReducer.state.value.dataViewState() ?: return + val objectWrapper = state.details.getObject(vmParams.ctx) + + val blocks = featuredBlock?.let { listOf(it) } ?: emptyList() + + val hasConflict = hasLayoutConflict( + currentObject = objectWrapper, + blocks = blocks, + storeOfObjectTypes = storeOfObjectTypes + ) + + dispatcher.send( + Payload( + context = vmParams.ctx, + events = listOf( + Event.Command.DataView.UpdateConflictState( + context = vmParams.ctx, + hasConflict = hasConflict + ) + ) + ) + ) + } + fun onStart(view: Id? = null) { Timber.d("onStart, ctx:[${vmParams.ctx}], space:[${vmParams.space}], view:[$view]") if (view != null) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/DefaultObjectStateReducer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/DefaultObjectStateReducer.kt index f4f1b0f671..47bba8b348 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/DefaultObjectStateReducer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/DefaultObjectStateReducer.kt @@ -102,6 +102,9 @@ class DefaultObjectStateReducer : ObjectStateReducer { is Command.AddBlock -> { handleAddBlock(state, event) } + is Command.DataView.UpdateConflictState -> { + handleUpdateConflictState(state, event) + } else -> { Timber.d("Ignoring event: $event") @@ -551,6 +554,24 @@ class DefaultObjectStateReducer : ObjectStateReducer { ObjectState.ErrorLayout -> state } } + + private fun handleUpdateConflictState( + state: ObjectState, + event: Command.DataView.UpdateConflictState + ): ObjectState { + return when (state) { + is ObjectState.DataView.Collection -> state.copy( + hasObjectLayoutConflict = event.hasConflict + ) + is ObjectState.DataView.Set -> state.copy( + hasObjectLayoutConflict = event.hasConflict + ) + is ObjectState.DataView.TypeSet -> state.copy( + hasObjectLayoutConflict = event.hasConflict + ) + else -> state + } + } //endregion private inline fun ObjectState.DataView.updateBlockContent( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/ObjectState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/ObjectState.kt index 9e0c0c0b91..1d8a5686e4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/ObjectState.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/state/ObjectState.kt @@ -26,12 +26,15 @@ sealed class ObjectState { abstract val dataViewBlock: Block abstract val viewers: List + abstract val hasObjectLayoutConflict: Boolean + data class Set( override val root: Id, override val blocks: List = emptyList(), override val details: ObjectViewDetails = ObjectViewDetails.EMPTY, override val objectRestrictions: List = emptyList(), override val dataViewRestrictions: List = emptyList(), + override val hasObjectLayoutConflict: Boolean = false, ) : DataView() { override val isInitialized get() = blocks.any { it.content is DV } @@ -46,6 +49,7 @@ sealed class ObjectState { override val details: ObjectViewDetails = ObjectViewDetails.EMPTY, override val objectRestrictions: List = emptyList(), override val dataViewRestrictions: List = emptyList(), + override val hasObjectLayoutConflict: Boolean = false, ) : DataView() { override val isInitialized get() = blocks.any { it.content is DV } @@ -60,6 +64,7 @@ sealed class ObjectState { override val details: ObjectViewDetails = ObjectViewDetails.EMPTY, override val objectRestrictions: List = emptyList(), override val dataViewRestrictions: List = emptyList(), + override val hasObjectLayoutConflict: Boolean = false, ) : DataView() { override val isInitialized get() = blocks.any { it.content is DV } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt index 33fe219bbe..58fd312715 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/objects/menu/ObjectMenuOptionsProviderImplTest.kt @@ -16,8 +16,8 @@ class ObjectMenuOptionsProviderImplTest { private val objectId: String = "objectId" private val details = MutableStateFlow(ObjectViewDetails.EMPTY) - private val restrictions = MutableStateFlow>(emptyList()) - private val provider = ObjectMenuOptionsProviderImpl(details, restrictions) + private val hasObjectLayoutConflict = MutableStateFlow(false) + private val provider = ObjectMenuOptionsProviderImpl(details, hasObjectLayoutConflict) @Test fun `when layout note - options are layout, relations, history`() { @@ -35,7 +35,8 @@ class ObjectMenuOptionsProviderImplTest { hasDescriptionShow = true, hasRelations = true, hasDiagnosticsVisibility = true, - hasHistory = true + hasHistory = true, + hasObjectLayoutConflict = false ) assertOptions( @@ -59,7 +60,8 @@ class ObjectMenuOptionsProviderImplTest { hasRelations = true, hasDiagnosticsVisibility = true, hasHistory = true, - hasDescriptionShow = true + hasDescriptionShow = true, + hasObjectLayoutConflict = false ) assertOptions( @@ -118,7 +120,8 @@ class ObjectMenuOptionsProviderImplTest { hasRelations = true, hasDiagnosticsVisibility = true, hasHistory = false, - hasDescriptionShow = true + hasDescriptionShow = true, + hasObjectLayoutConflict = false ) ) } @@ -143,7 +146,8 @@ class ObjectMenuOptionsProviderImplTest { hasRelations = true, hasDiagnosticsVisibility = true, hasHistory = false, - hasDescriptionShow = false + hasDescriptionShow = false, + hasObjectLayoutConflict = false ) ) }