Bug 1951950 - Add the setup checklist UI to the homepage view holder, set the enabled/disabled state and the list of items to display with the Nimbus configuration. r=android-reviewers,gmalekpour

Differential Revision: https://phabricator.services.mozilla.com/D243426
This commit is contained in:
t-p-white
2025-03-31 14:23:50 +00:00
parent 323b594e88
commit 709b897ab8
14 changed files with 307 additions and 91 deletions

View File

@@ -14,8 +14,6 @@ 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.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -32,6 +30,7 @@ import mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview
import mozilla.components.compose.base.theme.AcornTheme
import org.mozilla.fenix.R
import org.mozilla.fenix.components.appstate.setup.checklist.ChecklistItem
import org.mozilla.fenix.home.sessioncontrol.SetupChecklistInteractor
import org.mozilla.fenix.theme.FirefoxTheme
private const val ROTATE_180 = 180F
@@ -39,25 +38,26 @@ private const val ROTATE_180 = 180F
/**
* Renders a checklist for onboarding users.
*
* @param interactor the interactor to handle user actions.
* @param checkListItems The list of [ChecklistItem] displayed in setup checklist.
* @param onChecklistItemClicked Gets invoked when the user clicks a check list item.
*/
@Composable
fun CheckListView(
interactor: SetupChecklistInteractor,
checkListItems: List<ChecklistItem>,
onChecklistItemClicked: (ChecklistItem) -> Unit,
) {
LazyColumn {
itemsIndexed(checkListItems) { index, item ->
Column {
checkListItems.forEachIndexed { index, item ->
when (item) {
is ChecklistItem.Group -> GroupWithTasks(
group = item,
onChecklistItemClicked = onChecklistItemClicked,
onChecklistItemClicked = { interactor.onChecklistItemClicked(item) },
// No divider for the last group, in case it is the last element
// in the parent composable.
addDivider = index != checkListItems.size - 1,
)
is ChecklistItem.Task -> Task(item, onChecklistItemClicked)
is ChecklistItem.Task -> Task(item) { interactor.onChecklistItemClicked(item) }
}
}
}
@@ -87,7 +87,7 @@ private fun Task(
}
Text(
text = task.title,
text = stringResource(task.title),
modifier = Modifier
.weight(1f)
.semantics { heading() },
@@ -142,7 +142,7 @@ private fun Group(
horizontalAlignment = Alignment.Start,
) {
Text(
text = group.title,
text = stringResource(group.title),
style = FirefoxTheme.typography.subtitle1,
color = FirefoxTheme.colors.textPrimary,
modifier = Modifier.semantics { heading() },
@@ -178,32 +178,35 @@ private fun TasksChecklistPreview() {
val tasks = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.EXPLORE_EXTENSION,
title = "First task",
title = R.string.setup_checklist_task_explore_extensions,
icon = R.drawable.ic_addons_extensions,
isCompleted = true,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.INSTALL_SEARCH_WIDGET,
title = "Second task",
title = R.string.setup_checklist_task_search_widget,
icon = R.drawable.ic_search,
isCompleted = false,
),
)
val group1 = ChecklistItem.Group(
title = "First group",
title = R.string.setup_checklist_group_essentials,
tasks = tasks,
isExpanded = true,
)
val group2 = ChecklistItem.Group(
title = "Second group",
title = R.string.setup_checklist_group_customize,
tasks = tasks,
isExpanded = false,
)
CheckListView(
interactor = object : SetupChecklistInteractor {
override fun onChecklistItemClicked(item: ChecklistItem) { /* no op */ }
override fun onRemoveChecklistButtonClicked() { /* no op */ }
},
checkListItems = listOf(group1, group2),
onChecklistItemClicked = {},
)
}
}

View File

@@ -34,6 +34,7 @@ import org.mozilla.fenix.components.appstate.setup.checklist.ChecklistItem
import org.mozilla.fenix.components.appstate.setup.checklist.Progress
import org.mozilla.fenix.components.appstate.setup.checklist.SetupChecklistState
import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.home.sessioncontrol.SetupChecklistInteractor
import org.mozilla.fenix.theme.FirefoxTheme
private val elevation = AcornLayout.AcornElevation.xLarge
@@ -43,22 +44,21 @@ private val shapeChecklist = RoundedCornerShape(size = AcornLayout.AcornCorner.l
* The Setup checklist displayed on homepage that contains onboarding tasks.
*
* @param setupChecklistState The [SetupChecklistState] used to manage the state of feature.
* @param interactor The [SetupChecklistInteractor] used to handle user interactions.
* @param title The checklist's title.
* @param subtitle The checklist's subtitle.
* @param labelRemoveChecklistButton The label of the checklist's button to remove it.
* @param onChecklistItemClicked Gets invoked when the user clicks a check list item.
* @param onRemoveChecklistButtonClicked Invoked when the remove button is clicked.
*/
@Composable
fun SetupChecklist(
setupChecklistState: SetupChecklistState,
interactor: SetupChecklistInteractor,
title: String,
subtitle: String,
labelRemoveChecklistButton: String,
onChecklistItemClicked: (ChecklistItem) -> Unit,
onRemoveChecklistButtonClicked: () -> Unit,
) {
Card(
modifier = Modifier.padding(16.dp),
shape = shapeChecklist,
backgroundColor = FirefoxTheme.colors.layer1,
elevation = elevation,
@@ -67,14 +67,20 @@ fun SetupChecklist(
verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.Start,
) {
Header(title, subtitle, setupChecklistState.progress)
Header(title = title, subtitle = subtitle, progress = setupChecklistState.progress)
CheckListView(setupChecklistState.checklistItems, onChecklistItemClicked)
CheckListView(
interactor = interactor,
checkListItems = setupChecklistState.checklistItems,
)
if (setupChecklistState.progress.allTasksCompleted()) {
Divider()
RemoveChecklistButton(labelRemoveChecklistButton, onRemoveChecklistButtonClicked)
RemoveChecklistButton(
interactor = interactor,
labelRemoveChecklistButton = labelRemoveChecklistButton,
)
}
}
}
@@ -90,14 +96,8 @@ private fun Header(
progress: Progress,
) {
Column(
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 12.dp,
),
verticalArrangement = Arrangement.spacedBy(
12.dp,
Alignment.Top,
),
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
horizontalAlignment = Alignment.Start,
) {
Text(
@@ -121,9 +121,9 @@ private fun Header(
* The button that will remove the setup checklist from homepage.
*/
@Composable
fun RemoveChecklistButton(
private fun RemoveChecklistButton(
interactor: SetupChecklistInteractor,
labelRemoveChecklistButton: String,
onRemoveChecklistButtonClicked: () -> Unit,
) {
Column(
modifier = Modifier.padding(24.dp),
@@ -136,9 +136,8 @@ fun RemoveChecklistButton(
) {
PrimaryButton(
text = labelRemoveChecklistButton,
modifier = Modifier
.width(width = FirefoxTheme.layout.size.maxWidth.small),
onClick = onRemoveChecklistButtonClicked,
modifier = Modifier.width(width = FirefoxTheme.layout.size.maxWidth.small),
onClick = { interactor.onRemoveChecklistButtonClicked() },
)
}
}
@@ -154,57 +153,50 @@ private class SetupChecklistPreviewParameterProvider :
}
private fun createPreviewTasks() = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.SET_AS_DEFAULT,
title = "Set as default browser",
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.EXPLORE_EXTENSION,
title = "Explore extensions",
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.SIGN_IN,
title = "Sign in to your account",
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = true,
),
setAsDefaultTaskPreview(),
webExtensionTaskPreview(),
signInTaskPreview(),
)
private fun setAsDefaultTaskPreview() = ChecklistItem.Task(
type = ChecklistItem.Task.Type.SET_AS_DEFAULT,
title = R.string.setup_checklist_task_default_browser,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
)
private fun webExtensionTaskPreview() = ChecklistItem.Task(
type = ChecklistItem.Task.Type.EXPLORE_EXTENSION,
title = R.string.setup_checklist_task_explore_extensions,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
)
private fun signInTaskPreview() = ChecklistItem.Task(
type = ChecklistItem.Task.Type.SIGN_IN,
title = R.string.setup_checklist_task_account_sync,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = true,
)
private fun createPreviewGroups() = listOf(
ChecklistItem.Group(
title = "First group",
tasks = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.SET_AS_DEFAULT,
title = "Set as default browser",
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.SIGN_IN,
title = "Sign in to your account",
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = true,
),
),
title = R.string.setup_checklist_group_essentials,
tasks = listOf(setAsDefaultTaskPreview(), signInTaskPreview()),
isExpanded = true,
),
ChecklistItem.Group(
title = "Second group",
title = R.string.setup_checklist_group_customize,
tasks = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.SELECT_THEME,
title = "Select a theme",
title = R.string.setup_checklist_task_toolbar_selection,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.CHANGE_TOOLBAR_PLACEMENT,
title = "Choose toolbar placement",
title = R.string.setup_checklist_task_theme_selection,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
@@ -212,17 +204,17 @@ private fun createPreviewGroups() = listOf(
isExpanded = false,
),
ChecklistItem.Group(
title = "Second group",
title = R.string.setup_checklist_group_helpful_tools,
tasks = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.INSTALL_SEARCH_WIDGET,
title = "Install search widget",
title = R.string.setup_checklist_task_search_widget,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.EXPLORE_EXTENSION,
title = "Explore extensions",
title = R.string.setup_checklist_task_explore_extensions,
icon = R.drawable.mozac_ic_web_extension_default_icon,
isCompleted = false,
),
@@ -247,11 +239,13 @@ private fun SetupChecklistPreview(
) {
SetupChecklist(
setupChecklistState = initialState,
interactor = object : SetupChecklistInteractor {
override fun onChecklistItemClicked(item: ChecklistItem) { /* no op */ }
override fun onRemoveChecklistButtonClicked() { /* no op */ }
},
title = "Finish setting up Firefox",
subtitle = "Complete all 6 steps to set up Firefox for the best browsing experience.",
labelRemoveChecklistButton = "Remove checklist",
onChecklistItemClicked = {},
onRemoveChecklistButtonClicked = {},
)
}
}

View File

@@ -32,6 +32,8 @@ import org.mozilla.fenix.autofill.AutofillSearchActivity
import org.mozilla.fenix.autofill.AutofillUnlockActivity
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.setup.checklist.SetupChecklistState
import org.mozilla.fenix.components.appstate.setup.checklist.getSetupChecklistCollection
import org.mozilla.fenix.components.metrics.MetricsMiddleware
import org.mozilla.fenix.crashes.CrashReportingAppMiddleware
import org.mozilla.fenix.crashes.SettingsCrashReportCache
@@ -49,6 +51,7 @@ import org.mozilla.fenix.home.blocklist.BlocklistHandler
import org.mozilla.fenix.home.blocklist.BlocklistMiddleware
import org.mozilla.fenix.home.middleware.HomeTelemetryMiddleware
import org.mozilla.fenix.messaging.state.MessagingMiddleware
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.perf.AppStartReasonProvider
import org.mozilla.fenix.perf.StartupActivityLog
@@ -224,6 +227,7 @@ class Components(private val context: Context) {
emptyList()
},
recentHistory = emptyList(),
setupChecklistState = setupChecklistState(),
).run { filterState(blocklistHandler) },
middlewares = listOf(
BlocklistMiddleware(blocklistHandler),
@@ -250,6 +254,13 @@ class Components(private val context: Context) {
}
}
private fun setupChecklistState() = if (settings.showSetupChecklist) {
val type = FxNimbus.features.setupChecklist.value().setupChecklistType
SetupChecklistState(checklistItems = getSetupChecklistCollection(type))
} else {
null
}
val remoteSettingsService = lazyMonitored {
RemoteSettingsService(
context,

View File

@@ -5,27 +5,29 @@
package org.mozilla.fenix.components.appstate.setup.checklist
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.mozilla.fenix.R
import org.mozilla.fenix.nimbus.SetupChecklistType
/**
* A sealed class representing an item in the setup checklist.
*
* @property title the title of the checklist item.
* @property title the string resource for the checklist item title.
*/
sealed class ChecklistItem(open val title: String) {
sealed class ChecklistItem(@StringRes open val title: Int) {
/**
* A data class representing an individual task in the checklist.
*
* @property type The type of the task.
* @property title the task's title.
* @property icon The icon resource to be displayed on the right of the task.
* @property type the type of the task.
* @property title the string resource of the task's title.
* @property icon the icon resource to be displayed on the right of the task.
* @property isCompleted whether the task has been completed.
*/
data class Task(
val type: Type,
override val title: String,
@StringRes override val title: Int,
@DrawableRes val icon: Int,
val isCompleted: Boolean,
val isCompleted: Boolean = false,
) : ChecklistItem(title) {
/**
@@ -44,14 +46,14 @@ sealed class ChecklistItem(open val title: String) {
/**
* A data class representing a group of tasks in the checklist.
*
* @property title the group's title.
* @property title the string resource of the group's title.
* @property tasks the list of tasks that belong to this group.
* @property isExpanded whether the group is currently expanded.
*/
data class Group(
override val title: String,
@StringRes override val title: Int,
val tasks: List<Task>,
val isExpanded: Boolean,
val isExpanded: Boolean = false,
) : ChecklistItem(title) {
val progress: Progress = tasks.getTaskProgress()
}
@@ -60,14 +62,13 @@ sealed class ChecklistItem(open val title: String) {
/**
* A data class representing the task progress.
*
* @property totalTasks The total number of tasks in the checklist.
* @property completedTasks The number of completed tasks.
* @property totalTasks the total number of tasks in the checklist.
* @property completedTasks the number of completed tasks.
*/
data class Progress(
var totalTasks: Int = 0,
var completedTasks: Int = 0,
) {
/**
* Checks if all tasks in the checklist are completed.
*/
@@ -90,3 +91,68 @@ fun List<ChecklistItem>.getTaskProgress(): Progress {
completedTasks = tasks.count { it.isCompleted },
)
}
/**
* Gets the checklist items for the given [SetupChecklistType].
*/
fun getSetupChecklistCollection(collection: SetupChecklistType) = when (collection) {
SetupChecklistType.COLLECTION_1 -> createTasksCollection()
SetupChecklistType.COLLECTION_2 -> createGroupsCollection()
}
private fun createTasksCollection() =
listOf(defaultBrowserTask(), exploreExtensionTask(), signInTask())
private fun defaultBrowserTask() = ChecklistItem.Task(
type = ChecklistItem.Task.Type.SET_AS_DEFAULT,
title = R.string.setup_checklist_task_default_browser,
icon = R.drawable.mozac_ic_web_extension_default_icon,
)
private fun exploreExtensionTask() = ChecklistItem.Task(
type = ChecklistItem.Task.Type.EXPLORE_EXTENSION,
title = R.string.setup_checklist_task_explore_extensions,
icon = R.drawable.mozac_ic_web_extension_default_icon,
)
private fun signInTask() = ChecklistItem.Task(
type = ChecklistItem.Task.Type.SIGN_IN,
title = R.string.setup_checklist_task_account_sync,
icon = R.drawable.mozac_ic_web_extension_default_icon,
)
private fun createGroupsCollection() =
listOf(essentialsGroup(), customizeGroup(), helpfulToolsGroup())
private fun essentialsGroup() = ChecklistItem.Group(
title = R.string.setup_checklist_group_essentials,
tasks = listOf(defaultBrowserTask(), signInTask()),
)
private fun customizeGroup() = ChecklistItem.Group(
title = R.string.setup_checklist_group_customize,
tasks = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.SELECT_THEME,
title = R.string.setup_checklist_task_theme_selection,
icon = R.drawable.mozac_ic_web_extension_default_icon,
),
ChecklistItem.Task(
type = ChecklistItem.Task.Type.CHANGE_TOOLBAR_PLACEMENT,
title = R.string.setup_checklist_task_theme_selection,
icon = R.drawable.mozac_ic_web_extension_default_icon,
),
),
)
private fun helpfulToolsGroup() = ChecklistItem.Group(
title = R.string.setup_checklist_group_helpful_tools,
tasks = listOf(
ChecklistItem.Task(
type = ChecklistItem.Task.Type.INSTALL_SEARCH_WIDGET,
title = R.string.setup_checklist_task_theme_selection,
icon = R.drawable.mozac_ic_web_extension_default_icon,
),
exploreExtensionTask(),
),
)

View File

@@ -27,6 +27,7 @@ import mozilla.components.service.pocket.PocketStory.PocketSponsoredStoryShim
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.setup.checklist.ChecklistItem
import org.mozilla.fenix.compose.MessageCardColors
import org.mozilla.fenix.compose.MessageCardState
import org.mozilla.fenix.compose.SelectableChipColors
@@ -109,6 +110,10 @@ internal object FakeHomepagePreview {
override fun showWallpapersOnboardingDialog(state: WallpaperState): Boolean {
return false
}
override fun onChecklistItemClicked(item: ChecklistItem) { /* no op */ }
override fun onRemoveChecklistButtonClicked() { /* no op */ }
}
internal val privateBrowsingInteractor

View File

@@ -13,6 +13,7 @@ import org.mozilla.fenix.home.recentvisits.interactor.RecentVisitsInteractor
import org.mozilla.fenix.home.sessioncontrol.CollectionInteractor
import org.mozilla.fenix.home.sessioncontrol.CustomizeHomeIteractor
import org.mozilla.fenix.home.sessioncontrol.MessageCardInteractor
import org.mozilla.fenix.home.sessioncontrol.SetupChecklistInteractor
import org.mozilla.fenix.home.sessioncontrol.TabSessionInteractor
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
import org.mozilla.fenix.home.sessioncontrol.WallpaperInteractor
@@ -36,4 +37,5 @@ interface HomepageInteractor :
PocketStoriesInteractor,
PrivateBrowsingInteractor,
SearchSelectorInteractor,
WallpaperInteractor
WallpaperInteractor,
SetupChecklistInteractor

View File

@@ -35,6 +35,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CustomizeHomeButtonView
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.MessageCardViewHolder
import org.mozilla.fenix.home.setup.ui.SetupChecklistViewHolder
import org.mozilla.fenix.home.topsites.TopSitePagerViewHolder
import org.mozilla.fenix.home.topsites.TopSitesViewHolder
import mozilla.components.feature.tab.collections.Tab as ComponentTab
@@ -169,6 +170,11 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object PocketCategoriesItem : AdapterItem(PocketCategoriesViewHolder.LAYOUT_ID)
object PocketRecommendationsFooterItem : AdapterItem(PocketRecommendationsHeaderViewHolder.LAYOUT_ID)
/**
* Adapter item to hold the setup checklist feature view.
*/
data object SetupChecklist : AdapterItem(SetupChecklistViewHolder.LAYOUT_ID)
object BottomSpacer : AdapterItem(BottomSpacerViewHolder.LAYOUT_ID)
/**
@@ -291,6 +297,11 @@ class SessionControlAdapter(
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor,
)
SetupChecklistViewHolder.LAYOUT_ID -> return SetupChecklistViewHolder(
composeView = ComposeView(parent.context),
viewLifecycleOwner = viewLifecycleOwner,
interactor = interactor,
)
}
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
@@ -331,6 +342,7 @@ class SessionControlAdapter(
is PocketRecommendationsHeaderViewHolder,
is PocketStoriesViewHolder,
is TopSitesViewHolder,
is SetupChecklistViewHolder,
-> {
// no op
// This previously called "composeView.disposeComposition" which would have the

View File

@@ -54,6 +54,7 @@ import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.setup.checklist.ChecklistItem
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.components.toolbar.navbar.shouldAddNavigationBar
import org.mozilla.fenix.ext.components
@@ -189,6 +190,16 @@ interface SessionControlController {
* @see [SessionControlInteractor.reportSessionMetrics]
*/
fun handleReportSessionMetrics(state: AppState)
/**
* @see [SetupChecklistInteractor.onChecklistItemClicked]
*/
fun onChecklistItemClicked(item: ChecklistItem)
/**
* @see [SetupChecklistInteractor.onRemoveChecklistButtonClicked]
*/
fun onRemoveChecklistButtonClicked()
}
@Suppress("TooManyFunctions", "LargeClass", "LongParameterList")
@@ -663,4 +674,12 @@ class DefaultSessionControlController(
HomeBookmarks.bookmarksCount.set(state.bookmarks.size.toLong())
}
override fun onChecklistItemClicked(item: ChecklistItem) {
appStore.dispatch(AppAction.SetupChecklistAction.ChecklistItemClicked(item))
}
override fun onRemoveChecklistButtonClicked() {
appStore.dispatch(AppAction.SetupChecklistAction.Closed)
}
}

View File

@@ -11,6 +11,7 @@ import mozilla.components.service.nimbus.messaging.Message
import mozilla.components.service.pocket.PocketStory
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.setup.checklist.ChecklistItem
import org.mozilla.fenix.home.bookmarks.Bookmark
import org.mozilla.fenix.home.bookmarks.controller.BookmarksController
import org.mozilla.fenix.home.interactor.HomepageInteractor
@@ -218,6 +219,21 @@ interface WallpaperInteractor {
fun showWallpapersOnboardingDialog(state: WallpaperState): Boolean
}
/**
* Interface for setup checklist feature related actions.
*/
interface SetupChecklistInteractor {
/**
* Gets invoked when the user clicks a check list item.
*/
fun onChecklistItemClicked(item: ChecklistItem)
/**
* Invoked when the remove button is clicked.
*/
fun onRemoveChecklistButtonClicked()
}
/**
* Interactor for the Home screen. Provides implementations for the CollectionInteractor,
* OnboardingInteractor, TopSiteInteractor, TabSessionInteractor, ToolbarInteractor,
@@ -301,6 +317,14 @@ class SessionControlInteractor(
return controller.handleShowWallpapersOnboardingDialog(state)
}
override fun onChecklistItemClicked(item: ChecklistItem) {
controller.onChecklistItemClicked(item)
}
override fun onRemoveChecklistButtonClicked() {
controller.onRemoveChecklistButtonClicked()
}
override fun onToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
controller.handleToggleCollectionExpanded(collection, expand)
}

View File

@@ -67,6 +67,10 @@ internal fun normalModeAdapterItems(
}
}
if (settings.showSetupChecklist) {
items.add(AdapterItem.SetupChecklist)
}
if (showRecentTab) {
shouldShowCustomizeHome = true
items.add(AdapterItem.RecentTabsHeader)

View File

@@ -0,0 +1,53 @@
/* 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.home.setup.ui
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.LifecycleOwner
import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.checklist.SetupChecklist
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.home.sessioncontrol.SetupChecklistInteractor
/**
* View holder for the Setup Checklist feature.
*
* @param composeView [ComposeView] which will be populated with Jetpack Compose UI content.
* @param viewLifecycleOwner [LifecycleOwner] to which this Composable will be tied to.
* @param interactor [SetupChecklistInteractor] to handle user interactions.
*/
class SetupChecklistViewHolder(
composeView: ComposeView,
viewLifecycleOwner: LifecycleOwner,
private val interactor: SetupChecklistInteractor,
) : ComposeViewHolder(composeView, viewLifecycleOwner) {
@Composable
override fun Content() {
val appStore = components.appStore
val setupChecklistState =
appStore.observeAsComposableState { state -> state.setupChecklistState }.value
if (setupChecklistState != null) {
SetupChecklist(
setupChecklistState = setupChecklistState,
interactor = interactor,
title = "Finish setting up Firefox",
subtitle = "Complete all 6 steps to set up Firefox for the best browsing experience.",
labelRemoveChecklistButton = "Remove checklist",
)
}
}
/**
* The layout ID for this view holder.
*/
companion object {
val LAYOUT_ID = View.generateViewId()
}
}

View File

@@ -14,6 +14,7 @@ import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.setup.checklist.SetupChecklistState
import org.mozilla.fenix.components.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.shouldShowRecentSyncedTabs
@@ -73,6 +74,7 @@ internal sealed class HomepageState {
* @property showRecentlyVisited Whether to show recent history section.
* @property showPocketStories Whether to show the pocket stories section.
* @property showSearchBar Whether to show the search bar.
* @property setupChecklistState Optional state of the setup checklist feature.
* @property topSiteColors The color set defined by [TopSiteColors] used to style a top site.
* @property cardBackgroundColor Background color for card items.
* @property buttonBackgroundColor Background [Color] for buttons.
@@ -97,6 +99,7 @@ internal sealed class HomepageState {
val showRecentlyVisited: Boolean,
val showPocketStories: Boolean,
val showSearchBar: Boolean,
val setupChecklistState: SetupChecklistState?,
val topSiteColors: TopSiteColors,
val cardBackgroundColor: Color,
val buttonBackgroundColor: Color,
@@ -167,6 +170,7 @@ internal sealed class HomepageState {
showPocketStories = settings.showPocketRecommendationsFeature &&
recommendationState.pocketStories.isNotEmpty() && firstFrameDrawn,
showSearchBar = settings.enableHomepageSearchBar,
setupChecklistState = setupChecklistState,
topSiteColors = TopSiteColors.colors(wallpaperState = wallpaperState),
cardBackgroundColor = wallpaperState.cardBackgroundColor,
buttonBackgroundColor = wallpaperState.buttonBackgroundColor,

View File

@@ -27,6 +27,7 @@ import mozilla.telemetry.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.History
import org.mozilla.fenix.GleanMetrics.RecentlyVisitedHomepage
import org.mozilla.fenix.R
import org.mozilla.fenix.checklist.SetupChecklist
import org.mozilla.fenix.compose.MessageCard
import org.mozilla.fenix.compose.button.TertiaryButton
import org.mozilla.fenix.compose.home.HomeSectionHeader
@@ -125,6 +126,16 @@ internal fun Homepage(
)
}
if (setupChecklistState != null) {
SetupChecklist(
setupChecklistState = setupChecklistState,
interactor = interactor,
title = "Finish setting up Firefox",
subtitle = "Complete all 6 steps to set up Firefox for the best browsing experience.",
labelRemoveChecklistButton = "Remove checklist",
)
}
if (showRecentTabs) {
RecentTabsSection(
interactor = interactor,
@@ -413,6 +424,7 @@ private fun HomepagePreview() {
showRecentlyVisited = true,
showPocketStories = true,
showSearchBar = true,
setupChecklistState = null,
topSiteColors = TopSiteColors.colors(),
cardBackgroundColor = WallpaperState.default.cardBackgroundColor,
buttonTextColor = WallpaperState.default.buttonTextColor,
@@ -448,6 +460,7 @@ private fun HomepagePreviewCollections() {
showRecentlyVisited = true,
showPocketStories = true,
showSearchBar = true,
setupChecklistState = null,
topSiteColors = TopSiteColors.colors(),
cardBackgroundColor = WallpaperState.default.cardBackgroundColor,
buttonTextColor = WallpaperState.default.buttonTextColor,

View File

@@ -2496,4 +2496,10 @@ class Settings(private val appContext: Context) : PreferencesHolder {
key = appContext.getPreferenceKey(R.string.pref_key_setup_step_extensions),
default = false,
)
/**
* Indicates whether or not to show the checklist feature.
*/
val showSetupChecklist: Boolean
get() = FxNimbus.features.setupChecklist.value().enabled
}