Repository: element-hq/element-x-android (language: kotlin)

Issue:
[Task] FTUE - Remove the welcome screen
Please see and discuss the details in the meta issue.


Discussion hints:

Pull request: Remove Welcome screen from the FTUE
<!-- Please read [CONTRIBUTING.md](https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
 
## Type of change

- [ ] Feature
- [ ] Bugfix
- [ ] Technical
- [x] Other : UI flow changes

## Content

- Remove `WelcomeScreen` from the FTUE flow.

## Motivation and context

Closes #2584.

## Screenshots / GIFs

A few screenshots should have been removed.

## Tests

<!-- Explain how you tested your development -->

- Log into the app.

The welcome screen, which should appear as the first step in the FTUE flow, should be gone. You should see the notification permission screen instead in API >= 33 or the analytics one in API < 33.

## Tested devices

- [ ] Physical
- [x] Emulator
- OS version(s): 12, 13.

## Checklist

<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->

- [ ] Changes have been tested on an Android device or Android emulator with API 23
- [ ] UI change has been tested on both light and dark themes
- [ ] Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
- [x] Pull request is based on the develop branch
- [x] Pull request includes a new file under ./changelog.d. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#changelog
- [x] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off)
- [x] You've made a self review of your PR


Test command: ./gradlew :features:ftue:impl:testDebugUnitTest --tests "io.element.android.features.ftue.impl.DefaultFtueStateTests"

Fix patch:
diff --git a/changelog.d/2584.misc b/changelog.d/2584.misc
new file mode 100644
index 00000000000..de85b9d90fa
--- /dev/null
+++ b/changelog.d/2584.misc
@@ -0,0 +1,1 @@
+Remove Welcome screen from the FTUE.
diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt
index 983e0c8db2e..652b6dddd40 100644
--- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt
+++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt
@@ -36,7 +36,6 @@ import io.element.android.features.ftue.api.FtueEntryPoint
 import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode
 import io.element.android.features.ftue.impl.state.DefaultFtueState
 import io.element.android.features.ftue.impl.state.FtueStep
-import io.element.android.features.ftue.impl.welcome.WelcomeNode
 import io.element.android.features.lockscreen.api.LockScreenEntryPoint
 import io.element.android.libraries.architecture.BackstackView
 import io.element.android.libraries.architecture.BaseFlowNode
