mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 13:57:10 +09:00
DROID-1617 Set | Enhancement | Edit view widget (#345)
This commit is contained in:
parent
c751cd0291
commit
ee0e2fa0d6
14 changed files with 706 additions and 36 deletions
|
@ -54,6 +54,7 @@ import com.anytypeio.anytype.core_ui.views.ButtonPrimarySmallIcon
|
|||
import com.anytypeio.anytype.core_ui.widgets.FeaturedRelationGroupWidget
|
||||
import com.anytypeio.anytype.core_ui.widgets.ObjectTypeTemplatesWidget
|
||||
import com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
|
||||
import com.anytypeio.anytype.core_ui.widgets.dv.ViewerEditWidget
|
||||
import com.anytypeio.anytype.core_ui.widgets.dv.ViewersWidget
|
||||
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
|
||||
import com.anytypeio.anytype.core_ui.widgets.toolbar.DataViewInfo
|
||||
|
@ -82,6 +83,7 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetCommand
|
|||
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel
|
||||
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.sets.SetOrCollectionHeaderState
|
||||
import com.anytypeio.anytype.presentation.sets.ViewerEditWidgetUi
|
||||
import com.anytypeio.anytype.presentation.sets.model.Viewer
|
||||
import com.anytypeio.anytype.ui.base.NavigationFragment
|
||||
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectSetFragment
|
||||
|
@ -253,7 +255,6 @@ open class ObjectSetFragment :
|
|||
subscribe(binding.bottomPanel.root.findViewById<FrameLayout>(R.id.btnSettings).clicks()
|
||||
.throttleFirst()
|
||||
) {
|
||||
vm.onViewerSettingsClicked()
|
||||
}
|
||||
subscribe(
|
||||
binding.bottomPanel.root.findViewById<FrameLayout>(R.id.btnSort).clicks()
|
||||
|
@ -353,6 +354,16 @@ open class ObjectSetFragment :
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
binding.viewerEditWidget.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
ViewerEditWidget(
|
||||
state = vm.viewerEditWidgetState.collectAsStateWithLifecycle().value,
|
||||
action = vm::onViewerEditWidgetAction
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWindowInsetAnimation() {
|
||||
|
@ -1123,9 +1134,12 @@ open class ObjectSetFragment :
|
|||
vm.templatesWidgetState.value.showWidget -> {
|
||||
vm.onDismissTemplatesWidget()
|
||||
}
|
||||
vm.viewersWidgetState.value.showWidget -> {
|
||||
vm.viewersWidgetState.value.showWidget && !vm.viewerEditWidgetState.value.showWidget -> {
|
||||
vm.onViewersWidgetAction(ViewersWidgetUi.Action.Dismiss)
|
||||
}
|
||||
vm.viewersWidgetState.value.showWidget && vm.viewerEditWidgetState.value.showWidget -> {
|
||||
vm.onViewerEditWidgetAction(ViewerEditWidgetUi.Action.Dismiss)
|
||||
}
|
||||
else -> {
|
||||
vm.onSystemBackPressed()
|
||||
}
|
||||
|
|
|
@ -164,4 +164,12 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/viewerEditWidget"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
|
@ -0,0 +1,356 @@
|
|||
package com.anytypeio.anytype.core_ui.widgets.dv
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
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.ModalBottomSheetLayout
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.foundation.Divider
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
|
||||
import com.anytypeio.anytype.core_ui.views.BodyRegular
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.presentation.sets.ViewerEditWidgetUi
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ViewerEditWidget(
|
||||
state: ViewerEditWidgetUi,
|
||||
action: (ViewerEditWidgetUi.Action) -> Unit
|
||||
) {
|
||||
|
||||
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
|
||||
|
||||
LaunchedEffect(key1 = state, block = {
|
||||
if (state.showWidget) sheetState.show() else sheetState.hide()
|
||||
})
|
||||
|
||||
DisposableEffect(
|
||||
key1 = (sheetState.targetValue == ModalBottomSheetValue.Hidden
|
||||
&& sheetState.isVisible)
|
||||
) {
|
||||
onDispose {
|
||||
if (sheetState.currentValue == ModalBottomSheetValue.Hidden) {
|
||||
action(ViewerEditWidgetUi.Action.Dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModalBottomSheetLayout(
|
||||
sheetState = sheetState,
|
||||
sheetBackgroundColor = Color.Transparent,
|
||||
sheetShape = RoundedCornerShape(16.dp),
|
||||
sheetContent = {
|
||||
ViewerEditWidgetContent(state, action)
|
||||
},
|
||||
content = {
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.4f))
|
||||
.noRippleThrottledClickable { action.invoke(ViewerEditWidgetUi.Action.Dismiss) }
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ViewerEditWidgetContent(
|
||||
state: ViewerEditWidgetUi,
|
||||
action: (ViewerEditWidgetUi.Action) -> Unit
|
||||
) {
|
||||
|
||||
val currentState by rememberUpdatedState(state)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.shadow(
|
||||
elevation = 40.dp,
|
||||
spotColor = Color(0x40000000),
|
||||
ambientColor = Color(0x40000000)
|
||||
)
|
||||
.padding(start = 8.dp, end = 8.dp, bottom = 31.dp)
|
||||
.background(
|
||||
color = colorResource(id = R.color.background_secondary),
|
||||
shape = RoundedCornerShape(size = 16.dp)
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(bottom = 16.dp, top = 8.dp, start = 20.dp, end = 20.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp)
|
||||
) {
|
||||
Box(modifier = Modifier.align(Alignment.Center)) {
|
||||
Text(
|
||||
text = stringResource(R.string.edit_view),
|
||||
style = Title1,
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.noRippleThrottledClickable {
|
||||
action.invoke(ViewerEditWidgetUi.Action.More)
|
||||
},
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.padding(
|
||||
top = 12.dp,
|
||||
bottom = 12.dp
|
||||
),
|
||||
painter = painterResource(id = R.drawable.ic_style_toolbar_more),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
NameTextField(state = currentState, action = action)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
ColumnItem(
|
||||
title = stringResource(id = R.string.default_object),
|
||||
value = state.defaultObjectType?.name.orEmpty(),
|
||||
isEnable = state.isDefaultObjectTypeEnabled
|
||||
) {
|
||||
if (state.isDefaultObjectTypeEnabled) {
|
||||
action(ViewerEditWidgetUi.Action.DefaultObjectType(id = state.id))
|
||||
}
|
||||
}
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
|
||||
val layoutValue = when (state.layout) {
|
||||
DVViewerType.LIST -> stringResource(id = R.string.view_list)
|
||||
DVViewerType.GRID -> stringResource(id = R.string.view_grid)
|
||||
DVViewerType.GALLERY -> stringResource(id = R.string.view_gallery)
|
||||
DVViewerType.BOARD -> stringResource(id = R.string.view_kanban)
|
||||
else -> stringResource(id = R.string.none)
|
||||
}
|
||||
ColumnItem(
|
||||
title = stringResource(id = R.string.layout),
|
||||
value = layoutValue
|
||||
) { action(ViewerEditWidgetUi.Action.Layout(id = state.id)) }
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
|
||||
val relationsValue = when (state.relations.size) {
|
||||
0 -> stringResource(id = R.string.none)
|
||||
1 -> state.relations[0]
|
||||
else -> stringResource(id = R.string.num_applied, state.relations.size)
|
||||
}
|
||||
ColumnItem(
|
||||
title = stringResource(id = R.string.relations),
|
||||
value = relationsValue
|
||||
) { action(ViewerEditWidgetUi.Action.Relations(id = state.id)) }
|
||||
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
|
||||
val filtersValue = when (state.filters.size) {
|
||||
0 -> stringResource(id = R.string.none)
|
||||
1 -> state.filters[0]
|
||||
else -> stringResource(id = R.string.num_applied, state.filters.size)
|
||||
}
|
||||
ColumnItem(
|
||||
title = stringResource(id = R.string.filter),
|
||||
value = filtersValue
|
||||
) { action(ViewerEditWidgetUi.Action.Filters(id = state.id)) }
|
||||
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
|
||||
val sortsValue = when (state.sorts.size) {
|
||||
0 -> stringResource(id = R.string.none)
|
||||
1 -> state.sorts[0]
|
||||
else -> stringResource(id = R.string.num_applied, state.sorts.size)
|
||||
}
|
||||
ColumnItem(
|
||||
title = stringResource(id = R.string.sort),
|
||||
value = sortsValue
|
||||
) { action(ViewerEditWidgetUi.Action.Sorts(id = state.id)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun NameTextField(
|
||||
state: ViewerEditWidgetUi,
|
||||
action: (ViewerEditWidgetUi.Action) -> Unit
|
||||
) {
|
||||
var innerValue by remember(state.name) { mutableStateOf(state.name) }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = Color(0xFFE3E3E3),
|
||||
shape = RoundedCornerShape(size = 10.dp)
|
||||
)
|
||||
.padding(start = 16.dp, end = 16.dp, top = 10.dp, bottom = 10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.name),
|
||||
style = Caption1Medium,
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
)
|
||||
BasicTextField(
|
||||
value = innerValue,
|
||||
onValueChange = { innerValue = it },
|
||||
textStyle = Title1,
|
||||
singleLine = true,
|
||||
enabled = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 0.dp, top = 2.dp),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions {
|
||||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
action.invoke(
|
||||
ViewerEditWidgetUi.Action.UpdateName(
|
||||
id = state.id,
|
||||
name = innerValue
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnItem(
|
||||
title: String,
|
||||
isEnable: Boolean = true,
|
||||
value: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
ConstraintLayout(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.noRippleThrottledClickable(onClick = onClick)
|
||||
.alpha(if (isEnable) 1f else 0.2f)
|
||||
) {
|
||||
val (titleRef, valueRef, iconRef) = createRefs()
|
||||
val rightGuideline = createGuidelineFromStart(0.5f)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.constrainAs(titleRef) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
},
|
||||
text = title,
|
||||
style = BodyRegular,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.constrainAs(iconRef) {
|
||||
end.linkTo(parent.end)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
},
|
||||
painter = painterResource(id = R.drawable.ic_arrow_forward),
|
||||
contentDescription = "Arrow icon"
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.constrainAs(valueRef) {
|
||||
start.linkTo(rightGuideline)
|
||||
end.linkTo(iconRef.start, margin = 6.dp)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
width = Dimension.fillToConstraints
|
||||
},
|
||||
text = value,
|
||||
style = BodyCalloutRegular,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun PreviewNameTextField() {
|
||||
NameTextField(state = ViewerEditWidgetUi.init().copy(name = "Artist"), action = {})
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun PreviewViewerEditWidget() {
|
||||
val state = ViewerEditWidgetUi(
|
||||
showWidget = true,
|
||||
name = "Artist",
|
||||
defaultObjectType = ObjectWrapper.Type(buildMap { put("name", "Name") }),
|
||||
filters = listOf(),
|
||||
sorts = emptyList(),
|
||||
layout = DVViewerType.LIST,
|
||||
relations = listOf(),
|
||||
viewerId = "12"
|
||||
)
|
||||
ViewerEditWidget(state = state, action = {})
|
||||
}
|
|
@ -261,7 +261,7 @@ private fun ViewersWidgetContent(
|
|||
Image(
|
||||
modifier = Modifier
|
||||
.noRippleThrottledClickable {
|
||||
action.invoke(Edit(view.id))
|
||||
action.invoke(Edit(id = view.id))
|
||||
}
|
||||
.constrainAs(edit) {
|
||||
end.linkTo(dnd.start, margin = 16.dp)
|
||||
|
|
|
@ -656,5 +656,13 @@
|
|||
<string name="templates_menu_delete">Delete</string>
|
||||
<string name="unsupported">Unsupported</string>
|
||||
<string name="set_as_default">Set as default</string>
|
||||
<string name="edit_view">Edit view</string>
|
||||
<string name="default_object">Default object</string>
|
||||
<string name="layout">Layout</string>
|
||||
<string name="num_applied">%1$d applied</string>
|
||||
<string name="view_list">List</string>
|
||||
<string name="view_grid">Grid</string>
|
||||
<string name="view_gallery">Gallery</string>
|
||||
<string name="view_kanban">Kanban</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -11,7 +11,7 @@ android {
|
|||
buildConfigField "boolean", "ENABLE_LINK_APPERANCE_MENU", "true"
|
||||
buildConfigField "boolean", "USE_SIMPLE_TABLES_IN_EDITOR_EDDITING", "true"
|
||||
buildConfigField "boolean", "ENABLE_WIDGETS", "false"
|
||||
buildConfigField "boolean", "ENABLE_VIEWS_MENU", "false"
|
||||
buildConfigField "boolean", "ENABLE_VIEWS_MENU", "true"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -468,13 +468,14 @@ fun ObjectWrapper.Type.toTemplateViewBlank(
|
|||
)
|
||||
}
|
||||
|
||||
fun ObjectState.DataView.toViewersView(ctx: Id, session: ObjectSetSession): List<ViewerView> {
|
||||
suspend fun ObjectState.DataView.toViewersView(ctx: Id, session: ObjectSetSession, storeOfRelations: StoreOfRelations): List<ViewerView> {
|
||||
val viewers = dataViewContent.viewers
|
||||
return when (this) {
|
||||
is ObjectState.DataView.Collection -> mapViewers(
|
||||
defaultObjectType = { it.defaultObjectType },
|
||||
viewers = viewers,
|
||||
session = session
|
||||
session = session,
|
||||
storeOfRelations = storeOfRelations
|
||||
)
|
||||
is ObjectState.DataView.Set -> {
|
||||
val setOfValue = getSetOfValue(ctx)
|
||||
|
@ -482,32 +483,39 @@ fun ObjectState.DataView.toViewersView(ctx: Id, session: ObjectSetSession): List
|
|||
mapViewers(
|
||||
defaultObjectType = { it.defaultObjectType },
|
||||
viewers = viewers,
|
||||
session = session
|
||||
session = session,
|
||||
storeOfRelations = storeOfRelations
|
||||
)
|
||||
} else {
|
||||
mapViewers(
|
||||
defaultObjectType = { setOfValue.firstOrNull() },
|
||||
viewers = viewers,
|
||||
session = session
|
||||
session = session,
|
||||
storeOfRelations = storeOfRelations
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapViewers(
|
||||
private suspend fun mapViewers(
|
||||
defaultObjectType: (DVViewer) -> Id?,
|
||||
viewers: List<DVViewer>,
|
||||
session: ObjectSetSession
|
||||
session: ObjectSetSession,
|
||||
storeOfRelations: StoreOfRelations
|
||||
): List<ViewerView> {
|
||||
return viewers.mapIndexed { index, viewer ->
|
||||
ViewerView(
|
||||
id = viewer.id,
|
||||
name = viewer.name,
|
||||
type = viewer.type,
|
||||
isActive = isActiveViewer(index, viewer, session),
|
||||
isUnsupported = viewer.type == VIEW_TYPE_UNSUPPORTED,
|
||||
defaultObjectType = defaultObjectType.invoke(viewer)
|
||||
isActive = isActiveViewer(index, viewer, session),
|
||||
defaultObjectType = defaultObjectType.invoke(viewer),
|
||||
relations = viewer.viewerRelations.toView(storeOfRelations) { it.key },
|
||||
sorts = viewer.sorts.toView(storeOfRelations) { it.relationKey },
|
||||
filters = viewer.filters.toView(storeOfRelations) { it.relation },
|
||||
isDefaultObjectTypeEnabled = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,6 +178,7 @@ class ObjectSetViewModel(
|
|||
val isCustomizeViewPanelVisible = MutableStateFlow(false)
|
||||
val templatesWidgetState = MutableStateFlow(TemplatesWidgetUiState.init())
|
||||
val viewersWidgetState = MutableStateFlow(ViewersWidgetUi.init())
|
||||
val viewerEditWidgetState = MutableStateFlow(ViewerEditWidgetUi.init())
|
||||
|
||||
@Deprecated("could be deleted")
|
||||
val isLoading = MutableStateFlow(false)
|
||||
|
@ -489,6 +490,7 @@ class ObjectSetViewModel(
|
|||
return when (dataViewState) {
|
||||
DataViewState.Init -> {
|
||||
_dvViews.value = emptyList()
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.empty()
|
||||
if (dvViewer == null) {
|
||||
DataViewViewState.Collection.NoView
|
||||
} else {
|
||||
|
@ -496,7 +498,17 @@ class ObjectSetViewModel(
|
|||
}
|
||||
}
|
||||
is DataViewState.Loaded -> {
|
||||
_dvViews.value = objectState.dataViewState()?.toViewersView(context, session) ?: emptyList()
|
||||
_dvViews.value = objectState.dataViewState()?.toViewersView(context, session, storeOfRelations) ?: emptyList()
|
||||
viewerEditWidgetState.value = if (dvViewer != null) {
|
||||
viewerEditWidgetState.value.updateState(
|
||||
dvViewer = dvViewer,
|
||||
isDefaultObjectTypeEnabled = true,
|
||||
storeOfRelations = storeOfRelations,
|
||||
storeOfObjectTypes = storeOfObjectTypes
|
||||
)
|
||||
} else {
|
||||
viewerEditWidgetState.value.empty()
|
||||
}
|
||||
val relations = objectState.dataViewContent.relationLinks.mapNotNull {
|
||||
storeOfRelations.getByKey(it.key)
|
||||
}
|
||||
|
@ -537,6 +549,7 @@ class ObjectSetViewModel(
|
|||
return when (dataViewState) {
|
||||
DataViewState.Init -> {
|
||||
_dvViews.value = emptyList()
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.empty()
|
||||
when {
|
||||
setOfValue.isEmpty() || query.isEmpty() -> DataViewViewState.Set.NoQuery
|
||||
viewer == null -> DataViewViewState.Set.NoView
|
||||
|
@ -544,7 +557,18 @@ class ObjectSetViewModel(
|
|||
}
|
||||
}
|
||||
is DataViewState.Loaded -> {
|
||||
_dvViews.value = objectState.dataViewState()?.toViewersView(context, session) ?: emptyList()
|
||||
_dvViews.value = objectState.dataViewState()?.toViewersView(context, session, storeOfRelations) ?: emptyList()
|
||||
viewerEditWidgetState.value = if (viewer != null) {
|
||||
viewerEditWidgetState.value.updateState(
|
||||
dvViewer = viewer,
|
||||
isDefaultObjectTypeEnabled = objectState.isSetByRelation(setOfValue),
|
||||
storeOfRelations = storeOfRelations,
|
||||
storeOfObjectTypes = storeOfObjectTypes
|
||||
)
|
||||
} else {
|
||||
viewerEditWidgetState.value.empty()
|
||||
}
|
||||
|
||||
val relations = objectState.dataViewContent.relationLinks.mapNotNull {
|
||||
storeOfRelations.getByKey(it.key)
|
||||
}
|
||||
|
@ -1097,30 +1121,20 @@ class ObjectSetViewModel(
|
|||
dispatch(ObjectSetCommand.Modal.OpenCoverActionMenu(ctx = context))
|
||||
}
|
||||
|
||||
fun onViewerSettingsClicked() {
|
||||
Timber.d("onViewerSettingsClicked, ")
|
||||
fun onViewerSettingsClicked(viewer: Id) {
|
||||
Timber.d("onViewerSettingsClicked, viewer: [$viewer]")
|
||||
if (isRestrictionPresent(DataViewRestriction.RELATION)) {
|
||||
toast(NOT_ALLOWED)
|
||||
} else {
|
||||
val state = stateReducer.state.value.dataViewState() ?: return
|
||||
val dataViewBlock = state.dataViewBlock
|
||||
val dataViewContent = state.dataViewContent
|
||||
if (dataViewContent.viewers.isNotEmpty()) {
|
||||
val viewer = state.viewerById(session.currentViewerId.value)
|
||||
if (viewer == null) {
|
||||
Timber.e("onViewerSettingsClicked, Viewer is empty")
|
||||
return
|
||||
}
|
||||
dispatch(
|
||||
ObjectSetCommand.Modal.OpenSettings(
|
||||
ctx = context,
|
||||
dv = dataViewBlock.id,
|
||||
viewer = viewer.id
|
||||
)
|
||||
dispatch(
|
||||
ObjectSetCommand.Modal.OpenSettings(
|
||||
ctx = context,
|
||||
dv = dataViewBlock.id,
|
||||
viewer = viewer
|
||||
)
|
||||
} else {
|
||||
toast(DATA_VIEW_HAS_NO_VIEW_MSG)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1789,7 +1803,19 @@ class ObjectSetViewModel(
|
|||
)
|
||||
}
|
||||
}
|
||||
is ViewersWidgetUi.Action.Edit -> TODO()
|
||||
is ViewersWidgetUi.Action.Edit -> {
|
||||
val state = stateReducer.state.value.dataViewState() ?: return
|
||||
val viewer = state.viewerById(action.id) ?: return
|
||||
viewModelScope.launch {
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.updateState(
|
||||
dvViewer = viewer,
|
||||
storeOfRelations = storeOfRelations,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
isDefaultObjectTypeEnabled = true
|
||||
)
|
||||
.copy(showWidget = true)
|
||||
}
|
||||
}
|
||||
is ViewersWidgetUi.Action.OnMove -> {
|
||||
Timber.d("onMove Viewer, from:[$action.from], to:[$action.to]")
|
||||
if (action.from == action.to) return
|
||||
|
@ -1857,6 +1883,61 @@ class ObjectSetViewModel(
|
|||
toast(DATA_VIEW_HAS_NO_VIEW_MSG)
|
||||
}
|
||||
}
|
||||
|
||||
fun onViewerEditWidgetAction(action: ViewerEditWidgetUi.Action) {
|
||||
Timber.d("onViewerEditWidgetAction, action:[$action]")
|
||||
when (action) {
|
||||
ViewerEditWidgetUi.Action.Dismiss -> {
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.copy(showWidget = false)
|
||||
}
|
||||
is ViewerEditWidgetUi.Action.DefaultObjectType -> TODO()
|
||||
is ViewerEditWidgetUi.Action.Filters -> {
|
||||
viewersWidgetState.value = viewersWidgetState.value.copy(showWidget = false)
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.copy(showWidget = false)
|
||||
viewModelScope.launch {
|
||||
delay(DELAY_BEFORE_CREATING_TEMPLATE)
|
||||
openViewerFilters(viewerId = action.id)
|
||||
}
|
||||
}
|
||||
is ViewerEditWidgetUi.Action.Layout -> TODO()
|
||||
is ViewerEditWidgetUi.Action.Relations -> {
|
||||
viewersWidgetState.value = viewersWidgetState.value.copy(showWidget = false)
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.copy(showWidget = false)
|
||||
if (action.id == null) return
|
||||
viewModelScope.launch {
|
||||
delay(DELAY_BEFORE_CREATING_TEMPLATE)
|
||||
onViewerSettingsClicked(action.id)
|
||||
}
|
||||
}
|
||||
is ViewerEditWidgetUi.Action.Sorts -> {
|
||||
viewersWidgetState.value = viewersWidgetState.value.copy(showWidget = false)
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.copy(showWidget = false)
|
||||
viewModelScope.launch {
|
||||
delay(DELAY_BEFORE_CREATING_TEMPLATE)
|
||||
openViewerSorts(viewerId = action.id)
|
||||
}
|
||||
}
|
||||
is ViewerEditWidgetUi.Action.UpdateName -> {
|
||||
viewersWidgetState.value = viewersWidgetState.value.copy(showWidget = false)
|
||||
viewerEditWidgetState.value = viewerEditWidgetState.value.copy(showWidget = false)
|
||||
val state = stateReducer.state.value.dataViewState() ?: return
|
||||
val viewer = state.viewerById(action.id) ?: return
|
||||
viewModelScope.launch {
|
||||
viewerDelegate.onEvent(
|
||||
ViewerEvent.Rename(
|
||||
ctx = context,
|
||||
dv = state.dataViewBlock.id,
|
||||
viewer = viewer.copy(name = action.name)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ViewerEditWidgetUi.Action.More -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
// region CREATE OBJECT
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package com.anytypeio.anytype.presentation.sets
|
||||
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectTypeIds
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfRelations
|
||||
|
||||
data class ViewerEditWidgetUi(
|
||||
val showWidget: Boolean,
|
||||
val id: Id? = null,
|
||||
val name: String = "",
|
||||
val viewerId: Id,
|
||||
val defaultObjectType: ObjectWrapper.Type?,
|
||||
val isDefaultObjectTypeEnabled: Boolean = false,
|
||||
val layout: DVViewerType?,
|
||||
val relations: List<Id> = emptyList(),
|
||||
val filters: List<Id> = emptyList(),
|
||||
val sorts: List<Id> = emptyList(),
|
||||
val defaultTemplate: Id? = null,
|
||||
) {
|
||||
|
||||
fun empty() = this.copy(
|
||||
id = null,
|
||||
name = "",
|
||||
layout = null,
|
||||
relations = emptyList(),
|
||||
filters = emptyList(),
|
||||
sorts = emptyList(),
|
||||
defaultTemplate = null,
|
||||
defaultObjectType = null,
|
||||
isDefaultObjectTypeEnabled = false,
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun init() = ViewerEditWidgetUi(
|
||||
showWidget = false,
|
||||
id = null,
|
||||
name = "",
|
||||
layout = null,
|
||||
relations = emptyList(),
|
||||
filters = emptyList(),
|
||||
sorts = emptyList(),
|
||||
defaultTemplate = null,
|
||||
defaultObjectType = null,
|
||||
isDefaultObjectTypeEnabled = false,
|
||||
viewerId = "",
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Action {
|
||||
object Dismiss : Action()
|
||||
data class UpdateName(val id: Id?, val name: String) : Action()
|
||||
data class DefaultObjectType(val id: Id?) : Action()
|
||||
data class Layout(val id: Id?) : Action()
|
||||
data class Relations(val id: Id?) : Action()
|
||||
data class Filters(val id: Id?) : Action()
|
||||
data class Sorts(val id: Id?) : Action()
|
||||
object More: Action()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> List<T>.toView(
|
||||
storeOfRelations: StoreOfRelations,
|
||||
mapper: (T) -> String
|
||||
): List<String> =
|
||||
mapNotNull {
|
||||
val relation = storeOfRelations.getByKey(mapper(it))
|
||||
relation?.name.orEmpty().takeIf { _ -> relation != null }
|
||||
}
|
||||
|
||||
suspend fun ViewerEditWidgetUi.updateState(
|
||||
dvViewer: DVViewer,
|
||||
storeOfRelations: StoreOfRelations,
|
||||
storeOfObjectTypes: StoreOfObjectTypes,
|
||||
isDefaultObjectTypeEnabled: Boolean
|
||||
): ViewerEditWidgetUi {
|
||||
|
||||
val viewerDefaultObjectTypeId = dvViewer.defaultObjectType ?: ObjectTypeIds.PAGE
|
||||
val viewerDefaultTemplateId = dvViewer.defaultTemplate
|
||||
val defaultObjectType = storeOfObjectTypes.get(viewerDefaultObjectTypeId)
|
||||
return this.copy(
|
||||
id = dvViewer.id,
|
||||
name = dvViewer.name,
|
||||
sorts = dvViewer.sorts.toView(storeOfRelations) { it.relationKey },
|
||||
filters = dvViewer.filters.toView(storeOfRelations) { it.relation },
|
||||
relations = dvViewer.viewerRelations.toView(storeOfRelations) { it.key },
|
||||
layout = dvViewer.type,
|
||||
defaultObjectType = defaultObjectType,
|
||||
isDefaultObjectTypeEnabled = isDefaultObjectTypeEnabled,
|
||||
defaultTemplate = viewerDefaultTemplateId
|
||||
)
|
||||
}
|
|
@ -10,7 +10,11 @@ data class ViewerView(
|
|||
val isActive: Boolean,
|
||||
val showActionMenu: Boolean = false,
|
||||
val isUnsupported: Boolean = false,
|
||||
val defaultObjectType: Id?
|
||||
val defaultObjectType: Id?,
|
||||
val relations: List<Id>,
|
||||
val sorts: List<Id>,
|
||||
val filters: List<Id>,
|
||||
val isDefaultObjectTypeEnabled: Boolean
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -162,6 +162,11 @@ class MockSet(context: String, val setOfValue: String = "setOf-${RandomString.ma
|
|||
views = listOf(),
|
||||
relationLinks = listOf()
|
||||
)
|
||||
val dataViewWith3Views = StubDataView(
|
||||
id = "dv-${RandomString.make()}",
|
||||
views = listOf(viewerGrid, viewerGallery, viewerList),
|
||||
relationLinks = listOf(relationLink1, relationLink2, relationLink3, relationLink4, relationLink5)
|
||||
)
|
||||
|
||||
// RECORDS
|
||||
val obj1 = StubObject(id = "object-${RandomString.make()}")
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
package com.anytypeio.anytype.presentation.sets
|
||||
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectTypeIds
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.StubObject
|
||||
import com.anytypeio.anytype.presentation.collections.MockSet
|
||||
import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup
|
||||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class EditViewerWidgetStateTest : ObjectSetViewModelTestSetup() {
|
||||
|
||||
private lateinit var closable: AutoCloseable
|
||||
private lateinit var viewModel: ObjectSetViewModel
|
||||
private lateinit var mockObjectSet: MockSet
|
||||
|
||||
val templateView = StubObject(objectType = ObjectTypeIds.PAGE)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
closable = MockitoAnnotations.openMocks(this)
|
||||
viewModel = givenViewModel()
|
||||
runTest {
|
||||
mockObjectSet = MockSet(context = root, setOfValue = ObjectTypeIds.PAGE)
|
||||
val pageTypeMap = mapOf(
|
||||
Relations.ID to ObjectTypeIds.PAGE,
|
||||
Relations.TYPE to ObjectTypeIds.OBJECT_TYPE,
|
||||
Relations.RECOMMENDED_LAYOUT to ObjectType.Layout.BASIC.code.toDouble(),
|
||||
Relations.NAME to MockDataFactory.randomString()
|
||||
)
|
||||
stubWorkspaceManager(mockObjectSet.workspaceId)
|
||||
stubInterceptEvents()
|
||||
stubInterceptThreadStatus()
|
||||
stubOpenObject(
|
||||
doc = listOf(mockObjectSet.header, mockObjectSet.title, mockObjectSet.dataViewWith3Views),
|
||||
details = mockObjectSet.details
|
||||
)
|
||||
stubSubscriptionResults(
|
||||
subscription = mockObjectSet.subscriptionId,
|
||||
workspace = mockObjectSet.workspaceId,
|
||||
storeOfRelations = storeOfRelations,
|
||||
keys = mockObjectSet.dvKeys,
|
||||
sources = listOf(ObjectTypeIds.PAGE),
|
||||
dvFilters = mockObjectSet.filters
|
||||
)
|
||||
stubStoreOfObjectTypes(pageTypeMap)
|
||||
stubTemplatesContainer(
|
||||
type = ObjectTypeIds.PAGE,
|
||||
templates = listOf(templateView)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
rule.advanceTime()
|
||||
closable.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default object type for viewer should be PAGE`() {
|
||||
|
||||
//DataView with 3 Views: View1(Grid), View2(Gallery), View3(List)
|
||||
//View1(Grid) is active
|
||||
|
||||
session.currentViewerId.value = mockObjectSet.viewerGrid.id
|
||||
|
||||
// TESTING
|
||||
viewModel.onStart(ctx = root)
|
||||
viewModel.onViewersWidgetAction(action = ViewersWidgetUi.Action.Edit(mockObjectSet.viewerGallery.id))
|
||||
|
||||
// VERIFY
|
||||
val expected = ViewerEditWidgetUi(
|
||||
showWidget = true,
|
||||
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected = ObjectTypeIds.AUDIO,
|
||||
actual = viewModel.viewerEditWidgetState.value.defaultObjectType?.id
|
||||
)
|
||||
}
|
||||
}
|
|
@ -256,7 +256,7 @@ class ObjectSetRestrictionsTest : ObjectSetViewModelTestSetup() {
|
|||
|
||||
// ASSERT ERROR TOAST
|
||||
viewModel.toasts.test {
|
||||
viewModel.onViewerSettingsClicked()
|
||||
viewModel.onViewerSettingsClicked(viewer = mockObjectSet.viewer.id)
|
||||
assertEquals(ObjectSetViewModel.NOT_ALLOWED, awaitItem())
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
|
|
|
@ -77,6 +77,6 @@ class ObjectSetZeroViewTest : ObjectSetViewModelTestSetup() {
|
|||
vm.onViewerCustomizeButtonClicked()
|
||||
vm.onViewerSortsClicked()
|
||||
vm.onViewerFiltersClicked()
|
||||
vm.onViewerSettingsClicked()
|
||||
vm.onViewerSettingsClicked("")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue