Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
Bug 1876594 - Refactor ThumbnailStorage to be an implicit dependenc…
Browse files Browse the repository at this point in the history
…y within a factory helper
  • Loading branch information
MozillaNoah committed Feb 13, 2024
1 parent ab7cc95 commit f0429a5
Show file tree
Hide file tree
Showing 15 changed files with 114 additions and 152 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.compose

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.components.components
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.compose.ThumbnailImage as ThumbnailImageContent

/**
* Factory object used to instantiate Composables with preview-breaking dependencies, such as
* [components].
*/
object ComposableFactory {

/**
* Loads a thumbnail belonging to a [ImageLoadRequest], fetching asynchronously the bitmap from storage.
* If this is in a preview, an empty box will be rendered instead.
*
* @param request [ImageLoadRequest] used to fetch the thumbnail bitmap.
* @param contentScale [ContentScale] used to draw image content.
* @param alignment [Alignment] used to draw the image content.
* @param modifier [Modifier] used to draw the image content.
* @param fallbackContent The content to display with a thumbnail is unable to be loaded.
*/
@Composable
fun ThumbnailImage(
request: ImageLoadRequest,
contentScale: ContentScale,
alignment: Alignment,
modifier: Modifier = Modifier,
fallbackContent: @Composable () -> Unit,
) {
if (inComposePreview) {
Box(modifier = modifier.background(color = FirefoxTheme.colors.layer3))
} else {
ThumbnailImageContent(
request = request,
contentScale = contentScale,
alignment = alignment,
modifier = modifier,
fallbackContent = fallbackContent,
)
}
}
}

