mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Revert decryption banner & try fix context menu crash. (#525)
This commit is contained in:
@@ -26,6 +26,8 @@
|
|||||||
"room_timeline_syncing" = "Syncing";
|
"room_timeline_syncing" = "Syncing";
|
||||||
"room_timeline_unable_to_decrypt" = "Unable to decrypt";
|
"room_timeline_unable_to_decrypt" = "Unable to decrypt";
|
||||||
|
|
||||||
|
"room_timeline_context_menu_retry_decryption" = "Retry decryption";
|
||||||
|
|
||||||
"room_timeline_item_unsupported" = "Unsupported event";
|
"room_timeline_item_unsupported" = "Unsupported event";
|
||||||
"room_timeline_image_gif" = "GIF";
|
"room_timeline_image_gif" = "GIF";
|
||||||
"room_timeline_read_marker_title" = "New";
|
"room_timeline_read_marker_title" = "New";
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ extension ElementL10n {
|
|||||||
public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title")
|
public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title")
|
||||||
/// Failed loading messages
|
/// Failed loading messages
|
||||||
public static let roomTimelineBackpaginationFailure = ElementL10n.tr("Untranslated", "room_timeline_backpagination_failure")
|
public static let roomTimelineBackpaginationFailure = ElementL10n.tr("Untranslated", "room_timeline_backpagination_failure")
|
||||||
|
/// Retry decryption
|
||||||
|
public static let roomTimelineContextMenuRetryDecryption = ElementL10n.tr("Untranslated", "room_timeline_context_menu_retry_decryption")
|
||||||
/// Editing
|
/// Editing
|
||||||
public static let roomTimelineEditing = ElementL10n.tr("Untranslated", "room_timeline_editing")
|
public static let roomTimelineEditing = ElementL10n.tr("Untranslated", "room_timeline_editing")
|
||||||
/// GIF
|
/// GIF
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ enum RoomScreenViewAction {
|
|||||||
case cancelEdit
|
case cancelEdit
|
||||||
/// Mark the entire room as read - this is heavy handed as a starting point for now.
|
/// Mark the entire room as read - this is heavy handed as a starting point for now.
|
||||||
case markRoomAsRead
|
case markRoomAsRead
|
||||||
case retryDecryption
|
|
||||||
case contextMenuAction(itemID: String, action: TimelineItemContextMenuAction)
|
case contextMenuAction(itemID: String, action: TimelineItemContextMenuAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ struct RoomScreenViewState: BindableState {
|
|||||||
var items: [RoomTimelineViewProvider] = []
|
var items: [RoomTimelineViewProvider] = []
|
||||||
var canBackPaginate = true
|
var canBackPaginate = true
|
||||||
var isBackPaginating = false
|
var isBackPaginating = false
|
||||||
var showEncryptionBanner = false
|
|
||||||
var showLoading = false
|
var showLoading = false
|
||||||
var bindings: RoomScreenViewStateBindings
|
var bindings: RoomScreenViewStateBindings
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
private let timelineController: RoomTimelineControllerProtocol
|
private let timelineController: RoomTimelineControllerProtocol
|
||||||
private let timelineViewFactory: RoomTimelineViewFactoryProtocol
|
private let timelineViewFactory: RoomTimelineViewFactoryProtocol
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
|
||||||
init(timelineController: RoomTimelineControllerProtocol,
|
init(timelineController: RoomTimelineControllerProtocol,
|
||||||
timelineViewFactory: RoomTimelineViewFactoryProtocol,
|
timelineViewFactory: RoomTimelineViewFactoryProtocol,
|
||||||
mediaProvider: MediaProviderProtocol,
|
mediaProvider: MediaProviderProtocol,
|
||||||
@@ -67,10 +66,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
if self.state.isBackPaginating != isBackPaginating {
|
if self.state.isBackPaginating != isBackPaginating {
|
||||||
self.state.isBackPaginating = isBackPaginating
|
self.state.isBackPaginating = isBackPaginating
|
||||||
}
|
}
|
||||||
case .hasEncryptedItems(let hasEncryptedItems):
|
|
||||||
if self.state.showEncryptionBanner != hasEncryptedItems {
|
|
||||||
self.state.showEncryptionBanner = hasEncryptedItems
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -118,8 +113,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
state.bindings.composerText = ""
|
state.bindings.composerText = ""
|
||||||
case .markRoomAsRead:
|
case .markRoomAsRead:
|
||||||
await markRoomAsRead()
|
await markRoomAsRead()
|
||||||
case .retryDecryption:
|
|
||||||
retryDecryption()
|
|
||||||
case .contextMenuAction(let itemID, let action):
|
case .contextMenuAction(let itemID, let action):
|
||||||
processContentMenuAction(action, itemID: itemID)
|
processContentMenuAction(action, itemID: itemID)
|
||||||
}
|
}
|
||||||
@@ -189,19 +182,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retry decrypting any encrypted items in the timeline.
|
|
||||||
private func retryDecryption() {
|
|
||||||
Task {
|
|
||||||
let firstEncryptedItem = state.items.first { $0.sessionID != nil }
|
|
||||||
|
|
||||||
if let sessionID = firstEncryptedItem?.sessionID {
|
|
||||||
// Request for the first encrypted item to be decrypted as the SDK
|
|
||||||
// will continue to decrypt any following items automatically.
|
|
||||||
await timelineController.retryDecryption(for: sessionID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func displayError(_ type: RoomScreenErrorType) {
|
private func displayError(_ type: RoomScreenErrorType) {
|
||||||
switch type {
|
switch type {
|
||||||
case .alert(let message):
|
case .alert(let message):
|
||||||
@@ -244,7 +224,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
actions.append(.redact)
|
actions.append(.redact)
|
||||||
}
|
}
|
||||||
|
|
||||||
let debugActions: [TimelineItemContextMenuAction] = [.viewSource]
|
var debugActions: [TimelineItemContextMenuAction] = [.viewSource]
|
||||||
|
|
||||||
|
if let item = timelineItem as? EncryptedRoomTimelineItem,
|
||||||
|
case let .megolmV1AesSha2(sessionID) = item.encryptionType {
|
||||||
|
debugActions.append(.retryDecryption(sessionID: sessionID))
|
||||||
|
}
|
||||||
|
|
||||||
return .init(actions: actions, debugActions: debugActions)
|
return .init(actions: actions, debugActions: debugActions)
|
||||||
}
|
}
|
||||||
@@ -286,6 +271,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
let debugDescription = timelineController.debugDescription(for: item.id)
|
let debugDescription = timelineController.debugDescription(for: item.id)
|
||||||
MXLog.info(debugDescription)
|
MXLog.info(debugDescription)
|
||||||
state.bindings.debugInfo = .init(title: "Timeline item", content: debugDescription)
|
state.bindings.debugInfo = .init(title: "Timeline item", content: debugDescription)
|
||||||
|
case .retryDecryption(let sessionID):
|
||||||
|
Task {
|
||||||
|
await timelineController.retryDecryption(for: sessionID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if action.switchToDefaultComposer {
|
if action.switchToDefaultComposer {
|
||||||
@@ -302,14 +291,3 @@ extension RoomScreenViewModel {
|
|||||||
mediaProvider: MockMediaProvider(),
|
mediaProvider: MockMediaProvider(),
|
||||||
roomName: "Preview room")
|
roomName: "Preview room")
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension RoomTimelineViewProvider {
|
|
||||||
/// The item's session ID if it was unable to decrypt and uses megolm.
|
|
||||||
/// This will be nil for items that have already been decrypted.
|
|
||||||
var sessionID: String? {
|
|
||||||
guard case let .encrypted(item) = self,
|
|
||||||
case let .megolmV1AesSha2(sessionID) = item.encryptionType
|
|
||||||
else { return nil }
|
|
||||||
return sessionID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ struct RoomScreen: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
timeline
|
timeline
|
||||||
.background(Color.element.background.ignoresSafeArea()) // Kills the toolbar translucency.
|
.background(Color.element.background.ignoresSafeArea()) // Kills the toolbar translucency.
|
||||||
.overlay(alignment: .top) { encryptionBanner } // Overlay for now, safeAreaInset breaks timeline scroll offset.
|
|
||||||
.animation(.spring(), value: context.viewState.showEncryptionBanner)
|
|
||||||
.safeAreaInset(edge: .bottom, spacing: 0) { messageComposer }
|
.safeAreaInset(edge: .bottom, spacing: 0) { messageComposer }
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar { toolbar }
|
.toolbar { toolbar }
|
||||||
@@ -51,44 +49,6 @@ struct RoomScreen: View {
|
|||||||
.overlay(alignment: .bottomTrailing) { scrollToBottomButton }
|
.overlay(alignment: .bottomTrailing) { scrollToBottomButton }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
var encryptionBanner: some View {
|
|
||||||
if context.viewState.showEncryptionBanner {
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Label {
|
|
||||||
Text("Unable to decrypt all messages")
|
|
||||||
.foregroundColor(.element.primaryContent)
|
|
||||||
} icon: {
|
|
||||||
Image(systemName: "lock.shield")
|
|
||||||
.foregroundColor(.element.background)
|
|
||||||
.padding(4)
|
|
||||||
.background(Color.element.tertiaryContent)
|
|
||||||
.cornerRadius(5)
|
|
||||||
}
|
|
||||||
.font(.element.bodyBold)
|
|
||||||
|
|
||||||
Text("Accessing your encrypted message history is not fully supported yet.")
|
|
||||||
.font(.element.subheadline)
|
|
||||||
.foregroundColor(.element.secondaryContent)
|
|
||||||
.padding(.bottom, 8)
|
|
||||||
|
|
||||||
Button(ElementL10n.globalRetry) {
|
|
||||||
context.send(viewAction: .retryDecryption)
|
|
||||||
}
|
|
||||||
.buttonStyle(.elementCapsuleProminent)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 12)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.fill(Color.element.system)
|
|
||||||
}
|
|
||||||
.padding([.horizontal, .top], 16)
|
|
||||||
.background(Color.element.background)
|
|
||||||
.transition(.move(edge: .top).combined(with: .opacity))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var messageComposer: some View {
|
var messageComposer: some View {
|
||||||
MessageComposer(text: $context.composerText,
|
MessageComposer(text: $context.composerText,
|
||||||
focused: $context.composerFocused,
|
focused: $context.composerFocused,
|
||||||
@@ -150,8 +110,7 @@ struct RoomScreen_Previews: PreviewProvider {
|
|||||||
roomName: "Preview room")
|
roomName: "Preview room")
|
||||||
|
|
||||||
NavigationView {
|
NavigationView {
|
||||||
RoomScreen(context: viewModel.context).encryptionBanner
|
RoomScreen(context: viewModel.context)
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
var messageBubble: some View {
|
var messageBubble: some View {
|
||||||
styledContent
|
styledContent
|
||||||
.contentShape(.contextMenuPreview, RoundedCornerShape(radius: cornerRadius, corners: timelineItem.roundedCorners)) // Rounded corners for the context menu animation.
|
.contentShape(.contextMenuPreview, RoundedCornerShape(radius: cornerRadius, corners: timelineItem.roundedCorners)) // Rounded corners for the context menu animation.
|
||||||
.contextMenu { [weak context] in
|
.contextMenu {
|
||||||
context?.viewState.contextMenuActionProvider?(timelineItem.id).map { actions in
|
context.viewState.contextMenuActionProvider?(timelineItem.id).map { actions in
|
||||||
TimelineItemContextMenu(itemID: timelineItem.id, contextMenuActions: actions)
|
TimelineItemContextMenu(itemID: timelineItem.id, contextMenuActions: actions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ enum TimelineItemContextMenuAction: Identifiable, Hashable {
|
|||||||
case redact
|
case redact
|
||||||
case reply
|
case reply
|
||||||
case viewSource
|
case viewSource
|
||||||
|
case retryDecryption(sessionID: String)
|
||||||
|
|
||||||
var id: Self { self }
|
var id: Self { self }
|
||||||
|
|
||||||
@@ -102,6 +103,10 @@ public struct TimelineItemContextMenu: View {
|
|||||||
Button { send(action) } label: {
|
Button { send(action) } label: {
|
||||||
Label(ElementL10n.viewSource, systemImage: "doc.text.below.ecg")
|
Label(ElementL10n.viewSource, systemImage: "doc.text.below.ecg")
|
||||||
}
|
}
|
||||||
|
case .retryDecryption:
|
||||||
|
Button { send(action) } label: {
|
||||||
|
Label(ElementL10n.roomTimelineContextMenuRetryDecryption, systemImage: "arrow.down.message")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,12 +218,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
updateTimelineItems()
|
updateTimelineItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
private func updateTimelineItems() {
|
private func updateTimelineItems() {
|
||||||
var newTimelineItems = [RoomTimelineItemProtocol]()
|
var newTimelineItems = [RoomTimelineItemProtocol]()
|
||||||
var canBackPaginate = true
|
var canBackPaginate = true
|
||||||
var isBackPaginating = false
|
var isBackPaginating = false
|
||||||
var hasEncryptedItems = false
|
|
||||||
|
|
||||||
var createdIdentifiers = [String: Bool]()
|
var createdIdentifiers = [String: Bool]()
|
||||||
|
|
||||||
@@ -244,9 +243,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
if createdIdentifiers[timelineItem.id] == nil {
|
if createdIdentifiers[timelineItem.id] == nil {
|
||||||
newTimelineItems.append(timelineItem)
|
newTimelineItems.append(timelineItem)
|
||||||
createdIdentifiers[timelineItem.id] = true
|
createdIdentifiers[timelineItem.id] = true
|
||||||
if timelineItem is EncryptedRoomTimelineItem {
|
|
||||||
hasEncryptedItems = true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
MXLog.error("Found duplicated timeline item, ignoring")
|
MXLog.error("Found duplicated timeline item, ignoring")
|
||||||
}
|
}
|
||||||
@@ -284,7 +280,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
callbacks.send(.updatedTimelineItems)
|
callbacks.send(.updatedTimelineItems)
|
||||||
callbacks.send(.canBackPaginate(canBackPaginate))
|
callbacks.send(.canBackPaginate(canBackPaginate))
|
||||||
callbacks.send(.isBackPaginating(isBackPaginating))
|
callbacks.send(.isBackPaginating(isBackPaginating))
|
||||||
callbacks.send(.hasEncryptedItems(hasEncryptedItems))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func computeGroupState(for itemProxy: TimelineItemProxy,
|
private func computeGroupState(for itemProxy: TimelineItemProxy,
|
||||||
|
|||||||
-1
@@ -23,7 +23,6 @@ enum RoomTimelineControllerCallback {
|
|||||||
case updatedTimelineItem(_ itemId: String)
|
case updatedTimelineItem(_ itemId: String)
|
||||||
case canBackPaginate(Bool)
|
case canBackPaginate(Bool)
|
||||||
case isBackPaginating(Bool)
|
case isBackPaginating(Bool)
|
||||||
case hasEncryptedItems(Bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomTimelineControllerAction {
|
enum RoomTimelineControllerAction {
|
||||||
|
|||||||
Reference in New Issue
Block a user