1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-07 21:37:02 +09:00

DROID-3375 New settings | Main screen, base ui (#2129)

This commit is contained in:
Konstantin Ivanov 2025-03-01 14:20:59 +01:00 committed by GitHub
parent 4bc1e060f3
commit 77704b87d2
Signed by: github
GPG key ID: B5690EEEBB952194
16 changed files with 1144 additions and 1 deletions

View file

@ -271,6 +271,7 @@ sealed class ObjectWrapper {
val id: Id by default
val name: String? by default
val description: String? = getSingleValue(Relations.DESCRIPTION)
val iconImage: String? get() = getSingleValue(Relations.ICON_IMAGE)
val iconOption: Double? by default

View file

@ -20,11 +20,12 @@ import com.anytypeio.anytype.core_ui.views.Title1
@Composable
fun Section(
modifier: Modifier = Modifier,
title: String,
color: Color = colorResource(id = R.color.text_secondary),
textPaddingStart: Dp = 20.dp
) {
Box(modifier = Modifier
Box(modifier = modifier
.height(52.dp)
.fillMaxWidth()) {
Text(

View file

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="33dp"
android:height="32dp"
android:viewportWidth="33"
android:viewportHeight="32">
<path
android:pathData="M10.75,10a3.75,4 0,1 0,7.5 0a3.75,4 0,1 0,-7.5 0z"
android:fillColor="@color/glyph_button"/>
<path
android:pathData="M17.326,23.5C17.105,22.874 16.984,22.201 16.984,21.5C16.984,19.669 17.804,18.03 19.097,16.93C17.793,16.028 16.209,15.5 14.5,15.5C10.564,15.5 7.288,18.303 6.584,22.008C6.43,22.822 7.116,23.5 7.945,23.5H17.326Z"
android:fillColor="@color/glyph_button"
android:fillType="evenOdd"/>
<path
android:pathData="M22.984,26C25.478,26 27.5,23.978 27.5,21.484C27.5,18.99 25.478,16.969 22.984,16.969C20.49,16.969 18.469,18.99 18.469,21.484C18.469,23.978 20.49,26 22.984,26ZM22.484,19.234C22.484,18.958 22.708,18.734 22.984,18.734C23.26,18.734 23.484,18.958 23.484,19.234V20.984H25.234C25.51,20.984 25.734,21.208 25.734,21.484C25.734,21.76 25.51,21.984 25.234,21.984H23.484V23.734C23.484,24.01 23.26,24.234 22.984,24.234C22.708,24.234 22.484,24.01 22.484,23.734V21.984H20.734C20.458,21.984 20.234,21.76 20.234,21.484C20.234,21.208 20.458,20.984 20.734,20.984H22.484V19.234Z"
android:fillColor="@color/glyph_button"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="24dp"
android:viewportWidth="8"
android:viewportHeight="24">
<path
android:pathData="M0.725,18.28C0.425,17.987 0.425,17.513 0.725,17.22L5.824,12.25L0.725,7.28C0.425,6.987 0.425,6.513 0.725,6.22C1.026,5.927 1.513,5.927 1.813,6.22L8,12.25L1.813,18.28C1.513,18.573 1.026,18.573 0.725,18.28Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.027,13.299C13.239,14.396 14,15.982 14,17.746V18.5H21.25C21.664,18.5 22,18.164 22,17.75V16.5C22,13.462 19.538,11 16.5,11C14.657,11 13.025,11.907 12.027,13.299Z"
android:fillColor="@color/glyph_selected"
android:fillType="evenOdd"/>
<path
android:pathData="M2,18.5C2,15.462 4.462,13 7.5,13C10.537,13 13,15.462 13,18.5V19.75C13,20.164 12.664,20.5 12.25,20.5H2.75C2.336,20.5 2,20.164 2,19.75V18.5Z"
android:fillColor="@color/glyph_selected"/>
<path
android:pathData="M13.75,6.5a2.75,3 0,1 0,5.5 0a2.75,3 0,1 0,-5.5 0z"
android:fillColor="@color/glyph_selected"/>
<path
android:pathData="M4.75,8.5a2.75,3 0,1 0,5.5 0a2.75,3 0,1 0,-5.5 0z"
android:fillColor="@color/glyph_selected"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4.48,19.524C4.123,19.168 3.898,18.794 3.805,18.402C3.708,18.007 3.717,17.628 3.831,17.264C3.945,16.894 4.143,16.568 4.423,16.288C4.6,16.111 4.787,15.976 4.984,15.883C5.178,15.786 5.401,15.708 5.654,15.649C5.893,15.59 6.039,15.493 6.091,15.358C6.146,15.219 6.11,15.086 5.982,14.958L4.059,13.036C3.55,12.527 3.297,12.01 3.301,11.488C3.304,10.958 3.566,10.433 4.085,9.913L5.472,8.526C5.597,8.402 5.729,8.367 5.867,8.422C6.006,8.471 6.105,8.615 6.163,8.854C6.222,9.113 6.298,9.342 6.392,9.539C6.486,9.73 6.621,9.913 6.797,10.09C7.078,10.371 7.403,10.568 7.774,10.682C8.141,10.793 8.52,10.802 8.912,10.708C9.306,10.611 9.681,10.386 10.034,10.033C10.387,9.68 10.61,9.307 10.704,8.916C10.801,8.521 10.792,8.142 10.678,7.778C10.564,7.408 10.366,7.082 10.086,6.802C9.909,6.625 9.726,6.49 9.535,6.396C9.341,6.299 9.114,6.221 8.854,6.163C8.616,6.104 8.47,6.007 8.418,5.872C8.366,5.73 8.403,5.596 8.527,5.472L9.914,4.085C10.43,3.568 10.953,3.309 11.483,3.305C12.01,3.298 12.527,3.549 13.036,4.059L14.959,5.981C15.087,6.109 15.219,6.147 15.354,6.095C15.492,6.04 15.589,5.894 15.644,5.659C15.707,5.402 15.786,5.177 15.884,4.983C15.977,4.786 16.11,4.601 16.284,4.427C16.567,4.143 16.893,3.946 17.26,3.835C17.627,3.717 18.008,3.707 18.403,3.804C18.794,3.898 19.169,4.123 19.525,4.479C19.878,4.833 20.104,5.207 20.201,5.602C20.294,5.993 20.284,6.374 20.169,6.744C20.055,7.108 19.856,7.432 19.572,7.716C19.399,7.889 19.215,8.024 19.021,8.121C18.824,8.215 18.597,8.293 18.341,8.355C18.105,8.41 17.96,8.507 17.904,8.646C17.852,8.781 17.89,8.913 18.019,9.041L19.941,10.963C20.453,11.475 20.706,11.995 20.699,12.521C20.692,13.048 20.431,13.569 19.915,14.085L18.528,15.472C18.403,15.597 18.271,15.635 18.133,15.587C17.998,15.535 17.901,15.389 17.842,15.15C17.783,14.89 17.705,14.663 17.608,14.469C17.515,14.279 17.38,14.095 17.203,13.919C16.923,13.638 16.597,13.441 16.226,13.327C15.859,13.209 15.48,13.2 15.089,13.301C14.697,13.394 14.325,13.618 13.972,13.971C13.618,14.324 13.393,14.698 13.296,15.093C13.199,15.481 13.208,15.86 13.322,16.231C13.437,16.601 13.634,16.927 13.915,17.207C14.091,17.384 14.275,17.519 14.465,17.613C14.663,17.706 14.891,17.782 15.151,17.841C15.39,17.9 15.534,17.999 15.582,18.137C15.634,18.272 15.598,18.402 15.473,18.527L14.086,19.914C13.566,20.434 13.043,20.697 12.517,20.704C11.994,20.707 11.476,20.453 10.964,19.94L9.042,18.018C8.913,17.89 8.78,17.853 8.641,17.909C8.506,17.961 8.409,18.106 8.351,18.345C8.292,18.598 8.215,18.823 8.122,19.02C8.025,19.214 7.888,19.4 7.712,19.576C7.431,19.857 7.107,20.056 6.74,20.174C6.373,20.285 5.994,20.293 5.602,20.2C5.208,20.103 4.833,19.878 4.48,19.524Z"
android:fillColor="@color/glyph_selected"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="33dp"
android:height="32dp"
android:viewportWidth="33"
android:viewportHeight="32">
<path
android:pathData="M13.5,24.5H8.5C8.224,24.5 8,24.276 8,24V19C8,18.724 8.224,18.5 8.5,18.5H13.5C13.776,18.5 14,18.724 14,19V24C14,24.276 13.776,24.5 13.5,24.5ZM8.5,26C7.395,26 6.5,25.105 6.5,24L6.5,19C6.5,17.895 7.395,17 8.5,17H13.5C14.605,17 15.5,17.895 15.5,19V24C15.5,25.105 14.605,26 13.5,26H8.5ZM13.5,13.5H8.5C8.224,13.5 8,13.276 8,13V8C8,7.724 8.224,7.5 8.5,7.5H13.5C13.776,7.5 14,7.724 14,8V13C14,13.276 13.776,13.5 13.5,13.5ZM8.5,15C7.395,15 6.5,14.105 6.5,13V8C6.5,6.895 7.395,6 8.5,6H13.5C14.605,6 15.5,6.895 15.5,8V13C15.5,14.105 14.605,15 13.5,15H8.5ZM19.5,24.5L24.5,24.5C24.776,24.5 25,24.276 25,24V19C25,18.724 24.776,18.5 24.5,18.5H19.5C19.224,18.5 19,18.724 19,19V24C19,24.276 19.224,24.5 19.5,24.5ZM17.5,24C17.5,25.105 18.395,26 19.5,26L24.5,26C25.605,26 26.5,25.105 26.5,24V19C26.5,17.895 25.605,17 24.5,17H19.5C18.395,17 17.5,17.895 17.5,19V24ZM17.5,14.5C17.5,14.776 17.724,15 18,15H20C20.276,15 20.5,14.776 20.5,14.5V12.5C20.5,12.224 20.276,12 20,12H18C17.724,12 17.5,12.224 17.5,12.5V14.5ZM10,23C9.724,23 9.5,22.776 9.5,22.5V20.5C9.5,20.224 9.724,20 10,20H12C12.276,20 12.5,20.224 12.5,20.5V22.5C12.5,22.776 12.276,23 12,23H10ZM20.5,22.5C20.5,22.776 20.724,23 21,23L23,23C23.276,23 23.5,22.776 23.5,22.5V20.5C23.5,20.224 23.276,20 23,20H21C20.724,20 20.5,20.224 20.5,20.5V22.5ZM10,12C9.724,12 9.5,11.776 9.5,11.5V9.5C9.5,9.224 9.724,9 10,9H12C12.276,9 12.5,9.224 12.5,9.5V11.5C12.5,11.776 12.276,12 12,12H10ZM20.5,11.5C20.5,11.776 20.724,12 21,12H23C23.276,12 23.5,11.776 23.5,11.5L23.5,9.5C23.5,9.224 23.276,9 23,9L21,9C20.724,9 20.5,9.224 20.5,9.5V11.5ZM18,9C17.724,9 17.5,8.776 17.5,8.5V6.5C17.5,6.224 17.724,6 18,6H20C20.276,6 20.5,6.224 20.5,6.5V8.5C20.5,8.776 20.276,9 20,9H18ZM23.5,14.5C23.5,14.776 23.724,15 24,15H26C26.276,15 26.5,14.776 26.5,14.5V12.5C26.5,12.224 26.276,12 26,12H24C23.724,12 23.5,12.224 23.5,12.5V14.5ZM24,9C23.724,9 23.5,8.776 23.5,8.5V6.5C23.5,6.224 23.724,6 24,6H26C26.276,6 26.5,6.224 26.5,6.5V8.5C26.5,8.776 26.276,9 26,9H24Z"
android:fillColor="@color/glyph_button"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11.991,13.3C6.948,13.3 3,11.448 3,9.088C3,6.808 6.948,5 11.991,5C17.043,5 21,6.808 21,9.088C21,11.448 17.043,13.3 11.991,13.3ZM11.991,19C6.913,19 3,16.684 3,13.844V11.751C4.733,13.496 8.183,14.52 11.991,14.52C15.826,14.52 19.275,13.487 21,11.751V13.844C21,16.684 17.078,19 11.991,19Z"
android:fillColor="@color/glyph_selected"/>
</vector>

View file

@ -32,6 +32,7 @@ dependencies {
implementation libs.appcompat
implementation libs.compose
implementation libs.composeMaterial3
implementation libs.composeFoundation
implementation libs.composeMaterial
implementation libs.composeToolingPreview

View file

@ -0,0 +1,134 @@
package com.anytypeio.anytype.ui_settings.space.new_settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Divider
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.features.SpaceIconView
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Caption1Medium
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
import com.anytypeio.anytype.presentation.spaces.UiEvent
import com.anytypeio.anytype.ui_settings.R
import timber.log.Timber
@Composable
fun NewSpaceIcon(
icon: SpaceIconView,
modifier: Modifier = Modifier,
uiEvent: (UiEvent) -> Unit,
isEditEnabled: Boolean
) {
val context = LocalContext.current
val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri ->
if (uri != null) {
uiEvent(UiEvent.OnSpaceImagePicked(uri.toString()))
} else {
Timber.w("Uri was null after picking image")
}
}
)
val isSpaceIconMenuExpanded = remember {
mutableStateOf(false)
}
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
SpaceIconView(
modifier = Modifier.size(112.dp),
icon = icon,
onSpaceIconClick = {
if (isEditEnabled) {
isSpaceIconMenuExpanded.value = !isSpaceIconMenuExpanded.value
}
}
)
Text(
modifier = Modifier
.padding(top = 8.dp)
.noRippleThrottledClickable {
if (isEditEnabled) {
isSpaceIconMenuExpanded.value = !isSpaceIconMenuExpanded.value
}
},
text = stringResource(R.string.space_settings_icon_title),
style = Caption1Medium
)
MaterialTheme(
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(10.dp))
) {
DropdownMenu(
modifier = Modifier
.background(
shape = RoundedCornerShape(10.dp),
color = colorResource(id = R.color.background_secondary)
),
expanded = isSpaceIconMenuExpanded.value,
offset = DpOffset(x = 0.dp, y = 6.dp),
onDismissRequest = {
isSpaceIconMenuExpanded.value = false
}
) {
if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(context)) {
Divider(
thickness = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
DropdownMenuItem(
onClick = {
singlePhotoPickerLauncher.launch(
PickVisualMediaRequest(
ActivityResultContracts.PickVisualMedia.ImageOnly
)
)
isSpaceIconMenuExpanded.value = false
},
) {
Text(
text = stringResource(R.string.space_settings_apply_upload_image),
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
}
}
if (icon is SpaceIconView.Image) {
DropdownMenuItem(
onClick = {
uiEvent(UiEvent.IconMenu.OnRemoveIconClicked)
isSpaceIconMenuExpanded.value = false
},
) {
Text(
text = stringResource(R.string.remove_image),
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
}
}
}
}
}
}

View file

@ -0,0 +1,499 @@
package com.anytypeio.anytype.ui_settings.space.new_settings
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
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.material.ExperimentalMaterialApi
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.extensions.light
import com.anytypeio.anytype.core_ui.foundation.Section
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyBold
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular
import com.anytypeio.anytype.core_ui.widgets.ListWidgetObjectIcon
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.spaces.UiEvent
import com.anytypeio.anytype.presentation.spaces.UiSpaceSettingsItem
import com.anytypeio.anytype.ui_settings.R
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.filter
@Composable
fun MembersItem(
modifier: Modifier = Modifier,
item: UiSpaceSettingsItem.Members
) {
BaseButton(
modifier = modifier,
title = stringResource(id = R.string.space_settings_members_button_members),
icon = R.drawable.ic_members_24,
count = item.count
)
}
@Composable
fun ObjectTypesItem(
modifier: Modifier = Modifier
) {
BaseButton(
modifier = modifier,
title = stringResource(id = R.string.space_settings_types_button),
icon = R.drawable.ic_object_types_24,
)
}
@Composable
fun DefaultTypeItem(
modifier: Modifier = Modifier,
name: String,
icon: ObjectIcon
) {
Row(
modifier = modifier
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 20.dp)
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f),
text = stringResource(id = R.string.space_settings_default_type_button),
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
)
ListWidgetObjectIcon(
modifier = Modifier,
iconSize = 20.dp,
icon = icon
)
Text(
modifier = Modifier.padding(start = 8.dp),
text = name.take(10),
style = PreviewTitle1Regular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary),
)
Image(
painter = painterResource(id = R.drawable.ic_disclosure_8_24),
contentDescription = "Members icon",
modifier = Modifier.size(24.dp)
)
}
}
@Composable
fun WallpaperItem(
modifier: Modifier = Modifier,
item: UiSpaceSettingsItem.Wallpapers
) {
Row(
modifier = modifier
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 20.dp)
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f),
text = stringResource(id = R.string.space_settings_wallpaper_button),
style = PreviewTitle1Regular,
color = colorResource(id = R.color.text_primary),
)
Box(
modifier = Modifier
.size(20.dp)
.background(
color = light(item.color),
shape = RoundedCornerShape(4.dp)
)
.padding(horizontal = 6.dp),
)
Image(
painter = painterResource(id = R.drawable.ic_disclosure_8_24),
contentDescription = "Members icon",
modifier = Modifier.size(24.dp)
)
}
}
@Composable
fun SpaceInfoItem(
modifier: Modifier = Modifier
) {
BaseButton(
modifier = modifier,
title = stringResource(id = R.string.space_settings_space_info_button),
)
}
@Composable
fun DeleteSpaceItem(
modifier: Modifier = Modifier
) {
BaseButton(
modifier = modifier,
title = stringResource(id = R.string.space_settings_delete_space_button),
textColor = R.color.palette_dark_red
)
}
@Composable
fun BaseButton(
modifier: Modifier = Modifier,
icon: Int? = null,
title: String,
count: Int? = null,
textColor: Int = R.color.text_primary,
) {
Row(
modifier = modifier
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 20.dp)
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (icon != null) {
Image(
painter = painterResource(id = icon),
contentDescription = "Members icon",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
}
Text(
modifier = Modifier.weight(1f),
text = title,
style = PreviewTitle1Regular,
color = colorResource(id = textColor),
)
if (count != null) {
Text(
modifier = Modifier
.wrapContentSize()
.background(
color = colorResource(id = R.color.transparent_active),
shape = CircleShape
)
.padding(horizontal = 6.dp),
text = "$count",
textAlign = TextAlign.Center,
style = Caption1Regular,
color = colorResource(id = R.color.text_white),
)
}
Image(
painter = painterResource(id = R.drawable.ic_disclosure_8_24),
contentDescription = "Members icon",
modifier = Modifier.size(24.dp)
)
}
}
@OptIn(FlowPreview::class)
@Composable
fun NewSpaceNameBlock(
modifier: Modifier = Modifier,
name: String,
onNameSet: (String) -> Unit,
isEditEnabled: Boolean
) {
val nameValue = remember { mutableStateOf(name) }
val focusManager = LocalFocusManager.current
LaunchedEffect(nameValue.value) {
snapshotFlow { nameValue.value }
.debounce(300L)
.dropWhile { input -> input == name }
.distinctUntilChanged()
.filter { it.isNotEmpty() }
.collect { query ->
onNameSet(query)
}
}
Column(modifier = modifier) {
Text(
text = stringResource(id = R.string.space_name),
style = BodyCalloutRegular.copy(color = colorResource(id = R.color.text_primary)),
color = colorResource(id = R.color.text_secondary)
)
NewSettingsTextField(
value = nameValue.value,
textStyle = BodyBold.copy(color = colorResource(id = R.color.text_primary)),
onValueChange = {
nameValue.value = it
},
keyboardActions = KeyboardActions(
onDone = {
focusManager.clearFocus()
}
),
placeholderText = stringResource(id = R.string.space_settings_space_name_hint),
isEditEnabled = isEditEnabled
)
}
}
@OptIn(FlowPreview::class)
@Composable
fun NewSpaceDescriptionBlock(
modifier: Modifier = Modifier,
description: String,
onDescriptionSet: (String) -> Unit,
isEditEnabled: Boolean
) {
val descriptionValue = remember { mutableStateOf(description) }
val focusManager = LocalFocusManager.current
LaunchedEffect(descriptionValue.value) {
snapshotFlow { descriptionValue.value }
.debounce(300L)
.dropWhile { input -> input == description }
.distinctUntilChanged()
.filter { it.isNotEmpty() }
.collect { query ->
onDescriptionSet(query)
}
}
Column(modifier = modifier) {
Text(
text = stringResource(id = R.string.space_settings_space_description_hint),
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_secondary)
)
NewSettingsTextField(
textStyle = BodyRegular.copy(color = colorResource(id = R.color.text_primary)),
value = descriptionValue.value,
onValueChange = {
descriptionValue.value = it
},
keyboardActions = KeyboardActions(
onDone = {
focusManager.clearFocus()
}
),
placeholderText = stringResource(id = R.string.space_settings_space_description_hint),
isEditEnabled = isEditEnabled
)
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NewSettingsTextField(
value: String,
onValueChange: (String) -> Unit,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardActions: KeyboardActions = KeyboardActions.Default,
textStyle: TextStyle = BodyBold,
placeholderText: String,
isEditEnabled: Boolean
) {
BasicTextField(
value = value,
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth(),
onValueChange = onValueChange,
enabled = isEditEnabled,
readOnly = !isEditEnabled,
textStyle = textStyle,
cursorBrush = SolidColor(colorResource(id = R.color.orange)),
visualTransformation = visualTransformation,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done
),
keyboardActions = keyboardActions,
interactionSource = remember { MutableInteractionSource() },
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = value,
visualTransformation = visualTransformation,
innerTextField = innerTextField,
label = null,
leadingIcon = null,
trailingIcon = null,
singleLine = true,
enabled = true,
isError = false,
placeholder = {
androidx.compose.material.Text(
style = textStyle,
text = placeholderText,
color = colorResource(id = R.color.text_tertiary)
)
},
interactionSource = remember { MutableInteractionSource() },
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = colorResource(id = R.color.text_primary),
backgroundColor = Color.Transparent,
disabledBorderColor = Color.Transparent,
errorBorderColor = Color.Transparent,
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
placeholderColor = colorResource(id = R.color.text_tertiary),
cursorColor = colorResource(id = R.color.orange)
),
contentPadding = PaddingValues(),
border = {}
)
}
)
}
@Composable
fun MultiplayerButtons(
modifier: Modifier = Modifier,
uiEvent: (UiEvent) -> Unit
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Column(
modifier = Modifier
.noRippleThrottledClickable {
uiEvent(UiEvent.OnInviteClicked)
}
.weight(1f)
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 14.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier.size(32.dp),
painter = painterResource(id = R.drawable.ic_add_member_32),
contentDescription = "Invite new member icon"
)
Text(
modifier = Modifier.wrapContentSize(),
text = stringResource(id = R.string.space_settings_invite),
style = Caption1Regular,
color = colorResource(id = R.color.text_primary)
)
}
Column(
modifier = Modifier
.noRippleThrottledClickable {
uiEvent(UiEvent.OnQrCodeClicked)
}
.weight(1f)
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 14.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier.size(32.dp),
painter = painterResource(id = R.drawable.ic_qr_code_32),
contentDescription = "Share QR code icon"
)
Text(
modifier = Modifier.wrapContentSize(),
text = stringResource(id = R.string.space_settings_qrcode),
style = Caption1Regular,
color = colorResource(id = R.color.text_primary)
)
}
}
}
@Composable
fun SpaceSettingsSection(
modifier: Modifier = Modifier,
item: UiSpaceSettingsItem.Section
) {
val text = when (item) {
UiSpaceSettingsItem.Section.Collaboration ->
stringResource(id = R.string.space_settings_section_collaboration)
UiSpaceSettingsItem.Section.ContentModel ->
stringResource(id = R.string.space_settings_section_content_model)
UiSpaceSettingsItem.Section.DataManagement ->
stringResource(id = R.string.space_settings_section_data_management)
UiSpaceSettingsItem.Section.Misc ->
stringResource(id = R.string.space_settings_section_misc)
UiSpaceSettingsItem.Section.Preferences ->
stringResource(id = R.string.space_settings_section_preferences)
}
Section(
modifier = modifier,
title = text,
textPaddingStart = 0.dp
)
}

