diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt index 1946cedfdc..e4dc73c82a 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/ObjectWrapper.kt @@ -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 diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/System.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/System.kt index 434526c409..52781ab3e1 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/System.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/System.kt @@ -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( diff --git a/core-ui/src/main/res/drawable/ic_add_member_32.xml b/core-ui/src/main/res/drawable/ic_add_member_32.xml new file mode 100644 index 0000000000..ca352df216 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_add_member_32.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/core-ui/src/main/res/drawable/ic_disclosure_8_24.xml b/core-ui/src/main/res/drawable/ic_disclosure_8_24.xml new file mode 100644 index 0000000000..b5636b7cf4 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_disclosure_8_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_members_24.xml b/core-ui/src/main/res/drawable/ic_members_24.xml new file mode 100644 index 0000000000..50048064d2 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_members_24.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_object_types_24.xml b/core-ui/src/main/res/drawable/ic_object_types_24.xml new file mode 100644 index 0000000000..3bbcab48fd --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_object_types_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_qr_code_32.xml b/core-ui/src/main/res/drawable/ic_qr_code_32.xml new file mode 100644 index 0000000000..901cdfb20d --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_qr_code_32.xml @@ -0,0 +1,10 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_remote_storage_24.xml b/core-ui/src/main/res/drawable/ic_remote_storage_24.xml new file mode 100644 index 0000000000..99daee6ceb --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_remote_storage_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/feature-ui-settings/build.gradle b/feature-ui-settings/build.gradle index 5239a22449..eac608f655 100644 --- a/feature-ui-settings/build.gradle +++ b/feature-ui-settings/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation libs.appcompat implementation libs.compose + implementation libs.composeMaterial3 implementation libs.composeFoundation implementation libs.composeMaterial implementation libs.composeToolingPreview diff --git a/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Icon.kt b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Icon.kt new file mode 100644 index 0000000000..9e7f246c9d --- /dev/null +++ b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Icon.kt @@ -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) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Items.kt b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Items.kt new file mode 100644 index 0000000000..a332c1a501 --- /dev/null +++ b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Items.kt @@ -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 + ) +} \ No newline at end of file diff --git a/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/NewSettings.kt b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/NewSettings.kt new file mode 100644 index 0000000000..14724a6f49 --- /dev/null +++ b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/NewSettings.kt @@ -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() + .firstOrNull()?.name ?: "" + val initialDescription = uiState.items.filterIsInstance() + .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)) + } + } + } + + } + } + } + ) + +} \ No newline at end of file diff --git a/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Previews.kt b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Previews.kt new file mode 100644 index 0000000000..0a62607242 --- /dev/null +++ b/feature-ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/new_settings/Previews.kt @@ -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 = {}, + ) +} \ No newline at end of file diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 7627c7cc0f..09ed77968f 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1943,4 +1943,23 @@ Please provide specific details of your needs here. Add to the current type Remove from the object + Name + Description + Collaboration + Content Model + Preferences + Data Management + Misc + Invite + QR Code + Members + Object Types + Default Object Type + Wallpaper + Remote Storage + Space Information + Delete Space + Edit picture + Save + \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/UiEvent.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/UiEvent.kt new file mode 100644 index 0000000000..531e0614f9 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/UiEvent.kt @@ -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() + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/UiState.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/UiState.kt new file mode 100644 index 0000000000..b37acab578 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/UiState.kt @@ -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, + 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() + +} \ No newline at end of file