mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3375 New Settings | Fix | Basics, part 1 (#2140)
This commit is contained in:
parent
0f03390ae4
commit
d19f5a0ba4
8 changed files with 453 additions and 306 deletions
|
@ -16,10 +16,7 @@ import com.anytypeio.anytype.R
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_ui.common.ComposeDialogView
|
||||
import com.anytypeio.anytype.core_ui.extensions.throttledClick
|
||||
import com.anytypeio.anytype.core_utils.clipboard.copyPlainTextToClipboard
|
||||
import com.anytypeio.anytype.core_utils.ext.arg
|
||||
import com.anytypeio.anytype.core_utils.ext.parseImagePath
|
||||
import com.anytypeio.anytype.core_utils.ext.shareFile
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
|
@ -29,14 +26,14 @@ import com.anytypeio.anytype.presentation.spaces.SpaceSettingsViewModel.Command
|
|||
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
|
||||
import com.anytypeio.anytype.ui.multiplayer.LeaveSpaceWarning
|
||||
import com.anytypeio.anytype.ui.multiplayer.ShareSpaceFragment
|
||||
import com.anytypeio.anytype.ui.settings.SpacesStorageFragment
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import com.anytypeio.anytype.ui.spaces.DeleteSpaceWarning
|
||||
import com.anytypeio.anytype.ui_settings.space.SpaceSettingsScreen
|
||||
import com.anytypeio.anytype.ui_settings.space.new_settings.SpaceSettingsContainer
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO convert to Fragment.
|
||||
class SpaceSettingsFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
private val space get() = arg<Id>(ARG_SPACE_ID_KEY)
|
||||
|
@ -62,60 +59,9 @@ class SpaceSettingsFragment : BaseBottomSheetComposeFragment() {
|
|||
surface = colorResource(id = R.color.context_menu_background)
|
||||
)
|
||||
) {
|
||||
SpaceSettingsScreen(
|
||||
onNameSet = vm::onNameSet,
|
||||
state = vm.spaceViewState.collectAsStateWithLifecycle().value,
|
||||
onDeleteSpaceClicked = throttledClick(
|
||||
onClick = { vm.onDeleteSpaceClicked() }
|
||||
),
|
||||
onFileStorageClick = throttledClick(
|
||||
onClick = {
|
||||
findNavController().navigate(
|
||||
resId = R.id.spacesStorageScreen,
|
||||
args = SpacesStorageFragment.args(space = space)
|
||||
)
|
||||
}
|
||||
),
|
||||
onPersonalizationClicked = throttledClick(
|
||||
onClick = {
|
||||
findNavController().navigate(R.id.personalizationScreen)
|
||||
}
|
||||
),
|
||||
onSpaceIdClicked = {
|
||||
context.copyPlainTextToClipboard(
|
||||
plainText = it,
|
||||
label = "Space ID",
|
||||
successToast = context.getString(R.string.space_id_copied_toast_msg)
|
||||
)
|
||||
},
|
||||
onNetworkIdClicked = {
|
||||
context.copyPlainTextToClipboard(
|
||||
plainText = it,
|
||||
label = "Network ID",
|
||||
successToast = context.getString(R.string.network_id_copied_toast_msg)
|
||||
)
|
||||
},
|
||||
onCreatedByClicked = {
|
||||
context.copyPlainTextToClipboard(
|
||||
plainText = it,
|
||||
label = "Created-by ID",
|
||||
successToast = context.getString(R.string.created_by_id_copied_toast_msg)
|
||||
)
|
||||
},
|
||||
onDebugClicked = vm::onSpaceDebugClicked,
|
||||
onRemoveIconClicked = vm::onRemoveSpaceIconClicked,
|
||||
onManageSharedSpaceClicked = vm::onManageSharedSpaceClicked,
|
||||
onSharePrivateSpaceClicked = vm::onSharePrivateSpaceClicked,
|
||||
onAddMoreSpacesClicked = vm::onAddMoreSpacesClicked,
|
||||
onSpaceImagePicked = { uri ->
|
||||
runCatching {
|
||||
vm.onSpaceImagePicked(
|
||||
path = uri.parseImagePath(requireContext())
|
||||
)
|
||||
}.onFailure {
|
||||
toast(getString(R.string.error_while_loading_picture))
|
||||
}
|
||||
}
|
||||
SpaceSettingsContainer(
|
||||
uiState = vm.uiState.collectAsStateWithLifecycle().value,
|
||||
uiEvent = vm::onUiEvent
|
||||
)
|
||||
LaunchedEffect(Unit) { vm.toasts.collect { toast(it) } }
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
|
@ -316,7 +316,10 @@ fun SpaceSettingsScreenPreview() {
|
|||
shareLimitReached = SpaceSettingsViewModel.ShareLimitsState(
|
||||
shareLimitReached = false,
|
||||
sharedSpacesLimit = 0
|
||||
)
|
||||
),
|
||||
isUserOwner = false,
|
||||
isEditEnabled = false,
|
||||
description = ""
|
||||
)
|
||||
),
|
||||
onNameSet = {},
|
||||
|
|
|
@ -27,6 +27,8 @@ 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.core_utils.ext.parseImagePath
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
import com.anytypeio.anytype.presentation.spaces.UiEvent
|
||||
import com.anytypeio.anytype.ui_settings.R
|
||||
|
@ -40,16 +42,23 @@ fun NewSpaceIcon(
|
|||
isEditEnabled: Boolean
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val singlePhotoPickerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.PickVisualMedia(),
|
||||
onResult = { uri ->
|
||||
if (uri != null) {
|
||||
uiEvent(UiEvent.OnSpaceImagePicked(uri.toString()))
|
||||
runCatching {
|
||||
val path = uri.parseImagePath(context)
|
||||
uiEvent(UiEvent.OnSpaceImagePicked(path))
|
||||
}.onFailure {
|
||||
context.toast(context.getString(R.string.error_while_loading_picture))
|
||||
}
|
||||
} else {
|
||||
Timber.w("Uri was null after picking image")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val isSpaceIconMenuExpanded = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ import kotlinx.coroutines.flow.debounce
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.dropWhile
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import timber.log.Timber
|
||||
|
||||
@Composable
|
||||
fun MembersItem(
|
||||
|
@ -249,18 +250,24 @@ fun BaseButton(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@Composable
|
||||
fun NewSpaceNameBlock(
|
||||
fun NewSpaceNameInputField(
|
||||
modifier: Modifier = Modifier,
|
||||
name: String,
|
||||
onNameSet: (String) -> Unit,
|
||||
isEditEnabled: Boolean
|
||||
isEditEnabled: Boolean,
|
||||
onNameSet: (String) -> Unit = {}
|
||||
) {
|
||||
|
||||
val nameValue = remember { mutableStateOf(name) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
LaunchedEffect(name) {
|
||||
nameValue.value = name
|
||||
}
|
||||
|
||||
LaunchedEffect(nameValue.value) {
|
||||
snapshotFlow { nameValue.value }
|
||||
.debounce(300L)
|
||||
|
@ -300,13 +307,17 @@ fun NewSpaceNameBlock(
|
|||
fun NewSpaceDescriptionBlock(
|
||||
modifier: Modifier = Modifier,
|
||||
description: String,
|
||||
onDescriptionSet: (String) -> Unit,
|
||||
isEditEnabled: Boolean
|
||||
isEditEnabled: Boolean,
|
||||
onDescriptionSet: (String) -> Unit = {},
|
||||
) {
|
||||
|
||||
val descriptionValue = remember { mutableStateOf(description) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
LaunchedEffect(description) {
|
||||
descriptionValue.value = description
|
||||
}
|
||||
|
||||
LaunchedEffect(descriptionValue.value) {
|
||||
snapshotFlow { descriptionValue.value }
|
||||
.debounce(300L)
|
||||
|
|
|
@ -3,11 +3,9 @@ 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
|
||||
|
@ -16,11 +14,11 @@ 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.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -33,8 +31,8 @@ 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.Dragger
|
||||
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
|
||||
|
@ -42,6 +40,7 @@ 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
|
||||
import timber.log.Timber
|
||||
|
||||
@Composable
|
||||
fun SpaceSettingsContainer(
|
||||
|
@ -56,24 +55,17 @@ fun SpaceSettingsContainer(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NewSpaceSettingsScreen(
|
||||
uiState: UiSpaceSettingsState.SpaceSettings,
|
||||
uiEvent: (UiEvent) -> Unit
|
||||
) {
|
||||
val initialName = uiState.name
|
||||
val initialDescription = uiState.description
|
||||
|
||||
// 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
|
||||
var showEditDescription by remember { mutableStateOf(false) }
|
||||
var showEditTitle by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
@ -102,29 +94,6 @@ fun NewSpaceSettingsScreen(
|
|||
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 ->
|
||||
|
@ -160,10 +129,9 @@ fun NewSpaceSettingsScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
is UiSpaceSettingsItem.Name -> {
|
||||
item {
|
||||
NewSpaceNameBlock(
|
||||
NewSpaceNameInputField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
|
@ -172,12 +140,13 @@ fun NewSpaceSettingsScreen(
|
|||
color = colorResource(id = R.color.shape_primary)
|
||||
)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||
.animateItem(),
|
||||
name = nameInput,
|
||||
onNameSet = { newName ->
|
||||
nameInput = newName
|
||||
},
|
||||
isEditEnabled = uiState.isEditEnabled
|
||||
.animateItem()
|
||||
.noRippleClickable {
|
||||
showEditTitle = true
|
||||
}
|
||||
,
|
||||
name = item.name,
|
||||
isEditEnabled = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -193,12 +162,13 @@ fun NewSpaceSettingsScreen(
|
|||
color = colorResource(id = R.color.shape_primary)
|
||||
)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||
.animateItem(),
|
||||
isEditEnabled = uiState.isEditEnabled,
|
||||
description = descriptionInput,
|
||||
onDescriptionSet = { newDescription ->
|
||||
descriptionInput = newDescription
|
||||
}
|
||||
.animateItem()
|
||||
.noRippleClickable {
|
||||
showEditDescription = true
|
||||
}
|
||||
,
|
||||
isEditEnabled = false,
|
||||
description = initialDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +183,9 @@ fun NewSpaceSettingsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
is UiSpaceSettingsItem.Chat -> TODO()
|
||||
is UiSpaceSettingsItem.Chat -> {
|
||||
// TODO
|
||||
}
|
||||
is UiSpaceSettingsItem.DefaultObjectType -> {
|
||||
item {
|
||||
DefaultTypeItem(
|
||||
|
@ -257,7 +229,9 @@ fun NewSpaceSettingsScreen(
|
|||
}
|
||||
}
|
||||
|
||||
is UiSpaceSettingsItem.RemoteStorage -> TODO()
|
||||
is UiSpaceSettingsItem.RemoteStorage -> {
|
||||
// TODO
|
||||
}
|
||||
is UiSpaceSettingsItem.Section -> {
|
||||
item {
|
||||
SpaceSettingsSection(
|
||||
|
@ -302,4 +276,184 @@ fun NewSpaceSettingsScreen(
|
|||
}
|
||||
)
|
||||
|
||||
if (showEditDescription) {
|
||||
ModalBottomSheet(
|
||||
containerColor = colorResource(R.color.background_secondary),
|
||||
onDismissRequest = {
|
||||
showEditDescription = false
|
||||
},
|
||||
dragHandle = {
|
||||
Dragger(
|
||||
modifier = Modifier.padding(vertical = 6.dp)
|
||||
)
|
||||
},
|
||||
) {
|
||||
EditDescriptionField(
|
||||
initialInput = initialDescription,
|
||||
onSaveFieldValueClicked = {
|
||||
uiEvent(UiEvent.OnSaveDescriptionClicked(it)).also {
|
||||
showEditDescription = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showEditTitle) {
|
||||
ModalBottomSheet(
|
||||
containerColor = colorResource(R.color.background_secondary),
|
||||
onDismissRequest = {
|
||||
showEditTitle = false
|
||||
},
|
||||
dragHandle = {
|
||||
Dragger(
|
||||
modifier = Modifier.padding(vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
) {
|
||||
EditNameField(
|
||||
initialInput = initialName,
|
||||
onSaveFieldValueClicked = {
|
||||
uiEvent(UiEvent.OnSaveTitleClicked(it)).also {
|
||||
showEditTitle = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditNameField(
|
||||
initialInput: String,
|
||||
onSaveFieldValueClicked: (String) -> Unit
|
||||
) {
|
||||
|
||||
var fieldInput by remember { mutableStateOf(initialInput) }
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = colorResource(id = R.color.background_secondary),
|
||||
topBar = {
|
||||
Box(modifier = Modifier.fillMaxWidth().height(48.dp)) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(horizontal = 16.dp)
|
||||
.noRippleClickable {
|
||||
onSaveFieldValueClicked(fieldInput)
|
||||
},
|
||||
text = "Done",
|
||||
style = PreviewTitle1Medium,
|
||||
color = if (fieldInput != initialInput) {
|
||||
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
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||
.fillMaxSize()
|
||||
else
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
|
||||
Box(
|
||||
modifier = contentModifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
NewSpaceNameInputField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
width = 2.dp,
|
||||
color = colorResource(id = R.color.palette_system_amber_50)
|
||||
)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||
,
|
||||
name = fieldInput,
|
||||
onNameSet = { newName ->
|
||||
fieldInput = newName
|
||||
},
|
||||
isEditEnabled = true
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditDescriptionField(
|
||||
initialInput: String,
|
||||
onSaveFieldValueClicked: (String) -> Unit
|
||||
) {
|
||||
|
||||
var fieldInput by remember { mutableStateOf(initialInput) }
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = colorResource(id = R.color.background_secondary),
|
||||
topBar = {
|
||||
Box(modifier = Modifier.fillMaxWidth().height(48.dp)) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(horizontal = 16.dp)
|
||||
.noRippleClickable {
|
||||
onSaveFieldValueClicked(fieldInput)
|
||||
},
|
||||
text = "Done",
|
||||
style = PreviewTitle1Medium,
|
||||
color = if (fieldInput != initialInput) {
|
||||
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)
|
||||
|
||||
Box(
|
||||
modifier = contentModifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
NewSpaceDescriptionBlock(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
width = 2.dp,
|
||||
color = colorResource(id = R.color.palette_system_amber_50)
|
||||
)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||
,
|
||||
description = fieldInput,
|
||||
isEditEnabled = true,
|
||||
onDescriptionSet = {
|
||||
fieldInput = it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
|
@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenLeaveSpace
|
||||
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
|
||||
import com.anytypeio.anytype.analytics.base.sendEvent
|
||||
import com.anytypeio.anytype.analytics.props.Props
|
||||
|
@ -13,19 +12,11 @@ import com.anytypeio.anytype.core_models.Block
|
|||
import com.anytypeio.anytype.core_models.Filepath
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.PRIVATE_SPACE_TYPE
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.SHARED_SPACE_TYPE
|
||||
import com.anytypeio.anytype.core_models.SpaceType
|
||||
import com.anytypeio.anytype.core_models.UNKNOWN_SPACE_TYPE
|
||||
import com.anytypeio.anytype.core_models.asSpaceType
|
||||
import com.anytypeio.anytype.core_models.ext.isPossibleToUpgrade
|
||||
import com.anytypeio.anytype.core_models.membership.MembershipUpgradeReason
|
||||
import com.anytypeio.anytype.core_models.membership.TierId
|
||||
import com.anytypeio.anytype.core_models.multiplayer.ParticipantStatus
|
||||
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_utils.ui.ViewState
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.config.ConfigStorage
|
||||
import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader
|
||||
|
@ -41,6 +32,7 @@ import com.anytypeio.anytype.domain.spaces.DeleteSpace
|
|||
import com.anytypeio.anytype.domain.spaces.SetSpaceDetails
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.spaces.UiSpaceSettingsItem.Spacer
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
@ -70,13 +62,13 @@ class SpaceSettingsViewModel(
|
|||
|
||||
val commands = MutableSharedFlow<Command>()
|
||||
val isDismissed = MutableStateFlow(false)
|
||||
val spaceViewState = MutableStateFlow<ViewState<SpaceData>>(ViewState.Init)
|
||||
|
||||
val uiState = MutableStateFlow<UiSpaceSettingsState>(UiSpaceSettingsState.Initial)
|
||||
|
||||
val permissions = MutableStateFlow(SpaceMemberPermissions.NO_PERMISSIONS)
|
||||
|
||||
private val spaceConfig = spaceManager.getConfig()
|
||||
|
||||
init {
|
||||
Timber.d("SpaceSettingsViewModel, Init, vmParams: $params")
|
||||
viewModelScope.launch {
|
||||
analytics.sendEvent(
|
||||
eventName = EventsDictionary.screenSettingSpacesSpaceIndex
|
||||
|
@ -87,7 +79,6 @@ class SpaceSettingsViewModel(
|
|||
|
||||
private fun proceedWithObservingSpaceView() {
|
||||
viewModelScope.launch {
|
||||
val config = spaceManager.getConfig(params.space)
|
||||
combine(
|
||||
spaceViewContainer.observe(params.space),
|
||||
userPermissionProvider.observe(params.space),
|
||||
|
@ -95,7 +86,7 @@ class SpaceSettingsViewModel(
|
|||
.observe()
|
||||
.map { wrapper ->
|
||||
wrapper.getValue<Double?>(Relations.SHARED_SPACES_LIMIT)?.toInt() ?: 0
|
||||
},
|
||||
},
|
||||
spaceViewContainer.sharedSpaceCount(userPermissionProvider.all()),
|
||||
activeSpaceMemberSubscriptionContainer.observe(params.space),
|
||||
) { spaceView, permission, sharedSpaceLimit: Int, sharedSpaceCount: Int, store ->
|
||||
|
@ -111,56 +102,98 @@ class SpaceSettingsViewModel(
|
|||
null
|
||||
}
|
||||
val createdBy = spaceMember?.globalName?.takeIf { it.isNotEmpty() } ?: spaceMember?.identity
|
||||
SpaceData(
|
||||
name = spaceView.name.orEmpty(),
|
||||
icon = spaceView.spaceIcon(
|
||||
builder = urlBuilder,
|
||||
spaceGradientProvider = gradientProvider
|
||||
|
||||
//todo add logic
|
||||
val membersNumber = 3
|
||||
|
||||
UiSpaceSettingsState.SpaceSettings(
|
||||
items = listOf(
|
||||
UiSpaceSettingsItem.Icon(
|
||||
icon = spaceView.spaceIcon(
|
||||
builder = urlBuilder,
|
||||
spaceGradientProvider = gradientProvider
|
||||
)
|
||||
),
|
||||
UiSpaceSettingsItem.Spacer(height = 16),
|
||||
UiSpaceSettingsItem.Name(
|
||||
//todo support localization for empty space name
|
||||
name = spaceView.name.orEmpty()
|
||||
),
|
||||
UiSpaceSettingsItem.Spacer(height = 12),
|
||||
UiSpaceSettingsItem.Description(
|
||||
//todo support localization for empty space description
|
||||
description = spaceView.description.orEmpty()
|
||||
),
|
||||
UiSpaceSettingsItem.Spacer(height = 12),
|
||||
UiSpaceSettingsItem.Multiplayer,
|
||||
UiSpaceSettingsItem.Spacer(height = 8),
|
||||
UiSpaceSettingsItem.Section.Collaboration,
|
||||
UiSpaceSettingsItem.Members(count = membersNumber),
|
||||
),
|
||||
createdDateInMillis = spaceView
|
||||
.getValue<Double?>(Relations.CREATED_DATE)
|
||||
?.let { timeInSeconds -> (timeInSeconds * 1000L).toLong() },
|
||||
createdBy = createdBy,
|
||||
spaceId = params.space.id,
|
||||
network = config?.network.orEmpty(),
|
||||
isDeletable = resolveIsSpaceDeletable(spaceView),
|
||||
spaceType = spaceView.spaceAccessType?.asSpaceType() ?: UNKNOWN_SPACE_TYPE,
|
||||
permissions = permission ?: SpaceMemberPermissions.NO_PERMISSIONS,
|
||||
shareLimitReached = ShareLimitsState(
|
||||
shareLimitReached = sharedSpaceCount >= sharedSpaceLimit,
|
||||
sharedSpacesLimit = sharedSpaceLimit
|
||||
),
|
||||
requests = requests
|
||||
isEditEnabled = permission?.isOwnerOrEditor() == true
|
||||
)
|
||||
|
||||
}.collect { spaceData ->
|
||||
Timber.d("Space data: ${spaceData}")
|
||||
spaceViewState.value = ViewState.Success(spaceData)
|
||||
uiState.value = spaceData
|
||||
//spaceViewState.value = spaceData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNameSet(name: String) {
|
||||
Timber.d("onNameSet")
|
||||
if (name.isEmpty()) return
|
||||
if (isDismissed.value) return
|
||||
viewModelScope.launch {
|
||||
if (spaceConfig != null) {
|
||||
setSpaceDetails.async(
|
||||
SetSpaceDetails.Params(
|
||||
space = SpaceId(spaceConfig.space),
|
||||
details = mapOf(Relations.NAME to name)
|
||||
fun onUiEvent(uiEvent: UiEvent) {
|
||||
Timber.d("onUiEvent: $uiEvent")
|
||||
when(uiEvent) {
|
||||
UiEvent.IconMenu.OnRemoveIconClicked -> {
|
||||
proceedWithRemovingSpaceIcon()
|
||||
}
|
||||
UiEvent.OnBackPressed -> {
|
||||
isDismissed.value = true
|
||||
}
|
||||
UiEvent.OnDeleteSpaceClicked -> {
|
||||
sendToast("Coming soon")
|
||||
}
|
||||
UiEvent.OnFileStorageClick -> {
|
||||
sendToast("Coming soon")
|
||||
}
|
||||
UiEvent.OnInviteClicked -> {
|
||||
sendToast("Coming soon")
|
||||
}
|
||||
UiEvent.OnPersonalizationClicked -> {
|
||||
sendToast("Coming soon")
|
||||
}
|
||||
UiEvent.OnQrCodeClicked -> {
|
||||
sendToast("Coming soon")
|
||||
}
|
||||
is UiEvent.OnSaveDescriptionClicked -> {
|
||||
viewModelScope.launch {
|
||||
setSpaceDetails.async(
|
||||
params = SetSpaceDetails.Params(
|
||||
space = params.space,
|
||||
details = mapOf(
|
||||
Relations.DESCRIPTION to uiEvent.description
|
||||
)
|
||||
)
|
||||
)
|
||||
).fold(
|
||||
onFailure = {
|
||||
Timber.e(it, "Error while updating object details")
|
||||
sendToast("Something went wrong. Please try again")
|
||||
},
|
||||
onSuccess = {
|
||||
Timber.d("Name successfully set for current space: ${spaceConfig.space}")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Timber.w("Something went wrong: config is empty")
|
||||
}
|
||||
}
|
||||
is UiEvent.OnSaveTitleClicked -> {
|
||||
viewModelScope.launch {
|
||||
setSpaceDetails.async(
|
||||
params = SetSpaceDetails.Params(
|
||||
space = params.space,
|
||||
details = mapOf(
|
||||
Relations.NAME to uiEvent.title
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
UiEvent.OnSpaceIdClicked -> {
|
||||
sendToast("Coming soon")
|
||||
}
|
||||
is UiEvent.OnSpaceImagePicked -> {
|
||||
proceedWithSettingSpaceImage(uiEvent.uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,44 +202,39 @@ class SpaceSettingsViewModel(
|
|||
// TODO unsubscribe
|
||||
}
|
||||
|
||||
fun onSpaceDebugClicked() {
|
||||
proceedWithSpaceDebug()
|
||||
}
|
||||
|
||||
fun onRemoveSpaceIconClicked() {
|
||||
// fun onSpaceDebugClicked() {
|
||||
// proceedWithSpaceDebug()
|
||||
// }
|
||||
//
|
||||
private fun proceedWithRemovingSpaceIcon() {
|
||||
viewModelScope.launch {
|
||||
val config = spaceConfig
|
||||
if (config != null) {
|
||||
setSpaceDetails.async(
|
||||
SetSpaceDetails.Params(
|
||||
space = params.space,
|
||||
details = mapOf(
|
||||
Relations.ICON_OPTION to spaceGradientProvider.randomId().toDouble(),
|
||||
Relations.ICON_IMAGE to "",
|
||||
)
|
||||
setSpaceDetails.async(
|
||||
SetSpaceDetails.Params(
|
||||
space = params.space,
|
||||
details = mapOf(
|
||||
Relations.ICON_OPTION to spaceGradientProvider.randomId().toDouble(),
|
||||
Relations.ICON_IMAGE to "",
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDeleteSpaceClicked() {
|
||||
viewModelScope.launch {
|
||||
val state = spaceViewState.value
|
||||
if (state is ViewState.Success) {
|
||||
if (state.data.permissions.isOwner()) {
|
||||
commands.emit(Command.ShowDeleteSpaceWarning)
|
||||
analytics.sendEvent(
|
||||
eventName = EventsDictionary.clickDeleteSpace,
|
||||
props = Props(mapOf(EventsPropertiesKey.route to EventsDictionary.Routes.settings))
|
||||
)
|
||||
} else {
|
||||
commands.emit(Command.ShowLeaveSpaceWarning)
|
||||
analytics.sendEvent(eventName = screenLeaveSpace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fun onDeleteSpaceClicked() {
|
||||
// viewModelScope.launch {
|
||||
// val state = spaceViewState.value as? SpaceData.Success ?: return@launch
|
||||
// if (state.isUserOwner) {
|
||||
// commands.emit(Command.ShowDeleteSpaceWarning)
|
||||
// analytics.sendEvent(
|
||||
// eventName = EventsDictionary.clickDeleteSpace,
|
||||
// props = Props(mapOf(EventsPropertiesKey.route to EventsDictionary.Routes.settings))
|
||||
// )
|
||||
// } else {
|
||||
// commands.emit(Command.ShowLeaveSpaceWarning)
|
||||
// analytics.sendEvent(eventName = screenLeaveSpace)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun onDeleteSpaceWarningCancelled() {
|
||||
viewModelScope.launch {
|
||||
|
@ -243,29 +271,27 @@ class SpaceSettingsViewModel(
|
|||
}
|
||||
|
||||
private fun proceedWithSpaceDeletion() {
|
||||
val state = spaceViewState.value
|
||||
if (state is ViewState.Success) {
|
||||
val space = state.data.spaceId
|
||||
if (space != null) {
|
||||
viewModelScope.launch {
|
||||
deleteSpace.async(params = SpaceId(space)).fold(
|
||||
onSuccess = {
|
||||
analytics.sendEvent(
|
||||
eventName = EventsDictionary.deleteSpace,
|
||||
props = Props(mapOf(EventsPropertiesKey.type to "Private"))
|
||||
)
|
||||
spaceManager.clear()
|
||||
commands.emit(Command.ExitToVault)
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Error while deleting space")
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
sendToast("Space not found. Please, try again later")
|
||||
}
|
||||
}
|
||||
// val state = spaceViewState.value as? SpaceData.Success ?: return
|
||||
// val space = state.spaceId
|
||||
// if (space != null) {
|
||||
// viewModelScope.launch {
|
||||
// deleteSpace.async(params = SpaceId(space)).fold(
|
||||
// onSuccess = {
|
||||
// analytics.sendEvent(
|
||||
// eventName = EventsDictionary.deleteSpace,
|
||||
// props = Props(mapOf(EventsPropertiesKey.type to "Private"))
|
||||
// )
|
||||
// spaceManager.clear()
|
||||
// commands.emit(Command.ExitToVault)
|
||||
// },
|
||||
// onFailure = {
|
||||
// Timber.e(it, "Error while deleting space")
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// } else {
|
||||
// sendToast("Space not found. Please, try again later")
|
||||
// }
|
||||
}
|
||||
|
||||
private fun proceedWithSpaceDebug() {
|
||||
|
@ -289,78 +315,69 @@ class SpaceSettingsViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onSharePrivateSpaceClicked() {
|
||||
viewModelScope.launch {
|
||||
val data = spaceViewState.value
|
||||
when(data) {
|
||||
is ViewState.Success -> {
|
||||
when(data.data.spaceType) {
|
||||
PRIVATE_SPACE_TYPE -> {
|
||||
analytics.sendEvent(
|
||||
eventName = EventsDictionary.screenSettingsSpaceShare,
|
||||
props = Props(
|
||||
mapOf(
|
||||
EventsPropertiesKey.route to EventsDictionary.Routes.settings
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
SHARED_SPACE_TYPE -> {
|
||||
analytics.sendEvent(
|
||||
eventName = EventsDictionary.screenSettingsSpaceMembers,
|
||||
props = Props(
|
||||
mapOf(
|
||||
EventsPropertiesKey.route to EventsDictionary.Routes.settings
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val data = spaceViewState.value
|
||||
if (data is ViewState.Success) {
|
||||
val shareLimits = data.data.shareLimitReached
|
||||
if (!shareLimits.shareLimitReached) {
|
||||
commands.emit(Command.SharePrivateSpace(params.space))
|
||||
} else {
|
||||
commands.emit(Command.ShowShareLimitReachedError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fun onSharePrivateSpaceClicked() {
|
||||
// viewModelScope.launch {
|
||||
// val data = spaceViewState.value as? SpaceData.Success ?: return@launch
|
||||
// when(data.spaceType) {
|
||||
// PRIVATE_SPACE_TYPE -> {
|
||||
// analytics.sendEvent(
|
||||
// eventName = EventsDictionary.screenSettingsSpaceShare,
|
||||
// props = Props(
|
||||
// mapOf(
|
||||
// EventsPropertiesKey.route to EventsDictionary.Routes.settings
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// SHARED_SPACE_TYPE -> {
|
||||
// analytics.sendEvent(
|
||||
// eventName = EventsDictionary.screenSettingsSpaceMembers,
|
||||
// props = Props(
|
||||
// mapOf(
|
||||
// EventsPropertiesKey.route to EventsDictionary.Routes.settings
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// viewModelScope.launch {
|
||||
// val data = spaceViewState.value as? SpaceData.Success ?: return@launch
|
||||
// val shareLimits = data.shareLimitReached
|
||||
// if (!shareLimits.shareLimitReached) {
|
||||
// commands.emit(Command.SharePrivateSpace(params.space))
|
||||
// } else {
|
||||
// commands.emit(Command.ShowShareLimitReachedError)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun resolveIsSpaceDeletable(spaceView: ObjectWrapper.SpaceView) : Boolean {
|
||||
return spaceView.spaceAccessType != null
|
||||
}
|
||||
|
||||
fun onAddMoreSpacesClicked() {
|
||||
viewModelScope.launch {
|
||||
getMembership.async(GetMembershipStatus.Params(noCache = false)).fold(
|
||||
onSuccess = { membership ->
|
||||
if (membership != null) {
|
||||
val activeTier = TierId(membership.tier)
|
||||
if (activeTier.isPossibleToUpgrade(reason = MembershipUpgradeReason.NumberOfSharedSpaces)) {
|
||||
commands.emit(Command.NavigateToMembership)
|
||||
} else {
|
||||
commands.emit(Command.NavigateToMembershipUpdate)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Error while getting membership status")
|
||||
commands.emit(Command.NavigateToMembershipUpdate)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// fun onAddMoreSpacesClicked() {
|
||||
// viewModelScope.launch {
|
||||
// getMembership.async(GetMembershipStatus.Params(noCache = false)).fold(
|
||||
// onSuccess = { membership ->
|
||||
// if (membership != null) {
|
||||
// val activeTier = TierId(membership.tier)
|
||||
// if (activeTier.isPossibleToUpgrade(reason = MembershipUpgradeReason.NumberOfSharedSpaces)) {
|
||||
// commands.emit(Command.NavigateToMembership)
|
||||
// } else {
|
||||
// commands.emit(Command.NavigateToMembershipUpdate)
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// onFailure = {
|
||||
// Timber.e(it, "Error while getting membership status")
|
||||
// commands.emit(Command.NavigateToMembershipUpdate)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
fun onSpaceImagePicked(path: String) {
|
||||
fun proceedWithSettingSpaceImage(path: String) {
|
||||
Timber.d("onSpaceImageClicked: $path")
|
||||
viewModelScope.launch {
|
||||
uploadFile.async(
|
||||
|
@ -385,8 +402,8 @@ class SpaceSettingsViewModel(
|
|||
SetSpaceDetails.Params(
|
||||
space = params.space,
|
||||
details = mapOf(
|
||||
Relations.ICON_OPTION to null,
|
||||
Relations.ICON_IMAGE to file.id,
|
||||
Relations.ICON_OPTION to null,
|
||||
Relations.ICON_EMOJI to null
|
||||
)
|
||||
)
|
||||
|
@ -406,12 +423,15 @@ class SpaceSettingsViewModel(
|
|||
val createdBy: Id?,
|
||||
val network: Id?,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val icon: SpaceIconView,
|
||||
val isDeletable: Boolean = false,
|
||||
val spaceType: SpaceType,
|
||||
val permissions: SpaceMemberPermissions,
|
||||
val shareLimitReached: ShareLimitsState,
|
||||
val requests: Int = 0
|
||||
val requests: Int = 0,
|
||||
val isEditEnabled: Boolean,
|
||||
val isUserOwner: Boolean,
|
||||
val permissions: SpaceMemberPermissions,
|
||||
)
|
||||
|
||||
data class ShareLimitsState(
|
||||
|
|
|
@ -2,7 +2,8 @@ package com.anytypeio.anytype.presentation.spaces
|
|||
|
||||
sealed class UiEvent {
|
||||
data object OnBackPressed : UiEvent()
|
||||
data class OnSavedClicked(val name: String, val description: String) : UiEvent()
|
||||
data class OnSaveDescriptionClicked(val description: String) : UiEvent()
|
||||
data class OnSaveTitleClicked(val title: String) : UiEvent()
|
||||
data object OnDeleteSpaceClicked : UiEvent()
|
||||
data object OnFileStorageClick : UiEvent()
|
||||
data object OnPersonalizationClicked : UiEvent()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.anytypeio.anytype.presentation.spaces
|
||||
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
|
||||
sealed class UiSpaceSettingsState {
|
||||
|
@ -8,13 +9,16 @@ sealed class UiSpaceSettingsState {
|
|||
data class SpaceSettings(
|
||||
val items: List<UiSpaceSettingsItem>,
|
||||
val isEditEnabled: Boolean
|
||||
) : UiSpaceSettingsState()
|
||||
|
||||
) : UiSpaceSettingsState() {
|
||||
val name: String = items.filterIsInstance<UiSpaceSettingsItem.Name>()
|
||||
.firstOrNull()?.name ?: EMPTY_STRING_VALUE
|
||||
val description = items.filterIsInstance<UiSpaceSettingsItem.Description>()
|
||||
.firstOrNull()?.description ?: EMPTY_STRING_VALUE
|
||||
}
|
||||
data class SpaceSettingsError(val message: String) : UiSpaceSettingsState()
|
||||
}
|
||||
|
||||
sealed class UiSpaceSettingsItem {
|
||||
|
||||
sealed class Section : UiSpaceSettingsItem() {
|
||||
data object Collaboration : Section()
|
||||
data object ContentModel : Section()
|
||||
|
@ -36,5 +40,4 @@ sealed class UiSpaceSettingsItem {
|
|||
data class RemoteStorage(val size: Int) : UiSpaceSettingsItem()
|
||||
data object SpaceInfo : UiSpaceSettingsItem()
|
||||
data object DeleteSpace : UiSpaceSettingsItem()
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue