Merge pull request #5527 from element-hq/renovate/org.matrix.rustcomponents-sdk-android-25.x

fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.13
This commit is contained in:
Benoit Marty
2025-10-13 18:04:52 +02:00
committed by GitHub
29 changed files with 110 additions and 130 deletions

View File

@@ -8,15 +8,14 @@
package io.element.android.features.messages.api.timeline.voicemessages.composer
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.textcomposer.model.VoiceMessageState
import kotlinx.collections.immutable.toImmutableList
import kotlin.time.Duration.Companion.seconds
open class VoiceMessageComposerStateProvider : PreviewParameterProvider<VoiceMessageComposerState> {
override val values: Sequence<VoiceMessageComposerState>
get() = sequenceOf(
aVoiceMessageComposerState(voiceMessageState = VoiceMessageState.Recording(duration = 61.seconds, levels = aWaveformLevels)),
aVoiceMessageComposerState(voiceMessageState = VoiceMessageState.Recording(duration = 61.seconds, levels = WaveFormSamples.allRangeWaveForm)),
)
}
@@ -39,7 +38,5 @@ fun aVoiceMessagePreviewState() = VoiceMessageState.Preview(
showCursor = false,
playbackProgress = 0f,
time = 10.seconds,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
)
internal var aWaveformLevels = List(100) { it.toFloat() / 100 }.toImmutableList()

View File

@@ -167,7 +167,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
# All new features should not be implemented in the pull request that upgrades the version, developers should
# only fix API breaks and may add some TODOs.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.10.7"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.10.13"
# Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2023, 2024 New Vector 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.designsystem.components.media
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlin.random.Random
/**
* Generate a waveform for testing purposes.
*
* The waveform is a list of floats between 0 and 1.
*
* @param length The length of the waveform.
*/
fun createFakeWaveform(length: Int = 1000): ImmutableList<Float> {
val random = Random(seed = 2)
return List(length) { random.nextFloat() }
.toImmutableList()
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 New Vector 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.designsystem.components.media
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
object WaveFormSamples {
val allRangeWaveForm = List(100) { it.toFloat() / 100 }.toImmutableList()
val realisticWaveForm = persistentListOf(
0.000f, 0.000f, 0.000f, 0.003f, 0.354f,
0.353f, 0.365f, 0.790f, 0.787f, 0.167f,
0.333f, 0.975f, 0.000f, 0.102f, 0.003f,
0.531f, 0.584f, 0.317f, 0.140f, 0.475f,
0.496f, 0.561f, 0.042f, 0.263f, 0.169f,
0.829f, 0.349f, 0.010f, 0.000f, 0.000f,
1.000f, 0.334f, 0.321f, 0.011f, 0.000f,
0.000f, 0.003f,
)
val longRealisticWaveForm = List(4) { realisticWaveForm }.flatten().toPersistentList()
}

View File

@@ -49,7 +49,7 @@ private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
*
* @param playbackProgress The current playback progress, between 0 and 1.
* @param showCursor Whether to show the cursor or not.
* @param waveform The waveform to display. Use [createFakeWaveform] to generate a fake waveform.
* @param waveform The waveform to display.
* @param onSeek Callback when the user seeks the waveform. Called with a value between 0 and 1.
* @param modifier The modifier to be applied to the view.
* @param seekEnabled Whether the user can seek the waveform or not.
@@ -187,14 +187,14 @@ internal fun WaveformPlaybackViewPreview() = ElementPreview {
showCursor = false,
playbackProgress = 0.5f,
onSeek = {},
waveform = aWaveForm().toImmutableList(),
waveform = WaveFormSamples.realisticWaveForm,
)
WaveformPlaybackView(
modifier = Modifier.height(34.dp),
showCursor = true,
playbackProgress = 0.5f,
onSeek = {},
waveform = List(1024) { it / 1024f }.toImmutableList(),
waveform = WaveFormSamples.allRangeWaveForm,
)
}
}
@@ -217,45 +217,3 @@ private fun ImmutableList<Float>.normalisedData(maxSamplesCount: Int): Immutable
return result.toImmutableList()
}
fun aWaveForm(): List<Float> {
return listOf(
0.000f,
0.000f,
0.000f,
0.003f,
0.354f,
0.353f,
0.365f,
0.790f,
0.787f,
0.167f,
0.333f,
0.975f,
0.000f,
0.102f,
0.003f,
0.531f,
0.584f,
0.317f,
0.140f,
0.475f,
0.496f,
0.561f,
0.042f,
0.263f,
0.169f,
0.829f,
0.349f,
0.010f,
0.000f,
0.000f,
1.000f,
0.334f,
0.321f,
0.011f,
0.000f,
0.000f,
0.003f,
)
}

View File

