Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add banner for optional migration to simplified sliding sync #3429

Merged
merged 3 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueService
import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.logout.api.LogoutEntryPoint
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.preferences.api.PreferencesEntryPoint
Expand All @@ -65,6 +66,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
Expand Down Expand Up @@ -96,6 +98,8 @@ class LoggedInFlowNode @AssistedInject constructor(
private val shareEntryPoint: ShareEntryPoint,
private val matrixClient: MatrixClient,
private val sendingQueue: SendQueues,
private val logoutEntryPoint: LogoutEntryPoint,
private val enableNativeSlidingSyncUseCase: EnableNativeSlidingSyncUseCase,
snackbarDispatcher: SnackbarDispatcher,
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack(
Expand Down Expand Up @@ -225,6 +229,9 @@ class LoggedInFlowNode @AssistedInject constructor(

@Parcelize
data class IncomingShare(val intent: Intent) : NavTarget

@Parcelize
data object LogoutForNativeSlidingSyncMigrationNeeded : NavTarget
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
Expand Down Expand Up @@ -271,6 +278,10 @@ class LoggedInFlowNode @AssistedInject constructor(
override fun onRoomDirectorySearchClick() {
backstack.push(NavTarget.RoomDirectorySearch)
}

override fun onLogoutForNativeSlidingSyncMigrationNeeded() {
backstack.push(NavTarget.LogoutForNativeSlidingSyncMigrationNeeded)
}
}
roomListEntryPoint
.nodeBuilder(this, buildContext)
Expand Down Expand Up @@ -407,6 +418,20 @@ class LoggedInFlowNode @AssistedInject constructor(
.params(ShareEntryPoint.Params(intent = navTarget.intent))
.build()
}
is NavTarget.LogoutForNativeSlidingSyncMigrationNeeded -> {
val callback = object : LogoutEntryPoint.Callback {
override fun onChangeRecoveryKeyClick() {
backstack.push(NavTarget.SecureBackup())
}
}

logoutEntryPoint.nodeBuilder(this, buildContext)
.onSuccessfulLogoutPendingAction {
enableNativeSlidingSyncUseCase()
}
.callback(callback)
.build()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface LogoutEntryPoint : FeatureEntryPoint {
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder

interface NodeBuilder {
fun onSuccessfulLogoutPendingAction(action: () -> Unit): NodeBuilder
fun callback(callback: Callback): NodeBuilder
fun build(): Node
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
return this
}

override fun onSuccessfulLogoutPendingAction(action: () -> Unit): LogoutEntryPoint.NodeBuilder {
plugins += object : LogoutNode.SuccessfulLogoutPendingAction, Plugin {

Check warning on line 31 in features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt

View check run for this annotation

Codecov / codecov/patch

features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt#L31

Added line #L31 was not covered by tests
override fun onSuccessfulLogoutPendingAction() {
action()

Check warning on line 33 in features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt

View check run for this annotation

Codecov / codecov/patch

features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt#L33

Added line #L33 was not covered by tests
}
}
return this

Check warning on line 36 in features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt

View check run for this annotation

Codecov / codecov/patch

features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutEntryPoint.kt#L36

Added line #L36 was not covered by tests
}

override fun build(): Node {
return parentNode.createNode<LogoutNode>(buildContext, plugins)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ class LogoutNode @AssistedInject constructor(
plugins<LogoutEntryPoint.Callback>().forEach { it.onChangeRecoveryKeyClick() }
}

interface SuccessfulLogoutPendingAction : Plugin {
fun onSuccessfulLogoutPendingAction()
}

private val customOnSuccessfulLogoutPendingAction = plugins<SuccessfulLogoutPendingAction>().firstOrNull()

@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
Expand All @@ -41,7 +47,10 @@ class LogoutNode @AssistedInject constructor(
LogoutView(
state = state,
onChangeRecoveryKeyClick = ::onChangeRecoveryKeyClick,
onSuccessLogout = { onSuccessLogout(activity, isDark, it) },
onSuccessLogout = {
customOnSuccessfulLogoutPendingAction?.onSuccessfulLogoutPendingAction()
onSuccessLogout(activity, isDark, it)
},
onBackClick = ::navigateUp,
modifier = modifier,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ interface RoomListEntryPoint : FeatureEntryPoint {
fun onRoomSettingsClick(roomId: RoomId)
fun onReportBugClick()
fun onRoomDirectorySearchClick()
fun onLogoutForNativeSlidingSyncMigrationNeeded()
}
}
2 changes: 2 additions & 0 deletions features/roomlist/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
implementation(projects.libraries.push.api)
implementation(projects.features.invite.api)
implementation(projects.features.networkmonitor.api)
implementation(projects.features.logout.api)
implementation(projects.features.leaveroom.api)
implementation(projects.services.analytics.api)
implementation(libs.androidx.datastore.preferences)
Expand All @@ -75,6 +76,7 @@ dependencies {
testImplementation(projects.services.analytics.test)
testImplementation(projects.services.toolbox.test)
testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.features.logout.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.features.leaveroom.test)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ open class RoomListContentStateProvider : PreviewParameterProvider<RoomListConte
aRoomsContentState(summaries = persistentListOf()),
aSkeletonContentState(),
anEmptyContentState(),
aRoomsContentState(securityBannerState = SecurityBannerState.NeedsNativeSlidingSyncMigration),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
sealed interface RoomListEvents {
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
data object DismissRequestVerificationPrompt : RoomListEvents
data object DismissRecoveryKeyPrompt : RoomListEvents
data object DismissBanner : RoomListEvents
data object ToggleSearchResults : RoomListEvents
data class AcceptInvite(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
data class DeclineInvite(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.invite.api.response.AcceptDeclineInviteView
import io.element.android.features.logout.api.direct.DirectLogoutEvents
import io.element.android.features.logout.api.direct.DirectLogoutView
import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
import io.element.android.libraries.deeplink.usecase.InviteFriendsUseCase
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
import io.element.android.services.analytics.api.AnalyticsService

@ContributesNode(SessionScope::class)
Expand All @@ -36,6 +39,8 @@ class RoomListNode @AssistedInject constructor(
private val inviteFriendsUseCase: InviteFriendsUseCase,
private val analyticsService: AnalyticsService,
private val acceptDeclineInviteView: AcceptDeclineInviteView,
private val directLogoutView: DirectLogoutView,
private val enableNativeSlidingSyncUseCase: EnableNativeSlidingSyncUseCase,
) : Node(buildContext, plugins = plugins) {
init {
lifecycle.subscribe(
Expand Down Expand Up @@ -88,6 +93,7 @@ class RoomListNode @AssistedInject constructor(
override fun View(modifier: Modifier) {
val state = presenter.present()
val activity = LocalContext.current as Activity

RoomListView(
state = state,
onRoomClick = this::onRoomClick,
Expand All @@ -98,6 +104,13 @@ class RoomListNode @AssistedInject constructor(
onRoomSettingsClick = this::onRoomSettingsClick,
onMenuActionClick = { onMenuActionClick(activity, it) },
onRoomDirectorySearchClick = this::onRoomDirectorySearchClick,
onMigrateToNativeSlidingSyncClick = {
if (state.directLogoutState.canDoDirectSignOut) {
state.directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false))
} else {
plugins<RoomListEntryPoint.Callback>().forEach { it.onLogoutForNativeSlidingSyncMigrationNeeded() }
}
},
modifier = modifier,
) {
acceptDeclineInviteView.Render(
Expand All @@ -107,5 +120,9 @@ class RoomListNode @AssistedInject constructor(
modifier = Modifier
)
}

directLogoutView.Render(state.directLogoutState) {
enableNativeSlidingSyncUseCase()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.InviteData
import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
Expand Down Expand Up @@ -88,6 +89,7 @@ class RoomListPresenter @Inject constructor(
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter,
private val notificationCleaner: NotificationCleaner,
private val logoutPresenter: DirectLogoutPresenter,
) : Presenter<RoomListState> {
private val encryptionService: EncryptionService = client.encryptionService()
private val syncService: SyncService = client.syncService()
Expand Down Expand Up @@ -115,13 +117,15 @@ class RoomListPresenter @Inject constructor(

val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }

val directLogoutState = logoutPresenter.present()

fun handleEvents(event: RoomListEvents) {
when (event) {
is RoomListEvents.UpdateVisibleRange -> coroutineScope.launch {
updateVisibleRange(event.range)
}
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
RoomListEvents.DismissRecoveryKeyPrompt -> securityBannerDismissed = true
RoomListEvents.DismissBanner -> securityBannerDismissed = true
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
is RoomListEvents.ShowContextMenu -> {
coroutineScope.showContextMenu(event, contextMenu)
Expand Down Expand Up @@ -161,13 +165,15 @@ class RoomListPresenter @Inject constructor(
searchState = searchState,
contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState,
directLogoutState = directLogoutState,
eventSink = ::handleEvents,
)
}

@Composable
private fun securityBannerState(
securityBannerDismissed: Boolean,
needsSlidingSyncMigration: Boolean,
): State<SecurityBannerState> {
val currentSecurityBannerDismissed by rememberUpdatedState(securityBannerDismissed)
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
Expand All @@ -176,6 +182,7 @@ class RoomListPresenter @Inject constructor(
derivedStateOf {
when {
currentSecurityBannerDismissed -> SecurityBannerState.None
needsSlidingSyncMigration -> SecurityBannerState.NeedsNativeSlidingSyncMigration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that the NeedsNativeSlidingSyncMigration banner has higher priority than the SetUpRecovery and RecoveryKeyConfirmation banner?

It will fully hide the two latter, since if NeedsNativeSlidingSyncMigration is dismissed, currentSecurityBannerDismissed will be true and the other banner will never be displayed again (until the user logout and login again).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right, this one should have the lowest priority.

syncState == SyncState.Running -> {
when (recoveryState) {
RecoveryState.UNKNOWN,
Expand Down Expand Up @@ -209,11 +216,14 @@ class RoomListPresenter @Inject constructor(
loadingState == RoomList.LoadingState.NotLoaded || roomSummaries is AsyncData.Loading
}
}
val needsSlidingSyncMigration by produceState(false) {
value = client.isNativeSlidingSyncSupported() && !client.isUsingNativeSlidingSync()
}
return when {
showEmpty -> RoomListContentState.Empty
showSkeleton -> RoomListContentState.Skeleton(count = 16)
else -> {
val securityBannerState by securityBannerState(securityBannerDismissed)
val securityBannerState by securityBannerState(securityBannerDismissed, needsSlidingSyncMigration)
RoomListContentState.Rooms(
securityBannerState = securityBannerState,
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Immutable
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.search.RoomListSearchState
Expand All @@ -31,6 +32,7 @@ data class RoomListState(
val searchState: RoomListSearchState,
val contentState: RoomListContentState,
val acceptDeclineInviteState: AcceptDeclineInviteState,
val directLogoutState: DirectLogoutState,
val eventSink: (RoomListEvents) -> Unit,
) {
val displayFilters = contentState is RoomListContentState.Rooms
Expand Down Expand Up @@ -59,6 +61,7 @@ enum class SecurityBannerState {
None,
SetUpRecovery,
RecoveryKeyConfirmation,
NeedsNativeSlidingSyncMigration,
}

@Immutable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
Expand Down Expand Up @@ -57,6 +59,7 @@ internal fun aRoomListState(
filtersState: RoomListFiltersState = aRoomListFiltersState(),
contentState: RoomListContentState = aRoomsContentState(),
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
directLogoutState: DirectLogoutState = aDirectLogoutState(),
eventSink: (RoomListEvents) -> Unit = {}
) = RoomListState(
matrixUser = matrixUser,
Expand All @@ -69,6 +72,7 @@ internal fun aRoomListState(
searchState = searchState,
contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState,
directLogoutState = directLogoutState,
eventSink = eventSink,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fun RoomListView(
onRoomSettingsClick: (roomId: RoomId) -> Unit,
onMenuActionClick: (RoomListMenuAction) -> Unit,
onRoomDirectorySearchClick: () -> Unit,
onMigrateToNativeSlidingSyncClick: () -> Unit,
modifier: Modifier = Modifier,
acceptDeclineInviteView: @Composable () -> Unit,
) {
Expand All @@ -76,6 +77,7 @@ fun RoomListView(
onOpenSettings = onSettingsClick,
onCreateRoomClick = onCreateRoomClick,
onMenuActionClick = onMenuActionClick,
onMigrateToNativeSlidingSyncClick = onMigrateToNativeSlidingSyncClick,
modifier = Modifier.padding(top = topPadding),
)
// This overlaid view will only be visible when state.displaySearchResults is true
Expand Down Expand Up @@ -105,6 +107,7 @@ private fun RoomListScaffold(
onOpenSettings: () -> Unit,
onCreateRoomClick: () -> Unit,
onMenuActionClick: (RoomListMenuAction) -> Unit,
onMigrateToNativeSlidingSyncClick: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onRoomClick(room: RoomListRoomSummary) {
Expand Down Expand Up @@ -140,6 +143,7 @@ private fun RoomListScaffold(
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
onRoomClick = ::onRoomClick,
onCreateRoomClick = onCreateRoomClick,
onMigrateToNativeSlidingSyncClick = onMigrateToNativeSlidingSyncClick,
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding)
Expand Down Expand Up @@ -180,5 +184,6 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class)
onMenuActionClick = {},
onRoomDirectorySearchClick = {},
acceptDeclineInviteView = {},
onMigrateToNativeSlidingSyncClick = {},
)
}
Loading
Loading