View file

@ -0,0 +1,305 @@
package com.anytypeio.anytype.ui_settings.space.new_settings
import android.os.Build
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
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.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Scaffold
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.layout.ContentScale
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_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Medium
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.presentation.spaces.UiEvent
import com.anytypeio.anytype.presentation.spaces.UiSpaceSettingsItem
import com.anytypeio.anytype.presentation.spaces.UiSpaceSettingsState
import com.anytypeio.anytype.ui_settings.R
@Composable
fun SpaceSettingsContainer(
uiState: UiSpaceSettingsState,
uiEvent: (UiEvent) -> Unit
) {
if (uiState is UiSpaceSettingsState.SpaceSettings) {
NewSpaceSettingsScreen(
uiState = uiState,
uiEvent = uiEvent
)
}
}
@Composable
fun NewSpaceSettingsScreen(
uiState: UiSpaceSettingsState.SpaceSettings,
uiEvent: (UiEvent) -> Unit
) {
// Get the initial values from your uiState items.
val initialName = uiState.items.filterIsInstance<UiSpaceSettingsItem.Name>()
.firstOrNull()?.name ?: ""
val initialDescription = uiState.items.filterIsInstance<UiSpaceSettingsItem.Description>()
.firstOrNull()?.description ?: ""
// Keep state of the current (edited) values.
var nameInput by remember { mutableStateOf(initialName) }
var descriptionInput by remember { mutableStateOf(initialDescription) }
// Compare against the initial values to know if something has changed.
val isDirty = nameInput != initialName || descriptionInput != initialDescription
Scaffold(
modifier = Modifier.fillMaxWidth(),
containerColor = colorResource(id = R.color.background_primary),
topBar = {
Box(
modifier = if (Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
Modifier
.windowInsetsPadding(WindowInsets.statusBars)
.fillMaxWidth()
.height(48.dp)
else
Modifier
.fillMaxWidth()
.height(48.dp)
) {
Image(
painter = painterResource(R.drawable.ic_home_top_toolbar_back),
contentDescription = "Back button",
contentScale = ContentScale.Inside,
modifier = Modifier
.padding(start = 4.dp)
.size(48.dp)
.align(Alignment.CenterStart)
.noRippleClickable {
uiEvent(UiEvent.OnBackPressed)
}
)
Box(
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight()
.align(Alignment.CenterEnd)
.clickable(enabled = isDirty) {
uiEvent(UiEvent.OnSavedClicked(nameInput, descriptionInput))
},
contentAlignment = Alignment.Center
) {
Text(
modifier = Modifier
.wrapContentSize()
.padding(horizontal = 16.dp),
text = stringResource(R.string.space_settings_save_button),
style = PreviewTitle1Medium,
color = if (isDirty) {
colorResource(id = R.color.text_primary)
} else {
colorResource(id = R.color.text_tertiary)
}
)
}
}
},
content = { paddingValues ->
val contentModifier =
if (Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
Modifier
.windowInsetsPadding(WindowInsets.navigationBars)
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding())
else
Modifier
.fillMaxSize()
.padding(paddingValues)
val lazyListState = rememberLazyListState()
LazyColumn(
modifier = contentModifier
.padding(horizontal = 16.dp),
state = lazyListState,
horizontalAlignment = Alignment.CenterHorizontally
) {
uiState.items.forEach { item ->
when (item) {
is UiSpaceSettingsItem.Icon -> {
item {
NewSpaceIcon(
modifier = Modifier
.fillMaxWidth()
.animateItem(),
icon = item.icon,
isEditEnabled = uiState.isEditEnabled,
uiEvent = uiEvent
)
}
}
is UiSpaceSettingsItem.Name -> {
item {
NewSpaceNameBlock(
modifier = Modifier
.fillMaxWidth()
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 12.dp, horizontal = 16.dp)
.animateItem(),
name = nameInput,
onNameSet = { newName ->
nameInput = newName
},
isEditEnabled = uiState.isEditEnabled
)
}
}
is UiSpaceSettingsItem.Description -> {
item {
NewSpaceDescriptionBlock(
modifier = Modifier
.fillMaxWidth()
.border(
shape = RoundedCornerShape(16.dp),
width = 0.5.dp,
color = colorResource(id = R.color.shape_primary)
)
.padding(vertical = 12.dp, horizontal = 16.dp)
.animateItem(),
isEditEnabled = uiState.isEditEnabled,
description = descriptionInput,
onDescriptionSet = { newDescription ->
descriptionInput = newDescription
}
)
}
}
UiSpaceSettingsItem.Multiplayer -> {
item {
MultiplayerButtons(
modifier = Modifier
.fillMaxWidth(),
uiEvent = uiEvent
)
}
}
is UiSpaceSettingsItem.Chat -> TODO()
is UiSpaceSettingsItem.DefaultObjectType -> {
item {
DefaultTypeItem(
modifier = Modifier
.fillMaxWidth()
.animateItem(),
name = item.name,
icon = item.icon
)
}
}
UiSpaceSettingsItem.DeleteSpace -> {
item {
DeleteSpaceItem(
modifier = Modifier
.fillMaxWidth()
.animateItem()
)
}
}
is UiSpaceSettingsItem.Members -> {
item {
MembersItem(
modifier = Modifier
.fillMaxWidth()
.animateItem(),
item = item
)
}
}
UiSpaceSettingsItem.ObjectTypes -> {
item {
ObjectTypesItem(
modifier = Modifier
.fillMaxWidth()
.animateItem()
)
}
}
is UiSpaceSettingsItem.RemoteStorage -> TODO()
is UiSpaceSettingsItem.Section -> {
item {
SpaceSettingsSection(
modifier = Modifier
.fillMaxWidth()
.animateItem(),
item = item
)
}
}
UiSpaceSettingsItem.SpaceInfo -> {
item {
SpaceInfoItem(
modifier = Modifier
.fillMaxWidth()
.animateItem()
)
}
}
is UiSpaceSettingsItem.Wallpapers -> {
item {
WallpaperItem(
modifier = Modifier
.fillMaxWidth()
.animateItem(),
item = item
)
}
}
is UiSpaceSettingsItem.Spacer -> {
item {
Spacer(modifier = Modifier.height(item.height.dp))
}
}
}
}
}
}
)
}

