diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 883571c336..204079a888 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -168,6 +168,7 @@ class Components(private val context: Context) { strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { WallpaperManager( settings, + appStore, WallpaperDownloader(context, core.client), WallpaperFileManager(context.filesDir), currentLocale diff --git a/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt b/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt index d15d0764d8..23653209d3 100644 --- a/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt +++ b/app/src/main/java/org/mozilla/fenix/components/appstate/AppAction.kt @@ -21,6 +21,7 @@ import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem import org.mozilla.fenix.library.history.PendingDeletionHistory import org.mozilla.fenix.gleanplumb.Message import org.mozilla.fenix.gleanplumb.MessagingState +import org.mozilla.fenix.wallpapers.Wallpaper /** * [Action] implementation related to [AppStore]. @@ -146,4 +147,19 @@ sealed class AppAction : Action { */ data class MessageDismissed(val message: Message) : MessagingAction() } + + /** + * [Action]s related to interactions with the wallpapers feature. + */ + sealed class WallpaperAction : AppAction() { + /** + * Indicates that a different [wallpaper] was selected. + */ + data class UpdateCurrentWallpaper(val wallpaper: Wallpaper) : WallpaperAction() + + /** + * Indicates that the list of potential wallpapers has changed. + */ + data class UpdateAvailableWallpapers(val wallpapers: List) : WallpaperAction() + } } diff --git a/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt b/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt index be5338b228..27a2e4598e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt +++ b/app/src/main/java/org/mozilla/fenix/components/appstate/AppState.kt @@ -22,6 +22,7 @@ import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem import org.mozilla.fenix.library.history.PendingDeletionHistory import org.mozilla.fenix.gleanplumb.MessagingState +import org.mozilla.fenix.wallpapers.WallpaperState /** * Value type that represents the state of the tabs tray. @@ -68,4 +69,5 @@ data class AppState( val pocketSponsoredStories: List = emptyList(), val messaging: MessagingState = MessagingState(), val pendingDeletionHistoryItems: Set = emptySet(), + val wallpaperState: WallpaperState = WallpaperState.default ) : State 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 497dfa2a29..cf6e7fa234 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 @@ -202,6 +202,14 @@ internal object AppStoreReducer { is AppAction.UndoPendingDeletionSet -> state.copy(pendingDeletionHistoryItems = state.pendingDeletionHistoryItems - action.historyItems) + is AppAction.WallpaperAction.UpdateCurrentWallpaper -> + state.copy( + wallpaperState = state.wallpaperState.copy(currentWallpaper = action.wallpaper) + ) + is AppAction.WallpaperAction.UpdateAvailableWallpapers -> + state.copy( + wallpaperState = state.wallpaperState.copy(availableWallpapers = action.wallpapers) + ) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 422fcedde6..66dd594acb 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -46,7 +46,10 @@ import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import mozilla.components.browser.menu.view.MenuButton import mozilla.components.browser.state.selector.findTab @@ -66,6 +69,7 @@ import mozilla.components.feature.top.sites.TopSitesFrecencyConfig import mozilla.components.feature.top.sites.TopSitesProviderConfig import mozilla.components.lib.state.ext.consumeFlow import mozilla.components.lib.state.ext.consumeFrom +import mozilla.components.lib.state.ext.flow import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.content.res.resolveAttribute @@ -390,6 +394,7 @@ class HomeFragment : Fragment() { binding.root.doOnPreDraw { requireComponents.appStore.dispatch(AppAction.UpdateFirstFrameDrawn(drawn = true)) } + // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! requireComponents.core.engine.profiler?.addMarker( MarkersFragmentLifecycleCallbacks.MARKER_NAME, profilerStartTime, "HomeFragment.onCreateView", @@ -401,7 +406,6 @@ class HomeFragment : Fragment() { super.onConfigurationChanged(newConfig) getMenuButton()?.dismissMenu() - displayWallpaperIfEnabled() } /** @@ -958,12 +962,20 @@ class HomeFragment : Fragment() { private fun displayWallpaperIfEnabled() { if (shouldEnableWallpaper()) { - val wallpaperManger = requireComponents.wallpaperManager - // We only want to update the wallpaper when it's different from the default one - // as the default is applied already on xml by default. - if (wallpaperManger.currentWallpaper != WallpaperManager.defaultWallpaper) { - wallpaperManger.updateWallpaper(binding.wallpaperImageView, wallpaperManger.currentWallpaper) - } + requireComponents.appStore.flow() + .ifChanged { state -> state.wallpaperState.currentWallpaper } + .onEach { state -> + // We only want to update the wallpaper when it's different from the default one + // as the default is applied already on xml by default. + val currentWallpaper = state.wallpaperState.currentWallpaper + if (currentWallpaper != WallpaperManager.defaultWallpaper) { + requireComponents.wallpaperManager.updateWallpaper( + binding.wallpaperImageView, + currentWallpaper + ) + } + } + .launchIn(viewLifecycleOwner.lifecycleScope) } } diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt index a1ae8990b2..e825a0cf68 100644 --- a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperManager.kt @@ -19,6 +19,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.R +import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.perf.runBlockingIncrement import org.mozilla.fenix.utils.Settings import java.io.File @@ -30,6 +32,7 @@ import java.util.Date @Suppress("TooManyFunctions") class WallpaperManager( private val settings: Settings, + private val appStore: AppStore, private val downloader: WallpaperDownloader, private val fileManager: WallpaperFileManager, private val currentLocale: String, @@ -40,10 +43,14 @@ class WallpaperManager( val wallpapers = allWallpapers .filter(::filterExpiredRemoteWallpapers) .filter(::filterPromotionalWallpapers) + .also { + appStore.dispatch(AppAction.WallpaperAction.UpdateAvailableWallpapers(it)) + } var currentWallpaper: Wallpaper = getCurrentWallpaperFromSettings() set(value) { settings.currentWallpaper = value.name + appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(value)) field = value } @@ -96,6 +103,8 @@ class WallpaperManager( values.first() } else { values[index] + }.also { + appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(it)) } } @@ -122,6 +131,8 @@ class WallpaperManager( wallpapers.find { it.name == currentWallpaper } ?: fileManager.lookupExpiredWallpaper(currentWallpaper) ?: defaultWallpaper + }.also { + appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(it)) } } diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperState.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperState.kt new file mode 100644 index 0000000000..9edc10f92b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperState.kt @@ -0,0 +1,23 @@ +/* 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.wallpapers + +/** + * Represents all state related to the Wallpapers feature. + * + * @property currentWallpaper The currently selected [Wallpaper]. + * @property availableWallpapers The full list of wallpapers that can be selected. + */ +data class WallpaperState( + val currentWallpaper: Wallpaper, + val availableWallpapers: List, +) { + companion object { + val default = WallpaperState( + currentWallpaper = Wallpaper.Default, + availableWallpapers = listOf() + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt index 606dd1442b..699619a11a 100644 --- a/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/wallpapers/WallpaperManagerTest.kt @@ -8,10 +8,12 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.slot import kotlinx.coroutines.test.runTest +import mozilla.components.support.test.libstate.ext.waitUntilIdle import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test +import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.utils.Settings import java.util.Calendar import java.util.Date @@ -30,18 +32,22 @@ class WallpaperManagerTest { every { clean(any(), any()) } just runs } + private val appStore = AppStore() + @Test - fun `WHEN wallpaper set THEN current wallpaper updated in settings`() { + fun `WHEN wallpaper set THEN current wallpaper updated in settings and dispatched to store`() { val currentCaptureSlot = slot() every { mockSettings.currentWallpaper } returns "" every { mockSettings.currentWallpaper = capture(currentCaptureSlot) } just runs val updatedName = "new name" val updatedWallpaper = Wallpaper.Local.Firefox(updatedName, drawableId = 0) - val wallpaperManager = WallpaperManager(mockSettings, mockk(), mockFileManager, "en-US", listOf()) + val wallpaperManager = WallpaperManager(mockSettings, appStore, mockk(), mockFileManager, "en-US", listOf()) wallpaperManager.currentWallpaper = updatedWallpaper assertEquals(updatedWallpaper.name, currentCaptureSlot.captured) + appStore.waitUntilIdle() + assertEquals(updatedWallpaper, appStore.state.wallpaperState.currentWallpaper) } @Test @@ -52,6 +58,7 @@ class WallpaperManagerTest { } val wallpaperManager = WallpaperManager( mockSettings, + appStore, mockDownloader, mockFileManager, "en-US", @@ -72,6 +79,7 @@ class WallpaperManagerTest { } val wallpaperManager = WallpaperManager( mockSettings, + appStore, mockDownloader, mockFileManager, "en-CA", @@ -94,6 +102,7 @@ class WallpaperManagerTest { val fakeRemoteWallpapers = fakePromoWallpapers + fakeNonPromoWallpapers val wallpaperManager = WallpaperManager( mockSettings, + appStore, mockDownloader, mockFileManager, "en-CA", @@ -116,6 +125,7 @@ class WallpaperManagerTest { val activeRemoteWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "expired") val wallpaperManager = WallpaperManager( mockSettings, + appStore, mockDownloader, mockFileManager, "en-US", @@ -137,6 +147,7 @@ class WallpaperManagerTest { val wallpaperManager = WallpaperManager( mockSettings, + appStore, mockDownloader, mockFileManager, "en-US", @@ -157,6 +168,7 @@ class WallpaperManagerTest { val wallpaperManager = WallpaperManager( mockSettings, + appStore, mockDownloader, mockFileManager, "en-US", @@ -193,6 +205,21 @@ class WallpaperManagerTest { assertFalse(result) } + @Test + fun `WHEN manager initialized THEN available wallpapers are dispatched to store`() { + every { mockSettings.currentWallpaper } returns WallpaperManager.defaultWallpaper.name + val wallpaperManager = WallpaperManager( + mockSettings, + appStore, + mockDownloader, + mockFileManager, + "en-US", + ) + + appStore.waitUntilIdle() + assertEquals(wallpaperManager.wallpapers, appStore.state.wallpaperState.availableWallpapers) + } + private enum class TimeRelation { BEFORE, NOW,