From 980ef1fa7de6b0abd64f1cd28205f40b0c934855 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 16:09:14 +0100 Subject: [PATCH 01/10] DROID-4023 to_bin button logic --- .../ui/primitives/ObjectTypeFragment.kt | 3 +-- .../ui/menu/ObjectTypeMenu.kt | 22 +++++++++------- .../viewmodel/ObjectTypeViewModel.kt | 26 ++++++++++--------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt index 52abfd8546..08f654f717 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt @@ -24,7 +24,6 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.views.BaseAlertDialog import com.anytypeio.anytype.core_utils.ext.argString -import com.anytypeio.anytype.core_utils.ext.safeNavigate import com.anytypeio.anytype.core_utils.ext.subscribe import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment @@ -41,7 +40,6 @@ import com.anytypeio.anytype.feature_object_type.ui.menu.ObjectTypeMenu import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeViewModel import com.anytypeio.anytype.ui.editor.EditorModalFragment -import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuBaseFragment import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.TYPE_TEMPLATE_EDIT import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import com.google.accompanist.navigation.material.rememberBottomSheetNavigator @@ -167,6 +165,7 @@ class ObjectTypeFragment : BaseComposeFragment() { if (menuState.isVisible) { ObjectTypeMenu( isPinned = menuState.isPinned, + canDelete = menuState.canDelete, onEvent = vm::onMenuEvent ) } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt index f998485331..5a36116cde 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt @@ -34,13 +34,12 @@ import com.anytypeio.anytype.core_ui.foundation.Dragger import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable import com.anytypeio.anytype.core_ui.views.Caption2Regular import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular -import com.anytypeio.anytype.core_ui.views.Relations3 -import com.anytypeio.anytype.presentation.objects.ObjectIcon @OptIn(ExperimentalMaterial3Api::class) @Composable fun ObjectTypeMenu( isPinned: Boolean, + canDelete: Boolean, onEvent: (ObjectTypeMenuEvent) -> Unit ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -125,15 +124,17 @@ fun ObjectTypeMenu( onClick = { onEvent(ObjectTypeMenuEvent.OnPinToggleClick) } ) - Spacer(modifier = Modifier.width(12.dp)) + // To Bin button (only show if user has delete permission) + if (canDelete) { + Spacer(modifier = Modifier.width(12.dp)) - // To Bin button - BottomActionButton( - iconRes = R.drawable.ic_object_action_archive, - text = stringResource(R.string.action_bar_to_bin), - isDestructive = true, - onClick = { onEvent(ObjectTypeMenuEvent.OnToBinClick) } - ) + BottomActionButton( + iconRes = R.drawable.ic_object_action_archive, + text = stringResource(R.string.action_bar_to_bin), + isDestructive = true, + onClick = { onEvent(ObjectTypeMenuEvent.OnToBinClick) } + ) + } } Spacer(modifier = Modifier.height(16.dp)) @@ -216,6 +217,7 @@ private fun BottomActionButton( fun ObjectTypeMenuPreview() { ObjectTypeMenu( isPinned = false, + canDelete = true, onEvent = {} ) } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt index 1705419f3d..61d3130aac 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt @@ -9,9 +9,12 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.core_models.WidgetLayout import com.anytypeio.anytype.core_models.permissions.ObjectPermissions import com.anytypeio.anytype.core_models.permissions.toObjectPermissionsForTypes +import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.domain.base.fold import com.anytypeio.anytype.domain.dataview.SetDataViewProperties @@ -40,21 +43,23 @@ import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListState import com.anytypeio.anytype.feature_object_type.fields.UiLocalsFieldsInfoState import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand -import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand.* +import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand.Back +import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand.OpenAddNewPropertyScreen import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams import com.anytypeio.anytype.feature_object_type.ui.TypeEvent import com.anytypeio.anytype.feature_object_type.ui.UiDeleteAlertState import com.anytypeio.anytype.feature_object_type.ui.UiEditButton import com.anytypeio.anytype.feature_object_type.ui.UiErrorState -import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.* -import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.Reason.* +import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.Reason.ErrorEditingTypeDetails +import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.Reason.Other +import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.Show import com.anytypeio.anytype.feature_object_type.ui.UiHorizontalButtonsState -import com.anytypeio.anytype.feature_object_type.ui.UiPropertiesButtonState -import com.anytypeio.anytype.feature_object_type.ui.UiIconsPickerState import com.anytypeio.anytype.feature_object_type.ui.UiIconState +import com.anytypeio.anytype.feature_object_type.ui.UiIconsPickerState import com.anytypeio.anytype.feature_object_type.ui.UiLayoutButtonState import com.anytypeio.anytype.feature_object_type.ui.UiLayoutTypeState -import com.anytypeio.anytype.feature_object_type.ui.UiLayoutTypeState.* +import com.anytypeio.anytype.feature_object_type.ui.UiLayoutTypeState.Visible +import com.anytypeio.anytype.feature_object_type.ui.UiPropertiesButtonState import com.anytypeio.anytype.feature_object_type.ui.UiSyncStatusBadgeState import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesButtonState import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesModalListState @@ -70,7 +75,6 @@ import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.extension.sendAnalyticsLocalPropertyResolve -import com.anytypeio.anytype.presentation.widgets.findWidgetBlockForObject import com.anytypeio.anytype.presentation.extension.sendAnalyticsPropertiesLocalInfo import com.anytypeio.anytype.presentation.extension.sendAnalyticsReorderRelationEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsScreenObjectType @@ -83,10 +87,7 @@ import com.anytypeio.anytype.presentation.sync.toSyncStatusWidgetState import com.anytypeio.anytype.presentation.sync.updateStatus import com.anytypeio.anytype.presentation.templates.TemplateView import com.anytypeio.anytype.presentation.util.Dispatcher -import com.anytypeio.anytype.core_models.Position -import com.anytypeio.anytype.core_models.WidgetLayout -import com.anytypeio.anytype.core_models.primitives.SpaceId -import kotlin.collections.map +import com.anytypeio.anytype.presentation.widgets.findWidgetBlockForObject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -615,7 +616,8 @@ class ObjectTypeViewModel( uiMenuState.value = uiMenuState.value.copy( isVisible = true, icon = uiIconState.value.icon, - isPinned = pinnedWidgetBlockId.value != null + isPinned = pinnedWidgetBlockId.value != null, + canDelete = _objectTypePermissionsState.value?.canDelete ?: false ) } } From fc17c03521e03d45a95311ff76664e63ab4c6d43 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:05:13 +0100 Subject: [PATCH 02/10] DROID-4023 di --- .../anytype/di/feature/ObjectMenuDI.kt | 16 ++++++++-------- .../anytype/di/feature/ObjectRelationListDI.kt | 8 ++++---- .../di/feature/PrimitivesObjectTypeDI.kt | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 12 deletions(-) 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 717d5c7eae..bfd76f1544 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 @@ -230,14 +230,14 @@ object ObjectMenuModule { @JvmStatic @Provides @PerDialog - fun addToFeaturedRelations(repo: BlockRepository): AddToFeaturedRelations = - AddToFeaturedRelations(repo) + fun addToFeaturedRelations(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): AddToFeaturedRelations = + AddToFeaturedRelations(repo, dispatchers) @JvmStatic @Provides @PerDialog - fun removeFromFeaturedRelations(repo: BlockRepository): RemoveFromFeaturedRelations = - RemoveFromFeaturedRelations(repo) + fun removeFromFeaturedRelations(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): RemoveFromFeaturedRelations = + RemoveFromFeaturedRelations(repo, dispatchers) } @Module @@ -404,12 +404,12 @@ object ObjectSetMenuModule { @JvmStatic @Provides @PerDialog - fun addToFeaturedRelations(repo: BlockRepository): AddToFeaturedRelations = - AddToFeaturedRelations(repo) + fun addToFeaturedRelations(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): AddToFeaturedRelations = + AddToFeaturedRelations(repo, dispatchers) @JvmStatic @Provides @PerDialog - fun removeFromFeaturedRelations(repo: BlockRepository): RemoveFromFeaturedRelations = - RemoveFromFeaturedRelations(repo) + fun removeFromFeaturedRelations(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): RemoveFromFeaturedRelations = + RemoveFromFeaturedRelations(repo, dispatchers) } \ No newline at end of file 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 d0c1342b52..cd68695151 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 @@ -94,14 +94,14 @@ object ObjectRelationListModule { @JvmStatic @Provides @PerModal - fun addToFeaturedRelations(repo: BlockRepository): AddToFeaturedRelations = - AddToFeaturedRelations(repo) + fun addToFeaturedRelations(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): AddToFeaturedRelations = + AddToFeaturedRelations(repo, dispatchers) @JvmStatic @Provides @PerModal - fun removeFromFeaturedRelations(repo: BlockRepository): RemoveFromFeaturedRelations = - RemoveFromFeaturedRelations(repo) + fun removeFromFeaturedRelations(repo: BlockRepository, dispatchers: AppCoroutineDispatchers): RemoveFromFeaturedRelations = + RemoveFromFeaturedRelations(repo = repo, dispatchers = dispatchers) @JvmStatic @Provides diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt index 88f4fbe9f7..4a77953dae 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt @@ -28,7 +28,9 @@ import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeHeaderRecommendedFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields +import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations import com.anytypeio.anytype.domain.relations.CreateRelation +import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.types.CreateObjectType @@ -168,6 +170,22 @@ object ObjectTypeModule { dispatchers: AppCoroutineDispatchers ): SetObjectListIsArchived = SetObjectListIsArchived(repo, dispatchers) + @JvmStatic + @Provides + @PerScreen + fun provideAddToFeaturedRelations( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): AddToFeaturedRelations = AddToFeaturedRelations(repo, dispatchers) + + @JvmStatic + @Provides + @PerScreen + fun provideRemoveFromFeaturedRelations( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): RemoveFromFeaturedRelations = RemoveFromFeaturedRelations(repo, dispatchers) + @Module interface Declarations { @PerScreen From 6ed1823754adcba32113ba12c22c68c74427ae89 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:06:00 +0100 Subject: [PATCH 03/10] DROID-4023 use cases update --- .../domain/relations/AddToFeaturedRelations.kt | 12 +++++++----- .../relations/RemoveFromFeaturedRelations.kt | 12 +++++++----- .../objects/menu/ObjectMenuViewModel.kt | 16 ++++++++-------- .../objects/menu/ObjectSetMenuViewModel.kt | 16 ++++++++-------- .../relations/RelationListViewModel.kt | 16 ++++++++-------- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/relations/AddToFeaturedRelations.kt b/domain/src/main/java/com/anytypeio/anytype/domain/relations/AddToFeaturedRelations.kt index 2c17b51912..028814fd7e 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/relations/AddToFeaturedRelations.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/relations/AddToFeaturedRelations.kt @@ -2,18 +2,20 @@ package com.anytypeio.anytype.domain.relations import com.anytypeio.anytype.core_models.Id 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 adding one or more relations to featured relations list. */ class AddToFeaturedRelations( - private val repo: BlockRepository -) : BaseUseCase() { + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { - override suspend fun run(params: Params) = safe { - repo.addToFeaturedRelations( + override suspend fun doWork(params: Params): Payload { + return repo.addToFeaturedRelations( ctx = params.ctx, relations = params.relations ) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/relations/RemoveFromFeaturedRelations.kt b/domain/src/main/java/com/anytypeio/anytype/domain/relations/RemoveFromFeaturedRelations.kt index 151b4c4668..dcccbf05b7 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/relations/RemoveFromFeaturedRelations.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/relations/RemoveFromFeaturedRelations.kt @@ -2,18 +2,20 @@ package com.anytypeio.anytype.domain.relations import com.anytypeio.anytype.core_models.Id 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 removing one or more relations from featured relations list. */ class RemoveFromFeaturedRelations( - private val repo: BlockRepository -) : BaseUseCase() { + private val repo: BlockRepository, + dispatchers: AppCoroutineDispatchers +) : ResultInteractor(dispatchers.io) { - override suspend fun run(params: Params) = safe { - repo.removeFromFeaturedRelations( + override suspend fun doWork(params: Params): Payload { + return repo.removeFromFeaturedRelations( ctx = params.ctx, relations = params.relations ) 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 c55a592667..65ff919227 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 @@ -291,33 +291,33 @@ class ObjectMenuViewModel( Relations.DESCRIPTION ) == true if (isDescriptionAlreadyInFeatured) { - removeFromFeaturedRelations( + removeFromFeaturedRelations.async( params = RemoveFromFeaturedRelations.Params( ctx = ctx, relations = listOf(Relations.DESCRIPTION) ) - ).proceed( - success = { payload -> + ).fold( + onSuccess = { payload -> dispatcher.send(payload) Timber.d("Description was removed from featured relations") }, - failure = { + onFailure = { Timber.e(it, "Error while removing description from featured relations") _toasts.emit(SOMETHING_WENT_WRONG_MSG) } ) } else { - addToFeaturedRelations( + addToFeaturedRelations.async( params = AddToFeaturedRelations.Params( ctx = ctx, relations = listOf(Relations.DESCRIPTION) ) - ).proceed( - success = { payload -> + ).fold( + onSuccess = { payload -> dispatcher.send(payload) Timber.d("Description was added to featured relations") }, - failure = { + onFailure = { Timber.e(it, "Error while adding description to featured relations") _toasts.emit(SOMETHING_WENT_WRONG_MSG) } 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 710210666e..554e2b0717 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 @@ -203,33 +203,33 @@ class ObjectSetMenuViewModel( Relations.DESCRIPTION ) == true if (isDescriptionAlreadyInFeatured) { - removeFromFeaturedRelations.run( + removeFromFeaturedRelations.async( params = RemoveFromFeaturedRelations.Params( ctx = ctx, relations = listOf(Relations.DESCRIPTION) ) - ).proceed( - success = { payload -> + ).fold( + onSuccess = { payload -> dispatcher.send(payload) Timber.d("Description was removed from featured relations") }, - failure = { + onFailure = { Timber.e(it, "Error while removing description from featured relations") _toasts.emit(SOMETHING_WENT_WRONG_MSG) } ) } else { - addToFeaturedRelations.run( + addToFeaturedRelations.async( params = AddToFeaturedRelations.Params( ctx = ctx, relations = listOf(Relations.DESCRIPTION) ) - ).proceed( - success = { payload -> + ).fold( + onSuccess = { payload -> dispatcher.send(payload) Timber.d("Description was added to featured relations") }, - failure = { + onFailure = { Timber.e(it, "Error while adding description to featured relations") _toasts.emit(SOMETHING_WENT_WRONG_MSG) } 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 5219b971f1..2c84e20126 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 @@ -427,14 +427,14 @@ class RelationListViewModel( viewModelScope.launch { if (view.featured) { viewModelScope.launch { - removeFromFeaturedRelations( + removeFromFeaturedRelations.async( RemoveFromFeaturedRelations.Params( ctx = ctx, relations = listOf(relationKey) ) - ).process( - failure = { Timber.e(it, "Error while removing from featured relations") }, - success = { + ).fold( + onFailure = { Timber.e(it, "Error while removing from featured relations") }, + onSuccess = { dispatcher.send(it) analytics.sendAnalyticsRelationEvent( eventName = objectRelationUnfeature, @@ -447,14 +447,14 @@ class RelationListViewModel( } } else { viewModelScope.launch { - addToFeaturedRelations( + addToFeaturedRelations.async( AddToFeaturedRelations.Params( ctx = ctx, relations = listOf(relationKey) ) - ).process( - failure = { Timber.e(it, "Error while adding to featured relations") }, - success = { + ).fold( + onFailure = { Timber.e(it, "Error while adding to featured relations") }, + onSuccess = { dispatcher.send(it) analytics.sendAnalyticsRelationEvent( eventName = objectRelationFeature, From a9cd8906099fbad50dc9e377b6d4e87878e30b57 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:06:44 +0100 Subject: [PATCH 04/10] DROID-4023 ui --- .../ui/primitives/ObjectTypeFragment.kt | 2 + .../anytype/ui/primitives/WithSetScreen.kt | 20 ++++ .../anytype/feature_object_type/ui/UiEvent.kt | 1 + .../anytype/feature_object_type/ui/UiState.kt | 14 +++ .../ui/header/DescriptionWidget.kt | 96 +++++++++++++++++++ .../ui/menu/ObjectTypeMenu.kt | 11 ++- .../ui/menu/ObjectTypeMenuState.kt | 3 +- 7 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt index 08f654f717..6db8953ade 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/ObjectTypeFragment.kt @@ -141,6 +141,7 @@ class ObjectTypeFragment : BaseComposeFragment() { uiSyncStatusBadgeState = vm.uiSyncStatusBadgeState.collectAsStateWithLifecycle().value, uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value, uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value, + uiDescriptionState = vm.uiDescriptionState.collectAsStateWithLifecycle().value, uiHorizontalButtonsState = vm.uiHorizontalButtonsState.collectAsStateWithLifecycle().value, uiTemplatesModalListState = vm.uiTemplatesModalListState.collectAsStateWithLifecycle().value, uiLayoutTypeState = vm.uiTypeLayoutsState.collectAsStateWithLifecycle().value, @@ -166,6 +167,7 @@ class ObjectTypeFragment : BaseComposeFragment() { ObjectTypeMenu( isPinned = menuState.isPinned, canDelete = menuState.canDelete, + isDescriptionFeatured = menuState.isDescriptionFeatured, onEvent = vm::onMenuEvent ) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/primitives/WithSetScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/primitives/WithSetScreen.kt index fb875b4cc6..294180cf48 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/primitives/WithSetScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/primitives/WithSetScreen.kt @@ -40,7 +40,9 @@ import com.anytypeio.anytype.feature_object_type.ui.UiLayoutTypeState import com.anytypeio.anytype.feature_object_type.ui.UiSyncStatusBadgeState import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesModalListState import com.anytypeio.anytype.feature_object_type.ui.UiTitleState +import com.anytypeio.anytype.feature_object_type.ui.UiDescriptionState import com.anytypeio.anytype.feature_object_type.ui.alerts.DeleteAlertScreen +import com.anytypeio.anytype.feature_object_type.ui.header.DescriptionWidget import com.anytypeio.anytype.feature_object_type.ui.header.HorizontalButtons import com.anytypeio.anytype.feature_object_type.ui.header.IconAndTitleWidget import com.anytypeio.anytype.feature_object_type.ui.layouts.TypeLayoutsScreen @@ -57,6 +59,7 @@ fun WithSetScreen( //header uiIconState: UiIconState, uiTitleState: UiTitleState, + uiDescriptionState: UiDescriptionState, //layout, properties and templates buttons uiHorizontalButtonsState: UiHorizontalButtonsState, uiLayoutTypeState: UiLayoutTypeState, @@ -99,6 +102,7 @@ fun WithSetScreen( paddingValues = paddingValues, uiIconState = uiIconState, uiTitleState = uiTitleState, + uiDescriptionState = uiDescriptionState, uiHorizontalButtonsState = uiHorizontalButtonsState, objectId = objectId, space = space, @@ -142,6 +146,7 @@ private fun MainContentSet( paddingValues: PaddingValues, uiIconState: UiIconState, uiTitleState: UiTitleState, + uiDescriptionState: UiDescriptionState, uiHorizontalButtonsState: UiHorizontalButtonsState, objectId: String, space: String, @@ -170,6 +175,21 @@ private fun MainContentSet( uiTitleState = uiTitleState, onTypeEvent = onTypeEvent ) + + if (uiDescriptionState.isVisible) { + Spacer(modifier = Modifier.height(8.dp)) + DescriptionWidget( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 20.dp), + uiDescriptionState = uiDescriptionState, + onDescriptionChanged = { text -> + onTypeEvent(TypeEvent.OnDescriptionChanged(text)) + } + ) + } + if (uiHorizontalButtonsState.isVisible) { Spacer(modifier = Modifier.height(20.dp)) HorizontalButtons( diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiEvent.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiEvent.kt index d39f6c0e41..5ca9059740 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiEvent.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiEvent.kt @@ -21,6 +21,7 @@ sealed class TypeEvent { data object OnObjectTypeIconClick : TypeEvent() data class OnObjectTypeTitleUpdate(val title: String) : TypeEvent() data object OnObjectTypeTitleClick : TypeEvent() + data class OnDescriptionChanged(val text: String) : TypeEvent() //endregion //region Templates diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt index 9740f96b0f..6ff6db79aa 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiState.kt @@ -46,6 +46,20 @@ data class UiIconState(val icon: ObjectIcon.TypeIcon, val isEditable: Boolean) { val EMPTY = UiIconState(icon = ObjectIcon.TypeIcon.Default.DEFAULT, isEditable = false) } } + +data class UiDescriptionState( + val description: String, + val isVisible: Boolean, + val isEditable: Boolean +) { + companion object { + val EMPTY = UiDescriptionState( + description = "", + isVisible = false, + isEditable = false + ) + } +} //endregion //region LAYOUTS diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt new file mode 100644 index 0000000000..8a9e958ba8 --- /dev/null +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt @@ -0,0 +1,96 @@ +package com.anytypeio.anytype.feature_object_type.ui.header + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.views.Relations1 +import com.anytypeio.anytype.feature_object_type.ui.UiDescriptionState + +/** + * Description widget for ObjectType screen. + * Displays an editable description field styled as Relations1 (15sp, Inter Regular). + * Shows placeholder text when empty. + */ +@Composable +fun DescriptionWidget( + modifier: Modifier = Modifier, + uiDescriptionState: UiDescriptionState, + onDescriptionChanged: (String) -> Unit +) { + if (!uiDescriptionState.isVisible) return + + var text by remember(uiDescriptionState.description) { + mutableStateOf(uiDescriptionState.description) + } + + BasicTextField( + value = text, + onValueChange = { newText -> + text = newText + onDescriptionChanged(newText) + }, + modifier = modifier, + enabled = uiDescriptionState.isEditable, + textStyle = Relations1.copy( + color = colorResource(id = R.color.text_primary) + ), + cursorBrush = SolidColor(colorResource(id = R.color.text_primary)), + decorationBox = { innerTextField -> + Box { + if (text.isEmpty()) { + Text( + text = stringResource(R.string.description), + style = Relations1, + color = colorResource(id = R.color.text_tertiary) + ) + } + innerTextField() + } + } + ) +} + +@DefaultPreviews +@Composable +private fun DescriptionWidgetPreview() { + DescriptionWidget( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + uiDescriptionState = UiDescriptionState( + description = "", + isEditable = true, + isVisible = true + ), + onDescriptionChanged = {} + ) +} + +@DefaultPreviews +@Composable +private fun DescriptionWidgetWithTextPreview() { + DescriptionWidget( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + uiDescriptionState = UiDescriptionState( + description = "This is a description of the object type.", + isEditable = true, + isVisible = true + ), + onDescriptionChanged = {} + ) +} diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt index 5a36116cde..589f2bc194 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenu.kt @@ -40,6 +40,7 @@ import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular fun ObjectTypeMenu( isPinned: Boolean, canDelete: Boolean, + isDescriptionFeatured: Boolean, onEvent: (ObjectTypeMenuEvent) -> Unit ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -85,18 +86,21 @@ fun ObjectTypeMenu( Divider(paddingStart = 20.dp, paddingEnd = 20.dp) - // Description cell (disabled for now) + // Description cell MenuCell( iconRes = R.drawable.ic_obj_settings_description_24, title = stringResource(R.string.description), trailingContent = { Text( - text = stringResource(R.string.show), + text = if (isDescriptionFeatured) + stringResource(R.string.modal_hide) + else + stringResource(R.string.show), style = PreviewTitle1Regular, color = colorResource(R.color.text_secondary) ) }, - onClick = { /* onEvent(ObjectTypeMenuEvent.OnDescriptionClick) - Leave for later */ }, + onClick = { onEvent(ObjectTypeMenuEvent.OnDescriptionClick) } ) // Divider before bottom section @@ -218,6 +222,7 @@ fun ObjectTypeMenuPreview() { ObjectTypeMenu( isPinned = false, canDelete = true, + isDescriptionFeatured = false, onEvent = {} ) } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenuState.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenuState.kt index 9905afde87..b19945644e 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenuState.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/menu/ObjectTypeMenuState.kt @@ -9,7 +9,8 @@ data class UiObjectTypeMenuState( val isVisible: Boolean = false, val isPinned: Boolean = false, val canDelete: Boolean = true, - val icon: ObjectIcon = ObjectIcon.None + val icon: ObjectIcon = ObjectIcon.None, + val isDescriptionFeatured: Boolean = false ) { companion object { val Hidden = UiObjectTypeMenuState(isVisible = false) From c075fefc45112508c5020728b73c91323ddbc666 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:06:54 +0100 Subject: [PATCH 05/10] DROID-4023 featured for types --- .../java/com/anytypeio/anytype/core_models/ObjectWrapper.kt | 2 ++ .../anytype/domain/search/ObjectTypesSubscriptionManager.kt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt index d3848e0ee9..0600b8459a 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt @@ -234,6 +234,8 @@ sealed class ObjectWrapper { val orderId: String? get() = getSingleValue(Relations.ORDER_ID) + val featuredRelations: List get() = getValues(Relations.FEATURED_RELATIONS) + val allRecommendedRelations: List get() = recommendedFeaturedRelations + recommendedRelations + recommendedFileRelations + recommendedHiddenRelations diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/search/ObjectTypesSubscriptionManager.kt b/domain/src/main/java/com/anytypeio/anytype/domain/search/ObjectTypesSubscriptionManager.kt index 6f7ca6d22d..94cc25c018 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/search/ObjectTypesSubscriptionManager.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/search/ObjectTypesSubscriptionManager.kt @@ -122,7 +122,8 @@ class ObjectTypesSubscriptionManager ( Relations.WIDGET_LAYOUT, Relations.WIDGET_LIMIT, Relations.WIDGET_VIEW_ID, - Relations.ORDER_ID + Relations.ORDER_ID, + Relations.FEATURED_RELATIONS ), ignoreWorkspace = true ) From 598e5af215f8c5009f98bb40d60ab1ce19f60292 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:06:59 +0100 Subject: [PATCH 06/10] DROID-4023 vm --- .../anytype/feature_object_type/viewmodel/VmFactory.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt index c756b6bd20..255fed3b8f 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt @@ -19,6 +19,8 @@ import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields +import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations +import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.widgets.CreateWidget @@ -55,7 +57,9 @@ class ObjectTypeVMFactory @Inject constructor( private val createWidget: CreateWidget, private val deleteWidget: DeleteWidget, private val spaceManager: SpaceManager, - private val getObject: GetObject + private val getObject: GetObject, + private val addToFeaturedRelations: AddToFeaturedRelations, + private val removeFromFeaturedRelations: RemoveFromFeaturedRelations ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -85,6 +89,8 @@ class ObjectTypeVMFactory @Inject constructor( createWidget = createWidget, deleteWidget = deleteWidget, spaceManager = spaceManager, - getObject = getObject + getObject = getObject, + addToFeaturedRelations = addToFeaturedRelations, + removeFromFeaturedRelations = removeFromFeaturedRelations ) as T } \ No newline at end of file From 36da917498db04e3b881ac1964ddf82428abfa73 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:20:23 +0100 Subject: [PATCH 07/10] DROID-4023 logic --- .../di/feature/PrimitivesObjectTypeDI.kt | 10 ++ .../viewmodel/ObjectTypeViewModel.kt | 128 +++++++++++++++++- .../viewmodel/VmFactory.kt | 7 +- 3 files changed, 136 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt index 4a77953dae..88e1b34b82 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/PrimitivesObjectTypeDI.kt @@ -7,6 +7,7 @@ import com.anytypeio.anytype.core_utils.di.scope.CreateFromScratch import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.interactor.UpdateText import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.config.UserSettingsRepository @@ -186,6 +187,15 @@ object ObjectTypeModule { dispatchers: AppCoroutineDispatchers ): RemoveFromFeaturedRelations = RemoveFromFeaturedRelations(repo, dispatchers) + @JvmStatic + @Provides + @PerScreen + fun provideUpdateBlockUseCase( + repo: BlockRepository + ): UpdateText = UpdateText( + repo = repo + ) + @Module interface Declarations { @PerScreen diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt index 61d3130aac..1638f46df0 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt @@ -17,6 +17,7 @@ import com.anytypeio.anytype.core_models.permissions.toObjectPermissionsForTypes import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.extensions.simpleIcon import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.block.interactor.UpdateText import com.anytypeio.anytype.domain.dataview.SetDataViewProperties import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.library.StoreSearchParams @@ -33,6 +34,8 @@ import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.primitives.FieldParser import com.anytypeio.anytype.domain.primitives.GetObjectTypeConflictingFields import com.anytypeio.anytype.domain.primitives.SetObjectTypeRecommendedFields +import com.anytypeio.anytype.domain.relations.AddToFeaturedRelations +import com.anytypeio.anytype.domain.relations.RemoveFromFeaturedRelations import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.widgets.CreateWidget @@ -48,6 +51,7 @@ import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand.OpenAddNew import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams import com.anytypeio.anytype.feature_object_type.ui.TypeEvent import com.anytypeio.anytype.feature_object_type.ui.UiDeleteAlertState +import com.anytypeio.anytype.feature_object_type.ui.UiDescriptionState import com.anytypeio.anytype.feature_object_type.ui.UiEditButton import com.anytypeio.anytype.feature_object_type.ui.UiErrorState import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.Reason.ErrorEditingTypeDetails @@ -130,7 +134,10 @@ class ObjectTypeViewModel( private val createWidget: CreateWidget, private val deleteWidget: DeleteWidget, private val spaceManager: SpaceManager, - private val getObject: GetObject + private val getObject: GetObject, + private val addToFeaturedRelations: AddToFeaturedRelations, + private val removeFromFeaturedRelations: RemoveFromFeaturedRelations, + private val updateText: UpdateText ) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate { //region UI STATE @@ -144,6 +151,7 @@ class ObjectTypeViewModel( //header val uiTitleState = MutableStateFlow(UiTitleState.Companion.EMPTY) val uiIconState = MutableStateFlow(UiIconState.Companion.EMPTY) + val uiDescriptionState = MutableStateFlow(UiDescriptionState.EMPTY) //layout, properties and templates buttons val uiHorizontalButtonsState = @@ -194,6 +202,7 @@ class ObjectTypeViewModel( private val _objectTypeConflictingFieldIds = MutableStateFlow>(emptyList()) private val pinnedWidgetBlockId = MutableStateFlow(null) private var targetWidgetBlockId: Id? = null // Cached first widget block ID for positioning + private val _isDescriptionFeatured = MutableStateFlow(false) //endregion val showPropertiesScreen = MutableStateFlow(false) @@ -378,6 +387,10 @@ class ObjectTypeViewModel( (uiTitleAndIconUpdateState.value as? UiTypeSetupTitleAndIconState.Visible.EditType)?.let { uiTitleAndIconUpdateState.value = it.copy(icon = newIcon) } + + // Update description state + updateDescriptionState(objType, objectPermissions) + //turn off button, we give Move to Bin logic in Library now // if (objectPermissions.canDelete) { // uiEditButtonState.value = UiEditButton.Visible @@ -544,6 +557,10 @@ class ObjectTypeViewModel( showTitleAndIconUpdateScreen() } + is TypeEvent.OnDescriptionChanged -> { + onDescriptionChanged(event.text) + } + TypeEvent.OnMenuItemDeleteClick -> { uiAlertState.value = UiDeleteAlertState.Show } @@ -612,12 +629,14 @@ class ObjectTypeViewModel( } TypeEvent.OnMenuClick -> { - // Show Compose menu + // Check current description status before showing menu + checkDescriptionFeaturedStatus() uiMenuState.value = uiMenuState.value.copy( isVisible = true, icon = uiIconState.value.icon, isPinned = pinnedWidgetBlockId.value != null, - canDelete = _objectTypePermissionsState.value?.canDelete ?: false + canDelete = _objectTypePermissionsState.value?.canDelete ?: false, + isDescriptionFeatured = _isDescriptionFeatured.value ) } } @@ -634,10 +653,7 @@ class ObjectTypeViewModel( uiIconsPickerScreen.value = UiIconsPickerState.Visible } ObjectTypeMenuEvent.OnDescriptionClick -> { - // TODO: Implement description logic later - viewModelScope.launch { - commands.emit(ObjectTypeCommand.ShowToast("Not implemented yet")) - } + proceedWithDescriptionToggle() } ObjectTypeMenuEvent.OnToBinClick -> { uiMenuState.value = UiObjectTypeMenuState.Hidden @@ -745,6 +761,40 @@ class ObjectTypeViewModel( } } + private fun onDescriptionChanged(text: String) { + viewModelScope.launch { + val params = UpdateText.Params( + context = vmParams.objectId, + target = Relations.DESCRIPTION, + text = text, + marks = listOf() + ) + updateText(params).proceed( + failure = { + Timber.e(it, "Error while updating description") + }, + success = { + Timber.d("Description updated") + } + ) + } + } + + private fun updateDescriptionState( + objType: ObjectWrapper.Type, + objectPermissions: ObjectPermissions + ) { + // Access description and featured relations directly from objType + val descriptionText = objType.description.orEmpty() + val isDescriptionFeatured = objType.featuredRelations.contains(Relations.DESCRIPTION) + + uiDescriptionState.value = UiDescriptionState( + description = descriptionText, + isVisible = isDescriptionFeatured, + isEditable = objectPermissions.canEditDetails + ) + } + private fun updateIcon( iconName: String, newColor: CustomIconColor? @@ -1208,6 +1258,70 @@ class ObjectTypeViewModel( } } + /** + * Checks if description is in featured relations using cached object type state. + * Updates the internal state accordingly. + */ + private fun checkDescriptionFeaturedStatus() { + val objType = _objTypeState.value + _isDescriptionFeatured.value = objType?.featuredRelations?.contains(Relations.DESCRIPTION) ?: false + } + + private fun proceedWithDescriptionToggle() { + viewModelScope.launch { + // Permission check + if (userPermissionProvider.get(space = vmParams.spaceId)?.isOwnerOrEditor() != true) { + Timber.w("User doesn't have permission to modify featured relations") + commands.emit(ObjectTypeCommand.ShowToast("Permission denied")) + uiMenuState.value = UiObjectTypeMenuState.Hidden + return@launch + } + + val isCurrentlyFeatured = _isDescriptionFeatured.value + + if (isCurrentlyFeatured) { + // Remove description from featured relations + val params = RemoveFromFeaturedRelations.Params( + ctx = vmParams.objectId, + relations = listOf(Relations.DESCRIPTION) + ) + removeFromFeaturedRelations.async(params = params).fold( + onSuccess = { payload -> + dispatcher.send(payload) + _isDescriptionFeatured.value = false + uiMenuState.value = UiObjectTypeMenuState.Hidden + Timber.d("Description removed from featured relations") + }, + onFailure = { error -> + Timber.e(error, "Error removing description from featured relations") + commands.emit(ObjectTypeCommand.ShowToast("Failed to hide description")) + uiMenuState.value = UiObjectTypeMenuState.Hidden + } + ) + } else { + // Add description to featured relations + addToFeaturedRelations.async( + params = AddToFeaturedRelations.Params( + ctx = vmParams.objectId, + relations = listOf(Relations.DESCRIPTION) + ) + ).fold( + onSuccess = { payload -> + dispatcher.send(payload) + _isDescriptionFeatured.value = true + uiMenuState.value = UiObjectTypeMenuState.Hidden + Timber.d("Description added to featured relations") + }, + onFailure = { error -> + Timber.e(error, "Error adding description to featured relations") + commands.emit(ObjectTypeCommand.ShowToast("Failed to show description")) + uiMenuState.value = UiObjectTypeMenuState.Hidden + } + ) + } + } + } + /** * Checks if the given object type is pinned as a widget in the space's home screen. * Updates [pinnedWidgetBlockId] with the widget block ID if found, or null otherwise. diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt index 255fed3b8f..4523581129 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/VmFactory.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.domain.block.interactor.UpdateText import com.anytypeio.anytype.domain.dataview.SetDataViewProperties import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer @@ -59,7 +60,8 @@ class ObjectTypeVMFactory @Inject constructor( private val spaceManager: SpaceManager, private val getObject: GetObject, private val addToFeaturedRelations: AddToFeaturedRelations, - private val removeFromFeaturedRelations: RemoveFromFeaturedRelations + private val removeFromFeaturedRelations: RemoveFromFeaturedRelations, + private val updateText: UpdateText ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -91,6 +93,7 @@ class ObjectTypeVMFactory @Inject constructor( spaceManager = spaceManager, getObject = getObject, addToFeaturedRelations = addToFeaturedRelations, - removeFromFeaturedRelations = removeFromFeaturedRelations + removeFromFeaturedRelations = removeFromFeaturedRelations, + updateText = updateText ) as T } \ No newline at end of file From a161aa084e1b905dcb8ec70f4801dc3cdbb0a469 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:28:45 +0100 Subject: [PATCH 08/10] DROID-4023 keyboard action --- .../ui/header/DescriptionWidget.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt index 8a9e958ba8..e4df328c4f 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -11,9 +13,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.views.Relations1 @@ -36,6 +42,11 @@ fun DescriptionWidget( mutableStateOf(uiDescriptionState.description) } + val focusManager = LocalFocusManager.current + + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + BasicTextField( value = text, onValueChange = { newText -> @@ -59,7 +70,14 @@ fun DescriptionWidget( } innerTextField() } - } + }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions { + keyboardController?.hide() + focusManager.clearFocus() + }, ) } From 156eccd2a35eff61e7c3b1a54c03e894dbeff835 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:38:21 +0100 Subject: [PATCH 09/10] DROID-4023 fix --- .../ui/header/DescriptionWidget.kt | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt index e4df328c4f..82b047952a 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt @@ -6,14 +6,15 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -24,6 +25,7 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.views.Relations1 import com.anytypeio.anytype.feature_object_type.ui.UiDescriptionState +import kotlinx.coroutines.delay /** * Description widget for ObjectType screen. @@ -38,26 +40,32 @@ fun DescriptionWidget( ) { if (!uiDescriptionState.isVisible) return - var text by remember(uiDescriptionState.description) { + var text by remember { mutableStateOf(uiDescriptionState.description) } - val focusManager = LocalFocusManager.current + LaunchedEffect(uiDescriptionState.description) { + if (text != uiDescriptionState.description) { + text = uiDescriptionState.description + } + } + + LaunchedEffect(text) { + if (text != uiDescriptionState.description) { + delay(500L) // Debounce for 500ms + onDescriptionChanged(text) + } + } - val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current val keyboardController = LocalSoftwareKeyboardController.current BasicTextField( value = text, - onValueChange = { newText -> - text = newText - onDescriptionChanged(newText) - }, + onValueChange = { text = it }, modifier = modifier, enabled = uiDescriptionState.isEditable, - textStyle = Relations1.copy( - color = colorResource(id = R.color.text_primary) - ), + textStyle = Relations1.copy(color = MaterialTheme.colors.onSurface), cursorBrush = SolidColor(colorResource(id = R.color.text_primary)), decorationBox = { innerTextField -> Box { @@ -74,10 +82,18 @@ fun DescriptionWidget( keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done ), - keyboardActions = KeyboardActions { - keyboardController?.hide() - focusManager.clearFocus() - }, + keyboardActions = KeyboardActions( + onDone = { + // Send the final text immediately, bypassing the debounce + if (text != uiDescriptionState.description) { + onDescriptionChanged(text) + } + + // Then hide the keyboard and clear focus as before + keyboardController?.hide() + focusManager.clearFocus() + } + ) ) } From c14f112669f6cd1332c445b864d8eb60fd39c4c4 Mon Sep 17 00:00:00 2001 From: konstantiniiv Date: Mon, 24 Nov 2025 18:52:32 +0100 Subject: [PATCH 10/10] DROID-4023 ui fix --- .../feature_object_type/ui/header/DescriptionWidget.kt | 3 +-- .../feature_object_type/viewmodel/ObjectTypeViewModel.kt | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt index 82b047952a..0df2889abe 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/header/DescriptionWidget.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -64,8 +63,8 @@ fun DescriptionWidget( value = text, onValueChange = { text = it }, modifier = modifier, + textStyle = Relations1.copy(color = colorResource(id = R.color.text_primary)), enabled = uiDescriptionState.isEditable, - textStyle = Relations1.copy(color = MaterialTheme.colors.onSurface), cursorBrush = SolidColor(colorResource(id = R.color.text_primary)), decorationBox = { innerTextField -> Box { diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt index 1638f46df0..1025c97931 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt @@ -788,6 +788,9 @@ class ObjectTypeViewModel( val descriptionText = objType.description.orEmpty() val isDescriptionFeatured = objType.featuredRelations.contains(Relations.DESCRIPTION) + // Update both UI states from the same source of truth (the store) + _isDescriptionFeatured.value = isDescriptionFeatured + uiDescriptionState.value = UiDescriptionState( description = descriptionText, isVisible = isDescriptionFeatured, @@ -1288,7 +1291,6 @@ class ObjectTypeViewModel( removeFromFeaturedRelations.async(params = params).fold( onSuccess = { payload -> dispatcher.send(payload) - _isDescriptionFeatured.value = false uiMenuState.value = UiObjectTypeMenuState.Hidden Timber.d("Description removed from featured relations") }, @@ -1308,7 +1310,6 @@ class ObjectTypeViewModel( ).fold( onSuccess = { payload -> dispatcher.send(payload) - _isDescriptionFeatured.value = true uiMenuState.value = UiObjectTypeMenuState.Hidden Timber.d("Description added to featured relations") },