Bug 1821720 - Add juno onboarding pager ui

fenix/113.0
rahulsainani 1 year ago committed by mergify[bot]
parent d9ca5a8e0c
commit 128a59755a

@ -0,0 +1,131 @@
/* 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.onboarding.view
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.SupportUtils
/**
* Mapper to convert [JunoOnboardingPageType] to [OnboardingPageState] that is a param for
* [OnboardingPage] composable.
*/
@ReadOnlyComposable
@Composable
@Suppress("LongParameterList")
internal fun mapToOnboardingPageState(
onboardingPageType: JunoOnboardingPageType,
scrollToNextPageOrDismiss: () -> Unit,
onMakeFirefoxDefaultClick: () -> Unit,
onPrivacyPolicyClick: (String) -> Unit,
onSignInButtonClick: () -> Unit,
onNotificationPermissionButtonClick: () -> Unit,
): OnboardingPageState = when (onboardingPageType) {
JunoOnboardingPageType.DEFAULT_BROWSER -> defaultBrowserPageState(
onPositiveButtonClick = {
onMakeFirefoxDefaultClick()
scrollToNextPageOrDismiss()
},
onNegativeButtonClick = scrollToNextPageOrDismiss,
onUrlClick = onPrivacyPolicyClick,
)
JunoOnboardingPageType.SYNC_SIGN_IN -> syncSignInPageState(
onPositiveButtonClick = {
onSignInButtonClick()
scrollToNextPageOrDismiss()
},
onNegativeButtonClick = scrollToNextPageOrDismiss,
)
JunoOnboardingPageType.NOTIFICATION_PERMISSION -> notificationPermissionPageState(
onPositiveButtonClick = {
onNotificationPermissionButtonClick()
scrollToNextPageOrDismiss()
},
onNegativeButtonClick = scrollToNextPageOrDismiss,
)
}
@Composable
@ReadOnlyComposable
private fun notificationPermissionPageState(
onPositiveButtonClick: () -> Unit,
onNegativeButtonClick: () -> Unit,
) = OnboardingPageState(
image = R.drawable.ic_notification_permission,
title = stringResource(
id = R.string.juno_onboarding_enable_notifications_title,
formatArgs = arrayOf(stringResource(R.string.app_name)),
),
description = stringResource(
id = R.string.juno_onboarding_enable_notifications_description,
formatArgs = arrayOf(stringResource(R.string.app_name)),
),
primaryButton = Action(
text = stringResource(id = R.string.juno_onboarding_enable_notifications_positive_button),
onClick = onPositiveButtonClick,
),
secondaryButton = Action(
text = stringResource(id = R.string.juno_onboarding_enable_notifications_negative_button),
onClick = onNegativeButtonClick,
),
onRecordImpressionEvent = {},
)
@Composable
@ReadOnlyComposable
private fun syncSignInPageState(
onPositiveButtonClick: () -> Unit,
onNegativeButtonClick: () -> Unit,
) = OnboardingPageState(
image = R.drawable.ic_onboarding_sync,
title = stringResource(id = R.string.juno_onboarding_sign_in_title),
description = stringResource(id = R.string.juno_onboarding_sign_in_description),
primaryButton = Action(
text = stringResource(id = R.string.juno_onboarding_sign_in_positive_button),
onClick = onPositiveButtonClick,
),
secondaryButton = Action(
text = stringResource(id = R.string.juno_onboarding_sign_in_negative_button),
onClick = onNegativeButtonClick,
),
onRecordImpressionEvent = {},
)
@Composable
@ReadOnlyComposable
private fun defaultBrowserPageState(
onPositiveButtonClick: () -> Unit,
onNegativeButtonClick: () -> Unit,
onUrlClick: (String) -> Unit,
) = OnboardingPageState(
image = R.drawable.ic_onboarding_welcome,
title = stringResource(
id = R.string.juno_onboarding_default_browser_title,
formatArgs = arrayOf(stringResource(R.string.app_name)),
),
description = stringResource(
id = R.string.juno_onboarding_default_browser_description,
formatArgs = arrayOf(
stringResource(R.string.firefox),
stringResource(R.string.juno_onboarding_default_browser_description_link_text),
),
),
linkTextState = LinkTextState(
text = stringResource(id = R.string.juno_onboarding_default_browser_description_link_text),
url = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
onClick = onUrlClick,
),
primaryButton = Action(
text = stringResource(id = R.string.juno_onboarding_default_browser_positive_button),
onClick = onPositiveButtonClick,
),
secondaryButton = Action(
text = stringResource(id = R.string.juno_onboarding_default_browser_negative_button),
onClick = onNegativeButtonClick,
),
onRecordImpressionEvent = {},
)

