mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1032 Widgets | Enhancement | Support creating bundled widgets (#2998)
This commit is contained in:
parent
c5866bdf39
commit
12dc893b6b
33 changed files with 1224 additions and 472 deletions
|
@ -52,6 +52,7 @@ import com.anytypeio.anytype.presentation.widgets.FromIndex
|
|||
import com.anytypeio.anytype.presentation.widgets.ToIndex
|
||||
import com.anytypeio.anytype.presentation.widgets.TreePath
|
||||
import com.anytypeio.anytype.presentation.widgets.ViewId
|
||||
import com.anytypeio.anytype.presentation.widgets.Widget
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetId
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.ui.widgets.menu.WidgetActionButton
|
||||
|
@ -71,6 +72,7 @@ fun HomeScreen(
|
|||
widgets: List<WidgetView>,
|
||||
onExpand: (TreePath) -> Unit,
|
||||
onWidgetObjectClicked: (ObjectWrapper.Basic) -> Unit,
|
||||
onWidgetSourceClicked: (Widget.Source) -> Unit,
|
||||
onBundledWidgetClicked: (WidgetId) -> Unit,
|
||||
onCreateWidget: () -> Unit,
|
||||
onEditWidgets: () -> Unit,
|
||||
|
@ -91,6 +93,7 @@ fun HomeScreen(
|
|||
onExpand = onExpand,
|
||||
onWidgetMenuAction = onWidgetMenuAction,
|
||||
onWidgetObjectClicked = onWidgetObjectClicked,
|
||||
onWidgetSourceClicked = onWidgetSourceClicked,
|
||||
onBundledWidgetHeaderClicked = onBundledWidgetClicked,
|
||||
onToggleExpandedWidgetState = onToggleExpandedWidgetState,
|
||||
mode = mode,
|
||||
|
@ -152,6 +155,7 @@ private fun WidgetList(
|
|||
onExpand: (TreePath) -> Unit,
|
||||
onWidgetMenuAction: (WidgetId, DropDownMenuAction) -> Unit,
|
||||
onWidgetObjectClicked: (ObjectWrapper.Basic) -> Unit,
|
||||
onWidgetSourceClicked: (Widget.Source) -> Unit,
|
||||
onBundledWidgetHeaderClicked: (WidgetId) -> Unit,
|
||||
onToggleExpandedWidgetState: (WidgetId) -> Unit,
|
||||
mode: InteractionMode,
|
||||
|
@ -215,6 +219,7 @@ private fun WidgetList(
|
|||
onWidgetMenuAction(item.id, action)
|
||||
},
|
||||
onWidgetObjectClicked = onWidgetObjectClicked,
|
||||
onWidgetSourceClicked = onWidgetSourceClicked,
|
||||
onToggleExpandedWidgetState = onToggleExpandedWidgetState,
|
||||
mode = mode
|
||||
)
|
||||
|
@ -266,7 +271,7 @@ private fun WidgetList(
|
|||
onDropDownMenuAction = { action ->
|
||||
onWidgetMenuAction(item.id, action)
|
||||
},
|
||||
onWidgetObjectClicked = onWidgetObjectClicked,
|
||||
onWidgetSourceClicked = onWidgetSourceClicked,
|
||||
isInEditMode = mode is InteractionMode.Edit
|
||||
)
|
||||
AnimatedVisibility(
|
||||
|
@ -317,6 +322,7 @@ private fun WidgetList(
|
|||
DataViewListWidgetCard(
|
||||
item = item,
|
||||
onWidgetObjectClicked = onWidgetObjectClicked,
|
||||
onWidgetSourceClicked = onWidgetSourceClicked,
|
||||
onDropDownMenuAction = { action ->
|
||||
onWidgetMenuAction(item.id, action)
|
||||
},
|
||||
|
|
|
@ -68,6 +68,7 @@ class HomeScreenFragment : BaseComposeFragment() {
|
|||
vm.onDropDownMenuAction(widget, action)
|
||||
},
|
||||
onWidgetObjectClicked = vm::onWidgetObjectClicked,
|
||||
onWidgetSourceClicked = vm::onWidgetSourceClicked,
|
||||
onChangeWidgetView = vm::onChangeCurrentWidgetView,
|
||||
onToggleExpandedWidgetState = vm::onToggleCollapsedWidgetState,
|
||||
onSearchClicked = {
|
||||
|
|
|
@ -40,9 +40,9 @@ import com.anytypeio.anytype.ui.moving.hideProgress
|
|||
import com.anytypeio.anytype.ui.moving.showProgress
|
||||
import com.anytypeio.anytype.ui.search.ObjectSearchFragment
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class LinkToObjectFragment : BaseBottomSheetTextInputFragment<FragmentObjectSearchBinding>() {
|
||||
|
||||
|
@ -62,7 +62,7 @@ class LinkToObjectFragment : BaseBottomSheetTextInputFragment<FragmentObjectSear
|
|||
|
||||
private val moveToAdapter by lazy {
|
||||
DefaultObjectViewAdapter(
|
||||
onClick = vm::onObjectClicked
|
||||
onDefaultObjectClicked = vm::onObjectClicked
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class MoveToFragment : BaseBottomSheetTextInputFragment<FragmentObjectSearchBind
|
|||
|
||||
private val moveToAdapter by lazy {
|
||||
DefaultObjectViewAdapter(
|
||||
onClick = vm::onObjectClicked
|
||||
onDefaultObjectClicked = vm::onObjectClicked
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class ObjectSearchFragment :
|
|||
|
||||
private val searchAdapter by lazy {
|
||||
DefaultObjectViewAdapter(
|
||||
onClick = vm::onObjectClicked
|
||||
onDefaultObjectClicked = vm::onObjectClicked
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,8 @@ class SelectWidgetSourceFragment : BaseBottomSheetTextInputFragment<FragmentObje
|
|||
|
||||
private val selectWidgetSourceAdapter by lazy {
|
||||
DefaultObjectViewAdapter(
|
||||
onClick = vm::onObjectClicked
|
||||
onDefaultObjectClicked = vm::onObjectClicked,
|
||||
onBundledWidgetSourceClicked = vm::onBundledWidgetSourceClicked
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -83,9 +83,15 @@ class SelectWidgetTypeFragment : BaseBottomSheetComposeFragment() {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (forExistingWidget) {
|
||||
vm.onStartForExistingWidget(currentType = currentType)
|
||||
vm.onStartForExistingWidget(
|
||||
currentType = currentType,
|
||||
source = source
|
||||
)
|
||||
} else {
|
||||
vm.onStartForNewWidget(layout = sourceLayout)
|
||||
vm.onStartForNewWidget(
|
||||
layout = sourceLayout,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.anytypeio.anytype.ui.widgets.types
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -31,11 +32,12 @@ import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
|||
import com.anytypeio.anytype.core_utils.ext.orNull
|
||||
import com.anytypeio.anytype.presentation.home.InteractionMode
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.presentation.objects.getProperName
|
||||
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
||||
import com.anytypeio.anytype.presentation.widgets.ViewId
|
||||
import com.anytypeio.anytype.presentation.widgets.Widget
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetId
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName
|
||||
import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu
|
||||
|
||||
@Composable
|
||||
|
@ -43,6 +45,7 @@ fun DataViewListWidgetCard(
|
|||
item: WidgetView.SetOfObjects,
|
||||
mode: InteractionMode,
|
||||
onWidgetObjectClicked: (ObjectWrapper.Basic) -> Unit,
|
||||
onWidgetSourceClicked: (Widget.Source) -> Unit,
|
||||
onDropDownMenuAction: (DropDownMenuAction) -> Unit,
|
||||
onChangeWidgetView: (WidgetId, ViewId) -> Unit,
|
||||
onToggleExpandedWidgetState: (WidgetId) -> Unit
|
||||
|
@ -72,10 +75,15 @@ fun DataViewListWidgetCard(
|
|||
.padding(horizontal = 0.dp, vertical = 6.dp)
|
||||
) {
|
||||
WidgetHeader(
|
||||
title = item.obj.getProperName(),
|
||||
title = when (val source = item.source) {
|
||||
is Widget.Source.Default -> {
|
||||
source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled)
|
||||
}
|
||||
is Widget.Source.Bundled -> { stringResource(id = source.res()) }
|
||||
},
|
||||
isCardMenuExpanded = isCardMenuExpanded,
|
||||
isHeaderMenuExpanded = isHeaderMenuExpanded,
|
||||
onWidgetHeaderClicked = { onWidgetObjectClicked(item.obj) },
|
||||
onWidgetHeaderClicked = { onWidgetSourceClicked(item.source) },
|
||||
onExpandElement = { onToggleExpandedWidgetState(item.id) },
|
||||
isExpanded = item.isExpanded,
|
||||
onDropDownMenuAction = onDropDownMenuAction
|
||||
|
@ -217,4 +225,11 @@ fun ListWidgetElement(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
fun Widget.Source.Bundled.res() : Int = when(this) {
|
||||
Widget.Source.Bundled.Favorites -> R.string.favorites
|
||||
Widget.Source.Bundled.Recent -> R.string.recent
|
||||
Widget.Source.Bundled.Sets -> R.string.sets
|
||||
}
|
|
@ -29,9 +29,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
||||
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
||||
import com.anytypeio.anytype.presentation.widgets.Widget
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName
|
||||
import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu
|
||||
|
@ -40,7 +40,7 @@ import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu
|
|||
@Composable
|
||||
fun LinkWidgetCard(
|
||||
item: WidgetView.Link,
|
||||
onWidgetObjectClicked: (ObjectWrapper.Basic) -> Unit,
|
||||
onWidgetSourceClicked: (Widget.Source) -> Unit,
|
||||
onDropDownMenuAction: (DropDownMenuAction) -> Unit,
|
||||
isInEditMode: Boolean
|
||||
) {
|
||||
|
@ -66,7 +66,7 @@ fun LinkWidgetCard(
|
|||
}
|
||||
else
|
||||
Modifier.combinedClickable(
|
||||
onClick = { onWidgetObjectClicked(item.obj) },
|
||||
onClick = { onWidgetSourceClicked(item.source) },
|
||||
onLongClick = {
|
||||
isCardMenuExpanded.value = !isCardMenuExpanded.value
|
||||
},
|
||||
|
@ -82,7 +82,12 @@ fun LinkWidgetCard(
|
|||
.height(40.dp)
|
||||
) {
|
||||
Text(
|
||||
text = item.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled),
|
||||
text = when (val source = item.source) {
|
||||
is Widget.Source.Default -> {
|
||||
source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled)
|
||||
}
|
||||
is Widget.Source.Bundled -> { stringResource(id = source.res()) }
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
|
|
|
@ -48,6 +48,7 @@ import com.anytypeio.anytype.presentation.home.InteractionMode
|
|||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
||||
import com.anytypeio.anytype.presentation.widgets.TreePath
|
||||
import com.anytypeio.anytype.presentation.widgets.Widget
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetId
|
||||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.presentation.widgets.getWidgetObjectName
|
||||
|
@ -59,6 +60,7 @@ fun TreeWidgetCard(
|
|||
item: WidgetView.Tree,
|
||||
onExpandElement: (TreePath) -> Unit,
|
||||
onWidgetObjectClicked: (ObjectWrapper.Basic) -> Unit,
|
||||
onWidgetSourceClicked: (Widget.Source) -> Unit,
|
||||
onDropDownMenuAction: (DropDownMenuAction) -> Unit,
|
||||
onToggleExpandedWidgetState: (WidgetId) -> Unit
|
||||
) {
|
||||
|
@ -94,10 +96,15 @@ fun TreeWidgetCard(
|
|||
)
|
||||
) {
|
||||
WidgetHeader(
|
||||
title = item.obj.getWidgetObjectName().orEmpty(),
|
||||
title = when (val source = item.source) {
|
||||
is Widget.Source.Default -> {
|
||||
source.obj.getWidgetObjectName() ?: stringResource(id = R.string.untitled)
|
||||
}
|
||||
is Widget.Source.Bundled -> { stringResource(id = source.res()) }
|
||||
},
|
||||
isCardMenuExpanded = isCardMenuExpanded,
|
||||
isHeaderMenuExpanded = isHeaderMenuExpanded,
|
||||
onWidgetHeaderClicked = { onWidgetObjectClicked(item.obj) },
|
||||
onWidgetHeaderClicked = { onWidgetSourceClicked(item.source) },
|
||||
onExpandElement = { onToggleExpandedWidgetState(item.id) },
|
||||
isExpanded = item.isExpanded,
|
||||
onDropDownMenuAction = onDropDownMenuAction,
|
||||
|
|
|
@ -17,7 +17,9 @@ import com.anytypeio.anytype.core_utils.ext.visible
|
|||
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
|
||||
import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem
|
||||
import com.anytypeio.anytype.presentation.navigation.ObjectView
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchSection
|
||||
import com.anytypeio.anytype.presentation.widgets.source.BundledWidgetSourceView
|
||||
|
||||
@Deprecated("LEGACY SUSPECT")
|
||||
class PageLinksAdapter(
|
||||
|
@ -76,28 +78,42 @@ class PageLinksAdapter(
|
|||
}
|
||||
|
||||
class DefaultObjectViewAdapter(
|
||||
private val onClick: (DefaultObjectView) -> Unit,
|
||||
private val onDefaultObjectClicked: (DefaultObjectView) -> Unit,
|
||||
private val onBundledWidgetSourceClicked: (BundledWidgetSourceView) -> Unit = {}
|
||||
) : ListAdapter<DefaultSearchItem, DefaultObjectViewAdapter.ObjectViewHolder>(Differ) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): ObjectViewHolder {
|
||||
return when (viewType) {
|
||||
TYPE_ITEM -> ObjectItemViewHolder(inflate(parent, R.layout.item_list_object)).apply {
|
||||
itemView.setOnClickListener {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
val item = getItem(pos)
|
||||
if (item is DefaultObjectView) {
|
||||
onClick(item)
|
||||
}
|
||||
): ObjectViewHolder = when (viewType) {
|
||||
TYPE_ITEM -> ObjectItemViewHolder(inflate(parent, R.layout.item_list_object)).apply {
|
||||
itemView.setOnClickListener {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
val item = getItem(pos)
|
||||
if (item is DefaultObjectView) {
|
||||
onDefaultObjectClicked(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
TYPE_SECTION_RECENTLY_OPENED -> RecentlyOpenedHolder(inflate(parent, R.layout.item_search_section_recently_opened))
|
||||
else -> throw IllegalStateException("Unexpected view type: $viewType")
|
||||
}
|
||||
TYPE_BUNDLED_WIDGET_SOURCE -> BundledWidgetSourceHolder(
|
||||
ItemListObjectBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
).apply {
|
||||
itemView.setOnClickListener {
|
||||
val pos = bindingAdapterPosition
|
||||
if (pos != RecyclerView.NO_POSITION) {
|
||||
val item = getItem(pos)
|
||||
if (item is BundledWidgetSourceView) {
|
||||
onBundledWidgetSourceClicked(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TYPE_SECTION -> SectionViewHolder(inflate(parent, R.layout.item_object_search_section))
|
||||
else -> throw IllegalStateException("Unexpected view type: $viewType")
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ObjectViewHolder, position: Int) {
|
||||
|
@ -107,20 +123,39 @@ class DefaultObjectViewAdapter(
|
|||
check(item is DefaultObjectView)
|
||||
holder.bind(item)
|
||||
}
|
||||
else -> {}
|
||||
is SectionViewHolder -> {
|
||||
check(item is ObjectSearchSection)
|
||||
when(item) {
|
||||
ObjectSearchSection.RecentlyOpened -> {
|
||||
holder.title.setText(R.string.object_search_recently_opened_section_title)
|
||||
}
|
||||
ObjectSearchSection.SelectWidgetSource.FromLibrary -> {
|
||||
holder.title.setText(R.string.widget_source_anytype_library)
|
||||
}
|
||||
ObjectSearchSection.SelectWidgetSource.FromMyObjects -> {
|
||||
holder.title.setText(R.string.objects)
|
||||
}
|
||||
}
|
||||
}
|
||||
is BundledWidgetSourceHolder -> {
|
||||
check(item is BundledWidgetSourceView)
|
||||
holder.bind(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (val item = getItem(position)) {
|
||||
is DefaultObjectView -> TYPE_ITEM
|
||||
is ObjectSearchSection.RecentlyOpened -> TYPE_SECTION_RECENTLY_OPENED
|
||||
else -> throw IllegalStateException("Unexpected item type: ${item.javaClass.name}")
|
||||
}
|
||||
override fun getItemViewType(position: Int): Int = when (val item = getItem(position)) {
|
||||
is DefaultObjectView -> TYPE_ITEM
|
||||
is ObjectSearchSection -> TYPE_SECTION
|
||||
is BundledWidgetSourceView -> TYPE_BUNDLED_WIDGET_SOURCE
|
||||
else -> throw IllegalStateException("Unexpected item type: ${item.javaClass.name}")
|
||||
}
|
||||
|
||||
open class ObjectViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||
inner class RecentlyOpenedHolder(view: View) : ObjectViewHolder(view)
|
||||
|
||||
inner class SectionViewHolder(view: View) : ObjectViewHolder(view) {
|
||||
val title : TextView get() = view.findViewById(R.id.tvTitle)
|
||||
}
|
||||
|
||||
class ObjectItemViewHolder(view: View) : ObjectViewHolder(view) {
|
||||
|
||||
|
@ -158,8 +193,39 @@ class ObjectItemViewHolder(view: View) : ObjectViewHolder(view) {
|
|||
): Boolean = (oldItem as? DefaultObjectView) == (newItem as? DefaultObjectView)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class BundledWidgetSourceHolder(
|
||||
private val binding: ItemListObjectBinding
|
||||
) : DefaultObjectViewAdapter.ObjectViewHolder(binding.root) {
|
||||
|
||||
fun bind(item: BundledWidgetSourceView) {
|
||||
when (item) {
|
||||
BundledWidgetSourceView.Favorites -> {
|
||||
with(binding) {
|
||||
tvTitle.setText(R.string.favorites)
|
||||
tvSubtitle.setText(R.string.your_favorite_objects)
|
||||
ivIcon.setIcon(ObjectIcon.Basic.Emoji("⭐️"))
|
||||
}
|
||||
}
|
||||
BundledWidgetSourceView.Recent -> {
|
||||
with(binding) {
|
||||
tvTitle.setText(R.string.recent)
|
||||
tvSubtitle.setText(R.string.recently_opened_objects)
|
||||
ivIcon.setIcon(ObjectIcon.Basic.Emoji("🗓"))
|
||||
}
|
||||
}
|
||||
BundledWidgetSourceView.Sets -> {
|
||||
with(binding) {
|
||||
tvTitle.setText(R.string.sets)
|
||||
tvSubtitle.setText(R.string.sets_of_objects)
|
||||
ivIcon.setIcon(ObjectIcon.Basic.Emoji("📚"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TYPE_ITEM = 0
|
||||
private const val TYPE_SECTION_RECENTLY_OPENED = 1
|
||||
private const val TYPE_SECTION = 1
|
||||
private const val TYPE_BUNDLED_WIDGET_SOURCE = 2
|
|
@ -30,7 +30,7 @@ class ObjectSearchDividerItemDecoration(
|
|||
val right: Int = parent.width
|
||||
val childCount = parent.childCount
|
||||
val startPosition =
|
||||
if (parent.getChildViewHolder(parent.getChildAt(0)) is DefaultObjectViewAdapter.RecentlyOpenedHolder) {
|
||||
if (parent.getChildViewHolder(parent.getChildAt(0)) is DefaultObjectViewAdapter.SectionViewHolder) {
|
||||
POSITION_HEADER
|
||||
} else {
|
||||
POSITION_DATA
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
<dimen name="list_item_object_subtitle_margin_top">34dp</dimen>
|
||||
<dimen name="list_item_object_subtitle_margin_start">80dp</dimen>
|
||||
<dimen name="list_item_object_subtitle_margin_end">20dp</dimen>
|
||||
<dimen name="list_item_object_emoji_size">28dp</dimen>
|
||||
<dimen name="list_item_object_emoji_size">24dp</dimen>
|
||||
<dimen name="list_item_object_checkbox_size">24dp</dimen>
|
||||
<dimen name="list_item_object_initials_size">28sp</dimen>
|
||||
<dimen name="list_item_object_image_corner_radius">2dp</dimen>
|
||||
|
|
|
@ -604,6 +604,10 @@
|
|||
<string name="block_data_view_content_description_no_data">The source of the inline set has no type</string>
|
||||
|
||||
<string name="object_search_recently_opened_section_title">Recently opened</string>
|
||||
<string name="widget_source_anytype_library">Anytype Library</string>
|
||||
<string name="your_favorite_objects">Your favorite objects</string>
|
||||
<string name="recently_opened_objects">Recently opened objects</string>
|
||||
<string name="sets_of_objects">Sets of objects from your workspace</string>
|
||||
|
||||
<string name="content_description_customize_view_button">Customize-view button</string>
|
||||
<string name="collection_no_items_title">No objects</string>
|
||||
|
|
|
@ -48,7 +48,7 @@ object Emojifier {
|
|||
|
||||
/**
|
||||
* @param unicode emoji unicode
|
||||
* @return a pair constisting of emoji's page and emoji's index for this [unicode]
|
||||
* @return a pair consisting of emoji's page and emoji's index for this [unicode]
|
||||
*/
|
||||
private fun search(unicode: String): Pair<Int, Int>? {
|
||||
val cached = cache[unicode]
|
||||
|
|
|
@ -38,7 +38,6 @@ import com.anytypeio.anytype.presentation.widgets.CollapsedWidgetStateHolder
|
|||
import com.anytypeio.anytype.presentation.widgets.DataViewListWidgetContainer
|
||||
import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
||||
import com.anytypeio.anytype.presentation.widgets.LinkWidgetContainer
|
||||
import com.anytypeio.anytype.presentation.widgets.ListWidgetContainer
|
||||
import com.anytypeio.anytype.presentation.widgets.TreePath
|
||||
import com.anytypeio.anytype.presentation.widgets.TreeWidgetBranchStateHolder
|
||||
import com.anytypeio.anytype.presentation.widgets.TreeWidgetContainer
|
||||
|
@ -99,52 +98,9 @@ class HomeScreenViewModel(
|
|||
private val containers = MutableStateFlow<List<WidgetContainer>>(emptyList())
|
||||
private val treeWidgetBranchStateHolder = TreeWidgetBranchStateHolder()
|
||||
|
||||
// Bundled widget containing recently opened objects
|
||||
private val recent by lazy {
|
||||
ListWidgetContainer(
|
||||
workspace = configStorage.get().workspace,
|
||||
isWidgetCollapsed = isCollapsed(Subscriptions.SUBSCRIPTION_RECENT),
|
||||
storage = storelessSubscriptionContainer,
|
||||
urlBuilder = urlBuilder,
|
||||
subscription = Subscriptions.SUBSCRIPTION_RECENT
|
||||
)
|
||||
}
|
||||
|
||||
// Bundled widget containing sets
|
||||
private val sets by lazy {
|
||||
ListWidgetContainer(
|
||||
workspace = configStorage.get().workspace,
|
||||
isWidgetCollapsed = isCollapsed(Subscriptions.SUBSCRIPTION_SETS),
|
||||
storage = storelessSubscriptionContainer,
|
||||
urlBuilder = urlBuilder,
|
||||
subscription = Subscriptions.SUBSCRIPTION_SETS
|
||||
)
|
||||
}
|
||||
|
||||
// Bundled widget containing objects from favorites
|
||||
private val favorites by lazy {
|
||||
ListWidgetContainer(
|
||||
workspace = configStorage.get().workspace,
|
||||
isWidgetCollapsed = isCollapsed(Subscriptions.SUBSCRIPTION_FAVORITES),
|
||||
storage = storelessSubscriptionContainer,
|
||||
urlBuilder = urlBuilder,
|
||||
subscription = Subscriptions.SUBSCRIPTION_FAVORITES
|
||||
)
|
||||
}
|
||||
|
||||
// Bundled widget containing archived objects
|
||||
private val bin = WidgetView.Bin(Subscriptions.SUBSCRIPTION_ARCHIVED)
|
||||
|
||||
private val bundledWidgets by lazy {
|
||||
combine(
|
||||
listOf(
|
||||
favorites.view,
|
||||
recent.view,
|
||||
sets.view
|
||||
)
|
||||
) { array -> array }
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
viewModelScope.launch { unsubscriber.start() }
|
||||
|
@ -211,7 +167,8 @@ class HomeScreenViewModel(
|
|||
expandedBranches = treeWidgetBranchStateHolder.stream(widget.id),
|
||||
isWidgetCollapsed = isCollapsed(widget.id),
|
||||
urlBuilder = urlBuilder,
|
||||
dispatchers = appCoroutineDispatchers
|
||||
dispatchers = appCoroutineDispatchers,
|
||||
workspace = config.workspace
|
||||
)
|
||||
is Widget.List -> DataViewListWidgetContainer(
|
||||
widget = widget,
|
||||
|
@ -241,8 +198,6 @@ class HomeScreenViewModel(
|
|||
} else {
|
||||
flowOf(emptyList())
|
||||
}
|
||||
}.combine(bundledWidgets) { fromWidgetObject, bundled ->
|
||||
bundled.toList() + fromWidgetObject
|
||||
}.flowOn(appCoroutineDispatchers.io).collect {
|
||||
views.value = it + bin + actions
|
||||
}
|
||||
|
@ -276,7 +231,12 @@ class HomeScreenViewModel(
|
|||
|
||||
private fun proceedWithClosingWidgetObject(widgetObject: Id) {
|
||||
viewModelScope.launch {
|
||||
val subscriptions = widgets.value.map { widget -> widget.id }
|
||||
val subscriptions = widgets.value.map { widget ->
|
||||
if (widget.source is Widget.Source.Bundled)
|
||||
widget.source.id
|
||||
else
|
||||
widget.id
|
||||
}
|
||||
if (subscriptions.isNotEmpty()) unsubscribe(subscriptions)
|
||||
closeObject.stream(widgetObject).collect { status ->
|
||||
status.fold(
|
||||
|
@ -296,7 +256,7 @@ class HomeScreenViewModel(
|
|||
widgetEventDispatcher.flow().collect { dispatch ->
|
||||
Timber.d("New dispatch: $dispatch")
|
||||
when (dispatch) {
|
||||
is WidgetDispatchEvent.SourcePicked -> {
|
||||
is WidgetDispatchEvent.SourcePicked.Default -> {
|
||||
commands.emit(
|
||||
Command.SelectWidgetType(
|
||||
ctx = configStorage.get().widgets,
|
||||
|
@ -305,6 +265,15 @@ class HomeScreenViewModel(
|
|||
)
|
||||
)
|
||||
}
|
||||
is WidgetDispatchEvent.SourcePicked.Bundled -> {
|
||||
commands.emit(
|
||||
Command.SelectWidgetType(
|
||||
ctx = configStorage.get().widgets,
|
||||
source = dispatch.source,
|
||||
layout = ObjectType.Layout.SET.code
|
||||
)
|
||||
)
|
||||
}
|
||||
is WidgetDispatchEvent.SourceChanged -> {
|
||||
proceedWithUpdatingWidget(
|
||||
widget = dispatch.widget,
|
||||
|
@ -471,6 +440,30 @@ class HomeScreenViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onWidgetSourceClicked(source: Widget.Source) {
|
||||
when (source) {
|
||||
is Widget.Source.Bundled.Favorites -> {
|
||||
// TODO switch to bundled widgets id
|
||||
navigate(Navigation.ExpandWidget(Subscription.Favorites))
|
||||
}
|
||||
is Widget.Source.Bundled.Sets -> {
|
||||
// TODO switch to bundled widgets id
|
||||
navigate(Navigation.ExpandWidget(Subscription.Sets))
|
||||
}
|
||||
is Widget.Source.Bundled.Recent -> {
|
||||
// TODO switch to bundled widgets id
|
||||
navigate(Navigation.ExpandWidget(Subscription.Recent))
|
||||
}
|
||||
is Widget.Source.Default -> {
|
||||
if (source.obj.isArchived != true) {
|
||||
proceedWithOpeningObject(source.obj)
|
||||
} else {
|
||||
sendToast("Open bin to restore your archived object")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onDropDownMenuAction(widget: Id, action: DropDownMenuAction) {
|
||||
when (action) {
|
||||
DropDownMenuAction.ChangeWidgetSource -> {
|
||||
|
|
|
@ -4,4 +4,8 @@ import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem
|
|||
|
||||
sealed class ObjectSearchSection : DefaultSearchItem {
|
||||
object RecentlyOpened: ObjectSearchSection()
|
||||
sealed class SelectWidgetSource : ObjectSearchSection() {
|
||||
object FromMyObjects: SelectWidgetSource()
|
||||
object FromLibrary : SelectWidgetSource()
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ open class ObjectSearchViewModel(
|
|||
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
private val userInput = MutableStateFlow(EMPTY_QUERY)
|
||||
protected val userInput = MutableStateFlow(EMPTY_QUERY)
|
||||
private val searchQuery = userInput
|
||||
.take(1)
|
||||
.onCompletion {
|
||||
|
@ -78,26 +78,30 @@ open class ObjectSearchViewModel(
|
|||
)
|
||||
)
|
||||
}
|
||||
}.collectLatest { views ->
|
||||
if (views.isSuccess) {
|
||||
with(views.getOrThrow()) {
|
||||
if (this.isEmpty()) {
|
||||
stateData.postValue(ObjectSearchView.NoResults(userInput.value))
|
||||
} else {
|
||||
if (userInput.value.isEmpty()) {
|
||||
val items =
|
||||
mutableListOf<DefaultSearchItem>(ObjectSearchSection.RecentlyOpened)
|
||||
items.addAll(this)
|
||||
stateData.postValue(ObjectSearchView.Success(items))
|
||||
} else {
|
||||
stateData.postValue(ObjectSearchView.Success(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
}.collectLatest { result ->
|
||||
resolveViews(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun resolveViews(result: Resultat<List<DefaultObjectView>>) {
|
||||
if (result.isSuccess) {
|
||||
with(result.getOrThrow()) {
|
||||
if (this.isEmpty()) {
|
||||
stateData.postValue(ObjectSearchView.NoResults(userInput.value))
|
||||
} else {
|
||||
stateData.postValue(ObjectSearchView.Loading)
|
||||
if (userInput.value.isEmpty()) {
|
||||
val items =
|
||||
mutableListOf<DefaultSearchItem>(ObjectSearchSection.RecentlyOpened)
|
||||
items.addAll(this)
|
||||
stateData.postValue(ObjectSearchView.Success(items))
|
||||
} else {
|
||||
stateData.postValue(ObjectSearchView.Success(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stateData.postValue(ObjectSearchView.Loading)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,48 +31,53 @@ class DataViewListWidgetContainer(
|
|||
activeView.distinctUntilChanged(),
|
||||
isWidgetCollapsed
|
||||
) { view, isCollapsed -> Pair(view, isCollapsed) }.flatMapLatest { (view, isCollapsed) ->
|
||||
if (isCollapsed) {
|
||||
flowOf(
|
||||
WidgetView.SetOfObjects(
|
||||
id = widget.id,
|
||||
obj = widget.source,
|
||||
tabs = emptyList(),
|
||||
elements = emptyList(),
|
||||
isExpanded = false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val obj = getObject.run(widget.source.id)
|
||||
val params = obj.parse(viewer = view, source = widget.source)
|
||||
if (params != null) {
|
||||
storage.subscribe(params).map { objects ->
|
||||
WidgetView.SetOfObjects(
|
||||
id = widget.id,
|
||||
obj = widget.source,
|
||||
tabs = obj.tabs(viewer = view),
|
||||
elements = objects.map { obj ->
|
||||
WidgetView.SetOfObjects.Element(
|
||||
obj = obj,
|
||||
icon = ObjectIcon.from(
|
||||
obj = obj,
|
||||
layout = obj.layout,
|
||||
builder = urlBuilder
|
||||
)
|
||||
when(widget.source) {
|
||||
is Widget.Source.Bundled -> throw IllegalStateException("Bundled widgets do not support data view layout")
|
||||
is Widget.Source.Default -> {
|
||||
if (isCollapsed) {
|
||||
flowOf(
|
||||
WidgetView.SetOfObjects(
|
||||
id = widget.id,
|
||||
source = widget.source,
|
||||
tabs = emptyList(),
|
||||
elements = emptyList(),
|
||||
isExpanded = false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val obj = getObject.run(widget.source.id)
|
||||
val params = obj.parse(viewer = view, source = widget.source.obj)
|
||||
if (params != null) {
|
||||
storage.subscribe(params).map { objects ->
|
||||
WidgetView.SetOfObjects(
|
||||
id = widget.id,
|
||||
source = widget.source,
|
||||
tabs = obj.tabs(viewer = view),
|
||||
elements = objects.map { obj ->
|
||||
WidgetView.SetOfObjects.Element(
|
||||
obj = obj,
|
||||
icon = ObjectIcon.from(
|
||||
obj = obj,
|
||||
layout = obj.layout,
|
||||
builder = urlBuilder
|
||||
)
|
||||
)
|
||||
},
|
||||
isExpanded = true
|
||||
)
|
||||
},
|
||||
isExpanded = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
flowOf(
|
||||
WidgetView.SetOfObjects(
|
||||
id = widget.id,
|
||||
source = widget.source,
|
||||
tabs = emptyList(),
|
||||
elements = emptyList(),
|
||||
isExpanded = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flowOf(
|
||||
WidgetView.SetOfObjects(
|
||||
id = widget.id,
|
||||
obj = widget.source,
|
||||
tabs = emptyList(),
|
||||
elements = emptyList(),
|
||||
isExpanded = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class LinkWidgetContainer(
|
|||
override val view: Flow<WidgetView.Link> = flowOf(
|
||||
WidgetView.Link(
|
||||
id = widget.id,
|
||||
obj = widget.source
|
||||
source = widget.source
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -54,31 +54,45 @@ class ListWidgetContainer(
|
|||
private fun buildParams() = params(subscription = subscription, workspace = workspace)
|
||||
|
||||
private fun resolveType() = when (subscription) {
|
||||
Subscriptions.SUBSCRIPTION_RECENT -> WidgetView.ListOfObjects.Type.Recent
|
||||
Subscriptions.SUBSCRIPTION_SETS -> WidgetView.ListOfObjects.Type.Sets
|
||||
Subscriptions.SUBSCRIPTION_FAVORITES -> WidgetView.ListOfObjects.Type.Favorites
|
||||
BundledWidgetSourceIds.RECENT -> WidgetView.ListOfObjects.Type.Recent
|
||||
BundledWidgetSourceIds.SETS -> WidgetView.ListOfObjects.Type.Sets
|
||||
BundledWidgetSourceIds.FAVORITE -> WidgetView.ListOfObjects.Type.Favorites
|
||||
else -> throw IllegalStateException("Unexpected subscription: $subscription")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_COUNT = 3
|
||||
fun params(subscription: Id, workspace: Id) = when (subscription) {
|
||||
Subscriptions.SUBSCRIPTION_RECENT -> {
|
||||
fun params(
|
||||
subscription: Id,
|
||||
workspace: Id,
|
||||
keys: List<Id> = ObjectSearchConstants.defaultKeys,
|
||||
limit: Int = MAX_COUNT
|
||||
) = when (subscription) {
|
||||
BundledWidgetSourceIds.RECENT -> {
|
||||
StoreSearchParams(
|
||||
subscription = subscription,
|
||||
sorts = ObjectSearchConstants.sortTabRecent,
|
||||
filters = ObjectSearchConstants.filterTabRecent(workspace),
|
||||
keys = ObjectSearchConstants.defaultKeys,
|
||||
limit = MAX_COUNT
|
||||
keys = keys,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
Subscriptions.SUBSCRIPTION_SETS -> {
|
||||
BundledWidgetSourceIds.SETS -> {
|
||||
StoreSearchParams(
|
||||
subscription = subscription,
|
||||
sorts = ObjectSearchConstants.sortTabSets,
|
||||
filters = ObjectSearchConstants.filterTabSets(workspace),
|
||||
keys = ObjectSearchConstants.defaultKeys,
|
||||
limit = MAX_COUNT
|
||||
keys = keys,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
BundledWidgetSourceIds.FAVORITE -> {
|
||||
StoreSearchParams(
|
||||
subscription = subscription,
|
||||
sorts = emptyList(),
|
||||
filters = ObjectSearchConstants.filterTabFavorites(workspace),
|
||||
keys = keys,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
Subscriptions.SUBSCRIPTION_ARCHIVED -> {
|
||||
|
@ -86,17 +100,8 @@ class ListWidgetContainer(
|
|||
subscription = subscription,
|
||||
sorts = ObjectSearchConstants.sortTabArchive,
|
||||
filters = ObjectSearchConstants.filterTabArchive(workspace),
|
||||
keys = ObjectSearchConstants.defaultKeys,
|
||||
limit = MAX_COUNT
|
||||
)
|
||||
}
|
||||
Subscriptions.SUBSCRIPTION_FAVORITES -> {
|
||||
StoreSearchParams(
|
||||
subscription = subscription,
|
||||
sorts = emptyList(),
|
||||
filters = ObjectSearchConstants.filterTabFavorites(workspace),
|
||||
keys = ObjectSearchConstants.defaultKeys,
|
||||
limit = MAX_COUNT
|
||||
keys = keys,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
else -> throw IllegalStateException("Unexpected subscription: $subscription")
|
||||
|
|
|
@ -5,13 +5,18 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.domain.base.Resultat
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.search.SearchObjects
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchSection
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchView
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.presentation.widgets.source.BundledWidgetSourceView
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.take
|
||||
|
@ -47,6 +52,43 @@ class SelectWidgetSourceViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
override fun resolveViews(result: Resultat<List<DefaultObjectView>>) {
|
||||
result.fold(
|
||||
onSuccess = { views ->
|
||||
if (views.isEmpty()) {
|
||||
stateData.postValue(ObjectSearchView.NoResults(userInput.value))
|
||||
} else {
|
||||
if (userInput.value.isEmpty()) {
|
||||
stateData.postValue(
|
||||
ObjectSearchView.Success(
|
||||
buildList {
|
||||
add(ObjectSearchSection.SelectWidgetSource.FromLibrary)
|
||||
addAll(
|
||||
listOf(
|
||||
BundledWidgetSourceView.Favorites,
|
||||
BundledWidgetSourceView.Recent,
|
||||
BundledWidgetSourceView.Sets
|
||||
)
|
||||
)
|
||||
add(ObjectSearchSection.SelectWidgetSource.FromMyObjects)
|
||||
addAll(views)
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
stateData.postValue(ObjectSearchView.Success(views))
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoading = {
|
||||
stateData.postValue(ObjectSearchView.Loading)
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Error while selecting source for widget")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun onStartWithNewWidget() {
|
||||
Timber.d("onStart with picking source for new widget")
|
||||
config = Config.NewWidget
|
||||
|
@ -74,13 +116,42 @@ class SelectWidgetSourceViewModel(
|
|||
startProcessingSearchQuery(null)
|
||||
}
|
||||
|
||||
fun onBundledWidgetSourceClicked(view: BundledWidgetSourceView) {
|
||||
Timber.d("onBundledWidgetSourceClicked, view:[$view]")
|
||||
when (val curr = config) {
|
||||
is Config.NewWidget -> {
|
||||
viewModelScope.launch {
|
||||
dispatcher.send(
|
||||
WidgetDispatchEvent.SourcePicked.Bundled(source = view.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
is Config.ExistingWidget -> {
|
||||
viewModelScope.launch {
|
||||
dispatcher.send(
|
||||
WidgetDispatchEvent.SourceChanged(
|
||||
ctx = curr.ctx,
|
||||
widget = curr.widget,
|
||||
source = view.id,
|
||||
type = curr.type
|
||||
)
|
||||
)
|
||||
isDismissed.value = true
|
||||
}
|
||||
}
|
||||
Config.None -> {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onObjectClicked(view: DefaultObjectView) {
|
||||
Timber.d("onObjectClicked, view:[$view]")
|
||||
when(val curr = config) {
|
||||
is Config.NewWidget -> {
|
||||
viewModelScope.launch {
|
||||
dispatcher.send(
|
||||
WidgetDispatchEvent.SourcePicked(
|
||||
WidgetDispatchEvent.SourcePicked.Default(
|
||||
source = view.id,
|
||||
sourceLayout = view.layout?.code ?: -1
|
||||
)
|
||||
|
|
|
@ -34,18 +34,33 @@ class SelectWidgetTypeViewModel(
|
|||
|
||||
val isDismissed = MutableStateFlow(false)
|
||||
|
||||
fun onStartForExistingWidget(currentType: Int) {
|
||||
views.value = views.value.map { view -> view.setIsSelected(currentType) }
|
||||
fun onStartForExistingWidget(currentType: Int, source: Id) {
|
||||
Timber.d("onStart for existing widget")
|
||||
if (BundledWidgetSourceIds.ids.contains(source)) {
|
||||
views.value = listOf(
|
||||
WidgetTypeView.Tree().setIsSelected(currentType),
|
||||
WidgetTypeView.List().setIsSelected(currentType)
|
||||
)
|
||||
} else {
|
||||
views.value = views.value.map { view -> view.setIsSelected(currentType) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onStartForNewWidget(layout: Int) {
|
||||
Timber.d("onStart for new widget: $layout")
|
||||
val objectLayout = ObjectType.Layout.values().find { it.code == layout }
|
||||
if (objectLayout == ObjectType.Layout.SET) {
|
||||
fun onStartForNewWidget(layout: Int, source: Id) {
|
||||
Timber.d("onStart for new widget")
|
||||
if (BundledWidgetSourceIds.ids.contains(source)) {
|
||||
views.value = listOf(
|
||||
WidgetTypeView.List(isSelected = false),
|
||||
WidgetTypeView.Link(isSelected = false)
|
||||
WidgetTypeView.Tree(isSelected = false),
|
||||
WidgetTypeView.List(isSelected = false)
|
||||
)
|
||||
} else {
|
||||
val objectLayout = ObjectType.Layout.values().find { it.code == layout }
|
||||
if (objectLayout == ObjectType.Layout.SET) {
|
||||
views.value = listOf(
|
||||
WidgetTypeView.List(isSelected = false),
|
||||
WidgetTypeView.Link(isSelected = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +80,7 @@ class SelectWidgetTypeViewModel(
|
|||
type = when (view) {
|
||||
is WidgetTypeView.Link -> WidgetLayout.LINK
|
||||
is WidgetTypeView.Tree -> WidgetLayout.TREE
|
||||
is WidgetTypeView.List -> TODO()
|
||||
is WidgetTypeView.List -> WidgetLayout.LINK
|
||||
}
|
||||
)
|
||||
).flowOn(appCoroutineDispatchers.io).collect { result ->
|
||||
|
|
|
@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.map
|
|||
|
||||
class TreeWidgetContainer(
|
||||
private val widget: Widget.Tree,
|
||||
private val workspace: Id,
|
||||
private val container: StorelessSubscriptionContainer,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
|
@ -34,49 +35,113 @@ class TreeWidgetContainer(
|
|||
) { paths, isWidgetCollapsed ->
|
||||
paths to isWidgetCollapsed
|
||||
}.flatMapLatest { (paths, isWidgetCollapsed) ->
|
||||
container.subscribe(
|
||||
StoreSearchByIdsParams(
|
||||
subscription = widget.id,
|
||||
keys = keys,
|
||||
targets = if (!isWidgetCollapsed) {
|
||||
getSubscriptionTargets(paths = paths)
|
||||
} else {
|
||||
emptyList()
|
||||
when (widget.source) {
|
||||
is Widget.Source.Bundled -> {
|
||||
container.subscribe(
|
||||
ListWidgetContainer.params(
|
||||
subscription = widget.source.id,
|
||||
workspace = workspace
|
||||
)
|
||||
).map { rootLevelObjects ->
|
||||
rootLevelObjects.map { it.id }
|
||||
}.flatMapLatest { rootLevelObjects ->
|
||||
container.subscribe(
|
||||
StoreSearchByIdsParams(
|
||||
subscription = widget.id,
|
||||
keys = keys,
|
||||
targets = if (!isWidgetCollapsed) {
|
||||
getBundledSubscriptionTargets(
|
||||
paths = paths,
|
||||
links = rootLevelObjects
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
).map { data ->
|
||||
rootLevelObjects to data
|
||||
}
|
||||
}.map { (rootLevelLinks, objectWrappers) ->
|
||||
val valid = objectWrappers.filter { obj -> isValidObject(obj) }
|
||||
val data = valid.associateBy { r -> r.id }
|
||||
with(nodes) {
|
||||
clear()
|
||||
putAll(valid.associate { obj -> obj.id to obj.links })
|
||||
}
|
||||
WidgetView.Tree(
|
||||
id = widget.id,
|
||||
source = widget.source,
|
||||
isExpanded = !isWidgetCollapsed,
|
||||
elements = buildTree(
|
||||
links = rootLevelLinks,
|
||||
level = ROOT_INDENT,
|
||||
expanded = paths,
|
||||
path = widget.id + SEPARATOR + widget.source.id + SEPARATOR,
|
||||
data = data
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is Widget.Source.Default -> {
|
||||
container.subscribe(
|
||||
StoreSearchByIdsParams(
|
||||
subscription = widget.id,
|
||||
keys = keys,
|
||||
targets = if (!isWidgetCollapsed) {
|
||||
getDefaultSubscriptionTargets(
|
||||
paths = paths,
|
||||
source = widget.source
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
)
|
||||
).map { results ->
|
||||
val valid = results.filter { obj -> isValidObject(obj) }
|
||||
val data = valid.associateBy { r -> r.id }
|
||||
with(nodes) {
|
||||
clear()
|
||||
putAll(valid.associate { obj -> obj.id to obj.links })
|
||||
}
|
||||
WidgetView.Tree(
|
||||
id = widget.id,
|
||||
source = widget.source,
|
||||
isExpanded = !isWidgetCollapsed,
|
||||
elements = buildTree(
|
||||
links = widget.source.obj.links,
|
||||
level = ROOT_INDENT,
|
||||
expanded = paths,
|
||||
path = widget.id + SEPARATOR + widget.source.id + SEPARATOR,
|
||||
data = data
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
).map { results ->
|
||||
val valid = results.filter { obj -> isValidObject(obj) }
|
||||
val data = valid.associateBy { r -> r.id }
|
||||
with(nodes) {
|
||||
clear()
|
||||
putAll(valid.associate { obj -> obj.id to obj.links })
|
||||
}
|
||||
WidgetView.Tree(
|
||||
id = widget.id,
|
||||
obj = widget.source,
|
||||
isExpanded = !isWidgetCollapsed,
|
||||
elements = buildTree(
|
||||
links = widget.source.links,
|
||||
level = ROOT_INDENT,
|
||||
expanded = paths,
|
||||
path = widget.id + SEPARATOR + widget.source.id + SEPARATOR,
|
||||
data = data
|
||||
)
|
||||
)
|
||||
}
|
||||
}.flowOn(dispatchers.io)
|
||||
|
||||
private fun getSubscriptionTargets(
|
||||
paths: List<TreePath>
|
||||
private fun getDefaultSubscriptionTargets(
|
||||
paths: List<TreePath>,
|
||||
source: Widget.Source.Default
|
||||
) = buildList {
|
||||
if (widget.source.isArchived != true && widget.source.isDeleted != true) {
|
||||
addAll(widget.source.links)
|
||||
if (source.obj.isArchived != true && source.obj.isDeleted != true) {
|
||||
addAll(source.obj.links)
|
||||
nodes.forEach { (id, links) ->
|
||||
if (paths.any { path -> path.contains(id) }) addAll(links)
|
||||
}
|
||||
}
|
||||
}.distinct()
|
||||
|
||||
private fun getBundledSubscriptionTargets(
|
||||
paths: List<TreePath>,
|
||||
links: List<Id>,
|
||||
) = buildList {
|
||||
addAll(links)
|
||||
nodes.forEach { (id, links) ->
|
||||
if (paths.any { path -> path.contains(id) }) addAll(links)
|
||||
}
|
||||
}.distinct()
|
||||
|
||||
private fun buildTree(
|
||||
links: List<Id>,
|
||||
expanded: List<TreePath>,
|
||||
|
@ -145,11 +210,6 @@ class TreeWidgetContainer(
|
|||
add(Relations.LINKS)
|
||||
}
|
||||
}
|
||||
|
||||
data class Node(
|
||||
val id: Id,
|
||||
val children: List<Id>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.widgets
|
|||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.Struct
|
||||
import com.anytypeio.anytype.core_models.ext.asMap
|
||||
|
||||
|
@ -10,7 +11,7 @@ sealed class Widget {
|
|||
|
||||
abstract val id: Id
|
||||
|
||||
abstract val source: ObjectWrapper.Basic
|
||||
abstract val source: Source
|
||||
|
||||
/**
|
||||
* @property [id] id of the widget
|
||||
|
@ -18,7 +19,7 @@ sealed class Widget {
|
|||
*/
|
||||
data class Tree(
|
||||
override val id: Id,
|
||||
override val source: ObjectWrapper.Basic
|
||||
override val source: Source
|
||||
) : Widget()
|
||||
|
||||
/**
|
||||
|
@ -27,7 +28,7 @@ sealed class Widget {
|
|||
*/
|
||||
data class Link(
|
||||
override val id: Id,
|
||||
override val source: ObjectWrapper.Basic
|
||||
override val source: Source
|
||||
) : Widget()
|
||||
|
||||
/**
|
||||
|
@ -36,8 +37,36 @@ sealed class Widget {
|
|||
*/
|
||||
data class List(
|
||||
override val id: Id,
|
||||
override val source: ObjectWrapper.Basic
|
||||
override val source: Source
|
||||
) : Widget()
|
||||
|
||||
sealed class Source {
|
||||
|
||||
abstract val id: Id
|
||||
abstract val type: Id?
|
||||
|
||||
data class Default(val obj: ObjectWrapper.Basic) : Source() {
|
||||
override val id: Id = obj.id
|
||||
override val type: Id? = obj.type.firstOrNull()
|
||||
}
|
||||
|
||||
sealed class Bundled : Source() {
|
||||
object Favorites : Bundled() {
|
||||
override val id: Id = BundledWidgetSourceIds.FAVORITE
|
||||
override val type: Id? = null
|
||||
}
|
||||
|
||||
object Sets : Bundled() {
|
||||
override val id: Id = BundledWidgetSourceIds.SETS
|
||||
override val type: Id? = null
|
||||
}
|
||||
|
||||
object Recent : Bundled() {
|
||||
override val id: Id = BundledWidgetSourceIds.RECENT
|
||||
override val type: Id? = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<Block>.parseWidgets(
|
||||
|
@ -47,22 +76,34 @@ fun List<Block>.parseWidgets(
|
|||
val map = asMap()
|
||||
val widgets = map[root] ?: emptyList()
|
||||
widgets.forEach { w ->
|
||||
val content = w.content
|
||||
if (content is Block.Content.Widget) {
|
||||
val widgetContent = w.content
|
||||
if (widgetContent is Block.Content.Widget) {
|
||||
val child = (map[w.id] ?: emptyList()).firstOrNull()
|
||||
if (child != null) {
|
||||
val source = child.content
|
||||
if (source is Block.Content.Link) {
|
||||
val raw = details[source.target] ?: emptyMap()
|
||||
val data = ObjectWrapper.Basic(raw)
|
||||
val type = data.type.firstOrNull()
|
||||
if (!WidgetConfig.excludedTypes.contains(type)) {
|
||||
when (content.layout) {
|
||||
val sourceContent = child.content
|
||||
if (sourceContent is Block.Content.Link) {
|
||||
val target = sourceContent.target
|
||||
val raw = details[target] ?: run {
|
||||
if (BundledWidgetSourceIds.ids.contains(sourceContent.target)) {
|
||||
mapOf(Relations.ID to sourceContent.target)
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
val source = if (BundledWidgetSourceIds.ids.contains(target)) {
|
||||
target.bundled()
|
||||
} else {
|
||||
Widget.Source.Default(
|
||||
ObjectWrapper.Basic(raw)
|
||||
)
|
||||
}
|
||||
if (!WidgetConfig.excludedTypes.contains(source.type)) {
|
||||
when (widgetContent.layout) {
|
||||
Block.Content.Widget.Layout.TREE -> {
|
||||
add(
|
||||
Widget.Tree(
|
||||
id = w.id,
|
||||
source = data
|
||||
source = source
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -70,7 +111,7 @@ fun List<Block>.parseWidgets(
|
|||
add(
|
||||
Widget.Link(
|
||||
id = w.id,
|
||||
source = data
|
||||
source = source
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -82,6 +123,13 @@ fun List<Block>.parseWidgets(
|
|||
}
|
||||
}
|
||||
|
||||
fun Id.bundled() : Widget.Source.Bundled = when (this) {
|
||||
BundledWidgetSourceIds.RECENT -> Widget.Source.Bundled.Recent
|
||||
BundledWidgetSourceIds.SETS -> Widget.Source.Bundled.Sets
|
||||
BundledWidgetSourceIds.FAVORITE -> Widget.Source.Bundled.Favorites
|
||||
else -> throw throw IllegalStateException()
|
||||
}
|
||||
|
||||
typealias WidgetId = Id
|
||||
typealias ViewId = Id
|
||||
typealias FromIndex = Int
|
||||
|
|
|
@ -24,4 +24,11 @@ object WidgetConfig {
|
|||
&& obj.isDeleted != true
|
||||
&& SupportedLayouts.isSupported(obj.layout)
|
||||
}
|
||||
}
|
||||
|
||||
object BundledWidgetSourceIds {
|
||||
const val FAVORITE = "favorite"
|
||||
const val RECENT = "recent"
|
||||
const val SETS = "sets"
|
||||
val ids = listOf(FAVORITE, RECENT, SETS)
|
||||
}
|
|
@ -3,7 +3,14 @@ package com.anytypeio.anytype.presentation.widgets
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
|
||||
sealed class WidgetDispatchEvent {
|
||||
data class SourcePicked(val source: Id, val sourceLayout: Int) : WidgetDispatchEvent()
|
||||
sealed class SourcePicked : WidgetDispatchEvent() {
|
||||
data class Default(val source: Id, val sourceLayout: Int) : SourcePicked()
|
||||
|
||||
/**
|
||||
* [source] bundled source - one of [BundledWidgetSourceIds]
|
||||
*/
|
||||
data class Bundled(val source: Id) : SourcePicked()
|
||||
}
|
||||
data class TypePicked(val source: Id, val widgetType: Int) : WidgetDispatchEvent()
|
||||
data class SourceChanged(
|
||||
val ctx: Id,
|
||||
|
|
|
@ -7,9 +7,9 @@ import com.anytypeio.anytype.presentation.home.Command.ChangeWidgetType.Companio
|
|||
sealed class WidgetTypeView {
|
||||
abstract val isSelected: Boolean
|
||||
|
||||
data class List(override val isSelected: Boolean) : WidgetTypeView()
|
||||
data class Tree(override val isSelected: Boolean) : WidgetTypeView()
|
||||
data class Link(override val isSelected: Boolean) : WidgetTypeView()
|
||||
data class List(override val isSelected: Boolean = false) : WidgetTypeView()
|
||||
data class Tree(override val isSelected: Boolean = false) : WidgetTypeView()
|
||||
data class Link(override val isSelected: Boolean = false) : WidgetTypeView()
|
||||
|
||||
fun setIsSelected(type: Int): WidgetTypeView = when (this) {
|
||||
is Link -> copy(isSelected = type == TYPE_LINK)
|
||||
|
|
|
@ -12,7 +12,7 @@ sealed class WidgetView {
|
|||
|
||||
data class Tree(
|
||||
override val id: Id,
|
||||
val obj: ObjectWrapper.Basic,
|
||||
val source: Widget.Source,
|
||||
val elements: List<Element> = emptyList(),
|
||||
val isExpanded: Boolean = false,
|
||||
val isEditable: Boolean = true
|
||||
|
@ -34,12 +34,12 @@ sealed class WidgetView {
|
|||
|
||||
data class Link(
|
||||
override val id: Id,
|
||||
val obj: ObjectWrapper.Basic,
|
||||
val source: Widget.Source,
|
||||
) : WidgetView(), Draggable
|
||||
|
||||
data class SetOfObjects(
|
||||
override val id: Id,
|
||||
val obj: ObjectWrapper.Basic,
|
||||
val source: Widget.Source,
|
||||
val tabs: List<Tab>,
|
||||
val elements: List<Element>,
|
||||
val isExpanded: Boolean
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.anytypeio.anytype.presentation.widgets.source
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.presentation.navigation.DefaultSearchItem
|
||||
import com.anytypeio.anytype.presentation.widgets.BundledWidgetSourceIds
|
||||
|
||||
/**
|
||||
* Used for picking bundled widget source from list of objects.
|
||||
*/
|
||||
sealed class BundledWidgetSourceView : DefaultSearchItem {
|
||||
abstract val id: Id
|
||||
object Favorites : BundledWidgetSourceView() {
|
||||
override val id: Id get() = BundledWidgetSourceIds.FAVORITE
|
||||
}
|
||||
object Sets: BundledWidgetSourceView() {
|
||||
override val id: Id get() = BundledWidgetSourceIds.SETS
|
||||
}
|
||||
object Recent: BundledWidgetSourceView() {
|
||||
override val id: Id get() = BundledWidgetSourceIds.RECENT
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -51,6 +51,8 @@ class TreeWidgetContainerTest {
|
|||
|
||||
private lateinit var urlBuilder: UrlBuilder
|
||||
|
||||
private val workspace = MockDataFactory.randomUuid()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
|
@ -58,7 +60,7 @@ class TreeWidgetContainerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should search for data for source object's links when no data is available about expanded branches`() =
|
||||
fun `should search for data for source object links when no data is available about expanded branches`() =
|
||||
runTest {
|
||||
|
||||
// SETUP
|
||||
|
@ -74,7 +76,7 @@ class TreeWidgetContainerTest {
|
|||
|
||||
val widget = Widget.Tree(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
source = source
|
||||
source = Widget.Source.Default(source)
|
||||
)
|
||||
|
||||
val expanded = flowOf(emptyList<TreePath>())
|
||||
|
@ -85,7 +87,8 @@ class TreeWidgetContainerTest {
|
|||
expandedBranches = expanded,
|
||||
isWidgetCollapsed = flowOf(false),
|
||||
urlBuilder = urlBuilder,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
workspace = workspace
|
||||
)
|
||||
|
||||
stubObjectSearch(
|
||||
|
@ -136,7 +139,7 @@ class TreeWidgetContainerTest {
|
|||
|
||||
val widget = Widget.Tree(
|
||||
id = "widget",
|
||||
source = source
|
||||
source = Widget.Source.Default(source)
|
||||
)
|
||||
|
||||
val expanded = flowOf(
|
||||
|
@ -152,7 +155,8 @@ class TreeWidgetContainerTest {
|
|||
expandedBranches = expanded,
|
||||
isWidgetCollapsed = flowOf(false),
|
||||
urlBuilder = urlBuilder,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
workspace = workspace
|
||||
)
|
||||
|
||||
stubObjectSearch(
|
||||
|
@ -217,7 +221,7 @@ class TreeWidgetContainerTest {
|
|||
|
||||
val widget = Widget.Tree(
|
||||
id = "widget",
|
||||
source = source
|
||||
source = Widget.Source.Default(source)
|
||||
)
|
||||
|
||||
val delayBeforeExpanded = 100L
|
||||
|
@ -234,7 +238,8 @@ class TreeWidgetContainerTest {
|
|||
expandedBranches = expanded,
|
||||
isWidgetCollapsed = flowOf(false),
|
||||
urlBuilder = urlBuilder,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
workspace = workspace
|
||||
)
|
||||
|
||||
stubObjectSearch(
|
||||
|
@ -256,7 +261,7 @@ class TreeWidgetContainerTest {
|
|||
assertEquals(
|
||||
expected = WidgetView.Tree(
|
||||
id = widget.id,
|
||||
obj = widget.source,
|
||||
source = widget.source,
|
||||
elements = listOf(
|
||||
WidgetView.Tree.Element(
|
||||
indent = 0,
|
||||
|
@ -290,7 +295,7 @@ class TreeWidgetContainerTest {
|
|||
assertEquals(
|
||||
expected = WidgetView.Tree(
|
||||
id = widget.id,
|
||||
obj = widget.source,
|
||||
source = widget.source,
|
||||
elements = listOf(
|
||||
WidgetView.Tree.Element(
|
||||
indent = 0,
|
||||
|
@ -360,7 +365,7 @@ class TreeWidgetContainerTest {
|
|||
|
||||
val widget = Widget.Tree(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
source = source
|
||||
source = Widget.Source.Default(source)
|
||||
)
|
||||
|
||||
val expanded = flowOf(emptyList<TreePath>())
|
||||
|
@ -371,7 +376,8 @@ class TreeWidgetContainerTest {
|
|||
expandedBranches = expanded,
|
||||
isWidgetCollapsed = flowOf(false),
|
||||
urlBuilder = urlBuilder,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
workspace = workspace
|
||||
)
|
||||
|
||||
stubObjectSearch(
|
||||
|
@ -414,7 +420,7 @@ class TreeWidgetContainerTest {
|
|||
|
||||
val widget = Widget.Tree(
|
||||
id = MockDataFactory.randomUuid(),
|
||||
source = source
|
||||
source = Widget.Source.Default(source)
|
||||
)
|
||||
|
||||
val expanded = flowOf(emptyList<TreePath>())
|
||||
|
@ -425,7 +431,8 @@ class TreeWidgetContainerTest {
|
|||
expandedBranches = expanded,
|
||||
isWidgetCollapsed = flowOf(false),
|
||||
urlBuilder = urlBuilder,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
workspace = workspace
|
||||
)
|
||||
|
||||
stubObjectSearch(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue