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

DROID-3429 Primitives | Edit type properties, part 3 (#2147)

This commit is contained in:
Konstantin Ivanov 2025-03-11 09:58:04 +01:00 committed by GitHub
parent d0fcf8f527
commit 481650572e
Signed by: github
GPG key ID: B5690EEEBB952194
8 changed files with 1076 additions and 14 deletions

View file

@ -1,5 +1,7 @@
package com.anytypeio.anytype.feature_object_type.fields
import com.anytypeio.anytype.core_models.Id
sealed class FieldEvent {
data object OnEditPropertyScreenDismiss : FieldEvent()
@ -7,7 +9,7 @@ sealed class FieldEvent {
data class OnFieldItemClick(val item: UiFieldsListItem) : FieldEvent()
sealed class FieldItemMenu : FieldEvent() {
data class OnDeleteFromTypeClick(val item: UiFieldsListItem) : FieldItemMenu()
data class OnDeleteFromTypeClick(val id: Id) : FieldItemMenu()
data class OnAddLocalToTypeClick(val item: UiFieldsListItem) : FieldItemMenu()
}

View file

@ -226,7 +226,9 @@ fun FieldsMainScreen(
onLimitTypesClick = {},
onSaveButtonClicked = {},
onCreateNewButtonClicked = {},
onPropertyNameUpdate = { }
onPropertyNameUpdate = { },
onDeleteButtonClicked = { id -> fieldEvent(OnDeleteFromTypeClick(id)) },
)
}
@ -617,7 +619,7 @@ fun ItemDropDownMenu(
)
},
onClick = {
onFieldEvent(OnDeleteFromTypeClick(item))
onFieldEvent(OnDeleteFromTypeClick(id = item.id))
},
)
}

View file

