diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatBubbleItem.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatBubbleItem.swift new file mode 100644 index 000000000..b9a6e50c7 --- /dev/null +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatBubbleItem.swift @@ -0,0 +1,49 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + + +struct TemplateRoomChatBubble { + var id: String + var senderAvatar: AvatarInputProtocol + var senderDisplayName: String? + var items: [TemplateRoomChatBubbleItem] +} + +extension TemplateRoomChatBubble: Identifiable { } + +enum TemplateRoomChatBubbleItem { + var id: Self { self } + case message(TemplateRoomChatBubbleMessageItem) + case image(TemplateRoomChatBubbleImageItem) +} + +extension TemplateRoomChatBubbleItem: Hashable, Identifiable {} + +struct TemplateRoomChatBubbleMessageItem { + var id: String + var body: String +} + +extension TemplateRoomChatBubbleMessageItem: Hashable {} + +struct TemplateRoomChatBubbleImageItem { + var id: String + var image: UIImage +} + +extension TemplateRoomChatBubbleImageItem: Hashable {} diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatViewModelInput.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatViewModelInput.swift index 79b37684f..7ac56116c 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatViewModelInput.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Model/TemplateRoomChatViewModelInput.swift @@ -17,5 +17,5 @@ import Foundation struct TemplateRoomChatViewModelInput { - let messageInput: String + var messageInput: String } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift index f02ed7873..bbd85efbe 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift @@ -19,12 +19,13 @@ import Combine @available(iOS 14.0, *) class MockTemplateRoomChatService: TemplateRoomChatServiceProtocol { - + static let alice = TemplateRoomChatMember(id: "@alice:matrix.org", avatarUrl: "!aaabaa:matrix.org", displayName: "Alice") + static let mathew = TemplateRoomChatMember(id: "@mathew:matrix.org", avatarUrl: "!bbabb:matrix.org", displayName: "Mathew") static let mockMessages = [ - TemplateRoomChatMessage(id: "!aaabaa:matrix.org", body: "Shall I put it live?", sender: "@alice:matrix.org"), - TemplateRoomChatMessage(id: "!bbbabb:matrix.org", body: "Yea go for it! ...and then let's head to the pub", sender: "@patrice:matrix.org"), - TemplateRoomChatMessage(id: "!aaabaa:matrix.org", body: "Deal.", sender: "@alice:matrix.org"), - TemplateRoomChatMessage(id: "!aaabaa:matrix.org", body: "Ok, Done. 🍻", sender: "@alice:matrix.org"), + TemplateRoomChatMessage(id: "!11111:matrix.org", body: "Shall I put it live?", sender: alice), + TemplateRoomChatMessage(id: "!22222:matrix.org", body: "Yea go for it! ...and then let's head to the pub", sender: mathew), + TemplateRoomChatMessage(id: "!33333:matrix.org", body: "Deal.", sender: alice), + TemplateRoomChatMessage(id: "!44444:matrix.org", body: "Ok, Done. 🍻", sender: alice), ] var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift index d2d0936e7..c9fbb4bff 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift @@ -31,15 +31,25 @@ struct TemplateRoomChat: View { var body: some View { VStack { - LazyVStack { - - }.frame(maxHeight: .infinity) + if viewModel.viewState.bubbles.isEmpty { + VStack{ + Text("No messages") + } + .frame(maxHeight: .infinity) + } else { + LazyVStack { + ForEach(viewModel.viewState.bubbles) { bubble in + TemplateRoomChatBubbleView(bubble: bubble) + } + } + .frame(maxHeight: .infinity, alignment: .top) + } HStack { TextField(VectorL10n.roomMessageShortPlaceholder, text: $viewModel.input.messageInput) .textFieldStyle(BorderedInputFieldStyle()) Button(action: { - + }, label: { Image(uiImage: Asset.Images.sendIcon.image) }) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleItemView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleItemView.swift new file mode 100644 index 000000000..c40fa812f --- /dev/null +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleItemView.swift @@ -0,0 +1,49 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@available(iOS 14.0, *) +struct TemplateRoomChatBubbleItemView: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + let bubbleItem: TemplateRoomChatBubbleItem + + var body: some View { + switch bubbleItem { + case .message(let messageItem): + TemplateRoomChatBubbleMessage(messageItem: messageItem) + case .image(let imageItem): + TemplateRoomChatBubbleImage(imageItem: imageItem) + } + } +} + +// MARK: - Previews + +@available(iOS 14.0, *) +struct TemplateRoomChatBubbleItemView_Previews: PreviewProvider { + static var previews: some View { + EmptyView() + } +} diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift new file mode 100644 index 000000000..f1f08248f --- /dev/null +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift @@ -0,0 +1,44 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@available(iOS 14.0, *) +struct TemplateRoomChatBubbleMessage: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + let messageItem: TemplateRoomChatBubbleMessageItem + + var body: some View { + Text(messageItem.body) + } +} + +// MARK: - Previews + +@available(iOS 14.0, *) +struct TemplateRoomChatBubbleMessage_Previews: PreviewProvider { + static var previews: some View { + EmptyView() + } +} diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index 0a44c72ba..45b0719e3 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -31,9 +31,9 @@ struct TemplateRoomChatBubbleView: View { var body: some View { HStack{ - AvatarImage(avatarData: bubble.avatar, size: .xSmall) - VStack{ - Text(bubble.displayName ?? "") + AvatarImage(avatarData: bubble.senderAvatar, size: .xSmall) + VStack(alignment: .leading){ + Text(bubble.senderDisplayName ?? "") ForEach(bubble.items) { item in TemplateRoomChatBubbleItemView(bubbleItem: item) } @@ -52,7 +52,14 @@ struct TemplateRoomChatBubbleView: View { @available(iOS 14.0, *) struct TemplateRoomChatBubbleView_Previews: PreviewProvider { + static let bubble = TemplateRoomChatBubble( + id: "111", + senderAvatar: MockAvatarInput.example, + senderDisplayName: "Alice", + items: [.message(TemplateRoomChatBubbleMessageItem(id: "222", body: "Hello world! 🌎"))] + ) static var previews: some View { - EmptyView() + TemplateRoomChatBubbleView(bubble: bubble) + .addDependency(MockAvatarService.example) } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift index c4437a7e1..81252c16e 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift @@ -55,20 +55,34 @@ class TemplateRoomChatViewModel: ObservableObject, TemplateRoomChatViewModelProt private static func makeBubbles(messages: [TemplateRoomChatMessage]) -> [TemplateRoomChatBubble] { - + var bubbleOrder = [String]() + var bubbleMap = [String:TemplateRoomChatBubble]() messages.enumerated().forEach { i, message in - let currentMessage = messages[i] - if i > 0 { - let lastMessage = messages[i-1] - } else { - TemplateRoomChatBubble( - id: message., - avatar: <#T##AvatarInputProtocol#>, - displayName: <#T##String?#>, - items: <#T##[TemplateRoomChatBubbleItem]#> + if i > 0, + messages[i-1].sender.id == message.sender.id, + var existingBubble = bubbleMap[messages[i-1].id] { + let messageItem = TemplateRoomChatBubbleMessageItem( + id: message.id, + body: message.body ) + existingBubble.items.append(.message(messageItem)) + bubbleMap[existingBubble.id] = existingBubble + } else { + let messageItem = TemplateRoomChatBubbleMessageItem( + id: message.id, + body: message.body + ) + let bubble = TemplateRoomChatBubble( + id: message.id, + senderAvatar: message.sender.avatarData, + senderDisplayName: message.sender.displayName, + items: [.message(messageItem)] + ) + bubbleOrder.append(bubble.id) + bubbleMap[bubble.id] = bubble } } + return bubbleOrder.compactMap({ bubbleMap[$0] }) } // MARK: - Public