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 c54d2a70b0..41eac9e492 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,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() } 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 bc88014478..9941798cc2 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 @@ -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)) }, ) } 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 a2fc5f0c86..ad6aff4e8b 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 @@ -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() val sideBarItems = mutableListOf() val hiddenItems = mutableListOf() @@ -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") } ) } 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 72765d29f8..6d1ca7829e 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 @@ -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 + ) } \ No newline at end of file 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 new file mode 100644 index 0000000000..a9654a1add --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/EditPropertyScreen.kt @@ -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 = {}, + ) +} \ 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 new file mode 100644 index 0000000000..33ea49f2bc --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/NewPropertyScreen.kt @@ -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 = {} + ) +} \ 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 daf8e09c86..ef23150e8c 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 @@ -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 ={}, ) { -} \ No newline at end of file + 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 \ No newline at end of file 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 new file mode 100644 index 0000000000..50e41d68c2 --- /dev/null +++ b/feature-properties/src/main/java/com/anytypeio/anytype/feature_properties/edit/ui/ViewPropertyScreen.kt @@ -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 = {} + ) +} \ No newline at end of file