@@ -12,6 +12,7 @@ sealed interface QrCodeLoginStep {
data class EstablishingSecureChannel(val checkCode: String) : QrCodeLoginStep
data object Starting : QrCodeLoginStep
data class WaitingForToken(val userCode: String) : QrCodeLoginStep
data object SyncingSecrets : QrCodeLoginStep
data class Failed(val error: QrLoginException) : QrCodeLoginStep
data object Finished : QrCodeLoginStep
}

View File

@@ -15,6 +15,7 @@ fun QrLoginProgress.toStep(): QrCodeLoginStep {
is QrLoginProgress.EstablishingSecureChannel -> QrCodeLoginStep.EstablishingSecureChannel(checkCodeString)
is QrLoginProgress.Starting -> QrCodeLoginStep.Starting
is QrLoginProgress.WaitingForToken -> QrCodeLoginStep.WaitingForToken(userCode)
is QrLoginProgress.SyncingSecrets -> QrCodeLoginStep.SyncingSecrets
is QrLoginProgress.Done -> QrCodeLoginStep.Finished
}
}

View File

@@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.media.toMSC3246range
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.location.toInner
@@ -484,7 +483,7 @@ class RustTimeline(
inReplyTo = inReplyToEventId?.value,
),
audioInfo = audioInfo.map(),
waveform = waveform.toMSC3246range(),
waveform = waveform,
)
}
}

View File

@@ -9,7 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.media.aWaveForm
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState
@@ -71,7 +71,7 @@ open class MediaGalleryStateProvider : PreviewParameterProvider<MediaGalleryStat
aMediaItemAudio(id = UniqueId("4")),
aMediaItemVoice(
id = UniqueId("5"),
waveform = aWaveForm(),
waveform = WaveFormSamples.realisticWaveForm,
),
aMediaItemLoadingIndicator(),
).toImmutableList()

View File

@@ -8,7 +8,7 @@
package io.element.android.libraries.mediaviewer.impl.local.audio
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.components.media.aWaveForm
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo
@@ -17,7 +17,7 @@ open class MediaInfoAudioProvider : PreviewParameterProvider<MediaInfo> {
get() = sequenceOf(
anAudioMediaInfo(),
anAudioMediaInfo(
waveForm = aWaveForm(),
waveForm = WaveFormSamples.realisticWaveForm,
),
)
}

View File