View file

@ -0,0 +1,51 @@
package com.anytypeio.anytype.ui_settings.space.new_settings
import androidx.compose.runtime.Composable
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
import com.anytypeio.anytype.presentation.spaces.UiSpaceSettingsItem
import com.anytypeio.anytype.presentation.spaces.UiSpaceSettingsState
@Composable
@DefaultPreviews
fun NewSpaceSettingsScreenPreview() {
NewSpaceSettingsScreen(
uiState = UiSpaceSettingsState.SpaceSettings(
items = listOf(
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.Icon(SpaceIconView.Placeholder()),
UiSpaceSettingsItem.Spacer(height = 16),
UiSpaceSettingsItem.Name("Dream team"),
UiSpaceSettingsItem.Spacer(height = 12),
UiSpaceSettingsItem.Description("This is a dream team space"),
UiSpaceSettingsItem.Spacer(height = 12),
UiSpaceSettingsItem.Multiplayer,
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.Section.Collaboration,
UiSpaceSettingsItem.Members(5),
UiSpaceSettingsItem.Section.ContentModel,
UiSpaceSettingsItem.ObjectTypes,
UiSpaceSettingsItem.Section.Preferences,
UiSpaceSettingsItem.DefaultObjectType(
name = "Taskwithveryverlylongname",
icon = ObjectIcon.Empty.ObjectType
),
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.Wallpapers(color = ThemeColor.TEAL),
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.Section.DataManagement,
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.Section.Misc,
UiSpaceSettingsItem.SpaceInfo,
UiSpaceSettingsItem.Spacer(height = 8),
UiSpaceSettingsItem.DeleteSpace,
UiSpaceSettingsItem.Spacer(height = 32),
),
isEditEnabled = true
),
uiEvent = {},
)
}

