mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Add Encryption Authenticity explanations. (#3116)
This commit is contained in:
@@ -58,6 +58,7 @@ struct RoomInviterLabel: View {
|
|||||||
avatarSize: .custom(16),
|
avatarSize: .custom(16),
|
||||||
imageProvider: imageProvider)
|
imageProvider: imageProvider)
|
||||||
.alignmentGuide(.firstTextBaseline) { $0[.bottom] * 0.8 }
|
.alignmentGuide(.firstTextBaseline) { $0[.bottom] * 0.8 }
|
||||||
|
.accessibilityHidden(true)
|
||||||
|
|
||||||
Text(inviter.attributedInviteText)
|
Text(inviter.attributedInviteText)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,14 +57,18 @@ struct HomeScreenInviteCell: View {
|
|||||||
|
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 16) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
textualContent
|
HStack(alignment: .firstTextBaseline, spacing: 16) {
|
||||||
badge
|
textualContent
|
||||||
|
badge
|
||||||
|
}
|
||||||
|
|
||||||
|
inviterView
|
||||||
|
.padding(.top, 6)
|
||||||
|
.padding(.trailing, 16)
|
||||||
}
|
}
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
inviterView
|
.accessibilityElement(children: .combine)
|
||||||
.padding(.top, 6)
|
|
||||||
.padding(.trailing, 16)
|
|
||||||
|
|
||||||
buttons
|
buttons
|
||||||
.padding(.top, 14)
|
.padding(.top, 14)
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ enum RoomScreenViewAction {
|
|||||||
case itemDisappeared(itemID: TimelineItemIdentifier)
|
case itemDisappeared(itemID: TimelineItemIdentifier)
|
||||||
|
|
||||||
case itemTapped(itemID: TimelineItemIdentifier)
|
case itemTapped(itemID: TimelineItemIdentifier)
|
||||||
|
case itemSendInfoTapped(itemID: TimelineItemIdentifier)
|
||||||
case toggleReaction(key: String, itemID: TimelineItemIdentifier)
|
case toggleReaction(key: String, itemID: TimelineItemIdentifier)
|
||||||
case sendReadReceiptIfNeeded(TimelineItemIdentifier)
|
case sendReadReceiptIfNeeded(TimelineItemIdentifier)
|
||||||
case paginateBackwards
|
case paginateBackwards
|
||||||
@@ -241,6 +242,8 @@ struct ReadReceiptSummaryInfo: Identifiable {
|
|||||||
enum RoomScreenAlertInfoType: Hashable {
|
enum RoomScreenAlertInfoType: Hashable {
|
||||||
case audioRecodingPermissionError
|
case audioRecodingPermissionError
|
||||||
case pollEndConfirmation(String)
|
case pollEndConfirmation(String)
|
||||||
|
case sendingFailed
|
||||||
|
case encryptionAuthenticity(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomMemberState {
|
struct RoomMemberState {
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
case .itemTapped(let id):
|
case .itemTapped(let id):
|
||||||
Task { await handleItemTapped(with: id) }
|
Task { await handleItemTapped(with: id) }
|
||||||
|
case .itemSendInfoTapped(let itemID):
|
||||||
|
handleItemSendInfoTapped(itemID: itemID)
|
||||||
case .toggleReaction(let emoji, let itemId):
|
case .toggleReaction(let emoji, let itemId):
|
||||||
Task { await timelineController.toggleReaction(emoji, to: itemId) }
|
Task { await timelineController.toggleReaction(emoji, to: itemId) }
|
||||||
case .sendReadReceiptIfNeeded(let lastVisibleItemID):
|
case .sendReadReceiptIfNeeded(let lastVisibleItemID):
|
||||||
@@ -607,6 +609,23 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
state.showLoading = false
|
state.showLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleItemSendInfoTapped(itemID: TimelineItemIdentifier) {
|
||||||
|
guard let timelineItem = timelineController.timelineItems.firstUsingStableID(itemID) else {
|
||||||
|
MXLog.warning("Couldn't find timeline item.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let eventTimelineItem = timelineItem as? EventBasedTimelineItemProtocol else {
|
||||||
|
fatalError("Only events can have send info.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventTimelineItem.properties.deliveryStatus == .sendingFailed {
|
||||||
|
displayAlert(.sendingFailed)
|
||||||
|
} else if let authenticityMessage = eventTimelineItem.properties.encryptionAuthenticity?.message {
|
||||||
|
displayAlert(.encryptionAuthenticity(authenticityMessage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func sendCurrentMessage(_ message: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) async {
|
private func sendCurrentMessage(_ message: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) async {
|
||||||
guard !message.isEmpty else {
|
guard !message.isEmpty else {
|
||||||
@@ -851,6 +870,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
message: L10n.commonPollEndConfirmation,
|
message: L10n.commonPollEndConfirmation,
|
||||||
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil),
|
||||||
secondaryButton: .init(title: L10n.actionOk, action: { self.roomScreenInteractionHandler.endPoll(pollStartID: pollStartID) }))
|
secondaryButton: .init(title: L10n.actionOk, action: { self.roomScreenInteractionHandler.endPoll(pollStartID: pollStartID) }))
|
||||||
|
case .sendingFailed:
|
||||||
|
state.bindings.alertInfo = .init(id: type,
|
||||||
|
title: L10n.commonSendingFailed,
|
||||||
|
primaryButton: .init(title: L10n.actionOk, action: nil))
|
||||||
|
case .encryptionAuthenticity(let message):
|
||||||
|
state.bindings.alertInfo = .init(id: type,
|
||||||
|
title: message,
|
||||||
|
primaryButton: .init(title: L10n.actionOk, action: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct TimelineItemMenu: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
header
|
messagePreview
|
||||||
.frame(idealWidth: 300.0)
|
.frame(idealWidth: 300.0)
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@@ -63,34 +63,44 @@ struct TimelineItemMenu: View {
|
|||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var messagePreview: some View {
|
||||||
HStack(alignment: .top, spacing: 0.0) {
|
VStack(alignment: .leading, spacing: 20) {
|
||||||
LoadableAvatarImage(url: item.sender.avatarURL,
|
HStack(alignment: .top, spacing: 0.0) {
|
||||||
name: item.sender.displayName,
|
LoadableAvatarImage(url: item.sender.avatarURL,
|
||||||
contentID: item.sender.id,
|
name: item.sender.displayName,
|
||||||
avatarSize: .user(on: .timeline),
|
contentID: item.sender.id,
|
||||||
imageProvider: context.imageProvider)
|
avatarSize: .user(on: .timeline),
|
||||||
|
imageProvider: context.imageProvider)
|
||||||
Spacer(minLength: 8.0)
|
.accessibilityHidden(true)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
Text(item.sender.displayName ?? item.sender.id)
|
|
||||||
.font(.compound.bodySMSemibold)
|
|
||||||
.foregroundColor(.compound.textPrimary)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
|
|
||||||
Text(item.timelineMenuDescription)
|
Spacer(minLength: 8.0)
|
||||||
.font(.compound.bodyMD)
|
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text(item.sender.displayName ?? item.sender.id)
|
||||||
|
.font(.compound.bodySMSemibold)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
|
||||||
|
Text(item.timelineMenuDescription)
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.foregroundColor(.compound.textSecondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
Spacer(minLength: 16.0)
|
||||||
|
|
||||||
|
Text(item.timestamp)
|
||||||
|
.font(.compound.bodyXS)
|
||||||
.foregroundColor(.compound.textSecondary)
|
.foregroundColor(.compound.textSecondary)
|
||||||
.lineLimit(1)
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.accessibilityElement(children: .combine)
|
||||||
|
|
||||||
Spacer(minLength: 16.0)
|
if let authenticity = item.properties.encryptionAuthenticity {
|
||||||
|
Label(authenticity.message, icon: authenticity.icon, iconSize: .small, relativeTo: .compound.bodySMSemibold)
|
||||||
Text(item.timestamp)
|
.font(.compound.bodySMSemibold)
|
||||||
.font(.compound.bodyXS)
|
.foregroundStyle(authenticity.foregroundStyle)
|
||||||
.foregroundColor(.compound.textSecondary)
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 32.0)
|
.padding(.top, 32.0)
|
||||||
@@ -169,23 +179,54 @@ struct TimelineItemMenu: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
private extension EncryptionAuthenticity {
|
||||||
static let viewModel = RoomScreenViewModel.mock
|
var foregroundStyle: SwiftUI.Color {
|
||||||
|
switch color {
|
||||||
static var previews: some View {
|
case .red: .compound.textCriticalPrimary
|
||||||
testView
|
case .gray: .compound.textSecondary
|
||||||
.previewDisplayName("With button shapes off")
|
|
||||||
testView
|
|
||||||
.environment(\._accessibilityShowButtonShapes, true)
|
|
||||||
.previewDisplayName("With button shapes on")
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
static var testView: some View {
|
|
||||||
if let item = RoomTimelineItemFixtures.singleMessageChunk.first as? EventBasedTimelineItemProtocol,
|
|
||||||
let actions = TimelineItemMenuActions(isReactable: true, actions: [.copy, .edit, .reply(isThread: false), .pin, .redact], debugActions: [.viewSource]) {
|
|
||||||
TimelineItemMenu(item: item, actions: actions)
|
|
||||||
.environmentObject(viewModel.context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
||||||
|
static let viewModel = RoomScreenViewModel.mock
|
||||||
|
static let (item, actions) = makeItem()
|
||||||
|
static let (backupItem, _) = makeItem(authenticity: .notGuaranteed(color: .gray))
|
||||||
|
static let (unencryptedItem, _) = makeItem(authenticity: .sentInClear(color: .red))
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
TimelineItemMenu(item: item, actions: actions)
|
||||||
|
.environmentObject(viewModel.context)
|
||||||
|
.previewDisplayName("With button shapes off")
|
||||||
|
|
||||||
|
TimelineItemMenu(item: item, actions: actions)
|
||||||
|
.environmentObject(viewModel.context)
|
||||||
|
.environment(\._accessibilityShowButtonShapes, true)
|
||||||
|
.previewDisplayName("With button shapes on")
|
||||||
|
|
||||||
|
TimelineItemMenu(item: backupItem, actions: actions)
|
||||||
|
.environmentObject(viewModel.context)
|
||||||
|
.previewDisplayName("Authenticity not guaranteed")
|
||||||
|
|
||||||
|
TimelineItemMenu(item: unencryptedItem, actions: actions)
|
||||||
|
.environmentObject(viewModel.context)
|
||||||
|
.previewDisplayName("Unencrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeItem(authenticity: EncryptionAuthenticity? = nil) -> (TextRoomTimelineItem, TimelineItemMenuActions)! {
|
||||||
|
guard var item = RoomTimelineItemFixtures.singleMessageChunk.first as? TextRoomTimelineItem,
|
||||||
|
let actions = TimelineItemMenuActions(isReactable: true,
|
||||||
|
actions: [.copy, .edit, .reply(isThread: false), .pin, .redact],
|
||||||
|
debugActions: [.viewSource]) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let authenticity {
|
||||||
|
item.properties.encryptionAuthenticity = authenticity
|
||||||
|
}
|
||||||
|
|
||||||
|
return (item, actions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
|
|
||||||
var messageBubble: some View {
|
var messageBubble: some View {
|
||||||
contentWithReply
|
contentWithReply
|
||||||
.timelineItemSendInfo(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus)
|
.timelineItemSendInfo(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, context: context)
|
||||||
.bubbleStyle(insets: timelineItem.bubbleInsets,
|
.bubbleStyle(insets: timelineItem.bubbleInsets,
|
||||||
color: timelineItem.bubbleBackgroundColor,
|
color: timelineItem.bubbleBackgroundColor,
|
||||||
corners: roundedCorners)
|
corners: roundedCorners)
|
||||||
|
|||||||
@@ -20,15 +20,18 @@ import SwiftUI
|
|||||||
extension View {
|
extension View {
|
||||||
/// Adds the send info (timestamp along indicators for edits and delivery/encryption issues) for the given timeline item to this view.
|
/// Adds the send info (timestamp along indicators for edits and delivery/encryption issues) for the given timeline item to this view.
|
||||||
func timelineItemSendInfo(timelineItem: EventBasedTimelineItemProtocol,
|
func timelineItemSendInfo(timelineItem: EventBasedTimelineItemProtocol,
|
||||||
adjustedDeliveryStatus: TimelineItemDeliveryStatus?) -> some View {
|
adjustedDeliveryStatus: TimelineItemDeliveryStatus?,
|
||||||
|
context: RoomScreenViewModel.Context) -> some View {
|
||||||
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
|
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
|
||||||
adjustedDeliveryStatus: adjustedDeliveryStatus)))
|
adjustedDeliveryStatus: adjustedDeliveryStatus),
|
||||||
|
context: context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds the send info to a view with the correct layout.
|
/// Adds the send info to a view with the correct layout.
|
||||||
private struct TimelineItemSendInfoModifier: ViewModifier {
|
private struct TimelineItemSendInfoModifier: ViewModifier {
|
||||||
let sendInfo: TimelineItemSendInfo
|
let sendInfo: TimelineItemSendInfo
|
||||||
|
let context: RoomScreenViewModel.Context
|
||||||
|
|
||||||
var layout: AnyLayout {
|
var layout: AnyLayout {
|
||||||
switch sendInfo.layoutType {
|
switch sendInfo.layoutType {
|
||||||
@@ -44,7 +47,15 @@ private struct TimelineItemSendInfoModifier: ViewModifier {
|
|||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
layout {
|
layout {
|
||||||
content
|
content
|
||||||
|
|
||||||
TimelineItemSendInfoLabel(sendInfo: sendInfo)
|
TimelineItemSendInfoLabel(sendInfo: sendInfo)
|
||||||
|
.contentShape(.rect)
|
||||||
|
// Tap gesture to avoid the message being detected as a button by VoiceOver
|
||||||
|
// (and the action shows a description that is already read to the user).
|
||||||
|
.onTapGesture {
|
||||||
|
guard sendInfo.status != nil else { return }
|
||||||
|
context.send(viewAction: .itemSendInfoTapped(itemID: sendInfo.itemID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,26 +66,17 @@ private struct TimelineItemSendInfoLabel: View {
|
|||||||
|
|
||||||
var statusIcon: KeyPath<CompoundIcons, Image>? {
|
var statusIcon: KeyPath<CompoundIcons, Image>? {
|
||||||
switch sendInfo.status {
|
switch sendInfo.status {
|
||||||
case .sendingFailed:
|
case .sendingFailed: \.error
|
||||||
\.error
|
case .encryptionAuthenticity(let authenticity): authenticity.icon
|
||||||
case .encryptionAuthenticity(.notGuaranteed):
|
case .none: nil
|
||||||
\.infoSolid
|
|
||||||
case .encryptionAuthenticity(.unknownDevice),
|
|
||||||
.encryptionAuthenticity(.unsignedDevice),
|
|
||||||
.encryptionAuthenticity(.unverifiedIdentity),
|
|
||||||
.encryptionAuthenticity(.sentInClear):
|
|
||||||
\.lockOff
|
|
||||||
case .none:
|
|
||||||
nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusIconAccessibilityLabel: String? {
|
var statusIconAccessibilityLabel: String? {
|
||||||
switch sendInfo.status {
|
switch sendInfo.status {
|
||||||
case .sendingFailed: L10n.commonSendingFailed
|
case .sendingFailed: L10n.commonSendingFailed
|
||||||
case .none: nil
|
|
||||||
// Temporary testing strings.
|
|
||||||
case .encryptionAuthenticity(let authenticity): authenticity.message
|
case .encryptionAuthenticity(let authenticity): authenticity.message
|
||||||
|
case .none: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +106,10 @@ private struct TimelineItemSendInfoLabel: View {
|
|||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Text(sendInfo.localizedString)
|
Text(sendInfo.localizedString)
|
||||||
|
|
||||||
if let statusIcon, let statusIconAccessibilityLabel {
|
if let statusIcon {
|
||||||
CompoundIcon(statusIcon, size: .xSmall, relativeTo: .compound.bodyXS)
|
CompoundIcon(statusIcon, size: .xSmall, relativeTo: .compound.bodyXS)
|
||||||
.accessibilityLabel(statusIconAccessibilityLabel)
|
.accessibilityLabel(statusIconAccessibilityLabel ?? "")
|
||||||
|
.accessibilityHidden(statusIconAccessibilityLabel == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.compound.bodyXS)
|
.font(.compound.bodyXS)
|
||||||
@@ -125,6 +128,7 @@ private struct TimelineItemSendInfo {
|
|||||||
case overlay(capsuleStyle: Bool)
|
case overlay(capsuleStyle: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let itemID: TimelineItemIdentifier
|
||||||
let localizedString: String
|
let localizedString: String
|
||||||
var status: Status?
|
var status: Status?
|
||||||
let layoutType: LayoutType
|
let layoutType: LayoutType
|
||||||
@@ -143,6 +147,7 @@ private struct TimelineItemSendInfo {
|
|||||||
|
|
||||||
private extension TimelineItemSendInfo {
|
private extension TimelineItemSendInfo {
|
||||||
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) {
|
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) {
|
||||||
|
itemID = timelineItem.id
|
||||||
localizedString = timelineItem.localizedSendInfo
|
localizedString = timelineItem.localizedSendInfo
|
||||||
|
|
||||||
status = if adjustedDeliveryStatus == .sendingFailed {
|
status = if adjustedDeliveryStatus == .sendingFailed {
|
||||||
@@ -172,18 +177,9 @@ private extension TimelineItemSendInfo {
|
|||||||
|
|
||||||
private extension EncryptionAuthenticity {
|
private extension EncryptionAuthenticity {
|
||||||
var foregroundStyle: SwiftUI.Color {
|
var foregroundStyle: SwiftUI.Color {
|
||||||
switch self {
|
switch color {
|
||||||
case .notGuaranteed(let color),
|
case .red: .compound.textCriticalPrimary
|
||||||
.unknownDevice(let color),
|
case .gray: .compound.textSecondary
|
||||||
.unsignedDevice(let color),
|
|
||||||
.unverifiedIdentity(let color),
|
|
||||||
.sentInClear(let color):
|
|
||||||
switch color {
|
|
||||||
case .red:
|
|
||||||
.compound.textCriticalPrimary
|
|
||||||
case .gray:
|
|
||||||
.compound.textSecondary
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,20 +189,25 @@ private extension EncryptionAuthenticity {
|
|||||||
struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
struct TimelineItemSendInfoLabel_Previews: PreviewProvider, TestablePreview {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
TimelineItemSendInfoLabel(sendInfo: .init(itemID: .random,
|
||||||
|
localizedString: "09:47 AM",
|
||||||
layoutType: .horizontal()))
|
layoutType: .horizontal()))
|
||||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
TimelineItemSendInfoLabel(sendInfo: .init(itemID: .random,
|
||||||
|
localizedString: "09:47 AM",
|
||||||
status: .sendingFailed,
|
status: .sendingFailed,
|
||||||
layoutType: .horizontal()))
|
layoutType: .horizontal()))
|
||||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
TimelineItemSendInfoLabel(sendInfo: .init(itemID: .random,
|
||||||
|
localizedString: "09:47 AM",
|
||||||
status: .encryptionAuthenticity(.unsignedDevice(color: .red)),
|
status: .encryptionAuthenticity(.unsignedDevice(color: .red)),
|
||||||
layoutType: .horizontal()))
|
layoutType: .horizontal()))
|
||||||
TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
TimelineItemSendInfoLabel(sendInfo: .init(itemID: .random,
|
||||||
|
localizedString: "09:47 AM",
|
||||||
status: .encryptionAuthenticity(.notGuaranteed(color: .gray)),
|
status: .encryptionAuthenticity(.notGuaranteed(color: .gray)),
|
||||||
layoutType: .horizontal()))
|
layoutType: .horizontal()))
|
||||||
// TimelineItemSendInfoLabel(sendInfo: .init(localizedString: "09:47 AM",
|
TimelineItemSendInfoLabel(sendInfo: .init(itemID: .random,
|
||||||
// status: .unencrypted,
|
localizedString: "09:47 AM",
|
||||||
// layoutType: .horizontal()))
|
status: .encryptionAuthenticity(.sentInClear(color: .red)),
|
||||||
|
layoutType: .horizontal()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-1
@@ -14,8 +14,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Compound
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
/// Represents and issue with a timeline item's authenticity such as coming from an
|
/// Represents and issue with a timeline item's authenticity such as coming from an
|
||||||
/// unsigned session or being sent unencrypted in an encrypted room. See Rust's
|
/// unsigned session or being sent unencrypted in an encrypted room. See Rust's
|
||||||
@@ -43,6 +44,25 @@ enum EncryptionAuthenticity: Hashable {
|
|||||||
L10n.eventShieldReasonSentInClear
|
L10n.eventShieldReasonSentInClear
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var color: Color {
|
||||||
|
switch self {
|
||||||
|
case .notGuaranteed(let color),
|
||||||
|
.unknownDevice(let color),
|
||||||
|
.unsignedDevice(let color),
|
||||||
|
.unverifiedIdentity(let color),
|
||||||
|
.sentInClear(let color):
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon: KeyPath<CompoundIcons, Image> {
|
||||||
|
// TODO: Should sentInClear have a dedicated icon???
|
||||||
|
switch color {
|
||||||
|
case .red: \.error
|
||||||
|
case .gray: \.info
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EncryptionAuthenticity {
|
extension EncryptionAuthenticity {
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cfcaff9f0d5680fc4dbb30c906b8ce84213b6a41f6b4fb52c2ec256902fce350
|
oid sha256:f8ef2a2f846c6906a4cef21d909c3cf6b989a13b488a7d0ee530c4b29b1617c1
|
||||||
size 437852
|
size 438499
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ec285cfbd7793fa8feca3e8869b776f64214b3cfd116bb6e77d9f4149732625a
|
oid sha256:6274dbd83240ee4197699683c66b2f0e7b22490b5d4d904aa92f653eb7bc5847
|
||||||
size 440028
|
size 440632
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cb717b5c2f4c5e5dc9f0a473934bb237eebad19e401ea08353a62d5567d99fc3
|
oid sha256:07184e8192f4e80a0fb9fda161d0966c30858f17dacf43843f36bfc6a5c807de
|
||||||
size 343142
|
size 343391
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f8613ae1955fd57f8536f51e46b87913b0c6f156d8cff7db6a3e6f7f18846896
|
oid sha256:4571579a42406d66a4a61d91b79b6809e376dc95910a6e07d582f23fa99ac15f
|
||||||
size 344422
|
size 344668
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4a814e87ba746f5d1590bc620afae3d473eab3497875da9f56fa1f5aceb6b593
|
||||||
|
size 139034
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a6418fe1b15eb9f94ef1e4f1329508788d1df9616b24760a26ec7c0317d75546
|
||||||
|
size 131913
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:dc5eb6e01de2c2e7b57683ffa0881a300b073c1b0517d96dbc477689e2e4f2e7
|
||||||
|
size 146246
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:dfce5e379a6235cd954ae464fea78a419bb210b799b1df5a21cdc172e4233e3e
|
||||||
|
size 134505
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:32ef4ab70c1c611b8022a85855cbc3f0483aeb8b794bc31403dbdeeb417dca3f
|
||||||
|
size 91903
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:73da91674825a74fd803055253153543cf8b6985a670a46521897dd06db47851
|
||||||
|
size 83950
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:011c4ce8721d2d12fd159b9451e06f03419543ad6744c7c2ec2f977f46803687
|
||||||
|
size 107558
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7532e6e65ed00921a92b7d6c431485907f7b26bbf193ea3ecc75848566f660ec
|
||||||
|
size 89746
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8a4a63fd7a948b134d02a405070c038116405d6b19f7ab45930d8d8fe25d66c
|
oid sha256:897afefdd96515ac7269c760ebe56cbeaff0834f023caf384365439ae0838b8d
|
||||||
size 78530
|
size 81737
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8a4a63fd7a948b134d02a405070c038116405d6b19f7ab45930d8d8fe25d66c
|
oid sha256:897afefdd96515ac7269c760ebe56cbeaff0834f023caf384365439ae0838b8d
|
||||||
size 78530
|
size 81737
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:67533013d6b66f5256e00fbb7ee684f4ec364f640d10a9209dc14f067aa31905
|
oid sha256:82cb8d10fb5922d8fd6a69cae52509f5b493ba95c626f90df2ba3dec23ed5317
|
||||||
size 36782
|
size 39063
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:67533013d6b66f5256e00fbb7ee684f4ec364f640d10a9209dc14f067aa31905
|
oid sha256:82cb8d10fb5922d8fd6a69cae52509f5b493ba95c626f90df2ba3dec23ed5317
|
||||||
size 36782
|
size 39063
|
||||||
|
|||||||
Reference in New Issue
Block a user