diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt index 3273748b88..eae7fab816 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt @@ -84,6 +84,9 @@ import com.wire.android.ui.home.E2EIRequiredDialog import com.wire.android.ui.home.E2EISnoozeDialog import com.wire.android.ui.home.appLock.LockCodeTimeManager import com.wire.android.ui.home.sync.FeatureFlagNotificationViewModel +import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedDialog +import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedState +import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedViewModel import com.wire.android.ui.theme.ThemeOption import com.wire.android.ui.theme.WireTheme import com.wire.android.util.CurrentScreenManager @@ -120,6 +123,7 @@ class WireActivity : AppCompatActivity() { private val featureFlagNotificationViewModel: FeatureFlagNotificationViewModel by viewModels() private val commonTopAppBarViewModel: CommonTopAppBarViewModel by viewModels() + private val legalHoldRequestedViewModel: LegalHoldRequestedViewModel by viewModels() val navigationCommands: MutableSharedFlow = MutableSharedFlow() @@ -186,10 +190,11 @@ class WireActivity : AppCompatActivity() { ReportDrawnWhen { isLoaded } val navigator = rememberNavigator(this@WireActivity::finish) CommonTopAppBar( - connectivityUIState = commonTopAppBarViewModel.connectivityState, + commonTopAppBarState = commonTopAppBarViewModel.state, onReturnToCallClick = { establishedCall -> navigator.navigate(NavigationCommand(OngoingCallScreenDestination(establishedCall.conversationId))) - } + }, + onPendingClicked = legalHoldRequestedViewModel::show, ) NavigationGraph( navigator = navigator, @@ -254,6 +259,7 @@ class WireActivity : AppCompatActivity() { } } + @Suppress("ComplexMethod") @Composable private fun handleDialogs(navigate: (NavigationCommand) -> Unit) { featureFlagNotificationViewModel.loadInitialSync() @@ -286,6 +292,14 @@ class WireActivity : AppCompatActivity() { } ) } else { + if (legalHoldRequestedViewModel.state is LegalHoldRequestedState.Visible) { + LegalHoldRequestedDialog( + state = legalHoldRequestedViewModel.state as LegalHoldRequestedState.Visible, + passwordChanged = legalHoldRequestedViewModel::passwordChanged, + notNowClicked = legalHoldRequestedViewModel::notNowClicked, + acceptClicked = legalHoldRequestedViewModel::acceptClicked, + ) + } if (showFileSharingDialog) { FileRestrictionDialog( isFileSharingEnabled = isFileSharingEnabledState, diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt index e346fb02a0..f0e31d85f4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt @@ -38,56 +38,52 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntSize import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.wire.android.R +import com.wire.android.ui.legalhold.banner.LegalHoldStatusBar +import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId @Composable fun CommonTopAppBar( - connectivityUIState: ConnectivityUIState, - onReturnToCallClick: (ConnectivityUIState.Info.EstablishedCall) -> Unit + commonTopAppBarState: CommonTopAppBarState, + onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit, + onPendingClicked: () -> Unit, ) { - ConnectivityStatusBar( - connectivityInfo = connectivityUIState.info, - onReturnToCallClick = onReturnToCallClick - ) + Column { + ConnectivityStatusBar( + connectivityInfo = commonTopAppBarState.connectivityState, + onReturnToCallClick = onReturnToCallClick + ) + LegalHoldStatusBar( + legalHoldState = commonTopAppBarState.legalHoldState, + onPendingClicked = onPendingClicked + ) + } } @Composable private fun ConnectivityStatusBar( - connectivityInfo: ConnectivityUIState.Info, - onReturnToCallClick: (ConnectivityUIState.Info.EstablishedCall) -> Unit + connectivityInfo: ConnectivityUIState, + onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit ) { - val isVisible = connectivityInfo !is ConnectivityUIState.Info.None - - // So it keeps the current colour while the animation is collapsing the bar - val initialColour = MaterialTheme.wireColorScheme.primary - var lastVisibleBackgroundColor by remember { - mutableStateOf(initialColour) - } - + val isVisible = connectivityInfo !is ConnectivityUIState.None val backgroundColor = when (connectivityInfo) { - is ConnectivityUIState.Info.EstablishedCall -> - MaterialTheme.wireColorScheme.connectivityBarOngoingCallBackgroundColor - - ConnectivityUIState.Info.Connecting, ConnectivityUIState.Info.WaitingConnection -> - MaterialTheme.wireColorScheme.primary - - ConnectivityUIState.Info.None -> lastVisibleBackgroundColor + is ConnectivityUIState.EstablishedCall -> MaterialTheme.wireColorScheme.positive + ConnectivityUIState.Connecting, ConnectivityUIState.WaitingConnection -> MaterialTheme.wireColorScheme.primary + ConnectivityUIState.None -> MaterialTheme.wireColorScheme.background } if (!isVisible) { clearStatusBarColor() @@ -107,7 +103,7 @@ private fun ConnectivityStatusBar( .height(MaterialTheme.wireDimensions.ongoingCallLabelHeight) .background(backgroundColor) .run { - if (connectivityInfo is ConnectivityUIState.Info.EstablishedCall) { + if (connectivityInfo is ConnectivityUIState.EstablishedCall) { clickable(onClick = { onReturnToCallClick(connectivityInfo) }) } else this } @@ -123,11 +119,13 @@ private fun ConnectivityStatusBar( verticalArrangement = Arrangement.Center ) { when (connectivityInfo) { - is ConnectivityUIState.Info.EstablishedCall -> OngoingCallContent(connectivityInfo.isMuted) - ConnectivityUIState.Info.Connecting -> StatusLabel(R.string.connectivity_status_bar_connecting) - ConnectivityUIState.Info.WaitingConnection -> - StatusLabel(R.string.connectivity_status_bar_waiting_for_network) - ConnectivityUIState.Info.None -> {} + is ConnectivityUIState.EstablishedCall -> + OngoingCallContent(connectivityInfo.isMuted) + ConnectivityUIState.Connecting -> + StatusLabel(R.string.connectivity_status_bar_connecting, MaterialTheme.wireColorScheme.onPrimary) + ConnectivityUIState.WaitingConnection -> + StatusLabel(R.string.connectivity_status_bar_waiting_for_network, MaterialTheme.wireColorScheme.onPrimary) + ConnectivityUIState.None -> {} } } } @@ -136,33 +134,33 @@ private fun ConnectivityStatusBar( @Composable private fun OngoingCallContent(isMuted: Boolean) { Row { - MicrophoneIcon(isMuted) - CameraIcon() - StatusLabel(R.string.connectivity_status_bar_return_to_call) + MicrophoneIcon(isMuted, MaterialTheme.wireColorScheme.onPositive) + CameraIcon(MaterialTheme.wireColorScheme.onPositive) + StatusLabel(R.string.connectivity_status_bar_return_to_call, MaterialTheme.wireColorScheme.onPositive) } } @Composable -private fun StatusLabel(stringResource: Int) { +private fun StatusLabel(stringResource: Int, color: Color = MaterialTheme.wireColorScheme.onPrimary) { Text( text = stringResource(id = stringResource).uppercase(), - color = MaterialTheme.wireColorScheme.onPrimary, + color = color, style = MaterialTheme.wireTypography.title03, ) } @Composable -fun StatusLabel(message: String) { +fun StatusLabel(message: String, color: Color = MaterialTheme.wireColorScheme.onPrimary) { Text( text = message, textAlign = TextAlign.Center, - color = MaterialTheme.wireColorScheme.onPrimary, + color = color, style = MaterialTheme.wireTypography.title03, ) } @Composable -private fun CameraIcon() { +private fun CameraIcon(tint: Color = MaterialTheme.wireColorScheme.onPositive) { Icon( painter = painterResource(id = R.drawable.ic_camera_white_paused), contentDescription = stringResource(R.string.content_description_calling_call_paused_camera), @@ -170,12 +168,12 @@ private fun CameraIcon() { start = MaterialTheme.wireDimensions.spacing8x, end = MaterialTheme.wireDimensions.spacing8x ), - tint = MaterialTheme.wireColorScheme.onPrimary + tint = tint ) } @Composable -private fun MicrophoneIcon(isMuted: Boolean) { +private fun MicrophoneIcon(isMuted: Boolean, tint: Color = MaterialTheme.wireColorScheme.onPositive) { Icon( painter = painterResource( id = if (isMuted) R.drawable.ic_microphone_white_muted @@ -185,7 +183,7 @@ private fun MicrophoneIcon(isMuted: Boolean) { id = if (isMuted) R.string.content_description_calling_call_muted else R.string.content_description_calling_call_unmuted ), - tint = MaterialTheme.wireColorScheme.onPrimary + tint = tint ) } @@ -200,51 +198,53 @@ private fun clearStatusBarColor() { ) } -@Preview("is NOT muted") @Composable -fun PreviewCommonTopAppBarCallIsNotMuted() { - ConnectivityStatusBar( - connectivityInfo = ConnectivityUIState.Info.EstablishedCall( - ConversationId("what", "ever"), false - ), - onReturnToCallClick = { } - ) +private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState, legalHoldUIState: LegalHoldUIState) { + WireTheme { + CommonTopAppBar(CommonTopAppBarState(connectivityUIState, legalHoldUIState), {}, {}) + } } -@Preview("is muted") +@PreviewMultipleThemes @Composable -fun PreviewCommonTopAppBarCallIsMuted() { - ConnectivityStatusBar( - connectivityInfo = ConnectivityUIState.Info.EstablishedCall( - ConversationId("what", "ever"), true - ), - onReturnToCallClick = { } - ) -} +fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldNone() = + PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.None) -@Preview("is connecting") +@PreviewMultipleThemes @Composable -fun PreviewCommonTopAppConnectionStatusIsConnecting() { - ConnectivityStatusBar( - connectivityInfo = ConnectivityUIState.Info.Connecting, - onReturnToCallClick = { } - ) -} +fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldPending() = + PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.Pending) -@Preview("is waiting connection") +@PreviewMultipleThemes @Composable -fun PreviewCommonTopAppConnectionStatusIsWaitingConnection() { - ConnectivityStatusBar( - connectivityInfo = ConnectivityUIState.Info.WaitingConnection, - onReturnToCallClick = { } - ) -} +fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldActive() = + PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.Active) -@Preview("is None") +@PreviewMultipleThemes @Composable -fun PreviewCommonTopAppConnectionStatusIsNone() { - ConnectivityStatusBar( - connectivityInfo = ConnectivityUIState.Info.None, - onReturnToCallClick = { } - ) -} +fun PreviewCommonTopAppBar_ConnectivityConnecting_LegalHoldNone() = + PreviewCommonTopAppBar(ConnectivityUIState.Connecting, LegalHoldUIState.None) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityConnecting_LegalHoldPending() = + PreviewCommonTopAppBar(ConnectivityUIState.Connecting, LegalHoldUIState.Pending) +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityConnecting_LegalHoldActive() = + PreviewCommonTopAppBar(ConnectivityUIState.Connecting, LegalHoldUIState.Active) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldNone() = + PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.None) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldPending() = + PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.Pending) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldActive() = + PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.Active) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt new file mode 100644 index 0000000000..f8efc49031 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt @@ -0,0 +1,25 @@ +/* + * Wire + * Copyright (C) 2023 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.ui.common.topappbar + +import com.wire.android.ui.legalhold.banner.LegalHoldUIState + +data class CommonTopAppBarState( + val connectivityState: ConnectivityUIState = ConnectivityUIState.None, + val legalHoldState: LegalHoldUIState = LegalHoldUIState.None, +) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt index 256c4d1672..f186a3b2f8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt @@ -26,10 +26,12 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.di.KaliumCoreLogic +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 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.session.CurrentSessionResult @@ -46,17 +48,16 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject -abstract class CommonTopAppBarBaseViewModel : ViewModel() - @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class CommonTopAppBarViewModel @Inject constructor( private val currentScreenManager: CurrentScreenManager, @KaliumCoreLogic private val coreLogic: CoreLogic, -) : CommonTopAppBarBaseViewModel() { +) : ViewModel() { - var connectivityState by mutableStateOf(ConnectivityUIState(ConnectivityUIState.Info.None)) + var state by mutableStateOf(CommonTopAppBarState()) + private set private suspend fun currentScreenFlow() = currentScreenManager.observeCurrentScreen(viewModelScope) @@ -69,6 +70,7 @@ class CommonTopAppBarViewModel @Inject constructor( } } } + private suspend fun activeCallFlow(userId: UserId): Flow = coreLogic.sessionScope(userId) { calls.establishedCall().distinctUntilChanged().map { calls -> calls.firstOrNull() @@ -81,7 +83,7 @@ class CommonTopAppBarViewModel @Inject constructor( session.currentSessionFlow().flatMapLatest { when (it) { is CurrentSessionResult.Failure.Generic, - CurrentSessionResult.Failure.SessionNotFound -> flowOf(ConnectivityUIState.Info.None) + is CurrentSessionResult.Failure.SessionNotFound -> flowOf(ConnectivityUIState.None to LegalHoldUIState.None) is CurrentSessionResult.Success -> { val userId = it.accountInfo.userId @@ -90,11 +92,13 @@ class CommonTopAppBarViewModel @Inject constructor( currentScreenFlow(), connectivityFlow(userId) ) { activeCall, currentScreen, connectivity -> - mapToUIState(currentScreen, connectivity, activeCall) + mapToConnectivityUIState(currentScreen, connectivity, activeCall) to + mapToLegalHoldUIState(currentScreen, LegalHoldStatus.NO_CONSENT) // TODO: get legalHoldStatus } } } - }.collectLatest { connectivityUIState -> + }.collectLatest { (connectivityUIState, legalHoldUIState) -> + state = state.copy(legalHoldState = legalHoldUIState) /** * Adding some delay here to avoid some bad UX : ongoing call banner displayed and * hided in a short time when the user hangs up the call @@ -102,39 +106,52 @@ class CommonTopAppBarViewModel @Inject constructor( * could be called when the screen is changed, so we delayed * showing the banner until getting the correct calling values */ - if (connectivityUIState is ConnectivityUIState.Info.EstablishedCall) { + if (connectivityUIState is ConnectivityUIState.EstablishedCall) { delay(WAITING_TIME_TO_SHOW_ONGOING_CALL_BANNER) } - connectivityState = connectivityState.copy(info = connectivityUIState) + state = state.copy(connectivityState = connectivityUIState) } } } } - private fun mapToUIState( + private fun mapToConnectivityUIState( currentScreen: CurrentScreen, connectivity: Connectivity, activeCall: Call? - ): ConnectivityUIState.Info { + ): ConnectivityUIState { val canDisplayActiveCall = currentScreen !is CurrentScreen.OngoingCallScreen val canDisplayConnectivityIssues = currentScreen !is CurrentScreen.AuthRelated if (activeCall != null && canDisplayActiveCall) { - return ConnectivityUIState.Info.EstablishedCall(activeCall.conversationId, activeCall.isMuted) + return ConnectivityUIState.EstablishedCall(activeCall.conversationId, activeCall.isMuted) } return if (canDisplayConnectivityIssues) { when (connectivity) { - Connectivity.WAITING_CONNECTION -> ConnectivityUIState.Info.WaitingConnection - Connectivity.CONNECTING -> ConnectivityUIState.Info.Connecting - Connectivity.CONNECTED -> ConnectivityUIState.Info.None + Connectivity.WAITING_CONNECTION -> ConnectivityUIState.WaitingConnection + Connectivity.CONNECTING -> ConnectivityUIState.Connecting + Connectivity.CONNECTED -> ConnectivityUIState.None } } else { - ConnectivityUIState.Info.None + ConnectivityUIState.None } } + private fun mapToLegalHoldUIState( + currentScreen: CurrentScreen, + legalHoldStatus: LegalHoldStatus + ): LegalHoldUIState = when (legalHoldStatus) { + LegalHoldStatus.ENABLED -> LegalHoldUIState.Active + LegalHoldStatus.PENDING -> LegalHoldUIState.Pending + LegalHoldStatus.DISABLED, + LegalHoldStatus.NO_CONSENT -> LegalHoldUIState.None + }.let { legalHoldUIState -> + if (currentScreen is CurrentScreen.AuthRelated) LegalHoldUIState.None + else legalHoldUIState + } + private companion object { const val WAITING_TIME_TO_SHOW_ONGOING_CALL_BANNER = 600L } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt index bcc2096eb6..03330d414a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt @@ -23,21 +23,16 @@ package com.wire.android.ui.common.topappbar import androidx.compose.runtime.Stable import com.wire.kalium.logic.data.id.ConversationId -data class ConnectivityUIState( - val info: Info -) { +@Stable +sealed interface ConnectivityUIState { + data object Connecting : ConnectivityUIState - @Stable - sealed interface Info { - object Connecting : Info + data object WaitingConnection : ConnectivityUIState - object WaitingConnection : Info + data object None : ConnectivityUIState - object None : Info - - data class EstablishedCall( - val conversationId: ConversationId, - val isMuted: Boolean - ) : Info - } + data class EstablishedCall( + val conversationId: ConversationId, + val isMuted: Boolean + ) : ConnectivityUIState } diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldStatusBar.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldStatusBar.kt new file mode 100644 index 0000000000..5755c02d89 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldStatusBar.kt @@ -0,0 +1,134 @@ +/* + * Wire + * Copyright (C) 2023 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.ui.legalhold.banner + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandIn +import androidx.compose.animation.shrinkOut +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.IntSize +import com.wire.android.R +import com.wire.android.ui.common.LegalHoldIndicator +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.theme.WireTheme +import com.wire.android.ui.theme.wireColorScheme +import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.ui.PreviewMultipleThemes + +@Composable +fun LegalHoldStatusBar( + legalHoldState: LegalHoldUIState, + onPendingClicked: () -> Unit, +) { + val isVisible = legalHoldState !is LegalHoldUIState.None + AnimatedVisibility( + visible = isVisible, + enter = expandIn(initialSize = { fullSize -> IntSize(fullSize.width, 0) }), + exit = shrinkOut(targetSize = { fullSize -> IntSize(fullSize.width, 0) }) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth() + ) { + when (legalHoldState) { + is LegalHoldUIState.Pending -> LegalHoldPendingContent(onPendingClicked) + is LegalHoldUIState.Active -> LegalHoldActiveContent() + is LegalHoldUIState.None -> {} + } + } + } +} + +@Composable +private fun LegalHoldPendingContent(onPendingClicked: () -> Unit) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensions().spacing8x, Alignment.CenterHorizontally), + modifier = Modifier + .padding(horizontal = dimensions().spacing8x, vertical = dimensions().spacing4x) + .clickable(onClick = onPendingClicked) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_warning_circle), + contentDescription = null, + tint = MaterialTheme.wireColorScheme.error, + modifier = Modifier.size(dimensions().spacing12x) + ) + Text( + text = stringResource(id = R.string.legal_hold_is_pending_label).uppercase(), + color = MaterialTheme.wireColorScheme.error, + style = MaterialTheme.wireTypography.title03, + ) + } +} + +@Composable +private fun LegalHoldActiveContent() { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensions().spacing8x, Alignment.CenterHorizontally), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimensions().spacing8x, vertical = dimensions().spacing4x) + ) { + LegalHoldIndicator() + Text( + text = stringResource(id = R.string.legal_hold_is_active_label).uppercase(), + color = MaterialTheme.wireColorScheme.secondaryText, + style = MaterialTheme.wireTypography.title03, + ) + } +} + +@Composable +@PreviewMultipleThemes +fun PreviewLegalHoldStatusBarActive() { + WireTheme { + LegalHoldStatusBar(legalHoldState = LegalHoldUIState.Active) {} + } +} + +@Composable +@PreviewMultipleThemes +fun PreviewLegalHoldStatusBarPending() { + WireTheme { + LegalHoldStatusBar(legalHoldState = LegalHoldUIState.Pending) {} + } +} + +@Composable +@PreviewMultipleThemes +fun PreviewLegalHoldStatusBarNone() { + WireTheme { + LegalHoldStatusBar(legalHoldState = LegalHoldUIState.None) {} + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt new file mode 100644 index 0000000000..e542054396 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt @@ -0,0 +1,89 @@ +/* + * Wire + * Copyright (C) 2023 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.ui.legalhold.banner + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import com.wire.android.R +import com.wire.android.ui.common.LegalHoldIndicator +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.typography +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes +import com.wire.android.util.ui.stringWithStyledArgs + +@Composable +fun LegalHoldSubjectBanner( + onClick: () -> Unit = {}, + modifier: Modifier = Modifier, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensions().spacing8x), + modifier = modifier + .clip(RoundedCornerShape(dimensions().spacing12x)) + .background(colorsScheme().surface) + .border( + width = dimensions().spacing1x, + shape = RoundedCornerShape(dimensions().spacing12x), + color = colorsScheme().error + ) + .clickable(onClick = onClick) + .heightIn(min = 26.dp) + .padding( + horizontal = dimensions().spacing12x, + vertical = dimensions().spacing4x + ) + ) { + LegalHoldIndicator() + val resources = LocalContext.current.resources + Text( + text = resources.stringWithStyledArgs( + stringResId = R.string.legal_hold_subject_to, + normalStyle = typography().label01, + argsStyle = typography().label02.copy(textDecoration = TextDecoration.Underline), + normalColor = colorsScheme().onSurface, + argsColor = colorsScheme().onSurface, + resources.getString(R.string.legal_hold_label) + ), + ) + } +} + +@Composable +@PreviewMultipleThemes +fun PreviewLegalHoldSubjectBanner() { + WireTheme { + LegalHoldSubjectBanner() + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldUIState.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldUIState.kt new file mode 100644 index 0000000000..edc4c3b5ba --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldUIState.kt @@ -0,0 +1,27 @@ +/* + * Wire + * Copyright (C) 2023 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.ui.legalhold.banner + +import androidx.compose.runtime.Stable + +@Stable +sealed interface LegalHoldUIState { + data object None : LegalHoldUIState + data object Pending : LegalHoldUIState + data object Active : LegalHoldUIState +} diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedDialog.kt index b2b1257734..2a95689df7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedDialog.kt @@ -38,7 +38,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.window.DialogProperties -import androidx.hilt.navigation.compose.hiltViewModel import com.wire.android.R import com.wire.android.ui.common.WireDialog import com.wire.android.ui.common.WireDialogButtonProperties @@ -53,22 +52,10 @@ import com.wire.android.ui.theme.wireTypography import com.wire.android.util.extension.formatAsFingerPrint import com.wire.android.util.ui.PreviewMultipleThemes -@Composable -fun LegalHoldRequestedDialog( - viewModel: LegalHoldRequestedViewModel = hiltViewModel() -) { - LegalHoldRequestedDialogContent( - state = viewModel.state, - passwordChanged = viewModel::passwordChanged, - notNowClicked = viewModel::notNowClicked, - acceptClicked = viewModel::acceptClicked, - ) -} - @OptIn(ExperimentalComposeUiApi::class) @Composable -fun LegalHoldRequestedDialogContent( - state: LegalHoldRequestedState, +fun LegalHoldRequestedDialog( + state: LegalHoldRequestedState.Visible, passwordChanged: (TextFieldValue) -> Unit, notNowClicked: () -> Unit, acceptClicked: () -> Unit, @@ -153,8 +140,8 @@ fun LegalHoldRequestedDialogContent( @PreviewMultipleThemes fun PreviewLegalHoldRequestedDialogWithPassword() { WireTheme { - LegalHoldRequestedDialogContent( - LegalHoldRequestedState(legalHoldDeviceFingerprint = "0123456789ABCDEF", requiresPassword = true), {}, {}, {} + LegalHoldRequestedDialog( + LegalHoldRequestedState.Visible(legalHoldDeviceFingerprint = "0123456789ABCDEF", requiresPassword = true), {}, {}, {} ) } } @@ -163,8 +150,8 @@ fun PreviewLegalHoldRequestedDialogWithPassword() { @PreviewMultipleThemes fun PreviewLegalHoldRequestedDialogWithoutPassword() { WireTheme { - LegalHoldRequestedDialogContent( - LegalHoldRequestedState(legalHoldDeviceFingerprint = "0123456789ABCDEF", requiresPassword = false), {}, {}, {} + LegalHoldRequestedDialog( + LegalHoldRequestedState.Visible(legalHoldDeviceFingerprint = "0123456789ABCDEF", requiresPassword = false), {}, {}, {} ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedState.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedState.kt index abad9ff1fb..cb2c720077 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedState.kt @@ -20,15 +20,17 @@ package com.wire.android.ui.legalhold.dialog.requested import androidx.compose.ui.text.input.TextFieldValue import com.wire.kalium.logic.CoreFailure -data class LegalHoldRequestedState( - val done: Boolean = false, - val legalHoldDeviceFingerprint: String = "", - val password: TextFieldValue = TextFieldValue(""), - val requiresPassword: Boolean = false, - val loading: Boolean = false, - val acceptEnabled: Boolean = false, - val error: LegalHoldRequestedError = LegalHoldRequestedError.None, -) +sealed class LegalHoldRequestedState { + data object Hidden : LegalHoldRequestedState() + data class Visible( + val legalHoldDeviceFingerprint: String = "", + val password: TextFieldValue = TextFieldValue(""), + val requiresPassword: Boolean = false, + val loading: Boolean = false, + val acceptEnabled: Boolean = false, + val error: LegalHoldRequestedError = LegalHoldRequestedError.None, + ) : LegalHoldRequestedState() +} sealed class LegalHoldRequestedError { data object None : LegalHoldRequestedError() diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt index fba431e396..5ab82d5c5e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt @@ -23,7 +23,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.wire.android.appLogger +import com.wire.android.di.KaliumCoreLogic +import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase +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.launch @@ -31,46 +35,64 @@ import javax.inject.Inject @HiltViewModel class LegalHoldRequestedViewModel @Inject constructor( - private val isPasswordRequired: IsPasswordRequiredUseCase, private val validatePassword: ValidatePasswordUseCase, + @KaliumCoreLogic private val coreLogic: CoreLogic, ) : ViewModel() { - var state: LegalHoldRequestedState by mutableStateOf(LegalHoldRequestedState()) + var state: LegalHoldRequestedState by mutableStateOf(LegalHoldRequestedState.Hidden) private set + // TODO: get legal hold status of current account - init { - state = state.copy(legalHoldDeviceFingerprint = "0123456789ABCDEF") // TODO get fingerprint - viewModelScope.launch { - isPasswordRequired().let { - state = state.copy(requiresPassword = (it as? IsPasswordRequiredUseCase.Result.Success)?.value ?: true) - } - } + private fun LegalHoldRequestedState.ifVisible(action: (LegalHoldRequestedState.Visible) -> Unit) { + if (this is LegalHoldRequestedState.Visible) action(this) } fun passwordChanged(password: TextFieldValue) { - state = state.copy(password = password) - validatePassword(password.text).let { - state = state.copy(acceptEnabled = it.isValid) + state.ifVisible { + state = it.copy(password = password, acceptEnabled = validatePassword(password.text).isValid) } } fun notNowClicked() { - // TODO + state = LegalHoldRequestedState.Hidden } - fun acceptClicked() { - state = state.copy(acceptEnabled = false, loading = true) - // the accept button is enabled if the password is valid, this check is for safety only - validatePassword(state.password.text).let { - if (!it.isValid) { - state = state.copy(loading = false, error = LegalHoldRequestedError.InvalidCredentialsError) + 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 + ) } - if (it.isValid) { - viewModelScope.launch { - // TODO - state = state.copy(loading = false, done = true) + } + } + + fun acceptClicked() { + state.ifVisible { + 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 } } } } + + companion object { + private const val TAG = "LegalHoldRequestedViewModel" + } } diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt b/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt index 6811c2e7f7..d60f552071 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt +++ b/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt @@ -91,7 +91,6 @@ data class WireColorScheme( val onCallingHangupButtonColor: Color, val callingAnswerButtonColor: Color, val onCallingAnswerButtonColor: Color, - val connectivityBarOngoingCallBackgroundColor: Color, val connectivityBarIssueBackgroundColor: Color, val messageComposerBackgroundColor: Color, val messageComposerEditBackgroundColor: Color, @@ -226,7 +225,6 @@ private val LightWireColorScheme = WireColorScheme( onCallingHangupButtonColor = Color.White, callingAnswerButtonColor = WireColorPalette.LightGreen500, onCallingAnswerButtonColor = Color.White, - connectivityBarOngoingCallBackgroundColor = WireColorPalette.LightGreen500, connectivityBarIssueBackgroundColor = WireColorPalette.LightBlue500, messageComposerBackgroundColor = Color.White, messageComposerEditBackgroundColor = WireColorPalette.LightBlue50, @@ -249,7 +247,7 @@ private val DarkWireColorScheme = WireColorScheme( primary = WireColorPalette.DarkBlue500, onPrimary = Color.Black, primaryVariant = WireColorPalette.DarkBlue800, onPrimaryVariant = WireColorPalette.DarkBlue300, error = WireColorPalette.DarkRed500, onError = Color.Black, - errorOutline = WireColorPalette.DarkRed200, + errorOutline = WireColorPalette.DarkRed800, warning = WireColorPalette.DarkYellow500, onWarning = Color.Black, positive = WireColorPalette.DarkGreen500, onPositive = Color.Black, background = WireColorPalette.Gray100, onBackground = Color.White, @@ -335,7 +333,6 @@ private val DarkWireColorScheme = WireColorScheme( onCallingHangupButtonColor = Color.Black, callingAnswerButtonColor = WireColorPalette.DarkGreen500, onCallingAnswerButtonColor = Color.Black, - connectivityBarOngoingCallBackgroundColor = WireColorPalette.DarkGreen700, connectivityBarIssueBackgroundColor = WireColorPalette.LightBlue500, messageComposerBackgroundColor = WireColorPalette.Gray100, messageComposerEditBackgroundColor = WireColorPalette.DarkBlue800, diff --git a/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt b/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt index c93a635165..97fd53f706 100644 --- a/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt +++ b/app/src/main/kotlin/com/wire/android/util/ui/StyledStringUtil.kt @@ -119,7 +119,8 @@ private fun toSpanStyle(textStyle: TextStyle, color: Color) = SpanStyle( fontWeight = textStyle.fontWeight, fontSize = textStyle.fontSize, fontFamily = textStyle.fontFamily, - fontStyle = textStyle.fontStyle + fontStyle = textStyle.fontStyle, + textDecoration = textStyle.textDecoration, ) private fun String.bold() = STYLE_SEPARATOR + this + STYLE_SEPARATOR diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c410d0c78..f168de8e42 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1300,6 +1300,10 @@ All messages, pictures, and documents will be preserved for future access. It includes deleted, edited, and self-deleting messages. At least one person in this conversation is subject to legal hold. Send Anyway + Subject to %1$s. + legal hold + Legal hold is active + Legal hold is pending Conversation no longer verified diff --git a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt index efbbf2c8d6..9aa80a1e20 100644 --- a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt @@ -64,10 +64,10 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.Connecting::class + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.Connecting::class } @Test @@ -80,10 +80,10 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.Connecting::class + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.Connecting::class } @Test @@ -96,11 +96,11 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.EstablishedCall::class - info as ConnectivityUIState.Info.EstablishedCall + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class + info as ConnectivityUIState.EstablishedCall info.conversationId shouldBeEqualTo arrangement.activeCall.conversationId } @@ -114,10 +114,10 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.WaitingConnection::class + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.WaitingConnection::class } @Test @@ -131,11 +131,11 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.EstablishedCall::class - info as ConnectivityUIState.Info.EstablishedCall + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class + info as ConnectivityUIState.EstablishedCall info.isMuted shouldBe true } @@ -150,11 +150,11 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.EstablishedCall::class - info as ConnectivityUIState.Info.EstablishedCall + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.EstablishedCall::class + info as ConnectivityUIState.EstablishedCall info.isMuted shouldBe false } @@ -167,10 +167,10 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.None::class + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.None::class } @Test @@ -180,10 +180,10 @@ class CommonTopAppBarViewModelTest { .arrange() advanceUntilIdle() - val state = commonTopAppBarViewModel.connectivityState + val state = commonTopAppBarViewModel.state - val info = state.info - info shouldBeInstanceOf ConnectivityUIState.Info.None::class + val info = state.connectivityState + info shouldBeInstanceOf ConnectivityUIState.None::class } private class Arrangement {