diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index f03b7f90a96a..81efede87f03 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -202,6 +202,11 @@ interface SessionControlController { */ fun handleShowOnboardingDialog() + /** + * @see [OnboardingInteractor.showWallpapersOnboardingDialog] + */ + fun handleShowWallpapersOnboardingDialog() + /** * @see [SessionControlInteractor.reportSessionMetrics] */ @@ -513,6 +518,13 @@ class DefaultSessionControlController( ) } + override fun handleShowWallpapersOnboardingDialog() { + navController.nav( + R.id.homeFragment, + HomeFragmentDirections.actionGlobalWallpaperOnboardingDialog() + ) + } + override fun handleReadPrivacyNoticeClicked() { activity.openToBrowserAndLoad( searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE), diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index b9aa62db0274..b426043a12ee 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -168,6 +168,11 @@ interface OnboardingInteractor { * historyMetadata and pocketArticles sections. */ fun showOnboardingDialog() + + /** + * Show the Wallpapers onboarding dialog to onboard users about the feature. + */ + fun showWallpapersOnboardingDialog() } interface CustomizeHomeIteractor { @@ -332,6 +337,10 @@ class SessionControlInteractor( controller.handleShowOnboardingDialog() } + override fun showWallpapersOnboardingDialog() { + controller.handleShowWallpapersOnboardingDialog() + } + override fun onToggleCollectionExpanded(collection: TabCollection, expand: Boolean) { controller.handleToggleCollectionExpanded(collection, expand) } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 009387610335..41acdf5c205f 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -217,16 +217,26 @@ class SessionControlView( override fun onLayoutCompleted(state: RecyclerView.State?) { super.onLayoutCompleted(state) + var isCFRDisplayed = false if (!context.settings().showHomeOnboardingDialog) { if (context.settings().shouldShowJumpBackInCFR) { - JumpBackInCFRDialog(view).showIfNeeded() + isCFRDisplayed = JumpBackInCFRDialog(view).showIfNeeded() } else if (context.settings().showSyncCFR) { - SyncCFRPresenter( + isCFRDisplayed = SyncCFRPresenter( context = context, recyclerView = view, ).showSyncCFR() } } + if (!isCFRDisplayed && + !context.settings().showHomeOnboardingDialog && + (!context.settings().shouldShowJumpBackInCFR || + !context.settings().showSyncCFR) && + context.settings().showWallpaperOnboarding && + onboarding.userHasBeenOnboarded() + ) { + interactor.showWallpapersOnboardingDialog() + } // We want some parts of the home screen UI to be rendered first if they are // the most prominent parts of the visible part of the screen. diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt b/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt index f362c81a7ef1..6b201fc81688 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/JumpBackInCFRDialog.kt @@ -25,7 +25,7 @@ class JumpBackInCFRDialog(val recyclerView: RecyclerView) { /** * Try to show the crf dialog if it hasn't been shown before. */ - fun showIfNeeded() { + fun showIfNeeded(): Boolean { val jumpBackInView = findJumpBackInView() jumpBackInView?.let { val crfDialog = createJumpCRF(anchor = jumpBackInView) @@ -33,8 +33,10 @@ class JumpBackInCFRDialog(val recyclerView: RecyclerView) { val context = jumpBackInView.context context.settings().shouldShowJumpBackInCFR = false it.show() + return true } } + return false } private fun findJumpBackInView(): View? { diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/SyncCFRPresenter.kt b/app/src/main/java/org/mozilla/fenix/onboarding/SyncCFRPresenter.kt index 236636da5352..5d01d678cd86 100644 --- a/app/src/main/java/org/mozilla/fenix/onboarding/SyncCFRPresenter.kt +++ b/app/src/main/java/org/mozilla/fenix/onboarding/SyncCFRPresenter.kt @@ -37,7 +37,7 @@ class SyncCFRPresenter( /** * Find the synced view and if it is available, then show the synced tab CFR. */ - fun showSyncCFR() { + fun showSyncCFR() : Boolean { findSyncTabsView()?.let { CFRPopup( text = context.getString(R.string.sync_cfr_message), @@ -61,7 +61,9 @@ class SyncCFRPresenter( context.settings().shouldShowJumpBackInCFR = false Onboarding.synCfrShown.record(NoExtras()) + return true } + return false } private fun findSyncTabsView(): View? { 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 77f5d9c9e4ac..cd1e1d210c54 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 @@ -149,16 +149,18 @@ private fun WallpaperSnackbar( @OptIn(ExperimentalFoundationApi::class) @Composable @Suppress("LongParameterList") -private fun WallpaperThumbnails( +fun WallpaperThumbnails( wallpapers: List, defaultWallpaper: Wallpaper, loadWallpaperResource: suspend (Wallpaper) -> Bitmap?, selectedWallpaper: Wallpaper, numColumns: Int = 3, onSelectWallpaper: (Wallpaper) -> Unit, + verticalPadding: Int = 30, + horizontalPadding: Int = 20 ) { Surface(color = FirefoxTheme.colors.layer2) { - Column(modifier = Modifier.padding(vertical = 30.dp, horizontal = 20.dp)) { + Column(modifier = Modifier.padding(vertical = verticalPadding.dp, horizontal = horizontalPadding.dp)) { val numRows = (wallpapers.size + numColumns - 1) / numColumns for (rowIndex in 0 until numRows) { Row { diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 7bccdddf4256..66a8f71fee0f 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -209,7 +209,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { /** * Indicates if the wallpaper onboarding dialog should be shown. */ - val showWallpaperOnboarding by lazyFeatureFlagPreference( + var showWallpaperOnboarding by lazyFeatureFlagPreference( key = appContext.getPreferenceKey(R.string.pref_key_wallpapers_onboarding), featureFlag = FeatureFlags.wallpaperOnboardingEnabled, default = { mr2022Sections[Mr2022Section.WALLPAPERS_SELECTION_TOOL] == true }, diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperOnboarding.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperOnboarding.kt new file mode 100644 index 000000000000..3f309168324e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperOnboarding.kt @@ -0,0 +1,135 @@ +/* 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 + +import android.graphics.Bitmap +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.mozilla.fenix.R +import org.mozilla.fenix.settings.wallpaper.WallpaperThumbnails +import org.mozilla.fenix.theme.FirefoxTheme +import org.mozilla.fenix.theme.Theme + +/** + * A view that shows content of a WallpaperOnboarding dialog. + * + * @param wallpapers Wallpapers to add to grid. + * @param currentWallpaper The currently selected wallpaper. + * @param onCloseClicked Callback for when the close button is clicked. + * @param onButtonClicked Callback for when the bottom text button is clicked. + * @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. + */ + +@Suppress("LongParameterList") +@ExperimentalMaterialApi +@Composable +fun WallpaperOnboarding( + wallpapers: List, + currentWallpaper: Wallpaper, + onCloseClicked: () -> Unit, + onButtonClicked: () -> Unit, + loadWallpaperResource: suspend (Wallpaper) -> Bitmap?, + onSelectWallpaper: (Wallpaper) -> Unit, +) { + Surface( + color = FirefoxTheme.colors.layer2, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.mozac_ic_close), + contentDescription = stringResource(id = R.string.close_tab), + tint = FirefoxTheme.colors.iconPrimary, + modifier = Modifier + .clickable { onCloseClicked() } + .size(24.dp) + .align(Alignment.End) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.wallpapers_onboarding_dialog_title_text), + color = FirefoxTheme.colors.textPrimary, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.wallpapers_onboarding_dialog_body_text), + color = FirefoxTheme.colors.textSecondary, + fontSize = 12.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + WallpaperThumbnails( + wallpapers = wallpapers, + defaultWallpaper = Wallpaper.Default, + loadWallpaperResource = { loadWallpaperResource(it) }, + selectedWallpaper = currentWallpaper, + onSelectWallpaper = { onSelectWallpaper(it) }, + verticalPadding = 16, + horizontalPadding = 0 + ) + TextButton( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .fillMaxWidth(), + onClick = { onButtonClicked() } + ) { + Text( + text = stringResource(R.string.wallpapers_onboarding_dialog_explore_more_button_text), + fontWeight = FontWeight.Bold, + color = FirefoxTheme.colors.textAccent, + fontSize = 14.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + Spacer(modifier = Modifier.height(12.dp)) + } + } +} + +@Preview +@ExperimentalMaterialApi +@Composable +private fun WallpaperSnackbarPreview() { + FirefoxTheme(theme = Theme.getTheme()) { + WallpaperOnboarding( + wallpapers = listOf(Wallpaper.Default), + currentWallpaper = Wallpaper.Default, + onCloseClicked = {}, + onButtonClicked = {}, + loadWallpaperResource = { null }, + onSelectWallpaper = {} + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperOnboardingDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperOnboardingDialogFragment.kt new file mode 100644 index 000000000000..22531beff0cd --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/wallpapers/WallpaperOnboardingDialogFragment.kt @@ -0,0 +1,91 @@ +/* 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 + +import android.annotation.SuppressLint +import android.content.pm.ActivityInfo +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.navigation.fragment.findNavController +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.coroutines.launch +import mozilla.components.lib.state.ext.observeAsComposableState +import org.mozilla.fenix.NavGraphDirections +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.theme.FirefoxTheme + +/** + * Dialog displaying the wallpapers onboarding. + */ +@OptIn(ExperimentalMaterialApi::class) +class WallpaperOnboardingDialogFragment : BottomSheetDialogFragment() { + private val appStore by lazy { + requireComponents.appStore + } + + private val wallpaperUseCases by lazy { + requireComponents.useCases.wallpaperUseCases + } + + @SuppressLint("SourceLockedOrientationActivity") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.WallpaperOnboardingDialogStyle) + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + + override fun onDestroy() { + super.onDestroy() + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + requireContext().settings().showWallpaperOnboarding = false + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + + setContent { + FirefoxTheme { + val wallpapers = appStore.observeAsComposableState { state -> + state.wallpaperState.availableWallpapers + }.value ?: listOf() + val currentWallpaper = appStore.observeAsComposableState { state -> + state.wallpaperState.currentWallpaper + }.value ?: Wallpaper.Default + + val coroutineScope = rememberCoroutineScope() + + WallpaperOnboarding( + wallpapers = wallpapers, + currentWallpaper = currentWallpaper, + onCloseClicked = { dismiss() }, + onButtonClicked = { + val directions = NavGraphDirections.actionGlobalWallpaperSettingsFragment() + findNavController().navigate(directions) + }, + loadWallpaperResource = { wallpaperUseCases.loadThumbnail(it) }, + onSelectWallpaper = { + coroutineScope.launch { wallpaperUseCases.selectWallpaper(it) } + } + ) + } + } + } +} diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index bfe337e02ae2..c70d436acf5f 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -34,6 +34,11 @@ app:destination="@id/homeOnboardingDialogFragment" app:popUpTo="@id/homeFragment" /> + + + + Firefox logo - change the wallpaper, button + + Try a splash of color + + Choose a wallpaper that speaks to you. + + Explore more wallpapers diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 31e02b649a80..f88d54f56882 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -564,6 +564,10 @@