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)
|
2024-07-13 13:38:15 +00:00
|
|
|
} else if message.attachmentType != nil {
|
|
|
|
AttachmentView(message: message)
|
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.sentError {
|
|
|
|
Image(systemName: "exclamationmark.circle")
|
|
|
|
.font(.body3)
|
2024-07-04 08:21:12 +00:00
|
|
|
.foregroundColor(.Rainbow.red500)
|
2024-06-27 11:39:41 +00:00
|
|
|
} else if message.pending {
|
|
|
|
Image(systemName: "clock")
|
|
|
|
.font(.body3)
|
2024-07-04 08:21:12 +00:00
|
|
|
.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)
|
|
|
|
.foregroundColor(.red)
|
|
|
|
Text(message.body?.getContactJid.firstLetter ?? "?")
|
|
|
|
.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-07-11 15:14:31 +00:00
|
|
|
private struct AttachmentView: View {
|
2024-07-13 13:38:15 +00:00
|
|
|
let message: Message
|
2024-07-11 15:14:31 +00:00
|
|
|
|
|
|
|
var body: some View {
|
2024-07-16 12:36:57 +00:00
|
|
|
if message.attachmentDownloadFailed || (message.attachmentLocalName != nil && message.sentError) {
|
2024-07-13 14:23:03 +00:00
|
|
|
failed
|
|
|
|
} else {
|
|
|
|
switch message.attachmentType {
|
|
|
|
case .image:
|
|
|
|
if let thumbnail = thumbnail() {
|
|
|
|
thumbnail
|
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
|
|
|
} else {
|
|
|
|
placeholder
|
|
|
|
}
|
2024-07-12 11:43:14 +00:00
|
|
|
|
2024-07-13 14:23:03 +00:00
|
|
|
case .movie:
|
|
|
|
if let file = message.attachmentLocalPath {
|
|
|
|
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 = message.attachmentLocalPath {
|
|
|
|
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))
|
2024-07-13 13:38:15 +00:00
|
|
|
let imageName = progressImageName(message.attachmentType ?? .file)
|
|
|
|
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 {
|
2024-07-14 08:52:15 +00:00
|
|
|
if let url = message.attachmentRemotePath {
|
2024-07-14 10:08:51 +00:00
|
|
|
store.dispatch(.fileAction(.downloadAttachmentFile(messageId: message.id, attachmentRemotePath: url)))
|
2024-07-16 12:36:57 +00:00
|
|
|
} else if message.attachmentLocalName != nil && message.sentError {
|
|
|
|
store.dispatch(.sharingAction(.retrySharing(messageId: message.id)))
|
2024-07-14 08:52:15 +00:00
|
|
|
}
|
2024-07-12 11:43:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-13 13:38:15 +00:00
|
|
|
private func progressImageName(_ type: MessageAttachmentType) -> String {
|
2024-07-11 15:46:57 +00:00
|
|
|
switch type {
|
|
|
|
case .image:
|
|
|
|
return "photo"
|
|
|
|
case .audio:
|
|
|
|
return "music.note"
|
|
|
|
case .movie:
|
|
|
|
return "film"
|
|
|
|
case .file:
|
|
|
|
return "doc"
|
|
|
|
}
|
|
|
|
}
|
2024-07-12 11:43:14 +00:00
|
|
|
|
|
|
|
private func thumbnail() -> Image? {
|
2024-07-13 13:42:47 +00:00
|
|
|
guard let thumbnailPath = message.attachmentThumbnailPath 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|