From ed5d6a06331dbd1f3787870fd66c9e5d99a30ddc Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 30 Jan 2024 04:30:56 +0100 Subject: [PATCH] Image refactors and improvements --- .../twom/composables/avatar/AvatarImage.kt | 4 +- .../twom/composables/avatar/AvatarPicker.kt | 9 +-- .../twom/composables/avatar/AvatarURL.kt | 56 +------------- .../twom/composables/avatar/AvatarUser.kt | 4 +- .../twom/composables/avatar/AvatarUserId.kt | 4 +- .../avatar/bitmapFromMatrixFile.kt | 76 +++++++++++++++++++ .../eu/steffo/twom/utils/BitmapUtilities.kt | 6 ++ 7 files changed, 99 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/eu/steffo/twom/composables/avatar/bitmapFromMatrixFile.kt diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarImage.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarImage.kt index d994e2e..f2c418a 100644 --- a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarImage.kt +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarImage.kt @@ -11,7 +11,9 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview @Composable -@Preview(widthDp = 40, heightDp = 40) +@Preview(name = "Regular", widthDp = 40, heightDp = 40) +@Preview(name = "Double font scale", widthDp = 40, heightDp = 40, fontScale = 2f) +@Preview(name = "Quadruple font scale", widthDp = 40, heightDp = 40, fontScale = 4f) fun AvatarImage( modifier: Modifier = Modifier, bitmap: ImageBitmap? = null, diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt index 51be5dd..66d8308 100644 --- a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarPicker.kt @@ -16,7 +16,9 @@ import eu.steffo.twom.utils.BitmapUtilities @OptIn(ExperimentalFoundationApi::class) @Composable -@Preview(widthDp = 40, heightDp = 40) +@Preview(name = "Regular", widthDp = 40, heightDp = 40) +@Preview(name = "Double font scale", widthDp = 40, heightDp = 40, fontScale = 2f) +@Preview(name = "Quadruple font scale", widthDp = 40, heightDp = 40, fontScale = 4f) fun AvatarPicker( modifier: Modifier = Modifier, fallbackText: String = "?", @@ -31,10 +33,7 @@ fun AvatarPicker( rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) ImageSelect@{ it ?: return@ImageSelect - val rawBitmap = BitmapUtilities.getRawBitmap(resolver, it) ?: return@ImageSelect - val orientation = BitmapUtilities.getOrientation(resolver, it) ?: return@ImageSelect - - val correctedBitmap = BitmapUtilities.squareAndOrient(rawBitmap, orientation) + val correctedBitmap = BitmapUtilities.getCorrectedBitmap(resolver, it) onPick(correctedBitmap) } diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarURL.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarURL.kt index 0781e3a..31f7623 100644 --- a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarURL.kt +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarURL.kt @@ -1,22 +1,14 @@ package eu.steffo.twom.composables.avatar -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.util.Log import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.tooling.preview.Preview -import eu.steffo.twom.composables.matrix.LocalSession -import java.io.File @Composable -@Preview(widthDp = 40, heightDp = 40) +@Preview(name = "Regular", widthDp = 40, heightDp = 40) +@Preview(name = "Double font scale", widthDp = 40, heightDp = 40, fontScale = 2f) +@Preview(name = "Quadruple font scale", widthDp = 40, heightDp = 40, fontScale = 4f) fun AvatarURL( modifier: Modifier = Modifier, url: String? = "", @@ -24,49 +16,9 @@ fun AvatarURL( contentDescription: String = "", alpha: Float = 1.0f, ) { - val session = LocalSession.current - var bitmap by remember { mutableStateOf(null) } - - LaunchedEffect(session, url) GetAvatar@{ - if (session == null) { - Log.d("AvatarURL", "Not doing anything, session is null.") - bitmap = null - return@GetAvatar - } - if (url == null) { - Log.d("AvatarURL", "URL is null, not downloading anything.") - bitmap = null - return@GetAvatar - } - if (url.isEmpty()) { - Log.d("AvatarURL", "URL is a zero-length string, not downloading anything.") - bitmap = null - return@GetAvatar - } - - Log.d("AvatarURL", "Downloading avatar at: $url") - lateinit var avatarFile: File - try { - avatarFile = session.fileService().downloadFile( - fileName = "avatar", - url = url, - mimeType = null, - elementToDecrypt = null, - ) - } catch (e: Throwable) { - Log.e("AvatarURL", "Unable to download avatar at: $url", e) - return@GetAvatar - } - - // TODO: Should I check the MIME type? And the size of the image? - // FIXME: I feel this might be a race condition... - Log.d("AvatarURL", "File for $url is: $avatarFile") - bitmap = BitmapFactory.decodeFile(avatarFile.absolutePath) - } - AvatarImage( modifier = modifier, - bitmap = bitmap?.asImageBitmap(), + bitmap = bitmapFromMatrixFile(url)?.asImageBitmap(), fallbackText = fallbackText, contentDescription = contentDescription, alpha = alpha, diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUser.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUser.kt index f85df46..41e35c6 100644 --- a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUser.kt +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUser.kt @@ -7,7 +7,9 @@ import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem @Composable -@Preview(widthDp = 40, heightDp = 40) +@Preview(name = "Regular", widthDp = 40, heightDp = 40) +@Preview(name = "Double font scale", widthDp = 40, heightDp = 40, fontScale = 2f) +@Preview(name = "Quadruple font scale", widthDp = 40, heightDp = 40, fontScale = 4f) fun AvatarUser( modifier: Modifier = Modifier, user: User? = null, diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUserId.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUserId.kt index 71c372d..7a11446 100644 --- a/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUserId.kt +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/AvatarUserId.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.tooling.preview.Preview import eu.steffo.twom.composables.matrix.LocalSession @Composable -@Preview(widthDp = 40, heightDp = 40) +@Preview(name = "Regular", widthDp = 40, heightDp = 40) +@Preview(name = "Double font scale", widthDp = 40, heightDp = 40, fontScale = 2f) +@Preview(name = "Quadruple font scale", widthDp = 40, heightDp = 40, fontScale = 4f) fun AvatarUserId( modifier: Modifier = Modifier, userId: String = "", diff --git a/app/src/main/java/eu/steffo/twom/composables/avatar/bitmapFromMatrixFile.kt b/app/src/main/java/eu/steffo/twom/composables/avatar/bitmapFromMatrixFile.kt new file mode 100644 index 0000000..a406dc3 --- /dev/null +++ b/app/src/main/java/eu/steffo/twom/composables/avatar/bitmapFromMatrixFile.kt @@ -0,0 +1,76 @@ +package eu.steffo.twom.composables.avatar + +import android.graphics.Bitmap +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.core.net.toUri +import eu.steffo.twom.composables.matrix.LocalSession +import eu.steffo.twom.utils.BitmapUtilities +import kotlinx.coroutines.CancellationException +import org.matrix.android.sdk.api.util.md5 +import java.io.File + +const val TAG = "bitmapFromMatrixFile" + +@Composable +fun bitmapFromMatrixFile(url: String? = null): Bitmap? { + val session = LocalSession.current + val resolver = LocalContext.current.contentResolver + + var bitmap by remember { mutableStateOf(null) } + + Log.v(TAG, "Compositing: $url") + + // LaunchedEffect does not behave as expected: sometimes, the effect is cancelled, but not relaunched! + LaunchedEffect(session, url) Fetch@{ + Log.v(TAG, "Launching: $url") + + if (session == null) { + Log.d(TAG, "Session is null, clearing bitmap.") + bitmap = null + return@Fetch + } + if (url == null) { + Log.d(TAG, "URL is null, clearing bitmap.") + bitmap = null + return@Fetch + } + if (url.isEmpty()) { + Log.d(TAG, "URL is a zero-length string, clearing bitmap.") + bitmap = null + return@Fetch + } + + Log.i(TAG, "Downloading file at: $url") + lateinit var avatarFile: File + try { + avatarFile = session.fileService().downloadFile( + fileName = url.md5(), + url = url, + mimeType = null, + elementToDecrypt = null, + ) + } catch (e: CancellationException) { + // This makes sure no corrupt image is displayed, at least + Log.i(TAG, "Cancelled download of file at: $url", e) + bitmap = null + return@Fetch + } catch (e: Throwable) { + Log.e(TAG, "Unable to download file at: $url", e) + bitmap = null + return@Fetch + } + + Log.d(TAG, "File for $url is: $avatarFile") + + bitmap = BitmapUtilities.getCorrectedBitmap(resolver, avatarFile.toUri()) + } + + return bitmap +} \ No newline at end of file diff --git a/app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt b/app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt index 50a0b9e..500e2e0 100644 --- a/app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt +++ b/app/src/main/java/eu/steffo/twom/utils/BitmapUtilities.kt @@ -106,6 +106,12 @@ class BitmapUtilities { ) } + fun getCorrectedBitmap(resolver: ContentResolver, uri: Uri): Bitmap? { + val rawBitmap = getRawBitmap(resolver, uri) ?: return null + val orientation = getOrientation(resolver, uri) ?: return null + return squareAndOrient(rawBitmap, orientation) + } + fun bitmapToCache(id: String, bitmap: Bitmap): File { val file = File.createTempFile("bitmap_$id", ".jpg") file.outputStream().use {