another.im-ios/AnotherIM/View/Main/Conversation/ConversationMessageContainer.swift

261 lines
8.2 KiB
Swift
Raw Normal View History

2024-07-12 11:43:14 +00:00
import AVKit
2024-07-11 13:59:24 +00:00
import MapKit
2024-07-16 18:05:15 +00:00
import QuickLook
2024-06-27 11:39:41 +00:00
import SwiftUI
struct ConversationMessageContainer: View {
let message: Message
let isOutgoing: Bool
var body: some View {
2024-07-11 15:14:31 +00:00
if let msgText = message.body, msgText.isLocation {
EmbededMapView(location: msgText.getLatLon)
2024-07-16 18:14:59 +00:00
} else if let msgText = message.body, msgText.isContact {
ContactView(message: message)
} else if case .attachment(let attachment) = message.contentType {
AttachmentView(message: message, attachment: attachment)
2024-07-11 13:59:24 +00:00
} else {
2024-07-11 15:14:31 +00:00
Text(message.body ?? "...")
2024-07-11 13:59:24 +00:00
.font(.body2)
.foregroundColor(.Material.Text.main)
.multilineTextAlignment(.leading)
.padding(10)
}
2024-06-27 11:39:41 +00:00
}
}
struct MessageAttr: View {
let message: Message
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(message.date, style: .time)
.font(.sub2)
2024-07-04 08:21:12 +00:00
.foregroundColor(.Material.Shape.separator)
2024-06-27 11:39:41 +00:00
Spacer()
if message.status == .error {
2024-06-27 11:39:41 +00:00
Image(systemName: "exclamationmark.circle")
.font(.body3)
2024-07-04 08:21:12 +00:00
.foregroundColor(.Rainbow.red500)
} else if message.status == .pending {
2024-06-27 11:39:41 +00:00
Image(systemName: "clock")
.font(.body3)
2024-07-04 08:21:12 +00:00
.foregroundColor(.Material.Shape.separator)
2024-09-11 12:37:54 +00:00
} else if message.secure {
Image(systemName: "lock")
.font(.body3)
.foregroundColor(.Material.Shape.separator)
2024-06-27 11:39:41 +00:00
}
}
}
}
2024-07-11 13:59:24 +00:00
private struct EmbededMapView: View {
let location: CLLocationCoordinate2D
var body: some View {
Map(
coordinateRegion: .constant(MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))),
interactionModes: [],
showsUserLocation: false,
userTrackingMode: .none,
annotationItems: [location],
annotationContent: { _ in
MapMarker(coordinate: location, tint: .blue)
}
)
.frame(width: Const.mapPreviewSize, height: Const.mapPreviewSize)
.onTapGesture {
let mapItem = MKMapItem(placemark: MKPlacemark(coordinate: location))
mapItem.name = "Location"
mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving])
}
}
}
2024-07-11 15:14:31 +00:00
2024-07-16 18:14:59 +00:00
private struct ContactView: View {
let message: Message
var body: some View {
VStack {
ZStack {
Circle()
.frame(width: 44, height: 44)
2024-08-07 19:07:39 +00:00
.foregroundColor(contactName.firstLetterColor)
Text(contactName.firstLetter)
2024-07-16 18:14:59 +00:00
.foregroundColor(.white)
.font(.body1)
}
Text(message.body?.getContactJid ?? "...")
.font(.body2)
.foregroundColor(.Material.Text.main)
.multilineTextAlignment(.leading)
}
.padding()
.onTapGesture {
// TODO: Jump to add roster from here
}
}
2024-08-07 19:07:39 +00:00
private var contactName: String {
message.body?.getContactJid ?? "?"
}
2024-07-16 18:14:59 +00:00
}
2024-07-11 15:14:31 +00:00
private struct AttachmentView: View {
@EnvironmentObject var attachments: AttachmentsStore
2024-07-13 13:38:15 +00:00
let message: Message
let attachment: Attachment
2024-07-11 15:14:31 +00:00
var body: some View {
if message.status == .error {
2024-07-13 14:23:03 +00:00
failed
} else {
switch attachment.type {
2024-07-13 14:23:03 +00:00
case .image:
AsyncImage(url: attachment.thumbnailPath) { image in
image
2024-07-13 14:23:03 +00:00
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
} placeholder: {
2024-07-13 14:23:03 +00:00
placeholder
}
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
2024-07-12 11:43:14 +00:00
case .video:
if let file = attachment.localPath {
2024-07-13 14:23:03 +00:00
VideoPlayerView(url: file)
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
} else {
placeholder
}
2024-07-16 18:05:15 +00:00
case .file:
if let file = attachment.localPath {
2024-07-16 18:05:15 +00:00
DocumentPreview(url: file)
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
} else {
placeholder
}
2024-07-13 14:23:03 +00:00
default:
2024-07-12 11:43:14 +00:00
placeholder
2024-07-11 15:46:57 +00:00
}
}
}
2024-07-12 11:43:14 +00:00
@ViewBuilder private var placeholder: some View {
Rectangle()
.foregroundColor(.Material.Background.dark)
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
.overlay {
ZStack {
ProgressView()
.scaleEffect(1.5)
.progressViewStyle(CircularProgressViewStyle(tint: .Material.Elements.active))
let imageName = progressImageName(attachment.type)
2024-07-13 13:38:15 +00:00
Image(systemName: imageName)
.font(.body1)
.foregroundColor(.Material.Elements.active)
2024-07-12 11:43:14 +00:00
}
2024-07-13 14:23:03 +00:00
}
}
@ViewBuilder private var failed: some View {
Rectangle()
.foregroundColor(.Material.Background.dark)
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
.overlay {
ZStack {
2024-07-13 14:41:56 +00:00
VStack {
Text(L10n.Attachment.Downloading.retry)
.font(.body3)
.foregroundColor(.Rainbow.red500)
Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")
.font(.body1)
.foregroundColor(.Rainbow.red500)
}
2024-07-13 14:23:03 +00:00
}
2024-07-13 14:41:56 +00:00
}
.onTapGesture {
Task {
try? await message.setStatus(.pending)
2024-07-14 08:52:15 +00:00
}
2024-07-12 11:43:14 +00:00
}
}
private func progressImageName(_ type: AttachmentType) -> String {
2024-07-11 15:46:57 +00:00
switch type {
case .image:
return "photo"
2024-09-11 12:37:54 +00:00
2024-07-11 15:46:57 +00:00
case .audio:
return "music.note"
2024-09-11 12:37:54 +00:00
case .video:
2024-07-11 15:46:57 +00:00
return "film"
2024-09-11 12:37:54 +00:00
2024-07-11 15:46:57 +00:00
case .file:
return "doc"
}
}
2024-07-12 11:43:14 +00:00
private func thumbnail() -> Image? {
guard let thumbnailPath = attachment.thumbnailPath else { return nil }
2024-07-12 11:43:14 +00:00
guard let uiImage = UIImage(contentsOfFile: thumbnailPath.path()) else { return nil }
return Image(uiImage: uiImage)
}
2024-07-11 15:46:57 +00:00
}
2024-07-14 10:08:51 +00:00
// TODO: Make video player better!
2024-07-12 11:43:14 +00:00
private struct VideoPlayerView: UIViewControllerRepresentable {
let url: URL
2024-07-11 15:46:57 +00:00
2024-07-12 11:43:14 +00:00
func makeUIViewController(context _: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = AVPlayer(url: url)
2024-07-14 10:08:51 +00:00
controller.allowsPictureInPicturePlayback = true
2024-07-12 11:43:14 +00:00
return controller
}
func updateUIViewController(_: AVPlayerViewController, context _: Context) {
// Update the controller if needed.
2024-07-11 15:14:31 +00:00
}
}
2024-07-16 18:05:15 +00:00
struct DocumentPreview: UIViewControllerRepresentable {
var url: URL
func makeUIViewController(context: Context) -> QLPreviewController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
return controller
}
func updateUIViewController(_: QLPreviewController, context _: Context) {
// Update the controller if needed.
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
var parent: DocumentPreview
init(_ parent: DocumentPreview) {
self.parent = parent
}
func numberOfPreviewItems(in _: QLPreviewController) -> Int {
1
}
func previewController(_: QLPreviewController, previewItemAt _: Int) -> QLPreviewItem {
parent.url as QLPreviewItem
}
}
}