import AVKit import MapKit import SwiftUI struct ConversationMessageContainer: View { let message: Message let isOutgoing: Bool var body: some View { if let msgText = message.body, msgText.isLocation { EmbededMapView(location: msgText.getLatLon) } else if message.attachmentType != nil { AttachmentView(message: message) } else { Text(message.body ?? "...") .font(.body2) .foregroundColor(.Material.Text.main) .multilineTextAlignment(.leading) .padding(10) } } } struct MessageAttr: View { let message: Message var body: some View { VStack(alignment: .leading, spacing: 0) { Text(message.date, style: .time) .font(.sub2) .foregroundColor(.Material.Shape.separator) Spacer() if message.sentError { Image(systemName: "exclamationmark.circle") .font(.body3) .foregroundColor(.Rainbow.red500) } else if message.pending { Image(systemName: "clock") .font(.body3) .foregroundColor(.Material.Shape.separator) } } } } 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]) } } } private struct AttachmentView: View { let message: Message var body: some View { if message.attachmentDownloadFailed { 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 } case .movie: if let file = message.attachmentLocalPath { VideoPlayerView(url: file) .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize) } else { placeholder } default: placeholder } } } @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(message.attachmentType ?? .file) Image(systemName: imageName) .font(.body1) .foregroundColor(.Material.Elements.active) } } } @ViewBuilder private var failed: some View { Rectangle() .foregroundColor(.Material.Background.dark) .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize) .overlay { ZStack { VStack { Text(L10n.Attachment.Downloading.retry) .font(.body3) .foregroundColor(.Rainbow.red500) Image(systemName: "exclamationmark.arrow.triangle.2.circlepath") .font(.body1) .foregroundColor(.Rainbow.red500) } } } .onTapGesture { if let url = message.attachmentRemotePath { store.dispatch(.fileAction(.downloadAttachmentFile(messageId: message.id, attachmentRemotePath: url))) } } } private func progressImageName(_ type: MessageAttachmentType) -> String { switch type { case .image: return "photo" case .audio: return "music.note" case .movie: return "film" case .file: return "doc" } } private func thumbnail() -> Image? { guard let thumbnailPath = message.attachmentThumbnailPath else { return nil } guard let uiImage = UIImage(contentsOfFile: thumbnailPath.path()) else { return nil } return Image(uiImage: uiImage) } } // TODO: Make video player better! private struct VideoPlayerView: UIViewControllerRepresentable { let url: URL func makeUIViewController(context _: Context) -> AVPlayerViewController { let controller = AVPlayerViewController() controller.player = AVPlayer(url: url) controller.allowsPictureInPicturePlayback = true return controller } func updateUIViewController(_: AVPlayerViewController, context _: Context) { // Update the controller if needed. } }