Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, Crash

ProfilerMarkers.homeActivityOnStart(binding.rootContainer, components.core.engine.profiler)

if (components.settings.longfoxEntryPointShownCount < Settings.LONGFOX_ENTRY_POINT_MAX_SHOWS) {
if (components.settings.longfoxPeekAnimationShownCount < Settings.LONGFOX_PEEK_ANIMATION_MAX_SHOWS) {
components.settings.appLaunchCount++
components.appStore.dispatch(
AppAction.UpdateShowFoxPeekAnimation(components.settings.shouldShowLongfoxPeekAnimationThisTime()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ internal object FakeHomepagePreview {
override fun onPrivacyReportTapped() { /* no op */ }

override fun onLongfoxEntryPointClicked() { /* no op */ }

override fun onLongfoxEntryPointShown() { /* no op */ }
}

internal val sportsInteractor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ class LogoController(
fun handleLongfoxEntryPointClicked() {
if (container != null && longFoxEnabled) longFoxFeature.start(container = container)
}

/**
* When the longfox entry point is shown, record the telemetry event.
*/
fun handleLongfoxEntryPointShown() {
longFoxFeature.onEntryPointShown()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ interface TrackingProtectionInteractor {
* Invoked when the longfox entry point text is clicked.
*/
fun onLongfoxEntryPointClicked()

/**
* Invoked when the longfox entry point is shown.
*/
fun onLongfoxEntryPointShown()
}

/**
Expand Down Expand Up @@ -456,6 +461,10 @@ class SessionControlInteractor(
logoController.handleLongfoxEntryPointClicked()
}

override fun onLongfoxEntryPointShown() {
logoController.handleLongfoxEntryPointShown()
}

override fun onGetCustomWallpaperClicked() {
sportsController.handleOnGetCustomWallpaperClicked()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ internal sealed class HomepageState {
* @property showPocketStoriesCarousel Whether to show the pocket stories section.
* @property showCollections Whether to show the collections section.
* @property showPrivacyReport Whether to show the privacy report section.
* @property showLongfoxEntryPoint Whether to show the longfox entry point section.
* @property longfoxEnabled Whether the longfox game is enabled.
* @property showLongfoxAnimation Whether to play the fox peek animation on the privacy report card.
* @property trackersBlockedCount The number of trackers blocked for the privacy report.
* @property sportsWidgetState State of the sports widget on the homepage.
* @property headerState State related to the header of the homepage.
Expand Down Expand Up @@ -120,7 +121,8 @@ internal sealed class HomepageState {
val showPocketStoriesCarousel: Boolean,
val showCollections: Boolean,
val showPrivacyReport: Boolean,
val showLongfoxEntryPoint: Boolean,
val longfoxEnabled: Boolean,
val showLongfoxAnimation: Boolean,
val trackersBlockedCount: Int,
val sportsWidgetState: SportsWidgetState,
override val headerState: HeaderState,
Expand Down Expand Up @@ -245,7 +247,8 @@ internal sealed class HomepageState {
recommendationState.pocketStories.isNotEmpty() && !settings.privateModeAndStoriesEntryPointEnabled,
showCollections = settings.collections,
showPrivacyReport = settings.showPrivacyReportFeature,
showLongfoxEntryPoint = settings.longfoxEnabled && longfoxEntryPointReady,
longfoxEnabled = settings.longfoxEnabled,
showLongfoxAnimation = settings.longfoxEnabled && longfoxEntryPointReady,
trackersBlockedCount = blockedTrackersState.trackersBlockedCount,
sportsWidgetState = sportsWidgetState,
headerState = buildHeaderState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,22 @@ internal fun Homepage(
is HomepageState.Normal -> {
val settings = components.settings
val appStore = components.appStore
LaunchedEffect(showLongfoxEntryPoint) {
if (showLongfoxEntryPoint) {
settings.longfoxEntryPointShownCount++
LaunchedEffect(showLongfoxAnimation) {
if (showLongfoxAnimation) {
settings.longfoxPeekAnimationShownCount++
appStore.dispatch(
AppAction.UpdateShowFoxPeekAnimation(false),
)
}
}

val longfoxEntryPointShown = longfoxEnabled && showPrivacyReport
LaunchedEffect(longfoxEntryPointShown) {
if (longfoxEntryPointShown) {
interactor.onLongfoxEntryPointShown()
}
}

if (showTopSites) {
TopSitesSection(
topSites = topSites,
Expand All @@ -250,7 +257,8 @@ internal fun Homepage(
onPrivacyReportTapped = interactor::onPrivacyReportTapped,
onLongfoxEntryPointClicked = interactor::onLongfoxEntryPointClicked,
modifier = Modifier.padding(top = 16.dp),
showLongfoxEntryPoint = showLongfoxEntryPoint,
longfoxEnabled = longfoxEnabled,
showLongfoxAnimation = showLongfoxAnimation,
)
}

Expand Down Expand Up @@ -284,7 +292,7 @@ internal fun Homepage(
interactor = interactor,
cardBackgroundColor = cardBackgroundColor,
recentTabs = recentTabs,
reducedTopSpacing = showPrivacyReport && showLongfoxEntryPoint,
reducedTopSpacing = showPrivacyReport && showLongfoxAnimation,
)

if (showRecentSyncedTab) {
Expand Down Expand Up @@ -672,7 +680,8 @@ private fun HomepagePreview() {
showPocketStoriesCarousel = true,
showCollections = true,
showPrivacyReport = true,
showLongfoxEntryPoint = false,
longfoxEnabled = false,
showLongfoxAnimation = false,
trackersBlockedCount = 754,
sportsWidgetState = SportsWidgetState(),
headerState = HeaderState.Normal(
Expand Down Expand Up @@ -727,7 +736,8 @@ private fun HomepageBannerPreview() {
showPocketStoriesCarousel = true,
showCollections = true,
showPrivacyReport = true,
showLongfoxEntryPoint = false,
longfoxEnabled = false,
showLongfoxAnimation = false,
trackersBlockedCount = 754,
sportsWidgetState = SportsWidgetState(),
headerState = HeaderState.Normal(
Expand Down Expand Up @@ -782,7 +792,8 @@ private fun HomepagePreviewCollections() {
showPocketStoriesCarousel = true,
showCollections = true,
showPrivacyReport = true,
showLongfoxEntryPoint = false,
longfoxEnabled = false,
showLongfoxAnimation = false,
trackersBlockedCount = 754,
sportsWidgetState = SportsWidgetState(),
headerState = HeaderState.Normal(
Expand Down Expand Up @@ -837,7 +848,8 @@ private fun MinimalHomepagePreview() {
showPocketStoriesCarousel = true,
showCollections = false,
showPrivacyReport = true,
showLongfoxEntryPoint = false,
longfoxEnabled = false,
showLongfoxAnimation = false,
trackersBlockedCount = 754,
sportsWidgetState = SportsWidgetState(),
headerState = HeaderState.Normal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ private fun MiddleSearchHomepagePreview() {
showPocketStoriesCarousel = true,
showCollections = true,
showPrivacyReport = true,
showLongfoxEntryPoint = true,
longfoxEnabled = true,
showLongfoxAnimation = true,
trackersBlockedCount = 754,
sportsWidgetState = SportsWidgetState(),
headerState = HeaderState.Normal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,33 +62,42 @@ private const val DISPLAY_DURATION_MS = 3000L
private const val TYPEWRITER_REVERSE_DELAY_MS = 1200L

internal const val LONGFOX_FOX_IMAGE_TEST_TAG = "trackersBlockedCard.longfoxFox"
internal const val PROTECTION_STATUS_PILL_TEST_TAG = "trackersBlockedCard.protectionStatusPill"

/**
* A card that displays the number of trackers blocked with an animated fox.
*
* When [longfoxEnabled] is true the pill launches the longfox game via [onLongfoxEntryPointClicked];
* otherwise it opens the privacy report via [onPrivacyReportTapped]. This routing is independent of
* [showLongfoxAnimation], which only controls the occasional fox peek animation.
*
* @param trackersBlockedCount The number of trackers blocked to display.
* @param modifier Modifier to be applied to the card.
* @param onPrivacyReportTapped Invoked when the pill is tapped. If null, the pill is not clickable.
* @param onLongfoxEntryPointClicked Invoked when the longfox typewriter text is tapped.
* @param showLongfoxEntryPoint Whether to show the fox animation and typewriter text.
* @param onPrivacyReportTapped Invoked when the pill is tapped while longfox is disabled. If null,
* the pill is not clickable.
* @param onLongfoxEntryPointClicked Invoked when the pill is tapped while longfox is enabled.
* @param longfoxEnabled Whether the longfox game is enabled, routing pill taps to
* [onLongfoxEntryPointClicked] instead of [onPrivacyReportTapped].
* @param showLongfoxAnimation Whether to play the fox peek animation and typewriter text.
*/
@Composable
fun TrackersBlockedCard(
trackersBlockedCount: Int,
modifier: Modifier = Modifier,
onPrivacyReportTapped: (() -> Unit)? = null,
onLongfoxEntryPointClicked: () -> Unit = {},
showLongfoxEntryPoint: Boolean = false,
longfoxEnabled: Boolean = false,
showLongfoxAnimation: Boolean = false,
) {
var isPlayingAnimation by remember { mutableStateOf(false) }
val foxOffsetY = remember { Animatable(1f) }
var isReversing by remember { mutableStateOf(false) }

// Latch the entry point becoming visible into isPlayingAnimation. showLongfoxEntryPoint is
// Latch the animation becoming visible into isPlayingAnimation. showLongfoxAnimation is
// cleared as soon as the homepage consumes it, so the animation is driven off the latch below
// to ensure it runs to completion rather than being cancelled mid-flight.
LaunchedEffect(showLongfoxEntryPoint) {
if (showLongfoxEntryPoint) {
LaunchedEffect(showLongfoxAnimation) {
if (showLongfoxAnimation) {
isPlayingAnimation = true
}
}
Expand Down Expand Up @@ -138,17 +147,15 @@ fun TrackersBlockedCard(

ProtectionStatusPill(
trackersBlockedCount = trackersBlockedCount,
onPrivacyReportTapped = onPrivacyReportTapped,
onClick = if (longfoxEnabled) onLongfoxEntryPointClicked else onPrivacyReportTapped,
)
}

if (isPlayingAnimation && foxOffsetY.value < 1f) {
Spacer(modifier = Modifier.height(6.dp))

TypewriterText(
modifier = Modifier
.clickable { onLongfoxEntryPointClicked() }
.padding(bottom = FirefoxTheme.layout.space.static300),
modifier = Modifier.padding(bottom = FirefoxTheme.layout.space.static300),
text = stringResource(R.string.help_catch_trackers),
isReversing = isReversing,
)
Expand All @@ -159,19 +166,20 @@ fun TrackersBlockedCard(
@Composable
private fun ProtectionStatusPill(
trackersBlockedCount: Int,
onPrivacyReportTapped: (() -> Unit)? = null,
onClick: (() -> Unit)? = null,
) {
val shape = MaterialTheme.shapes.extraLarge
Row(
modifier = Modifier
.testTag(PROTECTION_STATUS_PILL_TEST_TAG)
.background(
color = MaterialTheme.colorScheme.surfaceBright,
shape = shape,
)
.clip(shape)
.thenConditional(
Modifier.clickable { onPrivacyReportTapped?.invoke() },
{ onPrivacyReportTapped != null },
Modifier.clickable { onClick?.invoke() },
{ onClick != null },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
Expand Down Expand Up @@ -274,7 +282,8 @@ private fun TrackersBlockedCardPreview() {
TrackersBlockedCard(
trackersBlockedCount = 754,
onPrivacyReportTapped = {},
showLongfoxEntryPoint = true,
longfoxEnabled = true,
showLongfoxAnimation = true,
)
}
}
Expand All @@ -288,7 +297,7 @@ private fun TrackersBlockedCardEmptyPreview() {
TrackersBlockedCard(
trackersBlockedCount = 0,
onPrivacyReportTapped = {},
showLongfoxEntryPoint = false,
showLongfoxAnimation = false,
)
}
}
Expand Down Expand Up @@ -321,7 +330,8 @@ private fun TrackersBlockedCardInteractivePreview() {
TrackersBlockedCard(
trackersBlockedCount = 754,
onPrivacyReportTapped = {},
showLongfoxEntryPoint = true,
longfoxEnabled = true,
showLongfoxAnimation = true,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_AUDIBLE
import org.mozilla.fenix.tabstray.DefaultTabManagementFeatureHelper
import org.mozilla.fenix.termsofuse.TOU_VERSION
import org.mozilla.fenix.utils.Settings.Companion.LONGFOX_ENTRY_POINT_MAX_SHOWS
import org.mozilla.fenix.utils.Settings.Companion.LONGFOX_PEEK_ANIMATION_MAX_SHOWS
import org.mozilla.fenix.wallpapers.Wallpaper
import java.security.InvalidParameterException
import java.util.concurrent.TimeUnit.MILLISECONDS
Expand Down Expand Up @@ -103,8 +103,8 @@ class Settings(
private const val ALLOWED_INT = 2
private const val INACTIVE_TAB_MINIMUM_TO_SHOW_AUTO_CLOSE_DIALOG = 20

const val LONGFOX_ENTRY_POINT_MAX_SHOWS = 5
const val LONGFOX_ENTRY_POINT_LAUNCH_INTERVAL = 3
const val LONGFOX_PEEK_ANIMATION_MAX_SHOWS = 5
const val LONGFOX_PEEK_ANIMATION_LAUNCH_INTERVAL = 3

const val THIRTY_SECONDS_MS = 30 * 1000L
const val FOUR_HOURS_MS = 60 * 60 * 4 * 1000L
Expand Down Expand Up @@ -3225,31 +3225,31 @@ class Settings(

/**
* Number of times the app has been foregrounded (cold start or returned from background).
* Used to gate the longfox entry point animation.
* Used to gate the longfox peek animation.
*/
var appLaunchCount by intPreference(
key = appContext.getPreferenceKey(R.string.pref_key_app_launch_count),
default = 0,
)

/**
* Number of times the longfox entry point animation has been shown on the homepage.
* Capped at [LONGFOX_ENTRY_POINT_MAX_SHOWS]; once reached the animation is no longer shown.
* Number of times the longfox peek animation has been shown on the homepage.
* Capped at [LONGFOX_PEEK_ANIMATION_MAX_SHOWS]; once reached the animation is no longer shown.
*/
var longfoxEntryPointShownCount by intPreference(
key = appContext.getPreferenceKey(R.string.pref_key_longfox_entry_point_shown_count),
var longfoxPeekAnimationShownCount by intPreference(
key = appContext.getPreferenceKey(R.string.pref_key_longfox_peek_animation_shown_count),
default = 0,
)

/**
* Returns true when the longfox entry point animation should be armed for the current
* Returns true when the longfox peek animation should be armed for the current
* app foreground: feature enabled, not yet reached the show cap, and on every Nth launch.
*/
fun shouldShowLongfoxPeekAnimationThisTime(): Boolean =
longfoxEnabled &&
longfoxEntryPointShownCount < LONGFOX_ENTRY_POINT_MAX_SHOWS &&
longfoxPeekAnimationShownCount < LONGFOX_PEEK_ANIMATION_MAX_SHOWS &&
appLaunchCount > 0 &&
appLaunchCount % LONGFOX_ENTRY_POINT_LAUNCH_INTERVAL == 0
appLaunchCount % LONGFOX_PEEK_ANIMATION_LAUNCH_INTERVAL == 0

/**
* Indicates whether the app should automatically clean up downloaded files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@
<!-- Longfox -->
<string name="pref_key_enable_longfox" translatable="false">pref_key_enable_longfox</string>
<string name="pref_key_app_launch_count" translatable="false">pref_key_app_launch_count</string>
<string name="pref_key_longfox_entry_point_shown_count" translatable="false">pref_key_longfox_entry_point_shown_count</string>
<string name="pref_key_longfox_peek_animation_shown_count" translatable="false">pref_key_longfox_peek_animation_shown_count</string>

<!-- Sports Widget -->
<string name="pref_key_sports_selected_countries" translatable="false">pref_key_sports_selected_countries</string>
Expand Down
Loading
Loading