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

DROID-3429 Primitives | Edit type properties, part 1 (#2138)

This commit is contained in:
Konstantin Ivanov 2025-03-10 13:46:53 +01:00 committed by GitHub
parent b566ba30e0
commit 3932c2fe87
Signed by: github
GPG key ID: B5690EEEBB952194
42 changed files with 750 additions and 1142 deletions

View file

@ -28,6 +28,7 @@ dependencies {
implementation project(':localization')
implementation project(':presentation')
implementation project(':library-emojifier')
implementation project(':feature-properties')
compileOnly libs.javaxInject

View file

@ -1,35 +1,13 @@
package com.anytypeio.anytype.feature_object_type.fields
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.RelationFormat
sealed class FieldEvent {
data object OnFieldEditScreenDismiss : FieldEvent()
data object OnAddFieldScreenDismiss : FieldEvent()
data object OnEditPropertyScreenDismiss : FieldEvent()
data class OnFieldItemClick(val item: UiFieldsListItem) : FieldEvent()
data class OnAddToHeaderFieldClick(
val item: UiAddFieldItem
) : FieldEvent()
data class OnAddToSidebarFieldClick(
val item: UiAddFieldItem
) : FieldEvent()
data class OnSaveButtonClicked(
val name: String,
val format: RelationFormat,
val limitObjectTypes: List<Id>
) : FieldEvent()
data object OnChangeTypeClick : FieldEvent()
data object OnLimitTypesClick : FieldEvent()
sealed class FieldItemMenu : FieldEvent() {
data class OnDeleteFromTypeClick(val item: UiFieldsListItem) : FieldItemMenu()
data class OnRemoveLocalClick(val item: UiFieldsListItem) : FieldItemMenu()
data class OnAddLocalToTypeClick(val item: UiFieldsListItem) : FieldItemMenu()
}
@ -38,7 +16,6 @@ sealed class FieldEvent {
}
sealed class Section : FieldEvent() {
data object OnAddToHeaderIconClick : Section()
data object OnAddToSidebarIconClick : Section()
data object OnLocalInfoClick : Section()
}
@ -47,6 +24,4 @@ sealed class FieldEvent {
data class OnMove(val fromKey: String, val toKey: String) : DragEvent()
data object OnDragEnd : DragEvent()
}
data class OnAddFieldSearchQueryChanged(val query: String) : FieldEvent()
}

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.feature_object_type.fields
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
import com.anytypeio.anytype.presentation.objects.ObjectIcon
//region Top bar
@ -34,6 +35,7 @@ data class UiFieldsListState(val items: List<UiFieldsListItem>) {
}
}
//todo rename to UiPropertiesListItem
sealed class UiFieldsListItem {
abstract val id: Id
@ -41,7 +43,7 @@ sealed class UiFieldsListItem {
abstract val fieldKey: Key
abstract val fieldTitle: String
abstract val format: RelationFormat
abstract val limitObjectTypes: List<UiFieldObjectItem>
abstract val limitObjectTypes: List<UiPropertyLimitTypeItem>
abstract val canDelete: Boolean
abstract val isEditableField: Boolean
@ -50,7 +52,7 @@ sealed class UiFieldsListItem {
override val fieldKey: Key,
override val fieldTitle: String,
override val format: RelationFormat,
override val limitObjectTypes: List<UiFieldObjectItem> = emptyList(),
override val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
override val canDelete: Boolean,
override val isEditableField: Boolean
) : Item()
@ -60,7 +62,7 @@ sealed class UiFieldsListItem {
override val fieldKey: Key,
override val fieldTitle: String,
override val format: RelationFormat,
override val limitObjectTypes: List<UiFieldObjectItem> = emptyList(),
override val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
override val canDelete: Boolean = false,
override val isEditableField: Boolean
) : Item()
@ -186,18 +188,4 @@ sealed class UiLocalsFieldsInfoState {
}
//endregion
//region Add Fields screen
sealed class UiAddFieldsScreenState {
data object Hidden : UiAddFieldsScreenState()
data class Visible(val items: List<UiAddFieldItem>, val addToHeader: Boolean) : UiAddFieldsScreenState()
}
data class UiAddFieldItem(
val id: Id,
val fieldKey: Key,
val fieldTitle: String,
val format: RelationFormat
)
//endregion

View file

@ -1,170 +0,0 @@
package com.anytypeio.anytype.feature_object_type.fields.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
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.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.foundation.DefaultSearchBar
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.widgets.dv.DragHandle
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent
import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldItem
import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldsScreenState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddFieldScreen(
state: UiAddFieldsScreenState,
fieldEvent: (FieldEvent) -> Unit
) {
val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
var isSearchEmpty by remember { mutableStateOf(true) }
val lazyListState = rememberLazyListState()
if (state is UiAddFieldsScreenState.Visible) {
ModalBottomSheet(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.safeDrawing)
.nestedScroll(rememberNestedScrollInteropConnection()),
dragHandle = { DragHandle() },
scrimColor = colorResource(id = R.color.modal_screen_outside_background),
containerColor = colorResource(id = R.color.background_primary),
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
sheetState = bottomSheetState,
onDismissRequest = {
fieldEvent(FieldEvent.OnAddFieldScreenDismiss)
},
) {
LazyColumn(
modifier = Modifier
.fillMaxSize(),
state = lazyListState
) {
item {
DefaultSearchBar(
modifier = Modifier.fillMaxWidth()
.padding(horizontal = 20.dp)
) {
isSearchEmpty = it.isEmpty()
fieldEvent(FieldEvent.OnAddFieldSearchQueryChanged(it))
}
}
items(
count = state.items.size,
key = { index -> state.items[index].id },
itemContent = { index ->
val item = state.items[index]
FieldItem(
modifier = commonItemModifier()
.noRippleThrottledClickable {
if (state.addToHeader) {
fieldEvent(
FieldEvent.OnAddToHeaderFieldClick(
item = item
)
)
} else {
fieldEvent(
FieldEvent.OnAddToSidebarFieldClick(
item = item
)
)
}
},
item = item
)
}
)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun FieldItem(
modifier: Modifier,
item: UiAddFieldItem
) {
Row(
modifier = modifier,
verticalAlignment = CenterVertically
) {
val formatIcon = item.format.simpleIcon()
if (formatIcon != null) {
Image(
modifier = Modifier
.padding(end = 10.dp)
.size(24.dp),
painter = painterResource(id = formatIcon),
contentDescription = "Relation format icon",
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.weight(1.0f)
.padding(end = 16.dp),
text = item.fieldTitle,
style = BodyRegular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@DefaultPreviews
@Composable
fun PreviewAddFieldScreen() {
AddFieldScreen(
state = UiAddFieldsScreenState.Visible(
items = listOf(
UiAddFieldItem(
id = "1",
fieldKey = "key",
fieldTitle = "Title",
format = RelationFormat.LONG_TEXT
)
),
addToHeader = true
),
fieldEvent = {}
)
}

View file

@ -1,386 +0,0 @@
package com.anytypeio.anytype.feature_object_type.fields.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.extensions.getPrettyName
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.HeadlineHeading
import com.anytypeio.anytype.core_ui.views.Title2
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.core_ui.widgets.dv.DragHandle
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent
import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState
import com.anytypeio.anytype.feature_object_type.fields.UiFieldObjectItem
import com.anytypeio.anytype.feature_object_type.ui.createDummyFieldDraggableItem
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditFieldScreen(
modifier: Modifier,
uiFieldEditOrNewState: UiFieldEditOrNewState,
fieldEvent: (FieldEvent) -> Unit
) {
if (uiFieldEditOrNewState is UiFieldEditOrNewState.Visible) {
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
modifier = modifier,
dragHandle = { DragHandle() },
scrimColor = colorResource(id = R.color.modal_screen_outside_background),
containerColor = colorResource(id = R.color.background_primary),
shape = RoundedCornerShape(16.dp),
sheetState = bottomSheetState,
onDismissRequest = { fieldEvent(FieldEvent.OnFieldEditScreenDismiss) },
) {
EditFieldContent(
modifier = Modifier.fillMaxWidth(),
uiState = uiFieldEditOrNewState,
fieldEvent = fieldEvent
)
}
}
}
@Composable
private fun EditFieldContent(
modifier: Modifier,
uiState: UiFieldEditOrNewState.Visible,
fieldEvent: (FieldEvent) -> Unit
) {
val field = uiState.item
var innerValue by remember(field.fieldTitle) { mutableStateOf(field.fieldTitle) }
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
val isEditable = field.isEditableField
val title = when (uiState) {
is UiFieldEditOrNewState.Visible.Edit -> stringResource(R.string.object_type_fields_edit_field)
is UiFieldEditOrNewState.Visible.New -> stringResource(R.string.object_type_fields_new_field)
is UiFieldEditOrNewState.Visible.ViewOnly -> stringResource(R.string.object_type_fields_preview_field)
}
Column(modifier = modifier) {
// Header title
Box(
modifier = Modifier
.fillMaxWidth()
.height(44.dp),
contentAlignment = Alignment.Center
) {
Text(
text = title,
style = Title2,
color = colorResource(id = R.color.text_primary)
)
}
// Name text field
NameTextField(
modifier = Modifier.fillMaxWidth(),
value = innerValue,
isEditable = isEditable,
focusRequester = focusRequester,
keyboardController = keyboardController,
onValueChange = { innerValue = it }
)
Spacer(modifier = Modifier.height(10.dp))
Divider()
// Field type section
FieldTypeSection(
format = field.format,
isEditable = isEditable,
onTypeClick = { fieldEvent(FieldEvent.OnChangeTypeClick) }
)
Divider()
// Limit object types (only for OBJECT format)
if (field.format == RelationFormat.OBJECT) {
LimitTypesSection(
objTypes = field.limitObjectTypes,
isEditable = isEditable,
onLimitTypesClick = { fieldEvent(FieldEvent.OnLimitTypesClick) }
)
Divider()
}
Spacer(modifier = Modifier.height(14.dp))
if (isEditable) {
ButtonPrimary(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResource(R.string.object_type_fields_btn_save),
onClick = {
fieldEvent(
FieldEvent.OnSaveButtonClicked(
name = innerValue,
format = field.format,
limitObjectTypes = field.limitObjectTypes.map { it.id }
)
)
},
size = ButtonSize.Large
)
}
}
}
@Composable
fun NameTextField(
modifier: Modifier,
value: String,
isEditable: Boolean,
focusRequester: FocusRequester,
keyboardController: SoftwareKeyboardController?,
onValueChange: (String) -> Unit
) {
val focusManager = LocalFocusManager.current
Column(modifier = modifier) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResource(id = R.string.name),
style = Caption1Regular,
color = colorResource(id = R.color.text_secondary)
)
BasicTextField(
value = value,
onValueChange = onValueChange,
textStyle = HeadlineHeading.copy(color = colorResource(id = R.color.text_primary)),
singleLine = true,
enabled = isEditable,
cursorBrush = SolidColor(colorResource(id = R.color.text_primary)),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 20.dp, top = 6.dp, end = 20.dp)
.focusRequester(focusRequester)
.onFocusChanged { /* You can handle focus changes here if needed */ },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions {
keyboardController?.hide()
focusManager.clearFocus()
onValueChange(value)
},
decorationBox = { innerTextField ->
if (value.isEmpty()) {
Text(
text = stringResource(id = R.string.untitled),
style = HeadlineHeading,
color = colorResource(id = R.color.text_tertiary),
modifier = Modifier.fillMaxWidth()
)
}
innerTextField()
}
)
}
}
@Composable
fun FieldTypeSection(
format: RelationFormat,
isEditable: Boolean,
onTypeClick: () -> Unit
) {
val icon = format.simpleIcon()
SectionItem(
modifier = Modifier
.fillMaxWidth()
.height(52.dp),
text = stringResource(id = R.string.type)
)
Divider()
Box(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { if (isEditable) onTypeClick() }
) {
if (icon != null) {
Image(
modifier = Modifier
.size(24.dp)
.align(Alignment.CenterStart),
painter = painterResource(id = icon),
contentDescription = "Relation format icon"
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.padding(start = 34.dp)
.align(Alignment.CenterStart),
text = stringResource(format.getPrettyName()),
style = BodyRegular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (isEditable) {
Image(
modifier = Modifier.align(Alignment.CenterEnd),
painter = painterResource(id = R.drawable.ic_arrow_forward_24),
contentDescription = "Change field format icon"
)
}
}
}
@Composable
fun LimitTypesSection(
objTypes: List<UiFieldObjectItem>,
isEditable: Boolean,
onLimitTypesClick: () -> Unit
) {
val size = objTypes.size
SectionItem(
modifier = Modifier
.fillMaxWidth()
.height(52.dp),
text = stringResource(id = R.string.limit_object_types)
)
Divider()
Box(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { if (isEditable) onLimitTypesClick() }
) {
if (objTypes.isNotEmpty()) {
Row(modifier = Modifier.align(Alignment.CenterStart)) {
ListWidgetObjectIcon(
modifier = Modifier.size(20.dp),
icon = objTypes.first().icon,
backgroundColor = R.color.transparent_black
)
Text(
modifier = Modifier.padding(start = 6.dp),
text = objTypes.first().title.take(20),
style = BodyRegular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (size > 1) {
Text(
text = " +${size - 1}",
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
}
}
} else {
Text(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterStart),
text = stringResource(R.string.none),
style = BodyRegular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (isEditable) {
Image(
modifier = Modifier.align(Alignment.CenterEnd),
painter = painterResource(id = R.drawable.ic_arrow_forward_24),
contentDescription = "Change field object types icon"
)
}
}
}
@Composable
fun SectionItem(modifier: Modifier, text: String) {
Box(modifier = modifier) {
Text(
modifier = Modifier
.padding(start = 20.dp, bottom = 8.dp)
.align(Alignment.BottomStart),
text = text,
style = Caption1Regular,
color = colorResource(id = R.color.text_secondary)
)
}
}
@DefaultPreviews
@Composable
private fun MyPreview() {
EditFieldScreen(
modifier = Modifier.fillMaxWidth(),
uiFieldEditOrNewState = UiFieldEditOrNewState.Visible.Edit(
item = createDummyFieldDraggableItem()
),
fieldEvent = {}
)
}
@DefaultPreviews
@Composable
private fun MyPreviewOnlyPreview() {
EditFieldScreen(
modifier = Modifier.fillMaxWidth(),
uiFieldEditOrNewState = UiFieldEditOrNewState.Visible.ViewOnly(
item = createDummyFieldDraggableItem(
isEditableField = false
)
),
fieldEvent = {}
)
}

View file

@ -35,24 +35,19 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.common.ReorderHapticFeedback
import com.anytypeio.anytype.core_ui.common.ReorderHapticFeedbackType
import com.anytypeio.anytype.core_ui.common.bottomBorder
import com.anytypeio.anytype.core_ui.common.rememberReorderHapticFeedback
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.foundation.Dragger
@ -68,14 +63,14 @@ import com.anytypeio.anytype.feature_object_type.R
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.*
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.FieldItemMenu.*
import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldsScreenState
import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Section
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.UiIconState
import com.anytypeio.anytype.feature_object_type.ui.UiTitleState
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.ui.PropertyScreen
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import kotlinx.coroutines.delay
import sh.calvin.reorderable.ReorderableItem
@ -88,9 +83,8 @@ fun FieldsMainScreen(
uiFieldsListState: UiFieldsListState,
uiTitleState: UiTitleState,
uiIconState: UiIconState,
uiFieldEditOrNewState: UiFieldEditOrNewState,
uiFieldLocalInfoState: UiLocalsFieldsInfoState,
uiAddFieldsScreenState: UiAddFieldsScreenState,
uiEditPropertyState: UiEditPropertyState,
fieldEvent: (FieldEvent) -> Unit
) {
@ -198,10 +192,7 @@ fun FieldsMainScreen(
item = item,
reorderingState = reorderableLazyColumnState,
fieldEvent = fieldEvent,
isReorderable = false,
onAddIconClick = {
fieldEvent(FieldEvent.Section.OnAddToHeaderIconClick)
}
isReorderable = false
)
}
is Section.Local,
@ -226,11 +217,16 @@ fun FieldsMainScreen(
}
)
if (uiFieldEditOrNewState is UiFieldEditOrNewState.Visible) {
EditFieldScreen(
if (uiEditPropertyState is UiEditPropertyState.Visible) {
PropertyScreen(
modifier = Modifier.fillMaxWidth(),
uiFieldEditOrNewState = uiFieldEditOrNewState,
fieldEvent = fieldEvent
uiState = uiEditPropertyState,
onDismissRequest = { fieldEvent(OnEditPropertyScreenDismiss) },
onFormatClick = {},
onLimitTypesClick = {},
onSaveButtonClicked = {},
onCreateNewButtonClicked = {},
onPropertyNameUpdate = { }
)
}
@ -241,13 +237,6 @@ fun FieldsMainScreen(
fieldEvent = fieldEvent
)
}
if (uiAddFieldsScreenState is UiAddFieldsScreenState.Visible) {
AddFieldScreen(
state = uiAddFieldsScreenState,
fieldEvent = fieldEvent
)
}
}
/** Returns a content type string based on the item type. **/
@ -599,29 +588,6 @@ private fun LazyItemScope.FieldItemDraggable(
}
}
@Composable
fun Modifier.bottomBorder(
strokeWidth: Dp = 0.5.dp,
color: Color = colorResource(R.color.shape_primary)
) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
Modifier.drawBehind {
val width = size.width
val height = size.height - strokeWidthPx / 2
drawLine(
color = color,
start = Offset(x = 0f, y = height),
end = Offset(x = width, y = height),
strokeWidth = strokeWidthPx
)
}
}
)
@Composable
fun ItemDropDownMenu(
item: UiFieldsListItem.Item,
@ -757,9 +723,8 @@ fun PreviewTypeFieldsMainScreen() {
)
),
fieldEvent = {},
uiFieldEditOrNewState = UiFieldEditOrNewState.Hidden,
uiFieldLocalInfoState = UiLocalsFieldsInfoState.Hidden,
uiAddFieldsScreenState = UiAddFieldsScreenState.Hidden
uiEditPropertyState = UiEditPropertyState.Hidden,
uiFieldLocalInfoState = UiLocalsFieldsInfoState.Hidden
)
}

View file

@ -1,39 +0,0 @@
package com.anytypeio.anytype.feature_object_type.ui
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.feature_object_type.fields.UiFieldObjectItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem
import com.anytypeio.anytype.presentation.objects.ObjectIcon
fun createDummyFieldDraggableItem(isEditableField: Boolean = true): UiFieldsListItem.Item.Draggable {
return UiFieldsListItem.Item.Draggable(
id = "dummyId",
fieldKey = "dummyKey",
fieldTitle = "Field Title",
format = RelationFormat.OBJECT,
limitObjectTypes = listOf(
UiFieldObjectItem(
id = "dummyObjectId1",
key = "dummyKey1",
title = "Dummy Object Type 1",
icon = ObjectIcon.Empty.ObjectType,
),
UiFieldObjectItem(
id = "dummyObjectId1",
key = "dummyKey1",
title = "Dummy Object Type 1",
icon = ObjectIcon.Empty.ObjectType,
),
UiFieldObjectItem(
id = "dummyObjectId1",
key = "dummyKey1",
title = "Dummy Object Type 1",
icon = ObjectIcon.Empty.ObjectType,
),
),
isEditableField = isEditableField,
canDelete = true
)
}

View file

@ -33,8 +33,7 @@ sealed class ObjectTypeCommand {
data object OpenFieldsScreen : ObjectTypeCommand()
data class OpenAddFieldScreen(val typeId: Id, val space: Id, val isSet: Boolean = false) :
ObjectTypeCommand()
data class OpenEditTypePropertiesScreen(val typeId: Id, val space: Id) : ObjectTypeCommand()
}
//region OBJECT TYPE HEADER (title + icon)

View file

@ -13,11 +13,10 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.primitives.FieldParser
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldObjectItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Item
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Section
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
import com.anytypeio.anytype.presentation.mapper.objectIcon
import com.anytypeio.anytype.presentation.relations.BasicObjectCoverWrapper
@ -56,7 +55,7 @@ fun ObjectWrapper.Basic.toTemplateView(
* Extension function to safely get a name for the relation.
* If the name is blank, returns a default untitled title.
*/
private fun ObjectWrapper.Relation.getName(stringResourceProvider: StringResourceProvider): String =
fun ObjectWrapper.Relation.getName(stringResourceProvider: StringResourceProvider): String =
if (name.isNullOrBlank()) {
stringResourceProvider.getUntitledObjectTitle()
} else {
@ -140,7 +139,7 @@ suspend fun buildUiFieldsList(
}
return buildList {
add(Section.Header(canAdd = true))
add(Section.Header(canAdd = false))
addAll(headerItems)
add(Section.SideBar(canAdd = true))
@ -173,11 +172,11 @@ private suspend fun mapLimitObjectTypes(
storeOfObjectTypes: StoreOfObjectTypes,
fieldParser: FieldParser,
urlBuilder: UrlBuilder
): List<UiFieldObjectItem> {
): List<UiPropertyLimitTypeItem> {
return if (relation.format == RelationFormat.OBJECT && relation.relationFormatObjectTypes.isNotEmpty()) {
relation.relationFormatObjectTypes.mapNotNull { key ->
storeOfObjectTypes.getByKey(key)?.let { objType ->
UiFieldObjectItem(
UiPropertyLimitTypeItem(
id = objType.id,
key = objType.uniqueKey,
title = fieldParser.getObjectName(objType),
@ -239,16 +238,3 @@ private suspend fun mapToUiFieldsLocalListItem(
)
}
fun ObjectWrapper.Relation.mapToUiAddFieldListItem(
stringResourceProvider: StringResourceProvider
): UiAddFieldItem? {
val field = this
if (field.key == Relations.DESCRIPTION) return null
return UiAddFieldItem(
id = field.id,
fieldKey = field.key,
fieldTitle = field.getName(stringResourceProvider),
format = field.format
)
}

View file

@ -4,14 +4,13 @@ import androidx.lifecycle.ViewModel
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.ObjectType
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_ui.extensions.simpleIcon
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
@ -22,17 +21,12 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.page.CreateObject
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.resources.StringResourceProvider
import com.anytypeio.anytype.domain.templates.CreateTemplate
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent
import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldItem
import com.anytypeio.anytype.feature_object_type.fields.UiAddFieldsScreenState
import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState
import com.anytypeio.anytype.feature_object_type.fields.UiFieldEditOrNewState.Visible.*
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListState
@ -50,19 +44,15 @@ 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.UiSyncStatusBadgeState
import com.anytypeio.anytype.feature_object_type.ui.UiTemplatesAddIconState
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
import com.anytypeio.anytype.feature_object_type.ui.toTemplateView
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
import com.anytypeio.anytype.presentation.editor.cover.UnsplashViewModel.Companion.DEBOUNCE_DURATION
import com.anytypeio.anytype.presentation.extension.sendAnalyticsScreenObjectType
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.search.ObjectSearchConstants.defaultKeys
import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState
@ -71,22 +61,14 @@ import com.anytypeio.anytype.presentation.sync.updateStatus
import com.anytypeio.anytype.presentation.templates.TemplateView
import kotlin.collections.map
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import timber.log.Timber
@ -105,18 +87,15 @@ class ObjectTypeViewModel(
private val storeOfObjectTypes: StoreOfObjectTypes,
private val storelessSubscriptionContainer: StorelessSubscriptionContainer,
private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider,
private val createObject: CreateObject,
private val fieldParser: FieldParser,
private val coverImageHashProvider: CoverImageHashProvider,
private val deleteObjects: DeleteObjects,
private val setObjectDetails: SetObjectDetails,
private val createObjectSet: CreateObjectSet,
private val stringResourceProvider: StringResourceProvider,
private val createTemplate: CreateTemplate,
private val duplicateObjects: DuplicateObjects,
private val getObjectTypeConflictingFields: GetObjectTypeConflictingFields,
private val objectTypeSetRecommendedFields: SetObjectTypeRecommendedFields,
private val objectTypeSetHeaderRecommendedFields: SetObjectTypeHeaderRecommendedFields
private val objectTypeSetRecommendedFields: SetObjectTypeRecommendedFields
) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
//region UI STATE
@ -148,13 +127,11 @@ class ObjectTypeViewModel(
val uiFieldLocalInfoState =
MutableStateFlow<UiLocalsFieldsInfoState>(UiLocalsFieldsInfoState.Hidden)
//fields
//properties list
val uiFieldsListState = MutableStateFlow<UiFieldsListState>(UiFieldsListState.EMPTY)
val uiFieldEditOrNewState =
MutableStateFlow<UiFieldEditOrNewState>(UiFieldEditOrNewState.Hidden)
//add new field
val uiAddFieldsState = MutableStateFlow<UiAddFieldsScreenState>(UiAddFieldsScreenState.Hidden)
//edit property
val uiEditPropertyScreen = MutableStateFlow<UiEditPropertyState>(UiEditPropertyState.Hidden)
//error
val errorState = MutableStateFlow<UiErrorState>(UiErrorState.Hidden)
@ -166,6 +143,8 @@ class ObjectTypeViewModel(
private val _objectTypeConflictingFieldIds = MutableStateFlow<List<Id>>(emptyList())
//endregion
val commands = MutableSharedFlow<ObjectTypeCommand>()
//region INIT AND LIFE CYCLE
init {
Timber.d("init, vmParams: $vmParams")
@ -191,7 +170,6 @@ class ObjectTypeViewModel(
//endregion
//region DATA
private fun proceedWithObservingObjectType() {
viewModelScope.launch {
combine(
@ -387,6 +365,31 @@ class ObjectTypeViewModel(
fun hideError() {
errorState.value = UiErrorState.Hidden
}
fun setupUiEditPropertyScreen(item: UiFieldsListItem.Item) {
val permissions = _objectTypePermissionsState.value
if (permissions?.participantCanEdit == true && item.isEditableField) {
uiEditPropertyScreen.value = UiEditPropertyState.Visible.Edit(
id = item.id,
key = item.fieldKey,
name = item.fieldTitle,
formatName = stringResourceProvider.getPropertiesFormatPrettyString(item.format),
formatIcon = item.format.simpleIcon(),
format = item.format,
limitObjectTypes = item.limitObjectTypes
)
} else {
uiEditPropertyScreen.value = UiEditPropertyState.Visible.View(
id = item.id,
key = item.fieldKey,
name = item.fieldTitle,
formatName = stringResourceProvider.getPropertiesFormatPrettyString(item.format),
formatIcon = item.format.simpleIcon(),
format = item.format,
limitObjectTypes = item.limitObjectTypes
)
}
}
//endregion
//region Ui EVENTS - TYPES
@ -623,33 +626,17 @@ class ObjectTypeViewModel(
fun onFieldEvent(event: FieldEvent) {
Timber.d("onFieldEvent: $event")
when (event) {
FieldEvent.OnChangeTypeClick -> TODO()
FieldEvent.OnFieldEditScreenDismiss -> {
uiFieldEditOrNewState.value = UiFieldEditOrNewState.Hidden
FieldEvent.OnEditPropertyScreenDismiss -> {
uiEditPropertyScreen.value = UiEditPropertyState.Hidden
}
is FieldEvent.OnFieldItemClick -> {
when (event.item) {
is UiFieldsListItem.Item -> {
val permissions = _objectTypePermissionsState.value
if (permissions?.participantCanEdit == true && event.item.isEditableField) {
uiFieldEditOrNewState.value = Edit(
event.item
)
} else {
uiFieldEditOrNewState.value = ViewOnly(
event.item
)
}
}
is UiFieldsListItem.Item -> setupUiEditPropertyScreen(item = event.item)
else -> {}
}
}
FieldEvent.OnLimitTypesClick -> TODO()
is FieldEvent.OnSaveButtonClicked -> TODO()
is FieldEvent.FieldItemMenu -> proceedWithFieldItemMenuClick(event)
FieldEvent.FieldLocalInfo.OnDismiss -> {
uiFieldLocalInfoState.value = UiLocalsFieldsInfoState.Hidden
@ -659,12 +646,15 @@ class ObjectTypeViewModel(
uiFieldLocalInfoState.value = UiLocalsFieldsInfoState.Visible
}
FieldEvent.Section.OnAddToHeaderIconClick -> {
proceedWithAddFieldToHeaderScreen()
}
FieldEvent.Section.OnAddToSidebarIconClick -> {
proceedWithAddFieldToSidebarScreen()
viewModelScope.launch {
commands.emit(
ObjectTypeCommand.OpenEditTypePropertiesScreen(
typeId = vmParams.objectId,
space = vmParams.spaceId.id,
)
)
}
}
FieldEvent.DragEvent.OnDragEnd -> {
@ -706,24 +696,6 @@ class ObjectTypeViewModel(
currentList.add(toIndex, item)
uiFieldsListState.value = UiFieldsListState(items = currentList)
}
FieldEvent.OnAddFieldScreenDismiss -> {
hideAddNewFieldScreen()
}
is FieldEvent.OnAddToHeaderFieldClick -> {
onAddToHeaderFieldClicked(item = event.item)
hideAddNewFieldScreen()
}
is FieldEvent.OnAddToSidebarFieldClick -> {
onAddToSidebarFieldClicked(item = event.item)
hideAddNewFieldScreen()
}
is FieldEvent.OnAddFieldSearchQueryChanged -> {
onQueryChanged(query = event.query)
}
}
}
@ -777,30 +749,6 @@ class ObjectTypeViewModel(
proceedWithSetRecommendedFields(newRecommendedFields)
}
is FieldEvent.FieldItemMenu.OnRemoveLocalClick -> TODO()
}
}
//endregion
//region NAVIGATION
val commands = MutableSharedFlow<ObjectTypeCommand>()
val navigation = MutableSharedFlow<OpenObjectNavigation>()
private fun proceedWithNavigation(
objectId: Id,
objectLayout: ObjectType.Layout?
) {
Timber.d("proceedWithNavigation, objectId: $objectId, objectLayout: $objectLayout")
val destination = objectLayout?.navigation(
target = objectId,
space = vmParams.spaceId.id
)
if (destination != null) {
viewModelScope.launch {
navigation.emit(destination)
}
} else {
Timber.w("No navigation destination found for object $objectId with layout $objectLayout")
}
}
//endregion
@ -960,124 +908,6 @@ class ObjectTypeViewModel(
)
}
}
private fun proceedWithSetHeaderRecommendedFields(fields: List<Id>) {
val params = SetObjectTypeHeaderRecommendedFields.Params(
objectTypeId = vmParams.objectId,
fields = fields
)
viewModelScope.launch {
objectTypeSetHeaderRecommendedFields.async(params).fold(
onSuccess = {
Timber.d("Header recommended fields set")
},
onFailure = {
Timber.e(it, "Error while setting header recommended fields")
}
)
}
}
//endregion
//region ADD NEW FIELD
private val input = MutableStateFlow("")
@OptIn(FlowPreview::class)
private val query = input.take(1).onCompletion {
emitAll(
input.drop(1).debounce(DEBOUNCE_DURATION).distinctUntilChanged()
)
}
private var addFieldSearchJob: Job? = null
/**
* Loads the available fields from type, applies filtering based on a search query,
* and then updates the UI state.
*/
private fun showAddFieldScreen(addToHeader: Boolean) {
// Collect field keys that are already present in the type fields list.
val typeFieldsKeys =
uiFieldsListState.value.items.mapNotNull { (it as? UiFieldsListItem.Item)?.fieldKey }
addFieldSearchJob = viewModelScope.launch {
// Combine the search query flow with the list of all fields.
combine(
query,
storeOfRelations.trackChanges()
) { queryText, _ ->
// Filter out fields by query and that already exist and are not valid.
filterFields(
allFields = storeOfRelations.getAll(),
typeKeys = typeFieldsKeys,
queryText = queryText
)
}.collect { filteredFields ->
val items = filteredFields.mapNotNull { field ->
field.mapToUiAddFieldListItem(stringResourceProvider)
}.sortedBy { it.fieldTitle }
uiAddFieldsState.value = UiAddFieldsScreenState.Visible(
items = items,
addToHeader = addToHeader
)
}
}
}
private fun filterFields(
allFields: List<ObjectWrapper.Relation>,
typeKeys: List<Key>,
queryText: String
): List<ObjectWrapper.Relation> = allFields.filter { field ->
field.key !in typeKeys &&
field.isValidToUse &&
(queryText.isBlank() || field.name?.contains(queryText, ignoreCase = true) == true)
}
private fun onQueryChanged(query: String) {
input.value = query
}
fun hideAddNewFieldScreen() {
input.value = ""
addFieldSearchJob?.cancel()
addFieldSearchJob = null
uiAddFieldsState.value = UiAddFieldsScreenState.Hidden
}
fun proceedWithAddFieldToHeaderScreen() {
showAddFieldScreen(addToHeader = true)
}
fun proceedWithAddFieldToSidebarScreen() {
showAddFieldScreen(addToHeader = false)
}
private fun updateFieldRecommendations(
currentFields: List<String>?,
item: UiAddFieldItem,
updateAction: (List<String>) -> Unit
) {
val newFields = currentFields.orEmpty() + item.id
updateAction(newFields)
}
private fun onAddToSidebarFieldClicked(item: UiAddFieldItem) {
updateFieldRecommendations(
currentFields = _objTypeState.value?.recommendedRelations,
item = item,
updateAction = ::proceedWithSetRecommendedFields
)
}
private fun onAddToHeaderFieldClicked(item: UiAddFieldItem) {
updateFieldRecommendations(
currentFields = _objTypeState.value?.recommendedFeaturedRelations,
item = item,
updateAction = ::proceedWithSetHeaderRecommendedFields
)
}
//endregion
companion object {

View file

@ -3,7 +3,6 @@ package com.anytypeio.anytype.feature_object_type.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
@ -13,10 +12,8 @@ import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.DeleteObjects
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.page.CreateObject
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.resources.StringResourceProvider
import com.anytypeio.anytype.domain.templates.CreateTemplate
@ -35,18 +32,15 @@ class ObjectTypeVMFactory @Inject constructor(
private val storeOfObjectTypes: StoreOfObjectTypes,
private val storelessSubscriptionContainer: StorelessSubscriptionContainer,
private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider,
private val createObject: CreateObject,
private val fieldParser: FieldParser,
private val coverImageHashProvider: CoverImageHashProvider,
private val deleteObjects: DeleteObjects,
private val setObjectDetails: SetObjectDetails,
private val createObjectSet: CreateObjectSet,
private val stringResourceProvider: StringResourceProvider,
private val createTemplate: CreateTemplate,
private val duplicateObjects: DuplicateObjects,
private val getObjectTypeConflictingFields: GetObjectTypeConflictingFields,
private val objectTypeSetRecommendedFields: SetObjectTypeRecommendedFields,
private val objectTypeSetHeaderRecommendedFields: SetObjectTypeHeaderRecommendedFields
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -61,17 +55,14 @@ class ObjectTypeVMFactory @Inject constructor(
storeOfObjectTypes = storeOfObjectTypes,
storelessSubscriptionContainer = storelessSubscriptionContainer,
spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider,
createObject = createObject,
fieldParser = fieldParser,
coverImageHashProvider = coverImageHashProvider,
deleteObjects = deleteObjects,
setObjectDetails = setObjectDetails,
createObjectSet = createObjectSet,
stringResourceProvider = stringResourceProvider,
createTemplate = createTemplate,
duplicateObjects = duplicateObjects,
getObjectTypeConflictingFields = getObjectTypeConflictingFields,
objectTypeSetRecommendedFields = objectTypeSetRecommendedFields,
objectTypeSetHeaderRecommendedFields = objectTypeSetHeaderRecommendedFields
) as T
}

View file

@ -193,29 +193,6 @@ class TestFieldsMappping {
)
}
@Test
fun `should not filter sidebar fields by hidden`() = runTest {
storeOfRelations.apply {
merge(allSpaceRelations)
}
storeOfObjectTypes.apply {
merge(listOf(testObjectType, fieldAssigneeObjType2, fieldAssigneeObjType1))
}
val parsedFields = fieldParser.getObjectTypeParsedFields(
objectType = testObjectType,
storeOfRelations = storeOfRelations,
objectTypeConflictingFieldsIds = listOf()
)
assertEquals(
expected = listOf(field3, field4, field5),
actual = parsedFields.sidebar
)
}
@Test
fun `should map hidden fields`() = runTest {
@ -233,8 +210,54 @@ class TestFieldsMappping {
objectTypeConflictingFieldsIds = listOf()
)
assertEquals(
expected = listOf(),
actual = parsedFields.hidden
)
}
@Test
fun `test filter duplicates`() = runTest {
storeOfRelations.apply {
merge(allSpaceRelations)
}
val testObjectType = StubObjectType(
recommendedFeaturedRelations = listOf(field1, field1, field2, field3).map { it.id },
recommendedRelations = listOf(field1, field4, field4).map { it.id },
recommendedFileRelations = listOf(field1, field2, field3, field4, field5, field5).map { it.id },
recommendedHiddenRelations = listOf(field1, field2, field3, field4, field5, fieldCreatedDate, fieldCreatedDate).map { it.id },
space = space
)
storeOfObjectTypes.apply {
merge(listOf(testObjectType, fieldAssigneeObjType2, fieldAssigneeObjType1))
}
val parsedFields = fieldParser.getObjectTypeParsedFields(
objectType = testObjectType,
storeOfRelations = storeOfRelations,
objectTypeConflictingFieldsIds = listOf()
)
assertEquals(
expected = listOf(field1, field2, field3),
actual = parsedFields.header
)
assertEquals(
expected = listOf(field4),
actual = parsedFields.sidebar
)
assertEquals(
expected = listOf(field5),
actual = parsedFields.file
)
assertEquals(
expected = listOf(fieldCreatedDate),
actual = parsedFields.hidden
)
}