Media: change the API
This commit is contained in:
parent
f99a29ef6d
commit
d64660237a
18 changed files with 242 additions and 212 deletions
|
|
@ -55,10 +55,10 @@ fun TimelineItemImageView(
|
||||||
.aspectRatio(content.aspectRatio),
|
.aspectRatio(content.aspectRatio),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
val isLoading = rememberSaveable(content.imageMeta) { mutableStateOf(true) }
|
val isLoading = rememberSaveable(content.mediaRequestData) { mutableStateOf(true) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val model = ImageRequest.Builder(context)
|
val model = ImageRequest.Builder(context)
|
||||||
.data(content.imageMeta)
|
.data(content.mediaRequestData)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,12 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
|
||||||
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||||
|
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineItemContentMessageFactory @Inject constructor() {
|
class TimelineItemContentMessageFactory @Inject constructor() {
|
||||||
|
|
@ -49,9 +49,9 @@ class TimelineItemContentMessageFactory @Inject constructor() {
|
||||||
}
|
}
|
||||||
TimelineItemImageContent(
|
TimelineItemImageContent(
|
||||||
body = messageType.body,
|
body = messageType.body,
|
||||||
imageMeta = MediaResolver.Meta(
|
mediaRequestData = MediaRequestData(
|
||||||
url = messageType.url,
|
url = messageType.url,
|
||||||
kind = MediaResolver.Kind.Content
|
kind = MediaRequestData.Kind.Content
|
||||||
),
|
),
|
||||||
blurhash = messageType.info?.blurhash,
|
blurhash = messageType.info?.blurhash,
|
||||||
aspectRatio = aspectRatio
|
aspectRatio = aspectRatio
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.model.event
|
package io.element.android.features.messages.impl.timeline.model.event
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||||
|
|
||||||
data class TimelineItemImageContent(
|
data class TimelineItemImageContent(
|
||||||
val body: String,
|
val body: String,
|
||||||
val imageMeta: MediaResolver.Meta,
|
val mediaRequestData: MediaRequestData,
|
||||||
val blurhash: String?,
|
val blurhash: String?,
|
||||||
val aspectRatio: Float
|
val aspectRatio: Float
|
||||||
) : TimelineItemEventContent
|
) : TimelineItemEventContent
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.messages.impl.timeline.model.event
|
package io.element.android.features.messages.impl.timeline.model.event
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||||
|
|
||||||
open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineItemImageContent> {
|
open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineItemImageContent> {
|
||||||
override val values: Sequence<TimelineItemImageContent>
|
override val values: Sequence<TimelineItemImageContent>
|
||||||
|
|
@ -30,7 +30,7 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineI
|
||||||
|
|
||||||
fun aTimelineItemImageContent() = TimelineItemImageContent(
|
fun aTimelineItemImageContent() = TimelineItemImageContent(
|
||||||
body = "a body",
|
body = "a body",
|
||||||
imageMeta = MediaResolver.Meta(url = null, kind = MediaResolver.Kind.Content),
|
mediaRequestData = MediaRequestData(url = "", kind = MediaRequestData.Kind.Content),
|
||||||
blurhash = null,
|
blurhash = null,
|
||||||
aspectRatio = 0.5f,
|
aspectRatio = 0.5f,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -33,25 +33,19 @@ interface MatrixClient : Closeable {
|
||||||
val sessionId: SessionId
|
val sessionId: SessionId
|
||||||
val roomSummaryDataSource: RoomSummaryDataSource
|
val roomSummaryDataSource: RoomSummaryDataSource
|
||||||
val invitesDataSource: RoomSummaryDataSource
|
val invitesDataSource: RoomSummaryDataSource
|
||||||
|
val mediaLoader: MatrixMediaLoader
|
||||||
fun getRoom(roomId: RoomId): MatrixRoom?
|
fun getRoom(roomId: RoomId): MatrixRoom?
|
||||||
fun findDM(userId: UserId): MatrixRoom?
|
fun findDM(userId: UserId): MatrixRoom?
|
||||||
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
|
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
|
||||||
suspend fun createDM(userId: UserId): Result<RoomId>
|
suspend fun createDM(userId: UserId): Result<RoomId>
|
||||||
fun startSync()
|
fun startSync()
|
||||||
fun stopSync()
|
fun stopSync()
|
||||||
fun mediaResolver(): MediaResolver
|
|
||||||
fun sessionVerificationService(): SessionVerificationService
|
fun sessionVerificationService(): SessionVerificationService
|
||||||
fun pushersService(): PushersService
|
fun pushersService(): PushersService
|
||||||
fun notificationService(): NotificationService
|
fun notificationService(): NotificationService
|
||||||
suspend fun logout()
|
suspend fun logout()
|
||||||
suspend fun loadUserDisplayName(): Result<String>
|
suspend fun loadUserDisplayName(): Result<String>
|
||||||
suspend fun loadUserAvatarURLString(): Result<String?>
|
suspend fun loadUserAvatarURLString(): Result<String?>
|
||||||
suspend fun loadMediaContent(url: String): Result<ByteArray>
|
|
||||||
suspend fun loadMediaThumbnail(
|
|
||||||
url: String,
|
|
||||||
width: Long,
|
|
||||||
height: Long
|
|
||||||
): Result<ByteArray>
|
|
||||||
|
|
||||||
fun onSlidingSyncUpdate()
|
fun onSlidingSyncUpdate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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.libraries.matrix.api.media
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
interface MatrixMediaLoader {
|
||||||
|
/**
|
||||||
|
* @param url to fetch the content for.
|
||||||
|
* @return a [Result] of ByteArray. It contains the binary data for the media.
|
||||||
|
*/
|
||||||
|
suspend fun loadMediaContent(url: String): Result<ByteArray>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url to fetch the data for.
|
||||||
|
* @param width: the desired width for rescaling the media as thumbnail
|
||||||
|
* @param height: the desired height for rescaling the media as thumbnail
|
||||||
|
* @return a [Result] of ByteArray. It contains the binary data for the media.
|
||||||
|
*/
|
||||||
|
suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result<ByteArray>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url to fetch the data for.
|
||||||
|
* @param mimeType: optional mime type
|
||||||
|
* @return a [Result] of [Path]. It's the path to the downloaded file.
|
||||||
|
*/
|
||||||
|
suspend fun loadMediaFile(url: String, mimeType: String?): Result<Path>
|
||||||
|
}
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2022 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.libraries.matrix.api.media
|
|
||||||
|
|
||||||
interface MediaResolver {
|
|
||||||
|
|
||||||
sealed interface Kind {
|
|
||||||
data class Thumbnail(val width: Int, val height: Int) : Kind {
|
|
||||||
constructor(size: Int) : this(size, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
object Content : Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Meta(
|
|
||||||
val url: String?,
|
|
||||||
val kind: Kind
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun resolve(url: String?, kind: Kind): Result<ByteArray>
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -23,14 +23,14 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.impl.media.RustMediaResolver
|
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
|
||||||
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
|
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
|
||||||
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
|
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
|
||||||
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
|
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
|
||||||
|
|
@ -53,7 +53,6 @@ import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder
|
||||||
import org.matrix.rustcomponents.sdk.SlidingSyncMode
|
import org.matrix.rustcomponents.sdk.SlidingSyncMode
|
||||||
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
|
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
|
||||||
import org.matrix.rustcomponents.sdk.TaskHandle
|
import org.matrix.rustcomponents.sdk.TaskHandle
|
||||||
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
|
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -172,9 +171,12 @@ class RustMatrixClient constructor(
|
||||||
override val invitesDataSource: RoomSummaryDataSource
|
override val invitesDataSource: RoomSummaryDataSource
|
||||||
get() = rustInvitesDataSource
|
get() = rustInvitesDataSource
|
||||||
|
|
||||||
|
private val rustMediaLoader = RustMediaLoader(dispatchers, client)
|
||||||
|
override val mediaLoader: MatrixMediaLoader
|
||||||
|
get() = rustMediaLoader
|
||||||
|
|
||||||
private var slidingSyncObserverToken: TaskHandle? = null
|
private var slidingSyncObserverToken: TaskHandle? = null
|
||||||
|
|
||||||
private val mediaResolver = RustMediaResolver(this)
|
|
||||||
private val isSyncing = AtomicBoolean(false)
|
private val isSyncing = AtomicBoolean(false)
|
||||||
|
|
||||||
private val roomMembershipObserver = RoomMembershipObserver()
|
private val roomMembershipObserver = RoomMembershipObserver()
|
||||||
|
|
@ -254,8 +256,6 @@ class RustMatrixClient constructor(
|
||||||
return createRoom(createRoomParams)
|
return createRoom(createRoomParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mediaResolver(): MediaResolver = mediaResolver
|
|
||||||
|
|
||||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||||
|
|
||||||
override fun pushersService(): PushersService = pushersService
|
override fun pushersService(): PushersService = pushersService
|
||||||
|
|
@ -310,34 +310,6 @@ class RustMatrixClient constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
|
||||||
override suspend fun loadMediaContent(url: String): Result<ByteArray> =
|
|
||||||
withContext(dispatchers.io) {
|
|
||||||
runCatching {
|
|
||||||
mediaSourceFromUrl(url).use { source ->
|
|
||||||
client.getMediaContent(source).toUByteArray().toByteArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
|
||||||
override suspend fun loadMediaThumbnail(
|
|
||||||
url: String,
|
|
||||||
width: Long,
|
|
||||||
height: Long
|
|
||||||
): Result<ByteArray> =
|
|
||||||
withContext(dispatchers.io) {
|
|
||||||
runCatching {
|
|
||||||
mediaSourceFromUrl(url).use { mediaSource ->
|
|
||||||
client.getMediaThumbnail(
|
|
||||||
mediaSource = mediaSource,
|
|
||||||
width = width.toULong(),
|
|
||||||
height = height.toULong()
|
|
||||||
).toUByteArray().toByteArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSlidingSyncUpdate() {
|
override fun onSlidingSyncUpdate() {
|
||||||
if (!verificationService.isReady.value) {
|
if (!verificationService.isReady.value) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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.libraries.matrix.impl.media
|
||||||
|
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.rustcomponents.sdk.Client
|
||||||
|
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
|
||||||
|
import org.matrix.rustcomponents.sdk.use
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
|
class RustMediaLoader(
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
|
private val innerClient: Client
|
||||||
|
) : MatrixMediaLoader {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
override suspend fun loadMediaContent(url: String): Result<ByteArray> =
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
mediaSourceFromUrl(url).use { source ->
|
||||||
|
innerClient.getMediaContent(source).toUByteArray().toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
override suspend fun loadMediaThumbnail(
|
||||||
|
url: String,
|
||||||
|
width: Long,
|
||||||
|
height: Long
|
||||||
|
): Result<ByteArray> =
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
mediaSourceFromUrl(url).use { mediaSource ->
|
||||||
|
innerClient.getMediaThumbnail(
|
||||||
|
mediaSource = mediaSource,
|
||||||
|
width = width.toULong(),
|
||||||
|
height = height.toULong()
|
||||||
|
).toUByteArray().toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadMediaFile(url: String, mimeType: String?): Result<Path> =
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
mediaSourceFromUrl(url).use { mediaSource ->
|
||||||
|
innerClient.getMediaFile(
|
||||||
|
source = mediaSource,
|
||||||
|
mimeType = mimeType ?: "application/octet-stream"
|
||||||
|
).use {
|
||||||
|
Path(it.path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,36 +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.libraries.matrix.impl.media
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
|
||||||
import java.lang.IllegalStateException
|
|
||||||
|
|
||||||
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
|
|
||||||
|
|
||||||
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): Result<ByteArray> {
|
|
||||||
if (url.isNullOrEmpty()) return Result.failure(IllegalStateException("The url is null or empty"))
|
|
||||||
return when (kind) {
|
|
||||||
is MediaResolver.Kind.Content -> client.loadMediaContent(url)
|
|
||||||
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnail(
|
|
||||||
url,
|
|
||||||
kind.width.toLong(),
|
|
||||||
kind.height.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,14 +21,14 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.test.media.FakeMediaResolver
|
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
||||||
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
|
|
@ -42,6 +42,7 @@ class FakeMatrixClient(
|
||||||
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
|
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
|
||||||
override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
||||||
override val invitesDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
override val invitesDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
||||||
|
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
|
||||||
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||||
private val pushersService: FakePushersService = FakePushersService(),
|
private val pushersService: FakePushersService = FakePushersService(),
|
||||||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||||
|
|
@ -77,10 +78,6 @@ class FakeMatrixClient(
|
||||||
|
|
||||||
override fun stopSync() = Unit
|
override fun stopSync() = Unit
|
||||||
|
|
||||||
override fun mediaResolver(): MediaResolver {
|
|
||||||
return FakeMediaResolver()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun logout() {
|
override suspend fun logout() {
|
||||||
delay(100)
|
delay(100)
|
||||||
logoutFailure?.let { throw it }
|
logoutFailure?.let { throw it }
|
||||||
|
|
@ -96,14 +93,6 @@ class FakeMatrixClient(
|
||||||
return userAvatarURLString
|
return userAvatarURLString
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadMediaContent(url: String): Result<ByteArray> {
|
|
||||||
return Result.success(ByteArray(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result<ByteArray> {
|
|
||||||
return Result.success(ByteArray(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
||||||
|
|
||||||
override fun pushersService(): PushersService = pushersService
|
override fun pushersService(): PushersService = pushersService
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.libraries.matrix.test.media
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
|
class FakeMediaLoader : MatrixMediaLoader {
|
||||||
|
|
||||||
|
var shouldFail = false
|
||||||
|
|
||||||
|
override suspend fun loadMediaContent(url: String): Result<ByteArray> {
|
||||||
|
return if (shouldFail) {
|
||||||
|
Result.failure(RuntimeException())
|
||||||
|
} else {
|
||||||
|
return Result.success(ByteArray(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result<ByteArray> {
|
||||||
|
return if (shouldFail) {
|
||||||
|
Result.failure(RuntimeException())
|
||||||
|
} else {
|
||||||
|
return Result.success(ByteArray(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadMediaFile(url: String, mimeType: String?): Result<Path> {
|
||||||
|
return if (shouldFail) {
|
||||||
|
Result.failure(RuntimeException())
|
||||||
|
} else {
|
||||||
|
return Result.success(Path("path"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +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.libraries.matrix.test.media
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
|
||||||
|
|
||||||
class FakeMediaResolver : MediaResolver {
|
|
||||||
|
|
||||||
private var result: Result<ByteArray> = Result.success(ByteArray(0))
|
|
||||||
|
|
||||||
fun givenResult(result: Result<ByteArray>) {
|
|
||||||
this.result = result
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): Result<ByteArray> {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,9 +17,13 @@
|
||||||
package io.element.android.libraries.matrix.ui.media
|
package io.element.android.libraries.matrix.ui.media
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import kotlin.math.roundToLong
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
fun AvatarData.toMetadata(): MediaResolver.Meta {
|
fun AvatarData.toMediaRequestData(): MediaRequestData? {
|
||||||
return MediaResolver.Meta(url = url, kind = MediaResolver.Kind.Thumbnail(size.dp.value.roundToInt()))
|
return url?.let {
|
||||||
|
MediaRequestData(
|
||||||
|
url = it,
|
||||||
|
kind = MediaRequestData.Kind.Thumbnail(size.dp.value.roundToLong())
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -22,20 +22,20 @@ import coil.fetch.Fetcher
|
||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
internal class MediaFetcher(
|
internal class CoilMediaFetcher(
|
||||||
private val mediaResolver: MediaResolver,
|
private val mediaLoader: MatrixMediaLoader,
|
||||||
private val meta: MediaResolver.Meta,
|
private val mediaData: MediaRequestData?,
|
||||||
private val options: Options,
|
private val options: Options,
|
||||||
private val imageLoader: ImageLoader
|
private val imageLoader: ImageLoader
|
||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
|
|
||||||
override suspend fun fetch(): FetchResult? {
|
override suspend fun fetch(): FetchResult? {
|
||||||
return mediaResolver.resolve(meta.url, meta.kind)
|
return loadMedia()
|
||||||
.map { byteArray ->
|
.map { data ->
|
||||||
ByteBuffer.wrap(byteArray)
|
ByteBuffer.wrap(data)
|
||||||
}.map { byteBuffer ->
|
}.map { byteBuffer ->
|
||||||
imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
|
imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
|
||||||
}
|
}
|
||||||
|
|
@ -45,16 +45,28 @@ internal class MediaFetcher(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetaFactory(private val client: MatrixClient) :
|
private suspend fun loadMedia(): Result<ByteArray> {
|
||||||
Fetcher.Factory<MediaResolver.Meta> {
|
if (mediaData == null) return Result.failure(IllegalStateException("No media data to fetch."))
|
||||||
|
return when (mediaData.kind) {
|
||||||
|
is MediaRequestData.Kind.Content -> mediaLoader.loadMediaContent(url = mediaData.url)
|
||||||
|
is MediaRequestData.Kind.Thumbnail -> mediaLoader.loadMediaThumbnail(
|
||||||
|
url = mediaData.url,
|
||||||
|
width = mediaData.kind.width,
|
||||||
|
height = mediaData.kind.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaRequestDataFactory(private val client: MatrixClient) :
|
||||||
|
Fetcher.Factory<MediaRequestData> {
|
||||||
override fun create(
|
override fun create(
|
||||||
data: MediaResolver.Meta,
|
data: MediaRequestData,
|
||||||
options: Options,
|
options: Options,
|
||||||
imageLoader: ImageLoader
|
imageLoader: ImageLoader
|
||||||
): Fetcher {
|
): Fetcher {
|
||||||
return MediaFetcher(
|
return CoilMediaFetcher(
|
||||||
mediaResolver = client.mediaResolver(),
|
mediaLoader = client.mediaLoader,
|
||||||
meta = data,
|
mediaData = data,
|
||||||
options = options,
|
options = options,
|
||||||
imageLoader = imageLoader
|
imageLoader = imageLoader
|
||||||
)
|
)
|
||||||
|
|
@ -63,14 +75,15 @@ internal class MediaFetcher(
|
||||||
|
|
||||||
class AvatarFactory(private val client: MatrixClient) :
|
class AvatarFactory(private val client: MatrixClient) :
|
||||||
Fetcher.Factory<AvatarData> {
|
Fetcher.Factory<AvatarData> {
|
||||||
|
|
||||||
override fun create(
|
override fun create(
|
||||||
data: AvatarData,
|
data: AvatarData,
|
||||||
options: Options,
|
options: Options,
|
||||||
imageLoader: ImageLoader
|
imageLoader: ImageLoader
|
||||||
): Fetcher {
|
): Fetcher {
|
||||||
return MediaFetcher(
|
return CoilMediaFetcher(
|
||||||
mediaResolver = client.mediaResolver(),
|
mediaLoader = client.mediaLoader,
|
||||||
meta = data.toMetadata(),
|
mediaData = data.toMediaRequestData(),
|
||||||
options = options,
|
options = options,
|
||||||
imageLoader = imageLoader
|
imageLoader = imageLoader
|
||||||
)
|
)
|
||||||
|
|
@ -34,10 +34,10 @@ class LoggedInImageLoaderFactory @Inject constructor(
|
||||||
.Builder(context)
|
.Builder(context)
|
||||||
.okHttpClient(okHttpClient)
|
.okHttpClient(okHttpClient)
|
||||||
.components {
|
.components {
|
||||||
add(AvatarKeyer())
|
add(AvatarDataKeyer())
|
||||||
add(MediaKeyer())
|
add(MediaRequestDataKeyer())
|
||||||
add(MediaFetcher.AvatarFactory(matrixClient))
|
add(CoilMediaFetcher.AvatarFactory(matrixClient))
|
||||||
add(MediaFetcher.MetaFactory(matrixClient))
|
add(CoilMediaFetcher.MediaRequestDataFactory(matrixClient))
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022 New Vector Ltd
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -14,23 +14,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.media
|
package io.element.android.libraries.matrix.ui.media
|
||||||
|
|
||||||
interface MediaResolver {
|
data class MediaRequestData(
|
||||||
|
val url: String,
|
||||||
|
val kind: Kind
|
||||||
|
) {
|
||||||
|
|
||||||
sealed interface Kind {
|
sealed interface Kind {
|
||||||
data class Thumbnail(val width: Int, val height: Int) : Kind {
|
data class Thumbnail(val width: Long, val height: Long) : Kind {
|
||||||
constructor(size: Int) : this(size, size)
|
constructor(size: Long) : this(size, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Content : Kind
|
object Content : Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Meta(
|
|
||||||
val url: String?,
|
|
||||||
val kind: Kind
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun resolve(url: String?, kind: Kind): ByteArray?
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,18 +19,17 @@ package io.element.android.libraries.matrix.ui.media
|
||||||
import coil.key.Keyer
|
import coil.key.Keyer
|
||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
|
||||||
|
|
||||||
internal class AvatarKeyer : Keyer<AvatarData> {
|
internal class AvatarDataKeyer : Keyer<AvatarData> {
|
||||||
override fun key(data: AvatarData, options: Options): String? {
|
override fun key(data: AvatarData, options: Options): String? {
|
||||||
return data.toMetadata().toKey()
|
return data.toMediaRequestData()?.toKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MediaKeyer : Keyer<MediaResolver.Meta> {
|
internal class MediaRequestDataKeyer : Keyer<MediaRequestData> {
|
||||||
override fun key(data: MediaResolver.Meta, options: Options): String? {
|
override fun key(data: MediaRequestData, options: Options): String? {
|
||||||
return data.toKey()
|
return data.toKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MediaResolver.Meta.toKey() = "${url}_${kind}"
|
private fun MediaRequestData.toKey() = "${url}_${kind}"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue