diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt index c588f67aeb..46eb4916a3 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/PropertiesDI.kt @@ -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 \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt index df51eb8960..b028922c42 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt @@ -206,6 +206,9 @@ sealed class ObjectWrapper { val allRecommendedRelations: List 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(RELATION_FORMAT_OBJECT_TYPES) + val relationFormatObjectTypes get() = getValues(RELATION_FORMAT_OBJECT_TYPES) val type: List get() = getValues(Relations.TYPE) diff --git a/core-ui/src/main/res/drawable/ic_arrow_forward.xml b/core-ui/src/main/res/drawable/ic_arrow_forward.xml index f0e3df9a3a..c58122867d 100644 --- a/core-ui/src/main/res/drawable/ic_arrow_forward.xml +++ b/core-ui/src/main/res/drawable/ic_arrow_forward.xml @@ -8,7 +8,7 @@ android:pathData="M0,24l0,-24l8,-0l0,24z"/> diff --git a/core-ui/src/main/res/values-night/colors.xml b/core-ui/src/main/res/values-night/colors.xml index e0cb7a5b2e..f319de1f87 100644 --- a/core-ui/src/main/res/values-night/colors.xml +++ b/core-ui/src/main/res/values-night/colors.xml @@ -85,6 +85,7 @@ #FFC83C #FFB522 #9F6B00 + #1F9F6B00 #F55522 #E51CA0 #AB50CC diff --git a/core-ui/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml index 167825c824..8865aada97 100644 --- a/core-ui/src/main/res/values/colors.xml +++ b/core-ui/src/main/res/values/colors.xml @@ -124,6 +124,7 @@ #F09C0E #FFB522 #FFD15B + #1FFFD15B #F55522 #E51CA0 #AB50CC diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt index b3768f97f4..ce9f17b1ec 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiEvent.kt @@ -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() } } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt index fffcfce6be..c16825292b 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/UiState.kt @@ -43,7 +43,7 @@ sealed class UiFieldsListItem { abstract val fieldKey: Key abstract val fieldTitle: String abstract val format: RelationFormat - abstract val limitObjectTypes: List + abstract val limitObjectTypes: List 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 = emptyList(), + override val limitObjectTypes: List, 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 = emptyList(), + override val limitObjectTypes: List, override val isPossibleToUnlinkFromType: Boolean = false, override val isEditableField: Boolean ) : Item() diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt index bc028d4750..e00d4632af 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/fields/ui/ListScreen.kt @@ -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() ) ) ), diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt index b08d48b1fd..fca00fad22 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/ui/UiStateExt.kt @@ -66,7 +66,6 @@ suspend fun buildUiFieldsList( objType: ObjectWrapper.Type, stringResourceProvider: StringResourceProvider, fieldParser: FieldParser, - urlBuilder: UrlBuilder, storeOfObjectTypes: StoreOfObjectTypes, storeOfRelations: StoreOfRelations, objTypeConflictingFields: List, @@ -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 { + storeOfObjectTypes: StoreOfObjectTypes +): List { 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) ) } diff --git a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt index 9d239764dd..4ed4959d00 100644 --- a/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt +++ b/feature-object-type/src/main/java/com/anytypeio/anytype/feature_object_type/viewmodel/ObjectTypeViewModel.kt @@ -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.Hidden) //type layouts - val uiTypeLayoutsState = MutableStateFlow(Hidden) + val uiTypeLayoutsState = MutableStateFlow(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 diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt index 432912416a..99d0c12773 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/EditTypePropertiesViewModel.kt @@ -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 { + 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() diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt index 65ece616be..1868b9d0db 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/VMFactory.kt @@ -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 } \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt index e723f5ecf3..dc326f0cb5 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/UiEditTypePropertiesEvent.kt @@ -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) : UiEditTypePropertiesEvent() + data object OnPropertyFormatClick : UiEditTypePropertiesEvent() data object OnPropertyFormatsListDismiss : UiEditTypePropertiesEvent() data class OnPropertyFormatSelected(val format: UiEditTypePropertiesItem.Format) : UiEditTypePropertiesEvent() diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt index 87f44b7f9c..2755d8df0a 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/add/ui/AddScreen.kt @@ -183,6 +183,15 @@ fun AddFieldScreen( }, onSaveButtonClicked = { event(UiEditTypePropertiesEvent.OnSaveButtonClicked) + }, + onLimitTypesClick = { + event(UiEditTypePropertiesEvent.OnLimitTypesClick) + }, + onDismissLimitTypes = { + event(UiEditTypePropertiesEvent.OnLimitTypesDismiss) + }, + onLimitObjectTypesDoneClick = { + event(UiEditTypePropertiesEvent.OnLimitTypesDoneClick(it)) } ) } diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt index 79ee6d8774..7cf9376ef4 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/UiEditPropertyState.kt @@ -18,7 +18,8 @@ sealed class UiEditPropertyState { val formatIcon: Int?, val format: RelationFormat, val limitObjectTypes: List = 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 = emptyList() + val limitObjectTypes: List = emptyList(), + val selectedLimitTypeIds: List = emptyList(), + val showLimitTypes: Boolean ) : Visible() data class View( @@ -37,13 +40,26 @@ sealed class UiEditPropertyState { val formatIcon: Int?, val format: RelationFormat, val limitObjectTypes: List = emptyList(), - val isPossibleToUnlinkFromType: Boolean + val isPossibleToUnlinkFromType: Boolean, + val showLimitTypes: Boolean ) : Visible() } } +sealed class UiPropertyLimitTypesScreen { + data class Visible( + val items: List + ) : 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 { diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/EditPropertyScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/EditPropertyScreen.kt index 1c6bf61126..f829e404c9 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/EditPropertyScreen.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/EditPropertyScreen.kt @@ -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 = {} ) } \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/IncludeTimeSection.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/IncludeTimeSection.kt new file mode 100644 index 0000000000..88b905b22c --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/IncludeTimeSection.kt @@ -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, + ) + ) + } +} \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/NewPropertyScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/NewPropertyScreen.kt index 36e4765866..a73282060e 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/NewPropertyScreen.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/NewPropertyScreen.kt @@ -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) -> 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 = {} ) } \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt index 3ae65a1942..3c963f83d3 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/PropertyScreen.kt @@ -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) -> 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) ) diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/ViewPropertyScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/ViewPropertyScreen.kt index 1f031c89bf..ffce627dc0 100644 --- a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/ViewPropertyScreen.kt +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/ViewPropertyScreen.kt @@ -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 = {} ) } \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/limit_types/LimitTypesEditScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/limit_types/LimitTypesEditScreen.kt new file mode 100644 index 0000000000..64b7785492 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/limit_types/LimitTypesEditScreen.kt @@ -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, + savedSelectedItemIds: List, + onDismissRequest: () -> Unit, + onDoneClick: (List) -> Unit +) { + + // Pre-populate currentItems with saved selection numbers, if available. + val currentItems = remember { + mutableStateListOf().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().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) + + ) + } + } +} \ No newline at end of file diff --git a/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/limit_types/LimitTypesPreviewScreen.kt b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/limit_types/LimitTypesPreviewScreen.kt new file mode 100644 index 0000000000..98243a77f8 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/limit_types/LimitTypesPreviewScreen.kt @@ -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, + onDismissRequest: () -> Unit, +) { + val currentItems = remember { + mutableStateListOf().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 + ) + } +} \ No newline at end of file diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 919d541eb2..a9a10d7d52 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1976,6 +1976,8 @@ Please provide specific details of your needs here. New property Limit objects to + Limit objects + All Error while creating new property Error while updating property @@ -1984,6 +1986,8 @@ Please provide specific details of your needs here. Select property format Delete from space Unlink from type + Limit objects + Include time I have read and want to delete this space New Version Update We\'re laying the groundwork for our new chats. Including counters, notifications and other features needed for smooth chat experience.