Merge branch 'develop' of https://github.com/vector-im/element-x-android into langleyd/live_waveform

This commit is contained in:
David Langley 2023-10-27 13:54:18 +01:00
commit a4df8f80cc
37 changed files with 153 additions and 91 deletions

View file

@ -38,6 +38,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@ -59,6 +60,8 @@ import io.element.android.features.messages.impl.timeline.components.TimelineIte
import io.element.android.features.messages.impl.timeline.components.TimelineItemVirtualRow
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineLoadingMoreIndicator
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.aFakeTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider
@ -333,15 +336,19 @@ internal fun TimelineViewPreview(
@PreviewParameter(TimelineItemEventContentProvider::class) content: TimelineItemEventContent
) = ElementPreview {
val timelineItems = aTimelineItemList(content)
TimelineView(
state = aTimelineState(timelineItems),
onMessageClicked = {},
onTimestampClicked = {},
onUserDataClicked = {},
onMessageLongClicked = {},
onReactionClicked = { _, _ -> },
onReactionLongClicked = { _, _ -> },
onMoreReactionsClicked = {},
onSwipeToReply = {},
)
CompositionLocalProvider(
LocalTimelineItemPresenterFactories provides aFakeTimelineItemPresenterFactories(),
) {
TimelineView(
state = aTimelineState(timelineItems),
onMessageClicked = {},
onTimestampClicked = {},
onUserDataClicked = {},
onMessageLongClicked = {},
onReactionClicked = { _, _ -> },
onReactionLongClicked = { _, _ -> },
onMoreReactionsClicked = {},
onSwipeToReply = {},
)
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.timeline.di
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
import io.element.android.features.messages.impl.voicemessages.timeline.aVoiceMessageState
import io.element.android.libraries.architecture.Presenter
/**
* A fake [TimelineItemPresenterFactories] for screenshot tests.
*/
fun aFakeTimelineItemPresenterFactories() = TimelineItemPresenterFactories(
mapOf(
Pair(
TimelineItemVoiceContent::class.java,
TimelineItemPresenterFactory<TimelineItemVoiceContent, VoiceMessageState> { Presenter { aVoiceMessageState() } },
),
)
)

View file

@ -29,7 +29,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
import io.element.android.features.messages.impl.voicemessages.fromMSC3246range
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
@ -118,7 +117,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
waveform = messageType.details?.waveform?.fromMSC3246range()?.toImmutableList() ?: persistentListOf(),
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
)
else -> TimelineItemAudioContent(
body = messageType.body,

View file

@ -28,8 +28,12 @@ class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEv
aTimelineItemVideoContent(),
aTimelineItemFileContent(),
aTimelineItemFileContent("A bigger file name which doesn't fit.pdf"),
aTimelineItemAudioContent(),
aTimelineItemAudioContent("An even bigger bigger bigger bigger bigger bigger bigger sound name which doesn't fit .mp3"),
aTimelineItemVoiceContent(),
aTimelineItemLocationContent(),
aTimelineItemLocationContent("Location description"),
aTimelineItemPollContent(),
aTimelineItemNoticeContent(),
aTimelineItemRedactedContent(),
aTimelineItemTextContent(),

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.voicemessages
/**
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
* to a [0;1] range float list to be used for waveform rendering.
*/
internal fun List<Int>.fromMSC3246range(): List<Float> = map { it / 1024f }
/**
* Resizes the given [0;1] float list to [0;1024] int list as per unstable MSC3246 spec.
*/
internal fun List<Float>.toMSC3246range(): List<Int> = map { (it * 1024).toInt() }

View file

@ -28,7 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.features.messages.impl.voicemessages.toMSC3246range
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn
@ -220,7 +219,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
val result = mediaSender.sendVoiceMessage(
uri = file.toUri(),
mimeType = mimeType,
waveForm = waveform.toMSC3246range(),
waveForm = waveform,
)
if (result.isFailure) {

View file

@ -21,35 +21,41 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class VoiceMessageStateProvider : PreviewParameterProvider<VoiceMessageState> {
override val values: Sequence<VoiceMessageState>
get() = sequenceOf(
VoiceMessageState(
aVoiceMessageState(
VoiceMessageState.Button.Downloading,
progress = 0f,
time = "0:00",
eventSink = {},
),
VoiceMessageState(
aVoiceMessageState(
VoiceMessageState.Button.Retry,
progress = 0.5f,
time = "0:01",
eventSink = {}
),
VoiceMessageState(
aVoiceMessageState(
VoiceMessageState.Button.Play,
progress = 1f,
time = "1:00",
eventSink = {}
),
VoiceMessageState(
aVoiceMessageState(
VoiceMessageState.Button.Pause,
progress = 0.2f,
time = "10:00",
eventSink = {}
),
VoiceMessageState(
aVoiceMessageState(
VoiceMessageState.Button.Disabled,
progress = 0.2f,
time = "30:00",
eventSink = {}
),
)
}
fun aVoiceMessageState(
button: VoiceMessageState.Button = VoiceMessageState.Button.Play,
progress: Float = 0f,
time: String = "1:00",
) = VoiceMessageState(
button = button,
progress = progress,
time = time,
eventSink = {},
)

View file

@ -18,7 +18,7 @@ package io.element.android.libraries.architecture
import androidx.compose.runtime.Composable
interface Presenter<State> {
fun interface Presenter<State> {
@Composable
fun present(): State
}

View file

@ -20,5 +20,5 @@ import java.time.Duration
data class AudioDetails(
val duration: Duration,
val waveform: List<Int>,
val waveform: List<Float>,
)

View file

@ -195,7 +195,7 @@ interface MatrixRoom : Closeable {
suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Int>,
waveform: List<Float>,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>

View file

@ -21,10 +21,26 @@ import org.matrix.rustcomponents.sdk.UnstableAudioDetailsContent as RustAudioDet
fun RustAudioDetails.map(): AudioDetails = AudioDetails(
duration = duration,
waveform = waveform.map { it.toInt() },
waveform = waveform.fromMSC3246range(),
)
fun AudioDetails.map(): RustAudioDetails = RustAudioDetails(
duration = duration,
waveform = waveform.map { it.toUShort() }
waveform = waveform.toMSC3246range()
)
/**
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
* to a [0;1] float list to be used for waveform rendering.
*
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
*/
internal fun List<UShort>.fromMSC3246range(): List<Float> = map { it.toInt() / 1024f }
/**
* Resizes the given [0;1] float list as per unstable MSC3246 spec
* to a [0;1024] int list to be used for waveform rendering.
*
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
*/
internal fun List<Float>.toMSC3246range(): List<UShort> = map { (it * 1024).toInt().toUShort() }

View file

@ -46,6 +46,7 @@ import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
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.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.location.toInner
@ -499,13 +500,13 @@ class RustMatrixRoom(
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Int>,
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
innerRoom.sendVoiceMessage(
url = file.path,
audioInfo = audioInfo.map(),
waveform = waveform.map { it.toUShort() },
waveform = waveform.toMSC3246range(),
progressWatcher = progressCallback?.toProgressWatcher(),
)
}

View file

@ -387,7 +387,7 @@ class FakeMatrixRoom(
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Int>,
waveform: List<Float>,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler> = fakeSendMedia(progressCallback)

View file

@ -55,7 +55,7 @@ class MediaSender @Inject constructor(
suspend fun sendVoiceMessage(
uri: Uri,
mimeType: String,
waveForm: List<Int>,
waveForm: List<Float>,
progressCallback: ProgressCallback? = null
): Result<Unit> {
return preProcessor

View file

@ -29,6 +29,6 @@ sealed interface MediaUploadInfo {
data class Image(override val file: File, val imageInfo: ImageInfo, val thumbnailFile: File) : MediaUploadInfo
data class Video(override val file: File, val videoInfo: VideoInfo, val thumbnailFile: File) : MediaUploadInfo
data class Audio(override val file: File, val audioInfo: AudioInfo) : MediaUploadInfo
data class VoiceMessage(override val file: File, val audioInfo: AudioInfo, val waveform: List<Int>) : MediaUploadInfo
data class VoiceMessage(override val file: File, val audioInfo: AudioInfo, val waveform: List<Float>) : MediaUploadInfo
data class AnyFile(override val file: File, val fileInfo: FileInfo) : MediaUploadInfo
}

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f531805df6b1b7541e1c4d443979cb5434d3ea6c688725debc7f8845abebccf
size 50375
oid sha256:ee02c20c1ab0b514e12461c95428ae89985ae86fab9e8538eb2301eeaa910603
size 347767

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d98aac1065f283b232c96f5a0032865e81bca8816bd235615e131d4bb104a9a5
size 67474
oid sha256:1fc3c477b1ce2c74b32cce19bb94c61f49bcf01f751b136c0ff75d573bc66a35
size 80231

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2b06025fe43c1cd3f39bcee49222952f3750d0cf7ab2b9281716fcc670ea6934
size 57499
oid sha256:7a121baf52bf6997fa6dcfeee9ce77e6d0f3b374227addae4a28305551a6914f
size 53893

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c433475dd2eb980e95ef67bf04c2465db1449bf34b3c0eca7f8c6f43b35113e
size 66532

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f531805df6b1b7541e1c4d443979cb5434d3ea6c688725debc7f8845abebccf
size 50375

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d98aac1065f283b232c96f5a0032865e81bca8816bd235615e131d4bb104a9a5
size 67474

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2b06025fe43c1cd3f39bcee49222952f3750d0cf7ab2b9281716fcc670ea6934
size 57499

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f14b44ab65ac39c868de2c28644f886923fb4058f29a0d5b12dca7b6d978238
size 393377
oid sha256:eea033f081eb0b62671b3b9c5a626a4d1bfb0b386ff0a8e0011effc40858add3
size 74258

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee02c20c1ab0b514e12461c95428ae89985ae86fab9e8538eb2301eeaa910603
size 347767
oid sha256:fc7461871275f11e5ee7abbb6dafaecf7b2c6eac38b3a538ac80b6b722aa0e16
size 105810

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a121baf52bf6997fa6dcfeee9ce77e6d0f3b374227addae4a28305551a6914f
size 53893
oid sha256:a4dd52ecde13c76639225b482238924485b8cdbbc56f1b270254689f06cae7e8
size 58016

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c433475dd2eb980e95ef67bf04c2465db1449bf34b3c0eca7f8c6f43b35113e
size 66532
oid sha256:4f14b44ab65ac39c868de2c28644f886923fb4058f29a0d5b12dca7b6d978238
size 393377

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93482a9053b5efc9331457b907964409e85855e4988358df6a01f1bea7ff1b9f
size 48797
oid sha256:1b1164272dc31a867c7dbc2bae37ac960168f31da63b233531d509583ea0cea2
size 178173

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70e0a380cca7ba52a3b695b82c31e68c82c1e03ad6364e6ba73e84f070295528
size 64748
oid sha256:eafc87ff92c2d9b1e50bb97251a1bba132afdd6f6ec6769146c3e322ecd95da6
size 79207

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bab28935e51e099811884a707858e5f463bb591a8a2766a276044c0dbbae92a1
size 55244
oid sha256:bd5f7838b304537265aa2616fd3b96db72346e963a785aa683826870e1a0190a
size 51920

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9157f4531a208f2605bfc93314a669d188904b43b2f29bfd154786a50607d8db
size 63928

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93482a9053b5efc9331457b907964409e85855e4988358df6a01f1bea7ff1b9f
size 48797

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70e0a380cca7ba52a3b695b82c31e68c82c1e03ad6364e6ba73e84f070295528
size 64748

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bab28935e51e099811884a707858e5f463bb591a8a2766a276044c0dbbae92a1
size 55244

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5cf70a69d194c8d297e03ee75be7659d06515c9698751181bdfc8f9da7da6055
size 189520
oid sha256:c475d64ef991b3438afd84d8b93d40eedc8060ed862e64523efc0458e6a2a477
size 71450

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b1164272dc31a867c7dbc2bae37ac960168f31da63b233531d509583ea0cea2
size 178173
oid sha256:b1eaf05f8e22a9b22f75d6dd28c1c9c004918f6e85cd60ee0ae245e43affccef
size 99788

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd5f7838b304537265aa2616fd3b96db72346e963a785aa683826870e1a0190a
size 51920
oid sha256:be817a2061d43e210c105b193f1baed6060c51f11d97f45d0593cb40ab7acd98
size 56403

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9157f4531a208f2605bfc93314a669d188904b43b2f29bfd154786a50607d8db
size 63928
oid sha256:5cf70a69d194c8d297e03ee75be7659d06515c9698751181bdfc8f9da7da6055
size 189520