Add AttachmentView

This commit is contained in:
Djorkaeff Alexandre 2024-01-18 19:49:49 -03:00
parent 9668aed381
commit 2fdf0e2c3b
10 changed files with 144 additions and 28 deletions

View File

@ -1,5 +1,7 @@
import Foundation import Foundation
let HISTORY_MESSAGE_COUNT = 50
struct HistoryRequest: Request { struct HistoryRequest: Request {
typealias Response = HistoryResponse typealias Response = HistoryResponse
@ -11,6 +13,7 @@ struct HistoryRequest: Request {
queryItems = [ queryItems = [
URLQueryItem(name: "roomId", value: roomId), URLQueryItem(name: "roomId", value: roomId),
URLQueryItem(name: "count", value: String(HISTORY_MESSAGE_COUNT)),
URLQueryItem(name: "latest", value: latest.iso8601withFractionalSeconds) URLQueryItem(name: "latest", value: latest.iso8601withFractionalSeconds)
] ]
} }

View File

@ -0,0 +1,7 @@
import CoreData
extension Attachment {
var aspectRatio: Double {
return width / height
}
}

View File

@ -3,10 +3,12 @@ import Foundation
final class MessageFormatter { final class MessageFormatter {
private let message: Message private let message: Message
private let previousMessage: Message? private let previousMessage: Message?
private let lastOpen: Date?
init(message: Message, previousMessage: Message?) { init(message: Message, previousMessage: Message?, lastOpen: Date?) {
self.message = message self.message = message
self.previousMessage = previousMessage self.previousMessage = previousMessage
self.lastOpen = lastOpen
} }
func hasDateSeparator() -> Bool { func hasDateSeparator() -> Bool {
@ -19,6 +21,20 @@ final class MessageFormatter {
return true return true
} }
func hasUnreadSeparator() -> Bool {
guard let messageTS = message.ts, let lastOpen else {
return false
}
if previousMessage == nil {
return messageTS > lastOpen
} else if let previousMessage, let previousMessageTS = previousMessage.ts {
return messageTS >= lastOpen && previousMessageTS < lastOpen
} else {
return false
}
}
func isHeader() -> Bool { func isHeader() -> Bool {
if let previousMessage, if let previousMessage,
let previousMessageTS = previousMessage.ts, let previousMessageTS = previousMessage.ts,

View File

@ -74,8 +74,10 @@ final class MessagesLoader {
} receiveValue: { [weak self] messagesResponse in } receiveValue: { [weak self] messagesResponse in
let messages = messagesResponse.messages let messages = messagesResponse.messages
if let lastMessage = messages.last, self?.database.message(id: lastMessage._id) == nil, messages.count == 20 { if messages.count == HISTORY_MESSAGE_COUNT {
room.hasMoreMessages = true room.hasMoreMessages = true
} else {
room.hasMoreMessages = false
} }
for message in messages { for message in messages {

View File

@ -19,6 +19,9 @@
}, },
"Try Again" : { "Try Again" : {
},
"Unread messages" : {
} }
}, },
"version" : "1.0" "version" : "1.0"

View File

@ -7,10 +7,14 @@ final class MessageViewModel: ObservableObject {
let messageFormatter: MessageFormatter let messageFormatter: MessageFormatter
init(message: Message, previousMessage: Message? = nil, server: Server?) { init(message: Message, previousMessage: Message? = nil, server: Server?, lastOpen: Date?) {
self.message = message self.message = message
self.previousMessage = previousMessage self.previousMessage = previousMessage
self.messageFormatter = MessageFormatter(message: message, previousMessage: previousMessage) self.messageFormatter = MessageFormatter(
message: message,
previousMessage: previousMessage,
lastOpen: lastOpen
)
self.server = server self.server = server
} }

View File

@ -0,0 +1,36 @@
import SwiftUI
struct AttachmentView: View {
private let attachment: Attachment
private let client: RocketChatClientProtocol
init(attachment: Attachment, client: RocketChatClientProtocol) {
self.attachment = attachment
self.client = client
}
var body: some View {
if let rawURL = attachment.imageURL {
VStack(alignment: .leading) {
if let msg = attachment.msg {
Text(msg)
.font(.caption)
.foregroundStyle(.white)
}
AsyncImage(url: client.authorizedURL(url: rawURL)) { image in
image
.resizable()
.scaledToFit()
} placeholder: {
Rectangle()
.foregroundStyle(.secondary)
.aspectRatio(attachment.aspectRatio, contentMode: .fit)
.overlay(ProgressView())
}
.cornerRadius(4)
}
} else {
EmptyView()
}
}
}

View File

@ -11,6 +11,9 @@ struct MessageListView: View {
private let server: Server private let server: Server
private let room: Room private let room: Room
@State private var lastMessageID: String?
@State private var lastOpen: Date?
@FetchRequest<Message> private var messages: FetchedResults<Message> @FetchRequest<Message> private var messages: FetchedResults<Message>
init( init(
@ -29,6 +32,7 @@ struct MessageListView: View {
self.room = room self.room = room
self.server = server self.server = server
_messages = FetchRequest(fetchRequest: room.messagesRequest, animation: .none) _messages = FetchRequest(fetchRequest: room.messagesRequest, animation: .none)
_lastOpen = State(wrappedValue: room.updatedSince)
} }
var body: some View { var body: some View {
@ -47,12 +51,17 @@ struct MessageListView: View {
let message = messages[index] let message = messages[index]
let previousMessage = messages.indices.contains(index - 1) ? messages[index - 1] : nil let previousMessage = messages.indices.contains(index - 1) ? messages[index - 1] : nil
MessageView(viewModel: .init(message: message, previousMessage: previousMessage, server: server)) MessageView(
.transition(.move(edge: .bottom)) client: client,
viewModel: .init(message: message, previousMessage: previousMessage, server: server, lastOpen: lastOpen)
)
.transition(.move(edge: .bottom))
} }
MessageComposerView(room: room) { MessageComposerView(room: room) {
messageSender.sendMessage($0, in: room) messageSender.sendMessage($0, in: room)
lastOpen = nil
} }
.id(messageComposer) .id(messageComposer)
} }
@ -69,7 +78,10 @@ struct MessageListView: View {
messagesLoader.stop() messagesLoader.stop()
} }
.onReceive(messages.publisher) { _ in .onReceive(messages.publisher) { _ in
reader.scrollTo(messageComposer, anchor: .bottom) if lastMessageID != messages.last?.id {
reader.scrollTo(messageComposer, anchor: .bottom)
lastMessageID = messages.last?.id
}
} }
} }
} }

View File

@ -3,28 +3,53 @@ import SwiftUI
struct MessageView: View { struct MessageView: View {
@ObservedObject private var viewModel: MessageViewModel @ObservedObject private var viewModel: MessageViewModel
init(viewModel: MessageViewModel) { private let client: RocketChatClientProtocol
init(client: RocketChatClientProtocol, viewModel: MessageViewModel) {
self.client = client
self.viewModel = viewModel self.viewModel = viewModel
} }
@ViewBuilder
private var unreadSeparator: some View {
HStack(alignment: .center) {
Text("Unread messages")
.lineLimit(1)
.font(.footnote)
.foregroundStyle(.red)
.layoutPriority(1)
VStack(alignment: .center) {
Divider()
.overlay(.red)
}
}
}
@ViewBuilder
private var dateSeparator: some View {
HStack(alignment: .center) {
VStack(alignment: .center) {
Divider()
.overlay(.secondary)
}
Text(viewModel.messageFormatter.date() ?? "")
.lineLimit(1)
.font(.footnote)
.foregroundStyle(.secondary)
.layoutPriority(1)
VStack(alignment: .center) {
Divider()
.overlay(.secondary)
}
}
}
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if viewModel.messageFormatter.hasDateSeparator() { if viewModel.messageFormatter.hasDateSeparator() {
HStack(alignment: .center) { dateSeparator
VStack(alignment: .center) { } else if viewModel.messageFormatter.hasUnreadSeparator() {
Divider() unreadSeparator
.overlay(.secondary)
}
Text(viewModel.messageFormatter.date() ?? "")
.lineLimit(1)
.font(.footnote)
.foregroundStyle(.secondary)
.layoutPriority(1)
VStack(alignment: .center) {
Divider()
.overlay(.secondary)
}
}
} }
if viewModel.messageFormatter.isHeader() { if viewModel.messageFormatter.isHeader() {
HStack(alignment: .center) { HStack(alignment: .center) {
@ -49,11 +74,11 @@ struct MessageView: View {
.font(.caption) .font(.caption)
.foregroundStyle(viewModel.message.status == "temp" ? .secondary : .primary) .foregroundStyle(viewModel.message.status == "temp" ? .secondary : .primary)
} }
// if let attachments = message.attachments?.allObjects as? Array<Attachment> { if let attachments = viewModel.message.attachments?.allObjects as? Array<Attachment> {
// ForEach(attachments) { attachment in ForEach(attachments) { attachment in
// AttachmentView(attachment: attachment) AttachmentView(attachment: attachment, client: client)
// } }
// } }
} }
} }
} }

View File

@ -111,6 +111,8 @@
1E76CBDA25152C8E0067298C /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; }; 1E76CBDA25152C8E0067298C /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; };
1E9A71672B599E6300477BA2 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A71662B599E6300477BA2 /* NotificationController.swift */; }; 1E9A71672B599E6300477BA2 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A71662B599E6300477BA2 /* NotificationController.swift */; };
1E9A71692B59B6E100477BA2 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A71682B59B6E100477BA2 /* MessageSender.swift */; }; 1E9A71692B59B6E100477BA2 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A71682B59B6E100477BA2 /* MessageSender.swift */; };
1E9A716F2B59CBCA00477BA2 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A716E2B59CBCA00477BA2 /* AttachmentView.swift */; };
1E9A71712B59CC1300477BA2 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A71702B59CC1300477BA2 /* Attachment.swift */; };
1EB375892B55DBFB00AEC3D7 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB375882B55DBFB00AEC3D7 /* Server.swift */; }; 1EB375892B55DBFB00AEC3D7 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB375882B55DBFB00AEC3D7 /* Server.swift */; };
1EB8EF722510F1EE00F352B7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB8EF712510F1EE00F352B7 /* Storage.swift */; }; 1EB8EF722510F1EE00F352B7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB8EF712510F1EE00F352B7 /* Storage.swift */; };
1EC6ACB722CB9FC300A41C61 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; }; 1EC6ACB722CB9FC300A41C61 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; };
@ -396,6 +398,8 @@
1E9A71652B599D3F00477BA2 /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = "<group>"; }; 1E9A71652B599D3F00477BA2 /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = "<group>"; };
1E9A71662B599E6300477BA2 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = "<group>"; }; 1E9A71662B599E6300477BA2 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = "<group>"; };
1E9A71682B59B6E100477BA2 /* MessageSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSender.swift; sourceTree = "<group>"; }; 1E9A71682B59B6E100477BA2 /* MessageSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSender.swift; sourceTree = "<group>"; };
1E9A716E2B59CBCA00477BA2 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
1E9A71702B59CC1300477BA2 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
1EB375882B55DBFB00AEC3D7 /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; }; 1EB375882B55DBFB00AEC3D7 /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; };
1EB8EF712510F1EE00F352B7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; }; 1EB8EF712510F1EE00F352B7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareRocketChatRN.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 1EC6ACB022CB9FC300A41C61 /* ShareRocketChatRN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareRocketChatRN.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@ -738,6 +742,7 @@
1EB375882B55DBFB00AEC3D7 /* Server.swift */, 1EB375882B55DBFB00AEC3D7 /* Server.swift */,
1E29A30D2B58608C0093C03C /* LoggedUser.swift */, 1E29A30D2B58608C0093C03C /* LoggedUser.swift */,
1E29A3112B5866090093C03C /* Room.swift */, 1E29A3112B5866090093C03C /* Room.swift */,
1E9A71702B59CC1300477BA2 /* Attachment.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -776,6 +781,7 @@
1E29A3152B5868DF0093C03C /* MessageListView.swift */, 1E29A3152B5868DF0093C03C /* MessageListView.swift */,
1E29A3172B5868E50093C03C /* MessageView.swift */, 1E29A3172B5868E50093C03C /* MessageView.swift */,
1E29A3232B5874FF0093C03C /* MessageComposerView.swift */, 1E29A3232B5874FF0093C03C /* MessageComposerView.swift */,
1E9A716E2B59CBCA00477BA2 /* AttachmentView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1843,6 +1849,7 @@
1E29A2FD2B585B070093C03C /* RoomsRequest.swift in Sources */, 1E29A2FD2B585B070093C03C /* RoomsRequest.swift in Sources */,
1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */, 1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */,
1E29A3222B5871CE0093C03C /* MessageFormatter.swift in Sources */, 1E29A3222B5871CE0093C03C /* MessageFormatter.swift in Sources */,
1E9A716F2B59CBCA00477BA2 /* AttachmentView.swift in Sources */,
1E29A3002B585B070093C03C /* JSONAdapter.swift in Sources */, 1E29A3002B585B070093C03C /* JSONAdapter.swift in Sources */,
1E29A3022B585B070093C03C /* DateCodingStrategy.swift in Sources */, 1E29A3022B585B070093C03C /* DateCodingStrategy.swift in Sources */,
1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */, 1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */,
@ -1887,6 +1894,7 @@
1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */, 1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */,
1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */, 1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */,
1ED033B02B55B25A004F4930 /* Database.swift in Sources */, 1ED033B02B55B25A004F4930 /* Database.swift in Sources */,
1E9A71712B59CC1300477BA2 /* Attachment.swift in Sources */,
1ED033C82B55CE78004F4930 /* DependencyStore.swift in Sources */, 1ED033C82B55CE78004F4930 /* DependencyStore.swift in Sources */,
1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */, 1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */,
1E29A2F72B585B070093C03C /* ReadResponse.swift in Sources */, 1E29A2F72B585B070093C03C /* ReadResponse.swift in Sources */,