From 2fdf0e2c3b5612480a88fc8ad0ead99fb1dd7b4e Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Thu, 18 Jan 2024 19:49:49 -0300 Subject: [PATCH] Add AttachmentView --- .../Client/Requests/HistoryRequest.swift | 3 + .../Database/Models/Attachment.swift | 7 ++ .../Formatters/MessageFormatter.swift | 18 ++++- .../Loaders/MessagesLoader.swift | 4 +- .../Localizable.xcstrings | 3 + .../ViewModels/MessageViewModel.swift | 8 ++- .../Views/AttachmentView.swift | 36 ++++++++++ .../Views/MessageListView.swift | 18 ++++- .../Views/MessageView.swift | 67 +++++++++++++------ ios/RocketChatRN.xcodeproj/project.pbxproj | 8 +++ 10 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 ios/RocketChat Watch App/Database/Models/Attachment.swift create mode 100644 ios/RocketChat Watch App/Views/AttachmentView.swift diff --git a/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift b/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift index c5384de03..6c33ce38c 100644 --- a/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift +++ b/ios/RocketChat Watch App/Client/Requests/HistoryRequest.swift @@ -1,5 +1,7 @@ import Foundation +let HISTORY_MESSAGE_COUNT = 50 + struct HistoryRequest: Request { typealias Response = HistoryResponse @@ -11,6 +13,7 @@ struct HistoryRequest: Request { queryItems = [ URLQueryItem(name: "roomId", value: roomId), + URLQueryItem(name: "count", value: String(HISTORY_MESSAGE_COUNT)), URLQueryItem(name: "latest", value: latest.iso8601withFractionalSeconds) ] } diff --git a/ios/RocketChat Watch App/Database/Models/Attachment.swift b/ios/RocketChat Watch App/Database/Models/Attachment.swift new file mode 100644 index 000000000..5ee92b33b --- /dev/null +++ b/ios/RocketChat Watch App/Database/Models/Attachment.swift @@ -0,0 +1,7 @@ +import CoreData + +extension Attachment { + var aspectRatio: Double { + return width / height + } +} diff --git a/ios/RocketChat Watch App/Formatters/MessageFormatter.swift b/ios/RocketChat Watch App/Formatters/MessageFormatter.swift index ef7be53bb..4b1768842 100644 --- a/ios/RocketChat Watch App/Formatters/MessageFormatter.swift +++ b/ios/RocketChat Watch App/Formatters/MessageFormatter.swift @@ -3,10 +3,12 @@ import Foundation final class MessageFormatter { private let message: 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.previousMessage = previousMessage + self.lastOpen = lastOpen } func hasDateSeparator() -> Bool { @@ -19,6 +21,20 @@ final class MessageFormatter { 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 { if let previousMessage, let previousMessageTS = previousMessage.ts, diff --git a/ios/RocketChat Watch App/Loaders/MessagesLoader.swift b/ios/RocketChat Watch App/Loaders/MessagesLoader.swift index e5322cb51..dce74c0f3 100644 --- a/ios/RocketChat Watch App/Loaders/MessagesLoader.swift +++ b/ios/RocketChat Watch App/Loaders/MessagesLoader.swift @@ -74,8 +74,10 @@ final class MessagesLoader { } receiveValue: { [weak self] messagesResponse in 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 + } else { + room.hasMoreMessages = false } for message in messages { diff --git a/ios/RocketChat Watch App/Localizable.xcstrings b/ios/RocketChat Watch App/Localizable.xcstrings index 661924f80..12f5812a9 100644 --- a/ios/RocketChat Watch App/Localizable.xcstrings +++ b/ios/RocketChat Watch App/Localizable.xcstrings @@ -19,6 +19,9 @@ }, "Try Again" : { + }, + "Unread messages" : { + } }, "version" : "1.0" diff --git a/ios/RocketChat Watch App/ViewModels/MessageViewModel.swift b/ios/RocketChat Watch App/ViewModels/MessageViewModel.swift index 4e32f3b22..2ac1a06a1 100644 --- a/ios/RocketChat Watch App/ViewModels/MessageViewModel.swift +++ b/ios/RocketChat Watch App/ViewModels/MessageViewModel.swift @@ -7,10 +7,14 @@ final class MessageViewModel: ObservableObject { 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.previousMessage = previousMessage - self.messageFormatter = MessageFormatter(message: message, previousMessage: previousMessage) + self.messageFormatter = MessageFormatter( + message: message, + previousMessage: previousMessage, + lastOpen: lastOpen + ) self.server = server } diff --git a/ios/RocketChat Watch App/Views/AttachmentView.swift b/ios/RocketChat Watch App/Views/AttachmentView.swift new file mode 100644 index 000000000..572db3957 --- /dev/null +++ b/ios/RocketChat Watch App/Views/AttachmentView.swift @@ -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() + } + } +} diff --git a/ios/RocketChat Watch App/Views/MessageListView.swift b/ios/RocketChat Watch App/Views/MessageListView.swift index 19943c1c4..83e46e1cb 100644 --- a/ios/RocketChat Watch App/Views/MessageListView.swift +++ b/ios/RocketChat Watch App/Views/MessageListView.swift @@ -11,6 +11,9 @@ struct MessageListView: View { private let server: Server private let room: Room + @State private var lastMessageID: String? + @State private var lastOpen: Date? + @FetchRequest private var messages: FetchedResults init( @@ -29,6 +32,7 @@ struct MessageListView: View { self.room = room self.server = server _messages = FetchRequest(fetchRequest: room.messagesRequest, animation: .none) + _lastOpen = State(wrappedValue: room.updatedSince) } var body: some View { @@ -47,12 +51,17 @@ struct MessageListView: View { let message = messages[index] let previousMessage = messages.indices.contains(index - 1) ? messages[index - 1] : nil - MessageView(viewModel: .init(message: message, previousMessage: previousMessage, server: server)) - .transition(.move(edge: .bottom)) + MessageView( + client: client, + viewModel: .init(message: message, previousMessage: previousMessage, server: server, lastOpen: lastOpen) + ) + .transition(.move(edge: .bottom)) } MessageComposerView(room: room) { messageSender.sendMessage($0, in: room) + + lastOpen = nil } .id(messageComposer) } @@ -69,7 +78,10 @@ struct MessageListView: View { messagesLoader.stop() } .onReceive(messages.publisher) { _ in - reader.scrollTo(messageComposer, anchor: .bottom) + if lastMessageID != messages.last?.id { + reader.scrollTo(messageComposer, anchor: .bottom) + lastMessageID = messages.last?.id + } } } } diff --git a/ios/RocketChat Watch App/Views/MessageView.swift b/ios/RocketChat Watch App/Views/MessageView.swift index ea649ce9c..45df6b36e 100644 --- a/ios/RocketChat Watch App/Views/MessageView.swift +++ b/ios/RocketChat Watch App/Views/MessageView.swift @@ -3,28 +3,53 @@ import SwiftUI struct MessageView: View { @ObservedObject private var viewModel: MessageViewModel - init(viewModel: MessageViewModel) { + private let client: RocketChatClientProtocol + + init(client: RocketChatClientProtocol, viewModel: MessageViewModel) { + self.client = client 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 { VStack(alignment: .leading) { if viewModel.messageFormatter.hasDateSeparator() { - 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) - } - } + dateSeparator + } else if viewModel.messageFormatter.hasUnreadSeparator() { + unreadSeparator } if viewModel.messageFormatter.isHeader() { HStack(alignment: .center) { @@ -49,11 +74,11 @@ struct MessageView: View { .font(.caption) .foregroundStyle(viewModel.message.status == "temp" ? .secondary : .primary) } - // if let attachments = message.attachments?.allObjects as? Array { - // ForEach(attachments) { attachment in - // AttachmentView(attachment: attachment) - // } - // } + if let attachments = viewModel.message.attachments?.allObjects as? Array { + ForEach(attachments) { attachment in + AttachmentView(attachment: attachment, client: client) + } + } } } } diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 18e7c18ef..f503ef6c6 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -111,6 +111,8 @@ 1E76CBDA25152C8E0067298C /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; }; 1E9A71672B599E6300477BA2 /* NotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9A71662B599E6300477BA2 /* NotificationController.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 */; }; 1EB8EF722510F1EE00F352B7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB8EF712510F1EE00F352B7 /* Storage.swift */; }; 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 = ""; }; 1E9A71662B599E6300477BA2 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = ""; }; 1E9A71682B59B6E100477BA2 /* MessageSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSender.swift; sourceTree = ""; }; + 1E9A716E2B59CBCA00477BA2 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = ""; }; + 1E9A71702B59CC1300477BA2 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = ""; }; 1EB375882B55DBFB00AEC3D7 /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = ""; }; 1EB8EF712510F1EE00F352B7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; 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 */, 1E29A30D2B58608C0093C03C /* LoggedUser.swift */, 1E29A3112B5866090093C03C /* Room.swift */, + 1E9A71702B59CC1300477BA2 /* Attachment.swift */, ); path = Models; sourceTree = ""; @@ -776,6 +781,7 @@ 1E29A3152B5868DF0093C03C /* MessageListView.swift */, 1E29A3172B5868E50093C03C /* MessageView.swift */, 1E29A3232B5874FF0093C03C /* MessageComposerView.swift */, + 1E9A716E2B59CBCA00477BA2 /* AttachmentView.swift */, ); path = Views; sourceTree = ""; @@ -1843,6 +1849,7 @@ 1E29A2FD2B585B070093C03C /* RoomsRequest.swift in Sources */, 1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */, 1E29A3222B5871CE0093C03C /* MessageFormatter.swift in Sources */, + 1E9A716F2B59CBCA00477BA2 /* AttachmentView.swift in Sources */, 1E29A3002B585B070093C03C /* JSONAdapter.swift in Sources */, 1E29A3022B585B070093C03C /* DateCodingStrategy.swift in Sources */, 1ED033B62B55B4A5004F4930 /* ServerListView.swift in Sources */, @@ -1887,6 +1894,7 @@ 1ED033B82B55B4BE004F4930 /* ServerListViewModel.swift in Sources */, 1ED033C42B55C65C004F4930 /* RocketChatAppRouter.swift in Sources */, 1ED033B02B55B25A004F4930 /* Database.swift in Sources */, + 1E9A71712B59CC1300477BA2 /* Attachment.swift in Sources */, 1ED033C82B55CE78004F4930 /* DependencyStore.swift in Sources */, 1E29A30A2B585B370093C03C /* Data+Extensions.swift in Sources */, 1E29A2F72B585B070093C03C /* ReadResponse.swift in Sources */,