Closes #26211: Download wallpapers when thumbnails clicked

This commit is contained in:
MatthewTighe 2022-08-19 15:47:55 -07:00 committed by mergify[bot]
parent 2cd8a41ea5
commit 8a9c68c872
14 changed files with 283 additions and 76 deletions

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.components
import android.content.Context
import android.os.StrictMode
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.fetch.Client
@ -23,6 +24,7 @@ import mozilla.components.feature.tabs.CustomTabsUseCases
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.top.sites.TopSitesStorage
import mozilla.components.feature.top.sites.TopSitesUseCases
import mozilla.components.support.locale.LocaleManager
import mozilla.components.support.locale.LocaleUseCases
import org.mozilla.fenix.components.bookmarks.BookmarksUseCase
import org.mozilla.fenix.perf.StrictModeManager
@ -107,6 +109,13 @@ class UseCases(
val bookmarksUseCases by lazyMonitored { BookmarksUseCase(bookmarksStorage, historyStorage) }
val wallpaperUseCases by lazyMonitored {
WallpapersUseCases(context, appStore, client, strictMode)
// Required to even access context.filesDir property and to retrieve current locale
val (rootStorageDirectory, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
val rootStorageDirectory = context.filesDir
val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag()
?: LocaleManager.getSystemDefault().toLanguageTag()
rootStorageDirectory to currentLocale
}
WallpapersUseCases(context, appStore, client, rootStorageDirectory, currentLocale)
}
}

View File

@ -169,5 +169,17 @@ sealed class AppAction : Action {
* Indicates that the list of potential wallpapers has changed.
*/
data class UpdateAvailableWallpapers(val wallpapers: List<Wallpaper>) : WallpaperAction()
/**
* Indicates a change in the download state of a wallpaper. Note that this is meant to be
* used for full size images, not thumbnails.
*
* @property wallpaper The wallpaper that is being updated.
* @property imageState The updated image state for the wallpaper.
*/
data class UpdateWallpaperDownloadState(
val wallpaper: Wallpaper,
val imageState: Wallpaper.ImageFileState
) : WallpaperAction()
}
}

View File

@ -209,6 +209,17 @@ internal object AppStoreReducer {
state.copy(
wallpaperState = state.wallpaperState.copy(availableWallpapers = action.wallpapers)
)
is AppAction.WallpaperAction.UpdateWallpaperDownloadState -> {
val wallpapers = state.wallpaperState.availableWallpapers.map {
if (it == action.wallpaper) {
it.copy(assetsFileState = action.imageState)
} else {
it
}
}
val wallpaperState = state.wallpaperState.copy(availableWallpapers = wallpapers)
state.copy(wallpaperState = wallpaperState)
}
}
}

View File

@ -8,10 +8,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
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
@ -47,12 +49,18 @@ class WallpaperSettingsFragment : Fragment() {
state.wallpaperState.currentWallpaper
}.value ?: Wallpaper.Default
var coroutineScope = rememberCoroutineScope()
WallpaperSettings(
wallpapers = wallpapers,
defaultWallpaper = Wallpaper.Default,
loadWallpaperResource = { wallpaperUseCases.loadBitmap(it) },
selectedWallpaper = currentWallpaper,
onSelectWallpaper = { wallpaperUseCases.selectWallpaper(it) },
loadWallpaperResource = {
wallpaperUseCases.loadThumbnail(it)
},
onSelectWallpaper = {
coroutineScope.launch { wallpaperUseCases.selectWallpaper(it) }
},
onViewWallpaper = { findNavController().navigate(R.id.homeFragment) },
)
}

View File

@ -38,7 +38,8 @@ class LegacyWallpaperFileManager(
collection = Wallpaper.DefaultCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
)
} else null
}

View File

