Try using custom fetcher for Coil...

This commit is contained in:
ganfra
2022-11-21 20:15:11 +01:00
parent f9d389fe97
commit 1ba025ed30
10 changed files with 118 additions and 11 deletions
+1
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"
@@ -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()
}
} }
+1
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)
@@ -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
@@ -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(
@@ -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
)
}
}
@@ -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
+1
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)
} }
@@ -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
)
}
}
}
@@ -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()
} }