wip
This commit is contained in:
parent
32086a28e8
commit
f89831f82d
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -121,3 +121,4 @@ xcuserdata
|
|||
/buildServer.json
|
||||
TODO.txt
|
||||
PASSWD.txt
|
||||
sandbox.xml
|
||||
|
|
|
@ -44,22 +44,6 @@ extension Database {
|
|||
table.column("type", .integer).notNull()
|
||||
}
|
||||
|
||||
// attachments
|
||||
try db.create(table: "attachments", options: [.ifNotExists]) { table in
|
||||
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
|
||||
}
|
||||
|
||||
// attachment items
|
||||
try db.create(table: "attachment_items", options: [.ifNotExists]) { table in
|
||||
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
|
||||
table.belongsTo("attachments", onDelete: .cascade).notNull()
|
||||
table.column("type", .integer).notNull()
|
||||
table.column("localPath", .text)
|
||||
table.column("remotePath", .text)
|
||||
table.column("localThumbnailPath", .text)
|
||||
table.column("string", .text)
|
||||
}
|
||||
|
||||
// messages
|
||||
try db.create(table: "messages", options: [.ifNotExists]) { table in
|
||||
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
|
||||
|
@ -74,7 +58,28 @@ extension Database {
|
|||
table.column("date", .datetime).notNull()
|
||||
table.column("pending", .boolean).notNull()
|
||||
table.column("sentError", .boolean).notNull()
|
||||
table.column("attachment", .text).references("attachments", onDelete: .cascade)
|
||||
}
|
||||
|
||||
// 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("localPath", .text)
|
||||
table.column("remotePath", .text)
|
||||
table.column("localThumbnailPath", .text)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -175,6 +175,22 @@ 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))))
|
||||
|
@ -279,6 +295,7 @@ 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)
|
||||
|
@ -291,3 +308,15 @@ private extension DatabaseMiddleware {
|
|||
.store(in: &conversationCancellables)
|
||||
}
|
||||
}
|
||||
|
||||
// try db.write { db in
|
||||
// // Update the attachment
|
||||
// var attachment = try Attachment.fetchOne(db, key: attachmentId)!
|
||||
// attachment.someField = newValue
|
||||
// try attachment.update(db)
|
||||
//
|
||||
// // Update the message
|
||||
// var message = try Message.fetchOne(db, key: messageId)!
|
||||
// message.someField = newValue
|
||||
// try message.update(db)
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
import Combine
|
||||
|
||||
final class FileMiddleware {
|
||||
static let shared = AccountsMiddleware()
|
||||
|
||||
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
||||
switch action {
|
||||
case .conversationAction(.messagesUpdated(let messages)):
|
||||
for msg in messages {
|
||||
if msg.attachment != nil {
|
||||
print("Attachment found")
|
||||
}
|
||||
}
|
||||
return Empty().eraseToAnyPublisher()
|
||||
|
||||
default:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,29 +8,41 @@ enum AttachmentType: Int, Stateable, DatabaseValueConvertible {
|
|||
case image = 1
|
||||
case audio = 2
|
||||
case file = 3
|
||||
case location = 4
|
||||
case contact = 5
|
||||
}
|
||||
|
||||
struct AttachmentItem: DBStorable {
|
||||
static let databaseTableName = "attachment_items"
|
||||
|
||||
let id: String
|
||||
static let attachment = belongsTo(Attachment.self)
|
||||
let type: AttachmentType
|
||||
let localPath: URL?
|
||||
let remotePath: URL?
|
||||
let localThumbnailPath: URL?
|
||||
let string: String?
|
||||
}
|
||||
|
||||
struct Attachment: DBStorable {
|
||||
static let databaseTableName = "attachments"
|
||||
|
||||
let id: String
|
||||
static let items = hasMany(AttachmentItem.self)
|
||||
static let message = hasOne(Message.self)
|
||||
let type: AttachmentType
|
||||
let localPath: URL?
|
||||
let remotePath: URL?
|
||||
let localThumbnailPath: URL?
|
||||
let messageId: String
|
||||
|
||||
static let message = belongsTo(Message.self)
|
||||
var message: QueryInterfaceRequest<Message> {
|
||||
request(for: Attachment.message)
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentItem: Equatable {}
|
||||
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 // Default to .file if the extension is not recognized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,11 @@ struct Message: DBStorable, Equatable {
|
|||
let sentError: Bool
|
||||
|
||||
static let attachment = hasOne(Attachment.self)
|
||||
var attachment: QueryInterfaceRequest<Attachment> {
|
||||
request(for: Message.attachment)
|
||||
}
|
||||
|
||||
var attachmentId: String?
|
||||
}
|
||||
|
||||
extension Message {
|
||||
|
|
|
@ -14,7 +14,8 @@ let store = AppStore(
|
|||
RostersMiddleware.shared.middleware,
|
||||
ChatsMiddleware.shared.middleware,
|
||||
ConversationMiddleware.shared.middleware,
|
||||
SharingMiddleware.shared.middleware
|
||||
SharingMiddleware.shared.middleware,
|
||||
FileMiddleware.shared.middleware
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -37,4 +37,7 @@ enum Const {
|
|||
|
||||
// Grid size for gallery preview (3 in a row)
|
||||
static let galleryGridSize = UIScreen.main.bounds.width / 3
|
||||
|
||||
// Size for map preview for location messages
|
||||
static let mapPreviewSize = UIScreen.main.bounds.width * 0.75
|
||||
}
|
||||
|
|
16
ConversationsClassic/Helpers/Map+Extensions.swift
Normal file
16
ConversationsClassic/Helpers/Map+Extensions.swift
Normal file
|
@ -0,0 +1,16 @@
|
|||
import MapKit
|
||||
|
||||
extension MKCoordinateRegion: Equatable {
|
||||
public static func == (lhs: MKCoordinateRegion, rhs: MKCoordinateRegion) -> Bool {
|
||||
lhs.center.latitude == rhs.center.latitude &&
|
||||
lhs.center.longitude == rhs.center.longitude &&
|
||||
lhs.span.latitudeDelta == rhs.span.latitudeDelta &&
|
||||
lhs.span.longitudeDelta == rhs.span.longitudeDelta
|
||||
}
|
||||
}
|
||||
|
||||
extension CLLocationCoordinate2D: Identifiable {
|
||||
public var id: String {
|
||||
"\(latitude)-\(longitude)"
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
@ -12,4 +13,16 @@ extension String {
|
|||
result = "> \(result)"
|
||||
return result
|
||||
}
|
||||
|
||||
var isLocation: Bool {
|
||||
hasPrefix("geo:")
|
||||
}
|
||||
|
||||
var getLatLon: CLLocationCoordinate2D {
|
||||
let geo = components(separatedBy: ":")[1]
|
||||
let parts = geo.components(separatedBy: ",")
|
||||
let lat = Double(parts[0]) ?? 0.0
|
||||
let lon = Double(parts[1]) ?? 0.0
|
||||
return CLLocationCoordinate2D(latitude: lat, longitude: lon)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,12 +132,3 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MKCoordinateRegion: Equatable {
|
||||
public static func == (lhs: MKCoordinateRegion, rhs: MKCoordinateRegion) -> Bool {
|
||||
lhs.center.latitude == rhs.center.latitude &&
|
||||
lhs.center.longitude == rhs.center.longitude &&
|
||||
lhs.span.latitudeDelta == rhs.span.latitudeDelta &&
|
||||
lhs.span.longitudeDelta == rhs.span.longitudeDelta
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
struct ConversationMessageContainer: View {
|
||||
|
@ -5,11 +6,23 @@ struct ConversationMessageContainer: View {
|
|||
let isOutgoing: Bool
|
||||
|
||||
var body: some View {
|
||||
Text(message.body ?? "...")
|
||||
.font(.body2)
|
||||
.foregroundColor(.Material.Text.main)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(10)
|
||||
if let msgText = message.body {
|
||||
if msgText.isLocation {
|
||||
EmbededMapView(location: msgText.getLatLon)
|
||||
} else {
|
||||
Text(message.body ?? "...")
|
||||
.font(.body2)
|
||||
.foregroundColor(.Material.Text.main)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(10)
|
||||
}
|
||||
} else {
|
||||
Text("...")
|
||||
.font(.body2)
|
||||
.foregroundColor(.Material.Text.main)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,3 +47,27 @@ struct MessageAttr: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.cornerRadius(10)
|
||||
.onTapGesture {
|
||||
let mapItem = MKMapItem(placemark: MKPlacemark(coordinate: location))
|
||||
mapItem.name = "Location"
|
||||
mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue