1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3409 Primitives | Reset layout conflict (#2217)

This commit is contained in:
Konstantin Ivanov 2025-04-01 10:48:43 +02:00 committed by GitHub
parent 7bec1556cb
commit eb0f7e16a9
Signed by: github
GPG key ID: B5690EEEBB952194
25 changed files with 405 additions and 99 deletions

View file

@ -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<ObjectState>,
updateFields: UpdateFields,
featureToggles: FeatureToggles,
dispatcher: Dispatcher<Payload>,
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<ObjectState>,
@Provides
@PerDialog
fun provideMenuOptionsProvider(
state: MutableStateFlow<ObjectState>,
): 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

View file

@ -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

View file

@ -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) {

View file

@ -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"

View file

@ -161,6 +161,9 @@
android:id="@+id/actionExitToSpaceWidgets"
app:popUpTo="@+id/homeScreen"
app:popUpToInclusive="false" />
<dialog android:id="@+id/objectTypeFieldsScreen"
android:name="com.anytypeio.anytype.ui.primitives.ObjectTypeFieldsFragment"
android:label="ObjectTypeFieldsScreen" />
</navigation>
<fragment

View file

@ -306,6 +306,11 @@ sealed class Event {
val defaultObjectTypeId: String?
)
}
data class UpdateConflictState(
override val context: Id,
val hasConflict: Boolean
) : DataView()
}
sealed class Chats : Command() {

View file

@ -103,6 +103,8 @@ object Relations {
const val SHARED_SPACES_LIMIT = "sharedSpacesLimit"
const val LAYOUT_ALIGN = "layoutAlign"
val systemRelationKeys = listOf(
"id",
"name",

View file

@ -467,8 +467,8 @@ class BlockDataRepository(
ctx: Id, relation: Id
): Payload? = remote.addRelationToObject(ctx, relation)
override suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload {
return remote.deleteRelationFromObject(ctx = ctx, relation = relation)
override suspend fun deleteRelationFromObject(ctx: Id, relations: List<Key>): Payload {
return remote.deleteRelationFromObject(ctx = ctx, relations = relations)
}
override suspend fun debugSpace(space: SpaceId): String = remote.debugSpace(space)

View file

@ -192,7 +192,7 @@ interface BlockRemote {
suspend fun cancelObjectSearchSubscription(subscriptions: List<Id>)
suspend fun addRelationToObject(ctx: Id, relation: Key): Payload?
suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload
suspend fun deleteRelationFromObject(ctx: Id, relations: List<Key>): Payload
suspend fun debugSpace(space: SpaceId): String

View file

@ -232,7 +232,7 @@ interface BlockRepository {
suspend fun cancelObjectSearchSubscription(subscriptions: List<Id>)
suspend fun addRelationToObject(ctx: Id, relation: Key): Payload?
suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload
suspend fun deleteRelationFromObject(ctx: Id, relations: List<Key>): Payload
suspend fun debugSpace(space: SpaceId): String

View file

@ -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<Payload, DeleteRelationFromObject.Params>() {
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<DeleteRelationFromObject.Params, Payload>(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<Key>
)
}

View file

@ -420,10 +420,10 @@ class BlockMiddleware(
override suspend fun deleteRelationFromObject(
ctx: Id,
relation: Id
relations: List<Key>
): Payload = middleware.objectRelationDelete(
ctx = ctx,
relation = relation
relations = relations
)
override suspend fun debugSpace(space: SpaceId): String = middleware.debugSpaceSummary(space)

View file

@ -1171,10 +1171,10 @@ class Middleware @Inject constructor(
}
@Throws(Exception::class)
fun objectRelationDelete(ctx: Id, relation: Key): Payload {
fun objectRelationDelete(ctx: Id, relations: List<Key>): Payload {
val request = Rpc.ObjectRelation.Delete.Request(
contextId = ctx,
relationKeys = listOf(relation)
relationKeys = relations
)
logRequestIfDebug(request)
val (response, time) = measureTimedValue { service.objectRelationDelete(request) }

View file

@ -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<BlockView>
) {
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() {

View file

@ -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<BlockView>,
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
}

View file

@ -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
)
}
}

View file

@ -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<ObjectViewDetails>,
private val restrictions: Flow<List<ObjectRestriction>>
private val hasObjectLayoutConflict: Flow<Boolean>
) : ObjectMenuOptionsProvider {
private fun observeLayout(ctx: Id): Flow<ObjectType.Layout?> = objectViewDetailsFlow
@ -41,16 +40,20 @@ class ObjectMenuOptionsProviderImpl(
return@map featuredRelations?.any { it == Relations.DESCRIPTION } == true
}
private fun observeHasObjectLayoutConflict(): Flow<Boolean> = hasObjectLayoutConflict
override fun provide(ctx: Id, isLocked: Boolean, isReadOnly: Boolean): Flow<Options> {
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

View file

@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectMenuViewModel(
@ -579,7 +655,8 @@ class ObjectMenuViewModel(
spaceViewSubscriptionContainer = spaceViewSubscriptionContainer,
addToFeaturedRelations = addToFeaturedRelations,
removeFromFeaturedRelations = removeFromFeaturedRelations,
userPermissionProvider = userPermissionProvider
userPermissionProvider = userPermissionProvider,
deleteRelationFromObject = deleteRelationFromObject
) as T
}
}

View file

@ -86,7 +86,8 @@ abstract class ObjectMenuViewModelBase(
hasRelations = false,
hasDiagnosticsVisibility = false,
hasHistory = false,
hasDescriptionShow = false
hasDescriptionShow = false,
hasObjectLayoutConflict = false
)
)
val options: Flow<ObjectMenuOptionsProvider.Options> = _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)

View file

@ -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 <T : ViewModel> create(modelClass: Class<T>): 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")
}
)
}
}
}

View file

@ -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,

View file

@ -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) {

View file

@ -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(

View file

@ -26,12 +26,15 @@ sealed class ObjectState {
abstract val dataViewBlock: Block
abstract val viewers: List<DVViewer>
abstract val hasObjectLayoutConflict: Boolean
data class Set(
override val root: Id,
override val blocks: List<Block> = emptyList(),
override val details: ObjectViewDetails = ObjectViewDetails.EMPTY,
override val objectRestrictions: List<ObjectRestriction> = emptyList(),
override val dataViewRestrictions: List<DataViewRestrictions> = 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<ObjectRestriction> = emptyList(),
override val dataViewRestrictions: List<DataViewRestrictions> = 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<ObjectRestriction> = emptyList(),
override val dataViewRestrictions: List<DataViewRestrictions> = emptyList(),
override val hasObjectLayoutConflict: Boolean = false,
) : DataView() {
override val isInitialized get() = blocks.any { it.content is DV }

View file

@ -16,8 +16,8 @@ class ObjectMenuOptionsProviderImplTest {
private val objectId: String = "objectId"
private val details = MutableStateFlow<ObjectViewDetails>(ObjectViewDetails.EMPTY)
private val restrictions = MutableStateFlow<List<ObjectRestriction>>(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
)
)
}