mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Require acknowledgement to send to verified users who have unsigned devices or have changed their identity. (#3215)
* Refactor HeroImage style. * Add a screen to resolve (crypto-related) timeline item send failures. * Refactor send failures. * Update the SDK.
This commit is contained in:
@@ -21,6 +21,9 @@ struct TimelineItemMenu: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
messagePreview
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 32.0)
|
||||
.padding(.bottom, 4.0)
|
||||
.frame(idealWidth: 300.0)
|
||||
|
||||
Divider()
|
||||
@@ -30,7 +33,6 @@ struct TimelineItemMenu: View {
|
||||
VStack(alignment: .leading, spacing: 0.0) {
|
||||
if !actions.reactions.isEmpty {
|
||||
reactionsSection
|
||||
.padding(.top, 4.0)
|
||||
.padding(.bottom, 8.0)
|
||||
|
||||
Divider()
|
||||
@@ -87,15 +89,20 @@ struct TimelineItemMenu: View {
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
|
||||
if let authenticity = item.properties.encryptionAuthenticity {
|
||||
if case let .sendingFailed(.verifiedUser(failure)) = item.properties.deliveryStatus {
|
||||
Divider()
|
||||
.padding(.horizontal, -16)
|
||||
|
||||
VerifiedUserSendFailureView(failure: failure, members: context.viewState.members) {
|
||||
send(.itemSendInfoTapped(itemID: item.id))
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
} else if let authenticity = item.properties.encryptionAuthenticity {
|
||||
Label(authenticity.message, icon: authenticity.icon, iconSize: .small, relativeTo: .compound.bodySMSemibold)
|
||||
.font(.compound.bodySMSemibold)
|
||||
.foregroundStyle(authenticity.foregroundStyle)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 32.0)
|
||||
.padding(.bottom, 4.0)
|
||||
}
|
||||
|
||||
private var reactionsSection: some View {
|
||||
@@ -162,10 +169,50 @@ struct TimelineItemMenu: View {
|
||||
}
|
||||
|
||||
private func send(_ action: TimelineItemMenuAction) {
|
||||
send(.handleTimelineItemMenuAction(itemID: item.id, action: action))
|
||||
}
|
||||
|
||||
private func send(_ action: TimelineViewAction) {
|
||||
dismiss()
|
||||
// Otherwise we might get errors that a sheet is already presented
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
context.send(viewAction: .handleTimelineItemMenuAction(itemID: item.id, action: action))
|
||||
context.send(viewAction: action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct VerifiedUserSendFailureView: View {
|
||||
let failure: TimelineItemSendFailure.VerifiedUser
|
||||
let action: () -> Void
|
||||
|
||||
private let memberDisplayName: String
|
||||
|
||||
init(failure: TimelineItemSendFailure.VerifiedUser,
|
||||
members: [String: RoomMemberState],
|
||||
action: @escaping () -> Void) {
|
||||
self.failure = failure
|
||||
self.action = action
|
||||
|
||||
let userIDs = failure.affectedUserIDs
|
||||
memberDisplayName = userIDs.first.map { members[$0]?.displayName ?? $0 } ?? ""
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch failure {
|
||||
case .hasUnsignedDevice: L10n.screenTimelineItemMenuSendFailureUnsignedDevice(memberDisplayName)
|
||||
case .changedIdentity: L10n.screenTimelineItemMenuSendFailureChangedIdentity(memberDisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 8) {
|
||||
Label(title, icon: \.error, iconSize: .small, relativeTo: .compound.bodySMSemibold)
|
||||
.font(.compound.bodySMSemibold)
|
||||
.foregroundStyle(.compound.textCriticalPrimary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
ListRowAccessory.navigationLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,20 +234,27 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
||||
static let (backupItem, _) = makeItem(authenticity: .notGuaranteed(color: .gray))
|
||||
static let (unsignedItem, _) = makeItem(authenticity: .unsignedDevice(color: .red))
|
||||
static let (unencryptedItem, _) = makeItem(authenticity: .sentInClear(color: .red))
|
||||
static let (unknownFailureItem, _) = makeItem(deliveryStatus: .sendingFailed(.unknown))
|
||||
static let (identityChangedItem, _) = makeItem(deliveryStatus: .sendingFailed(.verifiedUser(.changedIdentity(users: [
|
||||
"@alice:matrix.org"
|
||||
]))))
|
||||
static let (unsignedDevicesItem, _) = makeItem(deliveryStatus: .sendingFailed(.verifiedUser(.hasUnsignedDevice(devices: [
|
||||
"@alice:matrix.org": ["DEVICE1", "DEVICE2"]
|
||||
]))))
|
||||
|
||||
static var previews: some View {
|
||||
TimelineItemMenu(item: item, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("With button shapes off")
|
||||
.previewDisplayName("Normal")
|
||||
|
||||
TimelineItemMenu(item: item, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.environment(\._accessibilityShowButtonShapes, true)
|
||||
.previewDisplayName("With button shapes on")
|
||||
.previewDisplayName("Button shapes")
|
||||
|
||||
TimelineItemMenu(item: backupItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Authenticity not guaranteed")
|
||||
.previewDisplayName("Authenticity")
|
||||
|
||||
TimelineItemMenu(item: unsignedItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
@@ -209,9 +263,22 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
||||
TimelineItemMenu(item: unencryptedItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Unencrypted")
|
||||
|
||||
TimelineItemMenu(item: unknownFailureItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Unknown failure")
|
||||
|
||||
TimelineItemMenu(item: unsignedDevicesItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Unsigned Devices")
|
||||
|
||||
TimelineItemMenu(item: identityChangedItem, actions: actions)
|
||||
.environmentObject(viewModel.context)
|
||||
.previewDisplayName("Identity Changed")
|
||||
}
|
||||
|
||||
static func makeItem(authenticity: EncryptionAuthenticity? = nil) -> (TextRoomTimelineItem, TimelineItemMenuActions)! {
|
||||
static func makeItem(authenticity: EncryptionAuthenticity? = nil,
|
||||
deliveryStatus: TimelineItemDeliveryStatus? = nil) -> (TextRoomTimelineItem, TimelineItemMenuActions)! {
|
||||
guard var item = RoomTimelineItemFixtures.singleMessageChunk.first as? TextRoomTimelineItem,
|
||||
let actions = TimelineItemMenuActions(isReactable: true,
|
||||
actions: [.copy, .edit, .reply(isThread: false), .pin, .redact],
|
||||
@@ -223,6 +290,10 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
|
||||
item.properties.encryptionAuthenticity = authenticity
|
||||
}
|
||||
|
||||
if let deliveryStatus {
|
||||
item.properties.deliveryStatus = deliveryStatus
|
||||
}
|
||||
|
||||
return (item, actions)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user