Pinned messages list : hide reactions.

This commit is contained in:
ganfra 2024-09-09 14:49:53 +02:00
parent a284177900
commit 73bbd1e62a
9 changed files with 117 additions and 43 deletions

View file

@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -55,7 +56,7 @@ import kotlin.time.Duration.Companion.milliseconds
class PinnedMessagesListPresenter @AssistedInject constructor( class PinnedMessagesListPresenter @AssistedInject constructor(
@Assisted private val navigator: PinnedMessagesListNavigator, @Assisted private val navigator: PinnedMessagesListNavigator,
private val room: MatrixRoom, private val room: MatrixRoom,
private val timelineItemsFactory: TimelineItemsFactory, timelineItemsFactoryCreator: TimelineItemsFactory.Creator,
private val timelineProvider: PinnedEventsTimelineProvider, private val timelineProvider: PinnedEventsTimelineProvider,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
actionListPresenterFactory: ActionListPresenter.Factory, actionListPresenterFactory: ActionListPresenter.Factory,
@ -65,6 +66,12 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter
} }
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
config = TimelineItemsFactoryConfig(
computeReadReceipts = false,
computeReactions = false,
)
)
private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor()) private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor())
@Composable @Composable

View file

@ -22,6 +22,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.MessagesNavigator
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.NewEventState
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
@ -54,7 +55,7 @@ import kotlinx.coroutines.withContext
const val FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS = 200L const val FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS = 200L
class TimelinePresenter @AssistedInject constructor( class TimelinePresenter @AssistedInject constructor(
private val timelineItemsFactory: TimelineItemsFactory, timelineItemsFactoryCreator: TimelineItemsFactory.Creator,
private val timelineItemIndexer: TimelineItemIndexer, private val timelineItemIndexer: TimelineItemIndexer,
private val room: MatrixRoom, private val room: MatrixRoom,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
@ -71,6 +72,13 @@ class TimelinePresenter @AssistedInject constructor(
fun create(navigator: MessagesNavigator): TimelinePresenter fun create(navigator: MessagesNavigator): TimelinePresenter
} }
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
config = TimelineItemsFactoryConfig(
computeReadReceipts = true,
computeReactions = true,
)
)
@Composable @Composable
override fun present(): TimelineState { override fun present(): TimelineState {
val localScope = rememberCoroutineScope() val localScope = rememberCoroutineScope()

View file

@ -7,6 +7,9 @@
package io.element.android.features.messages.impl.timeline.factories package io.element.android.features.messages.impl.timeline.factories
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.timeline.TimelineItemIndexer import io.element.android.features.messages.impl.timeline.TimelineItemIndexer
import io.element.android.features.messages.impl.timeline.diff.TimelineItemsCacheInvalidator import io.element.android.features.messages.impl.timeline.diff.TimelineItemsCacheInvalidator
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemEventFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemEventFactory
@ -26,15 +29,21 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject
class TimelineItemsFactory @Inject constructor( class TimelineItemsFactory @AssistedInject constructor(
@Assisted config: TimelineItemsFactoryConfig,
eventItemFactoryCreator: TimelineItemEventFactory.Creator,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val eventItemFactory: TimelineItemEventFactory,
private val virtualItemFactory: TimelineItemVirtualFactory, private val virtualItemFactory: TimelineItemVirtualFactory,
private val timelineItemGrouper: TimelineItemGrouper, private val timelineItemGrouper: TimelineItemGrouper,
private val timelineItemIndexer: TimelineItemIndexer, private val timelineItemIndexer: TimelineItemIndexer,
) { ) {
@AssistedFactory
interface Creator {
fun create(config: TimelineItemsFactoryConfig): TimelineItemsFactory
}
private val eventItemFactory = eventItemFactoryCreator.create(config)
private val _timelineItems = MutableSharedFlow<ImmutableList<TimelineItem>>(replay = 1) private val _timelineItems = MutableSharedFlow<ImmutableList<TimelineItem>>(replay = 1)
private val lock = Mutex() private val lock = Mutex()
private val diffCache = MutableListDiffCache<TimelineItem>() private val diffCache = MutableListDiffCache<TimelineItem>()

View file

@ -0,0 +1,18 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.timeline.factories
/**
* Some data used to configure the creation of timeline items.
* @param computeReadReceipts when false, read receipts will be empty.
* @param computeReactions when false, reactions will be empty.
*/
data class TimelineItemsFactoryConfig(
val computeReadReceipts: Boolean,
val computeReactions: Boolean,
)

View file

@ -7,6 +7,10 @@
package io.element.android.features.messages.impl.timeline.factories.event package io.element.android.features.messages.impl.timeline.factories.event
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
import io.element.android.features.messages.impl.timeline.groups.canBeDisplayedInBubbleBlock import io.element.android.features.messages.impl.timeline.groups.canBeDisplayedInBubbleBlock
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionSender import io.element.android.features.messages.impl.timeline.model.AggregatedReactionSender
@ -26,17 +30,23 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.messages.reply.map
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import java.text.DateFormat import java.text.DateFormat
import java.util.Date import java.util.Date
import javax.inject.Inject
class TimelineItemEventFactory @Inject constructor( class TimelineItemEventFactory @AssistedInject constructor(
@Assisted private val config: TimelineItemsFactoryConfig,
private val contentFactory: TimelineItemContentFactory, private val contentFactory: TimelineItemContentFactory,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val permalinkParser: PermalinkParser, private val permalinkParser: PermalinkParser,
) { ) {
@AssistedFactory
interface Creator {
fun create(config: TimelineItemsFactoryConfig): TimelineItemEventFactory
}
suspend fun create( suspend fun create(
currentTimelineItem: MatrixTimelineItem.Event, currentTimelineItem: MatrixTimelineItem.Event,
index: Int, index: Int,
@ -92,8 +102,11 @@ class TimelineItemEventFactory @Inject constructor(
} }
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions { private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
if (!config.computeReactions) {
return TimelineItemReactions(reactions = persistentListOf())
}
val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
var aggregatedReactions = event.reactions.map { reaction -> var aggregatedReactions = this.event.reactions.map { reaction ->
// Sort reactions within an aggregation by timestamp descending. // Sort reactions within an aggregation by timestamp descending.
// This puts the most recent at the top, useful in cases like the // This puts the most recent at the top, useful in cases like the
// reaction summary view or getting the most recent reaction. // reaction summary view or getting the most recent reaction.
@ -129,6 +142,9 @@ class TimelineItemEventFactory @Inject constructor(
private fun MatrixTimelineItem.Event.computeReadReceiptState( private fun MatrixTimelineItem.Event.computeReadReceiptState(
roomMembers: List<RoomMember>, roomMembers: List<RoomMember>,
): TimelineItemReadReceipts { ): TimelineItemReadReceipts {
if (!config.computeReadReceipts) {
return TimelineItemReadReceipts(receipts = persistentListOf())
}
return TimelineItemReadReceipts( return TimelineItemReadReceipts(
receipts = event.receipts receipts = event.receipts
.map { receipt -> .map { receipt ->

View file

@ -18,7 +18,7 @@ import io.element.android.features.messages.impl.actionlist.FakeActionListPresen
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.draft.FakeComposerDraftService import io.element.android.features.messages.impl.draft.FakeComposerDraftService
import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aMessageEvent
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
@ -1024,7 +1024,7 @@ class MessagesPresenterTest {
permissionsPresenterFactory, permissionsPresenterFactory,
) )
val timelinePresenter = TimelinePresenter( val timelinePresenter = TimelinePresenter(
timelineItemsFactory = aTimelineItemsFactory(), timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
room = matrixRoom, room = matrixRoom,
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
appScope = this, appScope = this,

View file

@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.fixtures
import io.element.android.features.messages.impl.timeline.TimelineItemIndexer import io.element.android.features.messages.impl.timeline.TimelineItemIndexer
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFactory
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseStateFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseStateFactory
@ -39,40 +40,56 @@ import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorW
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
internal fun TestScope.aTimelineItemsFactoryCreator(
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
): TimelineItemsFactory.Creator {
return object : TimelineItemsFactory.Creator {
override fun create(config: TimelineItemsFactoryConfig): TimelineItemsFactory {
return aTimelineItemsFactory(config, timelineItemIndexer)
}
}
}
internal fun TestScope.aTimelineItemsFactory( internal fun TestScope.aTimelineItemsFactory(
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer() config: TimelineItemsFactoryConfig,
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
): TimelineItemsFactory { ): TimelineItemsFactory {
val timelineEventFormatter = aTimelineEventFormatter() val timelineEventFormatter = aTimelineEventFormatter()
val matrixClient = FakeMatrixClient() val matrixClient = FakeMatrixClient()
return TimelineItemsFactory( return TimelineItemsFactory(
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
eventItemFactory = TimelineItemEventFactory( eventItemFactoryCreator = object : TimelineItemEventFactory.Creator {
contentFactory = TimelineItemContentFactory( override fun create(config: TimelineItemsFactoryConfig): TimelineItemEventFactory {
messageFactory = TimelineItemContentMessageFactory( return TimelineItemEventFactory(
fileSizeFormatter = FakeFileSizeFormatter(), contentFactory = TimelineItemContentFactory(
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), messageFactory = TimelineItemContentMessageFactory(
featureFlagService = FakeFeatureFlagService(), fileSizeFormatter = FakeFileSizeFormatter(),
htmlConverterProvider = FakeHtmlConverterProvider(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
featureFlagService = FakeFeatureFlagService(),
htmlConverterProvider = FakeHtmlConverterProvider(),
permalinkParser = FakePermalinkParser(),
textPillificationHelper = FakeTextPillificationHelper(),
),
redactedMessageFactory = TimelineItemContentRedactedFactory(),
stickerFactory = TimelineItemContentStickerFactory(
fileSizeFormatter = FakeFileSizeFormatter(),
fileExtensionExtractor = FileExtensionExtractorWithoutValidation()
),
pollFactory = TimelineItemContentPollFactory(FakeFeatureFlagService(), FakePollContentStateFactory()),
utdFactory = TimelineItemContentUTDFactory(),
roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),
stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
),
matrixClient = matrixClient,
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(),
permalinkParser = FakePermalinkParser(), permalinkParser = FakePermalinkParser(),
textPillificationHelper = FakeTextPillificationHelper(), config = config
), )
redactedMessageFactory = TimelineItemContentRedactedFactory(), }
stickerFactory = TimelineItemContentStickerFactory( },
fileSizeFormatter = FakeFileSizeFormatter(),
fileExtensionExtractor = FileExtensionExtractorWithoutValidation()
),
pollFactory = TimelineItemContentPollFactory(FakeFeatureFlagService(), FakePollContentStateFactory()),
utdFactory = TimelineItemContentUTDFactory(),
roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),
stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
),
matrixClient = matrixClient,
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(),
permalinkParser = FakePermalinkParser(),
),
virtualItemFactory = TimelineItemVirtualFactory( virtualItemFactory = TimelineItemVirtualFactory(
daySeparatorFactory = TimelineItemDaySeparatorFactory( daySeparatorFactory = TimelineItemDaySeparatorFactory(
FakeDaySeparatorFormatter() FakeDaySeparatorFormatter()
@ -80,6 +97,7 @@ internal fun TestScope.aTimelineItemsFactory(
), ),
timelineItemGrouper = TimelineItemGrouper(), timelineItemGrouper = TimelineItemGrouper(),
timelineItemIndexer = timelineItemIndexer, timelineItemIndexer = timelineItemIndexer,
config = config
) )
} }

View file

@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.pinned.list
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkMonitor
@ -318,7 +318,7 @@ class PinnedMessagesListPresenterTest {
return PinnedMessagesListPresenter( return PinnedMessagesListPresenter(
navigator = navigator, navigator = navigator,
room = room, room = room,
timelineItemsFactory = aTimelineItemsFactory(), timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
timelineProvider = timelineProvider, timelineProvider = timelineProvider,
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
actionListPresenterFactory = FakeActionListPresenter.Factory, actionListPresenterFactory = FakeActionListPresenter.Factory,

View file

@ -14,9 +14,8 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.FakeMessagesNavigator import io.element.android.features.messages.impl.FakeMessagesNavigator
import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aMessageEvent
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
import io.element.android.features.messages.impl.timeline.components.aCriticalShield import io.element.android.features.messages.impl.timeline.components.aCriticalShield
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.NewEventState
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
@ -662,7 +661,6 @@ import kotlin.time.Duration.Companion.seconds
liveTimeline = timeline, liveTimeline = timeline,
canUserSendMessageResult = { _, _ -> Result.success(true) } canUserSendMessageResult = { _, _ -> Result.success(true) }
), ),
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(),
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(), messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
endPollAction: EndPollAction = FakeEndPollAction(), endPollAction: EndPollAction = FakeEndPollAction(),
@ -671,7 +669,7 @@ import kotlin.time.Duration.Companion.seconds
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
): TimelinePresenter { ): TimelinePresenter {
return TimelinePresenter( return TimelinePresenter(
timelineItemsFactory = timelineItemsFactory, timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
room = room, room = room,
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
appScope = this, appScope = this,