Skip to content

Commit

Permalink
Merge pull request #3360 from element-hq/feature/bma/sessionVerificat…
Browse files Browse the repository at this point in the history
…ionBannerIsBack

Add banner entry point to set up recovery
  • Loading branch information
bmarty authored Aug 30, 2024
2 parents 26b2f5d + d93762b commit 31d0621
Show file tree
Hide file tree
Showing 20 changed files with 173 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ class LoggedInFlowNode @AssistedInject constructor(
backstack.push(NavTarget.CreateRoom)
}

override fun onSetUpRecoveryClick() {
backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.SetUpRecovery))
}

override fun onSessionConfirmRecoveryKeyClick() {
backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface RoomListEntryPoint : FeatureEntryPoint {
fun onRoomClick(roomId: RoomId)
fun onCreateRoomClick()
fun onSettingsClick()
fun onSetUpRecoveryClick()
fun onSessionConfirmRecoveryKeyClick()
fun onRoomSettingsClick(roomId: RoomId)
fun onReportBugClick()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class RoomListNode @AssistedInject constructor(
plugins<RoomListEntryPoint.Callback>().forEach { it.onCreateRoomClick() }
}

private fun onSetUpRecoveryClick() {
plugins<RoomListEntryPoint.Callback>().forEach { it.onSetUpRecoveryClick() }
}

private fun onSessionConfirmRecoveryKeyClick() {
plugins<RoomListEntryPoint.Callback>().forEach { it.onSessionConfirmRecoveryKeyClick() }
}
Expand Down Expand Up @@ -98,6 +102,7 @@ class RoomListNode @AssistedInject constructor(
onRoomClick = this::onRoomClick,
onSettingsClick = this::onOpenSettings,
onCreateRoomClick = this::onCreateRoomClick,
onSetUpRecoveryClick = this::onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick,
onRoomSettingsClick = this::onRoomSettingsClick,
onMenuActionClick = { onMenuActionClick(activity, it) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,15 @@ class RoomListPresenter @Inject constructor(
derivedStateOf {
when {
currentSecurityBannerDismissed -> SecurityBannerState.None
recoveryState == RecoveryState.INCOMPLETE &&
syncState == SyncState.Running -> SecurityBannerState.RecoveryKeyConfirmation
syncState == SyncState.Running -> {
when (recoveryState) {
RecoveryState.UNKNOWN,
RecoveryState.DISABLED -> SecurityBannerState.SetUpRecovery
RecoveryState.INCOMPLETE -> SecurityBannerState.RecoveryKeyConfirmation
RecoveryState.WAITING_FOR_SYNC,
RecoveryState.ENABLED -> SecurityBannerState.None
}
}
else -> SecurityBannerState.None
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ enum class InvitesState {

enum class SecurityBannerState {
None,
SetUpRecovery,
RecoveryKeyConfirmation,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
aRoomListState(contentState = aSkeletonContentState()),
aRoomListState(matrixUser = MatrixUser(userId = UserId("@id:domain")), contentState = aMigrationContentState()),
aRoomListState(searchState = aRoomListSearchState(isSearchActive = true, query = "Test")),
aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery)),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fun RoomListView(
state: RoomListState,
onRoomClick: (RoomId) -> Unit,
onSettingsClick: () -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onCreateRoomClick: () -> Unit,
onRoomSettingsClick: (roomId: RoomId) -> Unit,
Expand All @@ -78,6 +79,7 @@ fun RoomListView(

RoomListScaffold(
state = state,
onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = onRoomClick,
onOpenSettings = onSettingsClick,
Expand Down Expand Up @@ -106,6 +108,7 @@ fun RoomListView(
@Composable
private fun RoomListScaffold(
state: RoomListState,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomId) -> Unit,
onOpenSettings: () -> Unit,
Expand Down Expand Up @@ -142,6 +145,7 @@ private fun RoomListScaffold(
contentState = state.contentState,
filtersState = state.filtersState,
eventSink = state.eventSink,
onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = ::onRoomClick,
onCreateRoomClick = onCreateRoomClick,
Expand Down Expand Up @@ -178,6 +182,7 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class)
state = state,
onRoomClick = {},
onSettingsClick = {},
onSetUpRecoveryClick = {},
onConfirmRecoveryKeyClick = {},
onCreateRoomClick = {},
onRoomSettingsClick = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ fun RoomListContentView(
contentState: RoomListContentState,
filtersState: RoomListFiltersState,
eventSink: (RoomListEvents) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
onCreateRoomClick: () -> Unit,
Expand All @@ -95,6 +96,7 @@ fun RoomListContentView(
state = contentState,
filtersState = filtersState,
eventSink = eventSink,
onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = onRoomClick,
)
Expand Down Expand Up @@ -141,6 +143,7 @@ private fun RoomsView(
state: RoomListContentState.Rooms,
filtersState: RoomListFiltersState,
eventSink: (RoomListEvents) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
modifier: Modifier = Modifier,
Expand All @@ -154,6 +157,7 @@ private fun RoomsView(
RoomsViewList(
state = state,
eventSink = eventSink,
onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = onRoomClick,
modifier = modifier.fillMaxSize(),
Expand All @@ -165,6 +169,7 @@ private fun RoomsView(
private fun RoomsViewList(
state: RoomListContentState.Rooms,
eventSink: (RoomListEvents) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
modifier: Modifier = Modifier,
Expand All @@ -188,21 +193,27 @@ private fun RoomsViewList(
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
contentPadding = PaddingValues(bottom = 80.dp)
) {
if (state.securityBannerState != SecurityBannerState.None) {
when (state.securityBannerState) {
SecurityBannerState.RecoveryKeyConfirmation -> {
item {
ConfirmRecoveryKeyBanner(
onContinueClick = onConfirmRecoveryKeyClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
)
}
when (state.securityBannerState) {
SecurityBannerState.SetUpRecovery -> {
item {
SetUpRecoveryKeyBanner(
onContinueClick = onSetUpRecoveryClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
)
}
else -> Unit
}
} else if (state.fullScreenIntentPermissionsState.shouldDisplayBanner) {
item {
FullScreenIntentPermissionBanner(state = state.fullScreenIntentPermissionsState)
SecurityBannerState.RecoveryKeyConfirmation -> {
item {
ConfirmRecoveryKeyBanner(
onContinueClick = onConfirmRecoveryKeyClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
)
}
}
SecurityBannerState.None -> if (state.fullScreenIntentPermissionsState.shouldDisplayBanner) {
item {
FullScreenIntentPermissionBanner(state = state.fullScreenIntentPermissionsState)
}
}
}

Expand Down Expand Up @@ -276,6 +287,7 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) }
),
eventSink = {},
onSetUpRecoveryClick = {},
onConfirmRecoveryKeyClick = {},
onRoomClick = {},
onCreateRoomClick = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.element.android.features.roomlist.impl.components

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import io.element.android.features.roomlist.impl.R
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight

@Composable
internal fun SetUpRecoveryKeyBanner(
onContinueClick: () -> Unit,
onDismissClick: () -> Unit,
modifier: Modifier = Modifier,
) {
DialogLikeBannerMolecule(
modifier = modifier,
title = stringResource(R.string.banner_set_up_recovery_title),
content = stringResource(R.string.banner_set_up_recovery_content),
onSubmitClick = onContinueClick,
onDismissClick = onDismissClick,
)
}

@PreviewsDayNight
@Composable
internal fun SetUpRecoveryKeyBannerPreview() = ElementPreview {
SetUpRecoveryKeyBanner(
onContinueClick = {},
onDismissClick = {},
)
}
2 changes: 2 additions & 0 deletions features/roomlist/impl/src/main/res/values/localazy.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_set_up_recovery_content">"Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices."</string>
<string name="banner_set_up_recovery_title">"Set up recovery"</string>
<string name="confirm_recovery_key_banner_message">"Your chat backup is currently out of sync. You need to enter your recovery key to maintain access to your chat backup."</string>
<string name="confirm_recovery_key_banner_title">"Enter your recovery key"</string>
<string name="full_screen_intent_banner_message">"To ensure you never miss an important call, please change your settings to allow full-screen notifications when your phone is locked."</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,21 @@ class RoomListPresenterTest {
val initialState = consumeItemsUntilPredicate {
it.contentState is RoomListContentState.Rooms
}.last()
assertThat(initialState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
assertThat(initialState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery)
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
val nextState = awaitItem()
assertThat(nextState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
// Also check other states
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery)
encryptionService.emitRecoveryState(RecoveryState.WAITING_FOR_SYNC)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery)
encryptionService.emitRecoveryState(RecoveryState.ENABLED)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery)
nextState.eventSink(RoomListEvents.DismissRecoveryKeyPrompt)
val finalState = awaitItem()
assertThat(finalState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ class RoomListViewTest {
eventsRecorder.assertSingle(RoomListEvents.DismissRecoveryKeyPrompt)
}

@Test
fun `clicking on close setup key banner emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
rule.setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
eventSink = eventsRecorder,
)
)

// Remove automatic initial events
eventsRecorder.clear()

val close = rule.activity.getString(CommonStrings.action_close)
rule.onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvents.DismissRecoveryKeyPrompt)
}

@Test
fun `clicking on continue recovery key banner invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
Expand All @@ -101,6 +119,27 @@ class RoomListViewTest {
}
}

@Test
fun `clicking on continue setup key banner invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
ensureCalledOnce { callback ->
rule.setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
eventSink = eventsRecorder,
),
onSetUpRecoveryClick = callback,
)

// Remove automatic initial events
eventsRecorder.clear()

rule.clickOn(CommonStrings.action_continue)

eventsRecorder.assertEmpty()
}
}

@Test
fun `clicking on start chat when the session has no room invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false)
Expand Down Expand Up @@ -208,6 +247,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
state: RoomListState,
onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onSettingsClick: () -> Unit = EnsureNeverCalled(),
onSetUpRecoveryClick: () -> Unit = EnsureNeverCalled(),
onConfirmRecoveryKeyClick: () -> Unit = EnsureNeverCalled(),
onCreateRoomClick: () -> Unit = EnsureNeverCalled(),
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
Expand All @@ -219,6 +259,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomL
state = state,
onRoomClick = onRoomClick,
onSettingsClick = onSettingsClick,
onSetUpRecoveryClick = onSetUpRecoveryClick,
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onCreateRoomClick = onCreateRoomClick,
onRoomSettingsClick = onRoomSettingsClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ interface SecureBackupEntryPoint : FeatureEntryPoint {
@Parcelize
data object Root : InitialTarget

@Parcelize
data object SetUpRecovery : InitialTarget

@Parcelize
data object EnterRecoveryKey : InitialTarget

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class SecureBackupFlowNode @AssistedInject constructor(
backstack = BackStack(
initialElement = when (plugins.filterIsInstance<SecureBackupEntryPoint.Params>().first().initialElement) {
SecureBackupEntryPoint.InitialTarget.Root -> NavTarget.Root
SecureBackupEntryPoint.InitialTarget.SetUpRecovery -> NavTarget.Setup
SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey -> NavTarget.EnterRecoveryKey
is SecureBackupEntryPoint.InitialTarget.ResetIdentity -> NavTarget.ResetIdentity
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class RoomListScreen(
state = state,
onRoomClick = ::onRoomClick,
onSettingsClick = {},
onSetUpRecoveryClick = {},
onConfirmRecoveryKeyClick = {},
onCreateRoomClick = {},
onRoomSettingsClick = {},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 31d0621

Please sign in to comment.