Move all binding containers to a di package and normalize their method names

Every @BindingContainer now lives in a di subpackage, in a file named after
it. Containers that were declared inside a shared file (SimplePlayer.kt,
LockScreenConfig.kt, VoiceMessagePresenter.kt, TimelineItemPresenterFactories.kt,
MediaItemPresenterFactories.kt) are extracted into their own file.

Method names are made consistent too:
- @Binds functions are named bind<ConcreteParameterType>, fixing three that
  named something else (notably RoomAliasResolverBindingContainer, whose
  provider was still called providesJoinRoomPresenterFactory).
- @Provides functions are named after what they provide, and all use the
  provides prefix rather than a mix of provide and provides.

ANDROID_KEYSTORE moves back to the cryptography impl package, next to
KeyStoreSecretKeyRepository which uses it; it is not DI specific and would
otherwise be unreachable from there.

No behaviour change: Metro discovers containers via @ContributesTo and does
not care about method names, so the only call site to follow was in
DefaultVoiceRecorderTest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-08-24 11:38:11 +02:00
parent a845763f5b
commit e6116d5840
38 changed files with 204 additions and 131 deletions
@@ -111,7 +111,7 @@ object AppBindingContainer {
@Provides @Provides
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
fun provideSnackbarDispatcher(): SnackbarDispatcher { fun providesSnackbarDispatcher(): SnackbarDispatcher {
return SnackbarDispatcher() return SnackbarDispatcher()
} }
} }
@@ -20,5 +20,5 @@ import io.element.android.libraries.di.SessionScope
@BindingContainer @BindingContainer
interface FtueBindingContainer { interface FtueBindingContainer {
@Binds @Binds
fun bindChooseSelfVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState> fun bindChooseSelfVerificationModePresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState>
} }
@@ -31,10 +31,10 @@ interface RoomListBindingContainer {
fun bindRoomListPresenter(presenter: RoomListPresenter): Presenter<RoomListState> fun bindRoomListPresenter(presenter: RoomListPresenter): Presenter<RoomListState>
@Binds @Binds
fun bindSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState> fun bindRoomListSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
@Binds @Binds
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState> fun bindRoomListFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
@Binds @Binds
fun bindSpaceFiltersPresenter(presenter: SpaceFiltersPresenter): Presenter<SpaceFiltersState> fun bindSpaceFiltersPresenter(presenter: SpaceFiltersPresenter): Presenter<SpaceFiltersState>
@@ -24,7 +24,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
@BindingContainer @BindingContainer
interface InviteBindingContainer { interface InviteBindingContainer {
@Binds @Binds
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState> fun bindAcceptDeclineInvitePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
companion object { companion object {
@Provides @Provides
@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.features.knockrequests.impl.data package io.element.android.features.knockrequests.impl.data.di
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.ContributesTo
@@ -14,6 +14,7 @@ import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.SingleIn
import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.features.knockrequests.api.KnockRequestPermissions
import io.element.android.features.knockrequests.api.knockRequestPermissions import io.element.android.features.knockrequests.api.knockRequestPermissions
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow
@@ -23,7 +24,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow
object KnockRequestsBindingContainer { object KnockRequestsBindingContainer {
@Provides @Provides
@SingleIn(RoomScope::class) @SingleIn(RoomScope::class)
fun knockRequestsService(room: JoinedRoom): KnockRequestsService { fun providesKnockRequestsService(room: JoinedRoom): KnockRequestsService {
return KnockRequestsService( return KnockRequestsService(
knockRequestsFlow = room.knockRequestsFlow, knockRequestsFlow = room.knockRequestsFlow,
permissionsFlow = room.permissionsFlow(KnockRequestPermissions.DEFAULT) { perms -> permissionsFlow = room.permissionsFlow(KnockRequestPermissions.DEFAULT) { perms ->
@@ -8,12 +8,7 @@
package io.element.android.features.lockscreen.impl package io.element.android.features.lockscreen.impl
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import kotlin.time.Duration import kotlin.time.Duration
import io.element.android.appconfig.LockScreenConfig as AppConfigLockScreenConfig
data class LockScreenConfig( data class LockScreenConfig(
val isPinMandatory: Boolean, val isPinMandatory: Boolean,
@@ -24,18 +19,3 @@ data class LockScreenConfig(
val isStrongBiometricsEnabled: Boolean, val isStrongBiometricsEnabled: Boolean,
val isWeakBiometricsEnabled: Boolean, val isWeakBiometricsEnabled: Boolean,
) )
@ContributesTo(AppScope::class)
@BindingContainer
object LockScreenConfigBindingContainer {
@Provides
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
isPinMandatory = AppConfigLockScreenConfig.IS_PIN_MANDATORY,
forbiddenPinCodes = AppConfigLockScreenConfig.FORBIDDEN_PIN_CODES,
pinSize = AppConfigLockScreenConfig.PIN_SIZE,
maxPinCodeAttemptsBeforeLogout = AppConfigLockScreenConfig.MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT,
gracePeriod = AppConfigLockScreenConfig.GRACE_PERIOD,
isStrongBiometricsEnabled = AppConfigLockScreenConfig.IS_STRONG_BIOMETRICS_ENABLED,
isWeakBiometricsEnabled = AppConfigLockScreenConfig.IS_WEAK_BIOMETRICS_ENABLED,
)
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.lockscreen.impl.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import io.element.android.features.lockscreen.impl.LockScreenConfig
@ContributesTo(AppScope::class)
@BindingContainer
object LockScreenConfigBindingContainer {
@Provides
fun providesLockScreenConfig(): LockScreenConfig = LockScreenConfig(
isPinMandatory = io.element.android.appconfig.LockScreenConfig.IS_PIN_MANDATORY,
forbiddenPinCodes = io.element.android.appconfig.LockScreenConfig.FORBIDDEN_PIN_CODES,
pinSize = io.element.android.appconfig.LockScreenConfig.PIN_SIZE,
maxPinCodeAttemptsBeforeLogout = io.element.android.appconfig.LockScreenConfig.MAX_PIN_CODE_ATTEMPTS_BEFORE_LOGOUT,
gracePeriod = io.element.android.appconfig.LockScreenConfig.GRACE_PERIOD,
isStrongBiometricsEnabled = io.element.android.appconfig.LockScreenConfig.IS_STRONG_BIOMETRICS_ENABLED,
isWeakBiometricsEnabled = io.element.android.appconfig.LockScreenConfig.IS_WEAK_BIOMETRICS_ENABLED,
)
}
@@ -21,5 +21,5 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
object MessagesProvidesBindingContainer { object MessagesProvidesBindingContainer {
@Provides @Provides
@LiveTimeline @LiveTimeline
fun provideLiveTimeline(joinedRoom: JoinedRoom): Timeline = joinedRoom.liveTimeline fun providesLiveTimeline(joinedRoom: JoinedRoom): Timeline = joinedRoom.liveTimeline
} }
@@ -10,28 +10,13 @@ package io.element.android.features.messages.impl.timeline.di
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.Multibinds
import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.SingleIn
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* Container that declares the [TimelineItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@BindingContainer
@ContributesTo(RoomScope::class)
interface TimelineItemPresenterFactoriesBindingContainer {
@Multibinds
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
}
/** /**
* Room level caching layer for the [TimelineItemPresenterFactory] instances. * Room level caching layer for the [TimelineItemPresenterFactory] instances.
* *
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.timeline.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Multibinds
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.libraries.di.RoomScope
import kotlin.reflect.KClass
/**
* Container that declares the [TimelineItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@BindingContainer
@ContributesTo(RoomScope::class)
interface TimelineItemPresenterFactoriesBindingContainer {
@Multibinds
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
}
@@ -12,27 +12,12 @@ import androidx.compose.runtime.Composable
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntoMap
import io.element.android.features.messages.impl.timeline.di.TimelineItemEventContentKey
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState import io.element.android.libraries.voiceplayer.api.VoiceMessageState
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterBindingContainer {
@Binds
@IntoMap
@TimelineItemEventContentKey(TimelineItemVoiceContent::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): TimelineItemPresenterFactory<*, *>
}
@AssistedInject @AssistedInject
class VoiceMessagePresenter( class VoiceMessagePresenter(
voiceMessagePresenterFactory: VoiceMessagePresenterFactory, voiceMessagePresenterFactory: VoiceMessagePresenterFactory,
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntoMap
import io.element.android.features.messages.impl.timeline.di.TimelineItemEventContentKey
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessagePresenter
import io.element.android.libraries.di.RoomScope
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterBindingContainer {
@Binds
@IntoMap
@TimelineItemEventContentKey(TimelineItemVoiceContent::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): TimelineItemPresenterFactory<*, *>
}
@@ -5,11 +5,13 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.features.preferences.impl.userstatus package io.element.android.features.preferences.impl.userstatus.di
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.ContributesTo
import io.element.android.features.preferences.impl.userstatus.UserStatusPresenter
import io.element.android.features.preferences.impl.userstatus.UserStatusState
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
@ContributesTo(SessionScope::class) @ContributesTo(SessionScope::class)
object RoomAliasResolverBindingContainer { object RoomAliasResolverBindingContainer {
@Provides @Provides
fun providesJoinRoomPresenterFactory( fun providesRoomAliasResolverPresenterFactory(
client: MatrixClient, client: MatrixClient,
): RoomAliasResolverPresenter.Factory { ): RoomAliasResolverPresenter.Factory {
return object : RoomAliasResolverPresenter.Factory { return object : RoomAliasResolverPresenter.Factory {
@@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
@ContributesTo(RoomScope::class) @ContributesTo(RoomScope::class)
object RoomMemberBindingContainer { object RoomMemberBindingContainer {
@Provides @Provides
fun provideRoomMemberDetailsPresenterFactory( fun providesRoomMemberDetailsPresenterFactory(
room: JoinedRoom, room: JoinedRoom,
userProfilePresenterFactory: UserProfilePresenterFactory, userProfilePresenterFactory: UserProfilePresenterFactory,
encryptionService: EncryptionService, encryptionService: EncryptionService,
@@ -25,7 +25,7 @@ import timber.log.Timber
object CacheStoreBindingContainer { object CacheStoreBindingContainer {
@Provides @Provides
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
fun provideCacheDatabase( fun providesCacheDatabase(
@ApplicationContext context: Context, @ApplicationContext context: Context,
): CacheDatabase { ): CacheDatabase {
val name = "cache_database" val name = "cache_database"
@@ -26,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap
import javax.crypto.KeyGenerator import javax.crypto.KeyGenerator
import javax.crypto.SecretKey import javax.crypto.SecretKey
internal const val ANDROID_KEYSTORE = "AndroidKeyStore"
/** /**
* Default implementation of [SecretKeyRepository] that uses the Android Keystore to store the keys. * Default implementation of [SecretKeyRepository] that uses the Android Keystore to store the keys.
* The generated key uses AES algorithm, with a key size of 128 bits, and the GCM block mode. * The generated key uses AES algorithm, with a key size of 128 bits, and the GCM block mode.
@@ -6,16 +6,15 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.cryptography.impl package io.element.android.libraries.cryptography.impl.di
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides import dev.zacsweers.metro.Provides
import io.element.android.libraries.cryptography.impl.ANDROID_KEYSTORE
import java.security.KeyStore import java.security.KeyStore
internal const val ANDROID_KEYSTORE = "AndroidKeyStore"
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
@BindingContainer @BindingContainer
object CryptographyBindingContainer { object CryptographyBindingContainer {
@@ -27,5 +27,5 @@ object DateFormatterBindingContainer {
fun providesLocale(): Locale = Locale.getDefault() fun providesLocale(): Locale = Locale.getDefault()
@Provides @Provides
fun providesTimezone(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() } fun providesTimezoneProvider(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() }
} }
@@ -56,7 +56,7 @@ object SessionMatrixBindingContainer {
} }
@Provides @Provides
fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver { fun providesRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver {
return matrixClient.roomMembershipObserver return matrixClient.roomMembershipObserver
} }
@@ -112,7 +112,7 @@ object SessionMatrixBindingContainer {
} }
@Provides @Provides
fun providesGetUrlResolverClient(matrixClient: MatrixClient): ClientUrlContentFetcher { fun providesClientUrlContentFetcher(matrixClient: MatrixClient): ClientUrlContentFetcher {
return matrixClient return matrixClient
} }
@@ -8,16 +8,8 @@
package io.element.android.libraries.mediaplayer.impl package io.element.android.libraries.mediaplayer.impl
import android.content.Context
import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.annotations.ApplicationContext
/** /**
* A subset of media3 [Player] that only exposes the few methods we need making it easier to mock. * A subset of media3 [Player] that only exposes the few methods we need making it easier to mock.
@@ -44,15 +36,6 @@ interface SimplePlayer {
} }
} }
@ContributesTo(RoomScope::class)
@BindingContainer
object SimplePlayerBindingContainer {
@Provides
fun simplePlayerProvider(
@ApplicationContext context: Context,
): SimplePlayer = DefaultSimplePlayer(ExoPlayer.Builder(context).setWakeMode(C.WAKE_MODE_LOCAL).build())
}
/** /**
* Default implementation of [SimplePlayer] backed by a media3 [Player]. * Default implementation of [SimplePlayer] backed by a media3 [Player].
*/ */
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.mediaplayer.impl.di
import android.content.Context
import androidx.media3.common.C
import androidx.media3.exoplayer.ExoPlayer
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.annotations.ApplicationContext
import io.element.android.libraries.mediaplayer.impl.DefaultSimplePlayer
import io.element.android.libraries.mediaplayer.impl.SimplePlayer
@ContributesTo(RoomScope::class)
@BindingContainer
object SimplePlayerBindingContainer {
@Provides
fun providesSimplePlayer(
@ApplicationContext context: Context,
): SimplePlayer = DefaultSimplePlayer(ExoPlayer.Builder(context).setWakeMode(C.WAKE_MODE_LOCAL).build())
}
@@ -10,28 +10,13 @@ package io.element.android.libraries.mediaviewer.impl.gallery.di
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.Multibinds
import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.mediaviewer.impl.model.MediaItem import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* Container that declares the [MediaItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@BindingContainer
@ContributesTo(RoomScope::class)
interface MediaItemPresenterFactoriesBindingContainer {
@Multibinds
fun multiBindMediaItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>
}
/** /**
* Room level caching layer for the [MediaItemPresenterFactory] instances. * Room level caching layer for the [MediaItemPresenterFactory] instances.
* *
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.mediaviewer.impl.gallery.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Multibinds
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import kotlin.reflect.KClass
/**
* Container that declares the [MediaItemPresenterFactory] map multi binding.
*
* Its sole purpose is to support the case of an empty map multibinding.
*/
@BindingContainer
@ContributesTo(RoomScope::class)
interface MediaItemPresenterFactoriesBindingContainer {
@Multibinds
fun multiBindMediaItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>
}
@@ -12,28 +12,13 @@ import androidx.compose.runtime.Composable
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntoMap
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemEventContentKey
import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresenterFactory import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresenterFactory
import io.element.android.libraries.mediaviewer.impl.model.MediaItem import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import kotlin.time.Duration import kotlin.time.Duration
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterBindingContainer {
@Binds
@IntoMap
@MediaItemEventContentKey(MediaItem.Voice::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): MediaItemPresenterFactory<*, *>
}
@AssistedInject @AssistedInject
class VoiceMessagePresenter( class VoiceMessagePresenter(
voiceMessagePresenterFactory: VoiceMessagePresenterFactory, voiceMessagePresenterFactory: VoiceMessagePresenterFactory,
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.mediaviewer.impl.gallery.voice.di
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntoMap
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemEventContentKey
import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresenterFactory
import io.element.android.libraries.mediaviewer.impl.gallery.voice.VoiceMessagePresenter
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterBindingContainer {
@Binds
@IntoMap
@MediaItemEventContentKey(MediaItem.Voice::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): MediaItemPresenterFactory<*, *>
}
@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.network package io.element.android.libraries.network.di
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.preferences.impl.store package io.element.android.libraries.preferences.impl.store.di
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.ContributesTo
@@ -15,6 +15,7 @@ import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStoreFactory
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@BindingContainer @BindingContainer
@@ -25,7 +25,7 @@ import io.element.android.libraries.push.impl.battery.BatteryOptimizationPresent
interface PushBindingContainer { interface PushBindingContainer {
companion object { companion object {
@Provides @Provides
fun provideNotificationCompatManager(@ApplicationContext context: Context): NotificationManagerCompat { fun providesNotificationManagerCompat(@ApplicationContext context: Context): NotificationManagerCompat {
return NotificationManagerCompat.from(context) return NotificationManagerCompat.from(context)
} }
} }
@@ -26,7 +26,7 @@ import timber.log.Timber
object PushHistoryBindingContainer { object PushHistoryBindingContainer {
@Provides @Provides
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
fun providePushDatabase( fun providesPushDatabase(
@ApplicationContext context: Context, @ApplicationContext context: Context,
): PushDatabase { ): PushDatabase {
val name = "push_database" val name = "push_database"
@@ -26,7 +26,7 @@ import timber.log.Timber
object SessionStorageBindingContainer { object SessionStorageBindingContainer {
@Provides @Provides
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
fun provideMatrixDatabase( fun providesSessionDatabase(
@ApplicationContext context: Context, @ApplicationContext context: Context,
): SessionDatabase { ): SessionDatabase {
val name = "session_database" val name = "session_database"
@@ -24,7 +24,7 @@ import io.element.android.opusencoder.OggOpusEncoder
@ContributesTo(RoomScope::class) @ContributesTo(RoomScope::class)
object VoiceRecorderBindingContainer { object VoiceRecorderBindingContainer {
@Provides @Provides
fun provideAudioConfig(): AudioConfig { fun providesAudioConfig(): AudioConfig {
val sampleRate = SampleRate val sampleRate = SampleRate
return AudioConfig( return AudioConfig(
format = AudioFormat.Builder() format = AudioFormat.Builder()
@@ -40,7 +40,7 @@ object VoiceRecorderBindingContainer {
} }
@Provides @Provides
public fun provideVoiceFileConfig(): VoiceFileConfig = public fun providesVoiceFileConfig(): VoiceFileConfig =
VoiceFileConfig( VoiceFileConfig(
cacheSubdir = "voice_recordings", cacheSubdir = "voice_recordings",
fileExt = "ogg", fileExt = "ogg",
@@ -48,5 +48,5 @@ object VoiceRecorderBindingContainer {
) )
@Provides @Provides
fun provideOggOpusEncoder(): OggOpusEncoder = OggOpusEncoder.create() fun providesOggOpusEncoder(): OggOpusEncoder = OggOpusEncoder.create()
} }
@@ -151,7 +151,7 @@ class DefaultVoiceRecorderTest {
} }
private fun TestScope.createDefaultVoiceRecorder(): DefaultVoiceRecorder { private fun TestScope.createDefaultVoiceRecorder(): DefaultVoiceRecorder {
val fileConfig = VoiceRecorderBindingContainer.provideVoiceFileConfig() val fileConfig = VoiceRecorderBindingContainer.providesVoiceFileConfig()
return DefaultVoiceRecorder( return DefaultVoiceRecorder(
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
timeSource = timeSource, timeSource = timeSource,
@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.workmanager.impl package io.element.android.libraries.workmanager.impl.di
import android.content.Context import android.content.Context
import androidx.work.WorkManager import androidx.work.WorkManager
@@ -17,5 +17,5 @@ import io.element.android.libraries.di.identifiers.SentrySdkDsn
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
object NoopAnalyticsBindingContainer { object NoopAnalyticsBindingContainer {
@Provides @Provides
fun provideSentrySdkDsn(): SentrySdkDsn? = null fun providesSentrySdkDsn(): SentrySdkDsn? = null
} }
@@ -19,8 +19,8 @@ import io.element.android.services.analyticsproviders.sentry.SentryConfig
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
object SentryBindingContainer { object SentryBindingContainer {
@Provides @Provides
fun provideSentryDsn(): SentryDsn? = SentryConfig.DSN.takeIf { it.isNotBlank() }?.let(::SentryDsn) fun providesSentryDsn(): SentryDsn? = SentryConfig.DSN.takeIf { it.isNotBlank() }?.let(::SentryDsn)
@Provides @Provides
fun provideSentrySdkDsn(): SentrySdkDsn? = SentryConfig.SDK_DSN.takeIf { it.isNotBlank() }?.let(::SentrySdkDsn) fun providesSentrySdkDsn(): SentrySdkDsn? = SentryConfig.SDK_DSN.takeIf { it.isNotBlank() }?.let(::SentrySdkDsn)
} }
@@ -22,7 +22,7 @@ import io.element.android.services.appnavstate.impl.initializer.AppForegroundSta
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
object AppNavStateBindingContainer { object AppNavStateBindingContainer {
@Provides @Provides
fun provideAppForegroundStateService( fun providesAppForegroundStateService(
@ApplicationContext context: Context @ApplicationContext context: Context
): AppForegroundStateService = ): AppForegroundStateService =
AppInitializer.getInstance(context).initializeComponent(AppForegroundStateServiceInitializer::class.java) AppInitializer.getInstance(context).initializeComponent(AppForegroundStateServiceInitializer::class.java)
@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.services.toolbox.impl.systemclock package io.element.android.services.toolbox.impl.systemclock.di
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
@@ -18,7 +18,7 @@ import kotlin.time.TimeSource
@ContributesTo(AppScope::class) @ContributesTo(AppScope::class)
object TimeBindingContainer { object TimeBindingContainer {
@Provides @Provides
fun timeSource(): TimeSource { fun providesTimeSource(): TimeSource {
return TimeSource.Monotonic return TimeSource.Monotonic
} }
} }