@ -22,6 +22,7 @@ data class Wallpaper(
val textColor: Long?,
val cardColor: Long?,
val thumbnailFileState: ImageFileState,
val assetsFileState: ImageFileState,
) {
/**
* Type that represents a collection that a [Wallpaper] belongs to.
@ -68,6 +69,7 @@ data class Wallpaper(
textColor = null,
cardColor = null,
thumbnailFileState = ImageFileState.Downloaded,
assetsFileState = ImageFileState.Downloaded,
)
/**
@ -104,6 +106,7 @@ data class Wallpaper(
cardColor = cardColor,
collection = DefaultCollection,
thumbnailFileState = ImageFileState.Downloaded,
assetsFileState = ImageFileState.Downloaded,
)
} else null
}
@ -127,9 +130,21 @@ data class Wallpaper(
* Defines the download state of wallpaper asset.
*/
enum class ImageFileState {
NotAvailable,
Unavailable,
Downloading,
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
}
}
}

View File

@ -37,30 +37,39 @@ class WallpaperDownloader(
* and will be stored in the local path:
* wallpapers/<wallpaper name>/<orientation>.png
*/
suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(dispatcher) {
listOf(Wallpaper.ImageType.Portrait, Wallpaper.ImageType.Landscape).map { imageType ->
wallpaper.downloadAsset(imageType)
suspend fun downloadWallpaper(wallpaper: Wallpaper): Wallpaper.ImageFileState = withContext(dispatcher) {
val portraitResult = downloadAsset(wallpaper, Wallpaper.ImageType.Portrait)
val landscapeResult = downloadAsset(wallpaper, Wallpaper.ImageType.Landscape)
return@withContext if (portraitResult == Wallpaper.ImageFileState.Downloaded &&
landscapeResult == Wallpaper.ImageFileState.Downloaded
) {
Wallpaper.ImageFileState.Downloaded
} else {
Wallpaper.ImageFileState.Error
}
}
/**
* Downloads a thumbnail for a wallpaper from the network. This is expected to be found remotely
* at:
* <WALLPAPER_URL>/<collection name>/<wallpaper name>/<orientation>.png
* <WALLPAPER_URL>/<collection name>/<wallpaper name>/thumbnail.png
* and stored locally at:
* wallpapers/<wallpaper name>/<orientation>.png
* wallpapers/<wallpaper name>/thumbnail.png
*/
suspend fun downloadThumbnail(wallpaper: Wallpaper): Wallpaper.ImageFileState = withContext(dispatcher) {
wallpaper.downloadAsset(Wallpaper.ImageType.Thumbnail)
downloadAsset(wallpaper, Wallpaper.ImageType.Thumbnail)
}
private suspend fun Wallpaper.downloadAsset(
private suspend fun downloadAsset(
wallpaper: Wallpaper,
imageType: Wallpaper.ImageType
): Wallpaper.ImageFileState = withContext(dispatcher) {
val localFile = File(storageRootDirectory, getLocalPath(name, imageType))
if (localFile.exists()) return@withContext Wallpaper.ImageFileState.Downloaded
val localFile = File(storageRootDirectory, getLocalPath(wallpaper.name, imageType))
if (localFile.exists()) {
return@withContext Wallpaper.ImageFileState.Downloaded
}
val remotePath = "${collection.name}/${name}/${imageType.lowercase()}.png"
val remotePath = "${wallpaper.collection.name}/${wallpaper.name}/${imageType.lowercase()}.png"
val request = Request(
url = "$remoteHost/$remotePath",
method = Request.Method.GET
@ -83,7 +92,7 @@ class WallpaperDownloader(
localFile.delete()
}
}
Wallpaper.ImageFileState.Downloaded
Wallpaper.ImageFileState.Error
}
}
}

View File

@ -31,20 +31,21 @@ class WallpaperFileManager(
* files for each of a portrait and landscape orientation as well as a thumbnail.
*/
suspend fun lookupExpiredWallpaper(name: String): Wallpaper? = withContext(Dispatchers.IO) {
if (getAllLocalWallpaperPaths(name).all { File(storageRootDirectory, it).exists() }) {
if (allAssetsExist(name)) {
Wallpaper(
name = name,
collection = Wallpaper.DefaultCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.Downloaded,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
)
} else null
}
private fun getAllLocalWallpaperPaths(name: String): List<String> =
Wallpaper.ImageType.values().map { orientation ->
getLocalPath(name, orientation)
private fun allAssetsExist(name: String): Boolean =
Wallpaper.ImageType.values().all { type ->
File(storageRootDirectory, getLocalPath(name, type)).exists()
}
/**
@ -60,4 +61,11 @@ class WallpaperFileManager(
}
}
}
/**
* Checks whether all the assets for a wallpaper exist on the file system.
*/
suspend fun wallpaperImagesExist(wallpaper: Wallpaper): Boolean = withContext(Dispatchers.IO) {
return@withContext allAssetsExist(wallpaper.name)
}
}

View File

@ -83,7 +83,8 @@ class WallpaperMetadataFetcher(
textColor = getArgbValueAsLong("text-color"),
cardColor = getArgbValueAsLong("card-color"),
collection = collection,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Unavailable,
)
}
}

View File

@ -8,19 +8,16 @@ import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.StrictMode
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.Wallpapers
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.utils.Settings
import java.io.File
import java.util.Date
@ -31,7 +28,8 @@ import java.util.Date
* @param context Used for various file and configuration checks.
* @param store Will receive dispatches of metadata updates like the currently selected wallpaper.
* @param client Handles downloading wallpapers and their metadata.
* @param strictMode Required for determining some device state like current locale and file paths.
* @param storageRootDirectory The top level app-local storage directory.
* @param currentLocale The locale currently being used on the device.
*
* @property initialize Usecase for initializing wallpaper feature. Should usually be called early
* in the app's lifetime to ensure that any potential long-running tasks can complete quickly.
@ -42,20 +40,13 @@ class WallpapersUseCases(
context: Context,
store: AppStore,
client: Client,
strictMode: StrictModeManager,
filesDir: File,
storageRootDirectory: File,
currentLocale: String,
) {
private val downloader = WallpaperDownloader(storageRootDirectory, client)
private val fileManager = WallpaperFileManager(storageRootDirectory)
val initialize: InitializeWallpapersUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
// Required to even access context.filesDir property and to retrieve current locale
val (storageRootDirectory, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
val storageRootDirectory = context.filesDir
val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag()
?: LocaleManager.getSystemDefault().toLanguageTag()
storageRootDirectory to currentLocale
}
val downloader = WallpaperDownloader(storageRootDirectory, client)
val fileManager = WallpaperFileManager(storageRootDirectory)
val metadataFetcher = WallpaperMetadataFetcher(client)
DefaultInitializeWallpaperUseCase(
store = store,
@ -66,13 +57,7 @@ class WallpapersUseCases(
currentLocale = currentLocale
)
} else {
// Required to even access context.filesDir property and to retrieve current locale
val (fileManager, currentLocale) = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
val fileManager = LegacyWallpaperFileManager(context.filesDir)
val currentLocale = LocaleManager.getCurrentLocale(context)?.toLanguageTag()
?: LocaleManager.getSystemDefault().toLanguageTag()
fileManager to currentLocale
}
val fileManager = LegacyWallpaperFileManager(storageRootDirectory)
val downloader = LegacyWallpaperDownloader(context, client)
LegacyInitializeWallpaperUseCase(
store = store,
@ -92,12 +77,18 @@ class WallpapersUseCases(
}
val loadThumbnail: LoadThumbnailUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
DefaultLoadThumbnailUseCase(filesDir)
DefaultLoadThumbnailUseCase(storageRootDirectory)
} else {
LegacyLoadThumbnailUseCase(context)
}
}
val selectWallpaper: SelectWallpaperUseCase by lazy { DefaultSelectWallpaperUseCase(context.settings(), store) }
val selectWallpaper: SelectWallpaperUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
DefaultSelectWallpaperUseCase(context.settings(), store, fileManager, downloader)
} else {
LegacySelectWallpaperUseCase(context.settings(), store)
}
}
/**
* Contract for usecases that initialize the wallpaper feature.
@ -190,21 +181,24 @@ class WallpapersUseCases(
collection = firefoxClassicCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
Wallpaper(
name = Wallpaper.ceruleanName,
collection = firefoxClassicCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
Wallpaper(
name = Wallpaper.sunriseName,
collection = firefoxClassicCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
)
private val remoteWallpapers: List<Wallpaper> = listOf(
@ -213,14 +207,16 @@ class WallpapersUseCases(
collection = firefoxClassicCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
Wallpaper(
name = Wallpaper.beachVibeName,
collection = firefoxClassicCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
)
val allWallpapers = listOf(Wallpaper.Default) + localWallpapers + remoteWallpapers
@ -257,8 +253,6 @@ class WallpapersUseCases(
possibleWallpapers
)
possibleWallpapers.forEach { downloader.downloadWallpaper(it) }
val wallpapersWithUpdatedThumbnailState = possibleWallpapers.map { wallpaper ->
val result = downloader.downloadThumbnail(wallpaper)
wallpaper.copy(thumbnailFileState = result)
@ -401,13 +395,13 @@ class WallpapersUseCases(
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class LegacyLoadThumbnailUseCase(private val context: Context): LoadThumbnailUseCase {
internal class LegacyLoadThumbnailUseCase(private val context: Context) : LoadThumbnailUseCase {
override suspend fun invoke(wallpaper: Wallpaper): Bitmap? =
LegacyLoadBitmapUseCase(context).invoke(wallpaper)
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultLoadThumbnailUseCase(private val filesDir: File): LoadThumbnailUseCase {
internal class DefaultLoadThumbnailUseCase(private val filesDir: File) : LoadThumbnailUseCase {
override suspend fun invoke(wallpaper: Wallpaper): Bitmap? = withContext(Dispatchers.IO) {
Result.runCatching {
val path = Wallpaper.getLocalPath(wallpaper.name, Wallpaper.ImageType.Thumbnail)
@ -428,11 +422,11 @@ class WallpapersUseCases(
*
* @param wallpaper The selected wallpaper.
*/
operator fun invoke(wallpaper: Wallpaper)
suspend operator fun invoke(wallpaper: Wallpaper)
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultSelectWallpaperUseCase(
internal class LegacySelectWallpaperUseCase(
private val settings: Settings,
private val store: AppStore,
) : SelectWallpaperUseCase {
@ -441,7 +435,7 @@ class WallpapersUseCases(
*
* @param wallpaper The selected wallpaper.
*/
override fun invoke(wallpaper: Wallpaper) {
override suspend fun invoke(wallpaper: Wallpaper) {
settings.currentWallpaperName = wallpaper.name
settings.currentWallpaperTextColor = wallpaper.textColor ?: 0
settings.currentWallpaperCardColor = wallpaper.cardColor ?: 0
@ -454,4 +448,46 @@ class WallpapersUseCases(
)
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultSelectWallpaperUseCase(
private val settings: Settings,
private val store: AppStore,
private val fileManager: WallpaperFileManager,
private val downloader: WallpaperDownloader,
) : SelectWallpaperUseCase {
/**
* Select a new wallpaper. Storage and the store will be updated appropriately.
*
* @param wallpaper The selected wallpaper.
*/
override suspend fun invoke(wallpaper: Wallpaper) {
if (wallpaper == Wallpaper.Default || fileManager.wallpaperImagesExist(wallpaper)) {
selectWallpaper(wallpaper)
dispatchDownloadState(wallpaper, Wallpaper.ImageFileState.Downloaded)
} else {
dispatchDownloadState(wallpaper, Wallpaper.ImageFileState.Downloading)
val result = downloader.downloadWallpaper(wallpaper)
dispatchDownloadState(wallpaper, result)
if (result == Wallpaper.ImageFileState.Downloaded) {
selectWallpaper(wallpaper)
}
}
}
private fun selectWallpaper(wallpaper: Wallpaper) {
settings.currentWallpaperName = wallpaper.name
store.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(wallpaper))
Wallpapers.wallpaperSelected.record(
Wallpapers.WallpaperSelectedExtra(
name = wallpaper.name,
themeCollection = wallpaper.collection.name
)
)
}
private fun dispatchDownloadState(wallpaper: Wallpaper, downloadState: Wallpaper.ImageFileState) {
store.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(wallpaper, downloadState))
}
}
}

View File

@ -98,7 +98,8 @@ class LegacyWallpaperFileManagerTest {
name = name,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
collection = Wallpaper.Collection(
name = Wallpaper.defaultName,
heading = null,

View File

@ -74,10 +74,7 @@ class WallpaperDownloaderTest {
@Test
fun `GIVEN that thumbnail request is successful WHEN downloading THEN file is created in expected location`() = runTest {
val wallpaper = generateWallpaper()
val thumbnailRequest = Request(
url = "$remoteHost/${wallpaper.collection.name}/${wallpaper.name}/thumbnail.png",
method = Request.Method.GET
)
val thumbnailRequest = wallpaper.generateRequest("thumbnail")
val mockThumbnailResponse = mockk<Response>()
every { mockThumbnailResponse.status } returns 200
every { mockThumbnailResponse.body } returns Response.Body(wallpaperBytes.byteInputStream())
@ -132,7 +129,8 @@ class WallpaperDownloaderTest {
collection = wallpaperCollection,
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Unavailable,
)
private fun Wallpaper.generateRequest(type: String) = Request(

View File

@ -3,6 +3,7 @@ package org.mozilla.fenix.wallpapers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@ -109,6 +110,30 @@ class WallpaperFileManagerTest {
assertTrue(getAllFiles(unavailableName).none { it.exists() })
}
@Test
fun `WHEN both wallpaper assets exist THEN the file lookup will succeed`() = runTest {
val wallpaper = generateWallpaper("name")
createAllFiles(wallpaper.name)
val result = fileManager.wallpaperImagesExist(wallpaper)
assertTrue(result)
}
@Test
fun `WHEN at least one wallpaper asset does not exist THEN the file lookup will fail`() = runTest {
val wallpaper = generateWallpaper("name")
val allFiles = getAllFiles(wallpaper.name)
(0 until (allFiles.size - 1)).forEach {
allFiles[it].mkdirs()
allFiles[it].createNewFile()
}
val result = fileManager.wallpaperImagesExist(wallpaper)
assertFalse(result)
}
private fun createAllFiles(name: String) {
for (file in getAllFiles(name)) {
file.mkdirs()
@ -131,6 +156,7 @@ class WallpaperFileManagerTest {
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.Downloaded,
collection = Wallpaper.DefaultCollection
assetsFileState = Wallpaper.ImageFileState.Downloaded,
collection = Wallpaper.DefaultCollection,
)
}

View File

@ -22,6 +22,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Wallpapers
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.utils.Settings
import java.util.Calendar
import java.util.Date
@ -319,11 +320,11 @@ class WallpapersUseCasesTest {
}
val expiredWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired")
val allWallpapers = listOf(expiredWallpaper) + fakeRemoteWallpapers
every { mockSettings.currentWallpaperName } returns "expired"
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns expiredWallpaper
coEvery { mockMetadataFetcher.downloadWallpaperList() } returns allWallpapers
coEvery { mockDownloader.downloadThumbnail(any()) } returns Wallpaper.ImageFileState.Downloaded
WallpapersUseCases.DefaultInitializeWallpaperUseCase(
appStore,
mockDownloader,
@ -366,7 +367,7 @@ class WallpapersUseCasesTest {
}
@Test
fun `GIVEN available wallpapers WHEN invoking initialize use case THEN available wallpapers downloaded`() = runTest {
fun `GIVEN available wallpapers WHEN invoking initialize use case THEN available wallpaper thumbnails downloaded`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
@ -385,12 +386,12 @@ class WallpapersUseCasesTest {
).invoke()
for (fakeRemoteWallpaper in fakeRemoteWallpapers) {
coVerify { mockDownloader.downloadWallpaper(fakeRemoteWallpaper) }
coVerify { mockDownloader.downloadThumbnail(fakeRemoteWallpaper) }
}
}
@Test
fun `GIVEN available wallpapers WHEN invoking initialize use case THEN thumbnails downloaded and store state reflects that`() = runTest {
fun `GIVEN available wallpapers WHEN invoking initialize use case THEN thumbnails downloaded and the store state is updated to reflect that`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
@ -412,13 +413,15 @@ class WallpapersUseCasesTest {
coVerify { mockDownloader.downloadThumbnail(fakeRemoteWallpaper) }
}
appStore.waitUntilIdle()
assertTrue(appStore.state.wallpaperState.availableWallpapers.all {
it.thumbnailFileState == Wallpaper.ImageFileState.Downloaded
})
assertTrue(
appStore.state.wallpaperState.availableWallpapers.all {
it.thumbnailFileState == Wallpaper.ImageFileState.Downloaded
}
)
}
@Test
fun `GIVEN thumbnail download fails WHEN invoking initialize use case THEN store state reflects that`() = runTest {
fun `GIVEN thumbnail download fails WHEN invoking initialize use case THEN the store state is updated to reflect that`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
@ -496,14 +499,14 @@ class WallpapersUseCasesTest {
}
@Test
fun `WHEN selected wallpaper usecase invoked THEN storage updated and store receives dispatch`() {
fun `WHEN legacy selected wallpaper usecase invoked THEN storage updated and store receives dispatch`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
val slot = slot<String>()
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
every { mockSettings.currentWallpaperName } returns ""
every { mockSettings.currentWallpaperName = capture(slot) } just runs
WallpapersUseCases.DefaultSelectWallpaperUseCase(
WallpapersUseCases.LegacySelectWallpaperUseCase(
mockSettings,
appStore
).invoke(selectedWallpaper)
@ -514,6 +517,73 @@ class WallpapersUseCasesTest {
assertEquals(selectedWallpaper.name, Wallpapers.wallpaperSelected.testGetValue()?.first()?.extra?.get("name")!!)
}
@Test
fun `GIVEN wallpaper downloaded WHEN selecting a wallpaper THEN storage updated and store receives dispatch`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
val slot = slot<String>()
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
every { mockSettings.currentWallpaperName } returns ""
every { mockSettings.currentWallpaperName = capture(slot) } just runs
coEvery { mockFileManager.wallpaperImagesExist(selectedWallpaper) } returns true
WallpapersUseCases.DefaultSelectWallpaperUseCase(
mockSettings,
appStore,
mockFileManager,
mockDownloader,
).invoke(selectedWallpaper)
appStore.waitUntilIdle()
assertEquals(selectedWallpaper.name, slot.captured)
assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper)
assertEquals(selectedWallpaper.name, Wallpapers.wallpaperSelected.testGetValue()?.first()?.extra?.get("name")!!)
}
@Test
fun `GIVEN wallpaper is not downloaded WHEN selecting a wallpaper and download succeeds THEN storage updated and store receives dispatch`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
val slot = slot<String>()
val mockStore = mockk<AppStore>(relaxed = true)
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
every { mockSettings.currentWallpaperName } returns ""
every { mockSettings.currentWallpaperName = capture(slot) } just runs
coEvery { mockFileManager.wallpaperImagesExist(selectedWallpaper) } returns false
coEvery { mockDownloader.downloadWallpaper(selectedWallpaper) } returns Wallpaper.ImageFileState.Downloaded
WallpapersUseCases.DefaultSelectWallpaperUseCase(
mockSettings,
mockStore,
mockFileManager,
mockDownloader,
).invoke(selectedWallpaper)
verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Downloading)) }
verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Downloaded)) }
verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(selectedWallpaper)) }
}
@Test
fun `GIVEN wallpaper is not downloaded WHEN selecting a wallpaper and any download fails THEN wallpaper not set and store receives dispatch`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
val slot = slot<String>()
val mockStore = mockk<AppStore>(relaxed = true)
coEvery { mockFileManager.lookupExpiredWallpaper(any()) } returns null
every { mockSettings.currentWallpaperName } returns ""
every { mockSettings.currentWallpaperName = capture(slot) } just runs
coEvery { mockFileManager.wallpaperImagesExist(selectedWallpaper) } returns false
coEvery { mockDownloader.downloadWallpaper(selectedWallpaper) } returns Wallpaper.ImageFileState.Error
WallpapersUseCases.DefaultSelectWallpaperUseCase(
mockSettings,
mockStore,
mockFileManager,
mockDownloader,
).invoke(selectedWallpaper)
verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Downloading)) }
verify { mockStore.dispatch(AppAction.WallpaperAction.UpdateWallpaperDownloadState(selectedWallpaper, Wallpaper.ImageFileState.Error)) }
}
private enum class TimeRelation {
BEFORE,
NOW,
@ -549,7 +619,8 @@ class WallpapersUseCasesTest {
),
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Unavailable,
)
} else {
Wallpaper(
@ -565,7 +636,8 @@ class WallpapersUseCasesTest {
),
textColor = null,
cardColor = null,
thumbnailFileState = Wallpaper.ImageFileState.NotAvailable,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Unavailable,
)
}
}