Merge branch 'develop' of https://github.com/vector-im/element-x-android into langleyd/live_waveform
This commit is contained in:
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() } },
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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() }
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ import java.time.Duration
|
||||
|
||||
data class AudioDetails(
|
||||
val duration: Duration,
|
||||
val waveform: List<Int>,
|
||||
val waveform: List<Float>,
|
||||
)
|
||||
|
||||
@@ -195,7 +195,7 @@ interface MatrixRoom : Closeable {
|
||||
suspend fun sendVoiceMessage(
|
||||
file: File,
|
||||
audioInfo: AudioInfo,
|
||||
waveform: List<Int>,
|
||||
waveform: List<Float>,
|
||||
progressCallback: ProgressCallback?
|
||||
): Result<MediaUploadHandler>
|
||||
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user