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

DROID-3435 Primitives | Type page as Set (#2134)

This commit is contained in:
Konstantin Ivanov 2025-03-06 22:00:01 +01:00 committed by GitHub
parent 51ffabc160
commit f179d64231
Signed by: github
GPG key ID: B5690EEEBB952194
23 changed files with 582 additions and 62 deletions

View file

@ -28,13 +28,12 @@ import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeMainScreen
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeViewModel
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_object_type.ui.UiErrorState
import com.anytypeio.anytype.feature_object_type.fields.ui.FieldsMainScreen
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeCommand
import com.anytypeio.anytype.feature_object_type.ui.ObjectTypeVmParams
import com.anytypeio.anytype.feature_object_type.ui.UiErrorState
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeVMFactory
import com.anytypeio.anytype.feature_object_type.viewmodel.ObjectTypeViewModel
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.ui.chats.ChatFragment
import com.anytypeio.anytype.ui.date.DateObjectFragment
@ -153,16 +152,20 @@ class ObjectTypeFragment : BaseComposeFragment() {
startDestination = OBJ_TYPE_MAIN
) {
composable(route = OBJ_TYPE_MAIN) {
ObjectTypeMainScreen(
WithSetScreen(
uiEditButtonState = vm.uiEditButtonState.collectAsStateWithLifecycle().value,
uiSyncStatusBadgeState = vm.uiSyncStatusBadgeState.collectAsStateWithLifecycle().value,
uiSyncStatusState = vm.uiSyncStatusWidgetState.collectAsStateWithLifecycle().value,
uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value,
uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value,
uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value,
uiFieldsButtonState = vm.uiFieldsButtonState.collectAsStateWithLifecycle().value,
uiLayoutButtonState = vm.uiLayoutButtonState.collectAsStateWithLifecycle().value,
uiDeleteAlertState = vm.uiAlertState.collectAsStateWithLifecycle().value,
uiEditButtonState = vm.uiEditButtonState.collectAsStateWithLifecycle().value,
uiTemplatesButtonState = vm.uiTemplatesButtonState.collectAsStateWithLifecycle().value,
uiTemplatesModalListState = vm.uiTemplatesModalListState.collectAsStateWithLifecycle().value,
uiLayoutTypeState = vm.uiTypeLayoutsState.collectAsStateWithLifecycle().value,
uiSyncStatusState = vm.uiSyncStatusWidgetState.collectAsStateWithLifecycle().value,
uiDeleteAlertState = vm.uiAlertState.collectAsStateWithLifecycle().value,
objectId = objectId,
space = space,
onTypeEvent = vm::onTypeEvent
)
}

View file

@ -0,0 +1,43 @@
package com.anytypeio.anytype.ui.primitives
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
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.UiFieldsButtonState
import com.anytypeio.anytype.feature_object_type.ui.UiIconState
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.UiSyncStatusBadgeState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesButtonState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesModalListState
import com.anytypeio.anytype.feature_object_type.ui.UiTitleState
import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WithSetScreen(
//top bar
uiEditButtonState: UiEditButton,
uiSyncStatusBadgeState: UiSyncStatusBadgeState,
uiSyncStatusState: SyncStatusWidgetState,
//header
uiIconState: UiIconState,
uiTitleState: UiTitleState,
//layout and fields buttons
uiFieldsButtonState: UiFieldsButtonState,
uiLayoutButtonState: UiLayoutButtonState,
uiLayoutTypeState: UiLayoutTypeState,
uiTemplatesButtonState: UiTemplatesButtonState,
//templates modal list
uiTemplatesModalListState: UiTemplatesModalListState,
//delete alert
uiDeleteAlertState: UiDeleteAlertState,
//events
onTypeEvent: (TypeEvent) -> Unit,
objectId: String,
space: String,
) {
//next PR
}

View file

@ -705,6 +705,57 @@ open class ObjectSetFragment :
dataViewInfo.hide()
setViewer(viewer = null)
}
is DataViewViewState.TypeSet.Default -> {
topToolbarThreeDotsButton.gone()
topToolbarStatusContainer.gone()
topBackButton.gone()
initView.gone()
header.gone()
dataViewHeader.visible()
viewerTitle.isEnabled = true
setupNewButtonsForTypeSet(state.isCreateObjectAllowed)
if (state.isEditingViewAllowed) {
customizeViewButton.visible()
} else {
customizeViewButton.invisible()
}
customizeViewButton.isEnabled = true
setCurrentViewerName(state.viewer?.title)
setViewer(viewer = state.viewer)
dataViewInfo.hide()
}
is DataViewViewState.TypeSet.NoItems -> {
topToolbarThreeDotsButton.gone()
topToolbarStatusContainer.gone()
topBackButton.gone()
initView.gone()
header.gone()
dataViewHeader.visible()
viewerTitle.isEnabled = true
setupNewButtonsForTypeSet(state.isCreateObjectAllowed)
if (state.isEditingViewAllowed) {
customizeViewButton.visible()
} else {
customizeViewButton.invisible()
}
customizeViewButton.isEnabled = true
setCurrentViewerName(state.title)
dataViewInfo.show(
type = DataViewInfo.TYPE.SET_NO_ITEMS,
isReadOnlyAccess = !state.isCreateObjectAllowed
)
setViewer(viewer = null)
}
is DataViewViewState.TypeSet.Error -> {
topToolbarThreeDotsButton.gone()
topToolbarStatusContainer.gone()
topBackButton.gone()
initView.gone()
header.gone()
dataViewHeader.visible()
setViewer(viewer = null)
}
}
}
@ -726,6 +777,16 @@ open class ObjectSetFragment :
}
}
private fun setupNewButtonsForTypeSet(isCreateObjectAllowed: Boolean) {
if (isCreateObjectAllowed) {
addNewButton.visible()
addNewIconButton.gone()
} else {
addNewButton.gone()
addNewIconButton.gone()
}
}
private fun setViewer(viewer: Viewer?) {
when (viewer) {
is Viewer.GridView -> {

View file

@ -36,6 +36,7 @@ dependencies {
implementation libs.appcompat
implementation libs.compose
implementation libs.fragmentCompose
implementation libs.composeFoundation
implementation libs.composeToolingPreview
implementation libs.composeMaterial3

View file

@ -168,7 +168,7 @@ private fun MainContent(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBarContent(
fun TopBarContent(
uiSyncStatusBadgeState: UiSyncStatusBadgeState,
uiEditButtonState: UiEditButton,
uiTitleState: UiTitleState,
@ -199,7 +199,7 @@ private fun TopBarContent(
}
@Composable
private fun BottomSyncStatus(
fun BottomSyncStatus(
uiSyncStatusState: SyncStatusWidgetState,
onDismiss: () -> Unit
) {

View file

@ -21,6 +21,7 @@ sealed class TypeEvent {
//endregion
//region Templates
data object OnTemplatesModalListDismiss : TypeEvent()
data object OnTemplatesAddIconClick : TypeEvent()
data class OnTemplateItemClick(val item: TemplateView) : TypeEvent()
sealed class OnTemplateMenuClick : TypeEvent() {
@ -29,6 +30,7 @@ sealed class TypeEvent {
data class Duplicate(val item: TemplateView) : OnTemplateMenuClick()
data class Delete(val item: TemplateView) : OnTemplateMenuClick()
}
//endregion
//region Layout type
@ -39,4 +41,5 @@ sealed class TypeEvent {
data object OnLayoutButtonClick : TypeEvent()
data object OnFieldsButtonClick : TypeEvent()
data object OnTemplatesButtonClick : TypeEvent()
}

View file

@ -33,7 +33,8 @@ sealed class ObjectTypeCommand {
data object OpenFieldsScreen : ObjectTypeCommand()
data class OpenAddFieldScreen(val typeId: Id, val space: Id, val isSet: Boolean = false) : ObjectTypeCommand()
data class OpenAddFieldScreen(val typeId: Id, val space: Id, val isSet: Boolean = false) :
ObjectTypeCommand()
}
//region OBJECT TYPE HEADER (title + icon)
@ -71,6 +72,11 @@ sealed class UiFieldsButtonState {
}
sealed class UiTemplatesButtonState {
data object Hidden : UiTemplatesButtonState()
data class Visible(val count: Int) : UiTemplatesButtonState()
}
//region MENU
@Immutable
sealed class UiMenuSetItem {
@ -153,6 +159,24 @@ data class UiTemplatesListState(
val EMPTY = UiTemplatesListState(items = emptyList())
}
}
sealed class UiTemplatesModalListState {
abstract val items: List<TemplateView>
data class Hidden(
override val items: List<TemplateView>,
) : UiTemplatesModalListState() {
companion object {
val EMPTY = Hidden(items = emptyList())
}
}
data class Visible(
override val items: List<TemplateView>,
val showAddIcon: Boolean
) :
UiTemplatesModalListState()
}
//endregion
//region OBJECTS HEADER

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.feature_object_type.ui.header
import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -21,21 +23,26 @@ import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.common.OldDevicesPreview
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
import com.anytypeio.anytype.feature_object_type.ui.TypeEvent
import com.anytypeio.anytype.feature_object_type.ui.UiFieldsButtonState
import com.anytypeio.anytype.feature_object_type.ui.UiLayoutButtonState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesButtonState
@Composable
fun HorizontalButtons(
modifier: Modifier,
uiLayoutButtonState: UiLayoutButtonState,
uiFieldsButtonState: UiFieldsButtonState,
uiTemplatesButtonState: UiTemplatesButtonState = UiTemplatesButtonState.Hidden,
onTypeEvent: (TypeEvent) -> Unit
) {
val horizontalScrollState = rememberScrollState()
Row(
modifier = modifier,
modifier = modifier.horizontalScroll(state = horizontalScrollState),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
val modifierButton = Modifier
@ -99,6 +106,32 @@ fun HorizontalButtons(
)
}
}
if (uiTemplatesButtonState is UiTemplatesButtonState.Visible) {
Row(
modifier = modifierButton.noRippleThrottledClickable {
onTypeEvent(TypeEvent.OnTemplatesButtonClick)
},
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.wrapContentSize()
.padding(start = 12.dp),
text = stringResource(R.string.button_templates),
style = PreviewTitle2Medium,
color = colorResource(R.color.text_primary)
)
Text(
modifier = Modifier
.wrapContentSize()
.padding(start = 6.dp, end = 12.dp),
text = uiTemplatesButtonState.count.toString(),
style = PreviewTitle2Medium,
color = colorResource(R.color.glyph_active)
)
}
}
}
}
@ -112,6 +145,7 @@ fun HorizontalButtonsPreview() {
.padding(start = 20.dp),
uiLayoutButtonState = UiLayoutButtonState.Visible(ObjectType.Layout.BASIC),
uiFieldsButtonState = UiFieldsButtonState.Visible(3),
uiTemplatesButtonState = UiTemplatesButtonState.Visible(2),
onTypeEvent = {}
)
}

View file

@ -109,7 +109,7 @@ fun TypeLayoutsScreen(
modifier = Modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
TemplateItemContent(
LayoutItemContent(
modifier = Modifier
.width(120.dp)
.height(224.dp)
@ -147,7 +147,7 @@ fun TypeLayoutsScreen(
}
@Composable
private fun TemplateItemContent(
private fun LayoutItemContent(
modifier: Modifier,
item: ObjectType.Layout
) {

View file

@ -10,8 +10,6 @@ import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.permissions.ObjectPermissions
import com.anytypeio.anytype.core_models.permissions.toObjectPermissionsForTypes
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider
@ -53,7 +51,8 @@ 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.UiSyncStatusBadgeState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesAddIconState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesHeaderState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesButtonState
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.buildUiFieldsList
import com.anytypeio.anytype.feature_object_type.ui.mapToUiAddFieldListItem
@ -97,7 +96,7 @@ import timber.log.Timber
* Models: @see [ObjectViewState]
*/
class ObjectTypeViewModel(
private val vmParams: ObjectTypeVmParams,
val vmParams: ObjectTypeVmParams,
private val analytics: Analytics,
private val urlBuilder: UrlBuilder,
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate,
@ -132,18 +131,17 @@ class ObjectTypeViewModel(
val uiTitleState = MutableStateFlow<UiTitleState>(UiTitleState.Companion.EMPTY)
val uiIconState = MutableStateFlow<UiIconState>(UiIconState.Companion.EMPTY)
//layout and fields buttons
//layout, fields and templates buttons
val uiFieldsButtonState = MutableStateFlow<UiFieldsButtonState>(UiFieldsButtonState.Hidden)
val uiLayoutButtonState = MutableStateFlow<UiLayoutButtonState>(UiLayoutButtonState.Hidden)
val uiTemplatesButtonState = MutableStateFlow<UiTemplatesButtonState>(UiTemplatesButtonState.Hidden)
//type layouts
val uiTypeLayoutsState = MutableStateFlow<UiLayoutTypeState>(Hidden)
//templates header
val uiTemplatesHeaderState =
MutableStateFlow<UiTemplatesHeaderState>(UiTemplatesHeaderState.Hidden)
val uiTemplatesAddIconState =
MutableStateFlow<UiTemplatesAddIconState>(UiTemplatesAddIconState.Hidden)
//templates modal list state
val uiTemplatesModalListState =
MutableStateFlow<UiTemplatesModalListState>(UiTemplatesModalListState.Hidden.EMPTY)
//alerts
val uiAlertState = MutableStateFlow<UiDeleteAlertState>(UiDeleteAlertState.Hidden)
@ -326,10 +324,6 @@ class ObjectTypeViewModel(
_objTypeState.value = objType
_objectTypePermissionsState.value = objectPermissions
if (!objectPermissions.canCreateTemplatesForThisType) {
uiTemplatesHeaderState.value = UiTemplatesHeaderState.Hidden
uiTemplatesAddIconState.value = UiTemplatesAddIconState.Hidden
}
uiTitleState.value = UiTitleState(
title = objType.name.orEmpty(),
isEditable = objectPermissions.canEditDetails
@ -368,31 +362,25 @@ class ObjectTypeViewModel(
templates: List<TemplateView>,
permissions: ObjectPermissions
) {
uiTemplatesHeaderState.value = UiTemplatesHeaderState.Visible(count = "${templates.size}")
uiTemplatesButtonState.value = UiTemplatesButtonState.Visible(count = templates.size)
// Update each template view regarding default selection.
val updatedTemplates = templates.map { template ->
when (template) {
is TemplateView.Blank -> template
is TemplateView.New -> template
is TemplateView.Template -> template.copy(
val updatedTemplates = templates.mapNotNull { template ->
if (template is TemplateView.Template) {
template.copy(
isDefault = template.id == objType.defaultTemplateId
)
} else {
null
}
}
// Build final list with an extra "new template" item if allowed.
val finalTemplates = buildList<TemplateView> {
addAll(updatedTemplates)
if (permissions.participantCanEdit) {
add(
TemplateView.New(
targetTypeId = TypeId(objType.id),
targetTypeKey = TypeKey(objType.uniqueKey)
)
)
uiTemplatesAddIconState.value = UiTemplatesAddIconState.Visible
}
val currentValue = uiTemplatesModalListState.value
uiTemplatesModalListState.value = when (currentValue) {
is UiTemplatesModalListState.Hidden -> currentValue.copy(updatedTemplates)
is UiTemplatesModalListState.Visible -> currentValue.copy(
updatedTemplates,
showAddIcon = permissions.canCreateTemplatesForThisType
)
}
}
@ -478,6 +466,22 @@ class ObjectTypeViewModel(
}
is TypeEvent.OnTemplateMenuClick -> proceedWithTemplateMenuClick(event)
TypeEvent.OnTemplatesModalListDismiss -> {
uiTemplatesModalListState.value = UiTemplatesModalListState.Hidden(
items = uiTemplatesModalListState.value.items
)
}
TypeEvent.OnTemplatesButtonClick -> {
viewModelScope.launch {
val currentState = uiTemplatesModalListState.value
uiTemplatesModalListState.value = UiTemplatesModalListState.Visible(
items = currentState.items,
showAddIcon = _objectTypePermissionsState.value?.canCreateTemplatesForThisType == true
)
}
}
}
}

View file

@ -790,6 +790,7 @@
<string name="layout">Layout</string>
<string name="button_fields">Fields</string>
<string name="button_layout">Layout</string>
<string name="button_templates">Templates</string>
<string name="restore_from_archive">Restore from archive</string>
<string name="icon">Icon</string>
<string name="icon_description">Emoji or image for object</string>
@ -1869,7 +1870,7 @@ Please provide specific details of your needs here.</string>
<string name="object_type_fields_section_file">File</string>
<string name="object_type_fields_section_local_fields">Local fields</string>
<string name="object_type_fields_title">Fields</string>
<string name="object_type_fields_info_text">You editing type</string>
<string name="object_type_fields_info_text">You\'re editing type</string>
<string name="object_type_fields_edit_field">Edit field</string>
<string name="object_type_fields_new_field">New field</string>
<string name="object_type_fields_preview_field">Preview field</string>
@ -1880,6 +1881,7 @@ Please provide specific details of your needs here.</string>
<string name="object_type_fields_local_info_title">Local fields</string>
<string name="object_type_fields_local_info_description">Some fields are not included in the object type. Please add them if you want to see them in all objects of this type, or remove them.</string>
<string name="object_type_fields_local_info_button">Got it</string>
<string name="object_type_templates_empty">This type doesnt have\nany templates</string>
<string name="object_type_open_type_error">Error opening current object type</string>
@ -1907,7 +1909,7 @@ Please provide specific details of your needs here.</string>
<string name="chat_message_reactions_no_reactions_message">Probably someone has just removed the reaction or technical issue happened</string>
<string name="object_types_human">Human</string>
<string name="object_type_settings_item_remove">Delete completely</string>
<string name="object_type_settings_item_remove">Delete type</string>
<string name="chats_alert_delete_this_message_description">It cannot be restored after confirmation</string>
<string name="chats_alert_delete_this_message">Delete this message?</string>

View file

@ -559,7 +559,7 @@ fun MDVView.toCoreModels(): DVViewer = DVViewer(
coverFit = coverFit,
coverRelationKey = coverRelationKey.ifEmpty { null },
defaultTemplate = defaultTemplateId.ifEmpty { null },
defaultObjectType = defaultObjectTypeId.ifEmpty { ObjectTypeIds.PAGE }
defaultObjectType = defaultObjectTypeId.ifEmpty { null }
)
fun MDVRelation.toCoreModels(): DVViewerRelation = DVViewerRelation(

View file

@ -1028,6 +1028,10 @@ fun CoroutineScope.logEvent(
is ObjectState.DataView.Set -> {
Pair("ot-set", state.viewerByIdOrFirst(currentViewId)?.type?.formattedName)
}
is ObjectState.DataView.TypeSet -> {
Pair("ot-set", state.viewerByIdOrFirst(currentViewId)?.type?.formattedName)
}
}
val scope = this
when (event) {
@ -1054,6 +1058,18 @@ fun CoroutineScope.logEvent(
spaceParams = spaceParams
)
)
is ObjectState.DataView.TypeSet -> scope.sendEvent(
analytics = analytics,
eventName = setScreenShow,
startTime = startTime,
middleTime = middleTime,
props = buildProps(
embedType = embedTypeDefault,
type = viewerType,
spaceParams = spaceParams
)
)
}
}
ObjectStateAnalyticsEvent.TURN_INTO_COLLECTION -> {
@ -1244,7 +1260,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.OBJECT_CREATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1262,7 +1278,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.SELECT_TEMPLATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1278,7 +1294,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.SHOW_TEMPLATES -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1294,7 +1310,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.CREATE_TEMPLATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1312,7 +1328,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.EDIT_TEMPLATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1330,7 +1346,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.DUPLICATE_TEMPLATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1348,7 +1364,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.DELETE_TEMPLATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,
@ -1366,7 +1382,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.SET_AS_DEFAULT_TYPE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
if (state.isChangingDefaultTypeAvailable()) {
scope.sendEvent(
@ -1386,7 +1402,7 @@ fun CoroutineScope.logEvent(
ObjectStateAnalyticsEvent.CHANGE_DEFAULT_TEMPLATE -> {
val route = when (state) {
is ObjectState.DataView.Collection -> EventsDictionary.Routes.objCreateCollection
is ObjectState.DataView.Set -> EventsDictionary.Routes.objCreateSet
is ObjectState.DataView.Set, is ObjectState.DataView.TypeSet -> EventsDictionary.Routes.objCreateSet
}
scope.sendEvent(
analytics = analytics,

View file

@ -62,6 +62,14 @@ class DataViewObjectRelationProvider(
}
}
}
is ObjectState.DataView.TypeSet -> {
val objectKeys = state.dataViewContent.relationLinks.map { it.key }
flow {
objectKeys.mapNotNull {
storeOfRelations.getByKey(it)
}
}
}
else -> emptyFlow()
}
}

View file

@ -46,6 +46,7 @@ class DataViewObjectValueProvider(
) : Struct = when (state) {
is ObjectState.DataView.Collection -> state.details.getStruct(target).orEmpty()
is ObjectState.DataView.Set -> state.details.getStruct(target).orEmpty()
is ObjectState.DataView.TypeSet -> state.details.getStruct(target).orEmpty()
else -> emptyMap()
}
}
@ -81,6 +82,7 @@ class SetOrCollectionObjectValueProvider(
) : Struct = when (state) {
is ObjectState.DataView.Collection -> state.details.getStruct(target).orEmpty()
is ObjectState.DataView.Set -> state.details.getStruct(target).orEmpty()
is ObjectState.DataView.TypeSet -> state.details.getStruct(target).orEmpty()
else -> emptyMap()
}
}

View file

@ -41,6 +41,10 @@ interface ObjectRelationListProvider {
state.details
}
is ObjectState.DataView.TypeSet -> {
state.details
}
else -> ObjectViewDetails.EMPTY
}
}

View file

@ -55,6 +55,30 @@ sealed class DataViewViewState {
) : Set()
}
sealed class TypeSet : DataViewViewState() {
abstract val isCreateObjectAllowed: Boolean
abstract val isEditingViewAllowed: Boolean
data class NoItems(
val title: String,
override val isCreateObjectAllowed: Boolean = true,
override val isEditingViewAllowed: Boolean = false
) : TypeSet()
data class Default(
val viewer: Viewer?,
override val isCreateObjectAllowed: Boolean = true,
override val isEditingViewAllowed: Boolean = false
) : TypeSet()
data class Error(
val msg: String,
override val isCreateObjectAllowed: Boolean = false,
override val isEditingViewAllowed: Boolean = false
) : TypeSet()
}
object Init: DataViewViewState()
data class Error(val msg: String) : DataViewViewState()
}

View file

@ -92,6 +92,19 @@ class ObjectSetCreateBookmarkRecordViewModel(
) { isCompleted.value = true }
}
}
is ObjectState.DataView.TypeSet -> {
val viewer = state.viewerByIdOrFirst(session.currentViewerId.value) ?: return
val prefilled = viewer.prefillNewObjectDetails(
dateProvider = dateProvider,
storeOfRelations = storeOfRelations,
dataViewRelationLinks = state.dataViewContent.relationLinks
)
createBookmark(
input = input,
details = prefilled
) { isCompleted.value = true }
}
}
} else {
sendToast("Url is invalid.")

View file

@ -397,6 +397,10 @@ fun ObjectState.DataView.Set.getSetOfValue(ctx: Id): List<Id> {
return details.getObject(ctx)?.setOf.orEmpty()
}
fun ObjectState.DataView.TypeSet.getSetOfValue(ctx: Id): List<Id> {
return details.getObject(ctx)?.setOf.orEmpty()
}
fun ObjectState.DataView.filterOutDeletedAndMissingObjects(query: List<Id>): List<Id> {
return query.filter(::isValidObject)
}
@ -488,6 +492,16 @@ suspend fun ObjectState.DataView.toViewersView(ctx: Id, session: ObjectSetSessio
)
}
}
is ObjectState.DataView.TypeSet -> {
val setOfValue = getSetOfValue(ctx)
mapViewers(
defaultObjectType = { setOfValue.firstOrNull() },
viewers = viewers,
session = session,
storeOfRelations = storeOfRelations
)
}
}
}
@ -551,6 +565,23 @@ suspend fun ObjectState.DataView.getActiveViewTypeAndTemplate(
}
}
}
is ObjectState.DataView.TypeSet -> {
val setOfValue = getSetOfValue(ctx)
val setOf = setOfValue.firstOrNull()
return if (setOf.isNullOrBlank()) {
Timber.d("Set by type setOf param is null or empty, not possible to get Type and Template")
Pair(null, null)
} else {
val defaultSetObjectType = details.getTypeObject(setOf)
if (activeView.defaultTemplate.isNullOrEmpty()) {
val defaultTemplateId = defaultSetObjectType?.defaultTemplateId
Pair(defaultSetObjectType, defaultTemplateId)
} else {
Pair(defaultSetObjectType, activeView.defaultTemplate)
}
}
}
}
}
@ -580,6 +611,7 @@ fun ObjectState.DataView.isChangingDefaultTypeAvailable(): Boolean {
val setOfValue = getSetOfValue(root)
isSetByRelation(setOfValue = setOfValue)
}
is ObjectState.DataView.TypeSet -> false
}
}

