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

Editor | Feature | Add Date objects as mentions (#1822)

* object search use case DI

* link to filter and sorts

* fixes

* di fix

* fix

* add support layout

* fixes

* fix

* fixes

* fixes

* fixes

* ci

* pr fix

* pr fix
This commit is contained in:
Konstantin Ivanov 2021-09-27 17:00:06 +04:00 committed by GitHub
parent 5f7cbc8b3c
commit 1e369208d7
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 358 additions and 164 deletions

View file

@ -2,7 +2,6 @@ package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.relations.RelationObjectValueAddViewModel
@ -42,11 +41,4 @@ object AddObjectRelationObjectValueModule {
RelationObjectValueAddViewModel.Factory(
relations, values, searchObjects, urlBuilder, objectTypesProvider
)
@JvmStatic
@Provides
@PerDialog
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -19,6 +19,7 @@ import com.anytypeio.anytype.domain.clipboard.Clipboard
import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.download.Downloader
@ -29,7 +30,6 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.domain.relations.AddFileToObject
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
@ -135,13 +135,13 @@ object EditorSessionModule {
renderer: DefaultBlockViewRenderer,
setObjectIsArchived: SetObjectIsArchived,
orchestrator: Orchestrator,
getListPages: GetListPages,
analytics: Analytics,
dispatcher: Dispatcher<Payload>,
detailModificationManager: DetailModificationManager,
updateDetail: UpdateDetail,
getCompatibleObjectTypes: GetCompatibleObjectTypes,
objectTypesProvider: ObjectTypesProvider
objectTypesProvider: ObjectTypesProvider,
searchObjects: SearchObjects
): EditorViewModelFactory = EditorViewModelFactory(
openPage = openPage,
closeObject = closePage,
@ -158,13 +158,13 @@ object EditorSessionModule {
renderer = renderer,
setObjectIsArchived = setObjectIsArchived,
orchestrator = orchestrator,
getListPages = getListPages,
analytics = analytics,
dispatcher = dispatcher,
detailModificationManager = detailModificationManager,
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
)
@JvmStatic
@ -291,11 +291,6 @@ object EditorSessionModule {
@Module
object EditorUseCaseModule {
@JvmStatic
@PerScreen
@Provides
fun getListPages(repo: BlockRepository): GetListPages = GetListPages(repo = repo)
@JvmStatic
@Provides
@PerScreen
@ -721,4 +716,11 @@ object EditorUseCaseModule {
fun provideGetCompatibleObjectTypesUseCase(
repository: BlockRepository
): GetCompatibleObjectTypes = GetCompatibleObjectTypes(repository)
@JvmStatic
@Provides
@PerScreen
fun searchObjects(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.di.feature.relations.RelationAddToDataViewSubComponent
import com.anytypeio.anytype.di.feature.relations.RelationCreateFromScratchForDataViewSubComponent
@ -269,4 +270,11 @@ object ObjectSetModule {
): SetObjectIsArchived = SetObjectIsArchived(
repo = repo
)
@JvmStatic
@Provides
@PerScreen
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -1,7 +1,6 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.relations.RelationFileValueAddViewModel
@ -40,11 +39,4 @@ object RelationFileValueAddModule {
RelationFileValueAddViewModel.Factory(
relations, values, searchObjects, urlBuilder
)
@JvmStatic
@Provides
@PerDialog
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -3,7 +3,6 @@ package com.anytypeio.anytype.di.feature.sets;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
@ -55,11 +54,4 @@ object CreateFilterModule {
urlBuilder = urlBuilder,
objectTypesProvider = objectTypesProvider
)
@JvmStatic
@Provides
@PerModal
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -3,7 +3,6 @@ package com.anytypeio.anytype.di.feature.sets;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
@ -55,11 +54,4 @@ object ModifyFilterModule {
urlBuilder = urlBuilder,
objectTypesProvider = objectTypesProvider
)
@JvmStatic
@Provides
@PerModal
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -1275,6 +1275,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
}
} else {
mentionSuggesterToolbar.invisible()
mentionSuggesterToolbar.clear()
recycler.removeItemDecoration(footerMentionDecorator)
}
}

View file

@ -111,6 +111,7 @@ class ObjectIconWidget @JvmOverloads constructor(
is ObjectIcon.Profile.Avatar -> setProfileInitials(icon.name)
is ObjectIcon.Profile.Image -> setCircularImage(icon.hash)
is ObjectIcon.Task -> setCheckbox(icon.isChecked)
ObjectIcon.None -> removeIcon()
}
}
@ -221,4 +222,10 @@ class ObjectIconWidget @JvmOverloads constructor(
rectangularIconContainer.invisible()
ivImage.invisible()
}
fun removeIcon() {
ivEmoji.setImageDrawable(null)
ivImage.setImageDrawable(null)
ivCheckbox.invisible()
}
}

View file

@ -4,11 +4,9 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.extensions.drawable
import com.anytypeio.anytype.core_ui.widgets.toolbar.adapter.MentionAdapter
import com.anytypeio.anytype.core_utils.ui.NpaLinearLayoutManager
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
import kotlinx.android.synthetic.main.widget_mention_menu.view.*
@ -20,6 +18,17 @@ class MentionToolbar @JvmOverloads constructor(
private var mentionClick: ((DefaultObjectView, String) -> Unit)? = null
private var newPageClick: ((String) -> Unit)? = null
private val mentionAdapter by lazy {
MentionAdapter(
data = arrayListOf(),
onClicked = { objectView, filter ->
mentionClick?.invoke(objectView, filter)
},
newClicked = { name ->
newPageClick?.invoke(name)
}
)
}
init {
LayoutInflater
@ -38,26 +47,22 @@ class MentionToolbar @JvmOverloads constructor(
private fun setup(context: Context) {
with(recyclerView) {
val lm = LinearLayoutManager(context)
val lm = NpaLinearLayoutManager(context)
layoutManager = lm
adapter = MentionAdapter(
data = arrayListOf(),
onClicked = { objectView, filter ->
mentionClick?.invoke(objectView, filter)
},
newClicked = { name ->
newPageClick?.invoke(name)
}
)
adapter = mentionAdapter
}
}
fun addItems(items: List<DefaultObjectView>) {
(recyclerView.adapter as? MentionAdapter)?.setData(items)
mentionAdapter.setData(items)
}
fun updateFilter(filter: String) {
(recyclerView.adapter as? MentionAdapter)?.updateFilter(filter)
mentionAdapter.updateFilter(filter)
}
fun clear() {
mentionAdapter.clear()
}
fun getMentionSuggesterWidgetMinHeight() = with(context.resources) {

View file

@ -6,7 +6,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.navigation.DefaultObjectViewAdapter
import com.anytypeio.anytype.presentation.editor.editor.mention.filterMentionsBy
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
class MentionAdapter(
@ -16,8 +15,6 @@ class MentionAdapter(
private val newClicked: (String) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val filteredData = mutableListOf<DefaultObjectView>()
fun setData(mentions: List<DefaultObjectView>) {
if (mentions.isEmpty()) {
data.clear()
@ -30,10 +27,13 @@ class MentionAdapter(
fun updateFilter(filter: String) {
mentionFilter = filter
filteredData.clear()
val filteredMentions = data.filterMentionsBy(text = mentionFilter)
filteredData.addAll(filteredMentions)
notifyDataSetChanged()
}
fun clear() {
mentionFilter = ""
val size = data.size
data.clear()
notifyItemRangeRemoved(0, size)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -53,7 +53,7 @@ class MentionAdapter(
itemView.setOnClickListener {
val pos = bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
onClicked(filteredData[pos - 1], mentionFilter)
onClicked(data[pos - 1], mentionFilter)
}
}
}
@ -61,7 +61,7 @@ class MentionAdapter(
}
}
override fun getItemCount(): Int = filteredData.size + 1
override fun getItemCount(): Int = data.size + 1
override fun getItemViewType(position: Int): Int = when (position) {
POSITION_NEW_PAGE -> TYPE_NEW_PAGE
@ -70,7 +70,7 @@ class MentionAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is DefaultObjectViewAdapter.ObjectViewHolder) {
holder.bind(filteredData[position - 1])
holder.bind(data[position - 1])
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.core_utils.ui
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
/**
* https://stackoverflow.com/questions/30220771/recyclerview-inconsistency-detected-invalid-item-position
*/
class NpaLinearLayoutManager(context: Context?) : LinearLayoutManager(context) {
/**
* Disable predictive animations. There is a bug in RecyclerView which causes views that
* are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
* adapter size has decreased since the ViewHolder was recycled.
*/
override fun supportsPredictiveItemAnimations(): Boolean {
return false
}
}

View file

@ -198,7 +198,7 @@ sealed class ControlPanelMachine {
sealed class Mentions : Event() {
data class OnStart(val cursorCoordinate: Int, val mentionFrom: Int) : Mentions()
data class OnQuery(val text: String) : Mentions()
data class OnResult(val mentions: List<DefaultObjectView>) : Mentions()
data class OnResult(val mentions: List<DefaultObjectView>, val text: String) : Mentions()
object OnMentionClicked : Mentions()
object OnStop : Mentions()
}
@ -787,6 +787,7 @@ sealed class ControlPanelMachine {
is Event.Mentions.OnResult -> state.copy(
mentionToolbar = state.mentionToolbar.copy(
mentions = event.mentions,
mentionFilter = event.text,
updateList = true
)
)

View file

@ -13,7 +13,6 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_BOOKMARK
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_DOCUMENT_ICON_MENU
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_DOCUMENT_MENU
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_MARKUP_LINK
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_MENTION_MENU
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_MULTI_SELECT_MENU
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_PROFILE_ICON_MENU
import com.anytypeio.anytype.analytics.base.EventsDictionary.POPUP_PROFILE_MENU
@ -40,13 +39,13 @@ import com.anytypeio.anytype.domain.block.interactor.RemoveLinkMark
import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.editor.Editor
import com.anytypeio.anytype.domain.error.Error
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.BuildConfig
import com.anytypeio.anytype.presentation.common.StateReducer
@ -64,6 +63,7 @@ import com.anytypeio.anytype.presentation.editor.editor.actions.ActionItemType
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
import com.anytypeio.anytype.presentation.editor.editor.ext.*
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
import com.anytypeio.anytype.presentation.editor.editor.mention.MentionConst
import com.anytypeio.anytype.presentation.editor.editor.mention.MentionConst.MENTION_PREFIX
import com.anytypeio.anytype.presentation.editor.editor.mention.MentionConst.MENTION_TITLE_EMPTY
import com.anytypeio.anytype.presentation.editor.editor.mention.MentionEvent
@ -89,14 +89,17 @@ import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.editor.search.search
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.linking.LinkToConstants
import com.anytypeio.anytype.presentation.mapper.mark
import com.anytypeio.anytype.presentation.mapper.style
import com.anytypeio.anytype.presentation.mapper.toMentionView
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
import com.anytypeio.anytype.presentation.navigation.SupportNavigation
import com.anytypeio.anytype.presentation.objects.SupportedLayouts
import com.anytypeio.anytype.presentation.objects.toDefaultObjectView
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.relations.views
import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
@ -124,13 +127,13 @@ class EditorViewModel(
private val urlBuilder: UrlBuilder,
private val renderer: DefaultBlockViewRenderer,
private val orchestrator: Orchestrator,
private val getListPages: GetListPages,
private val analytics: Analytics,
private val dispatcher: Dispatcher<Payload>,
private val detailModificationManager: DetailModificationManager,
private val updateDetail: UpdateDetail,
private val getCompatibleObjectTypes: GetCompatibleObjectTypes,
private val objectTypesProvider: ObjectTypesProvider
private val objectTypesProvider: ObjectTypesProvider,
private val searchObjects: SearchObjects
) : ViewStateViewModel<ViewState>(),
SupportNavigation<EventWrapper<AppNavigation.Command>>,
SupportCommand<Command>,
@ -199,11 +202,6 @@ class EditorViewModel(
*/
private var mediaBlockId = ""
/**
* Current position of last mentionFilter or -1 if none
*/
private var mentionFrom = -1
/**
* Currently pending text update. If null, it is not present or already dispatched.
*/
@ -4922,6 +4920,14 @@ class EditorViewModel(
//endregion
//region MENTION WIDGET
/**
* Current position of last mentionFilter or -1 if none
*/
private var mentionFrom = -1
private val mentionFilter = MutableStateFlow("")
val mentionSearchQuery = mentionFilter.asStateFlow()
private var jobMentionFilter: Job? = null
fun onStartMentionWidgetClicked() {
dispatch(Command.AddMentionWidgetTriggerToFocusedBlock)
}
@ -4930,6 +4936,7 @@ class EditorViewModel(
Timber.d("onMentionEvent, mentionEvent:[$mentionEvent]")
when (mentionEvent) {
is MentionEvent.MentionSuggestText -> {
mentionFilter.value = mentionEvent.text.toString()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Mentions.OnQuery(
text = mentionEvent.text.toString()
@ -4944,33 +4951,23 @@ class EditorViewModel(
mentionFrom = mentionEvent.mentionStart
)
)
viewModelScope.launch {
getListPages.invoke(Unit).proceed(
failure = { it.timber() },
success = { response ->
val objectTypes = objectTypesProvider.get()
val objectViews = response.listPages.map { pages ->
pages.toMentionView(
objectTypes = objectTypes,
urlBuilder = urlBuilder
)
}
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Mentions.OnResult(objectViews)
)
}
)
jobMentionFilter?.cancel()
mentionFilter.value = ""
jobMentionFilter = viewModelScope.launch {
mentionSearchQuery
.debounce(300)
.collect { onMentionFilter(it) }
}
viewModelScope.sendEvent(
analytics = analytics,
eventName = POPUP_MENTION_MENU
eventName = EventsDictionary.POPUP_MENTION_MENU
)
}
MentionEvent.MentionSuggestStop -> {
mentionFrom = -1
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Mentions.OnStop
)
jobMentionFilter?.cancel()
mentionFilter.value = ""
controlPanelInteractor.onEvent(ControlPanelMachine.Event.Mentions.OnStop)
}
}
}
@ -5066,5 +5063,43 @@ class EditorViewModel(
private fun onMentionClicked(target: String) {
proceedWithOpeningObjectByLayout(target)
}
private suspend fun onMentionFilter(filter: String) {
controlPanelViewState.value?.let { state ->
if (!state.mentionToolbar.isVisible) {
jobMentionFilter?.cancel()
return
}
val filters = LinkToConstants.filters
val sorts = LinkToConstants.sorts
val fullText = filter.removePrefix(MENTION_PREFIX)
val params = SearchObjects.Params(
limit = ObjectSearchViewModel.SEARCH_LIMIT,
filters = filters,
sorts = sorts,
fulltext = fullText
)
viewModelScope.launch {
searchObjects(params).process(
success = { raw ->
val objects = raw.toDefaultObjectView(
objectTypes = objectTypesProvider.get(),
urlBuilder = urlBuilder
).filter {
SupportedLayouts.layouts.contains(it.typeLayout)
&& it.type != ObjectTypeConst.TEMPLATE
}
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Mentions.OnResult(
objects,
filter
)
)
},
failure = { Timber.e(it, "Error while searching for mention objects") }
)
}
}
}
//endregion
}

View file

@ -11,11 +11,11 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.interactor.RemoveLinkMark
import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.editor.editor.DetailModificationManager
@ -39,13 +39,13 @@ open class EditorViewModelFactory(
private val urlBuilder: UrlBuilder,
private val renderer: DefaultBlockViewRenderer,
private val orchestrator: Orchestrator,
private val getListPages: GetListPages,
private val analytics: Analytics,
private val dispatcher: Dispatcher<Payload>,
private val detailModificationManager: DetailModificationManager,
private val updateDetail: UpdateDetail,
private val getCompatibleObjectTypes: GetCompatibleObjectTypes,
private val objectTypesProvider: ObjectTypesProvider
private val objectTypesProvider: ObjectTypesProvider,
private val searchObjects: SearchObjects
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -66,13 +66,13 @@ open class EditorViewModelFactory(
createDocument = createDocument,
createNewDocument = createNewDocument,
orchestrator = orchestrator,
getListPages = getListPages,
analytics = analytics,
dispatcher = dispatcher,
detailModificationManager = detailModificationManager,
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
) as T
}
}

View file

@ -0,0 +1,27 @@
package com.anytypeio.anytype.presentation.linking
import com.anytypeio.anytype.core_models.*
object LinkToConstants {
val filters = listOf(
DVFilter(
condition = DVFilterCondition.EQUAL,
value = false,
relationKey = Relations.IS_ARCHIVED,
operator = DVFilterOperator.AND
),
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
val sorts = listOf(
DVSort(
relationKey = Relations.LAST_OPENED_DATE,
type = DVSortType.DESC
)
)
}

View file

@ -28,27 +28,8 @@ class LinkToObjectViewModel(
override fun getSearchObjectsParams(): SearchObjects.Params {
val filteredTypes = types.value.map { objectType -> objectType.url }
val filters = listOf(
DVFilter(
condition = DVFilterCondition.EQUAL,
value = false,
relationKey = Relations.IS_ARCHIVED,
operator = DVFilterOperator.AND
),
DVFilter(
relationKey = Relations.IS_HIDDEN,
condition = DVFilterCondition.NOT_EQUAL,
value = true
)
)
val sorts = listOf(
DVSort(
relationKey = Relations.LAST_OPENED_DATE,
type = DVSortType.DESC
)
)
val filters = LinkToConstants.filters
val sorts = LinkToConstants.sorts
return SearchObjects.Params(
limit = SEARCH_LIMIT,

View file

@ -8,10 +8,8 @@ import com.anytypeio.anytype.presentation.editor.editor.Markup
import com.anytypeio.anytype.presentation.editor.editor.model.Alignment
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.editor.editor.model.UiBlock
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
import com.anytypeio.anytype.presentation.navigation.ObjectView
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.relations.getTypePrettyName
import com.anytypeio.anytype.presentation.relations.type
import com.anytypeio.anytype.presentation.sets.buildGridRow
import com.anytypeio.anytype.presentation.sets.model.*
@ -447,20 +445,6 @@ fun DocumentInfo.toView(
)
}
fun DocumentInfo.toMentionView(
urlBuilder: UrlBuilder, objectTypes: List<ObjectType>
) = DefaultObjectView(
id = id,
name = obj.name.orEmpty(),
typeName = objectTypes.getTypePrettyName(type = obj.type.firstOrNull()),
typeLayout = obj.layout,
icon = ObjectIcon.from(
obj = obj,
layout = obj.layout,
builder = urlBuilder
)
)
fun Block.Fields.getName(): String =
this.name.let { name ->
if (name.isNullOrBlank()) Relations.RELATION_NAME_EMPTY else name

View file

@ -7,6 +7,7 @@ import com.anytypeio.anytype.presentation.objects.ObjectIcon
data class DefaultObjectView(
val id: Id,
val name: String,
val type: String? = null,
val typeName: String? = null,
val typeLayout: ObjectType.Layout? = null,
val icon: ObjectIcon = ObjectIcon.None

View file

@ -0,0 +1,28 @@
package com.anytypeio.anytype.presentation.objects
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
fun List<Map<String, Any?>>.toDefaultObjectView(
urlBuilder: UrlBuilder,
objectTypes: List<ObjectType>
): List<DefaultObjectView> =
this.map { record ->
val obj = ObjectWrapper.Basic(record)
val type = obj.type.firstOrNull()
val layout = obj.layout ?: ObjectType.Layout.BASIC
DefaultObjectView(
id = obj.id,
name = obj.name.orEmpty(),
typeName = objectTypes.find { it.url == type }?.name.orEmpty(),
typeLayout = layout,
icon = ObjectIcon.from(
obj = obj,
layout = layout,
builder = urlBuilder
),
type = type
)
}

View file

@ -9,6 +9,7 @@ import com.anytypeio.anytype.presentation.editor.cover.CoverColor
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
import com.anytypeio.anytype.presentation.extension.isValueRequired
import com.anytypeio.anytype.presentation.mapper.*
import com.anytypeio.anytype.presentation.navigation.DefaultObjectView
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetViewState

View file

@ -26,9 +26,11 @@ open class ObjectSearchViewModel(
SupportNavigation<EventWrapper<AppNavigation.Command>> {
private val userInput = MutableStateFlow(EMPTY_QUERY)
private val searchQuery = userInput.take(1).onCompletion {
emitAll(userInput.debounce(DEBOUNCE_DURATION).distinctUntilChanged())
}
private val searchQuery = userInput
.take(1)
.onCompletion {
emitAll(userInput.debounce(DEBOUNCE_DURATION).distinctUntilChanged())
}
protected val types = MutableStateFlow(emptyList<ObjectType>())
protected val objects = MutableStateFlow(emptyList<ObjectWrapper.Basic>())

View file

@ -17,6 +17,7 @@ import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
@ -24,7 +25,6 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.MockBlockFactory
import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider
@ -130,9 +130,6 @@ open class EditorViewModelTest {
@Mock
lateinit var splitBlock: SplitBlock
@Mock
lateinit var getListPages: GetListPages
@Mock
lateinit var createPage: CreatePage
@ -211,6 +208,9 @@ open class EditorViewModelTest {
@Mock
lateinit var setObjectType: SetObjectType
@Mock
lateinit var searchObjects: SearchObjects
@Mock
lateinit var objectTypesProvider: ObjectTypesProvider
@ -3895,7 +3895,6 @@ open class EditorViewModelTest {
updateDetail = UpdateDetail(repo)
vm = EditorViewModel(
getListPages = getListPages,
openPage = openPage,
closePage = closePage,
createPage = createPage,
@ -3959,7 +3958,8 @@ open class EditorViewModelTest {
detailModificationManager = InternalDetailModificationManager(storage.details),
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
)
}

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.editor.editor
import MockDataFactory
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import app.cash.turbine.test
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
@ -12,7 +13,6 @@ import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.domain.page.CreateNewDocument
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState
import com.anytypeio.anytype.presentation.editor.editor.mention.MentionConst.MENTION_TITLE_EMPTY
@ -22,6 +22,8 @@ import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.presentation.util.TXT
import com.jraska.livedata.test
import net.lachlanmckee.timberjunit.TimberTestRule
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -30,6 +32,8 @@ import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.*
import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime
import kotlin.test.assertEquals
class EditorMentionTest : EditorPresentationTestSetup() {
@ -133,8 +137,8 @@ class EditorMentionTest : EditorPresentationTestSetup() {
onBlocking { invoke(any()) } doReturn Either.Right(Unit)
}
getListPages.stub {
onBlocking { invoke(any()) } doReturn Either.Right(GetListPages.Response(emptyList()))
searchObjects.stub {
onBlocking { invoke(any()) } doReturn Either.Right(listOf())
}
val vm = buildViewModel()
@ -285,8 +289,8 @@ class EditorMentionTest : EditorPresentationTestSetup() {
onBlocking { invoke(any()) } doReturn Either.Right(Unit)
}
getListPages.stub {
onBlocking { invoke(any()) } doReturn Either.Right(GetListPages.Response(emptyList()))
searchObjects.stub {
onBlocking { invoke(any()) } doReturn Either.Right(listOf())
}
Mockito.`when`(documentEmojiIconProvider.random()).thenReturn(emoji)
@ -451,8 +455,8 @@ class EditorMentionTest : EditorPresentationTestSetup() {
onBlocking { invoke(any()) } doReturn Either.Right(Unit)
}
getListPages.stub {
onBlocking { invoke(any()) } doReturn Either.Right(GetListPages.Response(emptyList()))
searchObjects.stub {
onBlocking { invoke(any()) } doReturn Either.Right(listOf())
}
Mockito.`when`(documentEmojiIconProvider.random()).thenReturn(emoji)
@ -585,8 +589,8 @@ class EditorMentionTest : EditorPresentationTestSetup() {
onBlocking { invoke(any()) } doReturn Either.Right(Unit)
}
getListPages.stub {
onBlocking { invoke(any()) } doReturn Either.Right(GetListPages.Response(emptyList()))
searchObjects.stub {
onBlocking { invoke(any()) } doReturn Either.Right(listOf())
}
val vm = buildViewModel()
@ -658,6 +662,121 @@ class EditorMentionTest : EditorPresentationTestSetup() {
slashWidget = ControlPanelState.Toolbar.SlashWidget.reset()
)
)
clearPendingCoroutines()
}
@ExperimentalTime
@Test
fun `test mention filters`() {
val a = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Start end",
marks = listOf(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(),
children = listOf(a.id)
)
val document = listOf(page, a)
stubOpenDocument(document)
stubInterceptEvents()
stubUpdateText()
stubSearchObjects()
val vm = buildViewModel()
vm.onStart(root)
//TESTING
runBlocking {
vm.mentionSearchQuery.test {
vm.apply {
onBlockFocusChanged(
id = a.id,
hasFocus = true
)
onSelectionChanged(
id = a.id,
selection = IntRange(6, 6)
)
vm.onMentionEvent(
MentionEvent.MentionSuggestStart(
cursorCoordinate = 999,
mentionStart = 6
)
)
vm.onMentionEvent(
MentionEvent.MentionSuggestText(text = "@")
)
vm.onTextBlockTextChanged(
view = BlockView.Text.Paragraph(
id = a.id,
marks = emptyList(),
text = "Start @end"
)
)
onSelectionChanged(
id = a.id,
selection = IntRange(7, 7)
)
}
assertEquals(
expected = "@", actual = expectMostRecentItem()
)
vm.onMentionEvent(
MentionEvent.MentionSuggestText(text = "@t")
)
vm.onTextBlockTextChanged(
view = BlockView.Text.Paragraph(
id = a.id,
marks = emptyList(),
text = "Start @tend"
)
)
vm.onSelectionChanged(
id = a.id,
selection = IntRange(8, 8)
)
assertEquals(
expected = "@t", actual = expectMostRecentItem()
)
vm.onMentionEvent(
MentionEvent.MentionSuggestText(text = "@to")
)
vm.onTextBlockTextChanged(
view = BlockView.Text.Paragraph(
id = a.id,
marks = emptyList(),
text = "Start @toend"
)
)
vm.onSelectionChanged(
id = a.id,
selection = IntRange(9, 9)
)
assertEquals(
expected = "@to", actual = expectMostRecentItem()
)
}
}
clearPendingCoroutines()
}
@Test

View file

@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
@ -23,7 +24,6 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.objects.SetObjectIsArchived
import com.anytypeio.anytype.domain.page.*
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.editor.DocumentExternalEventReducer
import com.anytypeio.anytype.presentation.editor.Editor
@ -64,9 +64,6 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var updateText: UpdateText
@Mock
lateinit var getListPages: GetListPages
@Mock
lateinit var updateCheckbox: UpdateCheckbox
@ -184,6 +181,9 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var objectTypesProvider: ObjectTypesProvider
@Mock
lateinit var searchObjects: SearchObjects
private val builder: UrlBuilder get() = UrlBuilder(gateway)
private lateinit var updateDetail: UpdateDetail
@ -240,7 +240,6 @@ open class EditorPresentationTestSetup {
)
return EditorViewModel(
getListPages = getListPages,
openPage = openPage,
closePage = closePage,
createPage = createPage,
@ -266,7 +265,8 @@ open class EditorPresentationTestSetup {
detailModificationManager = InternalDetailModificationManager(storage.details),
updateDetail = updateDetail,
getCompatibleObjectTypes = getCompatibleObjectTypes,
objectTypesProvider = objectTypesProvider
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects
)
}
@ -508,4 +508,10 @@ open class EditorPresentationTestSetup {
)
}
}
fun stubSearchObjects() {
searchObjects.stub {
onBlocking { invoke(any()) } doReturn Either.Right(listOf())
}
}
}