@@ -9,13 +9,13 @@ package io.element.android.features.ftue.impl.state
9
9
10
10
import android.Manifest
11
11
import android.os.Build
12
- import androidx.annotation.VisibleForTesting
13
12
import dev.zacsweers.metro.ContributesBinding
14
13
import dev.zacsweers.metro.Inject
15
14
import dev.zacsweers.metro.SingleIn
16
15
import io.element.android.features.ftue.api.state.FtueService
17
16
import io.element.android.features.ftue.api.state.FtueState
18
17
import io.element.android.features.lockscreen.api.LockScreenService
18
+ import io.element.android.libraries.core.coroutine.mapState
19
19
import io.element.android.libraries.di.SessionScope
20
20
import io.element.android.libraries.di.annotations.SessionCoroutineScope
21
21
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@@ -26,34 +26,37 @@ import io.element.android.services.analytics.api.AnalyticsService
26
26
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
27
27
import kotlinx.coroutines.CoroutineScope
28
28
import kotlinx.coroutines.flow.MutableStateFlow
29
+ import kotlinx.coroutines.flow.combine
29
30
import kotlinx.coroutines.flow.distinctUntilChanged
30
- import kotlinx.coroutines.flow.filter
31
31
import kotlinx.coroutines.flow.first
32
32
import kotlinx.coroutines.flow.launchIn
33
- import kotlinx.coroutines.flow.map
34
33
import kotlinx.coroutines.flow.onEach
34
+ import kotlinx.coroutines.launch
35
35
36
36
@ContributesBinding(SessionScope ::class )
37
37
@SingleIn(SessionScope ::class )
38
38
@Inject
39
39
class DefaultFtueService (
40
40
private val sdkVersionProvider : BuildVersionSdkIntProvider ,
41
- @SessionCoroutineScope sessionCoroutineScope : CoroutineScope ,
41
+ @SessionCoroutineScope private val sessionCoroutineScope : CoroutineScope ,
42
42
private val analyticsService : AnalyticsService ,
43
43
private val permissionStateProvider : PermissionStateProvider ,
44
44
private val lockScreenService : LockScreenService ,
45
45
private val sessionVerificationService : SessionVerificationService ,
46
46
private val sessionPreferencesStore : SessionPreferencesStore ,
47
47
) : FtueService {
48
- override val state = MutableStateFlow < FtueState >( FtueState . Unknown )
48
+ private val userNeedsToConfirmSessionVerificationSuccess = MutableStateFlow ( false )
49
49
50
- /* *
51
- * This flow emits true when the FTUE flow is ready to be displayed.
52
- * In this case, the FTUE flow is ready when the session verification status is known.
53
- */
54
- val isVerificationStatusKnown = sessionVerificationService.sessionVerifiedStatus
55
- .map { it != SessionVerifiedStatus .Unknown }
56
- .distinctUntilChanged()
50
+ val ftueStepStateFlow = MutableStateFlow <InternalFtueState >(InternalFtueState .Unknown )
51
+
52
+ override val state = ftueStepStateFlow
53
+ .mapState {
54
+ when (it) {
55
+ is InternalFtueState .Unknown -> FtueState .Unknown
56
+ is InternalFtueState .Incomplete -> FtueState .Incomplete
57
+ is InternalFtueState .Complete -> FtueState .Complete
58
+ }
59
+ }
57
60
58
61
override suspend fun reset () {
59
62
analyticsService.reset()
@@ -63,24 +66,37 @@ class DefaultFtueService(
63
66
}
64
67
65
68
init {
66
- sessionVerificationService.sessionVerifiedStatus
67
- .onEach { updateState() }
69
+ combine(
70
+ sessionVerificationService.sessionVerifiedStatus.onEach { sessionVerifiedStatus ->
71
+ if (sessionVerifiedStatus == SessionVerifiedStatus .NotVerified ) {
72
+ // Ensure we wait for the user to confirm the session verified screen before going further
73
+ userNeedsToConfirmSessionVerificationSuccess.value = true
74
+ }
75
+ },
76
+ userNeedsToConfirmSessionVerificationSuccess,
77
+ analyticsService.didAskUserConsentFlow.distinctUntilChanged(),
78
+ ) {
79
+ updateFtueStep()
80
+ }
68
81
.launchIn(sessionCoroutineScope)
82
+ }
69
83
70
- analyticsService.didAskUserConsentFlow
71
- .distinctUntilChanged()
72
- .onEach { updateState() }
73
- .launchIn(sessionCoroutineScope)
84
+ fun updateFtueStep () = sessionCoroutineScope.launch {
85
+ val step = getNextStep(null )
86
+ ftueStepStateFlow.value = when (step) {
87
+ null -> InternalFtueState .Complete
88
+ else -> InternalFtueState .Incomplete (step)
89
+ }
74
90
}
75
91
76
- suspend fun getNextStep (currentStep : FtueStep ? = null): FtueStep ? =
77
- when (currentStep ) {
92
+ private suspend fun getNextStep (completedStep : FtueStep ? = null): FtueStep ? =
93
+ when (completedStep ) {
78
94
null -> if (! isSessionVerificationStateReady()) {
79
95
FtueStep .WaitingForInitialState
80
96
} else {
81
97
getNextStep(FtueStep .WaitingForInitialState )
82
98
}
83
- FtueStep .WaitingForInitialState -> if (isSessionNotVerified()) {
99
+ FtueStep .WaitingForInitialState -> if (isSessionNotVerified() || userNeedsToConfirmSessionVerificationSuccess.value ) {
84
100
FtueStep .SessionVerification
85
101
} else {
86
102
getNextStep(FtueStep .SessionVerification )
@@ -108,9 +124,6 @@ class DefaultFtueService(
108
124
}
109
125
110
126
private suspend fun isSessionNotVerified (): Boolean {
111
- // Wait until the session verification status is known
112
- isVerificationStatusKnown.filter { it }.first()
113
-
114
127
return sessionVerificationService.sessionVerifiedStatus.value == SessionVerifiedStatus .NotVerified && ! canSkipVerification()
115
128
}
116
129
@@ -137,14 +150,8 @@ class DefaultFtueService(
137
150
return lockScreenService.isSetupRequired().first()
138
151
}
139
152
140
- @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
141
- internal suspend fun updateState () {
142
- val nextStep = getNextStep()
143
- state.value = when {
144
- // Final state, there aren't any more next steps
145
- nextStep == null -> FtueState .Complete
146
- else -> FtueState .Incomplete
147
- }
153
+ fun onUserCompletedSessionVerification () {
154
+ userNeedsToConfirmSessionVerificationSuccess.value = false
148
155
}
149
156
}
150
157
0 commit comments