From ab3cabec01ec619a0cd8ab3105a8f04f396593a2 Mon Sep 17 00:00:00 2001 From: boris Date: Tue, 18 Jun 2024 10:31:40 +0300 Subject: [PATCH] fix: Re-fetching of devices certificates on comming back from DeviceDetails [WPB-6970] (#3103) --- .../ui/settings/devices/SelfDevicesScreen.kt | 11 ++++- .../settings/devices/SelfDevicesViewModel.kt | 44 ++++++++++++------- .../lifecycle/LifecycleEventObservation.kt | 44 +++++++++++++++++++ .../devices/SelfDevicesViewModelTest.kt | 19 +++++++- 4 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/util/lifecycle/LifecycleEventObservation.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt index 9b10b2d1310..02f66eeb3e4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesScreen.kt @@ -28,12 +28,14 @@ import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph import com.wire.android.R @@ -48,6 +50,7 @@ import com.wire.android.ui.destinations.DeviceDetailsScreenDestination import com.wire.android.ui.settings.devices.model.SelfDevicesState import com.wire.android.ui.theme.wireColorScheme import com.wire.android.util.extension.folderWithElements +import com.wire.android.util.lifecycle.rememberLifecycleEvent @RootNavGraph @Destination @@ -56,6 +59,11 @@ fun SelfDevicesScreen( navigator: Navigator, viewModel: SelfDevicesViewModel = hiltViewModel() ) { + val lifecycleEvent = rememberLifecycleEvent() + LaunchedEffect(lifecycleEvent) { + if (lifecycleEvent == Lifecycle.Event.ON_RESUME) viewModel.loadCertificates() + } + SelfDevicesScreenContent( state = viewModel.state, onNavigateBack = navigator::navigateBack, @@ -97,7 +105,7 @@ fun SelfDevicesScreenContent( isE2EIEnabled = state.isE2EIEnabled, onDeviceClick = onDeviceClick, - ) + ) } folderDeviceItems( header = context.getString(R.string.other_devices_label), @@ -113,6 +121,7 @@ fun SelfDevicesScreenContent( } ) } + @Suppress("LongParameterList") private fun LazyListScope.folderDeviceItems( header: String, diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModel.kt index 572d963cde7..b89f7073076 100644 --- a/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModel.kt @@ -33,7 +33,10 @@ import com.wire.kalium.logic.feature.client.ObserveCurrentClientIdUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificatesUseCase import com.wire.kalium.logic.feature.user.IsE2EIEnabledUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -52,6 +55,9 @@ class SelfDevicesViewModel @Inject constructor( ) private set + private val refreshE2eiCertificates: MutableSharedFlow = MutableSharedFlow() + private val observeUserE2eiCertificates = refreshE2eiCertificates.map { getUserE2eiCertificates(currentAccountId) } + init { // this will cause the list to be refreshed loadClientsList() @@ -60,24 +66,24 @@ class SelfDevicesViewModel @Inject constructor( private fun observeClientList() { viewModelScope.launch { - observeClientList(currentAccountId).collect { result -> - state = when (result) { - is ObserveClientsByUserIdUseCase.Result.Failure -> state.copy(isLoadingClientsList = false) - is ObserveClientsByUserIdUseCase.Result.Success -> { - val currentClientId = currentClientIdUseCase().firstOrNull() - val e2eiCertificates = getUserE2eiCertificates(currentAccountId) - state.copy( - isLoadingClientsList = false, - currentDevice = result.clients - .firstOrNull { it.id == currentClientId } - ?.let { Device(it, e2eiCertificates[it.id.value]) }, - deviceList = result.clients - .filter { it.id != currentClientId } - .map { Device(it, e2eiCertificates[it.id.value]) } - ) + observeClientList(currentAccountId).combine(observeUserE2eiCertificates, ::Pair) + .collect { (result, e2eiCertificates) -> + state = when (result) { + is ObserveClientsByUserIdUseCase.Result.Failure -> state.copy(isLoadingClientsList = false) + is ObserveClientsByUserIdUseCase.Result.Success -> { + val currentClientId = currentClientIdUseCase().firstOrNull() + state.copy( + isLoadingClientsList = false, + currentDevice = result.clients + .firstOrNull { it.id == currentClientId } + ?.let { Device(it, e2eiCertificates[it.id.value]) }, + deviceList = result.clients + .filter { it.id != currentClientId } + .map { Device(it, e2eiCertificates[it.id.value]) } + ) + } } } - } } } @@ -86,4 +92,10 @@ class SelfDevicesViewModel @Inject constructor( fetchSelfClientsFromRemote() } } + + fun loadCertificates() { + viewModelScope.launch { + refreshE2eiCertificates.emit(Unit) + } + } } diff --git a/app/src/main/kotlin/com/wire/android/util/lifecycle/LifecycleEventObservation.kt b/app/src/main/kotlin/com/wire/android/util/lifecycle/LifecycleEventObservation.kt new file mode 100644 index 00000000000..9a85ca0b52b --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/lifecycle/LifecycleEventObservation.kt @@ -0,0 +1,44 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util.lifecycle + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner + +@Composable +fun rememberLifecycleEvent(lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current): Lifecycle.Event { + var state by remember { mutableStateOf(Lifecycle.Event.ON_ANY) } + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + state = event + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + return state +} diff --git a/app/src/test/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModelTest.kt index 295d6c05eca..2a17c3fda94 100644 --- a/app/src/test/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/settings/devices/SelfDevicesViewModelTest.kt @@ -32,6 +32,7 @@ import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificatesUseCase import com.wire.kalium.logic.feature.user.IsE2EIEnabledUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf @@ -49,14 +50,30 @@ class SelfDevicesViewModelTest { // given val (_, viewModel) = Arrangement() .arrange() + val currentDevice = Device(TestClient.CLIENT) // when - val currentDevice = Device(TestClient.CLIENT) + viewModel.loadCertificates() // then assert(!viewModel.state.deviceList.contains(currentDevice)) } + @Test + fun `given a self client id, when loadCertificates is called, then E2EI Certificates is fetched again`() = + runTest { + // given + val (arragne, viewModel) = Arrangement() + .arrange() + + // when + viewModel.loadCertificates() + viewModel.loadCertificates() + + // then + coVerify(exactly = 2) { arragne.getUserE2eiCertificates(any()) } + } + private class Arrangement { @MockK