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

feat: show pending legal hold request and approve it [WPB-4393] #2484

Merged
merged 11 commits into from
Nov 30, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.wire.kalium.logic.data.call.Call
import com.wire.kalium.logic.data.conversation.LegalHoldStatus
import com.wire.kalium.logic.data.sync.SyncState
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCaseResult
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -77,6 +78,16 @@ class CommonTopAppBarViewModel @Inject constructor(
}
}

private fun legalHoldStatusFlow(userId: UserId) = coreLogic.sessionScope(userId) {
observeLegalHoldRequest() // TODO combine with legal host status
ohassine marked this conversation as resolved.
Show resolved Hide resolved
.map { legalHoldRequestResult ->
when (legalHoldRequestResult) {
is ObserveLegalHoldRequestUseCaseResult.ObserveLegalHoldRequestAvailable -> LegalHoldStatus.PENDING
ohassine marked this conversation as resolved.
Show resolved Hide resolved
else -> LegalHoldStatus.NO_CONSENT
ohassine marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

init {
viewModelScope.launch {
coreLogic.globalScope {
Expand All @@ -90,10 +101,11 @@ class CommonTopAppBarViewModel @Inject constructor(
combine(
activeCallFlow(userId),
currentScreenFlow(),
connectivityFlow(userId)
) { activeCall, currentScreen, connectivity ->
connectivityFlow(userId),
legalHoldStatusFlow(userId),
) { activeCall, currentScreen, connectivity, legalHoldStatus ->
mapToConnectivityUIState(currentScreen, connectivity, activeCall) to
mapToLegalHoldUIState(currentScreen, LegalHoldStatus.NO_CONSENT) // TODO: get legalHoldStatus
mapToLegalHoldUIState(currentScreen, legalHoldStatus)
}
}
}
Expand Down Expand Up @@ -148,7 +160,10 @@ class CommonTopAppBarViewModel @Inject constructor(
LegalHoldStatus.DISABLED,
LegalHoldStatus.NO_CONSENT -> LegalHoldUIState.None
}.let { legalHoldUIState ->
if (currentScreen is CurrentScreen.AuthRelated) LegalHoldUIState.None
if (currentScreen is CurrentScreen.AuthRelated
|| currentScreen is CurrentScreen.OngoingCallScreen
|| currentScreen is CurrentScreen.IncomingCallScreen
ohassine marked this conversation as resolved.
Show resolved Hide resolved
) LegalHoldUIState.None
else legalHoldUIState
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import com.wire.android.ui.theme.wireDimensions
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.extension.formatAsFingerPrint
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.user.UserId

@OptIn(ExperimentalComposeUiApi::class)
@Composable
Expand Down Expand Up @@ -141,7 +142,11 @@ fun LegalHoldRequestedDialog(
fun PreviewLegalHoldRequestedDialogWithPassword() {
WireTheme {
LegalHoldRequestedDialog(
LegalHoldRequestedState.Visible(legalHoldDeviceFingerprint = "0123456789ABCDEF", requiresPassword = true), {}, {}, {}
LegalHoldRequestedState.Visible(
legalHoldDeviceFingerprint = "0123456789ABCDEF",
requiresPassword = true,
userId = UserId("", ""),
), {}, {}, {}
)
}
}
Expand All @@ -151,7 +156,11 @@ fun PreviewLegalHoldRequestedDialogWithPassword() {
fun PreviewLegalHoldRequestedDialogWithoutPassword() {
WireTheme {
LegalHoldRequestedDialog(
LegalHoldRequestedState.Visible(legalHoldDeviceFingerprint = "0123456789ABCDEF", requiresPassword = false), {}, {}, {}
LegalHoldRequestedState.Visible(
legalHoldDeviceFingerprint = "0123456789ABCDEF",
requiresPassword = false,
userId = UserId("", ""),
), {}, {}, {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.wire.android.ui.legalhold.dialog.requested

import androidx.compose.ui.text.input.TextFieldValue
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.user.UserId

sealed class LegalHoldRequestedState {
data object Hidden : LegalHoldRequestedState()
Expand All @@ -29,6 +30,7 @@ sealed class LegalHoldRequestedState {
val loading: Boolean = false,
val acceptEnabled: Boolean = false,
val error: LegalHoldRequestedError = LegalHoldRequestedError.None,
val userId: UserId,
) : LegalHoldRequestedState()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@
import com.wire.android.appLogger
import com.wire.android.di.KaliumCoreLogic
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.UserSessionScope
import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase
import com.wire.kalium.logic.feature.legalhold.ApproveLegalHoldRequestUseCase
import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCaseResult
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.user.IsPasswordRequiredUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -41,7 +52,62 @@

var state: LegalHoldRequestedState by mutableStateOf(LegalHoldRequestedState.Hidden)
private set
// TODO: get legal hold status of current account

private val legalHoldRequestDataStateFlow = currentSessionFlow(noSession = LegalHoldRequestData.None) { userId ->
observeLegalHoldRequest()
.mapLatest { legalHoldRequestResult ->
when (legalHoldRequestResult) {
is ObserveLegalHoldRequestUseCaseResult.Failure -> {
appLogger.e("$TAG: Failed to get legal hold request data: ${legalHoldRequestResult.failure}")
LegalHoldRequestData.None
}

ObserveLegalHoldRequestUseCaseResult.NoObserveLegalHoldRequest -> LegalHoldRequestData.None
is ObserveLegalHoldRequestUseCaseResult.ObserveLegalHoldRequestAvailable ->
users.isPasswordRequired()
.let {
LegalHoldRequestData.Pending(
legalHoldRequestResult.fingerprint.decodeToString(),
(it as? IsPasswordRequiredUseCase.Result.Success)?.value ?: true,
userId,
)
}
}
}
}.stateIn(viewModelScope, SharingStarted.Eagerly, LegalHoldRequestData.None)

private fun <T> currentSessionFlow(noSession: T, session: UserSessionScope.(UserId) -> Flow<T>): Flow<T> =
coreLogic.getGlobalScope().session.currentSessionFlow()
.flatMapLatest { currentSessionResult ->
when (currentSessionResult) {
is CurrentSessionResult.Failure.Generic -> {
appLogger.e("$TAG: Failed to get current session")
flowOf(noSession)
}

CurrentSessionResult.Failure.SessionNotFound -> flowOf(noSession)
is CurrentSessionResult.Success ->
currentSessionResult.accountInfo.userId.let { coreLogic.getSessionScope(it).session(it) }
}
}

init {
viewModelScope.launch {
legalHoldRequestDataStateFlow.collectLatest { legalHoldRequestData ->
state = when (legalHoldRequestData) {
is LegalHoldRequestData.Pending -> {
LegalHoldRequestedState.Visible(
requiresPassword = legalHoldRequestData.isPasswordRequired,
legalHoldDeviceFingerprint = legalHoldRequestData.fingerprint,
userId = legalHoldRequestData.userId,
)
}

LegalHoldRequestData.None -> LegalHoldRequestedState.Hidden
}
}
}

Check warning on line 109 in app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt#L109

Added line #L109 was not covered by tests
}

private fun LegalHoldRequestedState.ifVisible(action: (LegalHoldRequestedState.Visible) -> Unit) {
if (this is LegalHoldRequestedState.Visible) action(this)
Expand All @@ -57,25 +123,13 @@
state = LegalHoldRequestedState.Hidden
}

private suspend fun checkIfPasswordRequired(action: (Boolean) -> Unit) {
when (val currentSessionResult = coreLogic.getGlobalScope().session.currentSession()) {
CurrentSessionResult.Failure.SessionNotFound -> appLogger.e("$TAG: Session not found")
is CurrentSessionResult.Failure.Generic -> appLogger.e("$TAG: Failed to get current session")
is CurrentSessionResult.Success -> action(
coreLogic.getSessionScope(currentSessionResult.accountInfo.userId).users.isPasswordRequired()
.let { (it as? IsPasswordRequiredUseCase.Result.Success)?.value ?: true }
)
}
}

fun show() {
viewModelScope.launch {
checkIfPasswordRequired { isPasswordRequired ->
state = LegalHoldRequestedState.Visible(
requiresPassword = isPasswordRequired,
legalHoldDeviceFingerprint = "0123456789ABCDEF" // TODO: get legal hold client fingerprint
)
}
(legalHoldRequestDataStateFlow.value as? LegalHoldRequestData.Pending)?.let {
state = LegalHoldRequestedState.Visible(
requiresPassword = it.isPasswordRequired,
legalHoldDeviceFingerprint = it.fingerprint,
userId = it.userId,

Check warning on line 131 in app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt#L128-L131

Added lines #L128 - L131 were not covered by tests
)
}
}

Expand All @@ -84,14 +138,55 @@
state = it.copy(acceptEnabled = false, loading = true)
// the accept button is enabled if the password is valid, this check is for safety only
validatePassword(it.password.text).let { validatePasswordResult ->
state = when (validatePasswordResult.isValid) {
false -> it.copy(loading = false, error = LegalHoldRequestedError.InvalidCredentialsError)
true -> LegalHoldRequestedState.Hidden // TODO: accept legal hold
when (validatePasswordResult.isValid) {
false ->
state = it.copy(
loading = false,
error = LegalHoldRequestedError.InvalidCredentialsError
)

true ->
viewModelScope.launch {
coreLogic.sessionScope(it.userId) {
approveLegalHoldRequest(it.password.text).let { approveLegalHoldResult ->
state = when (approveLegalHoldResult) {
is ApproveLegalHoldRequestUseCase.Result.Success ->
LegalHoldRequestedState.Hidden

ApproveLegalHoldRequestUseCase.Result.Failure.InvalidPassword ->
it.copy(
loading = false,
error = LegalHoldRequestedError.InvalidCredentialsError
)

ApproveLegalHoldRequestUseCase.Result.Failure.PasswordRequired ->
it.copy(
loading = false,
requiresPassword = true,
error = LegalHoldRequestedError.InvalidCredentialsError
)

is ApproveLegalHoldRequestUseCase.Result.Failure.GenericFailure -> {
appLogger.e("$TAG: Failed to approve legal hold: ${approveLegalHoldResult.coreFailure}")
it.copy(
loading = false,
error = LegalHoldRequestedError.GenericError(approveLegalHoldResult.coreFailure)
)
}
}
}
}
}
}
}
}
}

private sealed class LegalHoldRequestData {
data object None : LegalHoldRequestData()
data class Pending(val fingerprint: String, val isPasswordRequired: Boolean, val userId: UserId) : LegalHoldRequestData()
}

companion object {
private const val TAG = "LegalHoldRequestedViewModel"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package com.wire.android.ui.common.topappbar

import com.wire.android.config.CoroutineTestExtension
import com.wire.android.ui.legalhold.banner.LegalHoldUIState
import com.wire.android.util.CurrentScreen
import com.wire.android.util.CurrentScreenManager
import com.wire.kalium.logic.CoreLogic
Expand All @@ -31,6 +32,7 @@ import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.sync.SyncState
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase
import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCaseResult
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.sync.ObserveSyncStateUseCase
import io.mockk.MockKAnnotations
Expand Down Expand Up @@ -159,18 +161,20 @@ class CommonTopAppBarViewModelTest {
}

@Test
fun givenActiveCallAndConnectivityIssueAndSomeOtherScreen_whenGettingState_thenShouldHaveNoInfo() = runTest {
fun givenActiveCallAndConnectivityIssueAndSomeOtherScreen_whenGettingState_thenShouldHaveActiveCallInfo() = runTest {
val (_, commonTopAppBarViewModel) = Arrangement()
.withCurrentSessionExist()
.withActiveCall()
.withCurrentScreen(CurrentScreen.SomeOther)
.withCallMuted(false)
.withSyncState(SyncState.Waiting)
.arrange()

advanceUntilIdle()
val state = commonTopAppBarViewModel.state

val info = state.connectivityState
info shouldBeInstanceOf ConnectivityUIState.None::class
info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class
}

@Test
Expand All @@ -182,10 +186,55 @@ class CommonTopAppBarViewModelTest {
advanceUntilIdle()
val state = commonTopAppBarViewModel.state

val info = state.connectivityState
info shouldBeInstanceOf ConnectivityUIState.None::class
state.connectivityState shouldBeInstanceOf ConnectivityUIState.None::class
state.legalHoldState shouldBeInstanceOf LegalHoldUIState.None::class
}

private fun testLegalHoldRequestInfo(
currentScreen: CurrentScreen,
result: ObserveLegalHoldRequestUseCaseResult,
expectedState: LegalHoldUIState,
) = runTest {
val (_, commonTopAppBarViewModel) = Arrangement()
.withCurrentSessionExist()
.withCurrentScreen(currentScreen)
.withLegalHoldRequestResult(result)
.arrange()

advanceUntilIdle()
val state = commonTopAppBarViewModel.state

state.legalHoldState shouldBeInstanceOf expectedState::class
}

@Test
fun givenNoLegalHoldRequest_whenGettingState_thenShouldNotHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo(
currentScreen = CurrentScreen.Home,
result = ObserveLegalHoldRequestUseCaseResult.NoObserveLegalHoldRequest,
expectedState = LegalHoldUIState.None
)

@Test
fun givenLegalHoldRequestAndHomeScreen_whenGettingState_thenShouldHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo(
currentScreen = CurrentScreen.Home,
result = ObserveLegalHoldRequestUseCaseResult.ObserveLegalHoldRequestAvailable(byteArrayOf()),
expectedState = LegalHoldUIState.Pending
)

@Test
fun givenLegalHoldRequestAndCallScreen_whenGettingState_thenShouldNotHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo(
currentScreen = CurrentScreen.OngoingCallScreen(mockk()),
result = ObserveLegalHoldRequestUseCaseResult.ObserveLegalHoldRequestAvailable(byteArrayOf()),
expectedState = LegalHoldUIState.None
)

@Test
fun givenLegalHoldRequestAndAuthRelatedScreen_whenGettingState_thenShouldNotHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo(
currentScreen = CurrentScreen.AuthRelated,
result = ObserveLegalHoldRequestUseCaseResult.ObserveLegalHoldRequestAvailable(byteArrayOf()),
expectedState = LegalHoldUIState.None
)

private class Arrangement {

val activeCall: Call = mockk()
Expand Down Expand Up @@ -230,6 +279,10 @@ class CommonTopAppBarViewModelTest {
every {
globalKaliumScope.session.currentSessionFlow()
} returns emptyFlow()

withSyncState(SyncState.Live)
withoutActiveCall()
withLegalHoldRequestResult(ObserveLegalHoldRequestUseCaseResult.NoObserveLegalHoldRequest)
}

private val commonTopAppBarViewModel by lazy {
Expand Down Expand Up @@ -274,6 +327,10 @@ class CommonTopAppBarViewModelTest {
coEvery { currentScreenManager.observeCurrentScreen(any()) } returns MutableStateFlow(currentScreen)
}

fun withLegalHoldRequestResult(result: ObserveLegalHoldRequestUseCaseResult) = apply {
every { coreLogic.getSessionScope(any()).observeLegalHoldRequest() } returns flowOf(result)
}

fun arrange() = this to commonTopAppBarViewModel
}
}
Loading
Loading