Skip to content

Commit 56d9f1a

Browse files
committed
Bug 2050032 - reset peeking fox animation when restoring from background.
1 parent 908ad9e commit 56d9f1a

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

mobile/android/fenix/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackersBlockedCard.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.compose.runtime.getValue
3232
import androidx.compose.runtime.mutableFloatStateOf
3333
import androidx.compose.runtime.mutableStateOf
3434
import androidx.compose.runtime.remember
35+
import androidx.compose.runtime.rememberCoroutineScope
3536
import androidx.compose.runtime.setValue
3637
import androidx.compose.ui.Alignment
3738
import androidx.compose.ui.Modifier
@@ -46,7 +47,10 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
4647
import androidx.compose.ui.unit.IntOffset
4748
import androidx.compose.ui.unit.dp
4849
import androidx.compose.ui.unit.sp
50+
import androidx.lifecycle.Lifecycle
51+
import androidx.lifecycle.compose.LifecycleEventEffect
4952
import kotlinx.coroutines.delay
53+
import kotlinx.coroutines.launch
5054
import mozilla.components.compose.base.modifier.thenConditional
5155
import org.mozilla.fenix.R
5256
import org.mozilla.fenix.theme.FirefoxTheme
@@ -92,6 +96,7 @@ fun TrackersBlockedCard(
9296
var isPlayingAnimation by remember { mutableStateOf(false) }
9397
val foxOffsetY = remember { Animatable(1f) }
9498
var isReversing by remember { mutableStateOf(false) }
99+
val coroutineScope = rememberCoroutineScope()
95100

96101
// Latch the animation becoming visible into isPlayingAnimation. showLongfoxAnimation is
97102
// cleared as soon as the homepage consumes it, so the animation is driven off the latch below
@@ -102,9 +107,20 @@ fun TrackersBlockedCard(
102107
}
103108
}
104109

110+
// see bug 2050032.
111+
// animateTo is frame-driven and freezes while backgrounded, but the delays below keep running,
112+
// so a peek interrupted by backgrounding would otherwise play its retract transition on return.
113+
// Reset to the hidden state when the card leaves the foreground so it restores cleanly.
114+
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
115+
isPlayingAnimation = false
116+
coroutineScope.launch { foxOffsetY.snapTo(1f) }
117+
}
118+
105119
LaunchedEffect(isPlayingAnimation) {
106120
if (isPlayingAnimation) {
107121
isReversing = false
122+
// make sure we always start from the beginning position.
123+
foxOffsetY.snapTo(1f)
108124
foxOffsetY.animateTo(
109125
targetValue = 0f,
110126
animationSpec = tween(durationMillis = FOX_ANIMATION_DURATION, easing = Ease),

mobile/android/fenix/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackersBlockedCardTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@
44

55
package org.mozilla.fenix.trackingprotection
66

7+
import androidx.compose.runtime.CompositionLocalProvider
78
import androidx.compose.runtime.getValue
89
import androidx.compose.runtime.mutableStateOf
910
import androidx.compose.runtime.setValue
1011
import androidx.compose.ui.test.junit4.v2.createComposeRule
1112
import androidx.compose.ui.test.onNodeWithTag
1213
import androidx.compose.ui.test.performClick
14+
import androidx.lifecycle.Lifecycle
15+
import androidx.lifecycle.compose.LocalLifecycleOwner
1316
import androidx.test.ext.junit.runners.AndroidJUnit4
1417
import org.junit.Assert.assertEquals
1518
import org.junit.Rule
1619
import org.junit.Test
1720
import org.junit.runner.RunWith
21+
import org.mozilla.fenix.helpers.lifecycle.TestLifecycleOwner
1822
import org.mozilla.fenix.theme.FirefoxTheme
1923
import org.mozilla.fenix.theme.Theme
2024

@@ -66,6 +70,37 @@ class TrackersBlockedCardTest {
6670
composeTestRule.onNodeWithTag(LONGFOX_FOX_IMAGE_TEST_TAG).assertDoesNotExist()
6771
}
6872

73+
@Test
74+
fun `when the card is backgrounded mid-animation the fox is removed instead of replaying its retract`() {
75+
// The animation is driven by the compose clock, so pause it to step through deterministically.
76+
composeTestRule.mainClock.autoAdvance = false
77+
78+
val lifecycleOwner = TestLifecycleOwner(initialState = Lifecycle.State.RESUMED)
79+
composeTestRule.setContent {
80+
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
81+
FirefoxTheme(theme = Theme.Light) {
82+
TrackersBlockedCard(
83+
trackersBlockedCount = 3,
84+
showLongfoxAnimation = true,
85+
)
86+
}
87+
}
88+
}
89+
90+
// The fox is peeking out once the animation starts.
91+
composeTestRule.mainClock.advanceTimeBy(100L)
92+
composeTestRule.onNodeWithTag(LONGFOX_FOX_IMAGE_TEST_TAG).assertExists()
93+
94+
// Sending the app to the background must reset the card to its hidden default state.
95+
composeTestRule.runOnUiThread { lifecycleOwner.onStop() }
96+
composeTestRule.mainClock.advanceTimeBy(100L)
97+
composeTestRule.onNodeWithTag(LONGFOX_FOX_IMAGE_TEST_TAG).assertDoesNotExist()
98+
99+
// It must stay hidden rather than playing the retract transition when the app returns.
100+
composeTestRule.mainClock.advanceTimeBy(20_000L)
101+
composeTestRule.onNodeWithTag(LONGFOX_FOX_IMAGE_TEST_TAG).assertDoesNotExist()
102+
}
103+
69104
@Test
70105
fun `when longfox is enabled, tapping the pill launches the game rather than the privacy report`() {
71106
var privacyReportTaps = 0

0 commit comments

Comments
 (0)