|
|
|
@ -20,265 +20,171 @@ struct MessageInfoView: View {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
|
NavigationView {
|
|
|
|
|
ZStack (alignment: .topLeading) {
|
|
|
|
|
if #available(iOS 14.0, *) {
|
|
|
|
|
ThemeManager.currentTheme.colorSwiftUI(for: .backgroundPrimary).ignoresSafeArea()
|
|
|
|
|
} else {
|
|
|
|
|
ThemeManager.currentTheme.colorSwiftUI(for: .backgroundPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ScrollView(.vertical, showsIndicators: false) {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: 10
|
|
|
|
|
) {
|
|
|
|
|
// Message bubble snapshot
|
|
|
|
|
MessageBubble(
|
|
|
|
|
messageViewModel: messageViewModel
|
|
|
|
|
)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: Self.cornerRadius)
|
|
|
|
|
.fill(
|
|
|
|
|
themeColor: (messageViewModel.variant == .standardIncoming || messageViewModel.variant == .standardIncomingDeleted ?
|
|
|
|
|
.messageBubble_incomingBackground :
|
|
|
|
|
.messageBubble_outgoingBackground)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
ZStack (alignment: .topLeading) {
|
|
|
|
|
ScrollView(.vertical, showsIndicators: false) {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: 10
|
|
|
|
|
) {
|
|
|
|
|
// Message bubble snapshot
|
|
|
|
|
MessageBubble(
|
|
|
|
|
messageViewModel: messageViewModel
|
|
|
|
|
)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: Self.cornerRadius)
|
|
|
|
|
.fill(
|
|
|
|
|
themeColor: (messageViewModel.variant == .standardIncoming || messageViewModel.variant == .standardIncomingDeleted ?
|
|
|
|
|
.messageBubble_incomingBackground :
|
|
|
|
|
.messageBubble_outgoingBackground)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.top, Values.smallSpacing)
|
|
|
|
|
.padding(.bottom, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isMessageFailed {
|
|
|
|
|
let (image, statusText, tintColor) = messageViewModel.state.statusIconInfo(
|
|
|
|
|
variant: messageViewModel.variant,
|
|
|
|
|
hasAtLeastOneReadReceipt: messageViewModel.hasAtLeastOneReadReceipt
|
|
|
|
|
)
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.top, Values.smallSpacing)
|
|
|
|
|
.padding(.bottom, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isMessageFailed {
|
|
|
|
|
let (image, statusText, tintColor) = messageViewModel.state.statusIconInfo(
|
|
|
|
|
variant: messageViewModel.variant,
|
|
|
|
|
hasAtLeastOneReadReceipt: messageViewModel.hasAtLeastOneReadReceipt
|
|
|
|
|
)
|
|
|
|
|
HStack(spacing: 6) {
|
|
|
|
|
if let image: UIImage = image?.withRenderingMode(.alwaysTemplate) {
|
|
|
|
|
Image(uiImage: image)
|
|
|
|
|
.resizable()
|
|
|
|
|
.scaledToFit()
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
.frame(width: 13, height: 12)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HStack(spacing: 6) {
|
|
|
|
|
if let image: UIImage = image?.withRenderingMode(.alwaysTemplate) {
|
|
|
|
|
Image(uiImage: image)
|
|
|
|
|
.resizable()
|
|
|
|
|
.scaledToFit()
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
.frame(width: 13, height: 12)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let statusText: String = statusText {
|
|
|
|
|
Text(statusText)
|
|
|
|
|
.font(.system(size: Values.verySmallFontSize))
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
}
|
|
|
|
|
if let statusText: String = statusText {
|
|
|
|
|
Text(statusText)
|
|
|
|
|
.font(.system(size: Values.verySmallFontSize))
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
}
|
|
|
|
|
.padding(.top, -Values.smallSpacing)
|
|
|
|
|
.padding(.bottom, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
.padding(.top, -Values.smallSpacing)
|
|
|
|
|
.padding(.bottom, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let attachments = messageViewModel.attachments,
|
|
|
|
|
messageViewModel.cellType == .mediaMessage
|
|
|
|
|
{
|
|
|
|
|
let attachment: Attachment = attachments[(index - 1 + attachments.count) % attachments.count]
|
|
|
|
|
|
|
|
|
|
if let attachments = messageViewModel.attachments,
|
|
|
|
|
messageViewModel.cellType == .mediaMessage
|
|
|
|
|
{
|
|
|
|
|
let attachment: Attachment = attachments[(index - 1 + attachments.count) % attachments.count]
|
|
|
|
|
|
|
|
|
|
ZStack(alignment: .bottomTrailing) {
|
|
|
|
|
if attachments.count > 1 {
|
|
|
|
|
// Attachment carousel view
|
|
|
|
|
SessionCarouselView_SwiftUI(
|
|
|
|
|
index: $index,
|
|
|
|
|
isOutgoing: (messageViewModel.variant == .standardOutgoing),
|
|
|
|
|
contentInfos: attachments
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
MediaView_SwiftUI(
|
|
|
|
|
attachment: attachments[0],
|
|
|
|
|
isOutgoing: (messageViewModel.variant == .standardOutgoing),
|
|
|
|
|
cornerRadius: 0
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.aspectRatio(1, contentMode: .fit)
|
|
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 15))
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if [ .downloaded, .uploaded ].contains(attachment.state) {
|
|
|
|
|
Button {
|
|
|
|
|
self.showMediaFullScreen(attachment: attachment)
|
|
|
|
|
} label: {
|
|
|
|
|
ZStack {
|
|
|
|
|
Circle()
|
|
|
|
|
.foregroundColor(.init(white: 0, opacity: 0.4))
|
|
|
|
|
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
|
|
|
|
.font(.system(size: 13))
|
|
|
|
|
.foregroundColor(.white)
|
|
|
|
|
}
|
|
|
|
|
.frame(width: 26, height: 26)
|
|
|
|
|
}
|
|
|
|
|
.padding(.bottom, Values.smallSpacing)
|
|
|
|
|
.padding(.trailing, 38)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
|
|
|
|
|
// Attachment Info
|
|
|
|
|
ZStack {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: Values.mediumSpacing
|
|
|
|
|
) {
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_FILE_ID".localized() + ":") {
|
|
|
|
|
Text(attachment.serverId ?? "")
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HStack(
|
|
|
|
|
alignment: .center
|
|
|
|
|
) {
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_FILE_TYPE".localized() + ":") {
|
|
|
|
|
Text(attachment.contentType)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_FILE_SIZE".localized() + ":") {
|
|
|
|
|
Text(Format.fileSize(attachment.byteCount))
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
}
|
|
|
|
|
HStack(
|
|
|
|
|
alignment: .center
|
|
|
|
|
) {
|
|
|
|
|
let resolution: String = {
|
|
|
|
|
guard let width = attachment.width, let height = attachment.height else { return "N/A" }
|
|
|
|
|
return "\(width)×\(height)"
|
|
|
|
|
}()
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_RESOLUTION".localized() + ":") {
|
|
|
|
|
Text(resolution)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
let duration: String = {
|
|
|
|
|
guard let duration = attachment.duration else { return "N/A" }
|
|
|
|
|
return floor(duration).formatted(format: .videoDuration)
|
|
|
|
|
}()
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_DURATION".localized() + ":") {
|
|
|
|
|
Text(duration)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ZStack(alignment: .bottomTrailing) {
|
|
|
|
|
if attachments.count > 1 {
|
|
|
|
|
// Attachment carousel view
|
|
|
|
|
SessionCarouselView_SwiftUI(
|
|
|
|
|
index: $index,
|
|
|
|
|
isOutgoing: (messageViewModel.variant == .standardOutgoing),
|
|
|
|
|
contentInfos: attachments
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.padding(.all, Values.largeSpacing)
|
|
|
|
|
} else {
|
|
|
|
|
MediaView_SwiftUI(
|
|
|
|
|
attachment: attachments[0],
|
|
|
|
|
isOutgoing: (messageViewModel.variant == .standardOutgoing),
|
|
|
|
|
cornerRadius: 0
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.aspectRatio(1, contentMode: .fit)
|
|
|
|
|
.clipShape(RoundedRectangle(cornerRadius: 15))
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if [ .downloaded, .uploaded ].contains(attachment.state) {
|
|
|
|
|
Button {
|
|
|
|
|
self.showMediaFullScreen(attachment: attachment)
|
|
|
|
|
} label: {
|
|
|
|
|
ZStack {
|
|
|
|
|
Circle()
|
|
|
|
|
.foregroundColor(.init(white: 0, opacity: 0.4))
|
|
|
|
|
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
|
|
|
|
.font(.system(size: 13))
|
|
|
|
|
.foregroundColor(.white)
|
|
|
|
|
}
|
|
|
|
|
.frame(width: 26, height: 26)
|
|
|
|
|
}
|
|
|
|
|
.padding(.bottom, Values.smallSpacing)
|
|
|
|
|
.padding(.trailing, 38)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxHeight: .infinity)
|
|
|
|
|
.background(themeColor: .backgroundSecondary)
|
|
|
|
|
.cornerRadius(Self.cornerRadius)
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Message Info
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
|
|
|
|
|
// Attachment Info
|
|
|
|
|
ZStack {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: Values.mediumSpacing
|
|
|
|
|
) {
|
|
|
|
|
InfoBlock(title: "MESSAGE_INFO_SENT".localized() + ":") {
|
|
|
|
|
Text(messageViewModel.dateForUI.fromattedForMessageInfo)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InfoBlock(title: "MESSAGE_INFO_RECEIVED".localized() + ":") {
|
|
|
|
|
Text(messageViewModel.receivedDateForUI.fromattedForMessageInfo)
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_FILE_ID".localized() + ":") {
|
|
|
|
|
Text(attachment.serverId ?? "")
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isMessageFailed {
|
|
|
|
|
let failureText: String = messageViewModel.mostRecentFailureText ?? "Message failed to send"
|
|
|
|
|
InfoBlock(title: "ALERT_ERROR_TITLE".localized() + ":") {
|
|
|
|
|
Text(failureText)
|
|
|
|
|
HStack(
|
|
|
|
|
alignment: .center
|
|
|
|
|
) {
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_FILE_TYPE".localized() + ":") {
|
|
|
|
|
Text(attachment.contentType)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_FILE_SIZE".localized() + ":") {
|
|
|
|
|
Text(Format.fileSize(attachment.byteCount))
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .danger)
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InfoBlock(title: "MESSAGE_INFO_FROM".localized() + ":") {
|
|
|
|
|
HStack(
|
|
|
|
|
spacing: 10
|
|
|
|
|
) {
|
|
|
|
|
let (info, additionalInfo) = ProfilePictureView.getProfilePictureInfo(
|
|
|
|
|
size: .message,
|
|
|
|
|
publicKey: messageViewModel.authorId,
|
|
|
|
|
threadVariant: .contact, // Always show the display picture in 'contact' mode
|
|
|
|
|
customImageData: nil,
|
|
|
|
|
profile: messageViewModel.profile,
|
|
|
|
|
profileIcon: (messageViewModel.isSenderOpenGroupModerator ? .crown : .none)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let size: ProfilePictureView.Size = .list
|
|
|
|
|
|
|
|
|
|
if let info: ProfilePictureView.Info = info {
|
|
|
|
|
ProfilePictureSwiftUI(
|
|
|
|
|
size: size,
|
|
|
|
|
info: info,
|
|
|
|
|
additionalInfo: additionalInfo
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
width: size.viewSize,
|
|
|
|
|
height: size.viewSize,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: Values.verySmallSpacing
|
|
|
|
|
) {
|
|
|
|
|
if !messageViewModel.authorName.isEmpty {
|
|
|
|
|
Text(messageViewModel.authorName)
|
|
|
|
|
.bold()
|
|
|
|
|
.font(.system(size: Values.mediumLargeFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
Text(messageViewModel.authorId)
|
|
|
|
|
.font(.spaceMono(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
HStack(
|
|
|
|
|
alignment: .center
|
|
|
|
|
) {
|
|
|
|
|
let resolution: String = {
|
|
|
|
|
guard let width = attachment.width, let height = attachment.height else { return "N/A" }
|
|
|
|
|
return "\(width)×\(height)"
|
|
|
|
|
}()
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_RESOLUTION".localized() + ":") {
|
|
|
|
|
Text(resolution)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
let duration: String = {
|
|
|
|
|
guard let duration = attachment.duration else { return "N/A" }
|
|
|
|
|
return floor(duration).formatted(format: .videoDuration)
|
|
|
|
|
}()
|
|
|
|
|
InfoBlock(title: "ATTACHMENT_INFO_DURATION".localized() + ":") {
|
|
|
|
|
Text(duration)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.frame(
|
|
|
|
@ -294,65 +200,152 @@ struct MessageInfoView: View {
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Actions
|
|
|
|
|
if !actions.isEmpty {
|
|
|
|
|
ZStack {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: 0
|
|
|
|
|
// Message Info
|
|
|
|
|
ZStack {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: Values.mediumSpacing
|
|
|
|
|
) {
|
|
|
|
|
InfoBlock(title: "MESSAGE_INFO_SENT".localized() + ":") {
|
|
|
|
|
Text(messageViewModel.dateForUI.fromattedForMessageInfo)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InfoBlock(title: "MESSAGE_INFO_RECEIVED".localized() + ":") {
|
|
|
|
|
Text(messageViewModel.receivedDateForUI.fromattedForMessageInfo)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if isMessageFailed {
|
|
|
|
|
let failureText: String = messageViewModel.mostRecentFailureText ?? "Message failed to send"
|
|
|
|
|
InfoBlock(title: "ALERT_ERROR_TITLE".localized() + ":") {
|
|
|
|
|
Text(failureText)
|
|
|
|
|
.font(.system(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .danger)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InfoBlock(title: "MESSAGE_INFO_FROM".localized() + ":") {
|
|
|
|
|
HStack(
|
|
|
|
|
spacing: 10
|
|
|
|
|
) {
|
|
|
|
|
ForEach(
|
|
|
|
|
0...(actions.count - 1),
|
|
|
|
|
id: \.self
|
|
|
|
|
) { index in
|
|
|
|
|
let tintColor: ThemeValue = actions[index].isDestructive ? .danger : .textPrimary
|
|
|
|
|
Button(
|
|
|
|
|
action: {
|
|
|
|
|
actions[index].work()
|
|
|
|
|
dismiss()
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
HStack(spacing: Values.largeSpacing) {
|
|
|
|
|
Image(uiImage: actions[index].icon!.withRenderingMode(.alwaysTemplate))
|
|
|
|
|
.resizable()
|
|
|
|
|
.scaledToFit()
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
.frame(width: 26, height: 26)
|
|
|
|
|
Text(actions[index].title)
|
|
|
|
|
.bold()
|
|
|
|
|
.font(.system(size: Values.mediumLargeFontSize))
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
|
|
|
}
|
|
|
|
|
let (info, additionalInfo) = ProfilePictureView.getProfilePictureInfo(
|
|
|
|
|
size: .message,
|
|
|
|
|
publicKey: messageViewModel.authorId,
|
|
|
|
|
threadVariant: .contact, // Always show the display picture in 'contact' mode
|
|
|
|
|
customImageData: nil,
|
|
|
|
|
profile: messageViewModel.profile,
|
|
|
|
|
profileIcon: (messageViewModel.isSenderOpenGroupModerator ? .crown : .none)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let size: ProfilePictureView.Size = .list
|
|
|
|
|
|
|
|
|
|
if let info: ProfilePictureView.Info = info {
|
|
|
|
|
ProfilePictureSwiftUI(
|
|
|
|
|
size: size,
|
|
|
|
|
info: info,
|
|
|
|
|
additionalInfo: additionalInfo
|
|
|
|
|
)
|
|
|
|
|
.frame(
|
|
|
|
|
width: size.viewSize,
|
|
|
|
|
height: size.viewSize,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.frame(height: 60)
|
|
|
|
|
|
|
|
|
|
if index < (actions.count - 1) {
|
|
|
|
|
Divider()
|
|
|
|
|
.foregroundColor(themeColor: .borderSeparator)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: Values.verySmallSpacing
|
|
|
|
|
) {
|
|
|
|
|
if !messageViewModel.authorName.isEmpty {
|
|
|
|
|
Text(messageViewModel.authorName)
|
|
|
|
|
.bold()
|
|
|
|
|
.font(.system(size: Values.mediumLargeFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
Text(messageViewModel.authorId)
|
|
|
|
|
.font(.spaceMono(size: Values.mediumFontSize))
|
|
|
|
|
.foregroundColor(themeColor: .textPrimary)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxHeight: .infinity)
|
|
|
|
|
.background(themeColor: .backgroundSecondary)
|
|
|
|
|
.cornerRadius(Self.cornerRadius)
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
}
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.padding(.all, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxHeight: .infinity)
|
|
|
|
|
.background(themeColor: .backgroundSecondary)
|
|
|
|
|
.cornerRadius(Self.cornerRadius)
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
|
|
|
|
|
// Actions
|
|
|
|
|
if !actions.isEmpty {
|
|
|
|
|
ZStack {
|
|
|
|
|
VStack(
|
|
|
|
|
alignment: .leading,
|
|
|
|
|
spacing: 0
|
|
|
|
|
) {
|
|
|
|
|
ForEach(
|
|
|
|
|
0...(actions.count - 1),
|
|
|
|
|
id: \.self
|
|
|
|
|
) { index in
|
|
|
|
|
let tintColor: ThemeValue = actions[index].isDestructive ? .danger : .textPrimary
|
|
|
|
|
Button(
|
|
|
|
|
action: {
|
|
|
|
|
actions[index].work()
|
|
|
|
|
dismiss()
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
HStack(spacing: Values.largeSpacing) {
|
|
|
|
|
Image(uiImage: actions[index].icon!.withRenderingMode(.alwaysTemplate))
|
|
|
|
|
.resizable()
|
|
|
|
|
.scaledToFit()
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
.frame(width: 26, height: 26)
|
|
|
|
|
Text(actions[index].title)
|
|
|
|
|
.bold()
|
|
|
|
|
.font(.system(size: Values.mediumLargeFontSize))
|
|
|
|
|
.foregroundColor(themeColor: tintColor)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
.frame(height: 60)
|
|
|
|
|
|
|
|
|
|
if index < (actions.count - 1) {
|
|
|
|
|
Divider()
|
|
|
|
|
.foregroundColor(themeColor: .borderSeparator)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.frame(
|
|
|
|
|
maxWidth: .infinity,
|
|
|
|
|
maxHeight: .infinity,
|
|
|
|
|
alignment: .topLeading
|
|
|
|
|
)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
.frame(maxHeight: .infinity)
|
|
|
|
|
.background(themeColor: .backgroundSecondary)
|
|
|
|
|
.cornerRadius(Self.cornerRadius)
|
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
|
.padding(.vertical, Values.verySmallSpacing)
|
|
|
|
|
.padding(.horizontal, Values.largeSpacing)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.background(themeColor: .backgroundPrimary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func showMediaFullScreen(attachment: Attachment) {
|
|
|
|
|