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

DROID-3466 Primitives | Property, format object relation (#2183)

This commit is contained in:
Konstantin Ivanov 2025-03-25 18:02:06 +01:00 committed by GitHub
parent 6ad8e54721
commit 546ab62b6c
Signed by: github
GPG key ID: B5690EEEBB952194
23 changed files with 821 additions and 154 deletions

View file

@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.di.common.ComponentDependencies
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
@ -84,5 +85,6 @@ interface EditTypePropertiesDependencies : ComponentDependencies {
fun provideStoreOfObjectTypes(): StoreOfObjectTypes
fun provideBlockRepository(): BlockRepository
fun provideAppCoroutineDispatchers(): AppCoroutineDispatchers
fun urlBuilder(): UrlBuilder
}
//endregion

View file

@ -206,6 +206,9 @@ sealed class ObjectWrapper {
val allRecommendedRelations: List<Id>
get() = recommendedRelations + recommendedFeaturedRelations + recommendedHiddenRelations + recommendedFileRelations
val isValid get() =
map.containsKey(Relations.UNIQUE_KEY) && map.containsKey(Relations.ID)
}
data class Relation(override val map: Struct) : ObjectWrapper() {
@ -252,7 +255,7 @@ sealed class ObjectWrapper {
else -> emptyList()
}
val relationFormatObjectTypes get() = getValues<Key>(RELATION_FORMAT_OBJECT_TYPES)
val relationFormatObjectTypes get() = getValues<Id>(RELATION_FORMAT_OBJECT_TYPES)
val type: List<Id> get() = getValues(Relations.TYPE)

View file

@ -8,7 +8,7 @@
android:pathData="M0,24l0,-24l8,-0l0,24z"/>
<path
android:pathData="M0.9597,18.0303C0.6668,17.7374 0.6668,17.2626 0.9597,16.9697L5.9293,12L0.9597,7.0303C0.6668,6.7374 0.6668,6.2626 0.9597,5.9697C1.2526,5.6768 1.7274,5.6768 2.0203,5.9697L8.0506,12L2.0203,18.0303C1.7274,18.3232 1.2526,18.3232 0.9597,18.0303Z"
android:fillColor="@color/text_tertiary"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</group>
</vector>

View file

@ -85,6 +85,7 @@
<color name="palette_system_amber_125">#FFC83C</color>
<color name="palette_system_amber_100">#FFB522</color>
<color name="palette_system_amber_50">#9F6B00</color>
<color name="palette_system_amber_50_opacity_12">#1F9F6B00</color>
<color name="palette_system_red">#F55522</color>
<color name="palette_system_pink">#E51CA0</color>
<color name="palette_system_purple">#AB50CC</color>

View file

@ -124,6 +124,7 @@
<color name="palette_system_amber_125">#F09C0E</color>
<color name="palette_system_amber_100">#FFB522</color>
<color name="palette_system_amber_50">#FFD15B</color>
<color name="palette_system_amber_50_opacity_12">#1FFFD15B</color>
<color name="palette_system_red">#F55522</color>
<color name="palette_system_pink">#E51CA0</color>
<color name="palette_system_purple">#AB50CC</color>

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.feature_object_type.fields
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesEvent
sealed class FieldEvent {
@ -30,5 +31,7 @@ sealed class FieldEvent {
sealed class EditProperty : FieldEvent() {
data class OnPropertyNameUpdate(val name: String) : EditProperty()
data object OnSaveButtonClicked : EditProperty()
data object OnLimitTypesClick : EditProperty()
data object OnLimitTypesDismiss : EditProperty()
}
}

View file

@ -43,7 +43,7 @@ sealed class UiFieldsListItem {
abstract val fieldKey: Key
abstract val fieldTitle: String
abstract val format: RelationFormat
abstract val limitObjectTypes: List<UiPropertyLimitTypeItem>
abstract val limitObjectTypes: List<Id>
abstract val isPossibleToUnlinkFromType: Boolean
abstract val isEditableField: Boolean
@ -52,7 +52,7 @@ sealed class UiFieldsListItem {
override val fieldKey: Key,
override val fieldTitle: String,
override val format: RelationFormat,
override val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
override val limitObjectTypes: List<Id>,
override val isPossibleToUnlinkFromType: Boolean,
override val isEditableField: Boolean
) : Item()
@ -62,7 +62,7 @@ sealed class UiFieldsListItem {
override val fieldKey: Key,
override val fieldTitle: String,
override val format: RelationFormat,
override val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
override val limitObjectTypes: List<Id>,
override val isPossibleToUnlinkFromType: Boolean = false,
override val isEditableField: Boolean
) : Item()

View file

@ -62,6 +62,8 @@ 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.fields.FieldEvent
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.*
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.EditProperty.OnLimitTypesClick
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.EditProperty.OnLimitTypesDismiss
import com.anytypeio.anytype.feature_object_type.fields.FieldEvent.FieldItemMenu.*
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem
import com.anytypeio.anytype.feature_object_type.fields.UiFieldsListItem.Section
@ -69,6 +71,7 @@ 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.add.UiEditTypePropertiesEvent
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.ui.PropertyScreen
import com.anytypeio.anytype.presentation.objects.ObjectIcon
@ -226,12 +229,12 @@ fun FieldsMainScreen(
uiState = uiEditPropertyState,
onDismissRequest = { fieldEvent(OnEditPropertyScreenDismiss) },
onFormatClick = {},
onLimitTypesClick = {},
onSaveButtonClicked = { fieldEvent(EditProperty.OnSaveButtonClicked) },
onCreateNewButtonClicked = {},
onPropertyNameUpdate = { fieldEvent(EditProperty.OnPropertyNameUpdate(it)) },
onMenuUnlinkClick = { fieldEvent(OnDeleteFromTypeClick(it)) }
onMenuUnlinkClick = { fieldEvent(OnDeleteFromTypeClick(it)) },
onLimitTypesClick = { fieldEvent(OnLimitTypesClick) },
onDismissLimitTypes = { fieldEvent(OnLimitTypesDismiss) },
)
}
@ -681,7 +684,8 @@ fun PreviewTypeFieldsMainScreen() {
fieldTitle = "Status",
format = RelationFormat.STATUS,
isPossibleToUnlinkFromType = true,
isEditableField = true
isEditableField = true,
limitObjectTypes = listOf()
),
UiFieldsListItem.Item.Draggable(
id = "id2",
@ -689,7 +693,8 @@ fun PreviewTypeFieldsMainScreen() {
fieldTitle = "Very long field title, just to test how it looks",
format = RelationFormat.LONG_TEXT,
isPossibleToUnlinkFromType = true,
isEditableField = true
isEditableField = true,
limitObjectTypes = listOf()
),
UiFieldsListItem.Section.SideBar(
canAdd = true
@ -700,7 +705,8 @@ fun PreviewTypeFieldsMainScreen() {
fieldTitle = "Links",
format = RelationFormat.URL,
isEditableField = true,
isPossibleToUnlinkFromType = true
isPossibleToUnlinkFromType = true,
limitObjectTypes = listOf()
),
UiFieldsListItem.Item.Draggable(
id = "id4",
@ -708,7 +714,8 @@ fun PreviewTypeFieldsMainScreen() {
fieldTitle = "Very long field title, just to test how it looks",
format = RelationFormat.DATE,
isEditableField = true,
isPossibleToUnlinkFromType = true
isPossibleToUnlinkFromType = true,
limitObjectTypes = listOf()
),
UiFieldsListItem.Section.Hidden(),
UiFieldsListItem.Item.Draggable(
@ -717,7 +724,8 @@ fun PreviewTypeFieldsMainScreen() {
fieldTitle = "Hidden field",
format = RelationFormat.LONG_TEXT,
isEditableField = true,
isPossibleToUnlinkFromType = true
isPossibleToUnlinkFromType = true,
limitObjectTypes = listOf()
),
UiFieldsListItem.Section.Local(),
UiFieldsListItem.Item.Local(
@ -725,14 +733,16 @@ fun PreviewTypeFieldsMainScreen() {
fieldKey = "key5",
fieldTitle = "Local field",
format = RelationFormat.LONG_TEXT,
isEditableField = true
isEditableField = true,
limitObjectTypes = listOf()
),
UiFieldsListItem.Item.Local(
id = "id6",
fieldKey = "key6",
fieldTitle = "Local Very long field title, just to test how it looks",
format = RelationFormat.LONG_TEXT,
isEditableField = true
isEditableField = true,
limitObjectTypes = listOf()
)
)
),

View file

@ -66,7 +66,6 @@ suspend fun buildUiFieldsList(
objType: ObjectWrapper.Type,
stringResourceProvider: StringResourceProvider,
fieldParser: FieldParser,
urlBuilder: UrlBuilder,
storeOfObjectTypes: StoreOfObjectTypes,
storeOfRelations: StoreOfRelations,
objTypeConflictingFields: List<Id>,
@ -85,7 +84,6 @@ suspend fun buildUiFieldsList(
field = it,
stringResourceProvider = stringResourceProvider,
fieldParser = fieldParser,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes
)
}
@ -94,7 +92,6 @@ suspend fun buildUiFieldsList(
field = it,
stringResourceProvider = stringResourceProvider,
fieldParser = fieldParser,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes
)
}
@ -103,7 +100,6 @@ suspend fun buildUiFieldsList(
field = it,
stringResourceProvider = stringResourceProvider,
fieldParser = fieldParser,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes
)
}
@ -112,7 +108,6 @@ suspend fun buildUiFieldsList(
field = it,
stringResourceProvider = stringResourceProvider,
fieldParser = fieldParser,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes
)
}
@ -123,7 +118,6 @@ suspend fun buildUiFieldsList(
field = it,
stringResourceProvider = stringResourceProvider,
fieldParser = fieldParser,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes
)
}
@ -133,7 +127,6 @@ suspend fun buildUiFieldsList(
field = it,
stringResourceProvider = stringResourceProvider,
fieldParser = fieldParser,
urlBuilder = urlBuilder,
storeOfObjectTypes = storeOfObjectTypes
)
}
@ -169,19 +162,16 @@ suspend fun buildUiFieldsList(
*/
private suspend fun mapLimitObjectTypes(
relation: ObjectWrapper.Relation,
storeOfObjectTypes: StoreOfObjectTypes,
fieldParser: FieldParser,
urlBuilder: UrlBuilder
): List<UiPropertyLimitTypeItem> {
storeOfObjectTypes: StoreOfObjectTypes
): List<Id> {
return if (relation.format == RelationFormat.OBJECT && relation.relationFormatObjectTypes.isNotEmpty()) {
relation.relationFormatObjectTypes.mapNotNull { key ->
storeOfObjectTypes.getByKey(key)?.let { objType ->
UiPropertyLimitTypeItem(
id = objType.id,
key = objType.uniqueKey,
title = fieldParser.getObjectName(objType),
icon = objType.objectIcon()
)
relation.relationFormatObjectTypes.mapNotNull { id ->
storeOfObjectTypes.get(id)?.let { objType ->
if (objType.isValid) {
objType.id
} else {
null
}
}
}
} else {
@ -197,18 +187,16 @@ private suspend fun mapToUiFieldsDraggableListItem(
field: ObjectWrapper.Relation,
stringResourceProvider: StringResourceProvider,
storeOfObjectTypes: StoreOfObjectTypes,
fieldParser: FieldParser,
urlBuilder: UrlBuilder
fieldParser: FieldParser
): UiFieldsListItem? {
if (field.key == Relations.DESCRIPTION) return null
val limitObjectTypes = mapLimitObjectTypes(field, storeOfObjectTypes, fieldParser, urlBuilder)
return Item.Draggable(
id = field.id,
fieldKey = field.key,
fieldTitle = field.getName(stringResourceProvider),
format = field.format,
limitObjectTypes = limitObjectTypes,
limitObjectTypes = mapLimitObjectTypes(field, storeOfObjectTypes),
isEditableField = fieldParser.isFieldEditable(field),
isPossibleToUnlinkFromType = fieldParser.isFieldCanBeDeletedFromType(field)
)
@ -223,17 +211,15 @@ private suspend fun mapToUiFieldsLocalListItem(
stringResourceProvider: StringResourceProvider,
storeOfObjectTypes: StoreOfObjectTypes,
fieldParser: FieldParser,
urlBuilder: UrlBuilder
): UiFieldsListItem? {
if (field.key == Relations.DESCRIPTION) return null
val limitObjectTypes = mapLimitObjectTypes(field, storeOfObjectTypes, fieldParser, urlBuilder)
return Item.Local(
id = field.id,
fieldKey = field.key,
fieldTitle = field.getName(stringResourceProvider),
format = field.format,
limitObjectTypes = limitObjectTypes,
limitObjectTypes = mapLimitObjectTypes(field, storeOfObjectTypes),
isEditableField = fieldParser.isFieldEditable(field)
)
}

View file

@ -37,6 +37,8 @@ import com.anytypeio.anytype.feature_object_type.ui.TypeEvent
import com.anytypeio.anytype.feature_object_type.ui.UiDeleteAlertState
import com.anytypeio.anytype.feature_object_type.ui.UiEditButton
import com.anytypeio.anytype.feature_object_type.ui.UiErrorState
import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.*
import com.anytypeio.anytype.feature_object_type.ui.UiErrorState.Reason.*
import com.anytypeio.anytype.feature_object_type.ui.UiFieldsButtonState
import com.anytypeio.anytype.feature_object_type.ui.UiIconsPickerState
import com.anytypeio.anytype.feature_object_type.ui.UiIconState
@ -51,6 +53,7 @@ import com.anytypeio.anytype.feature_object_type.ui.buildUiFieldsList
import com.anytypeio.anytype.feature_object_type.ui.toTemplateView
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState.Visible.View
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
import com.anytypeio.anytype.presentation.extension.sendAnalyticsScreenObjectType
@ -119,7 +122,7 @@ class ObjectTypeViewModel(
MutableStateFlow<UiTemplatesButtonState>(UiTemplatesButtonState.Hidden)
//type layouts
val uiTypeLayoutsState = MutableStateFlow<UiLayoutTypeState>(Hidden)
val uiTypeLayoutsState = MutableStateFlow<UiLayoutTypeState>(UiLayoutTypeState.Hidden)
//templates modal list state
val uiTemplatesModalListState =
@ -328,7 +331,6 @@ class ObjectTypeViewModel(
val items = buildUiFieldsList(
objType = objType,
stringResourceProvider = stringResourceProvider,
urlBuilder = urlBuilder,
fieldParser = fieldParser,
storeOfObjectTypes = storeOfObjectTypes,
storeOfRelations = storeOfRelations,
@ -373,29 +375,47 @@ class ObjectTypeViewModel(
}
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,
isPossibleToUnlinkFromType = item.isPossibleToUnlinkFromType
)
} 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,
isPossibleToUnlinkFromType = item.isPossibleToUnlinkFromType
)
viewModelScope.launch {
val permissions = _objectTypePermissionsState.value
val computedLimitTypes = item.limitObjectTypes.mapNotNull { id ->
storeOfObjectTypes.get(id = id)?.let { objType ->
UiPropertyLimitTypeItem(
id = objType.id,
name = fieldParser.getObjectName(objectWrapper = objType),
icon = objType.objectIcon(),
uniqueKey = objType.uniqueKey
)
}
}
val formatName = stringResourceProvider.getPropertiesFormatPrettyString(item.format)
val formatIcon = item.format.simpleIcon()
uiEditPropertyScreen.value = if (permissions?.participantCanEdit == true && item.isEditableField) {
UiEditPropertyState.Visible.Edit(
id = item.id,
key = item.fieldKey,
name = item.fieldTitle,
formatName = formatName,
formatIcon = formatIcon,
format = item.format,
limitObjectTypes = computedLimitTypes,
isPossibleToUnlinkFromType = item.isPossibleToUnlinkFromType,
showLimitTypes = false
)
} else {
UiEditPropertyState.Visible.View(
id = item.id,
key = item.fieldKey,
name = item.fieldTitle,
formatName = formatName,
formatIcon = formatIcon,
format = item.format,
limitObjectTypes = computedLimitTypes,
isPossibleToUnlinkFromType = item.isPossibleToUnlinkFromType,
showLimitTypes = false
)
}
}
}
//endregion
@ -461,7 +481,7 @@ class ObjectTypeViewModel(
}
TypeEvent.OnLayoutTypeDismiss -> {
uiTypeLayoutsState.value = Hidden
uiTypeLayoutsState.value = UiLayoutTypeState.Hidden
}
is TypeEvent.OnLayoutTypeItemClick -> {
@ -811,13 +831,30 @@ class ObjectTypeViewModel(
},
onFailure = { error ->
Timber.e(error, "Failed to update relation")
errorState.value = UiErrorState.Show(
reason = UiErrorState.Reason.Other(error.message ?: "")
errorState.value = Show(
reason = Other(error.message ?: "")
)
}
)
}
}
FieldEvent.EditProperty.OnLimitTypesClick -> {
uiEditPropertyScreen.value = when (val state = uiEditPropertyScreen.value) {
is UiEditPropertyState.Visible.Edit -> state.copy(showLimitTypes = true)
is UiEditPropertyState.Visible.New -> state.copy(showLimitTypes = true)
is View -> state.copy(showLimitTypes = true)
else -> state
}
}
FieldEvent.EditProperty.OnLimitTypesDismiss -> {
uiEditPropertyScreen.value = when (val state = uiEditPropertyScreen.value) {
is UiEditPropertyState.Visible.Edit -> state.copy(showLimitTypes = false)
is UiEditPropertyState.Visible.New -> state.copy(showLimitTypes = false)
is View -> state.copy(showLimitTypes = false)
else -> state
}
}
}
}
//endregion

View file

@ -3,10 +3,13 @@ package com.anytypeio.anytype.feature_properties
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
@ -24,6 +27,8 @@ import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState.Visible.*
import com.anytypeio.anytype.feature_properties.edit.UiPropertyFormatsListState
import com.anytypeio.anytype.feature_properties.edit.UiPropertyFormatsListState.*
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
import com.anytypeio.anytype.presentation.mapper.objectIcon
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -47,7 +52,8 @@ class EditTypePropertiesViewModel(
private val stringResourceProvider: StringResourceProvider,
private val createRelation: CreateRelation,
private val setObjectDetails: SetObjectDetails,
private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields
private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields,
private val urlBuilder: UrlBuilder
) : ViewModel() {
private val _uiState = MutableStateFlow(UiEditTypePropertiesState.Companion.EMPTY)
@ -193,12 +199,16 @@ class EditTypePropertiesViewModel(
when (event) {
is UiEditTypePropertiesEvent.OnCreate -> {
val format = event.item.format
uiPropertyEditState.value = New(
name = event.item.title,
formatName = stringResourceProvider.getPropertiesFormatPrettyString(format),
formatIcon = format.simpleIcon(),
format = format,
)
viewModelScope.launch {
uiPropertyEditState.value = New(
name = event.item.title,
formatName = stringResourceProvider.getPropertiesFormatPrettyString(format),
formatIcon = format.simpleIcon(),
format = format,
showLimitTypes = false,
limitObjectTypes = getAllObjectTypesByFormat(format)
)
}
}
is UiEditTypePropertiesEvent.OnExistingClicked -> {
@ -217,13 +227,17 @@ class EditTypePropertiesViewModel(
}
is UiEditTypePropertiesEvent.OnTypeClicked -> {
val format = event.item.format
uiPropertyEditState.value = New(
name = "",
formatName = stringResourceProvider.getPropertiesFormatPrettyString(format),
formatIcon = format.simpleIcon(),
format = format,
)
viewModelScope.launch {
val format = event.item.format
uiPropertyEditState.value = New(
name = "",
formatName = stringResourceProvider.getPropertiesFormatPrettyString(format),
formatIcon = format.simpleIcon(),
format = format,
showLimitTypes = false,
limitObjectTypes = getAllObjectTypesByFormat(format)
)
}
}
UiEditTypePropertiesEvent.OnEditPropertyScreenDismissed -> {
@ -263,23 +277,56 @@ class EditTypePropertiesViewModel(
}
is UiEditTypePropertiesEvent.OnPropertyFormatSelected -> {
uiPropertyFormatsListState.value = Hidden
val state = uiPropertyEditState.value as? UiEditPropertyState.Visible ?: return
uiPropertyEditState.value = when (state) {
is New -> {
val newFormat = event.format.format
state.copy(
formatName = stringResourceProvider.getPropertiesFormatPrettyString(
newFormat
),
formatIcon = newFormat.simpleIcon(),
format = newFormat,
)
}
viewModelScope.launch {
uiPropertyFormatsListState.value = Hidden
val state = uiPropertyEditState.value as? UiEditPropertyState.Visible ?: return@launch
uiPropertyEditState.value = when (state) {
is New -> {
val newFormat = event.format.format
state.copy(
formatName = stringResourceProvider.getPropertiesFormatPrettyString(
newFormat
),
formatIcon = newFormat.simpleIcon(),
format = newFormat,
limitObjectTypes = getAllObjectTypesByFormat(newFormat),
selectedLimitTypeIds = emptyList(),
showLimitTypes = false
)
}
else -> state
}
}
}
UiEditTypePropertiesEvent.OnLimitTypesClick -> {
uiPropertyEditState.value = when (val state = uiPropertyEditState.value) {
is New -> state.copy(showLimitTypes = true)
is View -> state.copy(showLimitTypes = true)
is Edit -> state.copy(showLimitTypes = true)
else -> state
}
}
UiEditTypePropertiesEvent.OnLimitTypesDismiss -> {
uiPropertyEditState.value = when (val state = uiPropertyEditState.value) {
is New -> state.copy(showLimitTypes = false)
is View -> state.copy(showLimitTypes = false)
is Edit -> state.copy(showLimitTypes = false)
else -> state
}
}
is UiEditTypePropertiesEvent.OnLimitTypesDoneClick -> {
val state = uiPropertyEditState.value as? New ?: return.also {
Timber.e("Possible only for New state")
}
uiPropertyEditState.value = state.copy(
selectedLimitTypeIds = event.items,
showLimitTypes = false
)
}
}
}
//endregion
@ -310,17 +357,12 @@ class EditTypePropertiesViewModel(
private fun proceedWithCreatingRelation() {
viewModelScope.launch {
val state = uiPropertyEditState.value as? UiEditPropertyState.Visible ?: return@launch
val (name, format) = when (state) {
is UiEditPropertyState.Visible.Edit -> state.name to state.format
is UiEditPropertyState.Visible.View -> state.name to state.format
is UiEditPropertyState.Visible.New -> state.name to state.format
}
val state = uiPropertyEditState.value as? New ?: return@launch
val params = CreateRelation.Params(
space = vmParams.spaceId.id,
format = format,
name = name,
limitObjectTypes = emptyList(),
format = state.format,
name = state.name,
limitObjectTypes = state.selectedLimitTypeIds,
prefilled = emptyMap()
)
createRelation(params).process(
@ -366,6 +408,39 @@ class EditTypePropertiesViewModel(
}
//endregion
//region Limit Object Types
private suspend fun getAllObjectTypesByFormat(format: RelationFormat): List<UiPropertyLimitTypeItem> {
if (format != RelationFormat.OBJECT) return emptyList()
return storeOfObjectTypes.getAll().mapNotNull { type ->
when (type.recommendedLayout) {
ObjectType.Layout.RELATION,
ObjectType.Layout.DASHBOARD,
ObjectType.Layout.SPACE,
ObjectType.Layout.RELATION_OPTION_LIST,
ObjectType.Layout.RELATION_OPTION,
ObjectType.Layout.SPACE_VIEW,
ObjectType.Layout.CHAT,
ObjectType.Layout.DATE,
ObjectType.Layout.OBJECT_TYPE,
ObjectType.Layout.CHAT_DERIVED,
ObjectType.Layout.TAG -> {
null
}
else -> {
UiPropertyLimitTypeItem(
id = type.id,
name = type.name.orEmpty(),
icon = type.objectIcon(),
uniqueKey = type.uniqueKey
)
}
}
}
}
//endregion
//region Commands
sealed class EditTypePropertiesCommand {
data object Exit : EditTypePropertiesCommand()

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.feature_properties
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.StoreOfRelations
@ -18,7 +19,8 @@ class EditTypePropertiesViewModelFactory @Inject constructor(
private val createRelation: CreateRelation,
private val setObjectDetails: SetObjectDetails,
private val storeOfObjectTypes: StoreOfObjectTypes,
private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields
private val setObjectTypeRecommendedFields: SetObjectTypeRecommendedFields,
private val urlBuilder: UrlBuilder
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -30,6 +32,7 @@ class EditTypePropertiesViewModelFactory @Inject constructor(
createRelation = createRelation,
setObjectDetails = setObjectDetails,
storeOfObjectTypes = storeOfObjectTypes,
setObjectTypeRecommendedFields = setObjectTypeRecommendedFields
setObjectTypeRecommendedFields = setObjectTypeRecommendedFields,
urlBuilder = urlBuilder
) as T
}

View file

@ -1,5 +1,7 @@
package com.anytypeio.anytype.feature_properties.add
import com.anytypeio.anytype.core_models.Id
sealed class UiEditTypePropertiesEvent {
data class OnSearchQueryChanged(val query: String) : UiEditTypePropertiesEvent()
data class OnCreate(val item: UiEditTypePropertiesItem.Create) : UiEditTypePropertiesEvent()
@ -10,6 +12,10 @@ sealed class UiEditTypePropertiesEvent {
data object OnEditPropertyScreenDismissed : UiEditTypePropertiesEvent()
data class OnPropertyNameUpdate(val name: String) : UiEditTypePropertiesEvent()
data object OnLimitTypesClick : UiEditTypePropertiesEvent()
data object OnLimitTypesDismiss : UiEditTypePropertiesEvent()
data class OnLimitTypesDoneClick(val items: List<Id>) : UiEditTypePropertiesEvent()
data object OnPropertyFormatClick : UiEditTypePropertiesEvent()
data object OnPropertyFormatsListDismiss : UiEditTypePropertiesEvent()
data class OnPropertyFormatSelected(val format: UiEditTypePropertiesItem.Format) : UiEditTypePropertiesEvent()

View file

@ -183,6 +183,15 @@ fun AddFieldScreen(
},
onSaveButtonClicked = {
event(UiEditTypePropertiesEvent.OnSaveButtonClicked)
},
onLimitTypesClick = {
event(UiEditTypePropertiesEvent.OnLimitTypesClick)
},
onDismissLimitTypes = {
event(UiEditTypePropertiesEvent.OnLimitTypesDismiss)
},
onLimitObjectTypesDoneClick = {
event(UiEditTypePropertiesEvent.OnLimitTypesDoneClick(it))
}
)
}

View file

@ -18,7 +18,8 @@ sealed class UiEditPropertyState {
val formatIcon: Int?,
val format: RelationFormat,
val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
val isPossibleToUnlinkFromType: Boolean
val isPossibleToUnlinkFromType: Boolean,
val showLimitTypes: Boolean
) : Visible()
data class New(
@ -26,7 +27,9 @@ sealed class UiEditPropertyState {
val formatName: String,
val formatIcon: Int?,
val format: RelationFormat,
val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList()
val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
val selectedLimitTypeIds: List<Id> = emptyList(),
val showLimitTypes: Boolean
) : Visible()
data class View(
@ -37,13 +40,26 @@ sealed class UiEditPropertyState {
val formatIcon: Int?,
val format: RelationFormat,
val limitObjectTypes: List<UiPropertyLimitTypeItem> = emptyList(),
val isPossibleToUnlinkFromType: Boolean
val isPossibleToUnlinkFromType: Boolean,
val showLimitTypes: Boolean
) : Visible()
}
}
sealed class UiPropertyLimitTypesScreen {
data class Visible(
val items: List<UiPropertyLimitTypeItem>
) : UiPropertyLimitTypesScreen()
data object Hidden : UiPropertyLimitTypesScreen()
}
data class UiPropertyLimitTypeItem(
val id: Id, val key: Key, val title: String, val icon: ObjectIcon
val id: Id,
val name: String,
val icon: ObjectIcon? = null,
val uniqueKey: Key? = null,
val number: Int? = null
)
sealed class UiPropertyFormatsListState {

View file

@ -40,6 +40,7 @@ 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.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.ui.limit_types.PropertyLimitTypesPreviewScreen
@Composable
fun PropertyEditScreen(
@ -49,7 +50,8 @@ fun PropertyEditScreen(
onFormatClick: () -> Unit,
onLimitTypesClick: () -> Unit,
onPropertyNameUpdate: (String) -> Unit,
onMenuUnlinkClick: (Id) -> Unit
onMenuUnlinkClick: (Id) -> Unit,
onDismissLimitTypes: () -> Unit
) {
var innerValue by remember(uiState.name) { mutableStateOf(uiState.name) }
@ -144,9 +146,9 @@ fun PropertyEditScreen(
Divider()
if (uiState.format == RelationFormat.OBJECT) {
PropertyLimitTypesEditSection(
PropertyLimitTypesViewSection(
limit = uiState.limitObjectTypes.size,
onLimitTypesClick = { onLimitTypesClick() }
onLimitTypesClick = onLimitTypesClick
)
Divider()
}
@ -167,6 +169,13 @@ fun PropertyEditScreen(
Spacer(modifier = Modifier.height(32.dp))
}
if (uiState.showLimitTypes) {
PropertyLimitTypesPreviewScreen(
items = uiState.limitObjectTypes,
onDismissRequest = onDismissLimitTypes,
)
}
}
@DefaultPreviews
@ -182,12 +191,14 @@ fun EditPropertyPreview() {
formatIcon = R.drawable.ic_relation_format_date_small,
limitObjectTypes = emptyList(),
format = RelationFormat.OBJECT,
isPossibleToUnlinkFromType = true
isPossibleToUnlinkFromType = true,
showLimitTypes = false
),
onSaveButtonClicked = {},
onFormatClick = {},
onLimitTypesClick = {},
onPropertyNameUpdate = {},
onMenuUnlinkClick = {}
onMenuUnlinkClick = {},
onDismissLimitTypes = {}
)
}

View file

@ -0,0 +1,57 @@
package com.anytypeio.anytype.feature_properties.edit.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.views.BodyRegular
@Composable
fun PropertyIncludeTimeSection(
modifier: Modifier,
isIncluded: Boolean,
isEditable: Boolean,
onChangeIncludeTimeClick: () -> Unit
) {
Box(modifier = modifier) {
Text(
modifier = Modifier.align(Alignment.CenterStart),
text = stringResource(id = R.string.property_include_time_section),
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
var checked = remember { mutableStateOf(isIncluded) }
Switch(
modifier = Modifier.align(Alignment.CenterEnd),
checked = checked.value,
onCheckedChange = {
checked.value = it
},
enabled = isEditable,
colors = SwitchDefaults.colors(
checkedThumbColor = colorResource(id = R.color.white),
disabledCheckedThumbColor = colorResource(id = R.color.white),
uncheckedThumbColor = colorResource(id = R.color.white),
disabledUncheckedThumbColor = colorResource(id = R.color.white),
checkedTrackColor = colorResource(id = R.color.palette_system_amber_50),
disabledCheckedTrackColor = colorResource(id = R.color.palette_system_amber_50_opacity_12),
uncheckedTrackColor = colorResource(id = R.color.shape_secondary),
disabledUncheckedTrackColor = colorResource(id = R.color.shape_tertiary),
disabledUncheckedBorderColor = Color.Transparent,
)
)
}
}

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -19,6 +20,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
@ -27,15 +29,19 @@ import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.ui.limit_types.PropertyLimitTypesEditScreen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PropertyNewScreen(
modifier: Modifier,
uiState: UiEditPropertyState.Visible.New,
onDismissLimitTypes: () -> Unit,
onCreateNewButtonClicked: () -> Unit,
onFormatClick: () -> Unit,
onPropertyNameUpdate: (String) -> Unit,
onLimitTypesClick: () -> Unit,
onPropertyNameUpdate: (String) -> Unit
onLimitObjectTypesDoneClick: (List<Id>) -> Unit
) {
var innerValue by remember(uiState.name) { mutableStateOf(uiState.name) }
@ -85,8 +91,12 @@ fun PropertyNewScreen(
if (uiState.format == RelationFormat.OBJECT) {
PropertyLimitTypesEditSection(
limit = uiState.limitObjectTypes.size,
onLimitTypesClick = { onLimitTypesClick() }
modifier = modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onLimitTypesClick() },
limit = uiState.selectedLimitTypeIds.size,
)
Divider()
}
@ -107,6 +117,15 @@ fun PropertyNewScreen(
Spacer(modifier = Modifier.height(32.dp))
}
if (uiState.showLimitTypes) {
PropertyLimitTypesEditScreen(
items = uiState.limitObjectTypes,
savedSelectedItemIds = uiState.selectedLimitTypeIds,
onDismissRequest = onDismissLimitTypes,
onDoneClick = onLimitObjectTypesDoneClick
)
}
}
@DefaultPreviews
@ -119,10 +138,14 @@ fun MyPreviewNew() {
formatName = "Text",
format = RelationFormat.OBJECT,
formatIcon = R.drawable.ic_relation_format_date_small,
showLimitTypes = false,
limitObjectTypes = listOf()
),
onCreateNewButtonClicked = {},
onFormatClick = {},
onPropertyNameUpdate = {},
onLimitTypesClick = {},
onPropertyNameUpdate = {}
onDismissLimitTypes = {},
onLimitObjectTypesDoneClick = {}
)
}

View file

@ -6,10 +6,12 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
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.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
@ -52,8 +54,10 @@ fun PropertyScreen(
onLimitTypesClick: () -> Unit = {},
onCreateNewButtonClicked: () -> Unit = {},
onDismissRequest: () -> Unit,
onDismissLimitTypes: () -> Unit = {},
onPropertyNameUpdate: (String) -> Unit,
onMenuUnlinkClick: (Id) -> Unit ={}
onMenuUnlinkClick: (Id) -> Unit ={},
onLimitObjectTypesDoneClick: (List<Id>) -> Unit = {}
) {
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
@ -73,7 +77,8 @@ fun PropertyScreen(
onFormatClick = onFormatClick,
onLimitTypesClick = onLimitTypesClick,
onPropertyNameUpdate = onPropertyNameUpdate,
onMenuUnlinkClick = onMenuUnlinkClick
onMenuUnlinkClick = onMenuUnlinkClick,
onDismissLimitTypes = onDismissLimitTypes
)
is UiEditPropertyState.Visible.View -> PropertyViewScreen(
@ -81,7 +86,8 @@ fun PropertyScreen(
uiState = uiState,
onFormatClick = onFormatClick,
onLimitTypesClick = onLimitTypesClick,
onMenuUnlinkClick = onMenuUnlinkClick
onMenuUnlinkClick = onMenuUnlinkClick,
onDismissLimitTypes = onDismissLimitTypes
)
is UiEditPropertyState.Visible.New -> PropertyNewScreen(
@ -89,8 +95,10 @@ fun PropertyScreen(
uiState = uiState,
onCreateNewButtonClicked = onCreateNewButtonClicked,
onFormatClick = onFormatClick,
onPropertyNameUpdate = onPropertyNameUpdate,
onLimitTypesClick = onLimitTypesClick,
onPropertyNameUpdate = onPropertyNameUpdate
onDismissLimitTypes = onDismissLimitTypes,
onLimitObjectTypesDoneClick = onLimitObjectTypesDoneClick
)
}
}
@ -213,16 +221,10 @@ fun PropertyFormatSection(
@Composable
fun PropertyLimitTypesEditSection(
modifier: Modifier,
limit: Int,
onLimitTypesClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onLimitTypesClick() }
) {
Box(modifier = modifier) {
Row(
modifier = Modifier.align(Alignment.CenterEnd),
verticalAlignment = Alignment.CenterVertically
@ -267,29 +269,55 @@ fun PropertyLimitTypesViewSection(
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onLimitTypesClick() }
.noRippleThrottledClickable {
if (limit > 0) {
onLimitTypesClick()
}
}
) {
Text(
modifier = Modifier
.align(Alignment.CenterStart)
.fillMaxWidth(),
text = stringResource(id = R.string.edit_property_limit_objects),
style = BodyRegular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary)
)
if (limit > 0) {
Text(
modifier = Modifier.align(Alignment.CenterEnd),
text = "$limit",
modifier = Modifier
.align(Alignment.CenterStart)
.fillMaxWidth(),
text = stringResource(id = R.string.edit_property_limit_objects),
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary)
)
Row(
modifier = Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier,
text = "$limit",
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)
Image(
modifier = Modifier.wrapContentSize().padding(start = 10.dp),
painter = painterResource(id = R.drawable.ic_arrow_forward),
contentDescription = "Change field format icon"
)
}
} else {
Text(
modifier = Modifier
.align(Alignment.CenterStart)
.fillMaxWidth(),
text = stringResource(id = R.string.edit_property_limit_objects_all),
style = BodyRegular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary)
)
Text(
modifier = Modifier.align(Alignment.CenterEnd),
text = stringResource(id = R.string.none),
text = stringResource(id = R.string.edit_property_limit_all),
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)

View file

@ -37,6 +37,8 @@ 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.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
import com.anytypeio.anytype.feature_properties.edit.ui.limit_types.PropertyLimitTypesPreviewScreen
@Composable
fun PropertyViewScreen(
@ -44,7 +46,8 @@ fun PropertyViewScreen(
uiState: UiEditPropertyState.Visible.View,
onFormatClick: () -> Unit,
onLimitTypesClick: () -> Unit,
onMenuUnlinkClick: (Id) -> Unit
onMenuUnlinkClick: (Id) -> Unit,
onDismissLimitTypes: () -> Unit,
) {
var innerValue by remember(uiState.name) { mutableStateOf(uiState.name) }
@ -139,13 +142,20 @@ fun PropertyViewScreen(
if (uiState.format == RelationFormat.OBJECT) {
PropertyLimitTypesViewSection(
limit = uiState.limitObjectTypes.size,
onLimitTypesClick = { onLimitTypesClick() }
onLimitTypesClick = onLimitTypesClick
)
Divider()
}
Spacer(modifier = Modifier.height(32.dp))
}
if (uiState.showLimitTypes) {
PropertyLimitTypesPreviewScreen(
items = uiState.limitObjectTypes,
onDismissRequest = onDismissLimitTypes,
)
}
}
@DefaultPreviews
@ -156,14 +166,25 @@ fun MyPreviewView() {
uiState = UiEditPropertyState.Visible.View(
id = "dummyId1",
key = "dummyKey1",
name = "View property",
formatName = "Text",
name = "View property very long name, so so long, very long",
formatName = "Object Relation",
formatIcon = R.drawable.ic_relation_format_date_small,
format = RelationFormat.FILE,
isPossibleToUnlinkFromType = true
format = RelationFormat.OBJECT,
isPossibleToUnlinkFromType = true,
showLimitTypes = false,
limitObjectTypes = listOf(
UiPropertyLimitTypeItem(
id = "dummyId1",
name = "Limit type 1",
icon = null,
uniqueKey = null,
number = 1
)
)
),
onFormatClick = {},
onLimitTypesClick = {},
onMenuUnlinkClick = {}
onMenuUnlinkClick = {},
onDismissLimitTypes = {}
)
}

View file

@ -0,0 +1,228 @@
package com.anytypeio.anytype.feature_properties.edit.ui.limit_types
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.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.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.relations.CircleIcon
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.feature_properties.R
import com.anytypeio.anytype.feature_properties.add.ui.commonItemModifier
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PropertyLimitTypesEditScreen(
items: List<UiPropertyLimitTypeItem>,
savedSelectedItemIds: List<Id>,
onDismissRequest: () -> Unit,
onDoneClick: (List<Id>) -> Unit
) {
// Pre-populate currentItems with saved selection numbers, if available.
val currentItems = remember {
mutableStateListOf<UiPropertyLimitTypeItem>().apply {
addAll(items.map { item ->
if (savedSelectedItemIds.contains(item.id)) {
// Assign number based on the order in savedSelectedItemIds (index + 1)
val number = savedSelectedItemIds.indexOf(item.id) + 1
item.copy(number = number)
} else {
item
}
})
}
}
// Initialize selectedIds with the saved IDs to maintain the selection order.
val selectedIds = remember {
mutableStateListOf<Id>().apply {
addAll(savedSelectedItemIds)
}
}
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val lazyListState = rememberLazyListState()
ModalBottomSheet(
modifier = Modifier
.padding(top = 60.dp)
.fillMaxWidth(),
onDismissRequest = onDismissRequest,
dragHandle = null,
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,
content = {
Box(
modifier = Modifier
.fillMaxWidth()
) {
Dragger(
modifier = Modifier
.padding(vertical = 6.dp)
.align(Alignment.TopCenter)
)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.height(48.dp),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.property_limit_objects_title),
style = Title1,
color = colorResource(R.color.text_primary),
textAlign = TextAlign.Center
)
}
LazyColumn(
modifier = Modifier
.padding(top = 64.dp)
.fillMaxWidth()
.nestedScroll(rememberNestedScrollInteropConnection()),
state = lazyListState
) {
items(
count = currentItems.size,
key = { index -> currentItems[index].id },
itemContent = { index ->
val item = currentItems[index]
TypeItem(
modifier = commonItemModifier()
.clickable {
// Toggle selection: add if not selected; remove if already selected.
if (!selectedIds.contains(item.id)) {
// Select the item: add its id and set its number.
selectedIds.add(item.id)
currentItems[index] =
item.copy(number = selectedIds.size)
} else {
// Deselect the item.
val removedNumber = item.number ?: 0
selectedIds.remove(item.id)
currentItems[index] = item.copy(number = null)
// Update the numbers for items that were selected after this item.
currentItems.forEachIndexed { idx, current ->
if (current.number != null && current.number > removedNumber) {
currentItems[idx] =
current.copy(number = current.number - 1)
}
}
}
},
item = item
)
}
)
item {
Spacer(modifier = Modifier.height(80.dp))
}
}
ButtonPrimary(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.align(Alignment.BottomCenter),
text = stringResource(R.string.done),
size = ButtonSize.Large,
onClick = {
// Filter and sort selected items by their 'number'
val selectedItemsSorted = currentItems
.filter { it.number != null }
.sortedBy { it.number }
onDoneClick(selectedItemsSorted.map { it.id })
}
)
}
},
)
}
@Composable
private fun TypeItem(
modifier: Modifier,
item: UiPropertyLimitTypeItem
) {
Box(
modifier = modifier
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(end = 56.dp)
.fillMaxHeight(),
verticalAlignment = CenterVertically,
) {
ListWidgetObjectIcon(
modifier = Modifier,
icon = item.icon!!,
iconSize = 24.dp
)
Spacer(modifier = Modifier.size(10.dp))
Text(
modifier = Modifier,
text = item.name,
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (item.number != null) {
CircleIcon(
number = item.number.toString(),
isSelected = true,
modifier = Modifier
.size(24.dp)
.align(Alignment.CenterEnd)
)
} else {
CircleIcon(
isSelected = false,
modifier = Modifier
.size(24.dp)
.align(Alignment.CenterEnd)
)
}
}
}

View file

@ -0,0 +1,143 @@
package com.anytypeio.anytype.feature_properties.edit.ui.limit_types
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.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.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.feature_properties.R
import com.anytypeio.anytype.feature_properties.add.ui.commonItemModifier
import com.anytypeio.anytype.feature_properties.edit.UiPropertyLimitTypeItem
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PropertyLimitTypesPreviewScreen(
items: List<UiPropertyLimitTypeItem>,
onDismissRequest: () -> Unit,
) {
val currentItems = remember {
mutableStateListOf<UiPropertyLimitTypeItem>().apply { addAll(items) }
}
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val lazyListState = rememberLazyListState()
ModalBottomSheet(
modifier = Modifier
.padding(top = 60.dp)
.fillMaxWidth(),
onDismissRequest = onDismissRequest,
dragHandle = null,
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,
content = {
Box(
modifier = Modifier
.fillMaxWidth()
) {
Dragger(
modifier = Modifier
.padding(vertical = 6.dp)
.align(Alignment.TopCenter)
)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.height(48.dp),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.property_limit_objects_title),
style = Title1,
color = colorResource(R.color.text_primary),
textAlign = TextAlign.Center
)
}
LazyColumn(
modifier = Modifier
.padding(top = 64.dp)
.fillMaxWidth()
.nestedScroll(rememberNestedScrollInteropConnection()),
state = lazyListState
) {
items(
count = currentItems.size,
key = { index -> currentItems[index].id },
itemContent = { index ->
val item = currentItems[index]
TypeItem(
modifier = commonItemModifier(),
item = item
)
}
)
item {
Spacer(modifier = Modifier.height(80.dp))
}
}
}
},
)
}
@Composable
private fun TypeItem(
modifier: Modifier,
item: UiPropertyLimitTypeItem
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(end = 20.dp)
.fillMaxHeight(),
verticalAlignment = CenterVertically,
) {
ListWidgetObjectIcon(
modifier = Modifier,
icon = item.icon!!,
iconSize = 24.dp
)
Spacer(modifier = Modifier.size(10.dp))
Text(
modifier = Modifier,
text = item.name,
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}

View file

@ -1976,6 +1976,8 @@ Please provide specific details of your needs here.</string>
<string name="new_property_hint">New property</string>
<string name="edit_property_limit_objects">Limit objects to</string>
<string name="edit_property_limit_objects_all">Limit objects</string>
<string name="edit_property_limit_all">All</string>
<string name="add_property_error_create_new">Error while creating new property</string>
<string name="add_property_error_update">Error while updating property</string>
@ -1984,6 +1986,8 @@ Please provide specific details of your needs here.</string>
<string name="property_select_format_title">Select property format</string>
<string name="property_edit_menu_delete">Delete from space</string>
<string name="property_edit_menu_unlink">Unlink from type</string>
<string name="property_limit_objects_title">Limit objects</string>
<string name="property_include_time_section">Include time</string>
<string name="delete_space_checkbox_text">I have read and want to delete this space</string>
<string name="migration_screen_new_version_update">New Version Update</string>
<string name="migration_screen_description_1">We\'re laying the groundwork for our new chats. Including counters, notifications and other features needed for smooth chat experience.</string>