From a576037cf5926eb98a3405be6221ed10479d477e Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 11 Sep 2023 11:41:24 +1000 Subject: [PATCH] WIP: fix messages not correctly shown in message info screen --- Session.xcodeproj/project.pbxproj | 4 + .../LinkPreviewView_SwiftUI.swift | 45 ++++ .../MessageInfoView.swift | 232 ++++++++++++++++-- 3 files changed, 256 insertions(+), 25 deletions(-) create mode 100644 Session/Conversations/Message Cells/Content Views/LinkPreviewView_SwiftUI.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 03a17cb88..a6289c579 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -146,6 +146,7 @@ 7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54CD27ACCEEC003D12F8 /* EmptySearchResultCell.swift */; }; 7BAF54D327ACCF01003D12F8 /* ShareAppExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D127ACCF01003D12F8 /* ShareAppExtensionContext.swift */; }; 7BAF54D427ACCF01003D12F8 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D227ACCF01003D12F8 /* SAEScreenLockViewController.swift */; }; + 7BAFA75A2AAEA281001DA43E /* LinkPreviewView_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAFA7592AAEA281001DA43E /* LinkPreviewView_SwiftUI.swift */; }; 7BB92B3F28C825FD0082762F /* NewConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB92B3E28C825FD0082762F /* NewConversationViewModel.swift */; }; 7BBBDC44286EAD2D00747E59 /* TappableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */; }; 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */; }; @@ -1262,6 +1263,7 @@ 7BAF54D127ACCF01003D12F8 /* ShareAppExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareAppExtensionContext.swift; sourceTree = ""; }; 7BAF54D227ACCF01003D12F8 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = ""; }; 7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; + 7BAFA7592AAEA281001DA43E /* LinkPreviewView_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewView_SwiftUI.swift; sourceTree = ""; }; 7BB92B3E28C825FD0082762F /* NewConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewModel.swift; sourceTree = ""; }; 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableLabel.swift; sourceTree = ""; }; 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTitleViewController.swift; sourceTree = ""; }; @@ -2459,6 +2461,7 @@ C328250E25CA06020062D0A7 /* VoiceMessageView.swift */, B8569AE225CBB19A00DBA3DB /* DocumentView.swift */, B849789525D4A2F500D0D0B3 /* LinkPreviewView.swift */, + 7BAFA7592AAEA281001DA43E /* LinkPreviewView_SwiftUI.swift */, B8D84EA225DF745A005A043E /* LinkPreviewState.swift */, B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */, 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */, @@ -6087,6 +6090,7 @@ B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */, FD71160428C95B5600B47552 /* PhotoCollectionPickerViewModel.swift in Sources */, FD37EA1928AC5CCA003AE748 /* NotificationSoundViewModel.swift in Sources */, + 7BAFA75A2AAEA281001DA43E /* LinkPreviewView_SwiftUI.swift in Sources */, FD71163E28E2C82900B47552 /* SessionCell.swift in Sources */, 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */, C328254925CA60E60062D0A7 /* ContextMenuVC+Action.swift in Sources */, diff --git a/Session/Conversations/Message Cells/Content Views/LinkPreviewView_SwiftUI.swift b/Session/Conversations/Message Cells/Content Views/LinkPreviewView_SwiftUI.swift new file mode 100644 index 000000000..e7a769da4 --- /dev/null +++ b/Session/Conversations/Message Cells/Content Views/LinkPreviewView_SwiftUI.swift @@ -0,0 +1,45 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import SwiftUI +import NVActivityIndicatorView +import SessionUIKit +import SessionMessagingKit + +public struct LinkPreviewView_SwiftUI: View { + private static let loaderSize: CGFloat = 24 + private static let cancelButtonSize: CGFloat = 45 + + private let maxWidth: CGFloat + private let onCancel: (() -> ())? + + public init(maxWidth: CGFloat, onCancel: (() -> ())? = nil) { + self.maxWidth = maxWidth + self.onCancel = onCancel + } + + public var body: some View { + VStack( + alignment: .leading, + spacing: 0 + ) { + HStack( + alignment: .center, + spacing: 0 + ) { + + + } + + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } + } +} + +struct LinkPreviewView_SwiftUI_Previews: PreviewProvider { + static var previews: some View { + LinkPreviewView_SwiftUI( + maxWidth: 200, + onCancel: nil + ) + } +} diff --git a/Session/Media Viewing & Editing/MessageInfoView.swift b/Session/Media Viewing & Editing/MessageInfoView.swift index e03157d30..283b9e777 100644 --- a/Session/Media Viewing & Editing/MessageInfoView.swift +++ b/Session/Media Viewing & Editing/MessageInfoView.swift @@ -12,14 +12,13 @@ struct MessageInfoView: View { @State var index = 1 @State var showingAttachmentFullScreen = false - static private let cornerRadius: CGFloat = 18 + static private let cornerRadius: CGFloat = 17 var actions: [ContextMenuVC.Action] var messageViewModel: MessageViewModel var isMessageFailed: Bool { return [.failed, .failedToSync].contains(messageViewModel.state) } - var snapshot: UIView? var dismiss: (() -> Void)? @@ -38,29 +37,9 @@ struct MessageInfoView: View { spacing: 10 ) { // Message bubble snapshot - ZStack { - if let snapshot = self.snapshot { - UIView_SwiftUI(view: snapshot) - } else { - if let body: String = messageViewModel.body, !body.isEmpty { - let (bubbleBackgroundColor, bubbleTextColor): (ThemeValue, ThemeValue) = ( - messageViewModel.variant == .standardIncoming || - messageViewModel.variant == .standardIncomingDeleted - ) ? - (.messageBubble_incomingBackground, .messageBubble_incomingText) : - (.messageBubble_outgoingBackground, .messageBubble_outgoingText) - - RoundedRectangle(cornerRadius: Self.cornerRadius) - .fill(themeColor: bubbleBackgroundColor) - - Text(body) - .foregroundColor(themeColor: bubbleTextColor) - .padding(.vertical, Values.smallSpacing) - .padding(.horizontal, Values.mediumSpacing) - - } - } - } + MessageBubble( + messageViewModel: messageViewModel + ) .frame( maxWidth: .infinity, maxHeight: .infinity, @@ -379,6 +358,209 @@ struct MessageInfoView: View { } } +struct MessageBubble: View { + static private let cornerRadius: CGFloat = 18 + + let messageViewModel: MessageViewModel + var bubbleBackgroundColor: ThemeValue { + messageViewModel.variant == .standardIncoming || messageViewModel.variant == .standardIncomingDeleted ? + .messageBubble_incomingBackground : + .messageBubble_outgoingBackground + } + + var body: some View { + ZStack { + switch messageViewModel.cellType { + case .typingIndicator, .dateHeader, .unreadMarker: break + + case .textOnlyMessage: + let inset: CGFloat = 12 + let maxWidth: CGFloat = (VisibleMessageCell.getMaxWidth(for: messageViewModel) - 2 * inset) + + if let linkPreview: LinkPreview = messageViewModel.linkPreview { + switch linkPreview.variant { + case .standard: + let linkPreviewView: LinkPreviewView = LinkPreviewView(maxWidth: maxWidth) + linkPreviewView.update( + with: LinkPreview.SentState( + linkPreview: linkPreview, + imageAttachment: messageViewModel.linkPreviewAttachment + ), + isOutgoing: (messageViewModel.variant == .standardOutgoing), + delegate: self, + cellViewModel: messageViewModel, + bodyLabelTextColor: bodyLabelTextColor, + lastSearchText: lastSearchText + ) + bubbleView.addSubview(linkPreviewView) + linkPreviewView.pin(to: bubbleView, withInset: 0) + snContentView.addArrangedSubview(bubbleBackgroundView) + self.bodyTappableLabel = linkPreviewView.bodyTappableLabel + + case .openGroupInvitation: + let openGroupInvitationView: OpenGroupInvitationView = OpenGroupInvitationView( + name: (linkPreview.title ?? ""), + url: linkPreview.url, + textColor: bodyLabelTextColor, + isOutgoing: (cellViewModel.variant == .standardOutgoing) + ) + bubbleView.addSubview(openGroupInvitationView) + bubbleView.pin(to: openGroupInvitationView) + snContentView.addArrangedSubview(bubbleBackgroundView) + } + } + else { + // Stack view + let stackView = UIStackView(arrangedSubviews: []) + stackView.axis = .vertical + stackView.spacing = 2 + + // Quote view + if let quote: Quote = cellViewModel.quote { + let hInset: CGFloat = 2 + let quoteView: QuoteView = QuoteView( + for: .regular, + authorId: quote.authorId, + quotedText: quote.body, + threadVariant: cellViewModel.threadVariant, + currentUserPublicKey: cellViewModel.currentUserPublicKey, + currentUserBlinded15PublicKey: cellViewModel.currentUserBlinded15PublicKey, + currentUserBlinded25PublicKey: cellViewModel.currentUserBlinded25PublicKey, + direction: (cellViewModel.variant == .standardOutgoing ? + .outgoing : + .incoming + ), + attachment: cellViewModel.quoteAttachment, + hInset: hInset, + maxWidth: maxWidth + ) + let quoteViewContainer = UIView(wrapping: quoteView, withInsets: UIEdgeInsets(top: 0, leading: hInset, bottom: 0, trailing: hInset)) + stackView.addArrangedSubview(quoteViewContainer) + } + + // Body text view + let bodyTappableLabel = VisibleMessageCell.getBodyTappableLabel( + for: cellViewModel, + with: maxWidth, + textColor: bodyLabelTextColor, + searchText: lastSearchText, + delegate: self + ) + self.bodyTappableLabel = bodyTappableLabel + stackView.addArrangedSubview(bodyTappableLabel) + + // Constraints + bubbleView.addSubview(stackView) + stackView.pin(to: bubbleView, withInset: inset) + stackView.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true + snContentView.addArrangedSubview(bubbleBackgroundView) + } + + case .mediaMessage: + // Body text view + if let body: String = cellViewModel.body, !body.isEmpty { + let inset: CGFloat = 12 + let maxWidth: CGFloat = (VisibleMessageCell.getMaxWidth(for: cellViewModel) - 2 * inset) + let bodyTappableLabel = VisibleMessageCell.getBodyTappableLabel( + for: cellViewModel, + with: maxWidth, + textColor: bodyLabelTextColor, + searchText: lastSearchText, + delegate: self + ) + + self.bodyTappableLabel = bodyTappableLabel + bubbleView.addSubview(bodyTappableLabel) + bodyTappableLabel.pin(to: bubbleView, withInset: inset) + snContentView.addArrangedSubview(bubbleBackgroundView) + } + + // Album view + let maxMessageWidth: CGFloat = VisibleMessageCell.getMaxWidth(for: cellViewModel) + let albumView = MediaAlbumView( + mediaCache: mediaCache, + items: (cellViewModel.attachments? + .filter { $0.isVisualMedia }) + .defaulting(to: []), + isOutgoing: (cellViewModel.variant == .standardOutgoing), + maxMessageWidth: maxMessageWidth + ) + self.albumView = albumView + let size = getSize(for: cellViewModel) + albumView.set(.width, to: size.width) + albumView.set(.height, to: size.height) + albumView.loadMedia() + snContentView.addArrangedSubview(albumView) + + unloadContent = { albumView.unloadMedia() } + + case .audio: + guard let attachment: Attachment = cellViewModel.attachments?.first(where: { $0.isAudio }) else { + return + } + + let voiceMessageView: VoiceMessageView = VoiceMessageView() + voiceMessageView.update( + with: attachment, + isPlaying: (playbackInfo?.state == .playing), + progress: (playbackInfo?.progress ?? 0), + playbackRate: (playbackInfo?.playbackRate ?? 1), + oldPlaybackRate: (playbackInfo?.oldPlaybackRate ?? 1) + ) + + bubbleView.addSubview(voiceMessageView) + voiceMessageView.pin(to: bubbleView) + snContentView.addArrangedSubview(bubbleBackgroundView) + self.voiceMessageView = voiceMessageView + + case .genericAttachment: + guard let attachment: Attachment = cellViewModel.attachments?.first else { preconditionFailure() } + + let inset: CGFloat = 12 + let maxWidth = (VisibleMessageCell.getMaxWidth(for: cellViewModel) - 2 * inset) + + // Stack view + let stackView = UIStackView(arrangedSubviews: []) + stackView.axis = .vertical + stackView.spacing = Values.smallSpacing + + // Document view + let documentView = DocumentView(attachment: attachment, textColor: bodyLabelTextColor) + stackView.addArrangedSubview(documentView) + + // Body text view + if let body: String = cellViewModel.body, !body.isEmpty { // delegate should always be set at this point + let bodyContainerView: UIView = UIView() + let bodyTappableLabel = VisibleMessageCell.getBodyTappableLabel( + for: cellViewModel, + with: maxWidth, + textColor: bodyLabelTextColor, + searchText: lastSearchText, + delegate: self + ) + + self.bodyTappableLabel = bodyTappableLabel + bodyContainerView.addSubview(bodyTappableLabel) + bodyTappableLabel.pin(.top, to: .top, of: bodyContainerView) + bodyTappableLabel.pin(.leading, to: .leading, of: bodyContainerView, withInset: 12) + bodyTappableLabel.pin(.trailing, to: .trailing, of: bodyContainerView, withInset: -12) + bodyTappableLabel.pin(.bottom, to: .bottom, of: bodyContainerView, withInset: -12) + stackView.addArrangedSubview(bodyContainerView) + } + + bubbleView.addSubview(stackView) + stackView.pin(to: bubbleView) + snContentView.addArrangedSubview(bubbleBackgroundView) + } + + } + .background( + RoundedRectangle(cornerRadius: Self.cornerRadius) + .fill(themeColor: bubbleBackgroundColor) + ) + } +} + struct InfoBlock: View where Content: View { let title: String let content: () -> Content