/**
* This preview does not demo anything. This is to ensure that [ComposableFactory.ThumbnailImage]
* does not break other previews.
*/
@Preview
@Composable
private fun ThumbnailImagePreview() {
FirefoxTheme {
ComposableFactory.ThumbnailImage(
request = ImageLoadRequest("1", 1, false),
modifier = Modifier.size(50.dp),
contentScale = ContentScale.Crop,
alignment = Alignment.Center,
fallbackContent = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.theme.FirefoxTheme

Expand All @@ -32,7 +30,6 @@ private const val FALLBACK_ICON_SIZE = 36
* will be displayed until the thumbnail is loaded.
*
* @param tab The given [TabSessionState] to render a thumbnail for.
* @param storage [ThumbnailStorage] to obtain tab thumbnail bitmaps from.
* @param size Size of the thumbnail.
* @param modifier [Modifier] used to draw the image content.
* @param backgroundColor [Color] used for the background of the favicon.
Expand All @@ -44,7 +41,6 @@ private const val FALLBACK_ICON_SIZE = 36
@Composable
fun TabThumbnail(
tab: TabSessionState,
storage: ThumbnailStorage,
size: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = FirefoxTheme.colors.layer2,
Expand All @@ -56,14 +52,12 @@ fun TabThumbnail(
modifier = modifier,
backgroundColor = backgroundColor,
) {
ThumbnailImage(
ComposableFactory.ThumbnailImage(
request = ImageLoadRequest(
id = tab.id,
size = size,
isPrivate = tab.content.private,
),
storage = storage,
modifier = modifier,
contentScale = contentScale,
alignment = alignment,
) {
Expand Down Expand Up @@ -100,7 +94,6 @@ private fun ThumbnailCardPreview() {
TabThumbnail(
tab = createTab(url = "www.mozilla.com", title = "Mozilla"),
size = 108,
storage = ThumbnailStorage(LocalContext.current),
modifier = Modifier
.size(108.dp, 80.dp)
.clip(RoundedCornerShape(8.dp)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import mozilla.components.browser.icons.compose.Loader
import mozilla.components.browser.icons.compose.Placeholder
import mozilla.components.browser.icons.compose.WithIcon
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.components.components
import org.mozilla.fenix.theme.FirefoxTheme
Expand All @@ -36,7 +34,6 @@ private const val FALLBACK_ICON_SIZE = 36
*
* @param url Url to display thumbnail for.
* @param request [ImageLoadRequest] used to fetch the thumbnail bitmap.
* @param storage [ThumbnailStorage] to obtain tab thumbnail bitmaps from.
* @param modifier [Modifier] used to draw the image content.
* @param backgroundColor [Color] used for the background of the favicon.
* @param contentDescription Text used by accessibility services
Expand All @@ -48,7 +45,6 @@ private const val FALLBACK_ICON_SIZE = 36
fun ThumbnailCard(
url: String,
request: ImageLoadRequest,
storage: ThumbnailStorage,
modifier: Modifier = Modifier,
backgroundColor: Color = FirefoxTheme.colors.layer2,
contentDescription: String? = null,
Expand All @@ -59,10 +55,8 @@ fun ThumbnailCard(
modifier = modifier,
backgroundColor = backgroundColor,
) {
ThumbnailImage(
ComposableFactory.ThumbnailImage(
request = request,
storage = storage,
modifier = modifier,
contentScale = contentScale,
alignment = alignment,
) {
Expand Down Expand Up @@ -98,7 +92,6 @@ private fun ThumbnailCardPreview() {
ThumbnailCard(
url = "https://mozilla.com",
request = ImageLoadRequest("123", THUMBNAIL_SIZE, false),
storage = ThumbnailStorage(LocalContext.current),
modifier = Modifier
.size(THUMBNAIL_SIZE.dp)
.clip(RoundedCornerShape(8.dp)),
Expand Down
102 changes: 37 additions & 65 deletions fenix/app/src/main/java/org/mozilla/fenix/compose/ThumbnailImage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ package org.mozilla.fenix.compose

import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
Expand All @@ -20,74 +18,66 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.components.components

/**
* Thumbnail belonging to a [ImageLoadRequest]. Asynchronously fetches the bitmap from storage.
*
* @param request [ImageLoadRequest] used to fetch the thumbnail bitmap.
* @param storage [ThumbnailStorage] to obtain tab thumbnail bitmaps from.
* @param modifier [Modifier] used to draw the image content.
* @param contentScale [ContentScale] used to draw image content.
* @param alignment [Alignment] used to draw the image content.
* @param modifier [Modifier] used to draw the image content.
* @param fallbackContent The content to display with a thumbnail is unable to be loaded.
*/
@Composable
fun ThumbnailImage(
internal fun ThumbnailImage(
request: ImageLoadRequest,
storage: ThumbnailStorage,
modifier: Modifier,
contentScale: ContentScale,
alignment: Alignment,
modifier: Modifier = Modifier,
fallbackContent: @Composable () -> Unit,
) {
if (inComposePreview) {
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer3))
} else {
var state by remember { mutableStateOf(ThumbnailImageState(null, false)) }
val scope = rememberCoroutineScope()

DisposableEffect(Unit) {
if (!state.hasLoaded) {
scope.launch {
val thumbnailBitmap = storage.loadThumbnail(request).await()
thumbnailBitmap?.prepareToDraw()
state = ThumbnailImageState(
bitmap = thumbnailBitmap,
hasLoaded = true,
)
}
}
var state by remember { mutableStateOf(ThumbnailImageState(null, false)) }
val scope = rememberCoroutineScope()
val storage = components.core.thumbnailStorage

onDispose {
// Recycle the bitmap to liberate the RAM. Without this, a list of [ThumbnailImage]
// will bloat the memory. This is a trade-off, however, as the bitmap
// will be re-fetched if this Composable is disposed and re-loaded.
state.bitmap?.recycle()
DisposableEffect(Unit) {
if (!state.hasLoaded) {
scope.launch {
val thumbnailBitmap = storage.loadThumbnail(request).await()
thumbnailBitmap?.prepareToDraw()
state = ThumbnailImageState(
bitmap = null,
hasLoaded = false,
bitmap = thumbnailBitmap,
hasLoaded = true,
)
}
}

if (state.bitmap == null && state.hasLoaded) {
fallbackContent()
} else {
state.bitmap?.let { bitmap ->
Image(
painter = BitmapPainter(bitmap.asImageBitmap()),
contentDescription = null,
modifier = modifier,
contentScale = contentScale,
alignment = alignment,
)
}
onDispose {
// Recycle the bitmap to liberate the RAM. Without this, a list of [ThumbnailImage]
// will bloat the memory. This is a trade-off, however, as the bitmap
// will be re-fetched if this Composable is disposed and re-loaded.
state.bitmap?.recycle()
state = ThumbnailImageState(
bitmap = null,
hasLoaded = false,
)
}
}

if (state.bitmap == null && state.hasLoaded) {
fallbackContent()
} else {
state.bitmap?.let { bitmap ->
Image(
painter = BitmapPainter(bitmap.asImageBitmap()),
contentDescription = null,
modifier = modifier,
contentScale = contentScale,
alignment = alignment,
)
}
}
}
Expand All @@ -99,21 +89,3 @@ private data class ThumbnailImageState(
val bitmap: Bitmap?,
val hasLoaded: Boolean,
)

/**
* This preview does not demo anything. This is to ensure that [ThumbnailImage] does not break other previews.
*/
@Preview
@Composable
private fun ThumbnailImagePreview() {
FirefoxTheme {
ThumbnailImage(
request = ImageLoadRequest("1", 1, false),
storage = ThumbnailStorage(LocalContext.current),
modifier = Modifier,
contentScale = ContentScale.Crop,
alignment = Alignment.Center,
fallbackContent = {},
)
}
}
Loading

0 comments on commit f0429a5

Please sign in to comment.