diff --git a/ConversationsClassic/AppData/Model/Message.swift b/ConversationsClassic/AppData/Model/Message.swift index cdd8b32..7113acc 100644 --- a/ConversationsClassic/AppData/Model/Message.swift +++ b/ConversationsClassic/AppData/Model/Message.swift @@ -37,6 +37,13 @@ enum MessageContentType: Codable & Equatable, DatabaseValueConvertible { case typing case invite case attachment(Attachment) + + var isAttachment: Bool { + if case .attachment = self { + return true + } + return false + } } enum MessageStatus: Int, Codable, DatabaseValueConvertible { diff --git a/ConversationsClassic/AppData/Store/AttachmentsStore.swift b/ConversationsClassic/AppData/Store/AttachmentsStore.swift index c86f915..1553385 100644 --- a/ConversationsClassic/AppData/Store/AttachmentsStore.swift +++ b/ConversationsClassic/AppData/Store/AttachmentsStore.swift @@ -1,5 +1,6 @@ import Combine import Foundation +import GRDB import Photos import SwiftUI @@ -12,11 +13,13 @@ final class AttachmentsStore: ObservableObject { private let client: Client private let roster: Roster + private var messagesCancellable: AnyCancellable? private var processing: Set = [] init(roster: Roster, client: Client) { self.client = client self.roster = roster + subscribe() } } @@ -191,38 +194,54 @@ extension AttachmentsStore { } } -// MARK: - Uploadings/Downloadings -extension AttachmentsStore { - func processAttachment(_ message: Message) { - // Prevent multiple processing - if processing.contains(message.id) { - return - } - - // Process in background - Task(priority: .background) { - // Do needed processing - if case .attachment(let attachment) = message.contentType { - if attachment.localPath != nil, attachment.remotePath == nil { - // Uploading - processing.insert(message.id) - await uploadAttachment(message) - processing.remove(message.id) - } else if attachment.localPath == nil, attachment.remotePath != nil { - // Downloading - processing.insert(message.id) - await downloadAttachment(message) - processing.remove(message.id) - } else if attachment.localPath != nil, attachment.remotePath != nil, attachment.thumbnailName == nil, attachment.type == .image { - // Generate thumbnail - processing.insert(message.id) - await generateThumbnail(message) - processing.remove(message.id) +// MARK: - Processing attachments +private extension AttachmentsStore { + func subscribe() { + messagesCancellable = ValueObservation.tracking(Message + .filter( + (Column("to") == roster.bareJid && Column("from") == roster.contactBareJid) || + (Column("from") == roster.bareJid && Column("to") == roster.contactBareJid) + ) + .order(Column("date").desc) + .fetchAll + ) + .publisher(in: Database.shared.dbQueue, scheduling: .immediate) + .receive(on: DispatchQueue.main) + .sink { _ in + } receiveValue: { [weak self] messages in + let forProcessing = messages + // .filter { $0.status == .pending } + .filter { self?.processing.contains($0.id) == false } + .filter { $0.contentType.isAttachment } + for message in forProcessing { + if case .attachment(let attachment) = message.contentType { + if attachment.localPath != nil, attachment.remotePath == nil { + // Uploading + self?.processing.insert(message.id) + Task(priority: .background) { + await self?.uploadAttachment(message) + } + } else if attachment.localPath == nil, attachment.remotePath != nil { + // Downloading + self?.processing.insert(message.id) + Task(priority: .background) { + await self?.downloadAttachment(message) + } + } else if attachment.localPath != nil, attachment.remotePath != nil, attachment.thumbnailName == nil, attachment.type == .image { + // Generate thumbnail + self?.processing.insert(message.id) + Task(priority: .background) { + await self?.generateThumbnail(message) + } + } } } } } +} +// MARK: - Uploadings/Downloadings +extension AttachmentsStore { private func uploadAttachment(_ message: Message) async { do { try await message.setStatus(.pending) @@ -246,8 +265,10 @@ extension AttachmentsStore { message.oobUrl = remotePath try await message.save() try await client.sendMessage(message) + processing.remove(message.id) try await message.setStatus(.sent) } catch { + processing.remove(message.id) try? await message.setStatus(.error) } } @@ -276,6 +297,7 @@ extension AttachmentsStore { remotePath: remotePath ) ) + processing.remove(message.id) try await message.save() } catch { logIt(.error, "Can't download attachment: \(error)") @@ -324,6 +346,7 @@ extension AttachmentsStore { remotePath: attachment.remotePath ) ) + processing.remove(message.id) try? await message.save() } } diff --git a/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift b/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift index 3ed2753..b106fa9 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationMessageContainer.swift @@ -150,15 +150,12 @@ private struct AttachmentView: View { ProgressView() .scaleEffect(1.5) .progressViewStyle(CircularProgressViewStyle(tint: .Material.Elements.active)) - let imageName = progressImageName(attachment.type ?? .file) + let imageName = progressImageName(attachment.type) Image(systemName: imageName) .font(.body1) .foregroundColor(.Material.Elements.active) } } - .onAppear { - attachments.processAttachment(message) - } } @ViewBuilder private var failed: some View { @@ -178,7 +175,9 @@ private struct AttachmentView: View { } } .onTapGesture { - attachments.processAttachment(message) + Task { + try? await message.setStatus(.pending) + } } }