Start migrating Anvil KSP to Metro

This commit is contained in:
Jorge Martín 2025-08-20 15:29:50 +02:00
parent d4d57b1e21
commit b76a71ebf5
703 changed files with 3523 additions and 2820 deletions

View file

@ -10,15 +10,16 @@ package io.element.android.libraries.mediaviewer.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import dev.zacsweers.metro.AppScope
import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint
import io.element.android.libraries.mediaviewer.impl.gallery.root.MediaGalleryRootNode
import javax.inject.Inject
import dev.zacsweers.metro.Inject
@ContributesBinding(AppScope::class)
class DefaultMediaGalleryEntryPoint @Inject constructor() : MediaGalleryEntryPoint {
@Inject
class DefaultMediaGalleryEntryPoint() : MediaGalleryEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MediaGalleryEntryPoint.NodeBuilder {
val plugins = ArrayList<Plugin>()

View file

@ -10,19 +10,20 @@ package io.element.android.libraries.mediaviewer.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.AppScope
import dev.zacsweers.metro.AppScope
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
import io.element.android.libraries.mediaviewer.impl.viewer.MediaViewerNode
import javax.inject.Inject
import dev.zacsweers.metro.Inject
@ContributesBinding(AppScope::class)
class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint {
@Inject
class DefaultMediaViewerEntryPoint() : MediaViewerEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MediaViewerEntryPoint.NodeBuilder {
val plugins = ArrayList<Plugin>()

View file

@ -42,9 +42,10 @@ import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import timber.log.Timber
import javax.inject.Inject
import dev.zacsweers.metro.Inject
class EventItemFactory @Inject constructor(
@Inject
class EventItemFactory(
private val fileSizeFormatter: FileSizeFormatter,
private val fileExtensionExtractor: FileExtensionExtractor,
private val dateFormatter: DateFormatter,

View file

@ -7,12 +7,12 @@
package io.element.android.libraries.mediaviewer.impl.datasource
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import javax.inject.Inject
import dev.zacsweers.metro.Inject
interface FocusedTimelineMediaGalleryDataSourceFactory {
fun createFor(
@ -23,7 +23,8 @@ interface FocusedTimelineMediaGalleryDataSourceFactory {
}
@ContributesBinding(RoomScope::class)
class DefaultFocusedTimelineMediaGalleryDataSourceFactory @Inject constructor(
@Inject
class DefaultFocusedTimelineMediaGalleryDataSourceFactory(
private val room: JoinedRoom,
private val timelineMediaItemsFactory: TimelineMediaItemsFactory,
private val mediaItemsPostProcessor: MediaItemsPostProcessor,

View file

@ -7,10 +7,10 @@
package io.element.android.libraries.mediaviewer.impl.datasource
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn
import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import dev.zacsweers.metro.Inject
interface MediaGalleryDataSource {
fun start()
@ -39,7 +39,8 @@ interface MediaGalleryDataSource {
@SingleIn(RoomScope::class)
@ContributesBinding(RoomScope::class)
class TimelineMediaGalleryDataSource @Inject constructor(
@Inject
class TimelineMediaGalleryDataSource(
private val room: BaseRoom,
private val mediaTimeline: MediaTimeline,
private val timelineMediaItemsFactory: TimelineMediaItemsFactory,

View file

@ -10,9 +10,10 @@ package io.element.android.libraries.mediaviewer.impl.datasource
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
import dev.zacsweers.metro.Inject
class MediaItemsPostProcessor @Inject constructor() {
@Inject
class MediaItemsPostProcessor() {
fun process(
mediaItems: List<MediaItem>,
): GroupedMediaItems {

View file

@ -7,9 +7,9 @@
package io.element.android.libraries.mediaviewer.impl.datasource
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn
import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
@ -21,7 +21,7 @@ import io.element.android.libraries.mediaviewer.impl.model.hasEvent
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import javax.inject.Inject
import dev.zacsweers.metro.Inject
interface MediaTimeline {
suspend fun getTimeline(): Result<Timeline>
@ -36,7 +36,8 @@ interface MediaTimeline {
*/
@SingleIn(RoomScope::class)
@ContributesBinding(RoomScope::class)
class LiveMediaTimeline @Inject constructor(
@Inject
class LiveMediaTimeline(
private val room: JoinedRoom,
) : MediaTimeline {
private var timeline: Timeline? = null

View file

@ -21,9 +21,10 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import javax.inject.Inject
import dev.zacsweers.metro.Inject
class TimelineMediaItemsFactory @Inject constructor(
@Inject
class TimelineMediaItemsFactory(
private val dispatchers: CoroutineDispatchers,
private val virtualItemFactory: VirtualItemFactory,
private val eventItemFactory: EventItemFactory,

View file

@ -12,9 +12,10 @@ import io.element.android.libraries.dateformatter.api.DateFormatterMode
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import javax.inject.Inject
import dev.zacsweers.metro.Inject
class VirtualItemFactory @Inject constructor(
@Inject
class VirtualItemFactory(
private val dateFormatter: DateFormatter,
) {
fun create(timelineItem: MatrixTimelineItem.Virtual): MediaItem? {

View file

@ -14,8 +14,8 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.Inject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
@ -24,7 +24,8 @@ import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresent
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
@ContributesNode(RoomScope::class)
class MediaGalleryNode @AssistedInject constructor(
@Inject
class MediaGalleryNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
presenterFactory: MediaGalleryPresenter.Factory,

View file

@ -16,9 +16,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.Inject
import io.element.android.libraries.androidutils.R
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
@ -44,7 +44,8 @@ import io.element.android.libraries.mediaviewer.impl.model.mediaSource
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch
class MediaGalleryPresenter @AssistedInject constructor(
@Inject
class MediaGalleryPresenter(
@Assisted private val navigator: MediaGalleryNavigator,
private val room: BaseRoom,
private val mediaGalleryDataSource: MediaGalleryDataSource,

View file

@ -18,7 +18,7 @@ import io.element.android.libraries.voiceplayer.api.aVoiceMessageState
fun aFakeMediaItemPresenterFactories() = MediaItemPresenterFactories(
mapOf(
Pair(
MediaItem.Voice::class.java,
MediaItem.Voice::class,
MediaItemPresenterFactory<MediaItem.Voice, VoiceMessageState> { Presenter { aVoiceMessageState() } },
),
)

View file

@ -7,7 +7,7 @@
package io.element.android.libraries.mediaviewer.impl.gallery.di
import dagger.MapKey
import dev.zacsweers.metro.MapKey
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import kotlin.reflect.KClass

View file

@ -9,25 +9,26 @@ package io.element.android.libraries.mediaviewer.impl.gallery.di
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.multibindings.Multibinds
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.Multibinds
import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import javax.inject.Inject
import kotlin.reflect.KClass
/**
* Dagger module that declares the [MediaItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@Module
@BindingContainer
@ContributesTo(RoomScope::class)
interface MediaItemPresenterFactoriesModule {
@Multibinds
fun multiBindMediaItemPresenterFactories(): @JvmSuppressWildcards Map<Class<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>
fun multiBindMediaItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>
}
/**
@ -38,8 +39,9 @@ interface MediaItemPresenterFactoriesModule {
* goes out of the [LazyColumn] viewport.
*/
@SingleIn(RoomScope::class)
class MediaItemPresenterFactories @Inject constructor(
private val factories: @JvmSuppressWildcards Map<Class<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>,
@Inject
class MediaItemPresenterFactories(
private val factories: @JvmSuppressWildcards Map<KClass<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>,
) {
private val presenters: MutableMap<MediaItem.Event, Presenter<*>> = mutableMapOf()
@ -57,7 +59,7 @@ class MediaItemPresenterFactories @Inject constructor(
@Composable
fun <C : MediaItem.Event, S : Any> rememberPresenter(
content: C,
contentClass: Class<C>,
contentClass: KClass<C>,
): Presenter<S> = remember(content) {
presenters[content]?.let {
@Suppress("UNCHECKED_CAST")
@ -86,5 +88,5 @@ inline fun <reified C : MediaItem.Event, S : Any> MediaItemPresenterFactories.re
content: C
): Presenter<S> = rememberPresenter(
content = content,
contentClass = C::class.java
contentClass = C::class
)

View file

@ -15,8 +15,8 @@ import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.Inject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.architecture.BackstackWithOverlayBox
import io.element.android.libraries.architecture.BaseFlowNode
@ -40,7 +40,8 @@ import io.element.android.libraries.mediaviewer.impl.model.thumbnailSource
import kotlinx.parcelize.Parcelize
@ContributesNode(RoomScope::class)
class MediaGalleryRootNode @AssistedInject constructor(
@Inject
class MediaGalleryRootNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val mediaViewerEntryPoint: MediaViewerEntryPoint

View file

@ -8,13 +8,13 @@
package io.element.android.libraries.mediaviewer.impl.gallery.voice
import androidx.compose.runtime.Composable
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.multibindings.IntoMap
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.IntoMap
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemEventContentKey
@ -24,7 +24,7 @@ import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import kotlin.time.Duration
@Module
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterModule {
@Binds
@ -33,7 +33,8 @@ interface VoiceMessagePresenterModule {
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): MediaItemPresenterFactory<*, *>
}
class VoiceMessagePresenter @AssistedInject constructor(
@Inject
class VoiceMessagePresenter(
voiceMessagePresenterFactory: VoiceMessagePresenterFactory,
@Assisted private val item: MediaItem.Voice,
) : Presenter<VoiceMessageState> {

View file

@ -29,14 +29,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.core.content.FileProvider
import androidx.core.content.PermissionChecker
import androidx.core.net.toFile
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import dev.zacsweers.metro.AppScope
import io.element.android.libraries.di.annotations.ApplicationContext
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -44,10 +44,11 @@ import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import javax.inject.Inject
import dev.zacsweers.metro.Inject
@ContributesBinding(AppScope::class)
class AndroidLocalMediaActions @Inject constructor(
@Inject
class AndroidLocalMediaActions(
@ApplicationContext private val context: Context,
private val coroutineDispatchers: CoroutineDispatchers,
private val buildMeta: BuildMeta,

View file

@ -10,14 +10,14 @@ package io.element.android.libraries.mediaviewer.impl.local
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.file.getFileName
import io.element.android.libraries.androidutils.file.getFileSize
import io.element.android.libraries.androidutils.file.getMimeType
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import dev.zacsweers.metro.AppScope
import io.element.android.libraries.di.annotations.ApplicationContext
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaFile
import io.element.android.libraries.matrix.api.media.toFile
@ -25,10 +25,11 @@ import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor
import javax.inject.Inject
import dev.zacsweers.metro.Inject
@ContributesBinding(AppScope::class)
class AndroidLocalMediaFactory @Inject constructor(
@Inject
class AndroidLocalMediaFactory(
@ApplicationContext private val context: Context,
private val fileSizeFormatter: FileSizeFormatter,
private val fileExtensionExtractor: FileExtensionExtractor,

View file

@ -10,19 +10,20 @@ package io.element.android.libraries.mediaviewer.impl.local
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.squareup.anvil.annotations.ContributesBinding
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.viewfolder.api.TextFileViewer
import io.element.android.libraries.audio.api.AudioFocus
import io.element.android.libraries.di.AppScope
import dev.zacsweers.metro.AppScope
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.api.local.LocalMediaRenderer
import me.saket.telephoto.zoomable.OverzoomEffect
import me.saket.telephoto.zoomable.ZoomSpec
import me.saket.telephoto.zoomable.rememberZoomableState
import javax.inject.Inject
import dev.zacsweers.metro.Inject
@ContributesBinding(AppScope::class)
class DefaultLocalMediaRenderer @Inject constructor(
@Inject
class DefaultLocalMediaRenderer(
private val textFileViewer: TextFileViewer,
private val audioFocus: AudioFocus,
) : LocalMediaRenderer {

View file

@ -8,13 +8,14 @@
package io.element.android.libraries.mediaviewer.impl.util
import android.webkit.MimeTypeMap
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.AppScope
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor
import javax.inject.Inject
import dev.zacsweers.metro.Inject
@ContributesBinding(AppScope::class)
class FileExtensionExtractorWithValidation @Inject constructor() : FileExtensionExtractor {
@Inject
class FileExtensionExtractorWithValidation() : FileExtensionExtractor {
override fun extractFromName(name: String): String {
val fileExtension = name.substringAfterLast('.', "")
// Makes sure the extension is known by the system, otherwise default to binary extension.

View file

@ -13,8 +13,8 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.Inject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.compound.theme.ForcedDarkElementTheme
import io.element.android.features.viewfolder.api.TextFileViewer
@ -33,7 +33,8 @@ import io.element.android.libraries.mediaviewer.impl.model.hasEvent
import io.element.android.services.toolbox.api.systemclock.SystemClock
@ContributesNode(RoomScope::class)
class MediaViewerNode @AssistedInject constructor(
@Inject
class MediaViewerNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
presenterFactory: MediaViewerPresenter.Factory,

View file

@ -21,9 +21,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
@ -49,7 +49,8 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import io.element.android.libraries.androidutils.R as UtilsR
class MediaViewerPresenter @AssistedInject constructor(
@Inject
class MediaViewerPresenter(
@Assisted private val inputs: MediaViewerEntryPoint.Params,
@Assisted private val navigator: MediaViewerNavigator,
@Assisted private val dataSource: MediaViewerDataSource,

View file

@ -9,7 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.viewer
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.eventId
import javax.inject.Inject
import dev.zacsweers.metro.Inject
/**
* x and y are loading items.
@ -35,7 +35,8 @@ import javax.inject.Inject
* -1 0 1 2 3 4 5 6
* (keyOffset = -1)
*/
class PagerKeysHandler @Inject constructor() {
@Inject
class PagerKeysHandler() {
private data class Data(
val mediaItems: List<MediaItem>,
val keyOffset: Long,