Merge branch 'develop' into fix/6371-save-file-name-conflicts

This commit is contained in:
Benoit Marty
2026-08-24 18:14:03 +02:00
committed by GitHub
478 changed files with 987 additions and 842 deletions
@@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
open class MediaBottomSheetStateDeleteConfirmationProvider : PreviewParameterProvider<MediaBottomSheetState.DeleteConfirmation> {
open class MediaBottomSheetStateDeleteConfirmationPreviewParam : PreviewParameterProvider<MediaBottomSheetState.DeleteConfirmation> {
override val values: Sequence<MediaBottomSheetState.DeleteConfirmation>
get() = sequenceOf(
aMediaBottomSheetStateDeleteConfirmation(),
@@ -14,7 +14,7 @@ import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
open class MediaBottomSheetStateDetailsProvider : PreviewParameterProvider<MediaBottomSheetState.Details> {
open class MediaBottomSheetStateDetailsPreviewParam : PreviewParameterProvider<MediaBottomSheetState.Details> {
override val values: Sequence<MediaBottomSheetState.Details>
get() = sequenceOf(
aMediaBottomSheetStateDetails(),
@@ -168,7 +168,7 @@ private fun MediaRow(
@PreviewsDayNight
@Composable
internal fun MediaDeleteConfirmationBottomSheetPreview(
@PreviewParameter(provider = MediaBottomSheetStateDeleteConfirmationProvider::class) state: MediaBottomSheetState.DeleteConfirmation,
@PreviewParameter(provider = MediaBottomSheetStateDeleteConfirmationPreviewParam::class) state: MediaBottomSheetState.DeleteConfirmation,
) = ElementPreview(fillMaxSize = true) {
MediaDeleteConfirmationBottomSheet(
state = state,
@@ -266,7 +266,7 @@ private fun SectionText(
@PreviewsDayNight
@Composable
internal fun MediaDetailsBottomSheetPreview(
@PreviewParameter(MediaBottomSheetStateDetailsProvider::class) state: MediaBottomSheetState.Details,
@PreviewParameter(MediaBottomSheetStateDetailsPreviewParam::class) state: MediaBottomSheetState.Details,
) = ElementPreview(fillMaxSize = true) {
MediaDetailsBottomSheet(
state = state,
@@ -28,7 +28,7 @@ import io.element.android.libraries.mediaviewer.impl.model.aMediaItemVideo
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemVoice
import kotlinx.collections.immutable.toImmutableList
open class MediaGalleryStateProvider : PreviewParameterProvider<MediaGalleryState> {
open class MediaGalleryStatePreviewParam : PreviewParameterProvider<MediaGalleryState> {
override val values: Sequence<MediaGalleryState>
get() = sequenceOf(
aMediaGalleryState(
@@ -597,7 +597,7 @@ private fun LoadingContent(
@PreviewsDayNight
@Composable
internal fun MediaGalleryViewPreview(
@PreviewParameter(MediaGalleryStateProvider::class) state: MediaGalleryState
@PreviewParameter(MediaGalleryStatePreviewParam::class) state: MediaGalleryState
) = ElementPreview {
CompositionLocalProvider(
LocalMediaItemPresenterFactories provides aFakeMediaItemPresenterFactories(),
@@ -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<*, *>>
}
@@ -46,7 +46,7 @@ fun AudioItemView(
@PreviewsDayNight
@Composable
internal fun AudioItemViewPreview(
@PreviewParameter(MediaItemAudioProvider::class) audio: MediaItem.Audio,
@PreviewParameter(MediaItemAudioPreviewParam::class) audio: MediaItem.Audio,
) = ElementPreview {
val states = remember {
listOf(
@@ -45,7 +45,7 @@ fun DateItemView(
@PreviewsDayNight
@Composable
internal fun DateItemViewPreview(
@PreviewParameter(MediaItemDateSeparatorProvider::class) date: MediaItem.DateSeparator,
@PreviewParameter(MediaItemDateSeparatorPreviewParam::class) date: MediaItem.DateSeparator,
) = ElementPreview {
DateItemView(date)
}
@@ -46,7 +46,7 @@ fun FileItemView(
@PreviewsDayNight
@Composable
internal fun FileItemViewPreview(
@PreviewParameter(MediaItemFileProvider::class) file: MediaItem.File,
@PreviewParameter(MediaItemFilePreviewParam::class) file: MediaItem.File,
) = ElementPreview {
val states = remember {
listOf(
@@ -13,7 +13,7 @@ import io.element.android.libraries.core.preview.loremIpsum
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemAudio
class MediaItemAudioProvider : PreviewParameterProvider<MediaItem.Audio> {
class MediaItemAudioPreviewParam : PreviewParameterProvider<MediaItem.Audio> {
override val values: Sequence<MediaItem.Audio>
get() = sequenceOf(
aMediaItemAudio(),
@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemDateSeparator
class MediaItemDateSeparatorProvider : PreviewParameterProvider<MediaItem.DateSeparator> {
class MediaItemDateSeparatorPreviewParam : PreviewParameterProvider<MediaItem.DateSeparator> {
override val values: Sequence<MediaItem.DateSeparator>
get() = sequenceOf(
aMediaItemDateSeparator(),
@@ -13,7 +13,7 @@ import io.element.android.libraries.core.preview.loremIpsum
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemFile
class MediaItemFileProvider : PreviewParameterProvider<MediaItem.File> {
class MediaItemFilePreviewParam : PreviewParameterProvider<MediaItem.File> {
override val values: Sequence<MediaItem.File>
get() = sequenceOf(
aMediaItemFile(),
@@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemVideo
class MediaItemVideoProvider : PreviewParameterProvider<MediaItem.Video> {
class MediaItemVideoPreviewParam : PreviewParameterProvider<MediaItem.Video> {
override val values: Sequence<MediaItem.Video>
get() = sequenceOf(
aMediaItemVideo(),
@@ -13,7 +13,7 @@ import io.element.android.libraries.core.preview.loremIpsum
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemVoice
class MediaItemVoiceProvider : PreviewParameterProvider<MediaItem.Voice> {
class MediaItemVoicePreviewParam : PreviewParameterProvider<MediaItem.Voice> {
override val values: Sequence<MediaItem.Voice>
get() = sequenceOf(
aMediaItemVoice(),
@@ -117,7 +117,7 @@ private fun VideoInfoRow(
@PreviewsDayNight
@Composable
internal fun VideoItemViewPreview(
@PreviewParameter(MediaItemVideoProvider::class) video: MediaItem.Video,
@PreviewParameter(MediaItemVideoPreviewParam::class) video: MediaItem.Video,
) = ElementPreview {
VideoItemView(
video = video,
@@ -54,7 +54,7 @@ import io.element.android.libraries.mediaviewer.impl.model.aMediaItemVoice
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvent
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider
import io.element.android.libraries.voiceplayer.api.VoiceMessageStatePreviewParam
import io.element.android.libraries.voiceplayer.api.aVoiceMessageState
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
@@ -279,7 +279,7 @@ private fun CustomIconButton(
@PreviewsDayNight
@Composable
internal fun VoiceItemViewPreview(
@PreviewParameter(MediaItemVoiceProvider::class) voice: MediaItem.Voice,
@PreviewParameter(MediaItemVoicePreviewParam::class) voice: MediaItem.Voice,
) = ElementPreview {
val states = remember {
listOf(
@@ -304,7 +304,7 @@ internal fun VoiceItemViewPreview(
@PreviewsDayNight
@Composable
internal fun VoiceItemViewPlayPreview(
@PreviewParameter(VoiceMessageStateProvider::class) state: VoiceMessageState,
@PreviewParameter(VoiceMessageStatePreviewParam::class) state: VoiceMessageState,
) = ElementPreview {
val validationState = remember {
listOf(
@@ -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<*, *>
}
@@ -393,7 +393,7 @@ private fun AudioInfoView(
@PreviewsDayNight
@Composable
internal fun MediaAudioViewPreview(
@PreviewParameter(MediaInfoAudioProvider::class) info: MediaInfo
@PreviewParameter(MediaInfoAudioPreviewParam::class) info: MediaInfo
) = ElementPreview {
MediaAudioView(
modifier = Modifier.fillMaxSize(),
@@ -13,7 +13,7 @@ import io.element.android.libraries.designsystem.components.media.WaveFormSample
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo
open class MediaInfoAudioProvider : PreviewParameterProvider<MediaInfo> {
open class MediaInfoAudioPreviewParam : PreviewParameterProvider<MediaInfo> {
override val values: Sequence<MediaInfo>
get() = sequenceOf(
anAudioMediaInfo(),
@@ -114,7 +114,7 @@ fun MediaFileView(
@PreviewsDayNight
@Composable
internal fun MediaFileViewPreview(
@PreviewParameter(MediaInfoFileProvider::class) info: MediaInfo
@PreviewParameter(MediaInfoFilePreviewParam::class) info: MediaInfo
) = ElementPreview {
MediaFileView(
modifier = Modifier.fillMaxSize(),
@@ -14,7 +14,7 @@ import io.element.android.libraries.mediaviewer.api.aZipMediaInfo
import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo
open class MediaInfoFileProvider : PreviewParameterProvider<MediaInfo> {
open class MediaInfoFilePreviewParam : PreviewParameterProvider<MediaInfo> {
override val values: Sequence<MediaInfo>
get() = sequenceOf(
aZipMediaInfo(),
@@ -10,7 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.local.player
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class MediaPlayerControllerStateProvider : PreviewParameterProvider<MediaPlayerControllerState> {
open class MediaPlayerControllerStatePreviewParam : PreviewParameterProvider<MediaPlayerControllerState> {
override val values: Sequence<MediaPlayerControllerState> = sequenceOf(
aMediaPlayerControllerState(),
aMediaPlayerControllerState(
@@ -213,7 +213,7 @@ fun MediaPlayerControllerView(
@PreviewsDayNight
@Composable
internal fun MediaPlayerControllerViewPreview(
@PreviewParameter(MediaPlayerControllerStateProvider::class) state: MediaPlayerControllerState
@PreviewParameter(MediaPlayerControllerStatePreviewParam::class) state: MediaPlayerControllerState
) = ElementPreview {
MediaPlayerControllerView(
state = state,
@@ -13,7 +13,7 @@ import io.element.android.libraries.architecture.AsyncData
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
open class TextFileContentProvider : PreviewParameterProvider<AsyncData<ImmutableList<String>>> {
open class TextFileContentPreviewParam : PreviewParameterProvider<AsyncData<ImmutableList<String>>> {
override val values: Sequence<AsyncData<ImmutableList<String>>>
get() = sequenceOf(
AsyncData.Uninitialized,
@@ -98,7 +98,7 @@ private fun TextFileContentView(
@PreviewsDayNight
@Composable
internal fun TextFileContentViewPreview(
@PreviewParameter(TextFileContentProvider::class) text: AsyncData<ImmutableList<String>>,
@PreviewParameter(TextFileContentPreviewParam::class) text: AsyncData<ImmutableList<String>>,
) = ElementPreview {
TextFileContentView(
data = text,
@@ -41,7 +41,7 @@ private const val LONG_CAPTION = "This is a very long caption that should be scr
"Line 4: Duis aute irure dolor in reprehenderit in voluptate velit esse cillum. " +
"Line 5: Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia."
open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> {
open class MediaViewerStatePreviewParam : PreviewParameterProvider<MediaViewerState> {
override val values: Sequence<MediaViewerState>
get() = sequenceOf(
aMediaViewerState(),
@@ -749,7 +749,7 @@ private fun ErrorView(
// Only preview in dark, dark theme is forced on the Node.
@Preview
@Composable
internal fun MediaViewerViewPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = ElementPreviewDark {
internal fun MediaViewerViewPreview(@PreviewParameter(MediaViewerStatePreviewParam::class) state: MediaViewerState) = ElementPreviewDark {
MediaViewerView(
state = state,
audioFocus = null,
@@ -760,7 +760,7 @@ internal fun MediaViewerViewPreview(@PreviewParameter(MediaViewerStateProvider::
@Preview(device = "${Devices.PHONE}, orientation=landscape")
@Composable
internal fun MediaViewerViewLandscapePreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = ElementPreviewDark {
internal fun MediaViewerViewLandscapePreview(@PreviewParameter(MediaViewerStatePreviewParam::class) state: MediaViewerState) = ElementPreviewDark {
MediaViewerView(
state = state,
audioFocus = null,