@ -500,8 +500,8 @@ class ObjectTypeViewModel(
is TypeEvent.OnTemplateMenuClick.Duplicate -> {
if (event.item is TemplateView.Template) {
proceedWithTemplateDuplicate(
template = event.item.id
proceedWithDuplicateObject(
objectId = event.item.id
)
}
}
@ -702,7 +702,7 @@ class ObjectTypeViewModel(
private fun proceedWithFieldItemMenuClick(event: FieldEvent.FieldItemMenu) {
when (event) {
is FieldEvent.FieldItemMenu.OnDeleteFromTypeClick -> {
val deleteId = event.item.id
val deleteId = event.id
val headerItems = mutableListOf<Id>()
val sideBarItems = mutableListOf<Id>()
val hiddenItems = mutableListOf<Id>()
@ -741,6 +741,7 @@ class ObjectTypeViewModel(
hiddenFields = hiddenItems,
fileFields = filesItems
)
uiEditPropertyScreen.value = UiEditPropertyState.Hidden
}
is FieldEvent.FieldItemMenu.OnAddLocalToTypeClick -> {
@ -859,17 +860,17 @@ class ObjectTypeViewModel(
}
}
private fun proceedWithTemplateDuplicate(template: Id) {
private fun proceedWithDuplicateObject(objectId: Id) {
val params = DuplicateObjects.Params(
ids = listOf(template)
ids = listOf(objectId)
)
viewModelScope.launch {
duplicateObjects.async(params).fold(
onSuccess = {
Timber.d("Template $template duplicated")
Timber.d("Object $objectId duplicated")
},
onFailure = {
Timber.e(it, "Error while duplicating template $template")
Timber.e(it, "Error while duplicating object $objectId")
}
)
}

View file

@ -1,10 +1,63 @@
package com.anytypeio.anytype.feature_properties.add.ui
import android.os.Build
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesEvent
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.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.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.common.bottomBorder
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.foundation.DefaultSearchBar
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.Caption1Medium
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.widgets.dv.DragHandle
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
import com.anytypeio.anytype.feature_properties.edit.ui.PropertyScreen
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesEvent
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesItem
import com.anytypeio.anytype.feature_properties.add.UiEditTypePropertiesState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -13,4 +66,315 @@ fun AddFieldScreen(
uiStateEditProperty: UiEditPropertyState,
event: (UiEditTypePropertiesEvent) -> Unit
) {
var isSearchEmpty by remember { mutableStateOf(true) }
val lazyListState = rememberLazyListState()
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(rememberNestedScrollInteropConnection()),
containerColor = Color.Transparent,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(
color = colorResource(id = R.color.background_primary),
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
),
horizontalAlignment = Alignment.CenterHorizontally
) {
DragHandle()
TopToolbar(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
)
DefaultSearchBar(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 10.dp),
hint = R.string.object_type_add_property_screen_search_hint
) { newQuery ->
isSearchEmpty = newQuery.isEmpty()
event(
UiEditTypePropertiesEvent.OnSearchQueryChanged(newQuery)
)
}
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
}
},
content = { paddingValues ->
val modifier = if (Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
Modifier
.windowInsetsPadding(WindowInsets.navigationBars)
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding())
else
Modifier
.fillMaxSize()
.padding(paddingValues)
LazyColumn(
modifier = modifier
.background(color = colorResource(id = R.color.background_primary)),
state = lazyListState
) {
items(
count = state.items.size,
key = { index -> state.items[index].id },
itemContent = { index ->
val item = state.items[index]
when (item) {
is UiEditTypePropertiesItem.Format -> {
PropertyTypeItem(
modifier = commonItemModifier()
.noRippleThrottledClickable {
event(UiEditTypePropertiesEvent.OnTypeClicked(item))
},
item = item
)
}
is UiEditTypePropertiesItem.Default -> {
FieldItem(
modifier = commonItemModifier()
.noRippleThrottledClickable {
event(UiEditTypePropertiesEvent.OnExistingClicked(item))
},
item = item
)
}
is UiEditTypePropertiesItem.Create -> {
PropertyCreateItem(
modifier = commonItemModifier()
.noRippleThrottledClickable {
event(UiEditTypePropertiesEvent.OnCreate(item))
},
item = item
)
}
is UiEditTypePropertiesItem.Section -> Section(item = item)
}
}
)
item {
Spacer(modifier = Modifier.height(100.dp))
}
}
}
)
if (uiStateEditProperty is UiEditPropertyState.Visible) {
PropertyScreen(
modifier = Modifier.fillMaxWidth(),
uiState = uiStateEditProperty,
onDismissRequest = { event(UiEditTypePropertiesEvent.OnEditPropertyScreenDismissed) },
onCreateNewButtonClicked = {
event(UiEditTypePropertiesEvent.OnCreateNewButtonClicked)
},
onPropertyNameUpdate = { name ->
event(UiEditTypePropertiesEvent.OnPropertyNameUpdate(name))
}
)
}
}
@Composable
private fun Section(
item: UiEditTypePropertiesItem.Section
) {
val title = when (item) {
is UiEditTypePropertiesItem.Section.Existing -> stringResource(R.string.object_type_add_property_screen_section_existing)
is UiEditTypePropertiesItem.Section.Types -> stringResource(R.string.object_type_add_property_screen_section_types)
}
Text(
modifier = Modifier
.padding(top = 26.dp, start = 20.dp, end = 20.dp, bottom = 8.dp)
.fillMaxWidth(),
text = title,
style = Caption1Medium,
color = colorResource(id = R.color.text_secondary),
)
}
@Composable
private fun PropertyTypeItem(
modifier: Modifier,
item: UiEditTypePropertiesItem.Format
) {
Row(
modifier = modifier,
verticalAlignment = CenterVertically
) {
val formatIcon = item.format.simpleIcon()
if (formatIcon != null) {
Image(
modifier = Modifier
.padding(end = 10.dp)
.size(24.dp),
painter = painterResource(id = formatIcon),
contentDescription = "Relation format icon",
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.weight(1.0f)
.padding(end = 16.dp),
text = item.prettyName,
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable
private fun PropertyCreateItem(
modifier: Modifier,
item: UiEditTypePropertiesItem.Create
) {
Row(
modifier = modifier,
verticalAlignment = CenterVertically
) {
val formatIcon = item.format.simpleIcon()
if (formatIcon != null) {
Image(
modifier = Modifier
.padding(end = 10.dp)
.size(24.dp),
painter = painterResource(id = formatIcon),
contentDescription = "Relation format icon",
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.weight(1.0f)
.padding(end = 16.dp),
text = stringResource(R.string.object_type_add_property_screen_create, item.title),
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun FieldItem(
modifier: Modifier,
item: UiEditTypePropertiesItem.Default
) {
Row(
modifier = modifier,
verticalAlignment = CenterVertically
) {
val formatIcon = item.format.simpleIcon()
if (formatIcon != null) {
Image(
modifier = Modifier
.padding(end = 10.dp)
.size(24.dp),
painter = painterResource(id = formatIcon),
contentDescription = "Relation format icon",
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.weight(1.0f)
.padding(end = 16.dp),
text = item.title,
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopToolbar(
modifier: Modifier
) {
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.object_type_add_property_screen_title),
style = Title1,
color = colorResource(R.color.text_primary),
textAlign = TextAlign.Center
)
}
}
/** A common modifier for list items. **/
@Composable
fun LazyItemScope.commonItemModifier() = Modifier
.height(52.dp)
.fillMaxWidth()
.padding(horizontal = 20.dp)
.bottomBorder()
.animateItem()
@DefaultPreviews
@Composable
fun PreviewAddFieldScreen() {
AddFieldScreen(
state = UiEditTypePropertiesState(
items = listOf(
UiEditTypePropertiesItem.Create(
id = "111",
format = RelationFormat.LONG_TEXT,
title = "This is very very long title, which is very very long, but not very very long"
),
UiEditTypePropertiesItem.Section.Types(),
UiEditTypePropertiesItem.Format(
id = "11",
format = RelationFormat.STATUS,
prettyName = "Status"
),
UiEditTypePropertiesItem.Format(
id = "12",
format = RelationFormat.OBJECT,
prettyName = "Object"
),
UiEditTypePropertiesItem.Format(
id = "13",
format = RelationFormat.LONG_TEXT,
prettyName = "Long Text"
),
UiEditTypePropertiesItem.Format(
id = "14",
format = RelationFormat.PHONE,
prettyName = "Phone"
),
UiEditTypePropertiesItem.Section.Existing(),
UiEditTypePropertiesItem.Default(
id = "1",
propertyKey = "key",
title = "Title",
format = RelationFormat.LONG_TEXT
),
UiEditTypePropertiesItem.Default(
id = "2",
propertyKey = "key",
title = "Some Object",
format = RelationFormat.OBJECT
)
)
),
event = {},
uiStateEditProperty = UiEditPropertyState.Hidden
)
}

View file

@ -0,0 +1,184 @@
package com.anytypeio.anytype.feature_properties.edit.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
@Composable
fun PropertyEditScreen(
modifier: Modifier,
uiState: UiEditPropertyState.Visible.Edit,
onSaveButtonClicked: () -> Unit,
onFormatClick: () -> Unit,
onLimitTypesClick: () -> Unit,
onPropertyNameUpdate: (String) -> Unit,
onDeleteButtonClicked: () -> Unit
) {
var innerValue by remember(uiState.name) { mutableStateOf(uiState.name) }
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
var isMenuExpanded by remember { mutableStateOf(false) }
Column(modifier = modifier) {
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
PropertyIcon(
modifier = propertyIconModifier(),
formatIconRes = uiState.formatIcon
)
PropertyName(
modifier = Modifier
.fillMaxWidth()
.padding(start = 13.dp, top = 7.dp)
.weight(1.0f),
value = innerValue,
isEditable = true,
focusRequester = focusRequester,
keyboardController = keyboardController,
emptyName = stringResource(R.string.untitled),
onValueChange = {
innerValue = it
onPropertyNameUpdate(it)
}
)
Spacer(modifier = Modifier.size(4.dp))
Box(
modifier = Modifier
.padding(end = 21.dp)
.size(40.dp)
.noRippleThrottledClickable {
isMenuExpanded = true
}
) {
Image(
modifier = Modifier
//.padding(end = 20.dp)
.wrapContentSize()
.align(Alignment.Center),
painter = painterResource(id = R.drawable.ic_widget_three_dots),
contentDescription = "Property menu icon",
contentScale = ContentScale.None,
)
DropdownMenu(
modifier = Modifier.width(244.dp),
expanded = isMenuExpanded,
onDismissRequest = { isMenuExpanded = false },
shape = RoundedCornerShape(size = 10.dp),
containerColor = colorResource(id = R.color.background_primary),
shadowElevation = 5.dp,
) {
DropdownMenuItem(
modifier = Modifier.height(44.dp),
onClick = {
onDeleteButtonClicked()
isMenuExpanded = false
},
text = {
Text(
text = stringResource(R.string.delete),
style = BodyRegular,
color = colorResource(id = R.color.palette_system_red),
modifier = Modifier
)
}
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
PropertyFormatSection(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onFormatClick() },
formatName = uiState.formatName,
isEditable = true,
)
Divider()
if (uiState.format == RelationFormat.OBJECT) {
PropertyLimitTypesEditSection(
limit = uiState.limitObjectTypes.size,
onLimitTypesClick = { onLimitTypesClick() }
)
Divider()
}
Spacer(modifier = Modifier.height(14.dp))
ButtonPrimary(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 22.dp),
text = stringResource(R.string.object_type_fields_btn_save),
onClick = {
onSaveButtonClicked()
},
size = ButtonSize.Large
)
}
}
@DefaultPreviews
@Composable
fun EditPropertyPreview() {
PropertyEditScreen(
modifier = Modifier.fillMaxWidth(),
uiState = UiEditPropertyState.Visible.Edit(
id = "dummyId1",
key = "dummyKey1",
name = "My property",
formatName = "Text",
formatIcon = R.drawable.ic_relation_format_date_small,
limitObjectTypes = emptyList(),
format = RelationFormat.OBJECT
),
onSaveButtonClicked = {},
onFormatClick = {},
onLimitTypesClick = {},
onPropertyNameUpdate = {},
onDeleteButtonClicked = {},
)
}

View file

@ -0,0 +1,124 @@
package com.anytypeio.anytype.feature_properties.edit.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.foundation.Divider
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
@Composable
fun PropertyNewScreen(
modifier: Modifier,
uiState: UiEditPropertyState.Visible.New,
onCreateNewButtonClicked: () -> Unit,
onFormatClick: () -> Unit,
onLimitTypesClick: () -> Unit,
onPropertyNameUpdate: (String) -> Unit
) {
var innerValue by remember(uiState.name) { mutableStateOf(uiState.name) }
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
Column(modifier = modifier) {
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
PropertyIcon(
modifier = propertyIconModifier(),
formatIconRes = uiState.formatIcon
)
PropertyName(
modifier = Modifier
.fillMaxWidth()
.padding(start = 13.dp, top = 7.dp)
.weight(1.0f),
value = innerValue,
isEditable = true,
focusRequester = focusRequester,
keyboardController = keyboardController,
emptyName = stringResource(R.string.new_property_hint),
onValueChange = {
innerValue = it
onPropertyNameUpdate(it)
}
)
Spacer(modifier = Modifier.size(4.dp))
}
Spacer(modifier = Modifier.height(8.dp))
PropertyFormatSection(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onFormatClick() },
formatName = uiState.formatName,
isEditable = true,
)
Divider()
if (uiState.format == RelationFormat.OBJECT) {
PropertyLimitTypesEditSection(
limit = uiState.limitObjectTypes.size,
onLimitTypesClick = { onLimitTypesClick() }
)
Divider()
}
Spacer(modifier = Modifier.height(14.dp))
ButtonPrimary(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 22.dp),
text = stringResource(R.string.object_type_fields_btn_save),
onClick = {
onCreateNewButtonClicked()
},
size = ButtonSize.Large
)
}
}
@DefaultPreviews
@Composable
fun MyPreviewNew() {
PropertyNewScreen(
modifier = Modifier.fillMaxWidth(),
uiState = UiEditPropertyState.Visible.New(
name = "",
formatName = "Text",
format = RelationFormat.OBJECT,
formatIcon = R.drawable.ic_relation_format_date_small,
),
onCreateNewButtonClicked = {},
onFormatClick = {},
onLimitTypesClick = {},
onPropertyNameUpdate = {}
)
}

View file

@ -1,8 +1,45 @@
package com.anytypeio.anytype.feature_properties.edit.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.HeadlineHeading
import com.anytypeio.anytype.core_ui.widgets.dv.DragHandle
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
@OptIn(ExperimentalMaterial3Api::class)
@ -15,6 +52,249 @@ fun PropertyScreen(
onLimitTypesClick: () -> Unit = {},
onCreateNewButtonClicked: () -> Unit = {},
onDismissRequest: () -> Unit,
onPropertyNameUpdate: (String) -> Unit
onPropertyNameUpdate: (String) -> Unit,
onDeleteButtonClicked: (Id) -> Unit ={},
) {
}
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
modifier = modifier,
dragHandle = { DragHandle() },
scrimColor = colorResource(id = R.color.modal_screen_outside_background),
containerColor = colorResource(id = R.color.background_primary),
shape = RoundedCornerShape(16.dp),
sheetState = bottomSheetState,
onDismissRequest = onDismissRequest,
) {
when (uiState) {
is UiEditPropertyState.Visible.Edit -> PropertyEditScreen(
modifier = Modifier.fillMaxWidth(),
uiState = uiState,
onSaveButtonClicked = onSaveButtonClicked,
onFormatClick = onFormatClick,
onLimitTypesClick = onLimitTypesClick,
onPropertyNameUpdate = onPropertyNameUpdate,
onDeleteButtonClicked = {
onDeleteButtonClicked(uiState.id)
}
)
is UiEditPropertyState.Visible.View -> PropertyViewScreen(
modifier = Modifier.fillMaxWidth(),
uiState = uiState,
onFormatClick = onFormatClick,
onLimitTypesClick = onLimitTypesClick
)
is UiEditPropertyState.Visible.New -> PropertyNewScreen(
modifier = Modifier.fillMaxWidth(),
uiState = uiState,
onCreateNewButtonClicked = onCreateNewButtonClicked,
onFormatClick = onFormatClick,
onLimitTypesClick = onLimitTypesClick,
onPropertyNameUpdate = onPropertyNameUpdate
)
}
}
}
//region Content Elements
@Composable
fun propertyIconModifier() = Modifier
.padding(start = 20.dp)
.size(40.dp)
.border(
width = 1.dp,
color = colorResource(id = R.color.shape_primary),
shape = RoundedCornerShape(5.dp)
)
@Composable
fun RowScope.PropertyIcon(
modifier: Modifier,
formatIconRes: Int?
) {
if (formatIconRes != null) {
Image(
painter = painterResource(id = formatIconRes),
contentDescription = "Property format icon",
contentScale = ContentScale.None,
modifier = modifier
)
}
}
@Composable
fun PropertyName(
modifier: Modifier,
value: String,
isEditable: Boolean,
focusRequester: FocusRequester,
keyboardController: SoftwareKeyboardController?,
emptyName: String,
onValueChange: (String) -> Unit
) {
val focusManager = LocalFocusManager.current
Column(modifier = modifier) {
BasicTextField(
value = value,
onValueChange = onValueChange,
textStyle = HeadlineHeading.copy(color = colorResource(id = R.color.text_primary)),
singleLine = false,
enabled = isEditable,
maxLines = 10,
cursorBrush = SolidColor(colorResource(id = R.color.text_primary)),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester)
.onFocusChanged { /* You can handle focus changes here if needed */ },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions {
keyboardController?.hide()
focusManager.clearFocus()
onValueChange(value)
},
decorationBox = { innerTextField ->
if (value.isEmpty()) {
Text(
text = emptyName,
style = HeadlineHeading,
color = colorResource(id = R.color.text_tertiary),
modifier = Modifier.fillMaxWidth()
)
}
innerTextField()
}
)
}
}
@Composable
fun PropertyFormatSection(
modifier: Modifier,
formatName: String,
isEditable: Boolean,
) {
Box(modifier = modifier) {
Text(
modifier = Modifier.align(Alignment.CenterStart),
text = stringResource(id = R.string.format),
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
if (isEditable) {
Row(
modifier = Modifier.align(Alignment.CenterEnd),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier,
text = formatName,
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)
Image(
modifier = Modifier,
painter = painterResource(id = R.drawable.ic_arrow_forward_24),
contentDescription = "Change field format icon"
)
}
} else {
Text(
modifier = Modifier.align(Alignment.CenterEnd),
text = formatName,
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)
}
}
}
@Composable
fun PropertyLimitTypesEditSection(
limit: Int,
onLimitTypesClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onLimitTypesClick() }
) {
Row(
modifier = Modifier.align(Alignment.CenterEnd),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.weight(1.0f)
.padding(end = 16.dp),
text = stringResource(id = R.string.edit_property_limit_objects),
style = BodyRegular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary)
)
val text = if (limit == 0) {
stringResource(id = R.string.add)
} else {
"$limit"
}
Text(
modifier = Modifier,
text = text,
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)
Image(
modifier = Modifier,
painter = painterResource(id = R.drawable.ic_arrow_forward_24),
contentDescription = "Change field format icon"
)
}
}
}
@Composable
fun PropertyLimitTypesViewSection(
limit: Int,
onLimitTypesClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { 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",
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)
} else {
Text(
modifier = Modifier.align(Alignment.CenterEnd),
text = stringResource(id = R.string.none),
style = BodyRegular,
color = colorResource(id = R.color.text_secondary)
)
}
}
}
//endregion

View file

@ -0,0 +1,105 @@
package com.anytypeio.anytype.feature_properties.edit.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.feature_properties.edit.UiEditPropertyState
@Composable
fun PropertyViewScreen(
modifier: Modifier,
uiState: UiEditPropertyState.Visible.View,
onFormatClick: () -> Unit,
onLimitTypesClick: () -> Unit
) {
var innerValue by remember(uiState.name) { mutableStateOf(uiState.name) }
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
Column(modifier = modifier) {
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
PropertyIcon(
modifier = propertyIconModifier(),
formatIconRes = uiState.formatIcon
)
PropertyName(
modifier = Modifier
.fillMaxWidth()
.padding(start = 13.dp, top = 7.dp)
.weight(1.0f),
value = innerValue,
isEditable = false,
focusRequester = focusRequester,
keyboardController = keyboardController,
emptyName = stringResource(R.string.untitled),
onValueChange = { innerValue = it }
)
Spacer(modifier = Modifier.size(20.dp))
}
Spacer(modifier = Modifier.height(8.dp))
PropertyFormatSection(
modifier = Modifier
.fillMaxWidth()
.height(52.dp)
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { onFormatClick() },
formatName = uiState.formatName,
isEditable = false,
)
Divider()
if (uiState.format == RelationFormat.OBJECT) {
PropertyLimitTypesViewSection(
limit = uiState.limitObjectTypes.size,
onLimitTypesClick = { onLimitTypesClick() }
)
Divider()
}
}
}
@DefaultPreviews
@Composable
fun MyPreviewView() {
PropertyViewScreen(
modifier = Modifier.fillMaxWidth(),
uiState = UiEditPropertyState.Visible.View(
id = "dummyId1",
key = "dummyKey1",
name = "View property",
formatName = "Text",
formatIcon = R.drawable.ic_relation_format_date_small,
format = RelationFormat.FILE
),
onFormatClick = {},
onLimitTypesClick = {}
)
}