View file

@ -1943,4 +1943,23 @@ Please provide specific details of your needs here.</string>
<string name="field_menu_add_to_type">Add to the current type</string>
<string name="field_menu_remove_from_object">Remove from the object</string>
<string name="space_settings_space_name_hint">Name</string>
<string name="space_settings_space_description_hint">Description</string>
<string name="space_settings_section_collaboration">Collaboration</string>
<string name="space_settings_section_content_model">Content Model</string>
<string name="space_settings_section_preferences">Preferences</string>
<string name="space_settings_section_data_management">Data Management</string>
<string name="space_settings_section_misc">Misc</string>
<string name="space_settings_invite">Invite</string>
<string name="space_settings_qrcode">QR Code</string>
<string name="space_settings_members_button_members">Members</string>
<string name="space_settings_types_button">Object Types</string>
<string name="space_settings_default_type_button">Default Object Type</string>
<string name="space_settings_wallpaper_button">Wallpaper</string>
<string name="space_settings_storage_button">Remote Storage</string>
<string name="space_settings_space_info_button">Space Information</string>
<string name="space_settings_delete_space_button">Delete Space</string>
<string name="space_settings_icon_title">Edit picture</string>
<string name="space_settings_save_button">Save</string>
</resources>

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.presentation.spaces
sealed class UiEvent {
data object OnBackPressed : UiEvent()
data class OnSavedClicked(val name: String, val description: String) : UiEvent()
data object OnDeleteSpaceClicked : UiEvent()
data object OnFileStorageClick : UiEvent()
data object OnPersonalizationClicked : UiEvent()
data object OnSpaceIdClicked : UiEvent()
data class OnSpaceImagePicked(val uri: String) : UiEvent()
data object OnInviteClicked : UiEvent()
data object OnQrCodeClicked : UiEvent()
sealed class IconMenu : UiEvent() {
data object OnRemoveIconClicked : IconMenu()
}
}

