refactor link preview view with SwiftUI
This commit is contained in:
parent
a576037cf5
commit
a943df4f9e
|
@ -6,31 +6,110 @@ import SessionUIKit
|
|||
import SessionMessagingKit
|
||||
|
||||
public struct LinkPreviewView_SwiftUI: View {
|
||||
private var state: LinkPreviewState
|
||||
private var isOutgoing: Bool
|
||||
private let maxWidth: CGFloat
|
||||
private var messageViewModel: MessageViewModel?
|
||||
private var bodyLabelTextColor: ThemeValue?
|
||||
private var lastSearchText: String?
|
||||
private let onCancel: (() -> ())?
|
||||
|
||||
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) {
|
||||
init(
|
||||
state: LinkPreviewState,
|
||||
isOutgoing: Bool,
|
||||
maxWidth: CGFloat = .infinity,
|
||||
messageViewModel: MessageViewModel? = nil,
|
||||
bodyLabelTextColor: ThemeValue? = nil,
|
||||
lastSearchText: String? = nil,
|
||||
onCancel: (() -> ())? = nil
|
||||
) {
|
||||
self.state = state
|
||||
self.isOutgoing = isOutgoing
|
||||
self.maxWidth = maxWidth
|
||||
self.messageViewModel = messageViewModel
|
||||
self.bodyLabelTextColor = bodyLabelTextColor
|
||||
self.lastSearchText = lastSearchText
|
||||
self.onCancel = onCancel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(
|
||||
alignment: .leading,
|
||||
spacing: 0
|
||||
spacing: Values.mediumSpacing
|
||||
) {
|
||||
HStack(
|
||||
alignment: .center,
|
||||
spacing: 0
|
||||
spacing: Values.mediumSpacing
|
||||
) {
|
||||
// Link preview image
|
||||
let imageSize: CGFloat = state is LinkPreview.SentState ? 100 : 80
|
||||
if let linkPreviewImage: UIImage = state.image {
|
||||
Image(uiImage: linkPreviewImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.foregroundColor(
|
||||
themeColor: isOutgoing ?
|
||||
.messageBubble_outgoingText :
|
||||
.messageBubble_incomingText
|
||||
)
|
||||
.frame(
|
||||
width: imageSize,
|
||||
height: imageSize
|
||||
)
|
||||
.cornerRadius(state is LinkPreview.SentState ? 0 : 8)
|
||||
} else {
|
||||
if
|
||||
state is LinkPreview.DraftState || state is LinkPreview.SentState,
|
||||
let defaultImage: UIImage = UIImage(named: "Link")?.withRenderingMode(.alwaysTemplate)
|
||||
{
|
||||
Image(uiImage: defaultImage)
|
||||
.foregroundColor(
|
||||
themeColor: isOutgoing ?
|
||||
.messageBubble_outgoingText :
|
||||
.messageBubble_incomingText
|
||||
)
|
||||
.frame(
|
||||
width: imageSize,
|
||||
height: imageSize
|
||||
)
|
||||
.cornerRadius(state is LinkPreview.SentState ? 0 : 8)
|
||||
}
|
||||
}
|
||||
|
||||
// Link preview title
|
||||
if let title: String = state.title {
|
||||
Text(title)
|
||||
.bold()
|
||||
.font(.system(size: Values.smallFontSize))
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(
|
||||
themeColor: isOutgoing ?
|
||||
.messageBubble_outgoingText :
|
||||
.messageBubble_incomingText
|
||||
)
|
||||
}
|
||||
|
||||
// Cancel button
|
||||
if state is LinkPreview.DraftState {
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Button(action: {
|
||||
onCancel?()
|
||||
}, label: {
|
||||
if let image: UIImage = UIImage(named: "X")?.withRenderingMode(.alwaysTemplate) {
|
||||
Image(uiImage: image)
|
||||
.foregroundColor(themeColor: .textPrimary)
|
||||
}
|
||||
})
|
||||
.frame(
|
||||
width: Self.cancelButtonSize,
|
||||
height: Self.cancelButtonSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +117,15 @@ public struct LinkPreviewView_SwiftUI: View {
|
|||
struct LinkPreviewView_SwiftUI_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LinkPreviewView_SwiftUI(
|
||||
maxWidth: 200,
|
||||
onCancel: nil
|
||||
state: LinkPreview.DraftState(
|
||||
linkPreviewDraft: .init(
|
||||
urlString: "https://github.com/oxen-io",
|
||||
title: "Github - oxen-io/session-ios: A private messenger for iOS.",
|
||||
jpegImageData: UIImage(named: "AppIcon")?.jpegData(compressionQuality: 1)
|
||||
)
|
||||
),
|
||||
isOutgoing: true
|
||||
)
|
||||
.padding(.horizontal, Values.mediumSpacing)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -370,188 +370,183 @@ struct MessageBubble: View {
|
|||
|
||||
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)
|
||||
}
|
||||
// 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:
|
||||
// LinkPreviewView_SwiftUI(
|
||||
// state: LinkPreview.SentState(
|
||||
// linkPreview: linkPreview,
|
||||
// imageAttachment: messageViewModel.linkPreviewAttachment
|
||||
// ),
|
||||
// isOutgoing: (messageViewModel.variant == .standardOutgoing),
|
||||
// maxWidth: maxWidth,
|
||||
// messageViewModel: messageViewModel,
|
||||
// bodyLabelTextColor: nil,
|
||||
// lastSearchText: nil
|
||||
// )
|
||||
//
|
||||
// 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(
|
||||
|
|
Loading…
Reference in New Issue