@@ -73,9 +72,6 @@ class FtueFlowNode @AssistedInject constructor(
         @Parcelize
         data object Placeholder : NavTarget
 
-        @Parcelize
-        data object WelcomeScreen : NavTarget
-
         @Parcelize
         data object NotificationsOptIn : NavTarget
 
@@ -110,15 +106,6 @@ class FtueFlowNode @AssistedInject constructor(
             NavTarget.Placeholder -> {
                 createNode<PlaceholderNode>(buildContext)
             }
-            NavTarget.WelcomeScreen -> {
-                val callback = object : WelcomeNode.Callback {
-                    override fun onContinueClicked() {
-                        ftueState.setWelcomeScreenShown()
-                        lifecycleScope.launch { moveToNextStep() }
-                    }
-                }
-                createNode<WelcomeNode>(buildContext, listOf(callback))
-            }
             NavTarget.NotificationsOptIn -> {
                 val callback = object : NotificationsOptInNode.Callback {
                     override fun onNotificationsOptInFinished() {
@@ -146,9 +133,6 @@ class FtueFlowNode @AssistedInject constructor(
 
     private fun moveToNextStep() {
         when (ftueState.getNextStep()) {
-            FtueStep.WelcomeScreen -> {
-                backstack.newRoot(NavTarget.WelcomeScreen)
-            }
             FtueStep.NotificationsOptIn -> {
                 backstack.newRoot(NavTarget.NotificationsOptIn)
             }
diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt
index ce7a769187f..f1a1c84545e 100644
--- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt
+++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt
@@ -21,7 +21,6 @@ import android.os.Build
 import androidx.annotation.VisibleForTesting
 import com.squareup.anvil.annotations.ContributesBinding
 import io.element.android.features.ftue.api.state.FtueState
-import io.element.android.features.ftue.impl.welcome.state.WelcomeScreenState
 import io.element.android.features.lockscreen.api.LockScreenService
 import io.element.android.libraries.di.SessionScope
 import io.element.android.libraries.permissions.api.PermissionStateProvider
@@ -40,14 +39,12 @@ class DefaultFtueState @Inject constructor(
     private val sdkVersionProvider: BuildVersionSdkIntProvider,
     coroutineScope: CoroutineScope,
     private val analyticsService: AnalyticsService,
-    private val welcomeScreenState: WelcomeScreenState,
     private val permissionStateProvider: PermissionStateProvider,
     private val lockScreenService: LockScreenService,
 ) : FtueState {
     override val shouldDisplayFlow = MutableStateFlow(isAnyStepIncomplete())
 
     override suspend fun reset() {
-        welcomeScreenState.reset()
         analyticsService.reset()
         if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             permissionStateProvider.resetPermission(Manifest.permission.POST_NOTIFICATIONS)
@@ -62,12 +59,7 @@ class DefaultFtueState @Inject constructor(
 
     fun getNextStep(currentStep: FtueStep? = null): FtueStep? =
         when (currentStep) {
-            null -> if (shouldDisplayWelcomeScreen()) {
-                FtueStep.WelcomeScreen
-            } else {
-                getNextStep(FtueStep.WelcomeScreen)
-            }
-            FtueStep.WelcomeScreen -> if (shouldAskNotificationPermissions()) {
+            null -> if (shouldAskNotificationPermissions()) {
                 FtueStep.NotificationsOptIn
             } else {
                 getNextStep(FtueStep.NotificationsOptIn)
@@ -87,7 +79,6 @@ class DefaultFtueState @Inject constructor(
 
     private fun isAnyStepIncomplete(): Boolean {
         return listOf(
-            { shouldDisplayWelcomeScreen() },
             { shouldAskNotificationPermissions() },
             { needsAnalyticsOptIn() },
             { shouldDisplayLockscreenSetup() },
@@ -99,10 +90,6 @@ class DefaultFtueState @Inject constructor(
         return runBlocking { analyticsService.didAskUserConsent().first().not() }
     }
 
-    private fun shouldDisplayWelcomeScreen(): Boolean {
-        return welcomeScreenState.isWelcomeScreenNeeded()
-    }
-
     private fun shouldAskNotificationPermissions(): Boolean {
         return if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             val permission = Manifest.permission.POST_NOTIFICATIONS
@@ -120,11 +107,6 @@ class DefaultFtueState @Inject constructor(
         }
     }
 
-    fun setWelcomeScreenShown() {
-        welcomeScreenState.setWelcomeScreenShown()
-        updateState()
-    }
-
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     internal fun updateState() {
         shouldDisplayFlow.value = isAnyStepIncomplete()
@@ -132,7 +114,6 @@ class DefaultFtueState @Inject constructor(
 }
 
 sealed interface FtueStep {
-    data object WelcomeScreen : FtueStep
     data object NotificationsOptIn : FtueStep
     data object AnalyticsOptIn : FtueStep
     data object LockscreenSetup : FtueStep


Test patch:
diff --git a/.maestro/tests/account/login.yaml b/.maestro/tests/account/login.yaml
index 397cc61f5c8..dcb5cd32234 100644
--- a/.maestro/tests/account/login.yaml
+++ b/.maestro/tests/account/login.yaml
@@ -23,8 +23,6 @@ appId: ${MAESTRO_APP_ID}
 - inputText: ${MAESTRO_PASSWORD}
 - pressKey: Enter
 - tapOn: "Continue"
-- runFlow: ../assertions/assertWelcomeScreenDisplayed.yaml
-- tapOn: "Continue"
 - runFlow: ../assertions/assertAnalyticsDisplayed.yaml
 - tapOn: "Not now"
 - runFlow: ../assertions/assertHomeDisplayed.yaml
diff --git a/.maestro/tests/assertions/assertWelcomeScreenDisplayed.yaml b/.maestro/tests/assertions/assertWelcomeScreenDisplayed.yaml
deleted file mode 100644
index 340d21ff2e0..00000000000
--- a/.maestro/tests/assertions/assertWelcomeScreenDisplayed.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-appId: ${MAESTRO_APP_ID}
----
-- extendedWaitUntil:
-    visible:
-      id: "welcome_screen-title"
-    timeout: 10000
diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt
index 834aaa5c1f4..1f0b8178509 100644
--- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt
+++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt
@@ -20,7 +20,6 @@ import android.os.Build
 import com.google.common.truth.Truth.assertThat
 import io.element.android.features.ftue.impl.state.DefaultFtueState
 import io.element.android.features.ftue.impl.state.FtueStep
-import io.element.android.features.ftue.impl.welcome.state.FakeWelcomeState
 import io.element.android.features.lockscreen.api.LockScreenService
 import io.element.android.features.lockscreen.test.FakeLockScreenService
 import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
@@ -47,7 +46,6 @@ class DefaultFtueStateTests {
 
     @Test
     fun `given all checks being true, should display flow is false`() = runTest {
-        val welcomeState = FakeWelcomeState()
         val analyticsService = FakeAnalyticsService()
         val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
         val lockScreenService = FakeLockScreenService()
@@ -55,13 +53,11 @@ class DefaultFtueStateTests {
 
         val state = createState(
             coroutineScope = coroutineScope,
-            welcomeState = welcomeState,
             analyticsService = analyticsService,
             permissionStateProvider = permissionStateProvider,
             lockScreenService = lockScreenService,
         )
 
-        welcomeState.setWelcomeScreenShown()
         analyticsService.setDidAskUserConsent()
         permissionStateProvider.setPermissionGranted()
         lockScreenService.setIsPinSetup(true)
@@ -75,7 +71,6 @@ class DefaultFtueStateTests {
 
     @Test
     fun `traverse flow`() = runTest {
-        val welcomeState = FakeWelcomeState()
         val analyticsService = FakeAnalyticsService()
         val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
         val lockScreenService = FakeLockScreenService()
@@ -83,26 +78,21 @@ class DefaultFtueStateTests {
 
         val state = createState(
             coroutineScope = coroutineScope,
-            welcomeState = welcomeState,
             analyticsService = analyticsService,
             permissionStateProvider = permissionStateProvider,
             lockScreenService = lockScreenService,
         )
         val steps = mutableListOf<FtueStep?>()
 
-        // First step, welcome screen
-        steps.add(state.getNextStep(steps.lastOrNull()))
-        welcomeState.setWelcomeScreenShown()
-
-        // Second step, notifications opt in
+        // Notifications opt in
         steps.add(state.getNextStep(steps.lastOrNull()))
         permissionStateProvider.setPermissionGranted()
 
-        // Third step, entering PIN code
+        // Entering PIN code
         steps.add(state.getNextStep(steps.lastOrNull()))
         lockScreenService.setIsPinSetup(true)
 
-        // Fourth step, analytics opt in
+        // Analytics opt in
         steps.add(state.getNextStep(steps.lastOrNull()))
         analyticsService.setDidAskUserConsent()
 
@@ -110,7 +100,6 @@ class DefaultFtueStateTests {
         steps.add(state.getNextStep(steps.lastOrNull()))
 
         assertThat(steps).containsExactly(
-            FtueStep.WelcomeScreen,
             FtueStep.NotificationsOptIn,
             FtueStep.LockscreenSetup,
             FtueStep.AnalyticsOptIn,
@@ -135,15 +124,14 @@ class DefaultFtueStateTests {
             lockScreenService = lockScreenService,
         )
 
-        // Skip first 3 steps
-        state.setWelcomeScreenShown()
+        // Skip first 2 steps
         permissionStateProvider.setPermissionGranted()
         lockScreenService.setIsPinSetup(true)
 
         assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
 
         analyticsService.setDidAskUserConsent()
-        assertThat(state.getNextStep(FtueStep.WelcomeScreen)).isNull()
+        assertThat(state.getNextStep(null)).isNull()
 
         // Cleanup
         coroutineScope.cancel()
@@ -162,14 +150,12 @@ class DefaultFtueStateTests {
             lockScreenService = lockScreenService,
         )
 
-        assertThat(state.getNextStep()).isEqualTo(FtueStep.WelcomeScreen)
-        state.setWelcomeScreenShown()
         lockScreenService.setIsPinSetup(true)
 
         assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
 
         analyticsService.setDidAskUserConsent()
-        assertThat(state.getNextStep(FtueStep.WelcomeScreen)).isNull()
+        assertThat(state.getNextStep(null)).isNull()
 
         // Cleanup
         coroutineScope.cancel()
@@ -177,7 +163,6 @@ class DefaultFtueStateTests {
 
     private fun createState(
         coroutineScope: CoroutineScope,
-        welcomeState: FakeWelcomeState = FakeWelcomeState(),
         analyticsService: AnalyticsService = FakeAnalyticsService(),
         permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
         lockScreenService: LockScreenService = FakeLockScreenService(),
@@ -187,7 +172,6 @@ class DefaultFtueStateTests {
         sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
         coroutineScope = coroutineScope,
         analyticsService = analyticsService,
-        welcomeScreenState = welcomeState,
         permissionStateProvider = permissionStateProvider,
         lockScreenService = lockScreenService,
     )