1
0
Fork 0
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:
Evgenii Kozlov 2023-03-13 11:24:22 +01:00 committed by GitHub
parent c5866bdf39
commit 12dc893b6b
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1224 additions and 472 deletions

View file

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

View file

@ -68,6 +68,7 @@ class HomeScreenFragment : BaseComposeFragment() {
vm.onDropDownMenuAction(widget, action)
},
onWidgetObjectClicked = vm::onWidgetObjectClicked,
onWidgetSourceClicked = vm::onWidgetSourceClicked,
onChangeWidgetView = vm::onChangeCurrentWidgetView,
onToggleExpandedWidgetState = vm::onToggleCollapsedWidgetState,
onSearchClicked = {

View file

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

View file

@ -60,7 +60,7 @@ class MoveToFragment : BaseBottomSheetTextInputFragment<FragmentObjectSearchBind
private val moveToAdapter by lazy {
DefaultObjectViewAdapter(
onClick = vm::onObjectClicked
onDefaultObjectClicked = vm::onObjectClicked
)
}

View file

@ -51,7 +51,7 @@ class ObjectSearchFragment :
private val searchAdapter by lazy {
DefaultObjectViewAdapter(
onClick = vm::onObjectClicked
onDefaultObjectClicked = vm::onObjectClicked
)
}

View file

@ -59,7 +59,8 @@ class SelectWidgetSourceFragment : BaseBottomSheetTextInputFragment<FragmentObje
private val selectWidgetSourceAdapter by lazy {
DefaultObjectViewAdapter(
onClick = vm::onObjectClicked
onDefaultObjectClicked = vm::onObjectClicked,
onBundledWidgetSourceClicked = vm::onBundledWidgetSourceClicked
)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@ class LinkWidgetContainer(
override val view: Flow<WidgetView.Link> = flowOf(
WidgetView.Link(
id = widget.id,
obj = widget.source
source = widget.source
)
)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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