mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1368 Widgets | Enhancement | Syncing active view (#106)
This commit is contained in:
parent
4df10d09d2
commit
ee42888ea9
18 changed files with 441 additions and 38 deletions
|
@ -14,7 +14,7 @@ class DefaultFeatureToggles @Inject constructor(
|
|||
private val buildProvider: BuildProvider
|
||||
) : FeatureToggles {
|
||||
|
||||
override val isLogFromMiddlewareLibrary =
|
||||
override val isLogFromGoProcess =
|
||||
BuildConfig.LOG_FROM_MW_LIBRARY && buildProvider.isDebug()
|
||||
|
||||
override val isLogMiddlewareInteraction =
|
||||
|
@ -32,4 +32,5 @@ class DefaultFeatureToggles @Inject constructor(
|
|||
|
||||
override val isAutoUpdateEnabled: Boolean = false
|
||||
|
||||
override val isConciseLogging: Boolean = true
|
||||
}
|
|
@ -4,10 +4,12 @@ interface FeatureToggles {
|
|||
|
||||
val isAutoUpdateEnabled: Boolean
|
||||
|
||||
val isLogFromMiddlewareLibrary: Boolean
|
||||
val isLogFromGoProcess: Boolean
|
||||
|
||||
val isLogMiddlewareInteraction: Boolean
|
||||
|
||||
val isConciseLogging: Boolean
|
||||
|
||||
val excludeThreadStatusLogging: Boolean
|
||||
|
||||
val isLogEditorViewModelEvents: Boolean
|
||||
|
|
|
@ -764,6 +764,16 @@ class BlockDataRepository(
|
|||
type = type
|
||||
)
|
||||
|
||||
override suspend fun setWidgetViewId(
|
||||
ctx: Id,
|
||||
widget: Id,
|
||||
view: Id
|
||||
): Payload = remote.setWidgetViewId(
|
||||
ctx = ctx,
|
||||
widget = widget,
|
||||
view = view
|
||||
)
|
||||
|
||||
override suspend fun addDataViewFilter(command: Command.AddFilter): Payload {
|
||||
return remote.addDataViewFilter(command = command)
|
||||
}
|
||||
|
|
|
@ -337,6 +337,12 @@ interface BlockRemote {
|
|||
type: Block.Content.Widget.Layout
|
||||
): Payload
|
||||
|
||||
suspend fun setWidgetViewId(
|
||||
ctx: Id,
|
||||
widget: Id,
|
||||
view: Id
|
||||
): Payload
|
||||
|
||||
suspend fun addDataViewFilter(command: Command.AddFilter): Payload
|
||||
suspend fun removeDataViewFilter(command: Command.RemoveFilter): Payload
|
||||
suspend fun replaceDataViewFilter(command: Command.ReplaceFilter): Payload
|
||||
|
|
|
@ -88,7 +88,7 @@ abstract class ResultInteractor<in P, R>(
|
|||
suspend fun run(params: P) = doWork(params)
|
||||
|
||||
/*
|
||||
* Executes on the caller's thread.
|
||||
* N.B. Executes on the caller's thread.
|
||||
* */
|
||||
suspend fun execute(params: P): Resultat<R> = runCatchingL { doWork(params) }
|
||||
|
||||
|
|
|
@ -389,6 +389,12 @@ interface BlockRepository {
|
|||
type: Block.Content.Widget.Layout
|
||||
): Payload
|
||||
|
||||
suspend fun setWidgetViewId(
|
||||
ctx: Id,
|
||||
widget: Id,
|
||||
view: Id
|
||||
): Payload
|
||||
|
||||
suspend fun addDataViewFilter(command: Command.AddFilter): Payload
|
||||
suspend fun removeDataViewFilter(command: Command.RemoveFilter): Payload
|
||||
suspend fun replaceDataViewFilter(command: Command.ReplaceFilter): Payload
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package com.anytypeio.anytype.domain.widgets
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use-case for setting active view id for widget with list or compact list layout.
|
||||
*/
|
||||
class SetWidgetActiveView @Inject constructor(
|
||||
private val repo: BlockRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<SetWidgetActiveView.Params, Payload>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Params) = repo.setWidgetViewId(
|
||||
ctx = params.ctx,
|
||||
widget = params.widget,
|
||||
view = params.view
|
||||
)
|
||||
|
||||
data class Params(
|
||||
val ctx: Id,
|
||||
val widget: Id,
|
||||
val view: Id
|
||||
)
|
||||
}
|
|
@ -728,6 +728,16 @@ class BlockMiddleware(
|
|||
type = type
|
||||
)
|
||||
|
||||
override suspend fun setWidgetViewId(
|
||||
ctx: Id,
|
||||
widget: Id,
|
||||
view: Id
|
||||
): Payload = middleware.setWidgetViewId(
|
||||
ctx = ctx,
|
||||
widget = widget,
|
||||
view = view
|
||||
)
|
||||
|
||||
override suspend fun addDataViewFilter(command: Command.AddFilter): Payload {
|
||||
return middleware.addDataViewFilter(command)
|
||||
}
|
||||
|
|
|
@ -1957,6 +1957,22 @@ class Middleware(
|
|||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
fun setWidgetViewId(
|
||||
ctx: Id,
|
||||
widget: Id,
|
||||
view: Id
|
||||
): Payload {
|
||||
val request = Rpc.BlockWidget.SetViewId.Request(
|
||||
contextId = ctx,
|
||||
blockId = widget,
|
||||
viewId = view
|
||||
)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.blockWidgetSetViewId(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun updateWidget(
|
||||
ctx: Id,
|
||||
|
|
|
@ -38,7 +38,8 @@ class MiddlewareEventChannel(
|
|||
msg.objectRelationsRemove,
|
||||
msg.blockDataviewViewUpdate,
|
||||
msg.blockDataviewTargetObjectIdSet,
|
||||
msg.blockDataviewIsCollectionSet
|
||||
msg.blockDataviewIsCollectionSet,
|
||||
msg.blockSetWidget
|
||||
)
|
||||
return events.any { it != null }
|
||||
}
|
||||
|
|
|
@ -47,9 +47,13 @@ interface MiddlewareProtobufLogger {
|
|||
}
|
||||
|
||||
private fun Any.toLogMessage(): String {
|
||||
return "${this::class.java.canonicalName}:\n${
|
||||
protobufConverter.provideConverter().toJson(this)
|
||||
}"
|
||||
return if (featureToggles.isConciseLogging) {
|
||||
this::class.java.canonicalName
|
||||
} else {
|
||||
"${this::class.java.canonicalName}:\n${
|
||||
protobufConverter.provideConverter().toJson(this)
|
||||
}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsOnlyThreadStatusEvents(event: Event) : Boolean {
|
||||
|
|
|
@ -393,7 +393,8 @@ fun MBlock.toCoreWidget(): Block.Content.Widget {
|
|||
MWidgetLayout.List -> Block.Content.Widget.Layout.LIST
|
||||
MWidgetLayout.CompactList -> Block.Content.Widget.Layout.COMPACT_LIST
|
||||
},
|
||||
limit = content.limit
|
||||
limit = content.limit,
|
||||
activeView = content.viewId.ifEmpty { null }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -427,6 +427,9 @@ interface MiddlewareService {
|
|||
@Throws(Exception::class)
|
||||
fun blockCreateWidget(request: Rpc.Block.CreateWidget.Request): Rpc.Block.CreateWidget.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun blockWidgetSetViewId(request: Rpc.BlockWidget.SetViewId.Request) : Rpc.BlockWidget.SetViewId.Response
|
||||
|
||||
//endregion
|
||||
|
||||
//region WORKSPACE
|
||||
|
|
|
@ -17,7 +17,7 @@ class MiddlewareServiceImplementation @Inject constructor(
|
|||
) : MiddlewareService {
|
||||
|
||||
init {
|
||||
if (!featureToggles.isLogFromMiddlewareLibrary) {
|
||||
if (!featureToggles.isLogFromGoProcess) {
|
||||
Service.setEnv("ANYTYPE_LOG_LEVEL", "*=fatal;anytype*=error")
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +164,19 @@ class MiddlewareServiceImplementation @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun blockWidgetSetViewId(request: Rpc.BlockWidget.SetViewId.Request): Rpc.BlockWidget.SetViewId.Response {
|
||||
val encoded = Service.blockWidgetSetViewId(
|
||||
Rpc.BlockWidget.SetViewId.Request.ADAPTER.encode(request)
|
||||
)
|
||||
val response = Rpc.BlockWidget.SetViewId.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.BlockWidget.SetViewId.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun blockDataViewActiveSet(request: Rpc.BlockDataview.View.SetActive.Request): Rpc.BlockDataview.View.SetActive.Response {
|
||||
val encoded = Service.blockDataviewViewSetActive(
|
||||
Rpc.BlockDataview.View.SetActive.Request.ADAPTER.encode(request)
|
||||
|
|
|
@ -43,6 +43,7 @@ import com.anytypeio.anytype.domain.widgets.CreateWidget
|
|||
import com.anytypeio.anytype.domain.widgets.DeleteWidget
|
||||
import com.anytypeio.anytype.domain.widgets.GetWidgetSession
|
||||
import com.anytypeio.anytype.domain.widgets.SaveWidgetSession
|
||||
import com.anytypeio.anytype.domain.widgets.SetWidgetActiveView
|
||||
import com.anytypeio.anytype.domain.widgets.UpdateWidget
|
||||
import com.anytypeio.anytype.presentation.extension.sendAddWidgetEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent
|
||||
|
@ -74,6 +75,7 @@ import com.anytypeio.anytype.presentation.widgets.WidgetSessionStateHolder
|
|||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.presentation.widgets.collection.Subscription
|
||||
import com.anytypeio.anytype.presentation.widgets.getActiveTabViews
|
||||
import com.anytypeio.anytype.presentation.widgets.parseActiveViews
|
||||
import com.anytypeio.anytype.presentation.widgets.parseWidgets
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
@ -128,7 +130,8 @@ class HomeScreenViewModel(
|
|||
private val saveWidgetSession: SaveWidgetSession,
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val objectWatcher: ObjectWatcher
|
||||
private val objectWatcher: ObjectWatcher,
|
||||
private val setWidgetActiveView: SetWidgetActiveView
|
||||
) : NavigationViewModel<HomeScreenViewModel.Navigation>(),
|
||||
Reducer<ObjectView, Payload>,
|
||||
WidgetActiveViewStateHolder by widgetActiveViewStateHolder,
|
||||
|
@ -269,7 +272,7 @@ class HomeScreenViewModel(
|
|||
root = state.obj.root,
|
||||
details = state.obj.details
|
||||
).also {
|
||||
// TODO active view logic here?
|
||||
widgetActiveViewStateHolder.init(state.obj.blocks.parseActiveViews())
|
||||
}
|
||||
}.collect {
|
||||
Timber.d("Emitting list of widgets: ${it.size}")
|
||||
|
@ -333,7 +336,7 @@ class HomeScreenViewModel(
|
|||
SaveWidgetSession.Params(
|
||||
WidgetSession(
|
||||
collapsed = collapsedWidgetStateHolder.get(),
|
||||
widgetsToActiveViews = views.value.getActiveTabViews()
|
||||
widgetsToActiveViews = emptyMap()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -783,6 +786,7 @@ class HomeScreenViewModel(
|
|||
)
|
||||
}
|
||||
is Event.Command.Widgets.SetWidget -> {
|
||||
Timber.d("Set widget event: $e")
|
||||
curr = curr.copy(
|
||||
blocks = curr.blocks.map { block ->
|
||||
if (block.id == e.widget) {
|
||||
|
@ -844,7 +848,6 @@ class HomeScreenViewModel(
|
|||
}
|
||||
if (session != null) {
|
||||
collapsedWidgetStateHolder.set(session.collapsed)
|
||||
widgetActiveViewStateHolder.init(session.widgetsToActiveViews)
|
||||
}
|
||||
proceedWithOpeningWidgetObject(widgetObject = configStorage.get().widgets)
|
||||
}
|
||||
|
@ -1053,6 +1056,28 @@ class HomeScreenViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onChangeCurrentWidgetView(widget: Id, view: Id) {
|
||||
widgetActiveViewStateHolder.onChangeCurrentWidgetView(
|
||||
widget = widget,
|
||||
view = view
|
||||
).also {
|
||||
viewModelScope.launch {
|
||||
setWidgetActiveView.stream(
|
||||
SetWidgetActiveView.Params(
|
||||
ctx = configStorage.get().widgets,
|
||||
widget = widget,
|
||||
view = view,
|
||||
)
|
||||
).collect { result ->
|
||||
result.fold(
|
||||
onSuccess = { objectPayloadDispatcher.send(it) },
|
||||
onFailure = { Timber.e(it, "Error while updating active view") }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
viewModelScope.launch {
|
||||
|
@ -1094,7 +1119,8 @@ class HomeScreenViewModel(
|
|||
private val saveWidgetSession: SaveWidgetSession,
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val objectWatcher: ObjectWatcher
|
||||
private val objectWatcher: ObjectWatcher,
|
||||
private val setWidgetActiveView: SetWidgetActiveView
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = HomeScreenViewModel(
|
||||
|
@ -1125,7 +1151,8 @@ class HomeScreenViewModel(
|
|||
saveWidgetSession = saveWidgetSession,
|
||||
spaceGradientProvider = spaceGradientProvider,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
objectWatcher = objectWatcher
|
||||
objectWatcher = objectWatcher,
|
||||
setWidgetActiveView = setWidgetActiveView
|
||||
) as T
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,20 @@ sealed class Widget {
|
|||
}
|
||||
}
|
||||
|
||||
fun List<Block>.parseActiveViews() : WidgetToActiveView {
|
||||
val result = mutableMapOf<WidgetId, WidgetActiveViewId>()
|
||||
forEach { block ->
|
||||
val content = block.content
|
||||
if (content is Block.Content.Widget) {
|
||||
val view = content.activeView
|
||||
if (!view.isNullOrEmpty()) {
|
||||
result[block.id] = view
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun List<Block>.parseWidgets(
|
||||
root: Id,
|
||||
details: Map<Id, Struct>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package com.anytypeio.anytype.presentation.widgets
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_utils.tools.toPrettyString
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import timber.log.Timber
|
||||
|
||||
interface WidgetActiveViewStateHolder {
|
||||
|
||||
|
@ -16,6 +18,7 @@ interface WidgetActiveViewStateHolder {
|
|||
private val widgetToActiveView = MutableStateFlow<WidgetToActiveView>(mapOf())
|
||||
|
||||
override fun init(map: WidgetToActiveView) {
|
||||
Timber.d("Initializing active view: ${map.toPrettyString()}")
|
||||
widgetToActiveView.value = map
|
||||
}
|
||||
|
||||
|
@ -29,4 +32,5 @@ interface WidgetActiveViewStateHolder {
|
|||
}
|
||||
}
|
||||
|
||||
typealias WidgetToActiveView = Map<Id, Id>
|
||||
typealias WidgetToActiveView = Map<Id, Id>
|
||||
typealias WidgetActiveViewId = Id
|
|
@ -3,6 +3,9 @@ package com.anytypeio.anytype.presentation.home
|
|||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.Block
|
||||
import com.anytypeio.anytype.core_models.DVFilter
|
||||
import com.anytypeio.anytype.core_models.DVFilterCondition
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
|
@ -13,6 +16,9 @@ import com.anytypeio.anytype.core_models.ObjectWrapper
|
|||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.StubConfig
|
||||
import com.anytypeio.anytype.core_models.StubDataView
|
||||
import com.anytypeio.anytype.core_models.StubDataViewView
|
||||
import com.anytypeio.anytype.core_models.StubFilter
|
||||
import com.anytypeio.anytype.core_models.StubLinkToObjectBlock
|
||||
import com.anytypeio.anytype.core_models.StubObject
|
||||
import com.anytypeio.anytype.core_models.StubObjectView
|
||||
|
@ -42,15 +48,16 @@ import com.anytypeio.anytype.domain.widgets.CreateWidget
|
|||
import com.anytypeio.anytype.domain.widgets.DeleteWidget
|
||||
import com.anytypeio.anytype.domain.widgets.GetWidgetSession
|
||||
import com.anytypeio.anytype.domain.widgets.SaveWidgetSession
|
||||
import com.anytypeio.anytype.domain.widgets.SetWidgetActiveView
|
||||
import com.anytypeio.anytype.domain.widgets.UpdateWidget
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
|
||||
import com.anytypeio.anytype.presentation.search.Subscriptions
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
|
||||
import com.anytypeio.anytype.presentation.util.Dispatcher
|
||||
import com.anytypeio.anytype.presentation.widgets.BundledWidgetSourceIds
|
||||
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.ListWidgetContainer
|
||||
import com.anytypeio.anytype.presentation.widgets.TreeWidgetContainer
|
||||
|
@ -81,6 +88,7 @@ import org.mockito.kotlin.stub
|
|||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.verifyBlocking
|
||||
import org.mockito.kotlin.verifyNoMoreInteractions
|
||||
|
||||
class HomeScreenViewModelTest {
|
||||
|
||||
|
@ -153,6 +161,9 @@ class HomeScreenViewModelTest {
|
|||
@Mock
|
||||
lateinit var getWidgetSession: GetWidgetSession
|
||||
|
||||
@Mock
|
||||
lateinit var setWidgetActiveView: SetWidgetActiveView
|
||||
|
||||
@Mock
|
||||
lateinit var storeOfObjectTypes: StoreOfObjectTypes
|
||||
|
||||
|
@ -205,7 +216,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events)
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubCollapsedWidgetState(any())
|
||||
stubGetWidgetSession()
|
||||
|
||||
|
@ -278,7 +289,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubCollapsedWidgetState(any())
|
||||
stubGetWidgetSession()
|
||||
|
||||
|
@ -349,7 +360,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -442,7 +453,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
|
@ -552,7 +563,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
|
@ -649,7 +660,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
|
@ -764,7 +775,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubFavoritesObjectWatcher()
|
||||
|
||||
stubSearchByIds(
|
||||
|
@ -956,7 +967,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -1036,7 +1047,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -1175,7 +1186,7 @@ class HomeScreenViewModelTest {
|
|||
}
|
||||
)
|
||||
stubConfig()
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -1242,7 +1253,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubConfig()
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -1328,7 +1339,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
|
||||
stubSearchByIds(
|
||||
subscription = favoriteWidgetBlock.id,
|
||||
|
@ -1464,7 +1475,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
|
||||
stubSearchByIds(
|
||||
subscription = favoriteWidgetBlock.id,
|
||||
|
@ -1676,7 +1687,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
|
||||
stubSearchByIds(
|
||||
subscription = favoriteWidgetBlock.id,
|
||||
|
@ -1872,7 +1883,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -1948,7 +1959,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -2015,7 +2026,7 @@ class HomeScreenViewModelTest {
|
|||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -2100,7 +2111,7 @@ class HomeScreenViewModelTest {
|
|||
)
|
||||
}
|
||||
)
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -2187,7 +2198,7 @@ class HomeScreenViewModelTest {
|
|||
)
|
||||
}
|
||||
)
|
||||
stubOpenObject(givenObjectView)
|
||||
stubOpenWidgetObject(givenObjectView)
|
||||
stubSearchByIds(
|
||||
subscription = widgetBlock.id,
|
||||
targets = emptyList()
|
||||
|
@ -2222,6 +2233,230 @@ class HomeScreenViewModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not re-fetch data after updating active view locally and then on mw`() = runTest {
|
||||
|
||||
val currentWidgetSourceObject = StubObject(
|
||||
id = "SOURCE OBJECT 1",
|
||||
links = emptyList(),
|
||||
objectType = ObjectTypeIds.SET
|
||||
)
|
||||
|
||||
val widgetSourceLink = StubLinkToObjectBlock(
|
||||
id = "SOURCE LINK",
|
||||
target = currentWidgetSourceObject.id
|
||||
)
|
||||
|
||||
val widgetBlock = StubWidgetBlock(
|
||||
id = "WIDGET BLOCK",
|
||||
layout = Block.Content.Widget.Layout.LIST,
|
||||
children = listOf(widgetSourceLink.id)
|
||||
)
|
||||
|
||||
val smartWidgetBlock = StubSmartBlock(
|
||||
id = WIDGET_OBJECT_ID,
|
||||
children = listOf(widgetBlock.id),
|
||||
)
|
||||
|
||||
val givenWidgetObjectView = StubObjectView(
|
||||
root = WIDGET_OBJECT_ID,
|
||||
blocks = listOf(
|
||||
smartWidgetBlock,
|
||||
widgetBlock,
|
||||
widgetSourceLink
|
||||
),
|
||||
details = mapOf(
|
||||
currentWidgetSourceObject.id to currentWidgetSourceObject.map
|
||||
)
|
||||
)
|
||||
|
||||
val dataViewFirstView = StubDataViewView()
|
||||
val dataViewSecondView = StubDataViewView(
|
||||
filters = listOf(StubFilter())
|
||||
)
|
||||
|
||||
val dataViewBlock = StubDataView(
|
||||
views = listOf(dataViewFirstView, dataViewSecondView)
|
||||
)
|
||||
|
||||
val dataViewSmartBlock = StubSmartBlock(
|
||||
id = currentWidgetSourceObject.id,
|
||||
children = listOf(dataViewBlock.id)
|
||||
)
|
||||
|
||||
val givenDataViewObjectView = StubObjectView(
|
||||
root = dataViewSmartBlock.id,
|
||||
blocks = listOf(
|
||||
dataViewSmartBlock,
|
||||
dataViewBlock
|
||||
),
|
||||
details = mapOf(
|
||||
currentWidgetSourceObject.id to mapOf(
|
||||
Relations.ID to currentWidgetSourceObject.map
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
stubConfig()
|
||||
stubInterceptEvents(events = emptyFlow())
|
||||
|
||||
stubOpenWidgetObject(givenWidgetObjectView)
|
||||
stubGetObject(givenDataViewObjectView)
|
||||
|
||||
stubWidgetActiveView(widgetBlock)
|
||||
|
||||
stubSetWidgetActiveView(
|
||||
widget = widgetBlock.id,
|
||||
view = dataViewSecondView.id
|
||||
)
|
||||
|
||||
val firstTimeParams = StoreSearchParams(
|
||||
subscription = widgetBlock.id,
|
||||
filters = buildList {
|
||||
addAll(ObjectSearchConstants.defaultDataViewFilters(config.workspace))
|
||||
add(
|
||||
DVFilter(
|
||||
relation = Relations.TYPE,
|
||||
condition = DVFilterCondition.NOT_IN,
|
||||
value = listOf(
|
||||
ObjectTypeIds.OBJECT_TYPE,
|
||||
ObjectTypeIds.RELATION,
|
||||
ObjectTypeIds.TEMPLATE,
|
||||
ObjectTypeIds.IMAGE,
|
||||
ObjectTypeIds.FILE,
|
||||
ObjectTypeIds.VIDEO,
|
||||
ObjectTypeIds.AUDIO,
|
||||
ObjectTypeIds.DASHBOARD,
|
||||
ObjectTypeIds.RELATION_OPTION,
|
||||
ObjectTypeIds.DASHBOARD,
|
||||
ObjectTypeIds.DATE
|
||||
)
|
||||
),
|
||||
)
|
||||
},
|
||||
sorts = emptyList(),
|
||||
limit = WidgetConfig.DEFAULT_LIST_MAX_COUNT,
|
||||
keys = buildList {
|
||||
addAll(ObjectSearchConstants.defaultDataViewKeys)
|
||||
add(Relations.DESCRIPTION)
|
||||
}.distinct(),
|
||||
source = currentWidgetSourceObject.setOf
|
||||
)
|
||||
|
||||
// Params expected after switching active view
|
||||
val secondTimeParams = firstTimeParams.copy(
|
||||
filters = buildList {
|
||||
addAll(dataViewSecondView.filters)
|
||||
addAll(firstTimeParams.filters)
|
||||
}
|
||||
)
|
||||
|
||||
stubDefaultSearch(
|
||||
params = firstTimeParams,
|
||||
results = emptyList()
|
||||
)
|
||||
|
||||
stubDefaultSearch(
|
||||
params = secondTimeParams,
|
||||
results = emptyList()
|
||||
)
|
||||
|
||||
stubCollapsedWidgetState(any())
|
||||
stubGetWidgetSession()
|
||||
stubGetDefaultPageType()
|
||||
stubObserveSpaceObject()
|
||||
|
||||
// Using real implementation here
|
||||
activeViewStateHolder = WidgetActiveViewStateHolder.Impl()
|
||||
|
||||
val vm = buildViewModel()
|
||||
|
||||
// TESTING
|
||||
|
||||
vm.onStart()
|
||||
|
||||
vm.views.test {
|
||||
val firstTimeState = awaitItem()
|
||||
assertEquals(
|
||||
actual = firstTimeState,
|
||||
expected = emptyList()
|
||||
)
|
||||
delay(1)
|
||||
val secondTimeItem = awaitItem()
|
||||
assertTrue {
|
||||
val firstWidget = secondTimeItem.first()
|
||||
firstWidget is WidgetView.SetOfObjects && firstWidget.tabs.first().isSelected
|
||||
}
|
||||
verifyBlocking(getObject, times(1)) {
|
||||
run(params = currentWidgetSourceObject.id)
|
||||
}
|
||||
verify(storelessSubscriptionContainer, times(1)).subscribe(
|
||||
StoreSearchByIdsParams(
|
||||
subscription = HomeScreenViewModel.HOME_SCREEN_SPACE_OBJECT_SUBSCRIPTION,
|
||||
targets = listOf(config.workspace),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.ICON_IMAGE,
|
||||
Relations.ICON_OPTION
|
||||
)
|
||||
)
|
||||
)
|
||||
verify(storelessSubscriptionContainer, times(1)).subscribe(
|
||||
firstTimeParams
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Changing active view
|
||||
vm.onChangeCurrentWidgetView(
|
||||
widget = widgetBlock.id,
|
||||
view = dataViewSecondView.id
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
val thirdTimeItem = awaitItem()
|
||||
advanceUntilIdle()
|
||||
assertTrue {
|
||||
val firstWidget = thirdTimeItem.first()
|
||||
firstWidget is WidgetView.SetOfObjects && firstWidget.tabs.last().isSelected
|
||||
}
|
||||
verify(storelessSubscriptionContainer, times(1)).subscribe(
|
||||
secondTimeParams
|
||||
)
|
||||
verifyNoMoreInteractions(storelessSubscriptionContainer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stubSetWidgetActiveView(
|
||||
widget: Id,
|
||||
view: Id,
|
||||
) {
|
||||
setWidgetActiveView.stub {
|
||||
on {
|
||||
stream(
|
||||
params = SetWidgetActiveView.Params(
|
||||
ctx = config.widgets,
|
||||
widget = widget,
|
||||
view = view
|
||||
)
|
||||
)
|
||||
} doReturn flowOf(
|
||||
Resultat.Success(
|
||||
Payload(
|
||||
context = WIDGET_OBJECT_ID,
|
||||
events = listOf(
|
||||
Event.Command.Widgets.SetWidget(
|
||||
context = WIDGET_OBJECT_ID,
|
||||
widget = widget,
|
||||
activeView = view
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stubInterceptEvents(events: Flow<List<Event>>) {
|
||||
interceptEvents.stub {
|
||||
on { build(InterceptEvents.Params(WIDGET_OBJECT_ID)) } doReturn events
|
||||
|
@ -2234,7 +2469,7 @@ class HomeScreenViewModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun stubOpenObject(givenObjectView: ObjectView) {
|
||||
private fun stubOpenWidgetObject(givenObjectView: ObjectView) {
|
||||
openObject.stub {
|
||||
on {
|
||||
stream(OpenObject.Params(WIDGET_OBJECT_ID, false))
|
||||
|
@ -2246,6 +2481,16 @@ class HomeScreenViewModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun stubGetObject(
|
||||
givenObjectView: ObjectView
|
||||
) {
|
||||
getObject.stub {
|
||||
onBlocking {
|
||||
run(givenObjectView.root)
|
||||
} doReturn givenObjectView
|
||||
}
|
||||
}
|
||||
|
||||
private fun stubCloseObject() {
|
||||
closeObject.stub {
|
||||
onBlocking {
|
||||
|
@ -2324,6 +2569,16 @@ class HomeScreenViewModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun stubGetDefaultPageType() {
|
||||
getDefaultPageType.stub {
|
||||
onBlocking {
|
||||
execute(any())
|
||||
} doReturn Resultat.Success(
|
||||
GetDefaultPageType.Response(null, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildViewModel() = HomeScreenViewModel(
|
||||
configStorage = configStorage,
|
||||
interceptEvents = interceptEvents,
|
||||
|
@ -2352,7 +2607,8 @@ class HomeScreenViewModelTest {
|
|||
saveWidgetSession = saveWidgetSession,
|
||||
spaceGradientProvider = spaceGradientProvider,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
objectWatcher = objectWatcher
|
||||
objectWatcher = objectWatcher,
|
||||
setWidgetActiveView = setWidgetActiveView
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue