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

Data View | Syncing status (#1640)

This commit is contained in:
Evgenii Kozlov 2021-07-15 11:13:08 +03:00 committed by GitHub
parent a7701a6b9a
commit eca24554bd
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 170 additions and 31 deletions

View file

@ -140,6 +140,8 @@ class ObjectSetGridColumnRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation1, relation2, relation3, relation4, relation5),
@ -156,8 +158,7 @@ class ObjectSetGridColumnRenderingTest : TestObjectSetSetup() {
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvHeader.rVMatcher()) {
// There should be 5 column headers + 1 plus button
checkIsRecyclerSize(6)
checkIsRecyclerSize(5)
onItemView(0, R.id.cellText).checkHasText(relation1.name)
onItemView(1, R.id.cellText).checkHasText(relation2.name)
onItemView(2, R.id.cellText).checkHasText(relation3.name)

View file

@ -149,6 +149,8 @@ class ObjectSetGridFileCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),

View file

@ -113,6 +113,8 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
@ -206,6 +208,8 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
@ -299,6 +303,8 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
@ -392,6 +398,8 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
@ -485,6 +493,8 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
@ -500,7 +510,6 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
onItemView(0, R.id.tvTitle).checkHasText("The Great Dictator")
onItemView(0, R.id.tvText).checkHasText("1234.0564321")
}
@ -578,6 +587,8 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubInterceptThreadStatus()
stubSetActiveViewer()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
@ -593,7 +604,6 @@ class ObjectSetGridNumberCellRenderingTest : TestObjectSetSetup() {
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
onItemView(0, R.id.tvTitle).checkHasText("The Great Dictator")
onItemView(0, R.id.tvText).checkHasText("-1234")
}

View file

