Skip to content

Commit

Permalink
SplashScreen Animation: Add wink animation (#5565)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1207908166761516/1209192748061740/f

### Description

Adds a new wink animation to the splashscreen for devices on sdk 32+😉 

On devices <32 we show the existing static logo splashscreen.

### Steps to test this PR

_Splashscreen on SDK 32+_
- [x] Open the app 
- [x] Check wink animation plays
- [x] Press back to exit the app
- [x] Launch the app
- [x] Check the animation plays

_Splashscreen on SDK < 31_
- [x] Open the app 
- [x] Check the existing static splashscreen logo is shown
- [x] Press back to exit the app
- [x] Launch the app
- [x] Check the existing static splashscreen logo is shown

### Demo

**Light**


https://github.com/user-attachments/assets/1bd02a3a-0c9b-4bba-814f-fab2a4af9f5d

**Dark**


https://github.com/user-attachments/assets/fdb63c14-7f84-44a1-92e0-3f2a8a0539e9



---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1209331097372337
  • Loading branch information
mikescamell authored Feb 10, 2025
1 parent f9240e7 commit 1af0d49
Show file tree
Hide file tree
Showing 9 changed files with 12,034 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ object PixelInterceptorPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
AppPixelName.SET_AS_DEFAULT_PROMPT_CLICK.pixelName to PixelParameter.removeAll(),
AppPixelName.SET_AS_DEFAULT_PROMPT_DISMISSED.pixelName to PixelParameter.removeAll(),
AppPixelName.SET_AS_DEFAULT_IN_MENU_CLICK.pixelName to PixelParameter.removeAll(),
AppPixelName.SPLASHSCREEN_SHOWN.pixelName to PixelParameter.removeAll(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import com.duckduckgo.app.browser.R
import com.duckduckgo.app.onboarding.ui.OnboardingActivity
import com.duckduckgo.common.ui.DuckDuckGoActivity
import com.duckduckgo.di.scopes.ActivityScope
import java.time.Instant
import java.time.temporal.ChronoUnit
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@InjectWith(ActivityScope::class)
Expand All @@ -35,13 +38,25 @@ class LaunchBridgeActivity : DuckDuckGoActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
splashScreen.setKeepOnScreenCondition { true }

setContentView(R.layout.activity_launch)

configureObservers()

lifecycleScope.launch { viewModel.determineViewToShow() }
splashScreen.setOnExitAnimationListener { splashScreenView ->
val splashScreenAnimationEndTime =
Instant.ofEpochMilli(splashScreenView.iconAnimationStartMillis + splashScreenView.iconAnimationDurationMillis)
val remainingAnimationTime = Instant.now().until(
splashScreenAnimationEndTime,
ChronoUnit.MILLIS,
)

lifecycleScope.launch {
viewModel.sendWelcomeScreenPixel()
delay(remainingAnimationTime)
viewModel.determineViewToShow()
}
}
}

private fun configureObservers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import androidx.lifecycle.ViewModel
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.store.isNewUser
import com.duckduckgo.app.pixels.AppPixelName.SPLASHSCREEN_SHOWN
import com.duckduckgo.app.referral.AppInstallationReferrerStateListener
import com.duckduckgo.app.referral.AppInstallationReferrerStateListener.Companion.MAX_REFERRER_WAIT_TIME_MS
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.SingleLiveEvent
import com.duckduckgo.di.scopes.ActivityScope
import javax.inject.Inject
Expand All @@ -32,6 +34,7 @@ import timber.log.Timber
class LaunchViewModel @Inject constructor(
private val userStageStore: UserStageStore,
private val appReferrerStateListener: AppInstallationReferrerStateListener,
private val pixel: Pixel,
) :
ViewModel() {

Expand All @@ -42,6 +45,10 @@ class LaunchViewModel @Inject constructor(
data class Home(val replaceExistingSearch: Boolean = false) : Command()
}

fun sendWelcomeScreenPixel() {
pixel.fire(SPLASHSCREEN_SHOWN)
}

suspend fun determineViewToShow() {
waitForReferrerData()

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
BROKEN_SITE_ALLOWLIST_REMOVE("m_broken_site_allowlist_remove"),
PROTECTION_TOGGLE_BROKEN_SITE_REPORT("m_protection-toggled-off-breakage-report"),

SPLASHSCREEN_SHOWN("m_splashscreen_shown"),

PREONBOARDING_INTRO_SHOWN_UNIQUE("m_preonboarding_intro_shown_unique"),
PREONBOARDING_COMPARISON_CHART_SHOWN_UNIQUE("m_preonboarding_comparison_chart_shown_unique"),
PREONBOARDING_CHOOSE_BROWSER_PRESSED("m_preonboarding_choose_browser_pressed"),
Expand Down
24 changes: 24 additions & 0 deletions app/src/test/java/com/duckduckgo/app/launch/LaunchViewModelTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import com.duckduckgo.app.launch.LaunchViewModel.Command.Home
import com.duckduckgo.app.launch.LaunchViewModel.Command.Onboarding
import com.duckduckgo.app.onboarding.store.AppStage
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.referral.StubAppReferrerFoundStateListener
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.fakes.FakePixel
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
Expand All @@ -45,6 +48,8 @@ class LaunchViewModelTest {
private val userStageStore = mock<UserStageStore>()
private val mockCommandObserver: Observer<LaunchViewModel.Command> = mock()

private val fakePixel: FakePixel = FakePixel()

private lateinit var testee: LaunchViewModel

@After
Expand All @@ -57,6 +62,7 @@ class LaunchViewModelTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx"),
fakePixel,
)
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.NEW)
testee.command.observeForever(mockCommandObserver)
Expand All @@ -71,6 +77,7 @@ class LaunchViewModelTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx", mockDelayMs = 1_000),
fakePixel,
)
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.NEW)
testee.command.observeForever(mockCommandObserver)
Expand All @@ -85,6 +92,7 @@ class LaunchViewModelTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE),
fakePixel,
)
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.NEW)
testee.command.observeForever(mockCommandObserver)
Expand All @@ -99,6 +107,7 @@ class LaunchViewModelTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx"),
fakePixel,
)
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
testee.command.observeForever(mockCommandObserver)
Expand All @@ -111,6 +120,7 @@ class LaunchViewModelTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx", mockDelayMs = 1_000),
fakePixel,
)
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
testee.command.observeForever(mockCommandObserver)
Expand All @@ -123,10 +133,24 @@ class LaunchViewModelTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE),
fakePixel,
)
whenever(userStageStore.getUserAppStage()).thenReturn(AppStage.DAX_ONBOARDING)
testee.command.observeForever(mockCommandObserver)
testee.determineViewToShow()
verify(mockCommandObserver).onChanged(any<Home>())
}

@Test
fun whenSendingWelcomeScreenPixelThenSplashScreenShownPixelIsSent() = runTest {
testee = LaunchViewModel(
userStageStore,
StubAppReferrerFoundStateListener("xx", mockDelayMs = Long.MAX_VALUE),
fakePixel,
)

testee.sendWelcomeScreenPixel()

assertEquals(AppPixelName.SPLASHSCREEN_SHOWN.pixelName, fakePixel.firedPixels.first())
}
}
Loading

0 comments on commit 1af0d49

Please sign in to comment.