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

@ -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
)
}
}