Introduce SecurityBannerState to replace Boolean set.
Also get the sessionVerificationService from the matrixClient, instead of injecting it separately.
This commit is contained in:
parent
7ec876bc8a
commit
4bc977d8dc
6 changed files with 55 additions and 59 deletions
|
|
@ -73,7 +73,6 @@ private const val EXTENDED_RANGE_SIZE = 40
|
||||||
|
|
||||||
class RoomListPresenter @Inject constructor(
|
class RoomListPresenter @Inject constructor(
|
||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val sessionVerificationService: SessionVerificationService,
|
|
||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val snackbarDispatcher: SnackbarDispatcher,
|
private val snackbarDispatcher: SnackbarDispatcher,
|
||||||
private val inviteStateDataSource: InviteStateDataSource,
|
private val inviteStateDataSource: InviteStateDataSource,
|
||||||
|
|
@ -87,6 +86,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
private val encryptionService: EncryptionService = client.encryptionService()
|
private val encryptionService: EncryptionService = client.encryptionService()
|
||||||
|
private val sessionVerificationService: SessionVerificationService = client.sessionVerificationService()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListState {
|
override fun present(): RoomListState {
|
||||||
|
|
@ -108,22 +108,25 @@ class RoomListPresenter @Inject constructor(
|
||||||
|
|
||||||
val isMigrating = migrationScreenPresenter.present().isMigrating
|
val isMigrating = migrationScreenPresenter.present().isMigrating
|
||||||
|
|
||||||
// Session verification status (unknown, not verified, verified)
|
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
|
||||||
val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false)
|
val displayVerificationPrompt by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false)
|
||||||
var verificationPromptDismissed by rememberSaveable { mutableStateOf(false) }
|
|
||||||
// We combine both values to only display the prompt if the session is not verified and it wasn't dismissed
|
|
||||||
val displayVerificationPrompt by remember {
|
|
||||||
derivedStateOf { canVerifySession && !verificationPromptDismissed }
|
|
||||||
}
|
|
||||||
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
||||||
val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage)
|
val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage)
|
||||||
.collectAsState(initial = null)
|
.collectAsState(initial = null)
|
||||||
var recoveryKeyPromptDismissed by rememberSaveable { mutableStateOf(false) }
|
|
||||||
val displayRecoveryKeyPrompt by remember {
|
val displayRecoveryKeyPrompt by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
secureStorageFlag == true &&
|
secureStorageFlag == true &&
|
||||||
recoveryState == RecoveryState.INCOMPLETE &&
|
recoveryState == RecoveryState.INCOMPLETE
|
||||||
!recoveryKeyPromptDismissed
|
}
|
||||||
|
}
|
||||||
|
val securityBannerState by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
securityBannerDismissed -> SecurityBannerState.None
|
||||||
|
displayVerificationPrompt -> SecurityBannerState.SessionVerification
|
||||||
|
displayRecoveryKeyPrompt -> SecurityBannerState.RecoveryKeyConfirmation
|
||||||
|
else -> SecurityBannerState.None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,8 +138,8 @@ class RoomListPresenter @Inject constructor(
|
||||||
fun handleEvents(event: RoomListEvents) {
|
fun handleEvents(event: RoomListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
|
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
|
||||||
RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true
|
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
||||||
RoomListEvents.DismissRecoveryKeyPrompt -> recoveryKeyPromptDismissed = true
|
RoomListEvents.DismissRecoveryKeyPrompt -> securityBannerDismissed = true
|
||||||
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
|
||||||
is RoomListEvents.ShowContextMenu -> {
|
is RoomListEvents.ShowContextMenu -> {
|
||||||
coroutineScope.showContextMenu(event, contextMenu)
|
coroutineScope.showContextMenu(event, contextMenu)
|
||||||
|
|
@ -157,8 +160,7 @@ class RoomListPresenter @Inject constructor(
|
||||||
matrixUser = matrixUser.value,
|
matrixUser = matrixUser.value,
|
||||||
showAvatarIndicator = showAvatarIndicator,
|
showAvatarIndicator = showAvatarIndicator,
|
||||||
roomList = roomList,
|
roomList = roomList,
|
||||||
displayVerificationPrompt = displayVerificationPrompt,
|
securityBannerState = securityBannerState,
|
||||||
displayRecoveryKeyPrompt = displayRecoveryKeyPrompt,
|
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
|
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
|
||||||
invitesState = inviteStateDataSource.inviteState(),
|
invitesState = inviteStateDataSource.inviteState(),
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ data class RoomListState(
|
||||||
val matrixUser: MatrixUser?,
|
val matrixUser: MatrixUser?,
|
||||||
val showAvatarIndicator: Boolean,
|
val showAvatarIndicator: Boolean,
|
||||||
val roomList: AsyncData<ImmutableList<RoomListRoomSummary>>,
|
val roomList: AsyncData<ImmutableList<RoomListRoomSummary>>,
|
||||||
val displayVerificationPrompt: Boolean,
|
val securityBannerState: SecurityBannerState,
|
||||||
val displayRecoveryKeyPrompt: Boolean,
|
|
||||||
val hasNetworkConnection: Boolean,
|
val hasNetworkConnection: Boolean,
|
||||||
val snackbarMessage: SnackbarMessage?,
|
val snackbarMessage: SnackbarMessage?,
|
||||||
val invitesState: InvitesState,
|
val invitesState: InvitesState,
|
||||||
|
|
@ -62,3 +61,9 @@ enum class InvitesState {
|
||||||
SeenInvites,
|
SeenInvites,
|
||||||
NewInvites,
|
NewInvites,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class SecurityBannerState {
|
||||||
|
None,
|
||||||
|
SessionVerification,
|
||||||
|
RecoveryKeyConfirmation,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
||||||
override val values: Sequence<RoomListState>
|
override val values: Sequence<RoomListState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomListState(),
|
aRoomListState(),
|
||||||
aRoomListState().copy(displayVerificationPrompt = true),
|
aRoomListState().copy(securityBannerState = SecurityBannerState.SessionVerification),
|
||||||
aRoomListState().copy(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
|
aRoomListState().copy(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
|
||||||
aRoomListState().copy(hasNetworkConnection = false),
|
aRoomListState().copy(hasNetworkConnection = false),
|
||||||
aRoomListState().copy(invitesState = InvitesState.SeenInvites),
|
aRoomListState().copy(invitesState = InvitesState.SeenInvites),
|
||||||
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
aRoomListState().copy(invitesState = InvitesState.NewInvites),
|
||||||
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||||
aRoomListState().copy(contextMenu = aContextMenuShown(isFavorite = true)),
|
aRoomListState().copy(contextMenu = aContextMenuShown(isFavorite = true)),
|
||||||
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
aRoomListState().copy(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||||
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
||||||
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||||
aRoomListState().copy(matrixUser = null, displayMigrationStatus = true),
|
aRoomListState().copy(matrixUser = null, displayMigrationStatus = true),
|
||||||
|
|
@ -58,8 +58,7 @@ internal fun aRoomListState() = RoomListState(
|
||||||
roomList = AsyncData.Success(aRoomListRoomSummaryList()),
|
roomList = AsyncData.Success(aRoomListRoomSummaryList()),
|
||||||
hasNetworkConnection = true,
|
hasNetworkConnection = true,
|
||||||
snackbarMessage = null,
|
snackbarMessage = null,
|
||||||
displayVerificationPrompt = false,
|
securityBannerState = SecurityBannerState.None,
|
||||||
displayRecoveryKeyPrompt = false,
|
|
||||||
invitesState = InvitesState.NoInvites,
|
invitesState = InvitesState.NoInvites,
|
||||||
contextMenu = RoomListState.ContextMenu.Hidden,
|
contextMenu = RoomListState.ContextMenu.Hidden,
|
||||||
leaveRoomState = aLeaveRoomState(),
|
leaveRoomState = aLeaveRoomState(),
|
||||||
|
|
|
||||||
|
|
@ -225,23 +225,25 @@ private fun RoomListContent(
|
||||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
|
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
|
||||||
contentPadding = PaddingValues(bottom = 80.dp)
|
contentPadding = PaddingValues(bottom = 80.dp)
|
||||||
) {
|
) {
|
||||||
when {
|
if (state.displayEmptyState.not()) {
|
||||||
state.displayEmptyState -> Unit
|
when (state.securityBannerState) {
|
||||||
state.displayVerificationPrompt -> {
|
SecurityBannerState.SessionVerification -> {
|
||||||
item {
|
item {
|
||||||
RequestVerificationHeader(
|
RequestVerificationHeader(
|
||||||
onVerifyClicked = onVerifyClicked,
|
onVerifyClicked = onVerifyClicked,
|
||||||
onDismissClicked = { state.eventSink(RoomListEvents.DismissRequestVerificationPrompt) }
|
onDismissClicked = { state.eventSink(RoomListEvents.DismissRequestVerificationPrompt) }
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
SecurityBannerState.RecoveryKeyConfirmation -> {
|
||||||
state.displayRecoveryKeyPrompt -> {
|
item {
|
||||||
item {
|
ConfirmRecoveryKeyBanner(
|
||||||
ConfirmRecoveryKeyBanner(
|
onContinueClicked = onOpenSettings,
|
||||||
onContinueClicked = onOpenSettings,
|
onDismissClicked = { state.eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
|
||||||
onDismissClicked = { state.eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
SecurityBannerState.None -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,6 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
@ -114,13 +112,13 @@ class RoomListPresenterTests {
|
||||||
fun `present - show avatar indicator`() = runTest {
|
fun `present - show avatar indicator`() = runTest {
|
||||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||||
val encryptionService = FakeEncryptionService()
|
val encryptionService = FakeEncryptionService()
|
||||||
|
val sessionVerificationService = FakeSessionVerificationService()
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
|
sessionVerificationService = sessionVerificationService,
|
||||||
)
|
)
|
||||||
val sessionVerificationService = FakeSessionVerificationService()
|
|
||||||
val presenter = createRoomListPresenter(
|
val presenter = createRoomListPresenter(
|
||||||
client = matrixClient,
|
client = matrixClient,
|
||||||
sessionVerificationService = sessionVerificationService,
|
|
||||||
coroutineScope = scope
|
coroutineScope = scope
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -239,27 +237,17 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
||||||
val roomListService = FakeRoomListService()
|
|
||||||
val matrixClient = FakeMatrixClient(
|
|
||||||
roomListService = roomListService,
|
|
||||||
)
|
|
||||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||||
val presenter = createRoomListPresenter(
|
val presenter = createRoomListPresenter(
|
||||||
client = matrixClient,
|
|
||||||
sessionVerificationService = FakeSessionVerificationService().apply {
|
|
||||||
givenIsReady(true)
|
|
||||||
givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
|
||||||
},
|
|
||||||
coroutineScope = scope,
|
coroutineScope = scope,
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val eventSink = awaitItem().eventSink
|
val eventSink = awaitItem().eventSink
|
||||||
assertThat(awaitItem().displayVerificationPrompt).isTrue()
|
assertThat(awaitItem().securityBannerState).isEqualTo(SecurityBannerState.SessionVerification)
|
||||||
|
|
||||||
eventSink(RoomListEvents.DismissRequestVerificationPrompt)
|
eventSink(RoomListEvents.DismissRequestVerificationPrompt)
|
||||||
assertThat(awaitItem().displayVerificationPrompt).isFalse()
|
assertThat(awaitItem().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,6 +257,9 @@ class RoomListPresenterTests {
|
||||||
val encryptionService = FakeEncryptionService()
|
val encryptionService = FakeEncryptionService()
|
||||||
val matrixClient = FakeMatrixClient(
|
val matrixClient = FakeMatrixClient(
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
|
sessionVerificationService = FakeSessionVerificationService().apply {
|
||||||
|
givenCanVerifySession(false)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||||
val presenter = createRoomListPresenter(
|
val presenter = createRoomListPresenter(
|
||||||
|
|
@ -280,13 +271,13 @@ class RoomListPresenterTests {
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.displayRecoveryKeyPrompt).isFalse()
|
assertThat(initialState.securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||||
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||||
val nextState = awaitItem()
|
val nextState = awaitItem()
|
||||||
assertThat(nextState.displayRecoveryKeyPrompt).isTrue()
|
assertThat(nextState.securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
|
||||||
nextState.eventSink(RoomListEvents.DismissRecoveryKeyPrompt)
|
nextState.eventSink(RoomListEvents.DismissRecoveryKeyPrompt)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.displayRecoveryKeyPrompt).isFalse()
|
assertThat(finalState.securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -579,7 +570,6 @@ class RoomListPresenterTests {
|
||||||
|
|
||||||
private fun TestScope.createRoomListPresenter(
|
private fun TestScope.createRoomListPresenter(
|
||||||
client: MatrixClient = FakeMatrixClient(),
|
client: MatrixClient = FakeMatrixClient(),
|
||||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
|
||||||
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
|
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
|
||||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||||
inviteStateDataSource: InviteStateDataSource = FakeInviteDataSource(),
|
inviteStateDataSource: InviteStateDataSource = FakeInviteDataSource(),
|
||||||
|
|
@ -599,7 +589,6 @@ class RoomListPresenterTests {
|
||||||
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||||
) = RoomListPresenter(
|
) = RoomListPresenter(
|
||||||
client = client,
|
client = client,
|
||||||
sessionVerificationService = sessionVerificationService,
|
|
||||||
networkMonitor = networkMonitor,
|
networkMonitor = networkMonitor,
|
||||||
snackbarDispatcher = snackbarDispatcher,
|
snackbarDispatcher = snackbarDispatcher,
|
||||||
inviteStateDataSource = inviteStateDataSource,
|
inviteStateDataSource = inviteStateDataSource,
|
||||||
|
|
@ -616,7 +605,7 @@ class RoomListPresenterTests {
|
||||||
),
|
),
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
indicatorService = DefaultIndicatorService(
|
indicatorService = DefaultIndicatorService(
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = client.sessionVerificationService(),
|
||||||
encryptionService = client.encryptionService(),
|
encryptionService = client.encryptionService(),
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ class RoomListScreen(
|
||||||
)
|
)
|
||||||
private val presenter = RoomListPresenter(
|
private val presenter = RoomListPresenter(
|
||||||
client = matrixClient,
|
client = matrixClient,
|
||||||
sessionVerificationService = sessionVerificationService,
|
|
||||||
networkMonitor = NetworkMonitorImpl(context, Singleton.appScope),
|
networkMonitor = NetworkMonitorImpl(context, Singleton.appScope),
|
||||||
snackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher = SnackbarDispatcher(),
|
||||||
inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers),
|
inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue