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 conversation Proteus verification status #2350

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,14 @@ fun MessagePreview.uiLastMessageContent(): UILastMessageContent {
}

MessagePreviewContent.CryptoSessionReset -> UILastMessageContent.None
MessagePreviewContent.VerificationChanged.VerifiedMls ->
UILastMessageContent.VerificationChanged(R.string.last_message_verified_conversation_mls)
MessagePreviewContent.VerificationChanged.VerifiedProteus ->
UILastMessageContent.VerificationChanged(R.string.last_message_verified_conversation_proteus)
MessagePreviewContent.VerificationChanged.DegradedMls ->
UILastMessageContent.VerificationChanged(R.string.last_message_conversations_verification_degraded_mls)
MessagePreviewContent.VerificationChanged.DegradedProteus ->
UILastMessageContent.VerificationChanged(R.string.last_message_conversations_verification_degraded_proteus)
Unknown -> UILastMessageContent.None
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@
userMessageTimer = null,
archived = false,
archivedDateTime = null,
verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED
mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED

Check warning on line 110 in app/src/main/kotlin/com/wire/android/migration/MigrationMapper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/migration/MigrationMapper.kt#L109-L110

Added lines #L109 - L110 were not covered by tests
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private fun DeviceItemTexts(
)
if (shouldShowVerifyLabel) {
Spacer(modifier = Modifier.width(MaterialTheme.wireDimensions.spacing8x))
if (device.isVerifiedProteus) ProteusVerifiedIcon(Modifier.wrapContentWidth())
if (device.isVerifiedProteus) ProteusVerifiedIcon(Modifier.wrapContentWidth().align(Alignment.CenterVertically))
}
}

Expand Down
20 changes: 18 additions & 2 deletions app/src/main/kotlin/com/wire/android/ui/common/VerifiedIcons.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.wire.android.ui.common

import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
Expand All @@ -26,10 +27,25 @@ import androidx.compose.ui.res.stringResource
import com.wire.android.R

@Composable
fun ProteusVerifiedIcon(modifier: Modifier = Modifier) {
fun ProteusVerifiedIcon(
modifier: Modifier = Modifier,
@StringRes contentDescriptionId: Int = R.string.label_client_verified
) {
Image(
modifier = modifier.padding(start = dimensions().spacing4x),
painter = painterResource(id = R.drawable.ic_certificate_valid_proteus),
contentDescription = stringResource(R.string.label_client_verified)
contentDescription = stringResource(contentDescriptionId)
)
}

@Composable
fun MLSVerifiedIcon(
modifier: Modifier = Modifier,
@StringRes contentDescriptionId: Int = R.string.label_client_verified
) {
Image(
modifier = modifier.padding(start = dimensions().spacing4x),
painter = painterResource(id = R.drawable.ic_certificate_valid_mls),
contentDescription = stringResource(contentDescriptionId)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ data class ConversationSheetContent(
val conversationTypeDetail: ConversationTypeDetail,
val selfRole: Conversation.Member.Role?,
val isTeamConversation: Boolean,
val isArchived: Boolean
val isArchived: Boolean,
val protocol: Conversation.ProtocolInfo,
val mlsVerificationStatus: Conversation.VerificationStatus,
val proteusVerificationStatus: Conversation.VerificationStatus
) {

private val isSelfUserMember: Boolean get() = selfRole != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ fun rememberConversationSheetState(
),
isTeamConversation = teamId != null,
selfRole = selfMemberRole,
isArchived = conversationItem.isArchived
isArchived = conversationItem.isArchived,
protocol = Conversation.ProtocolInfo.Proteus,
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED
)
}
}

is ConversationItem.PrivateConversation -> {
with(conversationItem) {
ConversationSheetContent(
Expand All @@ -95,10 +99,14 @@ fun rememberConversationSheetState(
),
isTeamConversation = isTeamConversation,
selfRole = Conversation.Member.Role.Member,
isArchived = conversationItem.isArchived
isArchived = conversationItem.isArchived,
protocol = Conversation.ProtocolInfo.Proteus,
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED
)
}
}

is ConversationItem.ConnectionConversation -> {
with(conversationItem) {
ConversationSheetContent(
Expand All @@ -110,7 +118,10 @@ fun rememberConversationSheetState(
),
isTeamConversation = isTeamConversation,
selfRole = Conversation.Member.Role.Member,
isArchived = conversationItem.isArchived
isArchived = conversationItem.isArchived,
protocol = Conversation.ProtocolInfo.Proteus,
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@

package com.wire.android.ui.home.conversations

import androidx.compose.foundation.Image
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.RowScope
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
Expand All @@ -48,6 +48,8 @@ import com.wire.android.R
import com.wire.android.model.UserAvatarData
import com.wire.android.ui.calling.controlbuttons.JoinButton
import com.wire.android.ui.calling.controlbuttons.StartCallButton
import com.wire.android.ui.common.MLSVerifiedIcon
import com.wire.android.ui.common.ProteusVerifiedIcon
import com.wire.android.ui.common.UserProfileAvatar
import com.wire.android.ui.common.button.WireSecondaryIconButton
import com.wire.android.ui.common.colorsScheme
Expand Down Expand Up @@ -133,7 +135,11 @@ private fun ConversationScreenTopAppBarContent(
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(weight = 1f, fill = false)
)
VerificationIcon(conversationInfoViewState.protocolInfo, conversationInfoViewState.verificationStatus)
VerificationIcons(
conversationInfoViewState.protocolInfo,
conversationInfoViewState.mlsVerificationStatus,
conversationInfoViewState.proteusVerificationStatus
)
if (isDropDownEnabled && isInteractionEnabled) {
Icon(
painter = painterResource(id = R.drawable.ic_dropdown_icon),
Expand Down Expand Up @@ -181,21 +187,29 @@ private fun ConversationScreenTopAppBarContent(
}

@Composable
private fun VerificationIcon(protocolInfo: Conversation.ProtocolInfo?, verificationStatus: Conversation.VerificationStatus?) {
if (verificationStatus != Conversation.VerificationStatus.VERIFIED || protocolInfo == null) return

val (iconId, contentDescriptionId) = when (protocolInfo) {
is Conversation.ProtocolInfo.MLS ->
R.drawable.ic_certificate_valid_mls to R.string.content_description_mls_certificate_valid
private fun RowScope.VerificationIcons(
protocolInfo: Conversation.ProtocolInfo?,
mlsVerificationStatus: Conversation.VerificationStatus?,
proteusVerificationStatus: Conversation.VerificationStatus?
) {
val mlsIcon: @Composable () -> Unit = {
if (mlsVerificationStatus == Conversation.VerificationStatus.VERIFIED) {
MLSVerifiedIcon(contentDescriptionId = R.string.content_description_mls_certificate_valid)
}
}
val proteusIcon: @Composable () -> Unit = {
if (proteusVerificationStatus == Conversation.VerificationStatus.VERIFIED) {
ProteusVerifiedIcon(contentDescriptionId = R.string.content_description_proteus_certificate_valid)
}
}

is Conversation.ProtocolInfo.Proteus, is Conversation.ProtocolInfo.Mixed ->
R.drawable.ic_certificate_valid_proteus to R.string.content_description_proteus_certificate_valid
if (protocolInfo is Conversation.ProtocolInfo.Proteus) {
proteusIcon()
mlsIcon()
} else {
mlsIcon()
proteusIcon()
}
Image(
modifier = Modifier.padding(start = dimensions().spacing4x),
painter = painterResource(id = iconId),
contentDescription = stringResource(contentDescriptionId)
)
}

@Composable
Expand Down Expand Up @@ -371,3 +385,30 @@ fun PreviewConversationScreenTopAppBarShortTitleWithOngoingCall() {
isSearchEnabled = false
)
}

@Preview("Topbar with a short conversation title and verified")
@Composable
fun PreviewConversationScreenTopAppBarShortTitleWithVerified() {
val conversationId = QualifiedID("", "")
ConversationScreenTopAppBarContent(
ConversationInfoViewState(
conversationId = ConversationId("value", "domain"),
conversationName = UIText.DynamicString("Short title"),
conversationDetailsData = ConversationDetailsData.Group(conversationId),
conversationAvatar = ConversationAvatar.Group(conversationId),
protocolInfo = Conversation.ProtocolInfo.Proteus,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED
),
onBackButtonClick = {},
onDropDownClick = {},
isDropDownEnabled = true,
onSearchButtonClick = {},
onPhoneButtonClick = {},
hasOngoingCall = false,
onJoinCallButtonClick = {},
onPermanentPermissionDecline = {},
isInteractionEnabled = true,
isSearchEnabled = false
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
Expand All @@ -35,6 +38,7 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -47,10 +51,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.ramcosta.composedestinations.annotation.Destination
Expand All @@ -64,7 +70,9 @@ import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.style.PopUpNavigationAnimation
import com.wire.android.ui.common.CollapsingTopBarScaffold
import com.wire.android.ui.common.MLSVerifiedIcon
import com.wire.android.ui.common.MoreOptionIcon
import com.wire.android.ui.common.ProteusVerifiedIcon
import com.wire.android.ui.common.TabItem
import com.wire.android.ui.common.WireTabRow
import com.wire.android.ui.common.bottomsheet.WireModalSheetLayout
Expand All @@ -73,17 +81,20 @@ import com.wire.android.ui.common.bottomsheet.conversation.rememberConversationS
import com.wire.android.ui.common.bottomsheet.rememberWireModalSheetState
import com.wire.android.ui.common.calculateCurrentTab
import com.wire.android.ui.common.dialogs.ArchiveConversationDialog
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.topBarElevation
import com.wire.android.ui.common.topappbar.NavigationIconType
import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar
import com.wire.android.ui.common.topappbar.WireTopAppBarTitle
import com.wire.android.ui.common.visbility.rememberVisibilityState
import com.wire.android.ui.destinations.AddMembersSearchScreenDestination
import com.wire.android.ui.destinations.EditConversationNameScreenDestination
import com.wire.android.ui.destinations.EditGuestAccessScreenDestination
import com.wire.android.ui.destinations.EditSelfDeletingMessagesScreenDestination
import com.wire.android.ui.destinations.GroupConversationAllParticipantsScreenDestination
import com.wire.android.ui.destinations.OtherUserProfileScreenDestination
import com.wire.android.ui.destinations.SearchConversationMessagesScreenDestination
import com.wire.android.ui.destinations.SelfUserProfileScreenDestination
import com.wire.android.ui.destinations.ServiceDetailsScreenDestination
import com.wire.android.ui.home.conversations.details.dialog.ClearConversationContentDialog
Expand All @@ -97,11 +108,12 @@ import com.wire.android.ui.home.conversations.details.participants.GroupConversa
import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant
import com.wire.android.ui.home.conversationslist.model.DialogState
import com.wire.android.ui.home.conversationslist.model.GroupDialogState
import com.wire.android.ui.destinations.SearchConversationMessagesScreenDestination
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.UIText
import com.wire.kalium.logic.data.conversation.Conversation
import kotlinx.coroutines.launch

@RootNavGraph
Expand Down Expand Up @@ -303,7 +315,14 @@ private fun GroupConversationDetailsContent(
topBarHeader = {
WireCenterAlignedTopAppBar(
elevation = elevationState,
title = stringResource(R.string.conversation_details_title),
titleContent = {
WireTopAppBarTitle(
title = stringResource(R.string.conversation_details_title),
style = MaterialTheme.wireTypography.title01,
maxLines = 2
)
VerificationInfo(conversationSheetContent)
},
navigationIconType = NavigationIconType.Close,
onNavigationPressed = onBackPressed,
actions = { MoreOptionIcon(onButtonClicked = openBottomSheet) }
Expand Down Expand Up @@ -446,6 +465,59 @@ private fun GroupConversationDetailsContent(
)
}

@Composable
private fun VerificationInfo(conversationSheetContent: ConversationSheetContent?) {
if (conversationSheetContent == null) return

val isProteusVerified = conversationSheetContent.proteusVerificationStatus == Conversation.VerificationStatus.VERIFIED
val isMlsVerified = conversationSheetContent.mlsVerificationStatus == Conversation.VerificationStatus.VERIFIED
val isProteusProtocol = conversationSheetContent.protocol == Conversation.ProtocolInfo.Proteus

if (isProteusVerified && (isProteusProtocol || !isMlsVerified)) {
ProteusVerifiedLabel()
} else if (isMlsVerified) {
MLSVerifiedLabel()
}
}

@Composable
private fun MLSVerifiedLabel() {
VerifiedLabel(
stringResource(id = R.string.label_conversations_details_verified_mls).uppercase(),
MaterialTheme.wireColorScheme.mlsVerificationTextColor
) { MLSVerifiedIcon() }
}

@Composable
private fun ProteusVerifiedLabel() {
VerifiedLabel(
stringResource(id = R.string.label_conversations_details_verified_proteus).uppercase(),
MaterialTheme.wireColorScheme.primary
) { ProteusVerifiedIcon() }
}

@Composable
private fun VerifiedLabel(text: String, color: Color, icon: @Composable RowScope.() -> Unit = {}) {
Row(
modifier = Modifier
.padding(top = dimensions().spacing4x)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.padding(
start = dimensions().spacing6x,
end = dimensions().spacing6x
),
text = text,
style = MaterialTheme.wireTypography.label01,
color = color,
overflow = TextOverflow.Ellipsis
)
icon()
}
}

enum class GroupConversationDetailsTabItem(@StringRes override val titleResId: Int) : TabItem {
OPTIONS(R.string.conversation_details_options_tab),
PARTICIPANTS(R.string.conversation_details_participants_tab);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ class GroupConversationDetailsViewModel @Inject constructor(
conversationTypeDetail = ConversationTypeDetail.Group(conversationId, groupDetails.isSelfUserCreator),
isTeamConversation = groupDetails.conversation.teamId?.value != null,
selfRole = groupDetails.selfRole,
isArchived = groupDetails.conversation.archived
isArchived = groupDetails.conversation.archived,
protocol = groupDetails.conversation.protocol,
mlsVerificationStatus = groupDetails.conversation.mlsVerificationStatus,
proteusVerificationStatus = groupDetails.conversation.proteusVerificationStatus
)
val isGuestAllowed = groupDetails.conversation.isGuestAllowed() || groupDetails.conversation.isNonTeamMemberAllowed()
val isUpdatingReadReceiptAllowed = if (selfTeam == null) {
Expand Down