Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Thumbnails to wallpapers UI #26671

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ internal object AppStoreReducer {
)
is AppAction.WallpaperAction.UpdateWallpaperDownloadState -> {
val wallpapers = state.wallpaperState.availableWallpapers.map {
if (it == action.wallpaper) {
if (it.name == action.wallpaper.name) {
it.copy(assetsFileState = action.imageState)
} else {
it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import mozilla.components.lib.state.Store
import org.mozilla.fenix.R
import org.mozilla.fenix.addons.showSnackBar
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
Expand Down Expand Up @@ -92,6 +94,13 @@ class WallpapersObserver(
bitmap?.let {
it.scaleToBottomOfView(wallpaperImageView)
wallpaperImageView.isVisible = true
} ?: run {
with(wallpaperImageView) {
showSnackBar(
view = this,
text = resources.getString(R.string.wallpaper_select_error_snackbar_message),
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.annotation.SuppressLint
import android.graphics.Bitmap
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
Expand All @@ -17,37 +18,28 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Scaffold
import androidx.compose.material.Snackbar
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.button.TextButton
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme
import org.mozilla.fenix.wallpapers.Wallpaper
Expand All @@ -58,10 +50,9 @@ import org.mozilla.fenix.wallpapers.Wallpaper
*
* @param wallpapers Wallpapers to add to grid.
* @param selectedWallpaper The currently selected wallpaper.
* @param defaultWallpaper The default wallpaper
* @param defaultWallpaper The default wallpaper.
* @param loadWallpaperResource Callback to handle loading a wallpaper bitmap. Only optional in the default case.
* @param onSelectWallpaper Callback for when a new wallpaper is selected.
* @param onViewWallpaper Callback for when the view action is clicked from snackbar.
*/
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
Expand All @@ -72,76 +63,27 @@ fun WallpaperSettings(
loadWallpaperResource: suspend (Wallpaper) -> Bitmap?,
selectedWallpaper: Wallpaper,
onSelectWallpaper: (Wallpaper) -> Unit,
onViewWallpaper: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()

Scaffold(
backgroundColor = FirefoxTheme.colors.layer1,
scaffoldState = scaffoldState,
snackbarHost = { hostState ->
SnackbarHost(hostState = hostState) {
WallpaperSnackbar(onViewWallpaper)
}
},
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.background(color = FirefoxTheme.colors.layer1),
) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
WallpaperThumbnails(
wallpapers = wallpapers,
defaultWallpaper = defaultWallpaper,
selectedWallpaper = selectedWallpaper,
loadWallpaperResource = loadWallpaperResource,
onSelectWallpaper = { updatedWallpaper ->
coroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(
message = "", // overwritten by WallpaperSnackbar
duration = SnackbarDuration.Short,
)
}
onSelectWallpaper(updatedWallpaper)
},
)
}
WallpaperThumbnails(
wallpapers = wallpapers,
defaultWallpaper = defaultWallpaper,
loadWallpaperResource = loadWallpaperResource,
selectedWallpaper = selectedWallpaper,
onSelectWallpaper = { updatedWallpaper -> onSelectWallpaper(updatedWallpaper) },
)
}
}

@Composable
private fun WallpaperSnackbar(
onViewWallpaper: () -> Unit,
) {
Snackbar(
modifier = Modifier
.padding(8.dp)
.heightIn(min = 48.dp),
backgroundColor = FirefoxTheme.colors.actionPrimary,
content = {
Text(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp),
text = stringResource(R.string.wallpaper_updated_snackbar_message),
textAlign = TextAlign.Start,
color = FirefoxTheme.colors.textOnColorPrimary,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
style = FirefoxTheme.typography.headline7,
)
},
action = {
TextButton(
text = stringResource(R.string.wallpaper_updated_snackbar_action),
onClick = onViewWallpaper,
modifier = Modifier.padding(all = 8.dp),
textColor = FirefoxTheme.colors.textOnColorPrimary,
)
},
)
}

/**
* A grid of selectable wallpaper thumbnails.
*
* @param wallpapers Wallpapers to add to grid.
* @param defaultWallpaper The default wallpaper
* @param defaultWallpaper The default wallpaper.
* @param loadWallpaperResource Callback to handle loading a wallpaper bitmap. Only optional in the default case.
* @param selectedWallpaper The currently selected wallpaper.
* @param numColumns The number of columns that will occupy the grid.
Expand Down Expand Up @@ -173,16 +115,18 @@ fun WallpaperThumbnails(
for (columnIndex in 0 until numColumns) {
val itemIndex = rowIndex * numColumns + columnIndex
if (itemIndex < wallpapers.size) {
val wallpaper = wallpapers[itemIndex]
Box(
modifier = Modifier
.weight(1f, fill = true)
.padding(4.dp),
) {
WallpaperThumbnailItem(
wallpaper = wallpapers[itemIndex],
wallpaper = wallpaper,
defaultWallpaper = defaultWallpaper,
loadWallpaperResource = loadWallpaperResource,
isSelected = selectedWallpaper == wallpapers[itemIndex],
isSelected = selectedWallpaper.name == wallpaper.name,
isLoading = wallpaper.assetsFileState == Wallpaper.ImageFileState.Downloading,
onSelect = onSelectWallpaper,
)
}
Expand All @@ -199,9 +143,14 @@ fun WallpaperThumbnails(
* A single wallpaper thumbnail.
*
* @param wallpaper The wallpaper to display.
* @param defaultWallpaper The default wallpaper.
* @param loadWallpaperResource Callback to handle loading a wallpaper bitmap.
* @param isSelected Whether the wallpaper is currently selected.
* @param isLoading Whether the wallpaper is currently downloading.
* @param aspectRatio The ratio of height to width of the thumbnail.
* @param onSelect Action to take when this wallpaper is selected.
* @param loadingOpacity Opacity of the currently downloading wallpaper.
* @param onSelect Action to take when a new wallpaper is selected.
*/
@Composable
@Suppress("LongParameterList")
Expand All @@ -210,7 +159,9 @@ private fun WallpaperThumbnailItem(
defaultWallpaper: Wallpaper,
loadWallpaperResource: suspend (Wallpaper) -> Bitmap?,
isSelected: Boolean,
isLoading: Boolean,
aspectRatio: Float = 1.1f,
loadingOpacity: Float = 0.5f,
onSelect: (Wallpaper) -> Unit,
) {
var bitmap: Bitmap? by remember { mutableStateOf(null) }
Expand All @@ -220,7 +171,12 @@ private fun WallpaperThumbnailItem(
val thumbnailShape = RoundedCornerShape(8.dp)
val border = if (isSelected) {
Modifier.border(
BorderStroke(width = 2.dp, color = FirefoxTheme.colors.borderAccent),
BorderStroke(width = 3.dp, color = FirefoxTheme.colors.borderAccent),
thumbnailShape,
)
} else if (wallpaper.name == Wallpaper.defaultName) {
Modifier.border(
BorderStroke(width = 1.dp, color = FirefoxTheme.colors.borderPrimary),
thumbnailShape,
)
} else {
Expand Down Expand Up @@ -249,8 +205,20 @@ private fun WallpaperThumbnailItem(
wallpaper.name,
),
modifier = Modifier.fillMaxSize(),
alpha = if (isLoading) loadingOpacity else 1.0f,
)
}
if (isLoading) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(24.dp),
) {
CircularProgressIndicator(
color = FirefoxTheme.colors.borderAccent,
)
}
}
}
}

Expand All @@ -264,17 +232,6 @@ private fun WallpaperThumbnailsPreview() {
wallpapers = listOf(Wallpaper.Default),
selectedWallpaper = Wallpaper.Default,
onSelectWallpaper = {},
onViewWallpaper = {},
)
}
}

@Preview
@Composable
private fun WallpaperSnackbarPreview() {
FirefoxTheme(theme = Theme.getTheme()) {
WallpaperSnackbar(
onViewWallpaper = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.launch
import mozilla.components.lib.state.ext.observeAsComposableState
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Wallpapers
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.theme.FirefoxTheme
Expand All @@ -36,7 +38,7 @@ class WallpaperSettingsFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
): View {
Wallpapers.wallpaperSettingsOpened.record(NoExtras())
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
Expand All @@ -49,7 +51,7 @@ class WallpaperSettingsFragment : Fragment() {
state.wallpaperState.currentWallpaper
}.value ?: Wallpaper.Default

var coroutineScope = rememberCoroutineScope()
val coroutineScope = rememberCoroutineScope()

WallpaperSettings(
wallpapers = wallpapers,
Expand All @@ -59,14 +61,52 @@ class WallpaperSettingsFragment : Fragment() {
wallpaperUseCases.loadThumbnail(it)
},
onSelectWallpaper = {
coroutineScope.launch { wallpaperUseCases.selectWallpaper(it) }
coroutineScope.launch {
val result = wallpaperUseCases.selectWallpaper(it)
onWallpaperSelected(it, result, this@apply)
}
},
onViewWallpaper = { findNavController().navigate(R.id.homeFragment) },
)
}
}
}
}

private fun onWallpaperSelected(
wallpaper: Wallpaper,
result: Wallpaper.ImageFileState,
view: View,
) {
when (result) {
Wallpaper.ImageFileState.Downloaded -> {
FenixSnackbar.make(
view = view,
isDisplayedWithBrowserToolbar = false,
)
.setText(view.context.getString(R.string.wallpaper_updated_snackbar_message))
.setAction(requireContext().getString(R.string.wallpaper_updated_snackbar_action)) {
findNavController().navigate(R.id.homeFragment)
}
.show()
}
Wallpaper.ImageFileState.Error -> {
FenixSnackbar.make(
view = view,
isDisplayedWithBrowserToolbar = false,
)
.setText(view.context.getString(R.string.wallpaper_download_error_snackbar_message))
.setAction(view.context.getString(R.string.wallpaper_download_error_snackbar_action)) {
viewLifecycleOwner.lifecycleScope.launch {
val retryResult = wallpaperUseCases.selectWallpaper(wallpaper)
onWallpaperSelected(wallpaper, retryResult, view)
}
}
.show()
}
else -> { /* noop */ }
}
}

override fun onResume() {
super.onResume()
showToolbar(getString(R.string.customize_wallpapers))
Expand Down
12 changes: 0 additions & 12 deletions app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,4 @@ data class Wallpaper(
Downloaded,
Error,
}

override fun hashCode(): Int {
return name.hashCode()
}

override fun equals(other: Any?): Boolean {
return if (other is Wallpaper) {
this.name == other.name
} else {
false
}
}
}
Loading