From 188ef3762d7c249a52abf1a1d9baa7c9fff797a0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 2 Oct 2023 17:31:34 -0400 Subject: [PATCH] Allows Base64 images on profiles. --- .../ui/components/RobohashAsyncImage.kt | 105 +++++++++++++++--- 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index 6da68fa4d..1e2a160d1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -1,7 +1,13 @@ package com.vitorpamplona.amethyst.ui.components +import android.content.Context +import android.graphics.BitmapFactory +import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -11,9 +17,19 @@ import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.core.graphics.drawable.toDrawable +import coil.ImageLoader import coil.compose.AsyncImage import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter +import coil.decode.DataSource +import coil.fetch.DrawableResult +import coil.fetch.FetchResult +import coil.fetch.Fetcher +import coil.request.ImageRequest +import coil.request.Options +import com.vitorpamplona.amethyst.service.checkNotInMainThread +import java.util.Base64 @Composable fun RobohashAsyncImage( @@ -67,19 +83,40 @@ fun RobohashFallbackAsyncImage( ) if (model != null && loadProfilePicture) { - AsyncImage( - model = model, - contentDescription = contentDescription, - modifier = modifier, - placeholder = painter, - fallback = painter, - error = painter, - alignment = alignment, - contentScale = contentScale, - alpha = alpha, - colorFilter = colorFilter, - filterQuality = filterQuality - ) + val isBase64 by remember { + derivedStateOf { + model.startsWith("data:image/jpeg;base64,") + } + } + + if (isBase64) { + val painter = rememberAsyncImagePainter( + model = Base64Requester.imageRequest(context, model) + ) + + Image( + painter = painter, + contentDescription = null, + modifier = modifier, + alignment = alignment, + contentScale = contentScale, + colorFilter = colorFilter + ) + } else { + AsyncImage( + model = model, + contentDescription = contentDescription, + modifier = modifier, + placeholder = painter, + fallback = painter, + error = painter, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality + ) + } } else { Image( painter = painter, @@ -118,3 +155,45 @@ fun RobohashAsyncImageProxy( loadProfilePicture = loadProfilePicture ) } + +object Base64Requester { + fun imageRequest(context: Context, message: String): ImageRequest { + return ImageRequest + .Builder(context) + .data(message) + .fetcherFactory(Base64Fetcher.Factory) + .build() + } +} + +@Stable +class Base64Fetcher( + private val options: Options, + private val data: Uri +) : Fetcher { + + override suspend fun fetch(): FetchResult { + checkNotInMainThread() + + val base64String = data.toString().removePrefix("data:image/jpeg;base64,") + + val byteArray = Base64.getDecoder().decode(base64String) + val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + + if (bitmap == null) { + throw Exception("Unable to load base64 $base64String") + } + + return DrawableResult( + drawable = bitmap.toDrawable(options.context.resources), + isSampled = false, + dataSource = DataSource.MEMORY + ) + } + + object Factory : Fetcher.Factory { + override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher { + return Base64Fetcher(options, data) + } + } +}