View file

@ -0,0 +1,40 @@
package com.anytypeio.anytype.presentation.spaces
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.presentation.objects.ObjectIcon
sealed class UiSpaceSettingsState {
data object Initial : UiSpaceSettingsState()
data class SpaceSettings(
val items: List<UiSpaceSettingsItem>,
val isEditEnabled: Boolean
) : UiSpaceSettingsState()
data class SpaceSettingsError(val message: String) : UiSpaceSettingsState()
}
sealed class UiSpaceSettingsItem {
sealed class Section : UiSpaceSettingsItem() {
data object Collaboration : Section()
data object ContentModel : Section()
data object Preferences : Section()
data object DataManagement : Section()
data object Misc : Section()
}
data class Spacer(val height: Int) : UiSpaceSettingsItem()
data class Icon(val icon: SpaceIconView) : UiSpaceSettingsItem()
data class Name(val name: String) : UiSpaceSettingsItem()
data class Description(val description: String) : UiSpaceSettingsItem()
data object Multiplayer : UiSpaceSettingsItem()
data class Members(val count: Int) : UiSpaceSettingsItem()
data class Chat(val isOn: Boolean) : UiSpaceSettingsItem()
data object ObjectTypes : UiSpaceSettingsItem()
data class DefaultObjectType(val name: String, val icon: ObjectIcon) : UiSpaceSettingsItem()
data class Wallpapers(val color: ThemeColor) : UiSpaceSettingsItem()
data class RemoteStorage(val size: Int) : UiSpaceSettingsItem()
data object SpaceInfo : UiSpaceSettingsItem()
data object DeleteSpace : UiSpaceSettingsItem()
}