Try using custom fetcher for Coil...

This commit is contained in:
ganfra 2022-11-21 20:15:11 +01:00
parent 0afc1bb334
commit 8f03957c23
10 changed files with 118 additions and 11 deletions

View file

@ -95,6 +95,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.1' implementation 'androidx.activity:activity-compose:1.6.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
implementation 'io.coil-kt:coil:2.2.1'
implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'com.jakewharton.timber:timber:5.0.1'
debugImplementation "androidx.compose.ui:ui-tooling" debugImplementation "androidx.compose.ui:ui-tooling"
debugImplementation "androidx.compose.ui:ui-test-manifest" debugImplementation "androidx.compose.ui:ui-test-manifest"

View file

@ -1,14 +1,17 @@
package io.element.android.x package io.element.android.x
import android.app.Application import android.app.Application
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import io.element.android.x.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
import io.element.android.x.matrix.media.MediaFetcher
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import timber.log.Timber import timber.log.Timber
class ElementXApplication : Application() { class ElementXApplication : Application(), ImageLoaderFactory {
private val applicationScope = MainScope() + CoroutineName("ElementX Scope") private val applicationScope = MainScope() + CoroutineName("ElementX Scope")
@ -18,4 +21,13 @@ class ElementXApplication : Application() {
MatrixInstance.init(this, applicationScope) MatrixInstance.init(this, applicationScope)
Mavericks.initialize(this) Mavericks.initialize(this)
} }
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(this)
.components {
add(MediaFetcher.Factory(MatrixInstance.getInstance()))
}
.build()
}
} }

View file