View file

@ -87,6 +87,7 @@ import com.anytypeio.anytype.presentation.relations.ObjectSetConfig.DEFAULT_LIMI
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
import com.anytypeio.anytype.presentation.relations.render
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.sets.ObjectSetCommand.Modal.*
import com.anytypeio.anytype.presentation.sets.model.CellView
import com.anytypeio.anytype.presentation.sets.model.Viewer
import com.anytypeio.anytype.presentation.sets.state.ObjectState
@ -479,6 +480,22 @@ class ObjectSetViewModel(
}
}
is ObjectState.DataView.TypeSet -> {
Timber.d("subscribeToObjectState, NEW TYPE SET STATE")
if (query.state.isInitialized) {
dataViewSubscription.startObjectTypeSetSubscription(
space = vmParams.space.id,
context = vmParams.ctx,
state = query.state,
currentViewerId = query.currentViewerId,
offset = query.offset,
dataViewRelationLinks = query.state.dataViewContent.relationLinks
)
} else {
emptyFlow()
}
}
else -> {
Timber.d("subscribeToObjectState, NEW STATE, ${query.state}")
emptyFlow()
@ -584,6 +601,12 @@ class ObjectSetViewModel(
)
ObjectState.Init -> DataViewViewState.Init
ObjectState.ErrorLayout -> DataViewViewState.Error(msg = "Wrong layout, couldn't open object")
is ObjectState.DataView.TypeSet -> processTypeSetState(
dataViewState = dataViewState,
objectState = objectState,
currentViewId = currentViewId,
permission = permission
)
}
}
@ -726,6 +749,75 @@ class ObjectSetViewModel(
}
}
private suspend fun processTypeSetState(
dataViewState: DataViewState,
objectState: ObjectState.DataView.TypeSet,
currentViewId: String?,
permission: SpaceMemberPermissions?
): DataViewViewState {
if (!objectState.isInitialized) return DataViewViewState.Init
val setOfValue = objectState.getSetOfValue(ctx = vmParams.ctx)
val query = objectState.filterOutDeletedAndMissingObjects(query = setOfValue)
val viewer = objectState.viewerByIdOrFirst(currentViewId)
return when (dataViewState) {
DataViewState.Init -> {
_dvViews.value = emptyList()
when {
setOfValue.isEmpty() || query.isEmpty() || viewer == null ->
DataViewViewState.TypeSet.Error(msg = "Error while rendering viewer")
else -> DataViewViewState.Init
}
}
is DataViewState.Loaded -> {
_dvViews.value = objectState.dataViewState()?.toViewersView(
ctx = vmParams.ctx,
session = session,
storeOfRelations = storeOfRelations
) ?: emptyList()
val relations = objectState.dataViewContent.relationLinks.mapNotNull {
storeOfRelations.getByKey(it.key)
}
val render = viewer?.render(
coverImageHashProvider = coverImageHashProvider,
builder = urlBuilder,
objects = dataViewState.objects,
dataViewRelations = relations,
store = objectStore,
storeOfRelations = storeOfRelations,
fieldParser = fieldParser
)
when {
render == null || query.isEmpty() || setOfValue.isEmpty() -> DataViewViewState.TypeSet.Error(
msg = "Error while rendering viewer",
)
render.isEmpty() -> {
val (defType, _) = objectState.getActiveViewTypeAndTemplate(
vmParams.ctx, viewer, storeOfObjectTypes
)
DataViewViewState.TypeSet.NoItems(
title = render.title,
isCreateObjectAllowed = objectState.isCreateObjectAllowed(defType) && (permission?.isOwnerOrEditor() == true),
isEditingViewAllowed = permission?.isOwnerOrEditor() == true
)
}
else -> {
val (defType, _) = objectState.getActiveViewTypeAndTemplate(
vmParams.ctx, viewer, storeOfObjectTypes
)
DataViewViewState.TypeSet.Default(
viewer = render,
isCreateObjectAllowed = objectState.isCreateObjectAllowed(defType) && (permission?.isOwnerOrEditor() == true),
isEditingViewAllowed = permission?.isOwnerOrEditor() == true
)
}
}
}
}
}
private suspend fun renderViewer(
objectState: ObjectState.DataView.Collection,
dataViewState: DataViewState.Loaded,
@ -1015,7 +1107,7 @@ class ObjectSetViewModel(
}
// TODO Multispaces refactor this method
private suspend fun proceedWithCreatingSetObject(currentState: ObjectState.DataView.Set, templateChosenBy: Id?) {
private suspend fun proceedWithCreatingSetObject(currentState: ObjectState.DataView, templateChosenBy: Id?) {
if (isRestrictionPresent(DataViewRestriction.CREATE_OBJECT)) {
toast(NOT_ALLOWED)
} else {
@ -1687,6 +1779,10 @@ class ObjectSetViewModel(
dispatch(ObjectSetCommand.ShowOnlyAccessError)
}
}
is ObjectState.DataView.TypeSet -> {
//do nothing
}
}
}
else -> {
@ -2687,6 +2783,13 @@ class ObjectSetViewModel(
templateChosenBy = templateId
)
}
is ObjectState.DataView.TypeSet -> {
proceedWithCreatingSetObject(
currentState = state,
templateChosenBy = templateId
)
}
}
}
}

View file

@ -133,6 +133,12 @@ class DefaultObjectStateReducer : ObjectStateReducer {
details = ObjectViewDetails(event.details),
dataViewRestrictions = event.dataViewRestrictions
)
ObjectType.Layout.OBJECT_TYPE.code -> ObjectState.DataView.TypeSet(
root = event.root,
blocks = event.blocks,
details = ObjectViewDetails(event.details),
dataViewRestrictions = event.dataViewRestrictions
)
else -> {
Timber.e("Wrong layout type: $layout")
ObjectState.ErrorLayout
@ -157,6 +163,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.target,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.target,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -184,6 +194,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.target,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.target,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -211,6 +225,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.dv,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.dv,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -236,6 +254,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.dv,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.dv,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -261,6 +283,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.dv,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.dv,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -284,6 +310,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.dv,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.dv,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -360,6 +390,10 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = event.block,
blockContentUpdate = updateBlockContent
)
is ObjectState.DataView.TypeSet -> state.updateBlockContent(
target = event.block,
blockContentUpdate = updateBlockContent
)
else -> state
}
}
@ -390,6 +424,15 @@ class DefaultObjectStateReducer : ObjectStateReducer {
)
)
}
is ObjectState.DataView.TypeSet -> {
state.copy(
details = ObjectViewDetails(
details = state.details.details.toMutableMap().apply {
put(event.target, event.details)
}
)
)
}
else -> state
}
}
@ -418,6 +461,14 @@ class DefaultObjectStateReducer : ObjectStateReducer {
)
)
)
is ObjectState.DataView.TypeSet -> state.copy(
details = state.details.copy(
details = state.details.details.amend(
target = event.target,
slice = event.details
)
)
)
else -> state
}
}
@ -446,6 +497,14 @@ class DefaultObjectStateReducer : ObjectStateReducer {
)
)
)
is ObjectState.DataView.TypeSet -> state.copy(
details = state.details.copy(
details = state.details.details.unset(
target = event.target,
keys = event.keys
)
)
)
else -> state
}
}
@ -470,6 +529,12 @@ class DefaultObjectStateReducer : ObjectStateReducer {
target = { block -> block.id == event.id }
)
)
is ObjectState.DataView.TypeSet -> state.copy(
blocks = state.blocks.replace(
replacement = { target -> target.copy(children = event.children) },
target = { block -> block.id == event.id }
)
)
else -> state
}
}
@ -481,6 +546,7 @@ class DefaultObjectStateReducer : ObjectStateReducer {
return when (state) {
is ObjectState.DataView.Collection -> state.copy(blocks = state.blocks + event.blocks)
is ObjectState.DataView.Set -> state.copy(blocks = state.blocks + event.blocks)
is ObjectState.DataView.TypeSet -> state.copy(blocks = state.blocks + event.blocks)
ObjectState.Init -> state
ObjectState.ErrorLayout -> state
}
@ -502,6 +568,7 @@ class DefaultObjectStateReducer : ObjectStateReducer {
return when (this) {
is ObjectState.DataView.Collection -> copy(blocks = updatedBlocks)
is ObjectState.DataView.Set -> copy(blocks = updatedBlocks)
is ObjectState.DataView.TypeSet -> copy(blocks = updatedBlocks)
}
}

View file

@ -53,6 +53,20 @@ sealed class ObjectState {
override val dataViewContent get() = dataViewBlock.content as DV
override val viewers get() = dataViewContent.viewers
}
data class TypeSet(
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(),
) : DataView() {
override val isInitialized get() = blocks.any { it.content is DV }
override val dataViewBlock get() = blocks.first { it.content is DV }
override val dataViewContent get() = dataViewBlock.content as DV
override val viewers get() = dataViewContent.viewers
}
}
object Init : ObjectState() {

View file

@ -40,6 +40,15 @@ interface DataViewSubscription {
dataViewRelationLinks: List<RelationLink>
): Flow<DataViewState>
suspend fun startObjectTypeSetSubscription(
context: Id,
space: Id,
state: ObjectState.DataView.TypeSet,
currentViewerId: Id?,
offset: Long,
dataViewRelationLinks: List<RelationLink>
): Flow<DataViewState>
suspend fun unsubscribe(ids: List<Id>)
}
@ -139,6 +148,59 @@ class DefaultDataViewSubscription(
return dataViewSubscriptionContainer.observe(params)
}
override suspend fun startObjectTypeSetSubscription(
context: Id,
space: Id,
state: ObjectState.DataView.TypeSet,
currentViewerId: Id?,
offset: Long,
dataViewRelationLinks: List<RelationLink>
): Flow<DataViewState> {
if (context.isEmpty()) {
Timber.w("Data view TypeSet subscription: context is empty")
return emptyFlow()
}
val activeViewer = state.viewerByIdOrFirst(currentViewerId)
if (activeViewer == null) {
Timber.w("Data view TypeSet subscription: active viewer is null")
return emptyFlow()
}
val setOfValue = state.getSetOfValue(ctx = context)
if (setOfValue.isEmpty()) {
Timber.w("Data view TypeSet subscription: setOf value is empty, proceed without subscription")
return emptyFlow()
}
val query = state.filterOutDeletedAndMissingObjects(setOfValue)
if (query.isEmpty()) {
Timber.w(
"Data view TypeSet subscription: query has no valid types or relations, " +
"proceed without subscription"
)
return emptyFlow()
}
val filters = buildList {
addAll(activeViewer.filters.updateFormatForSubscription(relationLinks = dataViewRelationLinks))
addAll(defaultDataViewFilters())
}
val dataViewLinksKeys = state.dataViewContent.relationLinks.map { it.key }
val keys = ObjectSearchConstants.defaultDataViewKeys + dataViewLinksKeys
val params = DataViewSubscriptionContainer.Params(
space = SpaceId(space),
subscription = getDataViewSubscriptionId(context),
sorts = activeViewer.sorts.updateWithRelationFormat(relationLinks = dataViewRelationLinks),
filters = filters,
sources = query,
keys = keys,
limit = ObjectSetConfig.DEFAULT_LIMIT,
offset = offset
)
return dataViewSubscriptionContainer.observe(params)
}
override suspend fun unsubscribe(ids: List<Id>) {
dataViewSubscriptionContainer.unsubscribe(ids)
}