mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3347 Primitives | Fields value screen, design (#2111)
This commit is contained in:
parent
880c71403d
commit
8ae50b27a2
24 changed files with 1942 additions and 312 deletions
|
@ -40,6 +40,12 @@ fun dark(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun dark(code: String): Color {
|
||||
val colorTheme = ThemeColor.entries.find { it.code == code } ?: ThemeColor.DEFAULT
|
||||
return dark(colorTheme)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun light(
|
||||
color: ThemeColor
|
||||
|
@ -57,6 +63,12 @@ fun light(
|
|||
ThemeColor.DEFAULT -> colorResource(id = R.color.palette_light_default)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun light(code: String): Color {
|
||||
val colorTheme = ThemeColor.entries.find { it.code == code } ?: ThemeColor.DEFAULT
|
||||
return light(colorTheme)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun Modifier.bouncingClickable(
|
||||
enabled: Boolean = true,
|
||||
|
@ -95,7 +107,7 @@ fun Modifier.bouncingClickable(
|
|||
)
|
||||
}
|
||||
|
||||
fun <T> SnapshotStateList<T>.swapList(newList: List<T>){
|
||||
fun <T> SnapshotStateList<T>.swapList(newList: List<T>) {
|
||||
clear()
|
||||
addAll(newList)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.Relation
|
||||
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.views.Relations1
|
||||
|
||||
@Composable
|
||||
fun FieldEmpty(modifier: Modifier = Modifier, title: String, fieldFormat: RelationFormat) {
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
when (fieldFormat) {
|
||||
Relation.Format.LONG_TEXT,
|
||||
Relation.Format.SHORT_TEXT,
|
||||
Relation.Format.URL -> {
|
||||
val emptyState = getEnterValueText(fieldFormat)
|
||||
FieldVerticalEmpty(
|
||||
modifier = defaultModifier,
|
||||
title = title,
|
||||
emptyState = emptyState
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val emptyState = getEnterValueText(fieldFormat)
|
||||
FieldHorizontalEmpty(
|
||||
modifier = defaultModifier,
|
||||
title = title,
|
||||
emptyState = emptyState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FieldVerticalEmpty(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
emptyState: String,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = emptyState,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_tertiary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FieldHorizontalEmpty(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
emptyState: String,
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.widthIn(max = halfScreenWidth),
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = emptyState,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_tertiary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getEnterValueText(format: RelationFormat): String {
|
||||
return when (format) {
|
||||
Relation.Format.LONG_TEXT,
|
||||
Relation.Format.SHORT_TEXT -> stringResource(R.string.field_text_empty)
|
||||
|
||||
Relation.Format.NUMBER -> stringResource(R.string.field_number_empty)
|
||||
Relation.Format.DATE -> stringResource(R.string.field_date_empty)
|
||||
Relation.Format.CHECKBOX -> ""
|
||||
Relation.Format.URL -> stringResource(R.string.field_url_empty)
|
||||
Relation.Format.EMAIL -> stringResource(R.string.field_email_empty)
|
||||
Relation.Format.PHONE -> stringResource(R.string.field_phone_empty)
|
||||
Relation.Format.OBJECT -> stringResource(R.string.field_object_empty)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun PreviewField() {
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
item {
|
||||
FieldEmpty(
|
||||
title = "Description",
|
||||
fieldFormat = Relation.Format.LONG_TEXT
|
||||
)
|
||||
}
|
||||
item {
|
||||
FieldEmpty(
|
||||
title = "Some Number, very long long long long long fields name",
|
||||
fieldFormat = Relation.Format.NUMBER
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
|
||||
@Composable
|
||||
fun FieldTypeCheckbox(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
isCheck: Boolean,
|
||||
) {
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
Row(
|
||||
modifier = defaultModifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Box(
|
||||
modifier = Modifier.widthIn(max = halfScreenWidth)
|
||||
) {
|
||||
if (isCheck) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_checkbox_checked),
|
||||
contentDescription = "Checkbox",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_checkbox_default),
|
||||
contentDescription = "Checkbox",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun FieldTypeCheckboxPreview() {
|
||||
FieldTypeCheckbox(
|
||||
title = "Creation date",
|
||||
isCheck = false
|
||||
)
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.DayOfWeekCustom
|
||||
import com.anytypeio.anytype.core_models.RelativeDate
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.extensions.getPrettyName
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCallout
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
|
||||
@Composable
|
||||
fun FieldTypeDate(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
relativeDate: RelativeDate
|
||||
) {
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
Row(
|
||||
modifier = defaultModifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Box(
|
||||
modifier = Modifier.widthIn(max = halfScreenWidth)
|
||||
) {
|
||||
Text(
|
||||
text = relativeDate.getPrettyName(),
|
||||
style = BodyCallout.copy(
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun FieldTypeDatePreview() {
|
||||
FieldTypeDate(
|
||||
title = "Creation date",
|
||||
relativeDate = RelativeDate.Tomorrow(
|
||||
initialTimeInMillis = System.currentTimeMillis(),
|
||||
dayOfWeek = DayOfWeekCustom.THURSDAY
|
||||
),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCallout
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.core_ui.views.Relations2
|
||||
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
|
||||
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
|
||||
import com.anytypeio.anytype.presentation.sets.model.FileView
|
||||
|
||||
@Composable
|
||||
fun FieldTypeFile(
|
||||
modifier: Modifier = Modifier,
|
||||
fieldObject: ObjectRelationView.File
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
if (fieldObject.files.size == 1) {
|
||||
// If there is only one item, display the title and the item in one row.
|
||||
val singleItem = fieldObject.files.first()
|
||||
Row(
|
||||
modifier = defaultModifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = fieldObject.name,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Box(
|
||||
modifier = Modifier.widthIn(max = halfScreenWidth)
|
||||
) {
|
||||
ItemView(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
objView = singleItem
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = defaultModifier
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
text = fieldObject.name,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
// The first item (if present)
|
||||
if (fieldObject.files.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
) {
|
||||
ItemView(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
objView = fieldObject.files.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
// The second item (if present)
|
||||
if (fieldObject.files.size > 1) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Box(modifier = Modifier.widthIn(max = halfScreenWidth)) {
|
||||
if (fieldObject.files.size == 2) {
|
||||
ItemView(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
objView = fieldObject.files[1]
|
||||
)
|
||||
} else {
|
||||
// If there are more than two items, display the second item with a "+n" suffix.
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
ListWidgetObjectIcon(
|
||||
icon = fieldObject.files[1].icon,
|
||||
iconSize = 18.dp,
|
||||
modifier = Modifier,
|
||||
onTaskIconClicked = {
|
||||
// Do nothing
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
// The main text with an integrated suffix that occupies the remaining space.
|
||||
FileNameWithSuffix(
|
||||
text = fieldObject.files[1].name,
|
||||
suffix = "+${fieldObject.files.size - 2}",
|
||||
textStyle = BodyCallout.copy(
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
),
|
||||
countStyle = Relations2.copy(
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to display a single item: icon (if available) + text.
|
||||
@Composable
|
||||
internal fun ItemView(modifier: Modifier, objView: FileView) {
|
||||
Row(
|
||||
modifier = modifier.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ListWidgetObjectIcon(
|
||||
icon = objView.icon,
|
||||
iconSize = 18.dp,
|
||||
modifier = Modifier,
|
||||
onTaskIconClicked = {
|
||||
// Do nothing
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = objView.name,
|
||||
style = BodyCallout.copy(
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that displays a row consisting of the main text and a suffix.
|
||||
* If the main text is short enough, the suffix (for example, "+n")
|
||||
* will appear immediately after it; if the text is long, it will be truncated (with an ellipsis)
|
||||
* to leave space for the suffix.
|
||||
*/
|
||||
@Composable
|
||||
internal fun FileNameWithSuffix(
|
||||
text: String,
|
||||
suffix: String,
|
||||
textStyle: TextStyle,
|
||||
countStyle: TextStyle,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
|
||||
SubcomposeLayout(modifier = modifier) { constraints ->
|
||||
val suffixConstraints = constraints.copy(minWidth = 0, maxWidth = Constraints.Infinity)
|
||||
val suffixPlaceable = subcompose("suffix") {
|
||||
Box(
|
||||
modifier = Modifier.background(
|
||||
color = colorResource(R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
text = suffix,
|
||||
style = countStyle,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}.first().measure(suffixConstraints)
|
||||
|
||||
// The available space for the main text is the total width minus the width of the suffix.
|
||||
val availableWidthForText = (constraints.maxWidth - suffixPlaceable.width).coerceAtLeast(0)
|
||||
val textConstraints = constraints.copy(minWidth = 0, maxWidth = availableWidthForText)
|
||||
val textPlaceable = subcompose("text") {
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}.first().measure(textConstraints)
|
||||
|
||||
// If width constraints are specified (e.g., when using weight), use the full available width.
|
||||
val finalWidth =
|
||||
if (constraints.hasBoundedWidth) constraints.maxWidth else (textPlaceable.width + suffixPlaceable.width)
|
||||
val height = maxOf(textPlaceable.height, suffixPlaceable.height)
|
||||
layout(finalWidth, height) {
|
||||
// Align content to the left.
|
||||
textPlaceable.placeRelative(0, 0)
|
||||
val offsetYPx = with(density) { 0.5.dp.roundToPx() }
|
||||
val offsetXPx = with(density) { 8.dp.roundToPx() }
|
||||
suffixPlaceable.placeRelative(textPlaceable.width + offsetXPx, offsetYPx)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.extensions.dark
|
||||
import com.anytypeio.anytype.core_ui.extensions.light
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.core_ui.views.Relations2
|
||||
import com.anytypeio.anytype.presentation.sets.model.TagView
|
||||
|
||||
@Composable
|
||||
fun FieldTypeMultiSelect(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
tags: List<TagView>
|
||||
) {
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
Row(
|
||||
modifier = defaultModifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
TagRow(
|
||||
tags = tags,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = Relations1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that displays a single tag “chip” with text and a background.
|
||||
*
|
||||
* @param text The tag text.
|
||||
* @param backgroundColor The chip’s background color.
|
||||
* @param textStyle The [TextStyle] used for the tag text.
|
||||
* @param isSingle If true, the chip is rendered in single-mode – meaning that if the chip does not fit
|
||||
* in the available width, its text will be truncated (TextOverflow.Ellipsis).
|
||||
* @param isOverflow If true, this chip is an overflow indicator (e.g. “+3”).
|
||||
* @param modifier Modifier to be applied to the chip.
|
||||
*/
|
||||
@Composable
|
||||
fun TagChip(
|
||||
text: String,
|
||||
tagColor: String,
|
||||
textStyle: TextStyle,
|
||||
isSingle: Boolean = false,
|
||||
isOverflow: Boolean = false,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
// In single mode, we allow truncation.
|
||||
Box(
|
||||
modifier = modifier
|
||||
.wrapContentWidth()
|
||||
.background(light(tagColor), shape = RoundedCornerShape(6.dp))
|
||||
.padding(horizontal = 6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
color = dark(tagColor),
|
||||
maxLines = 1,
|
||||
overflow = if (isSingle) TextOverflow.Ellipsis else TextOverflow.Clip
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that lays out a row of tags in a single horizontal line.
|
||||
*
|
||||
* The behavior is as follows:
|
||||
* 1. **Single tag case:** If there is only one tag, it is displayed in a row. If its intrinsic width
|
||||
* exceeds the available width, the text is truncated (TextOverflow.Ellipsis).
|
||||
*
|
||||
* 2. **Multiple tags case:** The layout tries to display as many tags as possible in full (i.e. without truncation).
|
||||
* - If a tag would be rendered with truncation, it is omitted and all remaining tags are replaced by
|
||||
* an overflow chip (e.g. “+n”).
|
||||
* - For example, if the first tag is short and fits but the second tag’s full width would exceed the available space,
|
||||
* then only the first tag is displayed and an overflow chip shows the remaining count.
|
||||
*
|
||||
* @param tags The list of [Tag] objects to display.
|
||||
* @param modifier Modifier to be applied to the overall layout.
|
||||
* @param textStyle The [TextStyle] used for the tag text.
|
||||
* @param spacing The spacing (in dp) between adjacent tags.
|
||||
* @param overflowChipColor The background color for the overflow chip.
|
||||
*/
|
||||
@Composable
|
||||
fun TagRow(
|
||||
tags: List<TagView>,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle,
|
||||
spacing: Dp = 4.dp,
|
||||
overflowChipColor: Color = Color.Red
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
|
||||
SubcomposeLayout(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentSize(Alignment.TopStart)
|
||||
) { constraints ->
|
||||
val availableWidth = constraints.maxWidth
|
||||
val spacingPx = spacing.roundToPx()
|
||||
|
||||
// If there are no tags, layout an empty box.
|
||||
if (tags.isEmpty()) {
|
||||
return@SubcomposeLayout layout(0, 0) {}
|
||||
}
|
||||
|
||||
// --- Single tag case ---
|
||||
if (tags.size == 1) {
|
||||
// Render the single tag in "single" mode so that it truncates if needed.
|
||||
val tagPlaceable = subcompose("tag0") {
|
||||
TagChip(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
text = tags[0].tag,
|
||||
tagColor = tags[0].color,
|
||||
textStyle = textStyle,
|
||||
isSingle = true
|
||||
)
|
||||
}.first().measure(constraints)
|
||||
return@SubcomposeLayout layout(
|
||||
width = availableWidth,
|
||||
height = tagPlaceable.height
|
||||
) {
|
||||
tagPlaceable.placeRelative(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Multiple tags case ---
|
||||
val measuredPlaceables = mutableListOf<Placeable>()
|
||||
var consumedWidth = 0
|
||||
var shownTagCount = 0
|
||||
|
||||
// Iterate over tags and measure their full intrinsic width (i.e. no truncation).
|
||||
for ((index, tag) in tags.withIndex()) {
|
||||
// Measure the tag chip with an "unbounded" width to get its full intrinsic width.
|
||||
val tagPlaceable = subcompose("tag$index") {
|
||||
TagChip(
|
||||
text = tags[index].tag,
|
||||
tagColor = tags[index].color,
|
||||
textStyle = textStyle,
|
||||
isSingle = false
|
||||
)
|
||||
}.first().measure(constraints.copy(maxWidth = Constraints.Infinity))
|
||||
|
||||
// Calculate additional spacing (if not the first tag).
|
||||
val additionalSpacing = if (shownTagCount > 0) spacingPx else 0
|
||||
|
||||
// How many tags would remain if we add this tag?
|
||||
val remainingCount = tags.size - (shownTagCount + 1)
|
||||
// Pre-measure an overflow chip if needed, using a unique key.
|
||||
val overflowPlaceableCandidate = if (remainingCount > 0) {
|
||||
subcompose("overflow_$index") {
|
||||
TagChip(
|
||||
text = "+$remainingCount",
|
||||
tagColor = tags[index].color,
|
||||
textStyle = textStyle,
|
||||
isOverflow = true
|
||||
)
|
||||
}.first().measure(constraints.copy(maxWidth = Constraints.Infinity))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Compute candidate width: current consumed width + spacing + tag width +
|
||||
// (if needed, spacing and overflow chip width)
|
||||
val candidateWidth = consumedWidth +
|
||||
additionalSpacing +
|
||||
tagPlaceable.width +
|
||||
(if (overflowPlaceableCandidate != null) spacingPx + overflowPlaceableCandidate.width else 0)
|
||||
|
||||
// If the candidate width fits into the available width, accept this tag.
|
||||
if (candidateWidth <= availableWidth) {
|
||||
measuredPlaceables.add(tagPlaceable)
|
||||
consumedWidth += additionalSpacing + tagPlaceable.width
|
||||
shownTagCount++
|
||||
} else {
|
||||
// Otherwise, do not include this tag; break out of the loop.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the number of remaining tags.
|
||||
val remainingCount = tags.size - shownTagCount
|
||||
val overflowPlaceable = if (remainingCount > 0) {
|
||||
subcompose("overflow_final") {
|
||||
Box(
|
||||
modifier = Modifier.background(
|
||||
color = colorResource(R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
text = "+$remainingCount",
|
||||
style = Relations2.copy(
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}.first().measure(constraints.copy(maxWidth = Constraints.Infinity))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Final width is the sum of consumed width plus spacing and overflow chip (if present)
|
||||
val totalWidth = if (overflowPlaceable != null) {
|
||||
consumedWidth + spacingPx + overflowPlaceable.width
|
||||
} else {
|
||||
consumedWidth
|
||||
}
|
||||
val maxHeight =
|
||||
(measuredPlaceables.map { it.height } + listOf(overflowPlaceable?.height ?: 0))
|
||||
.maxOrNull() ?: 0
|
||||
|
||||
layout(totalWidth, maxHeight) {
|
||||
var xPosition = 0
|
||||
measuredPlaceables.forEach { placeable ->
|
||||
placeable.placeRelative(xPosition, 0)
|
||||
xPosition += placeable.width + spacingPx
|
||||
}
|
||||
val offsetYPx = with(density) { 1.dp.roundToPx() }
|
||||
overflowPlaceable?.placeRelative(xPosition, offsetYPx)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCallout
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.core_ui.views.Relations2
|
||||
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
|
||||
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
|
||||
import com.anytypeio.anytype.presentation.sets.model.ObjectView
|
||||
|
||||
/**
|
||||
* The main composable for FieldObject.
|
||||
*
|
||||
* The first item is displayed in a Box with its width constrained to half the screen width.
|
||||
*
|
||||
* If a second item exists:
|
||||
* - If there are exactly two items, it is displayed normally.
|
||||
* - If there are more than two items, the second item is displayed with a suffix "+n"
|
||||
* (where n = total number of items minus two) immediately following its text.
|
||||
* If the text of the second item is long, it is truncated so that the suffix is always visible.
|
||||
*/
|
||||
@Composable
|
||||
fun FieldTypeObject(
|
||||
modifier: Modifier = Modifier,
|
||||
fieldObject: ObjectRelationView.Object
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
if (fieldObject.objects.size == 1) {
|
||||
// If there is only one item, display the title and the item in one row.
|
||||
val singleItem = fieldObject.objects.first()
|
||||
Row(
|
||||
modifier = defaultModifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = fieldObject.name,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Box(
|
||||
modifier = Modifier.widthIn(max = halfScreenWidth)
|
||||
) {
|
||||
ItemView(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
objView = singleItem
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = defaultModifier
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
text = fieldObject.name,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
// The first item (if present)
|
||||
if (fieldObject.objects.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
) {
|
||||
ItemView(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
objView = fieldObject.objects.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
// The second item (if present)
|
||||
if (fieldObject.objects.size > 1) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Box(modifier = Modifier.widthIn(max = halfScreenWidth)) {
|
||||
if (fieldObject.objects.size == 2) {
|
||||
ItemView(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
objView = fieldObject.objects[1]
|
||||
)
|
||||
} else {
|
||||
// If there are more than two items, display the second item with a "+n" suffix.
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
ListWidgetObjectIcon(
|
||||
icon = fieldObject.objects[1].icon,
|
||||
iconSize = 18.dp,
|
||||
modifier = Modifier,
|
||||
onTaskIconClicked = {
|
||||
// Do nothing
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
// The main text with an integrated suffix that occupies the remaining space.
|
||||
TextWithSuffix(
|
||||
text = fieldObject.objects[1].name,
|
||||
suffix = "+${fieldObject.objects.size - 2}",
|
||||
textStyle = BodyCallout.copy(
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
),
|
||||
countStyle = Relations2.copy(
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to display a single item: icon (if available) + text.
|
||||
@Composable
|
||||
internal fun ItemView(modifier: Modifier, objView: ObjectView) {
|
||||
Row(
|
||||
modifier = modifier.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ListWidgetObjectIcon(
|
||||
icon = objView.icon,
|
||||
iconSize = 18.dp,
|
||||
modifier = Modifier,
|
||||
onTaskIconClicked = {
|
||||
// Do nothing
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = objView.name,
|
||||
style = BodyCallout.copy(
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that displays a row consisting of the main text and a suffix.
|
||||
* If the main text is short enough, the suffix (for example, "+n")
|
||||
* will appear immediately after it; if the text is long, it will be truncated (with an ellipsis)
|
||||
* to leave space for the suffix.
|
||||
*/
|
||||
@Composable
|
||||
fun TextWithSuffix(
|
||||
text: String,
|
||||
suffix: String,
|
||||
textStyle: TextStyle,
|
||||
countStyle: TextStyle,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
|
||||
SubcomposeLayout(modifier = modifier) { constraints ->
|
||||
val suffixConstraints = constraints.copy(minWidth = 0, maxWidth = Constraints.Infinity)
|
||||
val suffixPlaceable = subcompose("suffix") {
|
||||
Box(
|
||||
modifier = Modifier.background(
|
||||
color = colorResource(R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
text = suffix,
|
||||
style = countStyle,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}.first().measure(suffixConstraints)
|
||||
|
||||
// The available space for the main text is the total width minus the width of the suffix.
|
||||
val availableWidthForText = (constraints.maxWidth - suffixPlaceable.width).coerceAtLeast(0)
|
||||
val textConstraints = constraints.copy(minWidth = 0, maxWidth = availableWidthForText)
|
||||
val textPlaceable = subcompose("text") {
|
||||
Text(
|
||||
text = text,
|
||||
style = textStyle,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}.first().measure(textConstraints)
|
||||
|
||||
// If width constraints are specified (e.g., when using weight), use the full available width.
|
||||
val finalWidth =
|
||||
if (constraints.hasBoundedWidth) constraints.maxWidth else (textPlaceable.width + suffixPlaceable.width)
|
||||
val height = maxOf(textPlaceable.height, suffixPlaceable.height)
|
||||
layout(finalWidth, height) {
|
||||
// Align content to the left.
|
||||
textPlaceable.placeRelative(0, 0)
|
||||
val offsetYPx = with(density) { 0.5.dp.roundToPx() }
|
||||
val offsetXPx = with(density) { 8.dp.roundToPx() }
|
||||
suffixPlaceable.placeRelative(textPlaceable.width + offsetXPx, offsetYPx)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.extensions.dark
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.presentation.sets.model.StatusView
|
||||
|
||||
@Composable
|
||||
fun FieldTypeSelect(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
status: StatusView
|
||||
) {
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val halfScreenWidth = screenWidth / 2 - 32.dp
|
||||
|
||||
Row(
|
||||
modifier = defaultModifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = halfScreenWidth)
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(
|
||||
text = status.status,
|
||||
style = Relations1,
|
||||
color = dark(status.color),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun FieldTypeSelectPreview() {
|
||||
FieldTypeSelect(
|
||||
title = "Status",
|
||||
status = StatusView(
|
||||
id = "1",
|
||||
status = "In Progress",
|
||||
color = ThemeColor.TEAL.code
|
||||
)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
|
||||
@Composable
|
||||
fun FieldTypeText(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
text: String
|
||||
) {
|
||||
val defaultModifier = modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
|
||||
Column(
|
||||
modifier = defaultModifier
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 16.dp),
|
||||
text = title,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = text,
|
||||
style = Relations1,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun FieldTypeTextPreview() {
|
||||
FieldTypeText(
|
||||
title = "Description",
|
||||
text = "Upon creating your profile, you’ll receive your very own 12 word mnemonic ‘Recovery’ phrase to protect your account. This phrase is generated on-device and represents your master key generated upon signup, similar to a Bitcoin wallet. It also prevents anyone - including Anytype - from accessing your account and decrypting your data.\n" +
|
||||
"\n" +
|
||||
"All data you create will be stored locally (on-device) first. We use zero-knowledge encryption, meaning that your data is encrypted before it leaves your device to sync with other devices or backup nodes."
|
||||
)
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
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.unit.dp
|
||||
import com.anytypeio.anytype.core_models.RelationFormat
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.relations.resRelationOrigin
|
||||
import com.anytypeio.anytype.core_ui.foundation.Dragger
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
|
||||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
|
||||
import com.anytypeio.anytype.presentation.relations.RelationListViewModel.Model
|
||||
import timber.log.Timber
|
||||
|
||||
@Composable
|
||||
fun FieldListScreen(
|
||||
state: List<Model>,
|
||||
onRelationClicked: (Model.Item) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(rememberNestedScrollInteropConnection())
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
item {
|
||||
Dragger(
|
||||
modifier = Modifier.padding(vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.height(48.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = stringResource(id = R.string.fields_screen_title),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
count = state.size,
|
||||
key = { index -> state[index].identifier },
|
||||
itemContent = { index ->
|
||||
val item = state[index]
|
||||
when (item) {
|
||||
is Model.Item -> {
|
||||
val field = item.view
|
||||
when (field) {
|
||||
is ObjectRelationView.Checkbox -> {
|
||||
FieldTypeCheckbox(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
isCheck = field.isChecked
|
||||
)
|
||||
}
|
||||
|
||||
is ObjectRelationView.Date -> {
|
||||
val relativeDate = field.relativeDate
|
||||
if (relativeDate != null) {
|
||||
FieldTypeDate(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
relativeDate = relativeDate
|
||||
)
|
||||
} else {
|
||||
FieldEmpty(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
fieldFormat = RelationFormat.DATE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ObjectRelationView.Default -> {
|
||||
val textValue = field.value
|
||||
if (field.key == Relations.ORIGIN) {
|
||||
val code = textValue?.toInt() ?: -1
|
||||
FieldTypeText(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
text = stringResource(code.resRelationOrigin())
|
||||
)
|
||||
} else {
|
||||
if (textValue.isNullOrEmpty() == true) {
|
||||
FieldEmpty(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
fieldFormat = RelationFormat.LONG_TEXT
|
||||
)
|
||||
} else {
|
||||
FieldTypeText(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
text = textValue
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is ObjectRelationView.File -> {
|
||||
if (field.files.isEmpty()) {
|
||||
FieldEmpty(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
fieldFormat = RelationFormat.FILE
|
||||
)
|
||||
} else {
|
||||
FieldTypeFile(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
fieldObject = field
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ObjectRelationView.Object -> {
|
||||
if (field.objects.isEmpty()) {
|
||||
FieldEmpty(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
fieldFormat = RelationFormat.OBJECT
|
||||
)
|
||||
} else {
|
||||
FieldTypeObject(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
fieldObject = field
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ObjectRelationView.Status -> {
|
||||
if (field.status.isEmpty()) {
|
||||
FieldEmpty(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
fieldFormat = RelationFormat.STATUS
|
||||
)
|
||||
} else {
|
||||
FieldTypeSelect(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
status = field.status.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ObjectRelationView.Tags -> {
|
||||
if (field.tags.isEmpty()) {
|
||||
FieldEmpty(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
fieldFormat = RelationFormat.TAG
|
||||
)
|
||||
} else {
|
||||
FieldTypeMultiSelect(
|
||||
modifier = Modifier.noRippleThrottledClickable {
|
||||
onRelationClicked(item)
|
||||
},
|
||||
title = field.name,
|
||||
tags = field.tags
|
||||
)
|
||||
}
|
||||
}
|
||||
is ObjectRelationView.Links.Backlinks,
|
||||
is ObjectRelationView.Links.From,
|
||||
is ObjectRelationView.ObjectType.Base,
|
||||
is ObjectRelationView.ObjectType.Deleted,
|
||||
is ObjectRelationView.Source -> {
|
||||
Timber.e("Unsupported field type: $field, shouldn't be in the fields list")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Model.Section.Featured -> {
|
||||
//TODO: Implement
|
||||
}
|
||||
|
||||
Model.Section.Other -> {
|
||||
//TODO: Implement
|
||||
}
|
||||
|
||||
is Model.Section.TypeFrom -> {
|
||||
//TODO: Implement
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
package com.anytypeio.anytype.core_ui.features.fields
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.sets.model.TagView
|
||||
|
||||
// --------------------
|
||||
// Test Case 1: Multiple Tags (Your First Test Case)
|
||||
// --------------------
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun TagsPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "Urgent",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
TagView(
|
||||
id = "2",
|
||||
tag = "Personal",
|
||||
color = ThemeColor.ORANGE.code,
|
||||
),
|
||||
TagView(
|
||||
id = "3",
|
||||
tag = "Done",
|
||||
color = ThemeColor.LIME.code,
|
||||
),
|
||||
TagView(
|
||||
id = "4",
|
||||
tag = "In Progress",
|
||||
color = ThemeColor.BLUE.code,
|
||||
),
|
||||
TagView(
|
||||
id = "5",
|
||||
tag = "Waiting",
|
||||
color = ThemeColor.YELLOW.code,
|
||||
),
|
||||
TagView(
|
||||
id = "6",
|
||||
tag = "Blocked",
|
||||
color = ThemeColor.PURPLE.code,
|
||||
),
|
||||
TagView(
|
||||
id = "7",
|
||||
tag = "Spam",
|
||||
color = ThemeColor.PINK.code,
|
||||
)
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Test Case 2: Single Tag with a Very Long Name (truncated)
|
||||
// --------------------
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun SingleLongTagPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "This is an extremely long tag that should be truncated if it doesn't fit in the available space",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Test Case 3: Single Tag with a Short Name
|
||||
// --------------------
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun SingleShortTagPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "Urgent",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Test Case 4: Two Tags – First Tag Short, Second Tag Very Long (second omitted → overflow)
|
||||
// --------------------
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun TwoTagsFirstShortSecondLongPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "Urgent",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
TagView(
|
||||
id = "2",
|
||||
tag = "This is a very long tag that might not fit entirely",
|
||||
color = ThemeColor.ORANGE.code,
|
||||
)
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Test Case 5: Two Short Tags (both displayed)
|
||||
// --------------------
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun TwoShortTagsPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "Urgent",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
TagView(
|
||||
id = "2",
|
||||
tag = "Personal",
|
||||
color = ThemeColor.ORANGE.code,
|
||||
)
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Test Case 6: Three Short Tags (all displayed)
|
||||
// --------------------
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun ThreeShortTagsPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "Urgent",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
TagView(
|
||||
id = "2",
|
||||
tag = "Personal",
|
||||
color = ThemeColor.ORANGE.code,
|
||||
),
|
||||
TagView(
|
||||
id = "3",
|
||||
tag = "Done",
|
||||
color = ThemeColor.LIME.code,
|
||||
),
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// Test Case 7: Four Tags with Overflow (only some tags displayed, remainder shown as +n)
|
||||
// --------------------
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun FourTagsWithOverflowPreview() {
|
||||
LazyColumn {
|
||||
item {
|
||||
FieldTypeMultiSelect(
|
||||
tags = listOf(
|
||||
TagView(
|
||||
id = "1",
|
||||
tag = "Urgent",
|
||||
color = ThemeColor.RED.code,
|
||||
),
|
||||
TagView(
|
||||
id = "2",
|
||||
tag = "Personal",
|
||||
color = ThemeColor.ORANGE.code,
|
||||
),
|
||||
TagView(
|
||||
id = "3",
|
||||
tag = "Done",
|
||||
color = ThemeColor.LIME.code,
|
||||
),
|
||||
TagView(
|
||||
id = "4",
|
||||
tag = "In Progress",
|
||||
color = ThemeColor.BLUE.code,
|
||||
)
|
||||
),
|
||||
title = "Tag"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
package com.anytypeio.anytype.core_ui.features.navigation
|
||||
|
||||
import com.anytypeio.anytype.presentation.navigation.ObjectView
|
||||
import com.anytypeio.anytype.presentation.navigation.filterBy
|
||||
import com.anytypeio.anytype.presentation.navigation.isContainsText
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class LinkToObjectViewKtTest {
|
||||
|
||||
@Test
|
||||
fun `should contain text`() {
|
||||
val pageLink = ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = "Subtitle first",
|
||||
title = "Title first",
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
val text = "IRst"
|
||||
|
||||
val result = pageLink.isContainsText(text)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not contain text`() {
|
||||
val pageLink = ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = "Subtitle first",
|
||||
title = "Title first",
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
val text = "ECO"
|
||||
|
||||
val result = pageLink.isContainsText(text)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return original list`() {
|
||||
val text = "same"
|
||||
val list = listOf(
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString() + text,
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
),
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString() + text,
|
||||
icon = ObjectIcon.None
|
||||
),
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString() + text,
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
)
|
||||
|
||||
val result = list.filterBy(text)
|
||||
|
||||
assertEquals(list, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return list without one item`() {
|
||||
val text = "same"
|
||||
val pageLink1 = ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString() + text,
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
val pageLink3 = ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString() + text + MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
val list = listOf(
|
||||
pageLink1,
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
),
|
||||
pageLink3
|
||||
)
|
||||
|
||||
val result = list.filterBy(text)
|
||||
|
||||
val expected = listOf(pageLink1, pageLink3)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return empty list`() {
|
||||
val text = "same"
|
||||
val list = listOf(
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
),
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
),
|
||||
ObjectView(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
subtitle = MockDataFactory.randomString(),
|
||||
title = MockDataFactory.randomString(),
|
||||
icon = ObjectIcon.None
|
||||
)
|
||||
)
|
||||
|
||||
val result = list.filterBy(text)
|
||||
|
||||
val expected = listOf<ObjectView>()
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectTypeIds
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.RelativeDate
|
||||
|
@ -111,6 +112,9 @@ class FieldParserImpl @Inject constructor(
|
|||
|
||||
//region ObjectWrapper.Basic fields
|
||||
override fun getObjectName(objectWrapper: ObjectWrapper.Basic): String {
|
||||
if (objectWrapper.isDeleted == true) {
|
||||
return stringResourceProvider.getDeletedObjectTitle()
|
||||
}
|
||||
val result = when (objectWrapper.layout) {
|
||||
ObjectType.Layout.DATE -> {
|
||||
val relativeDate = dateProvider.calculateRelativeDates(
|
||||
|
|
|
@ -1882,4 +1882,27 @@ Please provide specific details of your needs here.</string>
|
|||
<string name="migration_migration_failed">Migration failed</string>
|
||||
<string name="migration_error_please_free_up_space_and_run_the_process_again">Please free up space and run the process again.</string>
|
||||
|
||||
<string name="fields_screen_title">Fields</string>
|
||||
<string name="field_text_title">Text</string>
|
||||
<string name="field_text_empty">Enter</string>
|
||||
<string name="field_number_title">Number</string>
|
||||
<string name="field_number_empty">Enter</string>
|
||||
<string name="field_date_title">Date</string>
|
||||
<string name="field_date_empty">Value</string>
|
||||
<string name="field_url_title">URL</string>
|
||||
<string name="field_url_empty">Add</string>
|
||||
<string name="field_email_title">E-mail</string>
|
||||
<string name="field_email_empty">Enter</string>
|
||||
<string name="field_phone_title">Phone number</string>
|
||||
<string name="field_phone_empty">Enter</string>
|
||||
<string name="field_select_title">Select</string>
|
||||
<string name="field_select_empty">Select</string>
|
||||
<string name="field_multiselect_title">Multiselect</string>
|
||||
<string name="field_multiselect_empty">Select</string>
|
||||
<string name="field_object_title">Object</string>
|
||||
<string name="field_object_empty">Add</string>
|
||||
<string name="field_file_and_media_title">File & Media</string>
|
||||
<string name="field_file_and_media_empty">Add</string>
|
||||
<string name="field_checkbox_title">Select</string>
|
||||
|
||||
</resources>
|
|
@ -25,17 +25,3 @@ data class DefaultObjectView(
|
|||
val lastOpenedDate: Long = 0L,
|
||||
val isFavorite: Boolean = false
|
||||
) : DefaultSearchItem
|
||||
|
||||
data class ObjectView(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val icon: ObjectIcon,
|
||||
val layout: ObjectType.Layout? = null
|
||||
)
|
||||
|
||||
fun ObjectView.isContainsText(text: String): Boolean = title.contains(text, true) ||
|
||||
subtitle.contains(text, true)
|
||||
|
||||
fun List<ObjectView>.filterBy(text: String): List<ObjectView> =
|
||||
if (text.isNotEmpty()) this.filter { it.isContainsText(text) } else this
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
package com.anytypeio.anytype.presentation.objects
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectViewDetails
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Struct
|
||||
import com.anytypeio.anytype.core_models.ext.isValidObject
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.objects.ObjectStore
|
||||
import com.anytypeio.anytype.domain.primitives.FieldParser
|
||||
import com.anytypeio.anytype.presentation.extension.getObject
|
||||
import com.anytypeio.anytype.presentation.mapper.objectIcon
|
||||
import com.anytypeio.anytype.presentation.sets.model.ObjectView
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Mapper class for data class @see [com.anytypeio.anytype.presentation.sets.model.ObjectView]
|
||||
* that represents a view of an object in case of fields value.
|
||||
*/
|
||||
fun Struct.buildRelationValueObjectViews(
|
||||
relationKey: Key,
|
||||
details: ObjectViewDetails,
|
||||
builder: UrlBuilder,
|
||||
fieldParser: FieldParser
|
||||
): List<ObjectView> {
|
||||
return this[relationKey]
|
||||
.asIdList()
|
||||
.mapNotNull { id ->
|
||||
details.getObject(id)
|
||||
?.takeIf { it.isValid }
|
||||
?.toObjectView(urlBuilder = builder, fieldParser = fieldParser)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Struct.buildObjectViews(
|
||||
columnKey: Id,
|
||||
store: ObjectStore,
|
||||
builder: UrlBuilder,
|
||||
withIcon: Boolean = true,
|
||||
fieldParser: FieldParser
|
||||
): List<ObjectView> {
|
||||
return this.getOrDefault(columnKey, null)
|
||||
.asIdList()
|
||||
.mapNotNull { id ->
|
||||
val wrapper = store.get(id)
|
||||
if (wrapper == null || !wrapper.isValid) {
|
||||
Timber.w("Object was missing in object store: $id or was invalid")
|
||||
null
|
||||
} else if (wrapper.isDeleted == true) {
|
||||
ObjectView.Deleted(id = id, name = fieldParser.getObjectName(wrapper))
|
||||
} else {
|
||||
val icon = if (withIcon) wrapper.objectIcon(builder) else ObjectIcon.None
|
||||
ObjectView.Default(
|
||||
id = id,
|
||||
name = fieldParser.getObjectName(wrapper),
|
||||
icon = icon,
|
||||
types = wrapper.type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ObjectWrapper.Basic.objects(
|
||||
relation: Id,
|
||||
urlBuilder: UrlBuilder,
|
||||
storeOfObjects: ObjectStore,
|
||||
fieldParser: FieldParser
|
||||
): List<ObjectView> {
|
||||
return map.getOrDefault(relation, null)
|
||||
.asIdList()
|
||||
.mapNotNull { id ->
|
||||
storeOfObjects.get(id)
|
||||
?.takeIf { it.isValid }
|
||||
?.toObjectView(urlBuilder, fieldParser)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ObjectWrapper.Relation.toObjects(
|
||||
value: Any?,
|
||||
store: ObjectStore,
|
||||
urlBuilder: UrlBuilder,
|
||||
fieldParser: FieldParser
|
||||
): List<ObjectView> {
|
||||
return value.asIdList().mapNotNull { id ->
|
||||
val raw = store.get(id)?.map
|
||||
if (raw.isNullOrEmpty() || !raw.isValidObject()) null
|
||||
else {
|
||||
ObjectWrapper.Basic(raw).toObjectView(urlBuilder, fieldParser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts any value into a list of Ids.
|
||||
* Supports a single Id, a Collection (e.g. List) of Ids, or a Map whose values are Ids.
|
||||
*/
|
||||
private fun Any?.asIdList(): List<Id> = when (this) {
|
||||
is Id -> listOf(this)
|
||||
is Collection<*> -> this.filterIsInstance<Id>()
|
||||
is Map<*, *> -> this.values.filterIsInstance<Id>()
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Basic wrapper into an ObjectView.
|
||||
* isValid check performed already in the caller function.
|
||||
*/
|
||||
fun ObjectWrapper.Basic.toObjectView(
|
||||
urlBuilder: UrlBuilder,
|
||||
fieldParser: FieldParser
|
||||
): ObjectView = if (isDeleted == true)
|
||||
ObjectView.Deleted(id = id, name = fieldParser.getObjectName(this))
|
||||
else toObjectViewDefault(urlBuilder, fieldParser)
|
||||
|
||||
/**
|
||||
* Converts a non-deleted Basic wrapper into a Default ObjectView.
|
||||
* isValid check performed already in the caller function.
|
||||
*/
|
||||
fun ObjectWrapper.Basic.toObjectViewDefault(
|
||||
urlBuilder: UrlBuilder,
|
||||
fieldParser: FieldParser
|
||||
): ObjectView.Default = ObjectView.Default(
|
||||
id = id,
|
||||
name = fieldParser.getObjectName(this),
|
||||
icon = objectIcon(urlBuilder),
|
||||
types = type,
|
||||
isRelation = layout == ObjectType.Layout.RELATION
|
||||
)
|
|
@ -17,7 +17,6 @@ import com.anytypeio.anytype.presentation.sets.model.FileView
|
|||
import com.anytypeio.anytype.presentation.sets.model.ObjectView
|
||||
import com.anytypeio.anytype.presentation.sets.model.StatusView
|
||||
import com.anytypeio.anytype.presentation.sets.model.TagView
|
||||
import com.anytypeio.anytype.presentation.sets.toObjectView
|
||||
import timber.log.Timber
|
||||
|
||||
suspend fun ObjectWrapper.Basic.values(
|
||||
|
@ -324,28 +323,6 @@ suspend fun ObjectWrapper.Basic.files(
|
|||
return result
|
||||
}
|
||||
|
||||
suspend fun ObjectWrapper.Basic.objects(
|
||||
relation: Id,
|
||||
urlBuilder: UrlBuilder,
|
||||
storeOfObjects: ObjectStore,
|
||||
fieldParser: FieldParser
|
||||
) : List<ObjectView> {
|
||||
val result = mutableListOf<ObjectView>()
|
||||
|
||||
val ids : List<Id> = when(val value = map.getOrDefault(relation, null)) {
|
||||
is Id -> listOf(value)
|
||||
is List<*> -> value.typeOf()
|
||||
else -> emptyList()
|
||||
}
|
||||
ids.forEach { id ->
|
||||
val wrapper = storeOfObjects.get(id) ?: return@forEach
|
||||
if (wrapper.isValid) {
|
||||
result.add(wrapper.toObjectView(urlBuilder, fieldParser))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun ObjectWrapper.Basic.getDescriptionOrSnippet(): String? {
|
||||
return when (layout) {
|
||||
ObjectType.Layout.NOTE -> description
|
||||
|
|
|
@ -9,7 +9,6 @@ import com.anytypeio.anytype.core_models.DVViewer
|
|||
import com.anytypeio.anytype.core_models.DVViewerCardSize
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
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.Relation
|
||||
import com.anytypeio.anytype.core_models.ext.DateParser
|
||||
|
@ -36,7 +35,6 @@ import com.anytypeio.anytype.core_models.ObjectViewDetails
|
|||
import com.anytypeio.anytype.presentation.extension.getObject
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.extension.isValueRequired
|
||||
import com.anytypeio.anytype.presentation.mapper.objectIcon
|
||||
import com.anytypeio.anytype.presentation.mapper.toCheckboxView
|
||||
import com.anytypeio.anytype.presentation.mapper.toDateView
|
||||
import com.anytypeio.anytype.presentation.mapper.toGridRecordRows
|
||||
|
@ -47,13 +45,13 @@ import com.anytypeio.anytype.presentation.mapper.toTextView
|
|||
import com.anytypeio.anytype.presentation.mapper.toView
|
||||
import com.anytypeio.anytype.presentation.mapper.toViewerColumns
|
||||
import com.anytypeio.anytype.presentation.number.NumberParser
|
||||
import com.anytypeio.anytype.presentation.objects.toObjects
|
||||
import com.anytypeio.anytype.presentation.sets.buildGalleryViews
|
||||
import com.anytypeio.anytype.presentation.sets.buildListViews
|
||||
import com.anytypeio.anytype.presentation.sets.dataViewState
|
||||
import com.anytypeio.anytype.presentation.sets.filter.CreateFilterView
|
||||
import com.anytypeio.anytype.presentation.sets.model.FilterValue
|
||||
import com.anytypeio.anytype.presentation.sets.model.FilterView
|
||||
import com.anytypeio.anytype.presentation.sets.model.ObjectView
|
||||
import com.anytypeio.anytype.presentation.sets.model.SimpleRelationView
|
||||
import com.anytypeio.anytype.presentation.sets.model.StatusView
|
||||
import com.anytypeio.anytype.presentation.sets.model.TagView
|
||||
|
@ -472,34 +470,6 @@ suspend fun ObjectWrapper.Relation.toStatus(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun ObjectWrapper.Relation.toObjects(
|
||||
value: Any?,
|
||||
store: ObjectStore,
|
||||
urlBuilder: UrlBuilder,
|
||||
fieldParser: FieldParser
|
||||
) : List<ObjectView> {
|
||||
val ids = value.values<Id>()
|
||||
return buildList {
|
||||
ids.forEach { id ->
|
||||
val raw = store.get(id)?.map
|
||||
if (!raw.isNullOrEmpty()) {
|
||||
val wrapper = ObjectWrapper.Basic(raw)
|
||||
val obj = when (isDeleted) {
|
||||
true -> ObjectView.Deleted(id)
|
||||
else -> ObjectView.Default(
|
||||
id = id,
|
||||
name = fieldParser.getObjectName(wrapper),
|
||||
icon = wrapper.objectIcon(urlBuilder),
|
||||
types = type,
|
||||
isRelation = wrapper.layout == ObjectType.Layout.RELATION
|
||||
)
|
||||
}
|
||||
add(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun DVFilter.toView(
|
||||
store: ObjectStore,
|
||||
relation: ObjectWrapper.Relation,
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.ObjectViewDetails
|
|||
import com.anytypeio.anytype.presentation.extension.getOptionObject
|
||||
import com.anytypeio.anytype.presentation.extension.hasValue
|
||||
import com.anytypeio.anytype.presentation.number.NumberParser
|
||||
import com.anytypeio.anytype.presentation.objects.buildRelationValueObjectViews
|
||||
import com.anytypeio.anytype.presentation.sets.*
|
||||
import com.anytypeio.anytype.presentation.sets.model.ColumnView
|
||||
import com.anytypeio.anytype.presentation.sets.model.Viewer
|
||||
|
|
|
@ -14,7 +14,7 @@ import com.anytypeio.anytype.presentation.extension.getOptionObject
|
|||
import com.anytypeio.anytype.presentation.extension.getTypeObject
|
||||
import com.anytypeio.anytype.presentation.number.NumberParser
|
||||
import com.anytypeio.anytype.presentation.sets.buildFileViews
|
||||
import com.anytypeio.anytype.presentation.sets.buildRelationValueObjectViews
|
||||
import com.anytypeio.anytype.presentation.objects.buildRelationValueObjectViews
|
||||
import com.anytypeio.anytype.presentation.sets.model.StatusView
|
||||
import com.anytypeio.anytype.presentation.sets.model.TagView
|
||||
|
||||
|
|
|
@ -44,8 +44,8 @@ import com.anytypeio.anytype.core_models.ObjectViewDetails
|
|||
import com.anytypeio.anytype.presentation.extension.getObject
|
||||
import com.anytypeio.anytype.presentation.extension.getTypeObject
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.mapper.objectIcon
|
||||
import com.anytypeio.anytype.presentation.objects.getProperType
|
||||
import com.anytypeio.anytype.presentation.objects.toObjectViewDefault
|
||||
import com.anytypeio.anytype.presentation.relations.BasicObjectCoverWrapper
|
||||
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
|
||||
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig.ID_KEY
|
||||
|
@ -54,7 +54,6 @@ import com.anytypeio.anytype.presentation.relations.isSystemKey
|
|||
import com.anytypeio.anytype.presentation.relations.linksFeaturedRelation
|
||||
import com.anytypeio.anytype.presentation.relations.title
|
||||
import com.anytypeio.anytype.presentation.relations.view
|
||||
import com.anytypeio.anytype.presentation.sets.model.ObjectView
|
||||
import com.anytypeio.anytype.presentation.sets.model.SimpleRelationView
|
||||
import com.anytypeio.anytype.presentation.sets.model.Viewer
|
||||
import com.anytypeio.anytype.presentation.sets.state.ObjectState
|
||||
|
@ -297,21 +296,6 @@ fun List<DVFilter>.updateFormatForSubscription(relationLinks: List<RelationLink>
|
|||
fun List<SimpleRelationView>.filterHiddenRelations(): List<SimpleRelationView> =
|
||||
filter { !it.isHidden }
|
||||
|
||||
fun ObjectWrapper.Basic.toObjectView(urlBuilder: UrlBuilder, fieldParser: FieldParser): ObjectView = when (isDeleted) {
|
||||
true -> ObjectView.Deleted(id)
|
||||
else -> toObjectViewDefault(urlBuilder, fieldParser)
|
||||
}
|
||||
|
||||
fun ObjectWrapper.Basic.toObjectViewDefault(urlBuilder: UrlBuilder, fieldParser: FieldParser): ObjectView.Default {
|
||||
return ObjectView.Default(
|
||||
id = id,
|
||||
name = fieldParser.getObjectName(this),
|
||||
icon = this.objectIcon(builder = urlBuilder),
|
||||
types = type,
|
||||
isRelation = layout == ObjectType.Layout.RELATION
|
||||
)
|
||||
}
|
||||
|
||||
fun List<DVFilter>.updateFilters(updates: List<DVFilterUpdate>): List<DVFilter> {
|
||||
val filters = this.toMutableList()
|
||||
updates.forEach { update ->
|
||||
|
|
|
@ -15,12 +15,11 @@ import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
|||
import com.anytypeio.anytype.domain.primitives.FieldParser
|
||||
import com.anytypeio.anytype.core_models.ObjectViewDetails
|
||||
import com.anytypeio.anytype.presentation.extension.getFileObject
|
||||
import com.anytypeio.anytype.presentation.extension.getObject
|
||||
import com.anytypeio.anytype.presentation.objects.buildObjectViews
|
||||
import com.anytypeio.anytype.presentation.relations.getDateRelationFormat
|
||||
import com.anytypeio.anytype.presentation.sets.model.CellView
|
||||
import com.anytypeio.anytype.presentation.sets.model.ColumnView
|
||||
import com.anytypeio.anytype.presentation.sets.model.FileView
|
||||
import com.anytypeio.anytype.presentation.sets.model.ObjectView
|
||||
import com.anytypeio.anytype.presentation.sets.model.StatusView
|
||||
import com.anytypeio.anytype.presentation.sets.model.TagView
|
||||
import com.anytypeio.anytype.presentation.sets.model.Viewer
|
||||
|
@ -288,91 +287,6 @@ private fun ObjectWrapper.File.toView() : FileView {
|
|||
)
|
||||
}
|
||||
|
||||
fun Struct.buildRelationValueObjectViews(
|
||||
relationKey: Id,
|
||||
details: ObjectViewDetails,
|
||||
builder: UrlBuilder,
|
||||
fieldParser: FieldParser
|
||||
): List<ObjectView> {
|
||||
val objects = mutableListOf<ObjectView>()
|
||||
val value = this.getOrDefault(relationKey, null)
|
||||
if (value is Id) {
|
||||
val wrapper = details.getObject(value)
|
||||
if (wrapper != null) {
|
||||
objects.add(wrapper.toObjectView(urlBuilder = builder, fieldParser = fieldParser))
|
||||
}
|
||||
} else if (value is List<*>) {
|
||||
value.typeOf<Id>().forEach { id ->
|
||||
val wrapper = details.getObject(id)
|
||||
if (wrapper != null) {
|
||||
objects.add(wrapper.toObjectView(urlBuilder = builder, fieldParser = fieldParser))
|
||||
}
|
||||
}
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
suspend fun Struct.buildObjectViews(
|
||||
columnKey: Id,
|
||||
store: ObjectStore,
|
||||
builder: UrlBuilder,
|
||||
withIcon: Boolean = true,
|
||||
fieldParser: FieldParser
|
||||
): List<ObjectView> {
|
||||
val objects = mutableListOf<ObjectView>()
|
||||
val value = this.getOrDefault(columnKey, null)
|
||||
if (value is Id) {
|
||||
val wrapper = store.get(value)
|
||||
if (wrapper != null) {
|
||||
if (wrapper.isDeleted == true) {
|
||||
objects.add(ObjectView.Deleted(id = value))
|
||||
} else {
|
||||
val icon = if (withIcon) {
|
||||
wrapper.objectIcon(builder)
|
||||
} else {
|
||||
ObjectIcon.None
|
||||
}
|
||||
objects.add(
|
||||
ObjectView.Default(
|
||||
id = value,
|
||||
name = fieldParser.getObjectName(wrapper),
|
||||
icon = icon,
|
||||
types = wrapper.type
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Timber.w("Object was missing in object store: $value")
|
||||
}
|
||||
} else if (value is List<*>) {
|
||||
value.typeOf<Id>().forEach { id ->
|
||||
val wrapper = store.get(id)
|
||||
if (wrapper != null) {
|
||||
if (wrapper.isDeleted == true) {
|
||||
objects.add(ObjectView.Deleted(id = id))
|
||||
} else {
|
||||
val icon = if (withIcon) {
|
||||
wrapper.objectIcon(builder)
|
||||
} else {
|
||||
ObjectIcon.None
|
||||
}
|
||||
objects.add(
|
||||
ObjectView.Default(
|
||||
id = id,
|
||||
name = fieldParser.getObjectName(wrapper),
|
||||
icon = icon,
|
||||
types = wrapper.type
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Timber.w("Object was missing in object store: $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
fun Struct.buildTagViews(
|
||||
options: List<ObjectWrapper.Option>,
|
||||
relationKey: Key
|
||||
|
|
|
@ -11,16 +11,22 @@ data class StatusView(val id: String, val status: String, val color: String)
|
|||
sealed class ObjectView {
|
||||
|
||||
abstract val id: Id
|
||||
abstract val icon: ObjectIcon
|
||||
abstract val name: String
|
||||
|
||||
data class Default(
|
||||
override val id: String,
|
||||
val name: String,
|
||||
val icon: ObjectIcon,
|
||||
override val name: String,
|
||||
override val icon: ObjectIcon,
|
||||
val types: List<String> = emptyList(),
|
||||
val isRelation: Boolean = false
|
||||
) : ObjectView()
|
||||
|
||||
data class Deleted(override val id: String) : ObjectView()
|
||||
data class Deleted(
|
||||
override val id: String,
|
||||
override val name: String,
|
||||
override val icon: ObjectIcon = ObjectIcon.Deleted,
|
||||
) : ObjectView()
|
||||
}
|
||||
|
||||
data class FileView(
|
||||
|
|
|
@ -56,8 +56,7 @@ include ':app',
|
|||
':test:android-utils',
|
||||
':test:utils',
|
||||
':test:core-models-stub',
|
||||
':libs',
|
||||
':feature-date'
|
||||
':libs'
|
||||
|
||||
include ':feature-ui-settings'
|
||||
include ':crash-reporting'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue