diff --git a/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt b/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt
index 18f091a3e3b4..5355af13d5fc 100644
--- a/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/appstate/AppStoreReducer.kt
@@ -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
diff --git a/app/src/main/java/org/mozilla/fenix/home/WallpapersObserver.kt b/app/src/main/java/org/mozilla/fenix/home/WallpapersObserver.kt
index 9e01e6faee3f..d9868c77b0cc 100644
--- a/app/src/main/java/org/mozilla/fenix/home/WallpapersObserver.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/WallpapersObserver.kt
@@ -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
@@ -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),
+ )
+ }
}
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt
index b74cf59e29c1..17097c351299 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettings.kt
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -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,
)
}
@@ -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")
@@ -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) }
@@ -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 {
@@ -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,
+ )
+ }
+ }
}
}
@@ -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 = {},
)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt
index 262f9cf19698..173c7b9a8209 100644
--- a/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/settings/wallpaper/WallpaperSettingsFragment.kt
@@ -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
@@ -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)
@@ -49,7 +51,7 @@ class WallpaperSettingsFragment : Fragment() {
state.wallpaperState.currentWallpaper
}.value ?: Wallpaper.Default
- var coroutineScope = rememberCoroutineScope()
+ val coroutineScope = rememberCoroutineScope()
WallpaperSettings(
wallpapers = wallpapers,
@@ -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))
diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt
index d1a45939a70c..89207c4a62c2 100644
--- a/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt
+++ b/app/src/main/java/org/mozilla/fenix/wallpapers/Wallpaper.kt
@@ -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
- }
- }
}
diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt
index 73da1bbc92b8..9c2a4631bcde 100644
--- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt
+++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpapersUseCases.kt
@@ -423,7 +423,7 @@ class WallpapersUseCases(
*
* @param wallpaper The selected wallpaper.
*/
- suspend operator fun invoke(wallpaper: Wallpaper)
+ suspend operator fun invoke(wallpaper: Wallpaper): Wallpaper.ImageFileState
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -436,7 +436,7 @@ class WallpapersUseCases(
*
* @param wallpaper The selected wallpaper.
*/
- override suspend fun invoke(wallpaper: Wallpaper) {
+ override suspend fun invoke(wallpaper: Wallpaper): Wallpaper.ImageFileState {
settings.currentWallpaperName = wallpaper.name
settings.currentWallpaperTextColor = wallpaper.textColor ?: 0
settings.currentWallpaperCardColor = wallpaper.cardColor ?: 0
@@ -447,6 +447,7 @@ class WallpapersUseCases(
themeCollection = wallpaper.collection.name,
),
)
+ return Wallpaper.ImageFileState.Downloaded
}
}
@@ -462,10 +463,11 @@ class WallpapersUseCases(
*
* @param wallpaper The selected wallpaper.
*/
- override suspend fun invoke(wallpaper: Wallpaper) {
- if (wallpaper == Wallpaper.Default || fileManager.wallpaperImagesExist(wallpaper)) {
+ override suspend fun invoke(wallpaper: Wallpaper): Wallpaper.ImageFileState {
+ return if (wallpaper == Wallpaper.Default || fileManager.wallpaperImagesExist(wallpaper)) {
selectWallpaper(wallpaper)
dispatchDownloadState(wallpaper, Wallpaper.ImageFileState.Downloaded)
+ Wallpaper.ImageFileState.Downloaded
} else {
dispatchDownloadState(wallpaper, Wallpaper.ImageFileState.Downloading)
val result = downloader.downloadWallpaper(wallpaper)
@@ -473,6 +475,7 @@ class WallpapersUseCases(
if (result == Wallpaper.ImageFileState.Downloaded) {
selectWallpaper(wallpaper)
}
+ result
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c45940305785..3e2f66101494 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -453,6 +453,13 @@
Wallpaper updated!
View
+
+ Couldn’t download wallpaper
+
+ Try again
+
+ Couldn’t change wallpaper
+
Change wallpaper by tapping Firefox homepage logo