Skip to content

Commit

Permalink
feat: legal hold - update banners to v2 [WPB-6464] (#2668)
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk authored Feb 8, 2024
1 parent ecb7ff1 commit 8f8a6e1
Show file tree
Hide file tree
Showing 16 changed files with 259 additions and 192 deletions.
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

0 comments on commit 8f8a6e1

Please sign in to comment.