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: legal hold - update banners to v2 [WPB-6464] #2668

Merged
merged 15 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 14 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
1 change: 0 additions & 1 deletion app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ class WireActivity : AppCompatActivity() {
onReturnToCallClick = { establishedCall ->
navigator.navigate(NavigationCommand(OngoingCallScreenDestination(establishedCall.conversationId)))
},
onPendingClicked = legalHoldRequestedViewModel::show,
)
NavigationGraph(
navigator = navigator,
Expand Down
77 changes: 64 additions & 13 deletions app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
package com.wire.android.ui.common

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
Expand All @@ -40,28 +38,31 @@ import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import com.wire.android.R
import com.wire.android.model.Clickable
import com.wire.android.model.UserAvatarData
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.UserAvailabilityStatus
import kotlin.math.sqrt

@Composable
fun UserProfileAvatar(
avatarData: UserAvatarData = UserAvatarData(),
size: Dp = MaterialTheme.wireDimensions.avatarDefaultSize,
padding: Dp = MaterialTheme.wireDimensions.avatarClickablePadding,
statusBorderSize: PaddingValues = PaddingValues(all = dimensions().avatarStatusBorderSize),
modifier: Modifier = Modifier,
clickable: Clickable? = null,
showPlaceholderIfNoAsset: Boolean = true,
withCrossfadeAnimation: Boolean = false,
showStatusIndicator: Boolean = true
showStatusIndicator: Boolean = true,
withLegalHoldIndicator: Boolean = false,
) {
Box(
contentAlignment = Alignment.Center,
Expand All @@ -79,18 +80,50 @@ fun UserProfileAvatar(
painter = painter,
contentDescription = stringResource(R.string.content_description_user_avatar),
modifier = Modifier
.padding(statusBorderSize)
.background(MaterialTheme.wireColorScheme.divider, CircleShape)
.size(size)
.border(width = dimensions().spacing1x, shape = CircleShape, color = MaterialTheme.wireColorScheme.outline)
// we need to take borders into account
.size(size + (max(dimensions().avatarStatusBorderSize, dimensions().avatarLegalHoldIndicatorBorderSize) * 2))
.let {
if (withLegalHoldIndicator) {
it
.border(
width = dimensions().avatarLegalHoldIndicatorBorderSize / 2,
shape = CircleShape,
color = colorsScheme().error.copy(alpha = 0.3f)
)
.padding(dimensions().avatarLegalHoldIndicatorBorderSize / 2)
.border(
width = dimensions().avatarLegalHoldIndicatorBorderSize / 2,
shape = CircleShape,
color = colorsScheme().error.copy(alpha = 1.0f)
)
.padding(dimensions().avatarLegalHoldIndicatorBorderSize / 2)
} else {
it
// this is to make the border of the avatar to be the same size as with the legal hold indicator
.padding(dimensions().avatarLegalHoldIndicatorBorderSize - dimensions().spacing1x)
.border(
width = dimensions().spacing1x,
shape = CircleShape,
color = colorsScheme().outline
)
.padding(dimensions().spacing1x)
}
}
.clip(CircleShape)
.testTag("User avatar"),
contentScale = ContentScale.Crop
)
if (showStatusIndicator) {
val avatarWithLegalHoldRadius = (size.value / 2f) + dimensions().avatarLegalHoldIndicatorBorderSize.value
val statusRadius = (dimensions().userAvatarStatusSize - dimensions().avatarStatusBorderSize).value / 2f
// calculated using the trigonometry so that the status is always in the right place according to the avatar
val paddingToAlignWithAvatar = ((sqrt(2f) - 1f) * avatarWithLegalHoldRadius + (1f - sqrt(2f)) * statusRadius) / sqrt(2f)
UserStatusIndicator(
status = avatarData.availabilityStatus,
modifier = Modifier.align(Alignment.BottomEnd)
modifier = Modifier
// on designs the status border extends beyond the avatar's perimeter so we need to subtract it's size from the padding
.padding(paddingToAlignWithAvatar.dp - dimensions().avatarStatusBorderSize)
.align(Alignment.BottomEnd)
)
}
}
Expand Down Expand Up @@ -136,8 +169,26 @@ private fun getDefaultAvatarResourceId(membership: Membership): Int =
R.drawable.ic_default_user_avatar
}

@Preview
@PreviewMultipleThemes
@Composable
fun PreviewUserProfileAvatar() {
UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE))
WireTheme {
UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE))
}
}

@PreviewMultipleThemes
@Composable
fun PreviewUserProfileAvatarWithLegalHold() {
WireTheme {
UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), withLegalHoldIndicator = true)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewLargeUserProfileAvatarWithLegalHold() {
WireTheme {
UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), 48.dp, withLegalHoldIndicator = true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ import androidx.compose.ui.text.style.TextAlign
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
Expand All @@ -58,17 +56,12 @@ import com.wire.kalium.logic.data.id.ConversationId
fun CommonTopAppBar(
commonTopAppBarState: CommonTopAppBarState,
onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit,
onPendingClicked: () -> Unit,
) {
Column {
ConnectivityStatusBar(
connectivityInfo = commonTopAppBarState.connectivityState,
onReturnToCallClick = onReturnToCallClick
)
LegalHoldStatusBar(
legalHoldState = commonTopAppBarState.legalHoldState,
onPendingClicked = onPendingClicked
)
}
}

Expand Down Expand Up @@ -197,52 +190,23 @@ private fun clearStatusBarColor() {
}

@Composable
private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState, legalHoldUIState: LegalHoldUIState) {
private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState) {
WireTheme {
CommonTopAppBar(CommonTopAppBarState(connectivityUIState, legalHoldUIState), {}, {})
CommonTopAppBar(CommonTopAppBarState(connectivityUIState), {})
}
}

