Media: change the API
This commit is contained in:
parent
c920dfb97a
commit
4b5ca3acdd
18 changed files with 242 additions and 212 deletions
|
|
@ -55,10 +55,10 @@ fun TimelineItemImageView(
|
|||
.aspectRatio(content.aspectRatio),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val isLoading = rememberSaveable(content.imageMeta) { mutableStateOf(true) }
|
||||
val isLoading = rememberSaveable(content.mediaRequestData) { mutableStateOf(true) }
|
||||
val context = LocalContext.current
|
||||
val model = ImageRequest.Builder(context)
|
||||
.data(content.imageMeta)
|
||||
.data(content.mediaRequestData)
|
||||
.build()
|
||||
|
||||
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.TimelineItemUnknownContent
|
||||
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.ImageMessageType
|
||||
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.TextMessageType
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentMessageFactory @Inject constructor() {
|
||||
|
|
@ -49,9 +49,9 @@ class TimelineItemContentMessageFactory @Inject constructor() {
|
|||
}
|
||||
TimelineItemImageContent(
|
||||
body = messageType.body,
|
||||
imageMeta = MediaResolver.Meta(
|
||||
mediaRequestData = MediaRequestData(
|
||||
url = messageType.url,
|
||||
kind = MediaResolver.Kind.Content
|
||||
kind = MediaRequestData.Kind.Content
|
||||
),
|
||||
blurhash = messageType.info?.blurhash,
|
||||
aspectRatio = aspectRatio
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
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(
|
||||
val body: String,
|
||||
val imageMeta: MediaResolver.Meta,
|
||||
val mediaRequestData: MediaRequestData,
|
||||
val blurhash: String?,
|
||||
val aspectRatio: Float
|
||||
) : TimelineItemEventContent
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
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> {
|
||||
override val values: Sequence<TimelineItemImageContent>
|
||||
|
|
@ -30,7 +30,7 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineI
|
|||
|
||||
fun aTimelineItemImageContent() = TimelineItemImageContent(
|
||||
body = "a body",
|
||||
imageMeta = MediaResolver.Meta(url = null, kind = MediaResolver.Kind.Content),
|
||||
mediaRequestData = MediaRequestData(url = "", kind = MediaRequestData.Kind.Content),
|
||||
blurhash = null,
|
||||
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.UserId
|
||||
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.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
|
|
@ -33,25 +33,19 @@ interface MatrixClient : Closeable {
|
|||
val sessionId: SessionId
|
||||
val roomSummaryDataSource: RoomSummaryDataSource
|
||||
val invitesDataSource: RoomSummaryDataSource
|
||||
val mediaLoader: MatrixMediaLoader
|
||||
fun getRoom(roomId: RoomId): MatrixRoom?
|
||||
fun findDM(userId: UserId): MatrixRoom?
|
||||
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
|
||||
suspend fun createDM(userId: UserId): Result<RoomId>
|
||||
fun startSync()
|
||||
fun stopSync()
|
||||
fun mediaResolver(): MediaResolver
|
||||
fun sessionVerificationService(): SessionVerificationService
|
||||
fun pushersService(): PushersService
|
||||
fun notificationService(): NotificationService
|
||||
suspend fun logout()
|
||||
suspend fun loadUserDisplayName(): 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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.RoomPreset
|
||||
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.pusher.PushersService
|
||||
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.RoomSummaryDataSource
|
||||
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.pushers.RustPushersService
|
||||
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.SlidingSyncRequestListFilters
|
||||
import org.matrix.rustcomponents.sdk.TaskHandle
|
||||
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
|
@ -172,9 +171,12 @@ class RustMatrixClient constructor(
|
|||
override val invitesDataSource: RoomSummaryDataSource
|
||||
get() = rustInvitesDataSource
|
||||
|
||||
private val rustMediaLoader = RustMediaLoader(dispatchers, client)
|
||||
override val mediaLoader: MatrixMediaLoader
|
||||
get() = rustMediaLoader
|
||||
|
||||
private var slidingSyncObserverToken: TaskHandle? = null
|
||||
|
||||
private val mediaResolver = RustMediaResolver(this)
|
||||
private val isSyncing = AtomicBoolean(false)
|
||||
|
||||
private val roomMembershipObserver = RoomMembershipObserver()
|
||||
|
|
@ -254,8 +256,6 @@ class RustMatrixClient constructor(
|
|||
return createRoom(createRoomParams)
|
||||
}
|
||||
|
||||
override fun mediaResolver(): MediaResolver = mediaResolver
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||
|
||||
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() {
|
||||
if (!verificationService.isReady.value) {
|
||||
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.UserId
|
||||
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.pusher.PushersService
|
||||
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.RoomSummaryDataSource
|
||||
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.pushers.FakePushersService
|
||||
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),
|
||||
override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
||||
override val invitesDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(),
|
||||
override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(),
|
||||
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||
private val pushersService: FakePushersService = FakePushersService(),
|
||||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||
|
|
@ -77,10 +78,6 @@ class FakeMatrixClient(
|
|||
|
||||
override fun stopSync() = Unit
|
||||
|
||||
override fun mediaResolver(): MediaResolver {
|
||||
return FakeMediaResolver()
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
delay(100)
|
||||
logoutFailure?.let { throw it }
|
||||
|
|
@ -96,14 +93,6 @@ class FakeMatrixClient(
|
|||
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 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
|
||||
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
fun AvatarData.toMetadata(): MediaResolver.Meta {
|
||||
return MediaResolver.Meta(url = url, kind = MediaResolver.Kind.Thumbnail(size.dp.value.roundToInt()))
|
||||
fun AvatarData.toMediaRequestData(): MediaRequestData? {
|
||||
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 io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
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
|
||||
|
||||
internal class MediaFetcher(
|
||||
private val mediaResolver: MediaResolver,
|
||||
private val meta: MediaResolver.Meta,
|
||||
internal class CoilMediaFetcher(
|
||||
private val mediaLoader: MatrixMediaLoader,
|
||||
private val mediaData: MediaRequestData?,
|
||||
private val options: Options,
|
||||
private val imageLoader: ImageLoader
|
||||
) : Fetcher {
|
||||
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
return mediaResolver.resolve(meta.url, meta.kind)
|
||||
.map { byteArray ->
|
||||
ByteBuffer.wrap(byteArray)
|
||||
return loadMedia()
|
||||
.map { data ->
|
||||
ByteBuffer.wrap(data)
|
||||
}.map { byteBuffer ->
|
||||
imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
|
||||
}
|
||||
|
|
@ -45,16 +45,28 @@ internal class MediaFetcher(
|
|||
)
|
||||
}
|
||||
|
||||
class MetaFactory(private val client: MatrixClient) :
|
||||
Fetcher.Factory<MediaResolver.Meta> {
|
||||
private suspend fun loadMedia(): Result<ByteArray> {
|
||||
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(
|
||||
data: MediaResolver.Meta,
|
||||
data: MediaRequestData,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Fetcher {
|
||||
return MediaFetcher(
|
||||
mediaResolver = client.mediaResolver(),
|
||||
meta = data,
|
||||
return CoilMediaFetcher(
|
||||
mediaLoader = client.mediaLoader,
|
||||
mediaData = data,
|
||||
options = options,
|
||||
imageLoader = imageLoader
|
||||
)
|
||||
|
|
@ -63,14 +75,15 @@ internal class MediaFetcher(
|
|||
|
||||
class AvatarFactory(private val client: MatrixClient) :
|
||||
Fetcher.Factory<AvatarData> {
|
||||
|
||||
override fun create(
|
||||
data: AvatarData,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Fetcher {
|
||||
return MediaFetcher(
|
||||
mediaResolver = client.mediaResolver(),
|
||||
meta = data.toMetadata(),
|
||||
return CoilMediaFetcher(
|
||||
mediaLoader = client.mediaLoader,
|
||||
mediaData = data.toMediaRequestData(),
|
||||
options = options,
|
||||
imageLoader = imageLoader
|
||||
)
|
||||
|
|
@ -34,10 +34,10 @@ class LoggedInImageLoaderFactory @Inject constructor(
|
|||
.Builder(context)
|
||||
.okHttpClient(okHttpClient)
|
||||
.components {
|
||||
add(AvatarKeyer())
|
||||
add(MediaKeyer())
|
||||
add(MediaFetcher.AvatarFactory(matrixClient))
|
||||
add(MediaFetcher.MetaFactory(matrixClient))
|
||||
add(AvatarDataKeyer())
|
||||
add(MediaRequestDataKeyer())
|
||||
add(CoilMediaFetcher.AvatarFactory(matrixClient))
|
||||
add(CoilMediaFetcher.MediaRequestDataFactory(matrixClient))
|
||||
}
|
||||
.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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -14,23 +14,19 @@
|
|||
* 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 {
|
||||
data class Thumbnail(val width: Int, val height: Int) : Kind {
|
||||
constructor(size: Int) : this(size, size)
|
||||
data class Thumbnail(val width: Long, val height: Long) : Kind {
|
||||
constructor(size: Long) : this(size, size)
|
||||
}
|
||||
|
||||
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.request.Options
|
||||
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? {
|
||||
return data.toMetadata().toKey()
|
||||
return data.toMediaRequestData()?.toKey()
|
||||
}
|
||||
}
|
||||
|
||||
internal class MediaKeyer : Keyer<MediaResolver.Meta> {
|
||||
override fun key(data: MediaResolver.Meta, options: Options): String? {
|
||||
internal class MediaRequestDataKeyer : Keyer<MediaRequestData> {
|
||||
override fun key(data: MediaRequestData, options: Options): String? {
|
||||
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