@ -0,0 +1,14 @@
/* 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.onboarding.view
/**
* Model for different types of Onboarding Pages.
*/
enum class JunoOnboardingPageType {
DEFAULT_BROWSER,
SYNC_SIGN_IN,
NOTIFICATION_PERMISSION,
}

@ -0,0 +1,182 @@
/* 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.onboarding.view
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState
import kotlinx.coroutines.launch
import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.PagerIndicator
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme
private val OnboardingPageTypeList = listOf(
JunoOnboardingPageType.DEFAULT_BROWSER,
JunoOnboardingPageType.NOTIFICATION_PERMISSION,
JunoOnboardingPageType.SYNC_SIGN_IN,
)
/**
* A screen for displaying juno onboarding.
*
* @param onMakeFirefoxDefaultClick Invoked when the positive button on default browser page is
* clicked.
* @param onPrivacyPolicyClick Invoked when the privacy policy link text is clicked.
* @param onSignInButtonClick Invoked when the positive button on the sign in page is clicked.
* @param onNotificationPermissionButtonClick Invoked when the positive button on notification
* page is clicked.
* @param onFinish Invoked when the onboarding is completed.
*/
@Composable
fun JunoOnboardingScreen(
onMakeFirefoxDefaultClick: () -> Unit,
onPrivacyPolicyClick: (String) -> Unit,
onSignInButtonClick: () -> Unit,
onNotificationPermissionButtonClick: () -> Unit,
onFinish: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
val pagerState = rememberPagerState()
val isSignedIn: State<Boolean?> = components.backgroundServices.syncStore
.observeAsComposableState { it.account != null }
BackHandler(enabled = pagerState.currentPage > 0) {
coroutineScope.launch {
pagerState.animateScrollToPage(pagerState.currentPage - 1)
}
}
val scrollToNextPageOrDismiss: () -> Unit = {
if (pagerState.currentPage == pagerState.pageCount - 1) {
onFinish()
} else {
coroutineScope.launch {
pagerState.animateScrollToPage(pagerState.currentPage + 1)
}
}
}
LaunchedEffect(isSignedIn.value) {
if (isSignedIn.value == true) {
scrollToNextPageOrDismiss()
}
}
JunoOnboardingContent(
pagerState = pagerState,
scrollToNextPageOrDismiss = scrollToNextPageOrDismiss,
onMakeFirefoxDefaultClick = onMakeFirefoxDefaultClick,
onPrivacyPolicyClick = onPrivacyPolicyClick,
onSignInButtonClick = onSignInButtonClick,
onNotificationPermissionButtonClick = onNotificationPermissionButtonClick,
)
}
@Composable
@Suppress("LongParameterList")
private fun JunoOnboardingContent(
pagerState: PagerState,
scrollToNextPageOrDismiss: () -> Unit,
onMakeFirefoxDefaultClick: () -> Unit,
onPrivacyPolicyClick: (String) -> Unit,
onSignInButtonClick: () -> Unit,
onNotificationPermissionButtonClick: () -> Unit,
) {
val nestedScrollConnection = remember { DisableForwardSwipeNestedScrollConnection(pagerState) }
Column(
modifier = Modifier
.background(FirefoxTheme.colors.layer1)
.statusBarsPadding()
.navigationBarsPadding(),
) {
HorizontalPager(
count = OnboardingPageTypeList.size,
state = pagerState,
key = { OnboardingPageTypeList[it] },
modifier = Modifier
.weight(1f)
.nestedScroll(nestedScrollConnection),
) { pageIndex ->
val onboardingPageType = OnboardingPageTypeList[pageIndex]
val pageState = mapToOnboardingPageState(
onboardingPageType = onboardingPageType,
scrollToNextPageOrDismiss = scrollToNextPageOrDismiss,
onMakeFirefoxDefaultClick = onMakeFirefoxDefaultClick,
onPrivacyPolicyClick = onPrivacyPolicyClick,
onSignInButtonClick = onSignInButtonClick,
onNotificationPermissionButtonClick = onNotificationPermissionButtonClick,
)
OnboardingPage(pageState = pageState)
}
PagerIndicator(
pagerState = pagerState,
activeColor = FirefoxTheme.colors.actionPrimary,
inactiveColor = FirefoxTheme.colors.actionSecondary,
leaveTrail = true,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 16.dp),
)
}
}
private class DisableForwardSwipeNestedScrollConnection(
private val pagerState: PagerState,
) : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
return if (available.x > 0) {
// Allow going back on swipe
Offset.Zero
} else {
// For forward swipe, only allow if the visible item offset is less than 0,
// this would be a result of a slow back fling, and we should allow snapper to
// snap to the appropriate item.
// Else consume the whole offset and disable going forward.
if (pagerState.currentPageOffset < 0) {
return Offset.Zero
} else {
Offset(available.x, available.y)
}
}
}
}
@LightDarkPreview
@Composable
private fun JunoOnboardingScreenPreview() {
FirefoxTheme {
JunoOnboardingContent(
pagerState = PagerState(0),
onMakeFirefoxDefaultClick = {},
onPrivacyPolicyClick = {},
onSignInButtonClick = {},
onNotificationPermissionButtonClick = {},
scrollToNextPageOrDismiss = {},
)
}
}

@ -288,36 +288,36 @@
<!-- Juno first user onboarding flow experiment -->
<!-- Title for set firefox as default browser screen.
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="juno_onboarding_default_browser_title" tools:ignore="UnusedResources">Make %s your go-to browser</string>
<string name="juno_onboarding_default_browser_title">Make %s your go-to browser</string>
<!-- Description for set firefox as default browser screen.
The first parameter is the Firefox brand name.
The second parameter is the string with key "juno_onboarding_default_browser_description_link_text". -->
<string name="juno_onboarding_default_browser_description" tools:ignore="UnusedResources">%1$s puts people over profits and defends your privacy by blocking cross-site trackers.\n\nLearn more in our %2$s.</string>
<string name="juno_onboarding_default_browser_description">%1$s puts people over profits and defends your privacy by blocking cross-site trackers.\n\nLearn more in our %2$s.</string>
<!-- Text for the link to the privacy notice webpage for set as firefox default browser screen.
This is part of the string with the key "juno_onboarding_default_browser_description". -->
<string name="juno_onboarding_default_browser_description_link_text" tools:ignore="UnusedResources">privacy notice</string>
<string name="juno_onboarding_default_browser_description_link_text">privacy notice</string>
<!-- Text for the button to set firefox as default browser on the device -->
<string name="juno_onboarding_default_browser_positive_button" tools:ignore="UnusedResources">Set as default browser</string>
<string name="juno_onboarding_default_browser_positive_button">Set as default browser</string>
<!-- Text for the button dismiss the screen and move on with the flow -->
<string name="juno_onboarding_default_browser_negative_button" tools:ignore="UnusedResources">Not now</string>
<string name="juno_onboarding_default_browser_negative_button">Not now</string>
<!-- Title for sign in to sync screen. -->
<string name="juno_onboarding_sign_in_title" tools:ignore="UnusedResources">Hop from phone to laptop and back</string>
<string name="juno_onboarding_sign_in_title">Hop from phone to laptop and back</string>
<!-- Description for sign in to sync screen. -->
<string name="juno_onboarding_sign_in_description" tools:ignore="UnusedResources">Grab tabs and passwords from your other devices to pick up where you left off.</string>
<string name="juno_onboarding_sign_in_description">Grab tabs and passwords from your other devices to pick up where you left off.</string>
<!-- Text for the button to sign in to sync on the device -->
<string name="juno_onboarding_sign_in_positive_button" tools:ignore="UnusedResources">Sign in</string>
<string name="juno_onboarding_sign_in_positive_button">Sign in</string>
<!-- Text for the button dismiss the screen and move on with the flow -->
<string name="juno_onboarding_sign_in_negative_button" tools:ignore="UnusedResources">Not now</string>
<string name="juno_onboarding_sign_in_negative_button">Not now</string>
<!-- Title for enable notification permission screen.
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="juno_onboarding_enable_notifications_title" tools:ignore="UnusedResources">Notifications help you do more with %s</string>
<string name="juno_onboarding_enable_notifications_title">Notifications help you do more with %s</string>
<!-- Description for enable notification permission screen.
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="juno_onboarding_enable_notifications_description" tools:ignore="UnusedResources">Send tabs between devices, manage downloads, and get tips on getting the most out of %s.</string>
<string name="juno_onboarding_enable_notifications_description">Send tabs between devices, manage downloads, and get tips on getting the most out of %s.</string>
<!-- Text for the button to request notification permission on the device -->
<string name="juno_onboarding_enable_notifications_positive_button" tools:ignore="UnusedResources">Turn on notifications</string>
<string name="juno_onboarding_enable_notifications_positive_button">Turn on notifications</string>
<!-- Text for the button dismiss the screen and move on with the flow -->
<string name="juno_onboarding_enable_notifications_negative_button" tools:ignore="UnusedResources">Not now</string>
<string name="juno_onboarding_enable_notifications_negative_button">Not now</string>
<!-- Search Widget -->
<!-- Content description for searching with a widget. The first parameter is the name of the application.-->

Loading…
Cancel
Save