@PreviewMultipleThemes
@Composable
fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldNone() =
PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.None)

@PreviewMultipleThemes
@Composable
fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldPending() =
PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.Pending)

@PreviewMultipleThemes
@Composable
fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldActive() =
PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.Active)

@PreviewMultipleThemes
@Composable
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)
fun PreviewCommonTopAppBar_ConnectivityCallNotMuted() =
PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false))

@PreviewMultipleThemes
@Composable
fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldPending() =
PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.Pending)
fun PreviewCommonTopAppBar_ConnectivityConnecting() =
PreviewCommonTopAppBar(ConnectivityUIState.Connecting)

@PreviewMultipleThemes
@Composable
fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldActive() =
PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.Active)
fun PreviewCommonTopAppBar_ConnectivityNone() =
PreviewCommonTopAppBar(ConnectivityUIState.None)
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
*/
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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,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.user.LegalHoldStatus
import com.wire.kalium.logic.data.sync.SyncState
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -76,39 +73,26 @@ class CommonTopAppBarViewModel @Inject constructor(
}
}

private fun legalHoldStatusFlow(userId: UserId) = coreLogic.sessionScope(userId) {
observeLegalHoldRequest() // TODO combine with legal hold status
.map { legalHoldRequestResult ->
when (legalHoldRequestResult) {
is ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable -> LegalHoldStatus.PENDING
else -> LegalHoldStatus.DISABLED
}
}
}

init {
viewModelScope.launch {
coreLogic.globalScope {
session.currentSessionFlow().flatMapLatest {
when (it) {
is CurrentSessionResult.Failure.Generic,
is CurrentSessionResult.Failure.SessionNotFound -> flowOf(ConnectivityUIState.None to LegalHoldUIState.None)
is CurrentSessionResult.Failure.SessionNotFound -> flowOf(ConnectivityUIState.None)

is CurrentSessionResult.Success -> {
val userId = it.accountInfo.userId
combine(
activeCallFlow(userId),
currentScreenFlow(),
connectivityFlow(userId),
legalHoldStatusFlow(userId),
) { activeCall, currentScreen, connectivity, legalHoldStatus ->
mapToConnectivityUIState(currentScreen, connectivity, activeCall) to
mapToLegalHoldUIState(currentScreen, legalHoldStatus)
) { activeCall, currentScreen, connectivity ->
mapToConnectivityUIState(currentScreen, connectivity, activeCall)
}
}
}
}.collectLatest { (connectivityUIState, legalHoldUIState) ->
state = state.copy(legalHoldState = legalHoldUIState)
}.collectLatest { connectivityUIState ->
/**
* 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
Expand Down Expand Up @@ -149,19 +133,6 @@ class CommonTopAppBarViewModel @Inject constructor(
}
}

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 || currentScreen is CurrentScreen.CallScreen) LegalHoldUIState.None
else legalHoldUIState
}

private companion object {
const val WAITING_TIME_TO_SHOW_ONGOING_CALL_BANNER = 600L
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ fun HomeContent(
status = homeState.status,
title = stringResource(currentNavigationItem.title),
elevation = elevation,
withLegalHoldIndicator = homeState.shouldDisplayLegalHoldIndicator,
onHamburgerMenuClick = ::openDrawer,
onNavigateToSelfUserProfile = onSelfUserClick
)
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/kotlin/com/wire/android/ui/home/HomeState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import com.wire.kalium.logic.data.user.UserId
data class HomeState(
val avatarAsset: ImageAsset.UserAvatarAsset? = null,
val status: UserAvailabilityStatus = UserAvailabilityStatus.NONE,
val shouldDisplayWelcomeMessage: Boolean = false
val shouldDisplayWelcomeMessage: Boolean = false,
val shouldDisplayLegalHoldIndicator: Boolean = false,
)

sealed class HomeRequirement {
Expand Down
14 changes: 12 additions & 2 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeTopBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ fun HomeTopBar(
status: UserAvailabilityStatus,
title: String,
elevation: Dp,
withLegalHoldIndicator: Boolean,
onHamburgerMenuClick: () -> Unit,
onNavigateToSelfUserProfile: () -> Unit
) {
Expand All @@ -48,7 +49,8 @@ fun HomeTopBar(
actions = {
UserProfileAvatar(
avatarData = UserAvatarData(avatarAsset, status),
clickable = remember { Clickable(enabled = true) { onNavigateToSelfUserProfile() } }
clickable = remember { Clickable(enabled = true) { onNavigateToSelfUserProfile() } },
withLegalHoldIndicator = withLegalHoldIndicator,
)
},
elevation = elevation,
Expand All @@ -59,6 +61,14 @@ fun HomeTopBar(
@Composable
fun PreviewTopBar() {
WireTheme {
HomeTopBar(null, UserAvailabilityStatus.AVAILABLE, "Title", 0.dp, {}, {})
HomeTopBar(null, UserAvailabilityStatus.AVAILABLE, "Title", 0.dp, false, {}, {})
}
}

@PreviewMultipleThemes
@Composable
fun PreviewTopBarWithLegalHold() {
WireTheme {
HomeTopBar(null, UserAvailabilityStatus.AVAILABLE, "Title", 0.dp, true, {}, {})
}
}
Loading
Loading