wip
This commit is contained in:
parent
e21610d425
commit
f7eee58347
|
@ -2,7 +2,6 @@ enum ConversationAction: Codable {
|
|||
case makeConversationActive(chat: Chat, roster: Roster?)
|
||||
|
||||
case messagesUpdated(messages: [Message])
|
||||
case attachmentsUpdated(attachments: [Attachment])
|
||||
|
||||
case sendMessage(from: String, to: String, body: String)
|
||||
case setReplyText(String)
|
||||
|
|
|
@ -58,12 +58,7 @@ extension Database {
|
|||
table.column("date", .datetime).notNull()
|
||||
table.column("pending", .boolean).notNull()
|
||||
table.column("sentError", .boolean).notNull()
|
||||
}
|
||||
|
||||
// attachments
|
||||
try db.create(table: "attachments", options: [.ifNotExists]) { table in
|
||||
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
|
||||
table.column("type", .integer).notNull()
|
||||
table.column("attachmentType", .integer)
|
||||
table.column("localPath", .text)
|
||||
table.column("remotePath", .text)
|
||||
table.column("localThumbnailPath", .text)
|
||||
|
@ -71,19 +66,6 @@ extension Database {
|
|||
}
|
||||
}
|
||||
|
||||
// 2nd migration - add foreign key constraints
|
||||
migrator.registerMigration("Add foreign keys") { db in
|
||||
// messages to attachments
|
||||
try db.alter(table: "messages") { table in
|
||||
table.add(column: "attachmentId", .text).references("attachments", onDelete: .cascade)
|
||||
}
|
||||
|
||||
// attachments to messsages
|
||||
try db.alter(table: "attachments") { table in
|
||||
table.add(column: "messageId", .text).references("messages", onDelete: .cascade)
|
||||
}
|
||||
}
|
||||
|
||||
// return migrator
|
||||
return migrator
|
||||
}()
|
||||
|
|
|
@ -176,22 +176,6 @@ final class DatabaseMiddleware {
|
|||
try database._db.write { db in
|
||||
try message.insert(db)
|
||||
}
|
||||
if let remoteUrl = message.oobUrl {
|
||||
let attachment = Attachment(
|
||||
id: UUID().uuidString,
|
||||
type: remoteUrl.attachmentType,
|
||||
localPath: nil,
|
||||
remotePath: URL(string: remoteUrl),
|
||||
localThumbnailPath: nil,
|
||||
messageId: message.id
|
||||
)
|
||||
try database._db.write { db in
|
||||
try attachment.insert(db)
|
||||
try Message
|
||||
.filter(Column("id") == message.id)
|
||||
.updateAll(db, [Column("attachmentId").set(to: attachment.id)])
|
||||
}
|
||||
}
|
||||
promise(.success(.empty))
|
||||
} catch {
|
||||
promise(.success(.databaseAction(.storeMessageFailed(reason: error.localizedDescription))))
|
||||
|
@ -289,9 +273,9 @@ final class DatabaseMiddleware {
|
|||
}
|
||||
do {
|
||||
_ = try database._db.write { db in
|
||||
try Attachment
|
||||
try Message
|
||||
.filter(Column("id") == id)
|
||||
.updateAll(db, Column("downloadFailed").set(to: true))
|
||||
.updateAll(db, Column("downloadFailed").set(to: false))
|
||||
}
|
||||
promise(.success(.empty))
|
||||
} catch {
|
||||
|
@ -312,7 +296,7 @@ final class DatabaseMiddleware {
|
|||
}
|
||||
do {
|
||||
_ = try database._db.write { db in
|
||||
try Attachment
|
||||
try Message
|
||||
.filter(Column("id") == id)
|
||||
.updateAll(db, Column("localPath").set(to: localUrl))
|
||||
}
|
||||
|
@ -335,7 +319,7 @@ final class DatabaseMiddleware {
|
|||
}
|
||||
do {
|
||||
_ = try database._db.write { db in
|
||||
try Attachment
|
||||
try Message
|
||||
.filter(Column("id") == id)
|
||||
.updateAll(db, Column("localThumbnailPath").set(to: thumbnailUrl))
|
||||
}
|
||||
|
@ -365,7 +349,6 @@ private extension DatabaseMiddleware {
|
|||
(Column("from") == chat.account && Column("to") == chat.participant)
|
||||
)
|
||||
.order(Column("date").desc)
|
||||
.including(optional: Message.attachment)
|
||||
.fetchAll
|
||||
)
|
||||
.publisher(in: database._db, scheduling: .immediate)
|
||||
|
@ -375,23 +358,6 @@ private extension DatabaseMiddleware {
|
|||
DispatchQueue.main.async {
|
||||
store.dispatch(.conversationAction(.messagesUpdated(messages: messages)))
|
||||
}
|
||||
|
||||
// attachments
|
||||
var attachments: [Attachment] = []
|
||||
for message in messages {
|
||||
do {
|
||||
try self.database._db.read { db in
|
||||
if let attachment = try message.attachment.fetchOne(db) {
|
||||
attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to fetch attachment for message \(message.id): \(error)")
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
store.dispatch(.conversationAction(.attachmentsUpdated(attachments: attachments)))
|
||||
}
|
||||
}
|
||||
.store(in: &conversationCancellables)
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@ final class FileMiddleware {
|
|||
|
||||
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
||||
switch action {
|
||||
case .conversationAction(.attachmentsUpdated(let attachments)):
|
||||
case .conversationAction(.messagesUpdated(let messages)):
|
||||
return Future { promise in
|
||||
for attachment in attachments where attachment.localPath == nil && attachment.remotePath != nil {
|
||||
for message in messages where message.remotePath != nil && message.localPath == nil {
|
||||
DispatchQueue.main.async {
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
store.dispatch(.fileAction(.downloadAttachmentFile(id: attachment.id, remotePath: attachment.remotePath!)))
|
||||
store.dispatch(.fileAction(.downloadAttachmentFile(id: message.id, remotePath: message.remotePath!)))
|
||||
}
|
||||
}
|
||||
promise(.success(.empty))
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
import Foundation
|
||||
import GRDB
|
||||
import Martin
|
||||
import SwiftUI
|
||||
|
||||
enum AttachmentType: Int, Stateable, DatabaseValueConvertible {
|
||||
case movie = 0
|
||||
case image = 1
|
||||
case audio = 2
|
||||
case file = 3
|
||||
}
|
||||
|
||||
struct Attachment: DBStorable {
|
||||
static let databaseTableName = "attachments"
|
||||
|
||||
let id: String
|
||||
let type: AttachmentType
|
||||
let localPath: URL?
|
||||
let remotePath: URL?
|
||||
let localThumbnailPath: URL?
|
||||
let messageId: String
|
||||
var downloadFailed: Bool = false
|
||||
|
||||
static let message = belongsTo(Message.self)
|
||||
var message: QueryInterfaceRequest<Message> {
|
||||
request(for: Attachment.message)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attachment: Equatable {}
|
||||
|
||||
extension String {
|
||||
var attachmentType: AttachmentType {
|
||||
let ext = (self as NSString).pathExtension.lowercased()
|
||||
|
||||
switch ext {
|
||||
case "mov", "mp4", "avi":
|
||||
return .movie
|
||||
|
||||
case "jpg", "png", "gif":
|
||||
return .image
|
||||
|
||||
case "mp3", "wav", "m4a":
|
||||
return .audio
|
||||
|
||||
case "txt", "doc", "pdf":
|
||||
return .file
|
||||
|
||||
default:
|
||||
return .file
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,10 +8,18 @@ enum MessageType: String, Codable, DatabaseValueConvertible {
|
|||
case error
|
||||
}
|
||||
|
||||
enum MessageAttachmentType: Int, Stateable, DatabaseValueConvertible {
|
||||
case movie = 0
|
||||
case image = 1
|
||||
case audio = 2
|
||||
case file = 3
|
||||
}
|
||||
|
||||
enum MessageContentType: String, Codable, DatabaseValueConvertible {
|
||||
case text
|
||||
case typing
|
||||
case invite
|
||||
case attachment
|
||||
}
|
||||
|
||||
struct Message: DBStorable, Equatable {
|
||||
|
@ -33,12 +41,11 @@ struct Message: DBStorable, Equatable {
|
|||
let pending: Bool
|
||||
let sentError: Bool
|
||||
|
||||
static let attachment = hasOne(Attachment.self)
|
||||
var attachment: QueryInterfaceRequest<Attachment> {
|
||||
request(for: Message.attachment)
|
||||
}
|
||||
|
||||
var attachmentId: String?
|
||||
var attachmentType: MessageAttachmentType?
|
||||
var localPath: URL?
|
||||
var remotePath: URL?
|
||||
var localThumbnailPath: URL?
|
||||
var downloadFailed: Bool = false
|
||||
}
|
||||
|
||||
extension Message {
|
||||
|
@ -64,7 +71,9 @@ extension Message {
|
|||
|
||||
// Content type
|
||||
var contentType: MessageContentType = .text
|
||||
if martinMessage.hints.contains(.noStore) {
|
||||
if martinMessage.oob != nil {
|
||||
contentType = .attachment
|
||||
} else if martinMessage.hints.contains(.noStore) {
|
||||
contentType = .typing
|
||||
}
|
||||
|
||||
|
@ -73,7 +82,7 @@ extension Message {
|
|||
let to = martinMessage.to?.bareJid.stringValue
|
||||
|
||||
// Msg
|
||||
let msg = Message(
|
||||
var msg = Message(
|
||||
id: martinMessage.id ?? UUID().uuidString,
|
||||
type: type,
|
||||
contentType: contentType,
|
||||
|
@ -85,8 +94,17 @@ extension Message {
|
|||
oobUrl: martinMessage.oob,
|
||||
date: Date(),
|
||||
pending: false,
|
||||
sentError: false
|
||||
sentError: false,
|
||||
attachmentType: nil,
|
||||
localPath: nil,
|
||||
remotePath: nil,
|
||||
localThumbnailPath: nil,
|
||||
downloadFailed: false
|
||||
)
|
||||
if let oob = martinMessage.oob {
|
||||
msg.attachmentType = oob.attachmentType
|
||||
msg.remotePath = URL(string: oob)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,6 @@ extension ConversationState {
|
|||
state.replyText = text.makeReply
|
||||
}
|
||||
|
||||
case .attachmentsUpdated(let attachments):
|
||||
state.currentAttachments = attachments
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ struct ConversationState: Stateable {
|
|||
var currentChat: Chat?
|
||||
var currentRoster: Roster?
|
||||
var currentMessages: [Message]
|
||||
var currentAttachments: [Attachment]
|
||||
|
||||
var replyText: String
|
||||
}
|
||||
|
@ -11,7 +10,6 @@ struct ConversationState: Stateable {
|
|||
extension ConversationState {
|
||||
init() {
|
||||
currentMessages = []
|
||||
currentAttachments = []
|
||||
replyText = ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,3 +26,26 @@ extension String {
|
|||
return CLLocationCoordinate2D(latitude: lat, longitude: lon)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var attachmentType: MessageAttachmentType {
|
||||
let ext = (self as NSString).pathExtension.lowercased()
|
||||
|
||||
switch ext {
|
||||
case "mov", "mp4", "avi":
|
||||
return .movie
|
||||
|
||||
case "jpg", "png", "gif":
|
||||
return .image
|
||||
|
||||
case "mp3", "wav", "m4a":
|
||||
return .audio
|
||||
|
||||
case "txt", "doc", "pdf":
|
||||
return .file
|
||||
|
||||
default:
|
||||
return .file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ struct ConversationMessageContainer: View {
|
|||
var body: some View {
|
||||
if let msgText = message.body, msgText.isLocation {
|
||||
EmbededMapView(location: msgText.getLatLon)
|
||||
} else if let attachmentId = message.attachmentId {
|
||||
AttachmentView(attachmentId: attachmentId)
|
||||
} else if message.attachmentType != nil {
|
||||
AttachmentView(message: message)
|
||||
} else {
|
||||
Text(message.body ?? "...")
|
||||
.font(.body2)
|
||||
|
@ -67,13 +67,10 @@ private struct EmbededMapView: View {
|
|||
}
|
||||
|
||||
private struct AttachmentView: View {
|
||||
@EnvironmentObject var store: AppStore
|
||||
|
||||
let attachmentId: String
|
||||
let message: Message
|
||||
|
||||
var body: some View {
|
||||
if let attachment {
|
||||
switch attachment.type {
|
||||
switch message.attachmentType {
|
||||
case .image:
|
||||
if let thumbnail = thumbnail() {
|
||||
thumbnail
|
||||
|
@ -85,7 +82,7 @@ private struct AttachmentView: View {
|
|||
}
|
||||
|
||||
case .movie:
|
||||
if let file = attachment.localPath {
|
||||
if let file = message.localPath {
|
||||
VideoPlayerView(url: file)
|
||||
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||
.cornerRadius(Const.attachmentPreviewSize / 10)
|
||||
|
@ -97,9 +94,6 @@ private struct AttachmentView: View {
|
|||
default:
|
||||
placeholder
|
||||
}
|
||||
} else {
|
||||
placeholder
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var placeholder: some View {
|
||||
|
@ -111,21 +105,15 @@ private struct AttachmentView: View {
|
|||
ProgressView()
|
||||
.scaleEffect(1.5)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .Material.Elements.active))
|
||||
if let attachment {
|
||||
let imageName = progressImageName(attachment.type)
|
||||
let imageName = progressImageName(message.attachmentType ?? .file)
|
||||
Image(systemName: imageName)
|
||||
.font(.body1)
|
||||
.foregroundColor(.Material.Elements.active)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var attachment: Attachment? {
|
||||
store.state.conversationsState.currentAttachments.first(where: { $0.id == attachmentId })
|
||||
}
|
||||
|
||||
private func progressImageName(_ type: AttachmentType) -> String {
|
||||
private func progressImageName(_ type: MessageAttachmentType) -> String {
|
||||
switch type {
|
||||
case .image:
|
||||
return "photo"
|
||||
|
@ -139,8 +127,7 @@ private struct AttachmentView: View {
|
|||
}
|
||||
|
||||
private func thumbnail() -> Image? {
|
||||
guard let attachment = attachment else { return nil }
|
||||
guard let thumbnailPath = attachment.localThumbnailPath else { return nil }
|
||||
guard let thumbnailPath = message.localThumbnailPath else { return nil }
|
||||
guard let uiImage = UIImage(contentsOfFile: thumbnailPath.path()) else { return nil }
|
||||
return Image(uiImage: uiImage)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue