Add AttachmentView
This commit is contained in:
parent
9668aed381
commit
2fdf0e2c3b
|
@ -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)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
extension Attachment {
|
||||||
|
var aspectRatio: Double {
|
||||||
|
return width / height
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
},
|
},
|
||||||
"Try Again" : {
|
"Try Again" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Unread messages" : {
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 */,
|
||||||
|
|
Loading…
Reference in New Issue