@@ -7,7 +7,7 @@
package io.element.android.libraries.mediaviewer.impl.model
import io.element.android.libraries.designsystem.components.media.aWaveForm
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
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.core.UserId
@@ -91,7 +91,7 @@ fun aMediaItemVoice(
filename: String = "filename.ogg",
caption: String? = null,
duration: String? = "1:23",
waveform: List<Float> = aWaveForm(),
waveform: List<Float> = WaveFormSamples.realisticWaveForm,
): MediaItem.Voice {
return MediaItem.Voice(
id = id,

View File

@@ -11,7 +11,7 @@ import android.net.Uri
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.media.aWaveForm
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.timeline.Timeline
@@ -138,7 +138,7 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
mediaBottomSheetState = aMediaDeleteConfirmationState(),
),
anAudioMediaInfo(
waveForm = aWaveForm(),
waveForm = WaveFormSamples.realisticWaveForm,
).let {
aMediaViewerState(
listOf(

View File

@@ -10,7 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.viewer
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -128,7 +128,7 @@ class SingleMediaGalleryDataSourceTest {
fun `createFrom should create a SingleMediaGalleryDataSource with a voice item`() {
testFactory(
mediaInfo = aVoiceMediaInfo(
waveForm = createFakeWaveform(),
waveForm = WaveFormSamples.longRealisticWaveForm,
duration = "12:34",
),
expectedResult = { params ->

View File

@@ -52,7 +52,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.androidutils.ui.showKeyboard
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.designsystem.preview.DAY_MODE_NAME
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.NIGHT_MODE_NAME
@@ -808,30 +808,33 @@ internal fun TextComposerCaptionPreview() = ElementPreview {
internal fun TextComposerVoicePreview() = ElementPreview {
PreviewColumn(
items = persistentListOf(
VoiceMessageState.Recording(61.seconds, createFakeWaveform()),
VoiceMessageState.Recording(
duration = 61.seconds,
levels = WaveFormSamples.realisticWaveForm,
),
VoiceMessageState.Preview(
isSending = false,
isPlaying = false,
showCursor = false,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
time = 0.seconds,
playbackProgress = 0.0f
playbackProgress = 0.0f,
),
VoiceMessageState.Preview(
isSending = false,
isPlaying = true,
showCursor = true,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
time = 3.seconds,
playbackProgress = 0.2f
playbackProgress = 0.2f,
),
VoiceMessageState.Preview(
isSending = true,
isPlaying = false,
showCursor = false,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
time = 61.seconds,
playbackProgress = 0.0f
playbackProgress = 0.0f,
),
)
) { voiceMessageState ->
@@ -848,12 +851,15 @@ internal fun TextComposerVoicePreview() = ElementPreview {
internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
PreviewColumn(
items = persistentListOf(
VoiceMessageState.Recording(61.seconds, createFakeWaveform()),
VoiceMessageState.Recording(
duration = 61.seconds,
levels = WaveFormSamples.realisticWaveForm,
),
VoiceMessageState.Preview(
isSending = false,
isPlaying = false,
showCursor = false,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
time = 0.seconds,
playbackProgress = 0.0f
),
@@ -861,7 +867,7 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
isSending = false,
isPlaying = true,
showCursor = true,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
time = 3.seconds,
playbackProgress = 0.2f
),
@@ -869,7 +875,7 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
isSending = true,
isPlaying = false,
showCursor = false,
waveform = createFakeWaveform(),
waveform = WaveFormSamples.realisticWaveForm,
time = 61.seconds,
playbackProgress = 0.0f
),

View File

@@ -29,8 +29,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.components.media.WaveFormSamples
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -138,14 +138,18 @@ private fun PlayerButton(
private fun PauseIcon() = Icon(
imageVector = CompoundIcons.PauseSolid(),
contentDescription = stringResource(id = CommonStrings.a11y_pause),
modifier = Modifier.size(20.dp).padding(2.dp),
modifier = Modifier
.size(20.dp)
.padding(2.dp),
)
@Composable
private fun PlayIcon() = Icon(
imageVector = CompoundIcons.PlaySolid(),
contentDescription = stringResource(id = CommonStrings.a11y_play),
modifier = Modifier.size(20.dp).padding(2.dp),
modifier = Modifier
.size(20.dp)
.padding(2.dp),
)
@PreviewsDayNight
@@ -160,7 +164,7 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview {
time = 2.seconds,
playbackProgress = 0.2f,
showCursor = true,
waveform = createFakeWaveform()
waveform = WaveFormSamples.longRealisticWaveForm,
)
AVoiceMessagePreview(
isInteractive = true,
@@ -168,7 +172,7 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview {
time = 0.seconds,
playbackProgress = 0.0f,
showCursor = true,
waveform = createFakeWaveform()
waveform = WaveFormSamples.longRealisticWaveForm,
)
AVoiceMessagePreview(
isInteractive = false,
@@ -176,7 +180,7 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview {
time = 789.seconds,
playbackProgress = 0.0f,
showCursor = false,
waveform = createFakeWaveform()
waveform = WaveFormSamples.longRealisticWaveForm,
)
}
}

View File

@@ -21,11 +21,13 @@ sealed interface VoiceMessageState {
val showCursor: Boolean,
val playbackProgress: Float,
val time: Duration,
// Values are between 0 and 1
val waveform: ImmutableList<Float>,
) : VoiceMessageState
data class Recording(
val duration: Duration,
// Values are between 0 and 1
val levels: ImmutableList<Float>,
) : VoiceMessageState
}

View File

@@ -22,16 +22,19 @@ sealed interface VoiceRecorderState {
* The recorder is currently recording.
*
* @property elapsedTime The elapsed time since the recording started.
* @property levels The current audio levels of the recording as a fraction of 1.
* @property levels The current audio levels of the recording as a fraction of 1. All values are between 0 and 1.
*/
data class Recording(val elapsedTime: Duration, val levels: List<Float>) : VoiceRecorderState
data class Recording(
val elapsedTime: Duration,
val levels: List<Float>,
) : VoiceRecorderState
/**
* The recorder has finished recording.
*
* @property file The recorded file.
* @property mimeType The mime type of the file.
* @property waveform The waveform of the recording.
* @property waveform The waveform of the recording. All values are between 0 and 1.
* @property duration The total time spent recording.
*/
data class Finished(

View File

@@ -63,6 +63,8 @@ class DefaultVoiceRecorder(
private var outputFile: File? = null
private var audioReader: AudioReader? = null
private var recordingJob: Job? = null
// List of Float between 0 and 1 representing the audio levels
private val levels: MutableList<Float> = mutableListOf()
private val lock = Mutex()

View File

@@ -7,6 +7,8 @@
package io.element.android.libraries.voicerecorder.impl.audio
import androidx.annotation.FloatRange
interface AudioLevelCalculator {
/**
* Calculate the audio level of the audio buffer.
@@ -14,5 +16,6 @@ interface AudioLevelCalculator {
* @param buffer The audio buffer containing 16bit PCM audio data.
* @return A float value between 0 and 1 proportional to the audio level.
*/
@FloatRange(from = 0.0, to = 1.0)
fun calculateAudioLevel(buffer: ShortArray): Float
}