Merge branch 'develop' into fix/4657-preview-param-suffix

This commit is contained in:
Benoit Marty
2026-08-24 14:08:52 +02:00
committed by GitHub
60 changed files with 282 additions and 195 deletions
@@ -14,16 +14,16 @@ import kotlin.reflect.KClass
* Adds Node to the specified component graph.
* Equivalent to the following declaration:
*
* ```
* @BindingContainer
* @ContributesTo(Scope::class)
* abstract class YourNodeModule {
* @Binds
* @IntoMap
* @NodeKey(YourNode::class)
* abstract fun bindYourNodeFactory(factory: YourNode.Factory): AssistedNodeFactory<*>
*}
* interface YourNodeBindingContainer {
* @Binds
* @IntoMap
* @NodeKey(YourNode::class)
* fun bindYourNodeAssistedFactory(factory: YourNodeAssistedFactory): AssistedNodeFactory<*>
* }
* ```
*/
@Target(AnnotationTarget.CLASS)
annotation class ContributesNode(
@@ -38,7 +38,7 @@ import java.io.File
@BindingContainer
@ContributesTo(AppScope::class)
object AppModule {
object AppBindingContainer {
@Provides
@BaseDirectory
fun providesBaseDirectory(@ApplicationContext context: Context): File {
@@ -111,7 +111,7 @@ object AppModule {
@Provides
@SingleIn(AppScope::class)
fun provideSnackbarDispatcher(): SnackbarDispatcher {
fun providesSnackbarDispatcher(): SnackbarDispatcher {
return SnackbarDispatcher()
}
}
@@ -61,32 +61,32 @@ class ContributesNodeProcessor(
if (config.enableLogging) {
logger.warn("Processing ${ksClass.qualifiedName?.asString()}")
}
generateModule(ksClass)
generateBindingContainer(ksClass)
generateFactory(ksClass)
}
return invalidSymbols
}
private fun generateModule(ksClass: KSClassDeclaration) {
private fun generateBindingContainer(ksClass: KSClassDeclaration) {
val annotation = ksClass.annotations.find { it.shortName.asString() == "ContributesNode" }!!
val scope = annotation.arguments.find { it.name?.asString() == "scope" }!!.value as KSType
val modulePackage = ksClass.packageName.asString()
val moduleClassName = "${ksClass.simpleName.asString()}_Module"
val bindingContainerPackage = ksClass.packageName.asString()
val bindingContainerClassName = "${ksClass.simpleName.asString()}BindingContainer"
val nodeClassName = ClassName.bestGuess(ksClass.qualifiedName!!.asString())
val content = FileSpec.builder(
packageName = modulePackage,
fileName = moduleClassName,
packageName = bindingContainerPackage,
fileName = bindingContainerClassName,
)
.addType(
TypeSpec.interfaceBuilder(moduleClassName)
TypeSpec.interfaceBuilder(bindingContainerClassName)
.addAnnotation(AnnotationSpec.builder(Origin::class).addMember(CLASS_PLACEHOLDER, nodeClassName).build())
.addAnnotation(BindingContainer::class)
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember(CLASS_PLACEHOLDER, scope.toTypeName()).build())
.addFunction(
FunSpec.builder("bind${ksClass.simpleName.asString()}Factory")
FunSpec.builder("bind${ksClass.simpleName.asString()}AssistedFactory")
.addModifiers(KModifier.ABSTRACT)
.addParameter("factory", ClassName(modulePackage, "${ksClass.simpleName.asString()}_AssistedFactory"))
.addParameter("factory", ClassName(bindingContainerPackage, "${ksClass.simpleName.asString()}AssistedFactory"))
.returns(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(STAR))
.addAnnotation(Binds::class)
.addAnnotation(IntoMap::class)
@@ -114,7 +114,7 @@ class ContributesNodeProcessor(
@OptIn(KspExperimental::class)
private fun generateFactory(ksClass: KSClassDeclaration) {
val generatedPackage = ksClass.packageName.asString()
val assistedFactoryClassName = "${ksClass.simpleName.asString()}_AssistedFactory"
val assistedFactoryClassName = "${ksClass.simpleName.asString()}AssistedFactory"
val constructor = ksClass.getConstructors().first { it.parameters.isNotEmpty() }
val assistedParameters = constructor.parameters.filter { it.isAnnotationPresent(Assisted::class) }
if (assistedParameters.size != 2) {
@@ -18,7 +18,7 @@ import io.element.android.libraries.architecture.Presenter
@ContributesTo(AppScope::class)
@BindingContainer
interface AnalyticsModule {
interface AnalyticsBindingContainer {
@Binds
fun bindAnalyticsPreferencesPresenter(presenter: AnalyticsPreferencesPresenter): Presenter<AnalyticsPreferencesState>
}
@@ -60,7 +60,9 @@ class PictureInPicturePresenter(
}
is PictureInPictureEvent.SetPipOrientation -> {
Timber.tag(loggerTag.value).d("onOrientationChange: ${event.orientation}")
pipView?.setPipOrientation(event.orientation)
if (isPipSupported) {
pipView?.setPipOrientation(event.orientation)
}
}
}
}
@@ -8,9 +8,15 @@
package io.element.android.features.call.impl.pip
import android.os.Build
import androidx.annotation.RequiresApi
interface PipView {
@RequiresApi(Build.VERSION_CODES.O)
fun setPipOrientation(orientation: Int?)
@RequiresApi(Build.VERSION_CODES.O)
fun setPipParams()
@RequiresApi(Build.VERSION_CODES.O)
fun enterPipMode(): Boolean
fun hangUp()
}
@@ -202,7 +202,9 @@ class ElementCallActivity :
}
DisposableEffect(Unit) {
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
setPipParams()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setPipParams()
}
pipEventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(isInPictureInPictureMode))
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
@@ -300,6 +302,7 @@ class ElementCallActivity :
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun setPipOrientation(orientation: Int?) {
currentPipOrientation = orientation
setPictureInPictureParams(getPictureInPictureParams())
@@ -21,7 +21,7 @@ import kotlinx.coroutines.CoroutineScope
@BindingContainer
@ContributesTo(RoomScope::class)
object ContentScannerModule {
object ContentScannerBindingContainer {
@Provides
@SingleIn(RoomScope::class)
fun providesContentScannerService(
@@ -15,16 +15,16 @@ import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.runTest
import org.junit.Test
class ContentScannerModuleTest {
class ContentScannerBindingContainerTest {
@Test
fun `ContentScannerModule provides NoopContentScannerService when ContentScanner is null`() = runTest {
fun `ContentScannerBindingContainer provides NoopContentScannerService when ContentScanner is null`() = runTest {
// Given
val contentScanner: ContentScanner? = null
val sessionCoroutineScope = backgroundScope
val coroutineDispatchers = testCoroutineDispatchers()
// When
val contentScannerService = ContentScannerModule.providesContentScannerService(
val contentScannerService = ContentScannerBindingContainer.providesContentScannerService(
contentScanner = contentScanner,
coroutineScope = sessionCoroutineScope,
coroutineDispatchers = coroutineDispatchers,
@@ -35,14 +35,14 @@ class ContentScannerModuleTest {
}
@Test
fun `ContentScannerModule provides DefaultContentScannerService when ContentScanner is not null`() = runTest {
fun `ContentScannerBindingContainer provides DefaultContentScannerService when ContentScanner is not null`() = runTest {
// Given
val contentScanner: ContentScanner? = FakeContentScanner()
val sessionCoroutineScope = backgroundScope
val coroutineDispatchers = testCoroutineDispatchers()
// When
val contentScannerService = ContentScannerModule.providesContentScannerService(
val contentScannerService = ContentScannerBindingContainer.providesContentScannerService(
contentScanner = contentScanner,
coroutineScope = sessionCoroutineScope,
coroutineDispatchers = coroutineDispatchers,
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.SessionScope
@ContributesTo(SessionScope::class)
@BindingContainer
interface FtueModule {
interface FtueBindingContainer {
@Binds
fun bindChooseSelfVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState>
fun bindChooseSelfVerificationModePresenter(presenter: ChooseSelfVerificationModePresenter): Presenter<ChooseSelfVerificationModeState>
}
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.SessionScope
@BindingContainer
@ContributesTo(SessionScope::class)
interface HomeSpacesModule {
interface HomeSpacesBindingContainer {
@Binds
fun bindHomeSpacesPresenter(presenter: HomeSpacesPresenter): Presenter<HomeSpacesState>
}
@@ -26,15 +26,15 @@ import io.element.android.libraries.di.SessionScope
@ContributesTo(SessionScope::class)
@BindingContainer
interface RoomListModule {
interface RoomListBindingContainer {
@Binds
fun bindRoomListPresenter(presenter: RoomListPresenter): Presenter<RoomListState>
@Binds
fun bindSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
fun bindRoomListSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
@Binds
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
fun bindRoomListFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
@Binds
fun bindSpaceFiltersPresenter(presenter: SpaceFiltersPresenter): Presenter<SpaceFiltersState>
@@ -5,6 +5,8 @@
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.home.impl.search
import com.google.common.truth.Truth.assertThat
@@ -35,6 +37,7 @@ import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -22,9 +22,9 @@ import io.element.android.libraries.matrix.api.MatrixClient
@ContributesTo(SessionScope::class)
@BindingContainer
interface InviteModule {
interface InviteBindingContainer {
@Binds
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
fun bindAcceptDeclineInvitePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
companion object {
@Provides
@@ -27,7 +27,7 @@ import java.util.Optional
@BindingContainer
@ContributesTo(SessionScope::class)
object JoinRoomModule {
object JoinRoomBindingContainer {
@Provides
fun providesJoinRoomPresenterFactory(
client: MatrixClient,
@@ -6,7 +6,7 @@
* 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.ContributesTo
@@ -14,16 +14,17 @@ import dev.zacsweers.metro.Provides
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.impl.data.KnockRequestsService
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow
@BindingContainer
@ContributesTo(RoomScope::class)
object KnockRequestsModule {
object KnockRequestsBindingContainer {
@Provides
@SingleIn(RoomScope::class)
fun knockRequestsService(room: JoinedRoom): KnockRequestsService {
fun providesKnockRequestsService(room: JoinedRoom): KnockRequestsService {
return KnockRequestsService(
knockRequestsFlow = room.knockRequestsFlow,
permissionsFlow = room.permissionsFlow(KnockRequestPermissions.DEFAULT) { perms ->
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.SessionScope
@ContributesTo(SessionScope::class)
@BindingContainer
interface LeaveRoomModule {
interface LeaveRoomBindingContainer {
@Binds
fun bindLeaveRoomPresenter(presenter: LeaveRoomPresenter): Presenter<LeaveRoomState>
}
@@ -8,12 +8,7 @@
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 io.element.android.appconfig.LockScreenConfig as AppConfigLockScreenConfig
data class LockScreenConfig(
val isPinMandatory: Boolean,
@@ -24,18 +19,3 @@ data class LockScreenConfig(
val isStrongBiometricsEnabled: Boolean,
val isWeakBiometricsEnabled: Boolean,
)
@ContributesTo(AppScope::class)
@BindingContainer
object LockScreenConfigModule {
@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,
)
}
@@ -20,7 +20,7 @@ import io.element.android.libraries.architecture.Presenter
@ContributesTo(AppScope::class)
@BindingContainer
interface LoginModule {
interface LoginBindingContainer {
@Binds
fun bindChangeServerPresenter(presenter: ChangeServerPresenter): Presenter<ChangeServerState>
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.SessionScope
@ContributesTo(SessionScope::class)
@BindingContainer
interface LogoutModule {
interface LogoutBindingContainer {
@Binds
fun bindDirectLogoutPresenter(presenter: DirectLogoutPresenter): Presenter<DirectLogoutState>
}
@@ -34,7 +34,7 @@ import io.element.android.libraries.di.RoomScope
@ContributesTo(RoomScope::class)
@BindingContainer
interface MessagesBindsModule {
interface MessagesBindingContainer {
@Binds
fun bindPinnedMessagesBannerPresenter(presenter: PinnedMessagesBannerPresenter): Presenter<PinnedMessagesBannerState>
@@ -18,8 +18,8 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
@ContributesTo(RoomScope::class)
@BindingContainer
object MessagesProvidesModule {
object MessagesProvidesBindingContainer {
@Provides
@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.remember
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.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.libraries.architecture.Presenter
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 TimelineItemPresenterFactoriesModule {
@Multibinds
fun multiBindTimelineItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out TimelineItemEventContent>, TimelineItemPresenterFactory<*, *>>
}
/**
* 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.AssistedFactory
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.model.event.TimelineItemVoiceContent
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.VoiceMessageState
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterModule {
@Binds
@IntoMap
@TimelineItemEventContentKey(TimelineItemVoiceContent::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): TimelineItemPresenterFactory<*, *>
}
@AssistedInject
class VoiceMessagePresenter(
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<*, *>
}
@@ -17,7 +17,7 @@ import io.element.android.libraries.architecture.Presenter
@ContributesTo(AppScope::class)
@BindingContainer
interface DeveloperSettingsModule {
interface DeveloperSettingsBindingContainer {
@Binds
fun bindAppDeveloperSettingsPresenter(presenter: AppDeveloperSettingsPresenter): Presenter<AppDeveloperSettingsState>
}
@@ -5,11 +5,13 @@
* 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.Binds
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.di.SessionScope
@@ -22,7 +22,7 @@ import io.element.android.libraries.architecture.Presenter
@ContributesTo(AppScope::class)
@BindingContainer
interface RageshakeModule {
interface RageshakeBindingContainer {
@Binds
fun bindRageshakePreferencesPresenter(presenter: RageshakePreferencesPresenter): Presenter<RageshakePreferencesState>
@@ -18,9 +18,9 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
@BindingContainer
@ContributesTo(SessionScope::class)
object RoomAliasResolverModule {
object RoomAliasResolverBindingContainer {
@Provides
fun providesJoinRoomPresenterFactory(
fun providesRoomAliasResolverPresenterFactory(
client: MatrixClient,
): RoomAliasResolverPresenter.Factory {
return object : RoomAliasResolverPresenter.Factory {
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.RoomScope
@ContributesTo(RoomScope::class)
@BindingContainer
interface RoomCallModule {
interface RoomCallBindingContainer {
@Binds
fun bindRoomCallStatePresenter(presenter: RoomCallStatePresenter): Presenter<RoomCallState>
}
@@ -21,9 +21,9 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
@BindingContainer
@ContributesTo(RoomScope::class)
object RoomMemberModule {
object RoomMemberBindingContainer {
@Provides
fun provideRoomMemberDetailsPresenterFactory(
fun providesRoomMemberDetailsPresenterFactory(
room: JoinedRoom,
userProfilePresenterFactory: UserProfilePresenterFactory,
encryptionService: EncryptionService,
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.RoomScope
@ContributesTo(RoomScope::class)
@BindingContainer
interface RoomMemberModerationModule {
interface RoomMemberModerationBindingContainer {
@Binds
fun bindRoomMemberModerationPresenter(presenter: RoomMemberModerationPresenter): Presenter<RoomMemberModerationState>
}
@@ -22,10 +22,10 @@ import timber.log.Timber
@BindingContainer
@ContributesTo(AppScope::class)
object CacheStoreModule {
object CacheStoreBindingContainer {
@Provides
@SingleIn(AppScope::class)
fun provideCacheDatabase(
fun providesCacheDatabase(
@ApplicationContext context: Context,
): CacheDatabase {
val name = "cache_database"
@@ -26,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
internal const val ANDROID_KEYSTORE = "AndroidKeyStore"
/**
* 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.
@@ -6,19 +6,18 @@
* 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.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import io.element.android.libraries.cryptography.impl.ANDROID_KEYSTORE
import java.security.KeyStore
internal const val ANDROID_KEYSTORE = "AndroidKeyStore"
@ContributesTo(AppScope::class)
@BindingContainer
object CryptographyModule {
object CryptographyBindingContainer {
@Provides
fun providesAndroidKeyStore(): KeyStore {
return KeyStore.getInstance(ANDROID_KEYSTORE).apply {
@@ -19,7 +19,7 @@ import kotlin.time.Clock
@BindingContainer
@ContributesTo(AppScope::class)
object DateFormatterModule {
object DateFormatterBindingContainer {
@Provides
fun providesClock(): Clock = Clock.System
@@ -27,5 +27,5 @@ object DateFormatterModule {
fun providesLocale(): Locale = Locale.getDefault()
@Provides
fun providesTimezone(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() }
fun providesTimezoneProvider(): TimezoneProvider = TimezoneProvider { TimeZone.currentSystemDefault() }
}
@@ -18,7 +18,7 @@ import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvi
@BindingContainer
@ContributesTo(AppScope::class)
object FeatureFlagModule {
object FeatureFlagBindingContainer {
@JvmStatic
@Provides
@ElementsIntoSet
@@ -18,7 +18,7 @@ import io.element.android.libraries.fullscreenintent.impl.FullScreenIntentPermis
@ContributesTo(AppScope::class)
@BindingContainer
interface FullScreenIntentModule {
interface FullScreenIntentBindingContainer {
@Binds
fun bindFullScreenIntentPermissionsPresenter(presenter: FullScreenIntentPermissionsPresenter): Presenter<FullScreenIntentPermissionsState>
}
@@ -18,7 +18,7 @@ import kotlinx.coroutines.CoroutineScope
@BindingContainer
@ContributesTo(RoomScope::class)
object RoomModule {
object RoomBindingContainer {
@RoomCoroutineScope
@Provides
fun providesSessionCoroutineScope(room: BaseRoom): CoroutineScope {
@@ -34,7 +34,7 @@ import kotlinx.coroutines.CoroutineScope
@BindingContainer
@ContributesTo(SessionScope::class)
object SessionMatrixModule {
object SessionMatrixBindingContainer {
@Provides
fun providesSessionId(matrixClient: MatrixClient): SessionId {
return matrixClient.sessionId
@@ -56,7 +56,7 @@ object SessionMatrixModule {
}
@Provides
fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver {
fun providesRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver {
return matrixClient.roomMembershipObserver
}
@@ -112,7 +112,7 @@ object SessionMatrixModule {
}
@Provides
fun providesGetUrlResolverClient(matrixClient: MatrixClient): ClientUrlContentFetcher {
fun providesClientUrlContentFetcher(matrixClient: MatrixClient): ClientUrlContentFetcher {
return matrixClient
}
@@ -8,16 +8,8 @@
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.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.
@@ -44,15 +36,6 @@ interface SimplePlayer {
}
}
@ContributesTo(RoomScope::class)
@BindingContainer
object SimplePlayerModule {
@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].
*/
@@ -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.remember
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.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 MediaItemPresenterFactoriesModule {
@Multibinds
fun multiBindMediaItemPresenterFactories(): @JvmSuppressWildcards Map<KClass<out MediaItem.Event>, MediaItemPresenterFactory<*, *>>
}
/**
* 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.AssistedFactory
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.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.model.MediaItem
import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import kotlin.time.Duration
@BindingContainer
@ContributesTo(RoomScope::class)
interface VoiceMessagePresenterModule {
@Binds
@IntoMap
@MediaItemEventContentKey(MediaItem.Voice::class)
fun bindVoiceMessagePresenterFactory(factory: VoiceMessagePresenter.Factory): MediaItemPresenterFactory<*, *>
}
@AssistedInject
class VoiceMessagePresenter(
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.
*/
package io.element.android.libraries.network
package io.element.android.libraries.network.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
@@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit
@BindingContainer
@ContributesTo(AppScope::class)
object NetworkModule {
object NetworkBindingContainer {
@Provides
@SingleIn(AppScope::class)
fun providesOkHttpClient(
@@ -6,7 +6,7 @@
* 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.ContributesTo
@@ -15,11 +15,12 @@ import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStoreFactory
import kotlinx.coroutines.CoroutineScope
@BindingContainer
@ContributesTo(SessionScope::class)
object SessionPreferencesModule {
object SessionPreferencesBindingContainer {
@Provides
fun providesSessionPreferencesStore(
defaultSessionPreferencesStoreFactory: DefaultSessionPreferencesStoreFactory,
@@ -22,10 +22,10 @@ import io.element.android.libraries.push.impl.battery.BatteryOptimizationPresent
@BindingContainer
@ContributesTo(AppScope::class)
interface PushModule {
interface PushBindingContainer {
companion object {
@Provides
fun provideNotificationCompatManager(@ApplicationContext context: Context): NotificationManagerCompat {
fun providesNotificationManagerCompat(@ApplicationContext context: Context): NotificationManagerCompat {
return NotificationManagerCompat.from(context)
}
}
@@ -23,10 +23,10 @@ import timber.log.Timber
@BindingContainer
@ContributesTo(AppScope::class)
object PushHistoryModule {
object PushHistoryBindingContainer {
@Provides
@SingleIn(AppScope::class)
fun providePushDatabase(
fun providesPushDatabase(
@ApplicationContext context: Context,
): PushDatabase {
val name = "push_database"
@@ -23,10 +23,10 @@ import timber.log.Timber
@BindingContainer
@ContributesTo(AppScope::class)
object SessionStorageModule {
object SessionStorageBindingContainer {
@Provides
@SingleIn(AppScope::class)
fun provideMatrixDatabase(
fun providesSessionDatabase(
@ApplicationContext context: Context,
): SessionDatabase {
val name = "session_database"
@@ -22,9 +22,9 @@ import io.element.android.opusencoder.OggOpusEncoder
@BindingContainer
@ContributesTo(RoomScope::class)
object VoiceRecorderModule {
object VoiceRecorderBindingContainer {
@Provides
fun provideAudioConfig(): AudioConfig {
fun providesAudioConfig(): AudioConfig {
val sampleRate = SampleRate
return AudioConfig(
format = AudioFormat.Builder()
@@ -40,7 +40,7 @@ object VoiceRecorderModule {
}
@Provides
public fun provideVoiceFileConfig(): VoiceFileConfig =
public fun providesVoiceFileConfig(): VoiceFileConfig =
VoiceFileConfig(
cacheSubdir = "voice_recordings",
fileExt = "ogg",
@@ -48,5 +48,5 @@ object VoiceRecorderModule {
)
@Provides
fun provideOggOpusEncoder(): OggOpusEncoder = OggOpusEncoder.create()
fun providesOggOpusEncoder(): OggOpusEncoder = OggOpusEncoder.create()
}
@@ -18,7 +18,7 @@ import io.element.android.libraries.voicerecorder.api.VoiceRecorderState
import io.element.android.libraries.voicerecorder.impl.audio.Audio
import io.element.android.libraries.voicerecorder.impl.audio.AudioConfig
import io.element.android.libraries.voicerecorder.impl.audio.SampleRate
import io.element.android.libraries.voicerecorder.impl.di.VoiceRecorderModule
import io.element.android.libraries.voicerecorder.impl.di.VoiceRecorderBindingContainer
import io.element.android.libraries.voicerecorder.test.FakeAudioLevelCalculator
import io.element.android.libraries.voicerecorder.test.FakeAudioReaderFactory
import io.element.android.libraries.voicerecorder.test.FakeEncoder
@@ -151,7 +151,7 @@ class DefaultVoiceRecorderTest {
}
private fun TestScope.createDefaultVoiceRecorder(): DefaultVoiceRecorder {
val fileConfig = VoiceRecorderModule.provideVoiceFileConfig()
val fileConfig = VoiceRecorderBindingContainer.providesVoiceFileConfig()
return DefaultVoiceRecorder(
dispatchers = testCoroutineDispatchers(),
timeSource = timeSource,
@@ -5,7 +5,7 @@
* 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 androidx.work.WorkManager
@@ -18,7 +18,7 @@ import io.element.android.libraries.di.annotations.ApplicationContext
@BindingContainer
@ContributesTo(AppScope::class)
object WorkManagerModule {
object WorkManagerBindingContainer {
@Provides
@SingleIn(AppScope::class)
fun providesWorkManager(
@@ -15,7 +15,7 @@ import io.element.android.libraries.di.identifiers.SentrySdkDsn
@BindingContainer
@ContributesTo(AppScope::class)
object NoopAnalyticsModule {
object NoopAnalyticsBindingContainer {
@Provides
fun provideSentrySdkDsn(): SentrySdkDsn? = null
fun providesSentrySdkDsn(): SentrySdkDsn? = null
}
@@ -17,10 +17,10 @@ import io.element.android.services.analyticsproviders.sentry.SentryConfig
@BindingContainer
@ContributesTo(AppScope::class)
object SentryModule {
object SentryBindingContainer {
@Provides
fun provideSentryDsn(): SentryDsn? = SentryConfig.DSN.takeIf { it.isNotBlank() }?.let(::SentryDsn)
fun providesSentryDsn(): SentryDsn? = SentryConfig.DSN.takeIf { it.isNotBlank() }?.let(::SentryDsn)
@Provides
fun provideSentrySdkDsn(): SentrySdkDsn? = SentryConfig.SDK_DSN.takeIf { it.isNotBlank() }?.let(::SentrySdkDsn)
fun providesSentrySdkDsn(): SentrySdkDsn? = SentryConfig.SDK_DSN.takeIf { it.isNotBlank() }?.let(::SentrySdkDsn)
}
@@ -20,9 +20,9 @@ import io.element.android.services.appnavstate.impl.initializer.AppForegroundSta
@BindingContainer
@ContributesTo(AppScope::class)
object AppNavStateModule {
object AppNavStateBindingContainer {
@Provides
fun provideAppForegroundStateService(
fun providesAppForegroundStateService(
@ApplicationContext context: Context
): AppForegroundStateService =
AppInitializer.getInstance(context).initializeComponent(AppForegroundStateServiceInitializer::class.java)
@@ -6,7 +6,7 @@
* 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.BindingContainer
@@ -16,9 +16,9 @@ import kotlin.time.TimeSource
@BindingContainer
@ContributesTo(AppScope::class)
object TimeModule {
object TimeBindingContainer {
@Provides
fun timeSource(): TimeSource {
fun providesTimeSource(): TimeSource {
return TimeSource.Monotonic
}
}