DROID-2121 Relations | Tags & status screen logic (#817)
This commit is contained in:
parent
6b7d95b9e7
commit
d4d1b3ea3e
|
@ -8,7 +8,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
|||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.options.GetOptions
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModel
|
||||
|
@ -46,7 +46,7 @@ object TagStatusObjectModule {
|
|||
fun provideFactory(
|
||||
@Named(ObjectRelationProvider.INTRINSIC_PROVIDER_TYPE) relations: ObjectRelationProvider,
|
||||
@Named(ObjectRelationProvider.INTRINSIC_PROVIDER_TYPE) values: ObjectValueProvider,
|
||||
details: ObjectDetailProvider,
|
||||
storage: Editor.Storage,
|
||||
storeOfObjectTypes: StoreOfObjectTypes,
|
||||
urlBuilder: UrlBuilder,
|
||||
setObjectDetails: UpdateDetail,
|
||||
|
@ -58,7 +58,7 @@ object TagStatusObjectModule {
|
|||
): TagStatusViewModelFactory = TagStatusViewModelFactory(
|
||||
params = params,
|
||||
values = values,
|
||||
details = details,
|
||||
storage = storage,
|
||||
relations = relations,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
urlBuilder = urlBuilder,
|
||||
|
|
|
@ -151,6 +151,7 @@ import com.anytypeio.anytype.ui.relations.RelationAddToObjectBlockFragment
|
|||
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment
|
||||
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
|
||||
import com.anytypeio.anytype.ui.relations.RelationValueFragment
|
||||
import com.anytypeio.anytype.ui.relations.value.TagStatusFragment
|
||||
import com.anytypeio.anytype.ui.spaces.SelectSpaceFragment
|
||||
import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.ARG_TEMPLATE_ID
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
|
|
@ -4,19 +4,27 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_ui.relations.RelationsValueScreen
|
||||
import com.anytypeio.anytype.core_utils.ext.argBoolean
|
||||
import com.anytypeio.anytype.core_utils.ext.argString
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationContext
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewModelFactory
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import javax.inject.Inject
|
||||
|
||||
class TagStatusFragment : BaseBottomSheetComposeFragment() {
|
||||
|
@ -28,6 +36,8 @@ class TagStatusFragment : BaseBottomSheetComposeFragment() {
|
|||
private val ctx get() = argString(CTX_KEY)
|
||||
private val relationKey get() = argString(RELATION_KEY)
|
||||
private val objectId get() = argString(OBJECT_ID_KEY)
|
||||
private val isLocked get() = argBoolean(IS_LOCKED_KEY)
|
||||
private val relationContext get() = requireArguments().getSerializable(RELATION_CONTEXT_KEY) as RelationContext
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -36,11 +46,24 @@ class TagStatusFragment : BaseBottomSheetComposeFragment() {
|
|||
): View = ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
RelationsValueScreen(
|
||||
state = vm.viewState.collectAsStateWithLifecycle().value,
|
||||
action = vm::onAction
|
||||
)
|
||||
MaterialTheme(
|
||||
typography = typography,
|
||||
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(10.dp)),
|
||||
colors = MaterialTheme.colors.copy(
|
||||
surface = colorResource(id = R.color.context_menu_background)
|
||||
)
|
||||
) {
|
||||
RelationsValueScreen(
|
||||
state = vm.viewState.collectAsStateWithLifecycle().value,
|
||||
action = vm::onAction,
|
||||
onQueryChanged = vm::onQueryChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
vm.onStart()
|
||||
}
|
||||
|
||||
|
@ -48,7 +71,9 @@ class TagStatusFragment : BaseBottomSheetComposeFragment() {
|
|||
val params = TagStatusViewModel.Params(
|
||||
ctx = ctx,
|
||||
objectId = objectId,
|
||||
relationKey = relationKey
|
||||
relationKey = relationKey,
|
||||
isLocked = isLocked,
|
||||
relationContext = relationContext
|
||||
)
|
||||
componentManager()
|
||||
.tagStatusObjectComponent.get(params)
|
||||
|
@ -63,17 +88,23 @@ class TagStatusFragment : BaseBottomSheetComposeFragment() {
|
|||
fun new(
|
||||
ctx: Id,
|
||||
objectId: Id,
|
||||
relationKey: Key
|
||||
relationKey: Key,
|
||||
isLocked: Boolean,
|
||||
relationContext: RelationContext
|
||||
) = TagStatusFragment().apply {
|
||||
arguments = bundleOf(
|
||||
CTX_KEY to ctx,
|
||||
OBJECT_ID_KEY to objectId,
|
||||
RELATION_KEY to relationKey
|
||||
RELATION_KEY to relationKey,
|
||||
IS_LOCKED_KEY to isLocked,
|
||||
RELATION_CONTEXT_KEY to relationContext
|
||||
)
|
||||
}
|
||||
|
||||
const val CTX_KEY = "arg.tag-status.ctx"
|
||||
const val RELATION_KEY = "arg.tag-status.relation.key"
|
||||
const val OBJECT_ID_KEY = "arg.tag-status.object"
|
||||
const val IS_LOCKED_KEY = "arg.tag-status.is-locked"
|
||||
const val RELATION_CONTEXT_KEY = "arg.tag-status.relation-context"
|
||||
}
|
||||
}
|
|
@ -22,9 +22,11 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
||||
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.presentation.relations.model.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusAction
|
||||
|
||||
@Composable
|
||||
fun CommonContainer(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit) {
|
||||
|
@ -81,8 +83,10 @@ fun CheckedIcon(isSelected: Boolean, modifier: Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ItemTagOrStatusCreate(state: RelationsListItem.CreateItem) {
|
||||
CommonContainer(modifier = Modifier.padding(top = 8.dp)) {
|
||||
fun ItemTagOrStatusCreate(state: RelationsListItem.CreateItem, action: (TagStatusAction) -> Unit) {
|
||||
CommonContainer(modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.noRippleClickable { action(TagStatusAction.Click(state)) }) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package com.anytypeio.anytype.core_ui.relations
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.foundation.Divider
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCallout
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusAction
|
||||
|
||||
@Composable
|
||||
fun ItemMenu(
|
||||
item: RelationsListItem.Item?,
|
||||
action: (TagStatusAction) -> Unit,
|
||||
isMenuExpanded: MutableState<Boolean>
|
||||
) {
|
||||
if (item != null) {
|
||||
DropdownMenu(
|
||||
expanded = isMenuExpanded.value,
|
||||
onDismissRequest = { isMenuExpanded.value = false },
|
||||
modifier = Modifier.width(220.dp)
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 11.dp),
|
||||
onClick = {
|
||||
isMenuExpanded.value = false
|
||||
action(TagStatusAction.Edit(item.optionId))
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.edit),
|
||||
style = BodyCallout,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
)
|
||||
}
|
||||
Divider(paddingEnd = 0.dp, paddingStart = 0.dp)
|
||||
DropdownMenuItem(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 11.dp),
|
||||
onClick = {
|
||||
isMenuExpanded.value = false
|
||||
action(TagStatusAction.Duplicate(item.optionId))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.duplicate),
|
||||
style = BodyCallout,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
)
|
||||
}
|
||||
Divider(paddingEnd = 0.dp, paddingStart = 0.dp)
|
||||
DropdownMenuItem(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 11.dp),
|
||||
onClick = {
|
||||
isMenuExpanded.value = false
|
||||
action(TagStatusAction.Delete(item.optionId))
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.delete),
|
||||
style = BodyCallout,
|
||||
color = colorResource(id = R.color.palette_system_red),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ 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 com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.foundation.Divider
|
||||
import com.anytypeio.anytype.core_ui.foundation.Dragger
|
||||
|
@ -35,14 +36,15 @@ import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
|
|||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.core_ui.views.UXBody
|
||||
import com.anytypeio.anytype.core_ui.widgets.SearchField
|
||||
import com.anytypeio.anytype.presentation.relations.RelationValueView
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusAction
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusViewState
|
||||
|
||||
@Composable
|
||||
fun RelationsValueScreen(
|
||||
state: TagStatusViewState,
|
||||
action: (TagStatusAction) -> Unit
|
||||
action: (TagStatusAction) -> Unit,
|
||||
onQueryChanged: (String) -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
@ -62,7 +64,7 @@ fun RelationsValueScreen(
|
|||
Header(state = state, action = action)
|
||||
SearchField(
|
||||
onFocused = {},
|
||||
onQueryChanged = { s -> }
|
||||
onQueryChanged = onQueryChanged
|
||||
)
|
||||
Divider(paddingEnd = 0.dp, paddingStart = 0.dp)
|
||||
RelationsLazyList(state = state, action = action)
|
||||
|
@ -170,10 +172,10 @@ fun RelationsViewContent(
|
|||
items = state.items,
|
||||
itemContent = { _, item ->
|
||||
when (item) {
|
||||
// is RelationValueView.Create -> ItemTagOrStatusCreate(state = item)
|
||||
// is RelationValueView.Option.Status -> StatusItem(state = item)
|
||||
is RelationValueView.Option.Tag -> TagItem(state = item, action = action)
|
||||
else -> TODO()
|
||||
is RelationsListItem.Item.Tag -> TagItem(item, action)
|
||||
is RelationsListItem.Item.Status -> StatusItem(item, action)
|
||||
is RelationsListItem.CreateItem.Status -> ItemTagOrStatusCreate(item, action)
|
||||
is RelationsListItem.CreateItem.Tag -> ItemTagOrStatusCreate(item, action)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -191,7 +193,8 @@ fun RelationsViewLoading() {
|
|||
|
||||
private fun isClearButtonVisible(state: TagStatusViewState): Boolean {
|
||||
if (state !is TagStatusViewState.Content) return false
|
||||
return state.items.any { it is RelationValueView.Option.Tag && it.isSelected } && state.isRelationEditable
|
||||
return state.items.any { it is RelationsListItem.Item.Tag && it.isSelected
|
||||
|| it is RelationsListItem.Item.Status && it.isSelected } && state.isRelationEditable
|
||||
}
|
||||
|
||||
private fun isPlusButtonVisible(state: TagStatusViewState): Boolean {
|
||||
|
@ -217,26 +220,19 @@ fun MyWidgetHeader() {
|
|||
isRelationEditable = true,
|
||||
title = "Tags",
|
||||
items = listOf(
|
||||
RelationValueView.Option.Tag(
|
||||
RelationsListItem.Item.Tag(
|
||||
name = "Urgent",
|
||||
color = "red",
|
||||
//number = 1,
|
||||
color = ThemeColor.RED,
|
||||
number = 1,
|
||||
isSelected = true,
|
||||
id = "1",
|
||||
removable = false,
|
||||
isCheckboxShown = false
|
||||
optionId = "1"
|
||||
),
|
||||
RelationValueView.Option.Tag(
|
||||
RelationsListItem.Item.Tag(
|
||||
name = "Personal",
|
||||
color = "orange",
|
||||
//number = 1,
|
||||
color = ThemeColor.ORANGE,
|
||||
number = 2,
|
||||
isSelected = false,
|
||||
id = "1",
|
||||
removable = false,
|
||||
isCheckboxShown = false
|
||||
),
|
||||
RelationValueView.Create(
|
||||
name = "Done"
|
||||
optionId = "1"
|
||||
)
|
||||
)
|
||||
), action = {})
|
||||
|
|
|
@ -10,19 +10,39 @@ import androidx.compose.foundation.layout.wrapContentWidth
|
|||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.extensions.dark
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleCombinedClickable
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.presentation.relations.model.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusAction
|
||||
|
||||
@Composable
|
||||
fun StatusItem(state: RelationsListItem.Item.Status) {
|
||||
CommonContainer {
|
||||
fun StatusItem(
|
||||
state: RelationsListItem.Item.Status,
|
||||
action: (TagStatusAction) -> Unit
|
||||
) {
|
||||
val haptics = LocalHapticFeedback.current
|
||||
val isMenuExpanded = remember { mutableStateOf(false) }
|
||||
CommonContainer(
|
||||
modifier = Modifier
|
||||
.noRippleCombinedClickable(
|
||||
onClick = { action(TagStatusAction.Click(state)) },
|
||||
onLongClicked = {
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
isMenuExpanded.value = !isMenuExpanded.value
|
||||
}
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -38,13 +58,18 @@ fun StatusItem(state: RelationsListItem.Item.Status) {
|
|||
.align(Alignment.CenterEnd)
|
||||
)
|
||||
Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
ItemMenu(
|
||||
item = state,
|
||||
action = action,
|
||||
isMenuExpanded = isMenuExpanded
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StatusItemText(state: RelationsListItem.Item.Status) {
|
||||
Text(
|
||||
text = state.text,
|
||||
text = state.name,
|
||||
color = dark(state.color),
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
|
@ -65,22 +90,27 @@ fun PreviewStatusItem() {
|
|||
) {
|
||||
StatusItem(
|
||||
state = RelationsListItem.Item.Status(
|
||||
text = "In development",
|
||||
optionId = "1",
|
||||
name = "In development",
|
||||
color = ThemeColor.RED,
|
||||
isSelected = true
|
||||
)
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
StatusItem(
|
||||
state = RelationsListItem.Item.Status(
|
||||
text = "Designer",
|
||||
optionId = "2",
|
||||
name = "Designer",
|
||||
color = ThemeColor.TEAL,
|
||||
isSelected = false
|
||||
)
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
ItemTagOrStatusCreate(
|
||||
state = RelationsListItem.CreateItem.Status(
|
||||
text = "Personal"
|
||||
)
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,39 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.extensions.dark
|
||||
import com.anytypeio.anytype.core_ui.extensions.light
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleCombinedClickable
|
||||
import com.anytypeio.anytype.core_ui.views.Relations1
|
||||
import com.anytypeio.anytype.presentation.relations.RelationValueView
|
||||
import com.anytypeio.anytype.presentation.relations.model.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationsListItem
|
||||
import com.anytypeio.anytype.presentation.relations.value.tagstatus.TagStatusAction
|
||||
|
||||
@Composable
|
||||
fun TagItem(state: RelationValueView.Option.Tag, action: (TagStatusAction) -> Unit) {
|
||||
fun TagItem(
|
||||
state: RelationsListItem.Item.Tag,
|
||||
action: (TagStatusAction) -> Unit
|
||||
) {
|
||||
val haptics = LocalHapticFeedback.current
|
||||
val isMenuExpanded = remember { mutableStateOf(false) }
|
||||
CommonContainer(
|
||||
modifier = Modifier.noRippleClickable { action(TagStatusAction.Click(state)) }
|
||||
modifier = Modifier
|
||||
.noRippleCombinedClickable(
|
||||
onClick = { action(TagStatusAction.Click(state)) },
|
||||
onLongClicked = {
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
isMenuExpanded.value = !isMenuExpanded.value
|
||||
}
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
@ -40,26 +55,30 @@ fun TagItem(state: RelationValueView.Option.Tag, action: (TagStatusAction) -> Un
|
|||
TagItemText(state = state)
|
||||
}
|
||||
CircleIcon(
|
||||
//number = if (state.isSelected) state.number.toString() else null,
|
||||
number = if (state.isSelected) state.number.toString() else null,
|
||||
isSelected = state.isSelected,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.size(24.dp)
|
||||
.align(Alignment.CenterEnd)
|
||||
)
|
||||
Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
ItemMenu(
|
||||
item = state,
|
||||
action = action,
|
||||
isMenuExpanded = isMenuExpanded
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TagItemText(state: RelationValueView.Option.Tag) {
|
||||
val themeColor = ThemeColor.values().find { it.code == state.color } ?: ThemeColor.DEFAULT
|
||||
fun TagItemText(state: RelationsListItem.Item.Tag) {
|
||||
Text(
|
||||
text = state.name,
|
||||
color = dark(themeColor),
|
||||
color = dark(state.color),
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.background(
|
||||
color = light(color = themeColor),
|
||||
color = light(color = state.color),
|
||||
shape = RoundedCornerShape(size = 3.dp)
|
||||
)
|
||||
.padding(start = 6.dp, end = 6.dp),
|
||||
|
@ -78,33 +97,30 @@ fun PreviewTagItem() {
|
|||
.wrapContentHeight()
|
||||
) {
|
||||
TagItem(
|
||||
state = RelationValueView.Option.Tag(
|
||||
state = RelationsListItem.Item.Tag(
|
||||
name = "Urgent",
|
||||
color = "red",
|
||||
//number = 1,
|
||||
color = ThemeColor.RED,
|
||||
number = 1,
|
||||
isSelected = true,
|
||||
id = "1",
|
||||
removable = false,
|
||||
isCheckboxShown = false
|
||||
optionId = "1"
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
TagItem(
|
||||
state = RelationValueView.Option.Tag(
|
||||
state = RelationsListItem.Item.Tag(
|
||||
name = "Personal",
|
||||
color = "orange",
|
||||
//number = 1,
|
||||
color = ThemeColor.ORANGE,
|
||||
number = 1,
|
||||
isSelected = true,
|
||||
id = "1",
|
||||
removable = false,
|
||||
isCheckboxShown = false
|
||||
optionId = "1"
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
ItemTagOrStatusCreate(
|
||||
state = RelationsListItem.CreateItem.Tag(
|
||||
text = "Done"
|
||||
)
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -63,11 +63,11 @@ class GetOptions(
|
|||
Relations.NAME,
|
||||
Relations.RELATION_OPTION_COLOR
|
||||
),
|
||||
fulltext = "",
|
||||
fulltext = params.fulltext,
|
||||
).map {
|
||||
ObjectWrapper.Option(it)
|
||||
}
|
||||
}
|
||||
|
||||
data class Params(val space: Id, val relation: Key)
|
||||
data class Params(val space: Id, val relation: Key, val fulltext: String = "")
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package com.anytypeio.anytype.presentation.relations.model
|
||||
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
|
||||
sealed class RelationsListItem {
|
||||
|
||||
abstract val text: String
|
||||
|
||||
sealed class Item : RelationsListItem() {
|
||||
data class Tag(
|
||||
override val text: String,
|
||||
val color: ThemeColor,
|
||||
val isSelected: Boolean,
|
||||
val number: Int? = null
|
||||
) : Item()
|
||||
|
||||
data class Status(
|
||||
override val text: String,
|
||||
val color: ThemeColor,
|
||||
val isSelected: Boolean
|
||||
) : Item()
|
||||
}
|
||||
|
||||
sealed class CreateItem(
|
||||
override val text: String
|
||||
) : RelationsListItem() {
|
||||
class Tag(text: String) : CreateItem(text)
|
||||
class Status(text: String) : CreateItem(text)
|
||||
}
|
||||
}
|
|
@ -1,27 +1,40 @@
|
|||
package com.anytypeio.anytype.presentation.relations.value.tagstatus
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Relation
|
||||
import com.anytypeio.anytype.core_models.Struct
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
import com.anytypeio.anytype.core_utils.ext.typeOf
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.options.GetOptions
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.relations.RelationValueView
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRelationValueEvent
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.sets.filterIdsById
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class TagStatusViewModel(
|
||||
private val params: Params,
|
||||
private val relations: ObjectRelationProvider,
|
||||
private val values: ObjectValueProvider,
|
||||
private val details: ObjectDetailProvider,
|
||||
private val storage: Editor.Storage,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
|
@ -32,19 +45,298 @@ class TagStatusViewModel(
|
|||
) : BaseViewModel() {
|
||||
|
||||
val viewState = MutableStateFlow<TagStatusViewState>(TagStatusViewState.Loading)
|
||||
private val query = MutableSharedFlow<String>()
|
||||
private var isRelationNotEditable = false
|
||||
|
||||
fun onStart() {
|
||||
Timber.d("TagStatusViewModel onStart, params: $params")
|
||||
val obj = storage.details.current().details[params.objectId]
|
||||
isRelationNotEditable = params.isLocked || storage.objectRestrictions.current()
|
||||
.contains(ObjectRestriction.RELATIONS)
|
||||
Timber.d("TagStatusViewModel onStart, params: $params, obj: $obj, isRelationNotEditable: $isRelationNotEditable")
|
||||
viewModelScope.launch {
|
||||
combine(
|
||||
relations.observe(
|
||||
relation = params.relationKey
|
||||
),
|
||||
values.subscribe(
|
||||
ctx = params.ctx,
|
||||
target = params.objectId
|
||||
),
|
||||
query.onStart { emit("") }
|
||||
) { relation, record, query ->
|
||||
setupIsRelationNotEditable(relation)
|
||||
getAllOptions(
|
||||
relation = relation,
|
||||
record = record,
|
||||
query = query
|
||||
)
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fun onQueryChanged(input: String) {
|
||||
viewModelScope.launch {
|
||||
query.emit(input)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAction(action: TagStatusAction) {
|
||||
Timber.d("TagStatusViewModel onAction, action: $action")
|
||||
if (isRelationNotEditable) {
|
||||
Timber.d("TagStatusViewModel onAction, relation is not editable")
|
||||
sendToast("Relation is not editable")
|
||||
return
|
||||
}
|
||||
when (action) {
|
||||
TagStatusAction.Clear -> clearTagsOrStatus()
|
||||
is TagStatusAction.Click -> onActionClick(action.item)
|
||||
is TagStatusAction.LongClick -> {
|
||||
val currentState = viewState.value
|
||||
if (currentState is TagStatusViewState.Content) {
|
||||
viewState.value = currentState.copy(showItemMenu = action.item)
|
||||
}
|
||||
}
|
||||
TagStatusAction.Plus -> TODO()
|
||||
is TagStatusAction.Delete -> TODO()
|
||||
is TagStatusAction.Duplicate -> TODO()
|
||||
is TagStatusAction.Edit -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onActionClick(item: RelationsListItem) {
|
||||
when (item) {
|
||||
is RelationsListItem.Item.Status -> {
|
||||
if (item.isSelected) {
|
||||
clearTagsOrStatus()
|
||||
} else {
|
||||
addStatus(item.optionId)
|
||||
}
|
||||
}
|
||||
is RelationsListItem.Item.Tag -> {
|
||||
if (item.isSelected) {
|
||||
removeTag(item.optionId)
|
||||
} else {
|
||||
addTag(item.optionId)
|
||||
}
|
||||
}
|
||||
is RelationsListItem.CreateItem.Status -> TODO()
|
||||
is RelationsListItem.CreateItem.Tag -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAllOptions(
|
||||
relation: ObjectWrapper.Relation,
|
||||
record: Struct,
|
||||
query: String
|
||||
) {
|
||||
val params = GetOptions.Params(
|
||||
space = spaceManager.get(),
|
||||
relation = relation.key,
|
||||
fulltext = query
|
||||
)
|
||||
getOptions(params).proceed(
|
||||
success = { options ->
|
||||
Timber.d("TagStatusViewModel getAllOptions, options: ${options.size}")
|
||||
initViewState(
|
||||
relation = relation,
|
||||
record = record,
|
||||
options = options,
|
||||
query = query
|
||||
)
|
||||
},
|
||||
failure = {
|
||||
Timber.e(it, "Error while getting options by id")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun initViewState(
|
||||
relation: ObjectWrapper.Relation,
|
||||
record: Map<String, Any?>,
|
||||
options: List<ObjectWrapper.Option>,
|
||||
query: String
|
||||
) {
|
||||
val result = mutableListOf<RelationsListItem>()
|
||||
when (relation.format) {
|
||||
Relation.Format.STATUS -> {
|
||||
val ids: List<Id> = when (val value = record[params.relationKey]) {
|
||||
is Id -> listOf(value)
|
||||
is List<*> -> value.typeOf()
|
||||
else -> emptyList()
|
||||
}
|
||||
result.addAll(
|
||||
mapStatusOptions(
|
||||
ids = ids,
|
||||
options = options
|
||||
)
|
||||
)
|
||||
}
|
||||
Relation.Format.TAG -> {
|
||||
val ids: List<Id> = when (val value = record[params.relationKey]) {
|
||||
is Id -> listOf(value)
|
||||
is List<*> -> value.typeOf()
|
||||
else -> emptyList()
|
||||
}
|
||||
result.addAll(
|
||||
mapTagOptions(
|
||||
ids = ids,
|
||||
options = options
|
||||
)
|
||||
)
|
||||
if (query.isNotBlank()) {
|
||||
result.add(RelationsListItem.CreateItem.Tag(query))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Relation format should be Tag or Status but was: ${relation.format}")
|
||||
}
|
||||
}
|
||||
|
||||
viewState.value = if (result.isEmpty()) {
|
||||
TagStatusViewState.Empty(
|
||||
isRelationEditable = !isRelationNotEditable,
|
||||
title = relation.name.orEmpty(),
|
||||
)
|
||||
} else {
|
||||
TagStatusViewState.Content(
|
||||
isRelationEditable = !isRelationNotEditable,
|
||||
title = relation.name.orEmpty(),
|
||||
items = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTag(tag: Id) {
|
||||
viewModelScope.launch {
|
||||
val obj = values.get(ctx = params.ctx, target = params.objectId)
|
||||
val result = mutableListOf<Id>()
|
||||
val value = obj[params.relationKey]
|
||||
if (value is List<*>) {
|
||||
result.addAll(value.typeOf())
|
||||
} else if (value is Id) {
|
||||
result.add(value)
|
||||
}
|
||||
result.add(tag)
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
value = result
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while adding tag") },
|
||||
success = {
|
||||
dispatcher.send(it)
|
||||
sendAnalyticsRelationValueEvent(analytics)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeTag(tag: Id) {
|
||||
viewModelScope.launch {
|
||||
val obj = values.get(ctx = params.ctx, target = params.objectId)
|
||||
val remaining = obj[params.relationKey].filterIdsById(tag)
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
value = remaining
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while adding tag") },
|
||||
success = {
|
||||
dispatcher.send(it)
|
||||
sendAnalyticsRelationValueEvent(analytics)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun addStatus(status: Id) {
|
||||
viewModelScope.launch {
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
value = listOf(status)
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while adding tag") },
|
||||
success = {
|
||||
dispatcher.send(it)
|
||||
sendAnalyticsRelationValueEvent(analytics)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearTagsOrStatus() {
|
||||
viewModelScope.launch {
|
||||
setObjectDetails(
|
||||
UpdateDetail.Params(
|
||||
target = params.objectId,
|
||||
key = params.relationKey,
|
||||
value = null
|
||||
)
|
||||
).process(
|
||||
failure = { Timber.e(it, "Error while clearing tags or select") },
|
||||
success = {
|
||||
dispatcher.send(it)
|
||||
sendAnalyticsRelationValueEvent(analytics)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapTagOptions(
|
||||
ids: List<Id>,
|
||||
options: List<ObjectWrapper.Option>
|
||||
) = options.map { option ->
|
||||
val index = ids.indexOf(option.id)
|
||||
val isSelected = index != -1
|
||||
val number = if (isSelected) index + 1 else Int.MAX_VALUE
|
||||
RelationsListItem.Item.Tag(
|
||||
optionId = option.id,
|
||||
name = option.name.orEmpty(),
|
||||
color = getOrDefault(option.color),
|
||||
isSelected = isSelected,
|
||||
number = number
|
||||
)
|
||||
}.sortedBy { it.number }
|
||||
|
||||
private fun mapStatusOptions(
|
||||
ids: List<Id>,
|
||||
options: List<ObjectWrapper.Option>
|
||||
) = options.map { option ->
|
||||
val index = ids.indexOf(option.id)
|
||||
val isSelected = index != -1
|
||||
RelationsListItem.Item.Status(
|
||||
optionId = option.id,
|
||||
name = option.name.orEmpty(),
|
||||
color = getOrDefault(option.color),
|
||||
isSelected = isSelected
|
||||
)
|
||||
}
|
||||
|
||||
private fun getOrDefault(code: String?): ThemeColor {
|
||||
return ThemeColor.values().find { it.code == code } ?: ThemeColor.DEFAULT
|
||||
}
|
||||
|
||||
private fun setupIsRelationNotEditable(relation: ObjectWrapper.Relation) {
|
||||
isRelationNotEditable = params.isLocked
|
||||
|| storage.objectRestrictions.current().contains(ObjectRestriction.RELATIONS)
|
||||
|| relation.isReadonlyValue
|
||||
|| relation.isHidden == true
|
||||
|| relation.isDeleted == true
|
||||
|| relation.isArchived == true
|
||||
|| !relation.isValid
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val ctx: Id,
|
||||
val objectId: Id,
|
||||
val relationKey: Id
|
||||
val relationKey: Id,
|
||||
val isLocked: Boolean,
|
||||
val relationContext: RelationContext
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -56,14 +348,50 @@ sealed class TagStatusViewState {
|
|||
|
||||
data class Content(
|
||||
val title: String,
|
||||
val items: List<RelationValueView>,
|
||||
val isRelationEditable: Boolean
|
||||
val items: List<RelationsListItem>,
|
||||
val isRelationEditable: Boolean,
|
||||
val showItemMenu: RelationsListItem.Item? = null
|
||||
) : TagStatusViewState()
|
||||
}
|
||||
|
||||
sealed class TagStatusAction {
|
||||
data class Click(val item: RelationValueView) : TagStatusAction()
|
||||
data class LongClick(val item: RelationValueView) : TagStatusAction()
|
||||
data class Click(val item: RelationsListItem) : TagStatusAction()
|
||||
data class LongClick(val item: RelationsListItem.Item) : TagStatusAction()
|
||||
object Clear : TagStatusAction()
|
||||
object Plus : TagStatusAction()
|
||||
data class Edit(val optionId: Id) : TagStatusAction()
|
||||
data class Delete(val optionId: Id) : TagStatusAction()
|
||||
data class Duplicate(val optionId: Id) : TagStatusAction()
|
||||
}
|
||||
|
||||
enum class RelationContext{ OBJECT, OBJECT_SET, DATA_VIEW }
|
||||
|
||||
sealed class RelationsListItem {
|
||||
|
||||
sealed class Item : RelationsListItem() {
|
||||
|
||||
abstract val optionId: Id
|
||||
data class Tag(
|
||||
override val optionId: Id,
|
||||
val name: String,
|
||||
val color: ThemeColor,
|
||||
val isSelected: Boolean,
|
||||
val number: Int = Int.MAX_VALUE,
|
||||
val showMenu: Boolean = false
|
||||
) : Item()
|
||||
|
||||
data class Status(
|
||||
override val optionId: Id,
|
||||
val name: String,
|
||||
val color: ThemeColor,
|
||||
val isSelected: Boolean
|
||||
) : Item()
|
||||
}
|
||||
|
||||
sealed class CreateItem(
|
||||
val text: String
|
||||
) : RelationsListItem() {
|
||||
class Tag(text: String) : CreateItem(text)
|
||||
class Status(text: String) : CreateItem(text)
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
|||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.options.GetOptions
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
|
||||
import com.anytypeio.anytype.presentation.editor.Editor
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
|
||||
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
|
@ -19,7 +19,7 @@ class TagStatusViewModelFactory @Inject constructor(
|
|||
private val params: TagStatusViewModel.Params,
|
||||
private val relations: ObjectRelationProvider,
|
||||
private val values: ObjectValueProvider,
|
||||
private val details: ObjectDetailProvider,
|
||||
private val storage: Editor.Storage,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val dispatcher: Dispatcher<Payload>,
|
||||
|
@ -35,7 +35,7 @@ class TagStatusViewModelFactory @Inject constructor(
|
|||
params = params,
|
||||
relations = relations,
|
||||
values = values,
|
||||
details = details,
|
||||
storage = storage,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
dispatcher = dispatcher,
|
||||
setObjectDetails = setObjectDetails,
|
||||
|
|
Loading…
Reference in New Issue