1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-642 Relations | Feature | Suggest marketplace relations for user input search query + allow installing relation to user workspace (#2750)

This commit is contained in:
Evgenii Kozlov 2022-12-08 22:38:36 +03:00 committed by GitHub
parent 9ea7710e9c
commit c52bd3d44b
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 766 additions and 191 deletions

View file

@ -3,12 +3,15 @@ package com.anytypeio.anytype.di.feature.relations
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.AddRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.relations.AddRelationToObject
import com.anytypeio.anytype.domain.relations.GetRelations
import com.anytypeio.anytype.domain.relations.ObjectRelationList
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.presentation.relations.RelationAddToDataViewViewModel
import com.anytypeio.anytype.presentation.relations.RelationAddToObjectViewModel
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
@ -21,6 +24,7 @@ import com.anytypeio.anytype.ui.relations.RelationAddToObjectFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [RelationAddToObjectModule::class])
@ -48,12 +52,18 @@ object RelationAddToObjectModule {
dispatcher: Dispatcher<Payload>,
analytics: Analytics,
relationsProvider: ObjectRelationProvider,
getRelations: GetRelations,
appCoroutineDispatchers: AppCoroutineDispatchers,
addObjectToWorkspace: AddObjectToWorkspace
): RelationAddToObjectViewModel.Factory = RelationAddToObjectViewModel.Factory(
storeOfRelations = storeOfRelations,
addRelationToObject = addRelationToObject,
dispatcher = dispatcher,
analytics = analytics,
relationsProvider = relationsProvider,
getRelations = getRelations,
appCoroutineDispatchers = appCoroutineDispatchers,
addObjectToWorkspace = addObjectToWorkspace
)
@JvmStatic
@ -62,6 +72,31 @@ object RelationAddToObjectModule {
fun provideObjectRelationListUseCase(
repo: BlockRepository
): ObjectRelationList = ObjectRelationList(repo)
@JvmStatic
@Provides
@PerDialog
fun getRelations(repo: BlockRepository) : GetRelations = GetRelations(repo)
@JvmStatic
@Provides
@PerDialog
fun appCoroutineDispatchers() : AppCoroutineDispatchers = AppCoroutineDispatchers(
io = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main
)
@JvmStatic
@Provides
@PerDialog
fun addObjectToWorkspace(
repo: BlockRepository,
appCoroutineDispatchers: AppCoroutineDispatchers
) : AddObjectToWorkspace = AddObjectToWorkspace(
repo = repo,
dispatchers = appCoroutineDispatchers
)
}
@Subcomponent(modules = [RelationAddToDataViewModule::class])
@ -79,27 +114,32 @@ interface RelationAddToDataViewSubComponent {
@Module
object RelationAddToDataViewModule {
@JvmStatic
@Provides
@PerDialog
fun provideViewModelFactory(
addRelationToDataView: AddRelationToDataView,
storeOfRelations: StoreOfRelations,
dispatcher: Dispatcher<Payload>,
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
updateDataViewViewer: UpdateDataViewViewer,
analytics: Analytics,
relationsProvider: ObjectRelationProvider,
appCoroutineDispatchers: AppCoroutineDispatchers,
getRelations: GetRelations,
addObjectToWorkspace: AddObjectToWorkspace
): RelationAddToDataViewViewModel.Factory = RelationAddToDataViewViewModel.Factory(
storeOfRelations = storeOfRelations,
addRelationToDataView = addRelationToDataView,
dispatcher = dispatcher,
state = state,
session = session,
updateDataViewViewer = updateDataViewViewer,
analytics = analytics,
relationsProvider = relationsProvider
relationsProvider = relationsProvider,
appCoroutineDispatchers = appCoroutineDispatchers,
getRelations = getRelations,
addObjectToWorkspace = addObjectToWorkspace
)
@JvmStatic
@ -115,4 +155,29 @@ object RelationAddToDataViewModule {
fun provideAddRelationToDataViewUseCase(
repo: BlockRepository
): AddRelationToDataView = AddRelationToDataView(repo)
@JvmStatic
@Provides
@PerDialog
fun getRelations(repo: BlockRepository) : GetRelations = GetRelations(repo)
@JvmStatic
@Provides
@PerDialog
fun appCoroutineDispatchers() : AppCoroutineDispatchers = AppCoroutineDispatchers(
io = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main
)
@JvmStatic
@Provides
@PerDialog
fun addObjectToWorkspace(
repo: BlockRepository,
appCoroutineDispatchers: AppCoroutineDispatchers
) : AddObjectToWorkspace = AddObjectToWorkspace(
repo = repo,
dispatchers = appCoroutineDispatchers
)
}

View file

@ -17,6 +17,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.features.relations.RelationAddAdapter
import com.anytypeio.anytype.core_ui.features.relations.RelationAddHeaderAdapter
import com.anytypeio.anytype.core_ui.reactive.focusChanges
@ -34,7 +36,7 @@ import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.relations.RelationAddToDataViewViewModel
import com.anytypeio.anytype.presentation.relations.RelationAddToObjectViewModel
import com.anytypeio.anytype.presentation.relations.RelationAddViewModelBase
import com.anytypeio.anytype.presentation.relations.model.RelationView
import com.anytypeio.anytype.presentation.relations.RelationAddViewModelBase.Command
import com.google.android.material.bottomsheet.BottomSheetBehavior
import java.io.Serializable
import javax.inject.Inject
@ -56,7 +58,10 @@ abstract class RelationAddBaseFragment : BaseBottomSheetTextInputFragment<Fragme
}
private val relationAdapter = RelationAddAdapter { relation ->
onRelationSelected(ctx = ctx, relation = relation)
vm.onRelationSelected(
ctx = ctx,
relation = relation
)
}
private val concatAdapter = ConcatAdapter(createFromScratchAdapter, relationAdapter)
@ -94,6 +99,17 @@ abstract class RelationAddBaseFragment : BaseBottomSheetTextInputFragment<Fragme
subscribe(searchRelationInput.textChanges()) {
if (it.isEmpty()) clearSearchText.invisible() else clearSearchText.visible()
}
subscribe(vm.command) { command ->
when(command) {
is Command.DispatchSelectedRelation -> {
onRelationSelected(
ctx = command.ctx,
relation = command.relation,
format = command.format
)
}
}
}
}
}
@ -108,12 +124,7 @@ abstract class RelationAddBaseFragment : BaseBottomSheetTextInputFragment<Fragme
BottomSheetBehavior.from(root.parent as View).state = BottomSheetBehavior.STATE_EXPANDED
}
override fun onStart() {
super.onStart()
vm.onStart()
}
abstract fun onRelationSelected(ctx: Id, relation: RelationView.Existing)
abstract fun onRelationSelected(ctx: Id, relation: Key, format: RelationFormat)
abstract fun onCreateFromScratchClicked()
override fun inflateBinding(
@ -136,10 +147,11 @@ class RelationAddToObjectFragment : RelationAddBaseFragment() {
lateinit var factory: RelationAddToObjectViewModel.Factory
override val vm: RelationAddToObjectViewModel by viewModels { factory }
override fun onRelationSelected(ctx: Id, relation: RelationView.Existing) {
override fun onRelationSelected(ctx: Id, relation: Key, format: RelationFormat) {
vm.onRelationSelected(
ctx = ctx,
relation = relation,
format = format,
screenType = EventsDictionary.Type.menu
)
}
@ -177,10 +189,11 @@ class RelationAddToDataViewFragment : RelationAddBaseFragment() {
lateinit var factory: RelationAddToDataViewViewModel.Factory
override val vm: RelationAddToDataViewViewModel by viewModels { factory }
override fun onRelationSelected(ctx: Id, relation: RelationView.Existing) {
override fun onRelationSelected(ctx: Id, relation: Key, format: RelationFormat) {
vm.onRelationSelected(
ctx = ctx,
relation = relation,
format = format,
dv = dv,
screenType = EventsDictionary.Type.dataView
)
@ -235,10 +248,11 @@ class RelationAddToObjectBlockFragment : RelationAddBaseFragment() {
super.onStart()
}
override fun onRelationSelected(ctx: Id, relation: RelationView.Existing) {
override fun onRelationSelected(ctx: Id, relation: Key, format: RelationFormat) {
vm.onRelationSelected(
ctx = ctx,
relation = relation,
format = format,
screenType = EventsDictionary.Type.block
)
}

View file

@ -43,4 +43,8 @@ object MarketplaceObjectTypeIds {
const val BOOKMARK = "_otbookmark"
const val MARKETPLACE_OBJECT_TYPE_PREFIX = "_ot"
}
object Marketplace {
const val MARKETPLACE_ID = "_anytype_marketplace"
}

View file

@ -181,6 +181,8 @@ sealed class ObjectWrapper {
val id: Id by default
val key: Key get() = relationKey
val workspaceId: Id? by default
val sourceObject: Id? by default
val format: RelationFormat get() = relationFormat
val name: String? by default
val isHidden: Boolean? by default

View file

@ -4,7 +4,9 @@ import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.databinding.ItemDefaultListSectionBinding
class DefaultSectionViewHolder(
private val binding: ItemDefaultListSectionBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(name: String) { binding.tvSectionName.text = name }
val binding: ItemDefaultListSectionBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(name: String) {
binding.tvSectionName.text = name
}
}

View file

@ -2,54 +2,112 @@ package com.anytypeio.anytype.core_ui.features.relations
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.*
import com.anytypeio.anytype.core_ui.common.DefaultSectionViewHolder
import com.anytypeio.anytype.core_ui.databinding.ItemDefaultListSectionBinding
import com.anytypeio.anytype.core_ui.databinding.ItemRelationCreateFromScratchBinding
import com.anytypeio.anytype.core_ui.databinding.ItemRelationCreateFromScratchConnectWithBinding
import com.anytypeio.anytype.core_ui.databinding.ItemRelationCreateFromScratchLimitObjectTypesBinding
import com.anytypeio.anytype.core_ui.databinding.ItemRelationCreateFromScratchNameInputBinding
import com.anytypeio.anytype.core_ui.databinding.ItemRelationFormatBinding
import com.anytypeio.anytype.core_ui.databinding.ItemRelationFormatCreateFromScratchBinding
import com.anytypeio.anytype.core_ui.extensions.getPrettyName
import com.anytypeio.anytype.core_ui.features.relations.holders.DefaultRelationFormatViewHolder
import com.anytypeio.anytype.core_ui.features.relations.holders.DefaultRelationViewHolder
import com.anytypeio.anytype.core_utils.ext.dimen
import com.anytypeio.anytype.presentation.relations.model.LimitObjectTypeValueView
import com.anytypeio.anytype.presentation.relations.model.RelationItemView
import com.anytypeio.anytype.presentation.relations.model.RelationView
import com.anytypeio.anytype.presentation.relations.model.Section
class RelationAddAdapter(
val onItemClick: (RelationView.Existing) -> Unit
) : ListAdapter<RelationView.Existing, DefaultRelationViewHolder>(Differ) {
) : ListAdapter<RelationItemView, RecyclerView.ViewHolder>(Differ) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = DefaultRelationViewHolder(
binding = ItemRelationFormatBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
).apply {
itemView.setOnClickListener {
if (bindingAdapterPosition != RecyclerView.NO_POSITION)
onItemClick(getItem(bindingAdapterPosition))
) = when(viewType) {
R.layout.item_relation_format -> {
DefaultRelationViewHolder(
binding = ItemRelationFormatBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
).apply {
itemView.setOnClickListener {
if (bindingAdapterPosition != RecyclerView.NO_POSITION) {
val item = getItem(bindingAdapterPosition)
if (item is RelationView.Existing) { onItemClick(item) }
}
}
}
}
R.layout.item_default_list_section -> {
DefaultSectionViewHolder(
binding =ItemDefaultListSectionBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
).apply {
binding.tvSectionName.updateLayoutParams<FrameLayout.LayoutParams> {
marginStart = dimen(R.dimen.dp_20)
}
}
}
else -> throw IllegalStateException("Unexpected view type: $viewType")
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when(holder) {
is DefaultRelationViewHolder -> {
check(item is RelationView.Existing)
holder.bind(name = item.name, format = item.format)
}
is DefaultSectionViewHolder -> {
check(item is Section)
when(item) {
Section.Marketplace -> {
holder.bind(holder.itemView.resources.getString(R.string.marketplace))
}
Section.Library -> {
holder.bind(holder.itemView.resources.getString(R.string.my_relations))
}
}
}
}
}
override fun onBindViewHolder(holder: DefaultRelationViewHolder, position: Int) {
getItem(position).apply { holder.bind(name = name, format = format) }
override fun getItemViewType(position: Int): Int = when(val item = getItem(position)) {
is RelationView.Existing -> R.layout.item_relation_format
is Section -> R.layout.item_default_list_section
else -> throw IllegalStateException("Unexpected type: ${item::class.simpleName}")
}
override fun getItemViewType(position: Int): Int = R.layout.item_relation_format
object Differ : DiffUtil.ItemCallback<RelationView.Existing>() {
object Differ : DiffUtil.ItemCallback<RelationItemView>() {
override fun areItemsTheSame(
oldItem: RelationView.Existing,
newItem: RelationView.Existing
): Boolean = oldItem.key == newItem.key
oldItem: RelationItemView,
newItem: RelationItemView
): Boolean {
return if (oldItem is RelationView.Existing && newItem is RelationView.Existing)
oldItem.key == newItem.key
else
oldItem == newItem
}
override fun areContentsTheSame(
oldItem: RelationView.Existing,
newItem: RelationView.Existing
oldItem: RelationItemView,
newItem: RelationItemView
): Boolean = oldItem == newItem
}
}

View file

@ -583,5 +583,6 @@
<string name="set_by_type">Type: %1$s</string>
<string name="marketplace">Marketplace</string>
<string name="my_types">My types</string>
<string name="my_relations">My relations</string>
</resources>

View file

@ -0,0 +1,39 @@
package com.anytypeio.anytype.domain.relations
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.block.repo.BlockRepository
class GetRelations(
private val repo: BlockRepository
) : BaseUseCase<List<ObjectWrapper.Relation>, GetRelations.Params>() {
override suspend fun run(params: Params) = safe { proceedWithUseCase(params) }
suspend fun execute(params: Params) : List<ObjectWrapper.Relation> {
return proceedWithUseCase(params)
}
private suspend fun proceedWithUseCase(params: Params) = repo.searchObjects(
keys = params.keys,
filters = params.filters,
sorts = params.sorts,
limit = params.limit,
offset = params.offset,
fulltext = params.query
).map { struct ->
ObjectWrapper.Relation(struct)
}
data class Params(
val sorts: List<DVSort> = emptyList(),
val filters: List<DVFilter> = emptyList(),
val keys: List<Key> = emptyList(),
val offset: Int = 0,
val limit: Int = 0,
val query: String = ""
)
}

View file

@ -178,7 +178,7 @@ class ObjectTypeChangeViewModel(
)
}
},
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
query = query,
keys = ObjectSearchConstants.defaultKeysObjectType
)
@ -203,7 +203,7 @@ class ObjectTypeChangeViewModel(
)
}
},
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
query = query,
keys = ObjectSearchConstants.defaultKeysObjectType
)

View file

@ -7,13 +7,16 @@ import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.DV
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.dataview.interactor.AddRelationToDataView
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.relations.GetRelations
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.presentation.extension.getPropName
import com.anytypeio.anytype.presentation.extension.sendAnalyticsAddRelationEvent
import com.anytypeio.anytype.presentation.relations.model.RelationView
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
@ -23,25 +26,35 @@ import kotlinx.coroutines.launch
import timber.log.Timber
class RelationAddToDataViewViewModel(
storeOfRelations: StoreOfRelations,
relationsProvider: ObjectRelationProvider,
private val state: StateFlow<ObjectSet>,
private val session: ObjectSetSession,
private val updateDataViewViewer: UpdateDataViewViewer,
private val addRelationToDataView: AddRelationToDataView,
private val getRelations: GetRelations,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics
private val analytics: Analytics,
private val addObjectToWorkspace: AddObjectToWorkspace,
appCoroutineDispatchers: AppCoroutineDispatchers
) : RelationAddViewModelBase(
storeOfRelations = storeOfRelations,
relationsProvider = relationsProvider
relationsProvider = relationsProvider,
appCoroutineDispatchers = appCoroutineDispatchers,
getRelations = getRelations,
addObjectToWorkspace = addObjectToWorkspace
) {
fun onRelationSelected(ctx: Id, relation: RelationView.Existing, dv: Id, screenType: String) {
fun onRelationSelected(
ctx: Id,
relation: Key,
format: RelationFormat,
dv: Id,
screenType: String
) {
viewModelScope.launch {
addRelationToDataView(
AddRelationToDataView.Params(
ctx = ctx,
relation = relation.key,
relation = relation,
dv = dv
)
).process(
@ -49,13 +62,13 @@ class RelationAddToDataViewViewModel(
dispatcher.send(it).also {
proceedWithAddingNewRelationToCurrentViewer(
ctx = ctx,
relation = relation.key
relation = relation
)
}
sendAnalyticsAddRelationEvent(
analytics = analytics,
type = screenType,
format = relation.format.getPropName()
format = format.getPropName()
)
},
failure = {
@ -97,23 +110,27 @@ class RelationAddToDataViewViewModel(
private val state: StateFlow<ObjectSet>,
private val session: ObjectSetSession,
private val updateDataViewViewer: UpdateDataViewViewer,
private val storeOfRelations: StoreOfRelations,
private val addRelationToDataView: AddRelationToDataView,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val relationsProvider: ObjectRelationProvider,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val getRelations: GetRelations,
private val addObjectToWorkspace: AddObjectToWorkspace
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationAddToDataViewViewModel(
addRelationToDataView = addRelationToDataView,
storeOfRelations = storeOfRelations,
dispatcher = dispatcher,
session = session,
updateDataViewViewer = updateDataViewViewer,
state = state,
analytics = analytics,
relationsProvider = relationsProvider,
appCoroutineDispatchers = appCoroutineDispatchers,
getRelations = getRelations,
addObjectToWorkspace = addObjectToWorkspace
) as T
}
}

View file

@ -6,13 +6,17 @@ import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.relations.AddRelationToObject
import com.anytypeio.anytype.domain.relations.GetRelations
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.presentation.extension.getPropName
import com.anytypeio.anytype.presentation.extension.sendAnalyticsAddRelationEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsSearchQueryEvent
import com.anytypeio.anytype.presentation.relations.model.RelationView
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.util.Dispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
@ -24,29 +28,39 @@ class RelationAddToObjectViewModel(
private val addRelationToObject: AddRelationToObject,
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
val storeOfRelations: StoreOfRelations
val storeOfRelations: StoreOfRelations,
appCoroutineDispatchers: AppCoroutineDispatchers,
getRelations: GetRelations,
addObjectToWorkspace: AddObjectToWorkspace
) : RelationAddViewModelBase(
relationsProvider = relationsProvider,
storeOfRelations = storeOfRelations
appCoroutineDispatchers = appCoroutineDispatchers,
getRelations = getRelations,
addObjectToWorkspace = addObjectToWorkspace
) {
val commands = MutableSharedFlow<Command>(replay = 0)
fun onRelationSelected(ctx: Id, relation: RelationView.Existing, screenType: String) {
fun onRelationSelected(
ctx: Id,
relation: Key,
format: RelationFormat,
screenType: String
) {
viewModelScope.launch {
addRelationToObject(
AddRelationToObject.Params(
ctx = ctx,
relationKey = relation.key
relationKey = relation
)
).process(
success = {
dispatcher.send(it).also {
commands.emit(Command.OnRelationAdd(relation = relation.key))
commands.emit(Command.OnRelationAdd(relation = relation))
sendAnalyticsAddRelationEvent(
analytics = analytics,
type = screenType,
format = relation.format.getPropName()
format = format.getPropName()
)
isDismissed.value = true
}
@ -73,6 +87,9 @@ class RelationAddToObjectViewModel(
private val dispatcher: Dispatcher<Payload>,
private val analytics: Analytics,
private val relationsProvider: ObjectRelationProvider,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val getRelations: GetRelations,
private val addObjectToWorkspace: AddObjectToWorkspace
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -81,7 +98,10 @@ class RelationAddToObjectViewModel(
addRelationToObject = addRelationToObject,
storeOfRelations = storeOfRelations,
dispatcher = dispatcher,
analytics = analytics
analytics = analytics,
appCoroutineDispatchers = appCoroutineDispatchers,
getRelations = getRelations,
addObjectToWorkspace = addObjectToWorkspace
) as T
}
}

View file

@ -1,84 +1,230 @@
package com.anytypeio.anytype.presentation.relations
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.Marketplace
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_utils.ext.cancel
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.relations.GetRelations
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.relations.model.RelationItemView
import com.anytypeio.anytype.presentation.relations.model.RelationView
import com.anytypeio.anytype.presentation.relations.model.Section
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.defaultObjectSearchSorts
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.filterMarketplaceRelations
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants.filterMyRelations
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import timber.log.Timber
/**
* Base view model for adding a relation either to an object or to a set.
*/
abstract class RelationAddViewModelBase(
private val relationsProvider: ObjectRelationProvider,
private val storeOfRelations: StoreOfRelations
private val getRelations: GetRelations,
private val appCoroutineDispatchers: AppCoroutineDispatchers,
private val addObjectToWorkspace: AddObjectToWorkspace
) : BaseViewModel() {
private val jobs = mutableListOf<Job>()
private val userInput = MutableStateFlow(DEFAULT_INPUT)
private val views = MutableStateFlow<List<RelationView.Existing>>(emptyList())
private val searchQuery = userInput.take(1).onCompletion {
emitAll(userInput.drop(1).debounce(DEBOUNCE_DURATION).distinctUntilChanged())
}
val command = MutableSharedFlow<Command>()
val isDismissed = MutableStateFlow(false)
val results: Flow<List<RelationView.Existing>> = combine(searchQuery, views) { query, views ->
if (query.isEmpty())
views
else
views.filter { view -> view.name.contains(query, true) }
val results = MutableStateFlow(emptyList<RelationItemView>())
private val objectRelationKeys = relationsProvider.observeAll().map { relations ->
relations.map { r -> r.key }
}
abstract fun sendAnalyticsEvent(length: Int)
fun onStart() {
jobs += viewModelScope.launch {
val all = storeOfRelations.getAll().filter { it.isValid }
getVisibleRelations(available = all).collect { filtered ->
views.value = filtered
init {
viewModelScope.launch {
combine(
searchQuery,
objectRelationKeys
) { query, keys ->
val myRelations = proceedWithGettingMyRelations(query = query)
val marketplaceRelations = proceedWithGettingMarketplaceRelations(
query = query,
excluded = myRelations.mapNotNull { it.sourceObject }
)
buildViews(
myRelations = myRelations,
marketplaceRelations = marketplaceRelations,
objectRelationKeys = keys
)
}.flowOn(appCoroutineDispatchers.io).catch {
sendToast("An error occured. Please try again later.")
}.collect { views ->
results.value = views
}
}
}
fun onStop() {
jobs.cancel()
}
private fun getVisibleRelations(available: List<ObjectWrapper.Relation>): Flow<List<RelationView.Existing>> {
return relationsProvider.observeAll().map { addedRelations ->
val addedRelationKeys = addedRelations.map { it.key }.toSet()
available
.filter { it.isHidden != true }
.filter { relation -> !addedRelationKeys.contains(relation.key) }
.map {
RelationView.Existing(
key = it.key,
name = it.name.orEmpty(),
format = it.format
)
}
private fun buildViews(
myRelations: List<ObjectWrapper.Relation>,
marketplaceRelations: List<ObjectWrapper.Relation>,
objectRelationKeys: List<Key>,
) = buildList {
val my = myRelations.filter {
!objectRelationKeys.contains(it.key)
}.map { wrapper ->
RelationView.Existing(
id = wrapper.id,
key = wrapper.key,
name = wrapper.name.orEmpty(),
format = wrapper.format,
workspace = wrapper.workspaceId
)
}
val marketplace = marketplaceRelations.filter {
!objectRelationKeys.contains(it.key)
}.map { wrapper ->
RelationView.Existing(
id = wrapper.id,
key = wrapper.key,
name = wrapper.name.orEmpty(),
format = wrapper.format,
workspace = wrapper.workspaceId
)
}
if (my.isNotEmpty()) {
add(Section.Library)
addAll(my)
}
if (marketplace.isNotEmpty()) {
add(Section.Marketplace)
addAll(marketplace)
}
}
private suspend fun proceedWithGettingMarketplaceRelations(
excluded: List<Id>,
query: String
) = getRelations.execute(
GetRelations.Params(
sorts = defaultObjectSearchSorts(),
filters = buildList {
addAll(filterMarketplaceRelations())
if (excluded.isNotEmpty()) {
add(
DVFilter(
relationKey = Relations.ID,
condition = DVFilterCondition.NOT_IN,
value = excluded
)
)
}
add(
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.EQUAL,
value = false
)
)
},
query = query
)
)
private suspend fun proceedWithGettingMyRelations(query: String) = getRelations.execute(
GetRelations.Params(
sorts = defaultObjectSearchSorts(),
filters = buildList {
addAll(filterMyRelations())
add(
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.EQUAL,
value = false
)
)
},
query = query
)
)
abstract fun sendAnalyticsEvent(length: Int)
fun onQueryChanged(input: String) {
sendAnalyticsEvent(input.length)
userInput.value = input
}
fun onRelationSelected(
ctx: Id,
relation: RelationView.Existing
) {
viewModelScope.launch {
if (relation.workspace == Marketplace.MARKETPLACE_ID) {
addObjectToWorkspace(
AddObjectToWorkspace.Params(
objects = listOf(relation.id)
)
).proceed(
success = {
sendToast("Relation `${relation.name}` added to your library")
proceedWithDispatchingSelectedRelation(
ctx = ctx,
relation = relation
)
},
failure = {
Timber.e(it, "Error while adding relation to workspace.")
sendToast("Something went wrong. Please, try again later.")
}
)
}
proceedWithDispatchingSelectedRelation(
ctx = ctx,
relation = relation
)
}
}
private suspend fun proceedWithDispatchingSelectedRelation(
ctx: Id,
relation: RelationView.Existing
) {
command.emit(
Command.DispatchSelectedRelation(
ctx = ctx,
relation = relation.key,
format = relation.format
)
)
}
sealed class Command {
data class DispatchSelectedRelation(
val ctx: Id,
val relation: Key,
val format: RelationFormat
): Command()
}
companion object {
const val ERROR_MESSAGE = "Error while adding relation to object"
const val DEBOUNCE_DURATION = 300L

View file

@ -1,20 +1,31 @@
package com.anytypeio.anytype.presentation.relations.model
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Key
import com.anytypeio.anytype.core_models.RelationFormat
sealed class RelationView {
sealed class RelationItemView
sealed class RelationView : RelationItemView() {
abstract val format: RelationFormat
data class Existing(
val id: Id,
val key: Key,
val workspace: Id?,
val name: String,
override val format: RelationFormat
override val format: RelationFormat,
) : RelationView()
data class CreateFromScratch(
override val format: RelationFormat,
val isSelected: Boolean = false
) : RelationView()
}
sealed class Section : RelationItemView() {
object Library: Section()
object Marketplace : Section()
}

View file

@ -401,10 +401,46 @@ object ObjectSearchConstants {
//endregion
fun defaultObjectTypeSorts() : List<DVSort> = listOf(
fun defaultObjectSearchSorts() : List<DVSort> = listOf(
DVSort(
relationKey = Relations.NAME,
type = DVSortType.ASC
)
)
fun filterMyRelations() : List<DVFilter> = listOf(
DVFilter(
relationKey = Relations.TYPE,
condition = DVFilterCondition.EQUAL,
value = RELATION
),
DVFilter(
relationKey = Relations.IS_ARCHIVED,
condition = DVFilterCondition.EQUAL,
value = false
),
DVFilter(
relationKey = Relations.IS_DELETED,
condition = DVFilterCondition.EQUAL,
value = false
)
)
fun filterMarketplaceRelations() : List<DVFilter> = listOf(
DVFilter(
relationKey = Relations.TYPE,
condition = DVFilterCondition.EQUAL,
value = MarketplaceObjectTypeIds.RELATION
),
DVFilter(
relationKey = Relations.IS_ARCHIVED,
condition = DVFilterCondition.EQUAL,
value = false
),
DVFilter(
relationKey = Relations.IS_DELETED,
condition = DVFilterCondition.EQUAL,
value = false
)
)
}

View file

@ -1,81 +1,275 @@
package com.anytypeio.anytype.presentation.relations
import app.cash.turbine.test
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.StubRelationObject
import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.relations.GetRelations
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.presentation.relations.model.RelationView
import com.anytypeio.anytype.presentation.relations.model.Section
import com.anytypeio.anytype.presentation.relations.providers.FakeObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import kotlin.test.assertEquals
@ExperimentalCoroutinesApi
class RelationAddViewModelBaseTest {
@get:Rule
internal val coroutineTestRule = CoroutinesTestRule()
val coroutineTestRule = CoroutinesTestRule()
private val availableHidden = StubRelationObject(isHidden = true)
private val available = StubRelationObject()
private val availableRelations = listOf(available, availableHidden)
private val appCoroutineDispatchers = AppCoroutineDispatchers(
io = coroutineTestRule.testDispatcher,
main = coroutineTestRule.testDispatcher,
computation = coroutineTestRule.testDispatcher
)
@Mock
lateinit var repo: BlockRepository
private val relationsProvider = FakeObjectRelationProvider()
@Test
fun `no added relations - results are available without hidden`() {
runTest {
// SETUP
val store = DefaultStoreOfRelations()
val vm = createVM(store)
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
// TESTING
store.merge(availableRelations)
vm.onStart()
coroutineTestRule.testDispatcher.scheduler.runCurrent()
vm.results.test {
assertEquals(
actual = awaitItem(),
expected = listOf(
RelationView.Existing(
key = available.key,
name = available.name.orEmpty(),
format = available.format
@Test
fun `no added relations - results are available without hidden`() = runTest {
// SETUP
val relation = StubRelationObject()
repo.stub {
onBlocking {
searchObjects(
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
filters = buildList {
addAll(ObjectSearchConstants.filterMyRelations())
add(
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.EQUAL,
value = false
)
)
},
limit = 0,
offset = 0,
fulltext = ""
)
} doReturn listOf(relation.map)
}
repo.stub {
onBlocking {
searchObjects(
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
filters = buildList {
addAll(ObjectSearchConstants.filterMarketplaceRelations())
add(
DVFilter(
relationKey = Relations.ID,
condition = DVFilterCondition.NOT_IN,
value = listOf(relation.sourceObject)
)
)
add(
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.EQUAL,
value = false
)
)
},
limit = 0,
offset = 0,
fulltext = ""
)
} doReturn emptyList()
}
val vm = givenViewModel(relationsProvider = relationsProvider)
// TESTING
coroutineTestRule.testDispatcher.scheduler.runCurrent()
vm.results.test {
assertEquals(
actual = awaitItem(),
expected = listOf(
Section.Library,
RelationView.Existing(
key = relation.key,
id = relation.id,
name = relation.name.orEmpty(),
format = relation.format,
workspace = null
)
)
}
)
}
}
@Test
fun `added relations equal to available - results are empty`() {
relationsProvider.relation = available
runTest {
fun `added relations equal to available - results are empty`() = runTest {
// SETUP
val store = DefaultStoreOfRelations()
val vm = createVM(store)
val vm = givenViewModel(relationsProvider)
// TESTING
store.merge(availableRelations)
vm.onStart()
vm.results.test {
assertEquals(
actual = awaitItem(),
expected = emptyList()
)
}
}
@Test
fun `should query relations from library and marketplace filtering out already addded relations`() = runTest {
// SETUP
val marketplace = listOf(
StubRelationObject(),
StubRelationObject(),
StubRelationObject()
)
val library = listOf(
StubRelationObject(sourceObject = marketplace[0].id),
StubRelationObject(),
StubRelationObject()
)
repo.stub {
onBlocking {
searchObjects(
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
filters = buildList {
addAll(ObjectSearchConstants.filterMyRelations())
add(
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.EQUAL,
value = false
)
)
},
limit = 0,
offset = 0,
fulltext = ""
)
} doReturn library.map { it.map }
}
repo.stub {
onBlocking {
searchObjects(
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
filters = buildList {
addAll(ObjectSearchConstants.filterMarketplaceRelations())
add(
DVFilter(
relationKey = Relations.ID,
condition = DVFilterCondition.NOT_IN,
value = library.mapNotNull { it.sourceObject }
)
)
add(
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.EQUAL,
value = false
)
)
},
limit = 0,
offset = 0,
fulltext = ""
)
} doReturn marketplace.takeLast(2).map { it.map }
}
val vm = givenViewModel(relationsProvider = relationsProvider)
// TESTING
coroutineTestRule.testDispatcher.scheduler.runCurrent()
vm.results.test {
assertEquals(
actual = awaitItem(),
expected = listOf(
Section.Library,
RelationView.Existing(
key = library[0].key,
id = library[0].id,
name = library[0].name.orEmpty(),
format = library[0].format,
workspace = null
),
RelationView.Existing(
key = library[1].key,
id = library[1].id,
name = library[1].name.orEmpty(),
format = library[1].format,
workspace = null
),
RelationView.Existing(
key = library[2].key,
id = library[2].id,
name = library[2].name.orEmpty(),
format = library[2].format,
workspace = null
),
Section.Marketplace,
RelationView.Existing(
key = marketplace[1].key,
id = marketplace[1].id,
name = marketplace[1].name.orEmpty(),
format = marketplace[1].format,
workspace = null
),
RelationView.Existing(
key = marketplace[2].key,
id = marketplace[2].id,
name = marketplace[2].name.orEmpty(),
format = marketplace[2].format,
workspace = null
),
)
)
}
}
private fun createVM(
storeOfRelations: StoreOfRelations
private fun givenViewModel(
relationsProvider: ObjectRelationProvider
) = object : RelationAddViewModelBase(
storeOfRelations = storeOfRelations,
relationsProvider = relationsProvider
relationsProvider = relationsProvider,
getRelations = GetRelations(repo),
appCoroutineDispatchers = appCoroutineDispatchers,
addObjectToWorkspace = AddObjectToWorkspace(
repo = repo,
dispatchers = appCoroutineDispatchers
)
) {
override fun sendAnalyticsEvent(length: Int) {}
}

View file

@ -99,7 +99,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = ObjectSearchConstants.defaultKeysObjectType,
@ -143,7 +143,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = "",
@ -171,7 +171,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMyTypeKeys,
@ -184,7 +184,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMarketplaceTypeKeys,
@ -230,7 +230,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = "",
@ -245,7 +245,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = "",
@ -258,7 +258,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = query,
@ -273,7 +273,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = query,
@ -299,7 +299,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMyTypeKeys,
@ -312,7 +312,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMarketplaceTypeKeys,
@ -331,7 +331,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMyTypeKeys,
@ -344,7 +344,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMarketplaceTypeKeys,
@ -389,7 +389,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = "",
@ -404,7 +404,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = "",
@ -436,7 +436,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMyTypeKeys,
@ -449,7 +449,7 @@ class ObjectTypeChangeViewModelTest {
verifyBlocking(repo, times(1)) {
searchObjects(
filters = expectedMarketplaceTypeFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
keys = expectedMarketplaceTypeKeys,
@ -496,7 +496,7 @@ class ObjectTypeChangeViewModelTest {
onBlocking {
searchObjects(
filters = expectedMyTypesFilters,
sorts = ObjectSearchConstants.defaultObjectTypeSorts(),
sorts = ObjectSearchConstants.defaultObjectSearchSorts(),
limit = 0,
offset = 0,
fulltext = "",

View file

@ -2,31 +2,6 @@ package com.anytypeio.anytype.core_models
import com.anytypeio.anytype.test_utils.MockDataFactory
@Deprecated("To be deleted")
fun StubRelation(
key: String = MockDataFactory.randomString(),
name: String = MockDataFactory.randomString(),
format: Relation.Format = Relation.Format.SHORT_TEXT,
source: Relation.Source = Relation.Source.LOCAL,
isHidden: Boolean = false,
isReadOnly: Boolean = false,
isMulti: Boolean = false,
selections: List<Relation.Option> = emptyList(),
objectTypes: List<String> = emptyList(),
defaultValue: Any? = null
): Relation = Relation(
key,
name,
format,
source,
isHidden,
isReadOnly,
isMulti,
selections,
objectTypes,
defaultValue
)
fun StubRelationObject(
id: String = MockDataFactory.randomString(),
key: String = MockDataFactory.randomString(),
@ -35,7 +10,8 @@ fun StubRelationObject(
isHidden: Boolean = false,
isReadOnly: Boolean = false,
objectTypes: List<Id> = emptyList(),
relationOptionsDict: List<Id> = emptyList()
relationOptionsDict: List<Id> = emptyList(),
sourceObject: Id = MockDataFactory.randomUuid()
): ObjectWrapper.Relation = ObjectWrapper.Relation(
map = mapOf(
Relations.ID to id,
@ -45,21 +21,11 @@ fun StubRelationObject(
Relations.IS_READ_ONLY to isReadOnly,
Relations.RELATION_FORMAT_OBJECT_TYPES to objectTypes,
Relations.RELATION_FORMAT to format.code.toDouble(),
Relations.RELATION_OPTION_DICT to relationOptionsDict
Relations.RELATION_OPTION_DICT to relationOptionsDict,
Relations.SOURCE_OBJECT to sourceObject
)
)
@Deprecated("To be deleted")
fun StubRelationOption(
id: String = MockDataFactory.randomUuid(),
text: String = MockDataFactory.randomString(),
color: String = MockDataFactory.randomString()
): Relation.Option = Relation.Option(
id = id,
text = text,
color = color
)
fun StubRelationOptionObject(
id: String = MockDataFactory.randomUuid(),
text: String = MockDataFactory.randomString(),