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

fix: Re-fetching of devices certificates on comming back from DeviceDetails [WPB-6970] #3103

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 @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -97,7 +105,7 @@ fun SelfDevicesScreenContent(
isE2EIEnabled = state.isE2EIEnabled,
onDeviceClick = onDeviceClick,

)
)
}
folderDeviceItems(
header = context.getString(R.string.other_devices_label),
Expand All @@ -113,6 +121,7 @@ fun SelfDevicesScreenContent(
}
)
}

@Suppress("LongParameterList")
private fun LazyListScope.folderDeviceItems(
header: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -52,6 +55,9 @@ class SelfDevicesViewModel @Inject constructor(
)
private set

private val refreshE2eiCertificates: MutableSharedFlow<Unit> = MutableSharedFlow<Unit>()
private val observeUserE2eiCertificates = refreshE2eiCertificates.map { getUserE2eiCertificates(currentAccountId) }

init {
// this will cause the list to be refreshed
loadClientsList()
Expand All @@ -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]) }
)
}
}
}
}
}
}

Expand All @@ -86,4 +92,10 @@ class SelfDevicesViewModel @Inject constructor(
fetchSelfClientsFromRemote()
}
}

fun loadCertificates() {
viewModelScope.launch {
refreshE2eiCertificates.emit(Unit)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ 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 io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
Expand All @@ -49,14 +51,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
Expand Down
Loading