1
0
Fork 0
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:
Konstantin Ivanov 2023-09-13 19:48:02 +02:00 committed by GitHub
parent c751cd0291
commit ee0e2fa0d6
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 706 additions and 36 deletions

View file

@ -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()
}

View file

@ -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>

View file

@ -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 = {})
}

View file

@ -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)

View file

@ -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>

View file

@ -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"
}
}

View file

@ -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
)
}
}

View file

@ -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

View file

@ -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
)
}

View file

@ -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
)

View file

@ -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()}")

View file

@ -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
)
}
}

View file

@ -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()
}

View file

@ -77,6 +77,6 @@ class ObjectSetZeroViewTest : ObjectSetViewModelTestSetup() {
vm.onViewerCustomizeButtonClicked()
vm.onViewerSortsClicked()
vm.onViewerFiltersClicked()
vm.onViewerSettingsClicked()
vm.onViewerSettingsClicked("")
}
}