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

DROID-3013 Primitives | Type screen, Sets migration, part 1 (#2133)

This commit is contained in:
Konstantin Ivanov 2025-03-05 16:13:28 +01:00 committed by GitHub
parent 37da962c6a
commit e0acb86116
Signed by: github
GPG key ID: B5690EEEBB952194
8 changed files with 8 additions and 584 deletions

View file

@ -71,7 +71,6 @@ class ObjectTypeFieldsFragment : BaseBottomSheetComposeFragment() {
val params = ObjectTypeVmParams(
spaceId = SpaceId(space),
objectId = typeId,
withSubscriptions = false,
showHiddenFields = true
)
componentManager().objectTypeComponent.get(params).inject(this)

View file

@ -160,15 +160,6 @@ class ObjectTypeFragment : BaseComposeFragment() {
uiIconState = vm.uiIconState.collectAsStateWithLifecycle().value,
uiFieldsButtonState = vm.uiFieldsButtonState.collectAsStateWithLifecycle().value,
uiLayoutButtonState = vm.uiLayoutButtonState.collectAsStateWithLifecycle().value,
uiTemplatesHeaderState = vm.uiTemplatesHeaderState.collectAsStateWithLifecycle().value,
uiTemplatesAddIconState = vm.uiTemplatesAddIconState.collectAsStateWithLifecycle().value,
uiTemplatesListState = vm.uiTemplatesListState.collectAsStateWithLifecycle().value,
uiObjectsHeaderState = vm.uiObjectsHeaderState.collectAsStateWithLifecycle().value,
uiObjectsAddIconState = vm.uiObjectsAddIconState.collectAsStateWithLifecycle().value,
uiObjectsSettingsIconState = vm.uiObjectsSettingsIconState.collectAsStateWithLifecycle().value,
uiObjectsMenuState = vm.uiMenuState.collectAsStateWithLifecycle().value,
uiObjectsListState = vm.uiObjectsListState.collectAsStateWithLifecycle().value,
uiContentState = vm.uiContentState.collectAsStateWithLifecycle().value,
uiDeleteAlertState = vm.uiAlertState.collectAsStateWithLifecycle().value,
uiEditButtonState = vm.uiEditButtonState.collectAsStateWithLifecycle().value,
uiLayoutTypeState = vm.uiTypeLayoutsState.collectAsStateWithLifecycle().value,
@ -303,7 +294,6 @@ class ObjectTypeFragment : BaseComposeFragment() {
val params = ObjectTypeVmParams(
spaceId = SpaceId(space),
objectId = objectId,
withSubscriptions = true,
showHiddenFields = true
)
componentManager().objectTypeComponent.get(params).inject(this)

View file

@ -15,21 +15,16 @@ import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Text
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate
@ -38,21 +33,8 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncNetwork
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate
import com.anytypeio.anytype.core_models.primitives.TypeId
import com.anytypeio.anytype.core_models.primitives.TypeKey
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.swapList
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.lists.objects.ListItemLoading
import com.anytypeio.anytype.core_ui.lists.objects.ObjectsListItem
import com.anytypeio.anytype.core_ui.lists.objects.UiContentState
import com.anytypeio.anytype.core_ui.lists.objects.UiObjectsListState
import com.anytypeio.anytype.core_ui.syncstatus.SpaceSyncStatusScreen
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.Relations3
import com.anytypeio.anytype.core_ui.views.Title2
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.feature_object_type.R
import com.anytypeio.anytype.feature_object_type.ui.alerts.DeleteAlertScreen
@ -60,13 +42,8 @@ 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.header.TopToolbar
import com.anytypeio.anytype.feature_object_type.ui.layouts.TypeLayoutsScreen
import com.anytypeio.anytype.feature_object_type.ui.objects.ObjectsHeader
import com.anytypeio.anytype.feature_object_type.ui.templates.TemplatesScreen
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState
import com.anytypeio.anytype.presentation.templates.TemplateView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -82,28 +59,12 @@ fun ObjectTypeMainScreen(
uiFieldsButtonState: UiFieldsButtonState,
uiLayoutButtonState: UiLayoutButtonState,
uiLayoutTypeState: UiLayoutTypeState,
//templates header
uiTemplatesHeaderState: UiTemplatesHeaderState,
uiTemplatesAddIconState: UiTemplatesAddIconState,
//templates list
uiTemplatesListState: UiTemplatesListState,
//objects header
uiObjectsHeaderState: UiObjectsHeaderState,
uiObjectsAddIconState: UiObjectsAddIconState,
uiObjectsSettingsIconState: UiObjectsSettingsIconState,
uiObjectsMenuState: UiMenuState,
//objects list
uiObjectsListState: UiObjectsListState,
uiContentState: UiContentState,
//delete alert
uiDeleteAlertState: UiDeleteAlertState,
//events
onTypeEvent: (TypeEvent) -> Unit
) {
val objects = remember { mutableStateListOf<UiObjectsListItem>() }
objects.swapList(uiObjectsListState.items)
val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(
state = rememberTopAppBarState()
)
@ -130,14 +91,6 @@ fun ObjectTypeMainScreen(
uiTitleState = uiTitleState,
uiFieldsButtonState = uiFieldsButtonState,
uiLayoutButtonState = uiLayoutButtonState,
uiTemplatesHeaderState = uiTemplatesHeaderState,
uiTemplatesAddIconState = uiTemplatesAddIconState,
uiTemplatesListState = uiTemplatesListState,
uiObjectsHeaderState = uiObjectsHeaderState,
uiObjectsAddIconState = uiObjectsAddIconState,
uiObjectsSettingsIconState = uiObjectsSettingsIconState,
uiObjectsMenuState = uiObjectsMenuState,
objects = objects,
onTypeEvent = onTypeEvent
)
}
@ -170,14 +123,6 @@ private fun MainContent(
uiTitleState: UiTitleState,
uiFieldsButtonState: UiFieldsButtonState,
uiLayoutButtonState: UiLayoutButtonState,
uiTemplatesHeaderState: UiTemplatesHeaderState,
uiTemplatesAddIconState: UiTemplatesAddIconState,
uiTemplatesListState: UiTemplatesListState,
uiObjectsHeaderState: UiObjectsHeaderState,
uiObjectsAddIconState: UiObjectsAddIconState,
uiObjectsSettingsIconState: UiObjectsSettingsIconState,
uiObjectsMenuState: UiMenuState,
objects: List<UiObjectsListItem>,
onTypeEvent: (TypeEvent) -> Unit
) {
// Adjust content modifier based on SDK version for proper insets handling
@ -218,98 +163,6 @@ private fun MainContent(
onTypeEvent = onTypeEvent
)
}
if (uiTemplatesHeaderState is UiTemplatesHeaderState.Visible) {
item {
TemplatesScreen(
uiTemplatesHeaderState = uiTemplatesHeaderState,
uiTemplatesAddIconState = uiTemplatesAddIconState,
uiTemplatesListState = uiTemplatesListState,
onTypeEvent = onTypeEvent
)
}
}
item {
ObjectsHeader(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.padding(horizontal = 20.dp),
uiObjectsHeaderState = uiObjectsHeaderState,
uiObjectsAddIconState = uiObjectsAddIconState,
uiObjectsSettingsIconState = uiObjectsSettingsIconState,
uiObjectsMenuState = uiObjectsMenuState,
onTypeEvent = onTypeEvent
)
}
if (objects.isEmpty()) {
item {
EmptyScreen(
modifier = Modifier.padding(top = 18.dp)
)
}
} else {
items(
count = objects.size,
key = { index -> objects[index].id },
contentType = { index ->
when (objects[index]) {
is UiObjectsListItem.Loading -> "loading"
is UiObjectsListItem.Item -> "item"
}
}
) { index ->
when (val item = objects[index]) {
is UiObjectsListItem.Item -> {
ObjectsListItem(
modifier = Modifier
.fillMaxWidth()
.animateItem()
.padding(horizontal = 4.dp)
.noRippleThrottledClickable {
onTypeEvent(TypeEvent.OnObjectItemClick(item))
},
item = item
)
Divider(paddingStart = 20.dp, paddingEnd = 20.dp)
}
is UiObjectsListItem.Loading -> {
ListItemLoading(modifier = Modifier)
}
}
}
}
// Objects menu actions
when (val itemSet = uiObjectsMenuState.objSetItem) {
UiMenuSetItem.CreateSet -> {
item {
ButtonSecondary(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 20.dp, end = 20.dp),
text = stringResource(R.string.object_type_objects_menu_create_set),
size = ButtonSize.Large,
onClick = { onTypeEvent(TypeEvent.OnCreateSetClick) }
)
}
}
is UiMenuSetItem.OpenSet -> {
item {
ButtonSecondary(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 20.dp, end = 20.dp),
text = stringResource(R.string.object_type_objects_menu_open_set),
size = ButtonSize.Large,
onClick = { onTypeEvent(TypeEvent.OnOpenSetClick(setId = itemSet.setId)) }
)
}
}
UiMenuSetItem.Hidden -> Unit
}
}
}
@ -367,30 +220,6 @@ private fun BottomSyncStatus(
}
}
@Composable
fun EmptyScreen(modifier: Modifier) {
Column(modifier = modifier) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
text = stringResource(R.string.object_type_empty_items_title),
color = colorResource(id = R.color.text_secondary),
style = Title2,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
text = stringResource(R.string.object_type_empty_items_subtitle),
color = colorResource(id = R.color.text_secondary),
style = Relations3,
textAlign = TextAlign.Center
)
}
}
@DefaultPreviews
@Composable
fun ObjectTypeMainScreenPreview() {
@ -413,46 +242,6 @@ fun ObjectTypeMainScreenPreview() {
uiTitleState = UiTitleState(title = "title", isEditable = true),
uiFieldsButtonState = UiFieldsButtonState.Visible(4),
uiLayoutButtonState = UiLayoutButtonState.Visible(layout = ObjectType.Layout.VIDEO),
uiTemplatesHeaderState = UiTemplatesHeaderState.Visible(count = "3"),
uiTemplatesAddIconState = UiTemplatesAddIconState.Visible,
uiTemplatesListState = UiTemplatesListState(
items = listOf(
TemplateView.Template(
id = "1",
name = "Template 1",
targetTypeId = TypeId("page"),
targetTypeKey = TypeKey("ot-page"),
layout = ObjectType.Layout.BASIC,
image = null,
emoji = ":)",
coverColor = CoverColor.RED,
coverGradient = null,
coverImage = null,
),
TemplateView.Template(
id = "2",
name = "Template 2",
targetTypeId = TypeId("note"),
targetTypeKey = TypeKey("ot-note"),
layout = ObjectType.Layout.NOTE,
image = null,
emoji = null,
coverColor = null,
coverGradient = null,
coverImage = null,
),
TemplateView.New(
targetTypeId = TypeId("32423"),
targetTypeKey = TypeKey("43232")
)
)
),
uiObjectsAddIconState = UiObjectsAddIconState.Visible,
uiObjectsHeaderState = UiObjectsHeaderState(count = "3"),
uiObjectsSettingsIconState = UiObjectsSettingsIconState.Visible,
uiObjectsListState = UiObjectsListState(emptyList()),
uiContentState = UiContentState.Idle(),
uiObjectsMenuState = UiMenuState.EMPTY,
uiDeleteAlertState = UiDeleteAlertState.Hidden,
uiEditButtonState = UiEditButton.Visible,
uiLayoutTypeState = UiLayoutTypeState.Hidden,

View file

@ -1,10 +1,7 @@
package com.anytypeio.anytype.feature_object_type.ui
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType.Layout
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
import com.anytypeio.anytype.presentation.templates.TemplateView
sealed class TypeEvent {
@ -23,20 +20,6 @@ sealed class TypeEvent {
data class OnObjectTypeTitleUpdate(val title: String) : TypeEvent()
//endregion
//region Sets
data object OnCreateSetClick : TypeEvent()
data class OnOpenSetClick(val setId: Id) : TypeEvent()
//endregion
//region Objects Header
data class OnSortClick(val sort: ObjectsListSort) : TypeEvent()
data object OnCreateObjectIconClick : TypeEvent()
//endregion
//region Objects list
data class OnObjectItemClick(val item: UiObjectsListItem) : TypeEvent()
//endregion
//region Templates
data object OnTemplatesAddIconClick : TypeEvent()
data class OnTemplateItemClick(val item: TemplateView) : TypeEvent()
@ -56,7 +39,4 @@ sealed class TypeEvent {
data object OnLayoutButtonClick : TypeEvent()
data object OnFieldsButtonClick : TypeEvent()
data object OnObjectsSettingsIconClick : TypeEvent()
}

View file

@ -15,7 +15,6 @@ import com.anytypeio.anytype.presentation.templates.TemplateView
data class ObjectTypeVmParams(
val objectId: Id,
val spaceId: SpaceId,
val withSubscriptions: Boolean,
val showHiddenFields: Boolean
)

View file

@ -111,7 +111,6 @@ fun ObjectsHeader(
title = stringResource(R.string.object_type_objects_menu_create_set),
isSelected = false,
modifier = Modifier
.clickable { onTypeEvent(TypeEvent.OnCreateSetClick) }
)
Divider(
height = 8.dp,
@ -126,7 +125,6 @@ fun ObjectsHeader(
title = stringResource(R.string.object_type_objects_menu_open_set),
isSelected = false,
modifier = Modifier
.clickable { onTypeEvent(TypeEvent.OnOpenSetClick(setId = item.setId)) }
)
Divider(
height = 8.dp,
@ -144,7 +142,6 @@ fun ObjectsHeader(
types = uiObjectsMenuState.types,
sortingExpanded = isSortingExpanded,
onSortClick = {
onTypeEvent(TypeEvent.OnSortClick(it))
},
onChangeSortExpandedState = { isSortingExpanded = it }
)
@ -157,7 +154,6 @@ fun ObjectsHeader(
.height(48.dp)
.width(32.dp)
.noRippleThrottledClickable {
onTypeEvent(TypeEvent.OnCreateObjectIconClick)
},
contentAlignment = Alignment.CenterEnd
) {

View file

@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.ObjectOrigin
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
@ -13,9 +12,6 @@ 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.core_ui.lists.objects.UiContentState
import com.anytypeio.anytype.core_ui.lists.objects.UiObjectsListState
import com.anytypeio.anytype.core_utils.ext.orNull
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider
@ -55,15 +51,9 @@ 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.UiLayoutTypeState.*
import com.anytypeio.anytype.feature_object_type.ui.UiMenuSetItem
import com.anytypeio.anytype.feature_object_type.ui.UiMenuState
import com.anytypeio.anytype.feature_object_type.ui.UiObjectsAddIconState
import com.anytypeio.anytype.feature_object_type.ui.UiObjectsHeaderState
import com.anytypeio.anytype.feature_object_type.ui.UiObjectsSettingsIconState
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.UiTemplatesListState
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
@ -75,13 +65,6 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsScreenObjectTyp
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.objects.ObjectsListSort
import com.anytypeio.anytype.presentation.objects.UiObjectsListItem
import com.anytypeio.anytype.presentation.objects.toDVSort
import com.anytypeio.anytype.presentation.objects.toMenuSortContainer
import com.anytypeio.anytype.presentation.objects.toSortOptions
import com.anytypeio.anytype.presentation.objects.toSortTypeOptions
import com.anytypeio.anytype.presentation.objects.toUiObjectsListItem
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultKeys
import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState
import com.anytypeio.anytype.presentation.sync.toSyncStatusWidgetState
@ -102,10 +85,8 @@ import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import timber.log.Timber
@ -164,26 +145,6 @@ class ObjectTypeViewModel(
val uiTemplatesAddIconState =
MutableStateFlow<UiTemplatesAddIconState>(UiTemplatesAddIconState.Hidden)
//templates list
val uiTemplatesListState =
MutableStateFlow<UiTemplatesListState>(UiTemplatesListState.Companion.EMPTY)
//objects header
val uiObjectsHeaderState =
MutableStateFlow<UiObjectsHeaderState>(UiObjectsHeaderState.Companion.EMPTY)
val uiObjectsAddIconState =
MutableStateFlow<UiObjectsAddIconState>(UiObjectsAddIconState.Hidden)
val uiObjectsSettingsIconState =
MutableStateFlow<UiObjectsSettingsIconState>(UiObjectsSettingsIconState.Hidden)
val uiMenuState = MutableStateFlow<UiMenuState>(UiMenuState.Companion.EMPTY)
//objects list
val uiObjectsListState = MutableStateFlow<UiObjectsListState>(UiObjectsListState.Empty)
val uiContentState = MutableStateFlow<UiContentState>(UiContentState.Idle())
private val restartSubscription = MutableStateFlow(0L)
private var shouldScrollToTopItems = false
private val _sortState = MutableStateFlow<ObjectsListSort>(ObjectsListSort.ByName())
//alerts
val uiAlertState = MutableStateFlow<UiDeleteAlertState>(UiDeleteAlertState.Hidden)
val uiFieldLocalInfoState =
@ -213,14 +174,11 @@ class ObjectTypeViewModel(
proceedWithObservingSyncStatus()
proceedWithObservingObjectType()
proceedWithGetObjectTypeConflictingFields()
setupObjectsMenuFlow()
}
fun onStart() {
Timber.d("onStart, vmParams: $vmParams")
if (vmParams.withSubscriptions) {
startSubscriptions()
}
startSubscriptions()
viewModelScope.launch {
sendAnalyticsScreenObjectType(
analytics = analytics
@ -230,28 +188,11 @@ class ObjectTypeViewModel(
fun onStop() {
Timber.d("onStop")
if (vmParams.withSubscriptions) {
stopSubscriptions()
}
uiObjectsListState.value = UiObjectsListState.Empty
stopSubscriptions()
}
//endregion
//region DATA
private fun setupObjectsMenuFlow() {
viewModelScope.launch {
_sortState.map { sort ->
uiMenuState.value.copy(
container = sort.toMenuSortContainer(),
sorts = sort.toSortOptions(),
types = sort.toSortTypeOptions()
)
}
.collect { newMenuState ->
uiMenuState.value = newMenuState
}
}
}
private fun proceedWithObservingObjectType() {
viewModelScope.launch {
@ -309,8 +250,6 @@ class ObjectTypeViewModel(
}
private fun startSubscriptions() {
startObjectsSubscription()
startSetSubscription()
startTemplatesSubscription()
}
@ -318,59 +257,12 @@ class ObjectTypeViewModel(
viewModelScope.launch {
storelessSubscriptionContainer.unsubscribe(
listOf(
objectsSubId(vmParams.objectId),
setsSubId(vmParams.objectId),
templatesSubId(vmParams.objectId),
)
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun startObjectsSubscription() {
viewModelScope.launch {
combine(
restartSubscription,
_objTypeState,
_objectTypePermissionsState
) { restart, objType, permission ->
objType to permission
}.flatMapLatest { (objType, permission) ->
if (objType == null || permission == null) {
emptyFlow()
} else {
loadObjects(
typeName = fieldParser.getObjectName(objectWrapper = objType),
permissions = permission
).map { items -> items to permission }
}
}.catch {
Timber.e(it, "Error while observing objects")
errorState.value =
UiErrorState.Show(UiErrorState.Reason.ErrorGettingObjects(it.message ?: ""))
}.collect { (items, permission) ->
mapObjectsSubscriptionToUi(items, permission)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun startSetSubscription() {
viewModelScope.launch {
_objectTypePermissionsState
.flatMapLatest { permissions ->
if (permissions != null) {
loadSet().map { items -> items to permissions }
} else {
emptyFlow()
}
}.collect { (items, permissions) ->
Timber.d("items: $items, permissions: $permissions")
mapSetSubscriptionToUi(items, permissions)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun startTemplatesSubscription() {
viewModelScope.launch {
@ -393,60 +285,6 @@ class ObjectTypeViewModel(
}
}
private fun loadObjects(
typeName: String,
permissions: ObjectPermissions
): Flow<List<UiObjectsListItem>> {
val activeSort = _sortState.value
val searchParams = StoreSearchParams(
filters = filtersForSearch(objectTypeId = vmParams.objectId),
sorts = listOf(activeSort.toDVSort()),
space = vmParams.spaceId,
limit = OBJECTS_MAX_COUNT,
keys = defaultKeys,
subscription = objectsSubId(vmParams.objectId)
)
return storelessSubscriptionContainer.subscribe(searchParams)
.onStart {
uiContentState.value = UiContentState.Paging
}
.map { objWrappers ->
val items = objWrappers.map {
it.toUiObjectsListItem(
space = vmParams.spaceId,
urlBuilder = urlBuilder,
typeName = typeName,
fieldParser = fieldParser,
isOwnerOrEditor = permissions.participantCanEdit
)
}
items
}.catch { e ->
handleError(e)
}
}
private fun loadSet(): Flow<List<ObjectWrapper.Basic>> {
val searchParams = StoreSearchParams(
filters = filtersForSetsSearch(objectTypeId = vmParams.objectId),
sorts = listOf(sortForSetSearch()),
space = vmParams.spaceId,
limit = 1,
keys = defaultKeys,
subscription = setsSubId(vmParams.objectId)
)
return storelessSubscriptionContainer.subscribe(searchParams)
.catch {
handleError(it)
emit(emptyList())
}
}
private fun loadTemplates(objType: ObjectWrapper.Type): Flow<List<TemplateView>> {
val searchParams = StoreSearchParams(
@ -478,18 +316,6 @@ class ObjectTypeViewModel(
//region UI STATE
private fun updateDefaultTemplates(defaultTemplate: Id?) {
val templates = uiTemplatesListState.value.items
uiTemplatesListState.value = uiTemplatesListState.value.copy(
templates.map { template ->
when (template) {
is TemplateView.Blank -> template
is TemplateView.New -> template
is TemplateView.Template -> {
template.copy(isDefault = template.id == defaultTemplate)
}
}
}
)
}
private suspend fun mapObjectTypeToUi(
@ -502,7 +328,6 @@ class ObjectTypeViewModel(
if (!objectPermissions.canCreateTemplatesForThisType) {
uiTemplatesHeaderState.value = UiTemplatesHeaderState.Hidden
uiTemplatesListState.value = UiTemplatesListState.EMPTY
uiTemplatesAddIconState.value = UiTemplatesAddIconState.Hidden
}
uiTitleState.value = UiTitleState(
@ -513,10 +338,6 @@ class ObjectTypeViewModel(
icon = objType.objectIcon(urlBuilder),
isEditable = objectPermissions.canEditDetails
)
if (objectPermissions.canCreateObjectThisType) {
uiObjectsAddIconState.value = UiObjectsAddIconState.Visible
}
uiObjectsSettingsIconState.value = UiObjectsSettingsIconState.Visible
if (objectPermissions.canDelete) {
uiEditButtonState.value = UiEditButton.Visible
}
@ -542,47 +363,6 @@ class ObjectTypeViewModel(
)
}
private fun mapObjectsSubscriptionToUi(
items: List<UiObjectsListItem>,
permission: ObjectPermissions
) {
if (items.isEmpty()) {
uiObjectsListState.value = UiObjectsListState.Empty
uiContentState.value = UiContentState.Idle()
uiObjectsHeaderState.value = UiObjectsHeaderState(count = "0")
uiObjectsSettingsIconState.value = UiObjectsSettingsIconState.Visible
} else {
uiContentState.value = UiContentState.Idle(
scrollToTop = shouldScrollToTopItems.also { shouldScrollToTopItems = false }
)
uiObjectsListState.value = UiObjectsListState(items = items)
uiObjectsHeaderState.value = UiObjectsHeaderState(count = "${items.size}")
uiObjectsSettingsIconState.value = UiObjectsSettingsIconState.Visible
}
if (permission.canCreateObjectThisType) {
uiObjectsAddIconState.value = UiObjectsAddIconState.Visible
}
}
private fun mapSetSubscriptionToUi(
items: List<ObjectWrapper.Basic>,
permissions: ObjectPermissions
) {
uiMenuState.value = if (!permissions.participantCanEdit) {
if (items.isEmpty()) {
uiMenuState.value.copy(objSetItem = UiMenuSetItem.Hidden)
} else {
uiMenuState.value.copy(objSetItem = UiMenuSetItem.OpenSet(setId = items[0].id))
}
} else {
if (items.isEmpty()) {
uiMenuState.value.copy(objSetItem = UiMenuSetItem.CreateSet)
} else {
uiMenuState.value.copy(objSetItem = UiMenuSetItem.OpenSet(setId = items[0].id))
}
}
}
private fun mapTemplatesSubscriptionToUi(
objType: ObjectWrapper.Type,
templates: List<TemplateView>,
@ -614,7 +394,6 @@ class ObjectTypeViewModel(
uiTemplatesAddIconState.value = UiTemplatesAddIconState.Visible
}
}
uiTemplatesListState.value = UiTemplatesListState(items = finalTemplates)
}
fun hideError() {
@ -661,26 +440,6 @@ class ObjectTypeViewModel(
updateTitle(event.title)
}
is TypeEvent.OnSortClick -> onSortClicked(event.sort)
TypeEvent.OnObjectsSettingsIconClick -> {
}
TypeEvent.OnCreateSetClick -> {
proceedWithCreateSet()
}
is TypeEvent.OnOpenSetClick -> {
proceedWithNavigation(
objectId = event.setId,
objectLayout = ObjectType.Layout.SET
)
}
TypeEvent.OnCreateObjectIconClick -> {
shouldScrollToTopItems = true
proceedWithCreateObjectOfThisType()
}
TypeEvent.OnMenuItemDeleteClick -> {
uiAlertState.value = UiDeleteAlertState.Show
}
@ -694,21 +453,6 @@ class ObjectTypeViewModel(
uiAlertState.value = UiDeleteAlertState.Hidden
}
is TypeEvent.OnObjectItemClick -> {
when (event.item) {
is UiObjectsListItem.Item -> {
proceedWithNavigation(
objectId = event.item.id,
objectLayout = event.item.layout
)
}
is UiObjectsListItem.Loading -> {
//do nothing
}
}
}
TypeEvent.OnObjectTypeIconClick -> {
viewModelScope.launch {
commands.emit(OpenEmojiPicker)
@ -812,30 +556,6 @@ class ObjectTypeViewModel(
}
}
fun onSortClicked(sort: ObjectsListSort) {
Timber.d("onSortClicked: $sort")
val newSort = when (sort) {
is ObjectsListSort.ByDateCreated -> {
sort.copy(isSelected = true)
}
is ObjectsListSort.ByDateUpdated -> {
sort.copy(isSelected = true)
}
is ObjectsListSort.ByName -> {
sort.copy(isSelected = true)
}
is ObjectsListSort.ByDateUsed -> {
sort.copy(isSelected = true)
}
}
shouldScrollToTopItems = true
_sortState.value = newSort
restartSubscription.value++
}
private fun updateTitle(input: String) {
viewModelScope.launch {
val params = SetObjectDetails.Params(
@ -1128,33 +848,6 @@ class ObjectTypeViewModel(
}
}
private fun proceedWithCreateObjectOfThisType() {
val uniqueKeys = _objTypeState.value?.uniqueKey ?: return
val defaultTemplate =
uiTemplatesListState.value.items.firstOrNull { it.isDefault } as? TemplateView.Template
val params = CreateObject.Param(
space = vmParams.spaceId,
type = TypeKey(uniqueKeys),
template = defaultTemplate?.id,
prefilled = mapOf(
Relations.ORIGIN to ObjectOrigin.BUILT_IN.code.toDouble()
)
)
viewModelScope.launch {
createObject.async(params).fold(
onSuccess = { result ->
proceedWithNavigation(
objectId = result.objectId,
objectLayout = result.obj.layout
)
},
onFailure = {
Timber.e(it, "Error while creating object")
}
)
}
}
private fun proceedWithObjectTypeDelete() {
val params = DeleteObjects.Params(
targets = listOf(vmParams.objectId)
@ -1188,31 +881,6 @@ class ObjectTypeViewModel(
}
}
private fun proceedWithCreateSet() {
val typeName = _objTypeState.value?.name.orEmpty()
val emoji = _objTypeState.value?.iconEmoji.orNull()
val params = CreateObjectSet.Params(
space = vmParams.spaceId.id,
type = vmParams.objectId,
details = mapOf(
Relations.NAME to "${stringResourceProvider.getSetOfObjectsTitle()} $typeName",
Relations.ICON_EMOJI to emoji
)
)
viewModelScope.launch {
createObjectSet.run(params).process(
failure = {},
success = { response ->
val obj = ObjectWrapper.Basic(response.details)
proceedWithNavigation(
objectId = obj.id,
objectLayout = obj.layout
)
}
)
}
}
private fun proceedWithCreateTemplate() {
val params = CreateTemplate.Params(
targetObjectTypeId = vmParams.objectId,
@ -1409,11 +1077,8 @@ class ObjectTypeViewModel(
//endregion
companion object {
const val OBJECTS_MAX_COUNT = 20
const val TEMPLATE_MAX_COUNT = 100
fun objectsSubId(objectId: Id) = "TYPE-OBJECTS-SUB-ID-$objectId"
fun setsSubId(objectId: Id) = "TYPE-SET-ID--$objectId"
fun templatesSubId(objectId: Id) = "TYPE-TEMPLATES-SUB-ID--$objectId"
}
}

View file

@ -526,6 +526,12 @@ class TreeWidgetContainerTest {
links = links.map { it.id },
isDeleted = true
)
stringResourceProvider.stub {
onBlocking {
getDeletedObjectTitle()
} doReturn "Deleted object"
}
val widget = Widget.Tree(
id = MockDataFactory.randomUuid(),