@ -12,6 +12,7 @@ dependencies {
implementation(project(":libraries:designsystem")) implementation(project(":libraries:designsystem"))
implementation(project(":libraries:textcomposer")) implementation(project(":libraries:textcomposer"))
implementation(libs.mavericks.compose) implementation(libs.mavericks.compose)
implementation(libs.coil.compose)
implementation(libs.timber) implementation(libs.timber)
implementation(libs.datetime) implementation(libs.datetime)
implementation(libs.accompanist.flowlayout) implementation(libs.accompanist.flowlayout)

View file

@ -87,7 +87,16 @@ class MessageTimelineItemStateMapper(
body = messageType.content.body, body = messageType.content.body,
formattedBody = messageType.content.formatted formattedBody = messageType.content.formatted
) )
is MessageType.Image -> MessagesTimelineItemUnknownContent is MessageType.Image -> {
MessagesTimelineItemImageContent(
body = messageType.content.body,
imageMeta = MediaResolver.Meta(
source = messageType.content.source,
kind = MediaResolver.Kind.Content
),
blurhash = messageType.content.info?.blurhash
)
}
is MessageType.Notice -> MessagesTimelineItemNoticeContent( is MessageType.Notice -> MessagesTimelineItemNoticeContent(
body = messageType.content.body, body = messageType.content.body,
formattedBody = messageType.content.formatted formattedBody = messageType.content.formatted

View file

@ -43,10 +43,7 @@ import io.element.android.x.features.messages.components.*
import io.element.android.x.features.messages.model.MessagesItemGroupPosition import io.element.android.x.features.messages.model.MessagesItemGroupPosition
import io.element.android.x.features.messages.model.MessagesTimelineItemState import io.element.android.x.features.messages.model.MessagesTimelineItemState
import io.element.android.x.features.messages.model.MessagesViewState import io.element.android.x.features.messages.model.MessagesViewState
import io.element.android.x.features.messages.model.content.MessagesTimelineItemEncryptedContent import io.element.android.x.features.messages.model.content.*
import io.element.android.x.features.messages.model.content.MessagesTimelineItemRedactedContent
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
import io.element.android.x.features.messages.model.content.MessagesTimelineItemUnknownContent
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
import io.element.android.x.features.messages.textcomposer.MessageComposerViewState import io.element.android.x.features.messages.textcomposer.MessageComposerViewState
import io.element.android.x.textcomposer.TextComposer import io.element.android.x.textcomposer.TextComposer
@ -302,6 +299,10 @@ fun MessageEventRow(
content = messageEvent.content, content = messageEvent.content,
modifier = contentModifier modifier = contentModifier
) )
is MessagesTimelineItemImageContent -> MessagesTimelineItemImageView(
content = messageEvent.content,
modifier = contentModifier
)
} }
} }
MessagesReactionsView( MessagesReactionsView(

View file

@ -0,0 +1,23 @@
package io.element.android.x.features.messages.components
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import coil.compose.AsyncImage
import io.element.android.x.features.messages.model.content.MessagesTimelineItemImageContent
@Composable
fun MessagesTimelineItemImageView(
content: MessagesTimelineItemImageContent,
modifier: Modifier = Modifier
) {
Box(modifier) {
AsyncImage(
model = content.imageMeta,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = modifier
)
}
}

View file

@ -0,0 +1,9 @@
package io.element.android.x.features.messages.model.content
import io.element.android.x.matrix.media.MediaResolver
data class MessagesTimelineItemImageContent(
val body: String,
val imageMeta: MediaResolver.Meta,
val blurhash: String?
) : MessagesTimelineItemContent

View file

@ -12,6 +12,7 @@ dependencies {
implementation(project(":libraries:core")) implementation(project(":libraries:core"))
implementation(libs.timber) implementation(libs.timber)
implementation("net.java.dev.jna:jna:5.10.0@aar") implementation("net.java.dev.jna:jna:5.10.0@aar")
implementation(libs.coil.compose)
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
implementation(libs.serialization.json) implementation(libs.serialization.json)
} }

View file

@ -0,0 +1,38 @@
package io.element.android.x.matrix.media
import coil.ImageLoader
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.Options
import io.element.android.x.matrix.Matrix
import java.nio.ByteBuffer
class MediaFetcher(
private val mediaResolver: MediaResolver,
private val meta: MediaResolver.Meta,
private val options: Options,
private val imageLoader: ImageLoader
) : Fetcher {
override suspend fun fetch(): FetchResult? {
val byteArray = mediaResolver.resolve(meta) ?: return null
val byteBuffer = ByteBuffer.wrap(byteArray)
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
}
class Factory(private val matrix: Matrix) : Fetcher.Factory<MediaResolver.Meta> {
override fun create(
data: MediaResolver.Meta,
options: Options,
imageLoader: ImageLoader
): Fetcher {
val activeClient = matrix.activeClient()
return MediaFetcher(
mediaResolver = activeClient.mediaResolver(),
meta = data,
options = options,
imageLoader = imageLoader
)
}
}
}

View file

@ -1,6 +1,7 @@
package io.element.android.x.matrix.media package io.element.android.x.matrix.media
import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.MatrixClient
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
interface MediaResolver { interface MediaResolver {
@ -13,7 +14,14 @@ interface MediaResolver {
object Content : Kind object Content : Kind
} }
data class Meta(
val source: MediaSource,
val kind: Kind
)
suspend fun resolve(url: String?, kind: Kind): ByteArray? suspend fun resolve(url: String?, kind: Kind): ByteArray?
suspend fun resolve(meta: Meta): ByteArray?
} }
@ -22,12 +30,16 @@ internal class RustMediaResolver(private val client: MatrixClient) : MediaResolv
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? { override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
if (url.isNullOrEmpty()) return null if (url.isNullOrEmpty()) return null
val mediaSource = mediaSourceFromUrl(url) val mediaSource = mediaSourceFromUrl(url)
return when (kind) { return resolve(MediaResolver.Meta(mediaSource, kind))
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(mediaSource) }
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
return when (meta.kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(meta.source)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource( is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
mediaSource, meta.source,
kind.width.toLong(), meta.kind.width.toLong(),
kind.height.toLong() meta.kind.height.toLong()
) )
}.getOrNull() }.getOrNull()
} }