@ -13,6 +13,8 @@ import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.sets.ObjectSetRecordCache
@ -37,6 +39,7 @@ abstract class TestObjectSetSetup {
private lateinit var createDataViewRecord: CreateDataViewRecord
private lateinit var closeBlock: CloseBlock
private lateinit var setActiveViewer: SetActiveViewer
private lateinit var interceptThreadStatus: InterceptThreadStatus
lateinit var urlBuilder: UrlBuilder
@ -46,6 +49,8 @@ abstract class TestObjectSetSetup {
lateinit var gateway: Gateway
@Mock
lateinit var interceptEvents: InterceptEvents
@Mock
lateinit var threadStatusChannel: ThreadStatusChannel
private val session = ObjectSetSession()
private val reducer = ObjectSetReducer()
@ -85,6 +90,7 @@ abstract class TestObjectSetSetup {
updateDataViewRecord = UpdateDataViewRecord(repo)
updateDataViewViewer = UpdateDataViewViewer(repo)
setActiveViewer = SetActiveViewer(repo)
interceptThreadStatus = InterceptThreadStatus(channel = threadStatusChannel)
closeBlock = CloseBlock(repo)
urlBuilder = UrlBuilder(gateway)
@ -93,6 +99,7 @@ abstract class TestObjectSetSetup {
closeBlock = closeBlock,
addDataViewRelation = addDataViewRelation,
interceptEvents = interceptEvents,
interceptThreadStatus = interceptThreadStatus,
updateDataViewViewer = updateDataViewViewer,
setActiveViewer = setActiveViewer,
createDataViewRecord = createDataViewRecord,
@ -112,6 +119,29 @@ abstract class TestObjectSetSetup {
}
}
fun stubSetActiveViewer() {
repo.stub {
onBlocking {
setActiveDataViewViewer(
context = any(),
block = any(),
view = any(),
offset = any(),
limit = any()
)
} doReturn Payload(
context = ctx,
events = emptyList()
)
}
}
fun stubInterceptThreadStatus() {
threadStatusChannel.stub {
onBlocking { observe(any()) } doReturn emptyFlow()
}
}
fun stubOpenObjectSet(
set: List<Block>,
details: Block.Details = Block.Details(),

View file

@ -20,6 +20,8 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.relations.AddFileToRecord
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
import com.anytypeio.anytype.presentation.relations.providers.*
import com.anytypeio.anytype.presentation.sets.*
import com.anytypeio.anytype.presentation.util.Dispatcher
@ -79,6 +81,7 @@ object ObjectSetModule {
updateDataViewRecord: UpdateDataViewRecord,
updateText: UpdateText,
interceptEvents: InterceptEvents,
interceptThreadStatus: InterceptThreadStatus,
createDataViewRecord: CreateDataViewRecord,
reducer: ObjectSetReducer,
dispatcher: Dispatcher<Payload>,
@ -95,6 +98,7 @@ object ObjectSetModule {
createDataViewRecord = createDataViewRecord,
updateText = updateText,
interceptEvents = interceptEvents,
interceptThreadStatus = interceptThreadStatus,
reducer = reducer,
dispatcher = dispatcher,
objectSetRecordCache = objectSetRecordCache,
@ -159,6 +163,15 @@ object ObjectSetModule {
context = Dispatchers.IO
)
@JvmStatic
@Provides
@PerScreen
fun provideInterceptThreadStatus(
channel: ThreadStatusChannel
) : InterceptThreadStatus = InterceptThreadStatus(
channel = channel
)
@JvmStatic
@Provides
@PerScreen

View file

@ -5,7 +5,6 @@ import android.animation.ObjectAnimator
import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.graphics.Color
import android.graphics.Point
import android.net.Uri
import android.os.Build
@ -43,12 +42,11 @@ import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Block.Content.Text
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.core_models.ext.getFirstLinkMarkupParam
import com.anytypeio.anytype.core_models.ext.getSubstring
import com.anytypeio.anytype.core_ui.extensions.color
import com.anytypeio.anytype.core_ui.extensions.cursorYBottomCoordinate
import com.anytypeio.anytype.core_ui.extensions.isKeyboardVisible
import com.anytypeio.anytype.core_ui.extensions.tint
import com.anytypeio.anytype.core_ui.features.page.BlockAdapter
import com.anytypeio.anytype.core_ui.features.page.TurnIntoActionReceiver
import com.anytypeio.anytype.core_ui.features.page.scrollandmove.DefaultScrollAndMoveTargetDescriptor
@ -62,7 +60,6 @@ import com.anytypeio.anytype.core_utils.common.EventWrapper
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ext.PopupExtensions.calculateRectInWindow
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.domain.status.SyncStatus
import com.anytypeio.anytype.emojifier.Emojifier
import com.anytypeio.anytype.ext.extractMarks
import com.anytypeio.anytype.presentation.page.PageViewModel
@ -689,18 +686,7 @@ open class PageFragment :
}
private fun bindSyncStatus(status: SyncStatus?) {
when (status) {
SyncStatus.UNKNOWN, SyncStatus.FAILED, SyncStatus.OFFLINE -> topToolbar.status.tint(
color = requireContext().color(R.color.sync_status_red)
)
SyncStatus.SYNCING -> topToolbar.status.tint(
color = requireContext().color(R.color.sync_status_orange)
)
SyncStatus.SYNCED -> topToolbar.status.tint(
color = requireContext().color(R.color.sync_status_green)
)
else -> topToolbar.status.tint(Color.WHITE)
}
topToolbar.status.bind(status)
}
override fun onDestroyView() {

View file

@ -12,6 +12,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.core_ui.extensions.color
import com.anytypeio.anytype.core_ui.extensions.tint
@ -24,7 +25,6 @@ import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ext.withParent
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.domain.status.SyncStatus
import com.anytypeio.anytype.presentation.`object`.ObjectAction
import com.anytypeio.anytype.presentation.`object`.ObjectMenuViewModel
import com.anytypeio.anytype.ui.page.cover.DocCoverSliderFragment

View file

@ -113,7 +113,6 @@ open class ObjectSetFragment :
}
subscribe(objectSetIcon.clicks()) { vm.onIconClicked() }
subscribe(customizeViewButton.clicks()) { vm.onViewerCustomizeButtonClicked() }
subscribe(icBack.clicks()) { vm.onBackButtonPressed() }
subscribe(tvCurrentViewerName.clicks()) { vm.onExpandViewerMenuClicked() }
subscribe(bottomPanel.findViewById<FrameLayout>(R.id.btnFilter).clicks()) {
vm.onViewerFiltersClicked()
@ -198,6 +197,7 @@ open class ObjectSetFragment :
super.onActivityCreated(savedInstanceState)
vm.navigation.observe(viewLifecycleOwner, navObserver)
lifecycleScope.subscribe(vm.toasts.stream()) { toast(it) }
lifecycleScope.subscribe(vm.status) { statusBadge.bind(it) }
lifecycleScope.subscribe(vm.isCustomizeViewPanelVisible) { isCustomizeViewPanelVisible ->
if (isCustomizeViewPanelVisible) showBottomPanel() else hideBottomPanel()
}

View file

@ -19,7 +19,34 @@
android:indeterminate="true"
app:indicatorColor="@color/gray" />
<com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
android:id="@+id/statusBadge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="19dp"
android:layout_marginTop="19dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/threeDotsButton"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="12dp"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_action_more" />
</FrameLayout>
<ImageView
android:visibility="invisible"
android:id="@+id/icBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -1,4 +1,4 @@
package com.anytypeio.anytype.domain.status
package com.anytypeio.anytype.core_models
enum class SyncStatus {
UNKNOWN,

View file

@ -0,0 +1,36 @@
package com.anytypeio.anytype.core_ui.widgets
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.extensions.color
import com.anytypeio.anytype.core_ui.extensions.tint
class StatusBadgeWidget @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
init {
setBackgroundResource(R.drawable.circle_solid_default)
tint(Color.WHITE)
}
fun bind(status: SyncStatus?) {
when (status) {
SyncStatus.UNKNOWN, SyncStatus.FAILED, SyncStatus.OFFLINE -> tint(
color = context.color(R.color.sync_status_red)
)
SyncStatus.SYNCING -> tint(
color = context.color(R.color.sync_status_orange)
)
SyncStatus.SYNCED -> tint(
color = context.color(R.color.sync_status_green)
)
else -> tint(Color.WHITE)
}
}
}

View file

@ -9,11 +9,12 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
import kotlinx.android.synthetic.main.widget_document_top_toolbar.view.*
class DocumentTopToolbar : ConstraintLayout {
val status: View get() = syncStatusBadge
val status: StatusBadgeWidget get() = syncStatusBadge
val back: View get() = toolbarBackButton
val menu: View get() = toolbarMenu
val container: FrameLayout get() = toolbarIconContainer

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:fillColor="#929082"/>
<path
android:pathData="M12,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:fillColor="#929082"/>
<path
android:pathData="M18,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:fillColor="#929082"/>
</vector>

View file

@ -68,14 +68,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
<com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
android:id="@+id/syncStatusBadge"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/circle_solid_default"
android:backgroundTint="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:backgroundTint="@color/anytype_text_green" />

View file

@ -1,6 +1,6 @@
package com.anytypeio.anytype.data.auth.status
import com.anytypeio.anytype.domain.status.SyncStatus
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.domain.status
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.domain.base.FlowUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.domain.status
import com.anytypeio.anytype.core_models.SyncStatus
import kotlinx.coroutines.flow.Flow
interface ThreadStatusChannel {

View file

@ -47,7 +47,6 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
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.domain.status.SyncStatus
import com.anytypeio.anytype.presentation.BuildConfig
import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.common.SupportCommand

View file

@ -2,8 +2,8 @@ package com.anytypeio.anytype.presentation.page.editor
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.SmartBlockType
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.domain.status.SyncStatus
import com.anytypeio.anytype.presentation.page.editor.model.BlockView
sealed class Command {

View file

@ -13,6 +13,7 @@ import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.mapper.toDomain
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.navigation.SupportNavigation
@ -43,12 +44,14 @@ class ObjectSetViewModel(
private val createDataViewRecord: CreateDataViewRecord,
private val updateText: UpdateText,
private val interceptEvents: InterceptEvents,
private val interceptThreadStatus: InterceptThreadStatus,
private val dispatcher: Dispatcher<Payload>,
private val objectSetRecordCache: ObjectSetRecordCache,
private val urlBuilder: UrlBuilder,
private val session: ObjectSetSession
) : ViewModel(), SupportNavigation<EventWrapper<AppNavigation.Command>> {
val status = MutableStateFlow(SyncStatus.UNKNOWN)
private val total = MutableStateFlow(0)
private val offset = MutableStateFlow(0)
@ -145,11 +148,19 @@ class ObjectSetViewModel(
fun onStart(ctx: Id) {
Timber.d("onStart, ctx:[$ctx]")
context = ctx
jobs += viewModelScope.launch {
interceptEvents
.build(InterceptEvents.Params(context))
.collect { events -> reducer.dispatch(events) }
}
jobs += viewModelScope.launch {
interceptThreadStatus
.build(InterceptThreadStatus.Params(ctx))
.collect { status.value = it }
}
viewModelScope.launch {
isLoading.value = true
openObjectSet(ctx).process(

View file

@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.util.Dispatcher
class ObjectSetViewModelFactory(
@ -22,6 +23,7 @@ class ObjectSetViewModelFactory(
private val createDataViewRecord: CreateDataViewRecord,
private val updateText: UpdateText,
private val interceptEvents: InterceptEvents,
private val interceptThreadStatus: InterceptThreadStatus,
private val dispatcher: Dispatcher<Payload>,
private val objectSetRecordCache: ObjectSetRecordCache,
private val urlBuilder: UrlBuilder,
@ -40,6 +42,7 @@ class ObjectSetViewModelFactory(
createDataViewRecord = createDataViewRecord,
updateText = updateText,
interceptEvents = interceptEvents,
interceptThreadStatus = interceptThreadStatus,
dispatcher = dispatcher,
objectSetRecordCache = objectSetRecordCache,
urlBuilder = urlBuilder,

View file

@ -1,9 +1,9 @@
package com.anytypeio.anytype.presentation.page.editor
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.core_models.SyncStatus
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.status.SyncStatus
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory.page
import com.anytypeio.anytype.presentation.MockTypicalDocumentFactory.profile

View file

@ -11,6 +11,7 @@ import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.presentation.sets.ObjectSetRecordCache
import com.anytypeio.anytype.presentation.sets.ObjectSetReducer
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
@ -54,6 +55,9 @@ open class ObjectSetViewModelTestSetup {
@Mock
lateinit var setActiveViewer: SetActiveViewer
@Mock
lateinit var interceptThreadStatus: InterceptThreadStatus
@Mock
lateinit var gateway: Gateway
@ -73,6 +77,7 @@ open class ObjectSetViewModelTestSetup {
updateDataViewViewer = updateDataViewViewer,
updateText = updateText,
interceptEvents = interceptEvents,
interceptThreadStatus = interceptThreadStatus,
createDataViewRecord = createDataViewRecord,
setActiveViewer = setActiveViewer,
dispatcher = dispatcher,