diff --git a/ConversationsClassic/AppData/Model/Message.swift b/ConversationsClassic/AppData/Model/Message.swift index 6254351..cdd8b32 100644 --- a/ConversationsClassic/AppData/Model/Message.swift +++ b/ConversationsClassic/AppData/Model/Message.swift @@ -49,17 +49,17 @@ struct Message: DBStorable, Equatable { static let databaseTableName = "messages" let id: String - let type: MessageType + var type: MessageType let date: Date var contentType: MessageContentType var status: MessageStatus - let from: String - let to: String? + var from: String + var to: String? var body: String? - let subject: String? - let thread: String? + var subject: String? + var thread: String? var oobUrl: String? } @@ -77,4 +77,20 @@ extension Message { try updatedMessage.update(db, columns: ["status"]) } } + + static var blank: Message { + Message( + id: UUID().uuidString, + type: .chat, + date: Date(), + contentType: .text, + status: .pending, + from: "", + to: nil, + body: nil, + subject: nil, + thread: nil, + oobUrl: nil + ) + } } diff --git a/ConversationsClassic/AppData/Store/ConversationStore.swift b/ConversationsClassic/AppData/Store/ConversationStore.swift index b48c421..b67d126 100644 --- a/ConversationsClassic/AppData/Store/ConversationStore.swift +++ b/ConversationsClassic/AppData/Store/ConversationStore.swift @@ -11,8 +11,6 @@ final class ConversationStore: ObservableObject { private(set) var roster: Roster private let client: Client - private let blockSize = Const.messagesPageSize - private let messagesMax = Const.messagesMaxSize private var messagesCancellable: AnyCancellable? @@ -25,28 +23,18 @@ final class ConversationStore: ObservableObject { extension ConversationStore { func sendMessage(_ message: String) async { - // prepare message - let message = Message( - id: UUID().uuidString, - type: .chat, - date: Date(), - contentType: .text, - status: .pending, - from: roster.bareJid, - to: roster.contactBareJid, - body: message, - subject: nil, - thread: nil, - oobUrl: nil - ) + var msg = Message.blank + msg.from = roster.bareJid + msg.to = roster.contactBareJid + msg.body = message // store as pending on db, and send do { - try await message.save() - try await client.sendMessage(message) - try await message.setStatus(.sent) + try await msg.save() + try await client.sendMessage(msg) + try await msg.setStatus(.sent) } catch { - try? await message.setStatus(.error) + try? await msg.setStatus(.error) } } } @@ -67,7 +55,10 @@ extension ConversationStore { func sendCaptured(_ data: Data, _ type: GalleryMediaType) async { // save locally and make message - let messageId = UUID().uuidString + var message = Message.blank + message.from = roster.bareJid + message.to = roster.contactBareJid + let localName: String let msgType: AttachmentType do { @@ -78,11 +69,11 @@ extension ConversationStore { let msgType: AttachmentType switch type { case .photo: - localName = "\(messageId)_\(fileId).jpg" + localName = "\(message.id)_\(fileId).jpg" msgType = .image case .video: - localName = "\(messageId)_\(fileId).mov" + localName = "\(message.id)_\(fileId).mov" msgType = .video } @@ -97,25 +88,13 @@ extension ConversationStore { } // save message - let message = Message( - id: UUID().uuidString, - type: .chat, - date: Date(), - contentType: .attachment( - Attachment( - type: msgType, - localName: localName, - thumbnailName: nil, - remotePath: nil - ) - ), - status: .pending, - from: roster.bareJid, - to: roster.contactBareJid, - body: nil, - subject: nil, - thread: nil, - oobUrl: nil + message.contentType = .attachment( + Attachment( + type: msgType, + localName: localName, + thumbnailName: nil, + remotePath: nil + ) ) do { try await message.save() diff --git a/ConversationsClassic/Helpers/Const.swift b/ConversationsClassic/Helpers/Const.swift index c627201..67fef11 100644 --- a/ConversationsClassic/Helpers/Const.swift +++ b/ConversationsClassic/Helpers/Const.swift @@ -2,15 +2,6 @@ import Foundation import UIKit enum Const { - // // Network - // #if DEBUG - // static let baseUrl = "staging.some.com/api" - // #else - // static let baseUrl = "prod.some.com/api" - // #endif - // static let requestTimeout = 15.0 - // static let networkLogging = true - // App static var appVersion: String { let info = Bundle.main.infoDictionary @@ -54,8 +45,4 @@ enum Const { // Lenght in days for MAM request static let mamRequestDaysLength = 30 - - // Limits for messages pagination - static let messagesPageSize = 20 // size for block requesting - static let messagesMaxSize = 100 // total messages in memory } diff --git a/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift b/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift index 0590243..b0ba4a0 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift @@ -12,8 +12,8 @@ struct ConversationMessageContainer: View { EmbededMapView(location: msgText.getLatLon) } else if let msgText = message.body, msgText.isContact { ContactView(message: message) - // } else if message.attachmentType != nil { - // AttachmentView(message: message) + } else if case .attachment(let attachment) = message.contentType { + AttachmentView(message: message, attachment: attachment) } else { Text(message.body ?? "...") .font(.body2) @@ -98,107 +98,108 @@ private struct ContactView: View { } } -// private struct AttachmentView: View { -// let message: Message -// -// var body: some View { -// if message.attachmentDownloadFailed || (message.attachmentLocalName != nil && message.sentError) { -// 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 -// } -// -// case .file: -// if let file = message.attachmentLocalPath { -// DocumentPreview(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))) -// } else if message.attachmentLocalName != nil && message.sentError { -// store.dispatch(.sharingAction(.retrySharing(messageId: message.id))) -// } -// } -// } -// -// 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) -// } -// } +private struct AttachmentView: View { + let message: Message + let attachment: Attachment + + var body: some View { + if message.status == .error { + failed + } else { + switch attachment.type { + case .image: + if let thumbnail = thumbnail() { + thumbnail + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize) + } else { + placeholder + } + + case .video: + if let file = attachment.localPath { + VideoPlayerView(url: file) + .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize) + } else { + placeholder + } + + case .file: + if let file = attachment.localPath { + DocumentPreview(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(attachment.type ?? .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))) + // } else if message.attachmentLocalName != nil && message.sentError { + // store.dispatch(.sharingAction(.retrySharing(messageId: message.id))) + // } + } + } + + private func progressImageName(_ type: AttachmentType) -> String { + switch type { + case .image: + return "photo" + case .audio: + return "music.note" + case .video: + return "film" + case .file: + return "doc" + } + } + + private func thumbnail() -> Image? { + guard let thumbnailPath = attachment.thumbnailPath 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 { diff --git a/ConversationsClassic/View/Main/Conversation/ConversationMessageRow.swift b/ConversationsClassic/View/Main/Conversation/ConversationMessageRow.swift index 1a5291b..5047eaa 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationMessageRow.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationMessageRow.swift @@ -50,7 +50,7 @@ struct ConversationMessageRow: View { } if value.translation.width <= targetWidth { DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) { - // store.dispatch(.conversationAction(.setReplyText(message.body ?? ""))) + conversation.replyText = message.body ?? "" } } }