diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeNamesColorsExtension.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeNamesColorsExtension.swift new file mode 100644 index 000000000..3cdae85a1 --- /dev/null +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeNamesColorsExtension.swift @@ -0,0 +1,27 @@ +// +// 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 Foundation +import SwiftUI + +@available(iOS 14.0, *) +extension ThemeSwiftUI { + + func displayNameColor(for userId: String) -> Color { + let senderNameColorIndex = Int(userId.vc_hashCode % Int32(colors.namesAndAvatars.count)) + return colors.namesAndAvatars[senderNameColorIndex] + } +} diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift index 68498f853..fcc4ce70b 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift @@ -35,7 +35,6 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { private let eventFormatter: EventFormatter private var roomState: MXRoomState? private var roomListenerReference: Any? - private var usernameColorGenerator: UserNameColorGenerator() // MARK: Public private(set) var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> @@ -47,7 +46,6 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { // MARK: - Setup init(room: MXRoom) { - ) self.room = room self.eventFormatter = EventFormatter(matrixSession: room.mxSession) self.chatMessagesSubject = CurrentValueSubject([]) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift index 30b013f79..695b47f7f 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift @@ -37,13 +37,27 @@ struct TemplateRoomChat: View { } .frame(maxHeight: .infinity) } else { - ScrollView{ - LazyVStack { - ForEach(viewModel.viewState.bubbles) { bubble in - TemplateRoomChatBubbleView(bubble: bubble) + ScrollViewReader { reader in + ScrollView{ + LazyVStack { + ForEach(viewModel.viewState.bubbles) { bubble in + TemplateRoomChatBubbleView(bubble: bubble) + .id(bubble.id) + } } + .onAppear { + // Start at the bottom + reader.scrollTo(viewModel.viewState.bubbles.last?.id, anchor: .bottom) + } + .onChange(of: itemCount) { _ in + // When new items are added animate to the new items + withAnimation { + reader.scrollTo(viewModel.viewState.bubbles.last?.id, anchor: .bottom) + } + } + // When the scroll content takes less than the screen space align at the top + .frame(maxHeight: .infinity, alignment: .top) } - .frame(maxHeight: .infinity, alignment: .top) } .frame(maxHeight: .infinity) } @@ -59,6 +73,7 @@ struct TemplateRoomChat: View { }) } } + // When displaying/hiding the send button slide it on/off from the right side .animation(.easeOut(duration: 0.25)) .transition(.move(edge: .trailing)) .padding(.horizontal) @@ -78,6 +93,14 @@ struct TemplateRoomChat: View { } } } + + private var itemCount: Int { + return viewModel.viewState + .bubbles + .map(\.items) + .map(\.count) + .reduce(0, +) + } } // MARK: - Previews diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift index 2082688e2..7c8f59393 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleMessage.swift @@ -32,6 +32,7 @@ struct TemplateRoomChatBubbleMessage: View { var body: some View { Text(messageContent.body) .foregroundColor(theme.colors.primaryContent) + .font(theme.fonts.body) } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index 121e8227e..08a8ed47a 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -34,12 +34,12 @@ struct TemplateRoomChatBubbleView: View { AvatarImage(avatarData: bubble.sender.avatarData, size: .xSmall) VStack(alignment: .leading){ Text(bubble.sender.displayName ?? "") - .foregroundColor(theme.colors.primaryContent) + .foregroundColor(theme.displayNameColor(for: bubble.sender.id)) + .font(theme.fonts.bodySB) ForEach(bubble.items) { item in TemplateRoomChatBubbleContentView(bubbleItem: item) } } - Spacer() } //add to a style diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift index 04aa5f0ba..9f77cf594 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/ViewModel/TemplateRoomChatViewModel.swift @@ -43,14 +43,16 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat init(templateRoomChatService: TemplateRoomChatServiceProtocol) { self.templateRoomChatService = templateRoomChatService super.init(initialViewState: Self.defaultState(templateRoomChatService: templateRoomChatService)) - templateRoomChatService.chatMessagesSubject + setupMessageObserving() + } + + private func setupMessageObserving() { + let messageActionPublisher = templateRoomChatService + .chatMessagesSubject .map(Self.makeBubbles(messages:)) .map(TemplateRoomChatStateAction.updateBubbles) - .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] action in - self?.dispatch(action:action) - }) - .store(in: &cancellables) + .eraseToAnyPublisher() + dispatch(actionPublisher: messageActionPublisher) } private static func defaultState(templateRoomChatService: TemplateRoomChatServiceProtocol) -> TemplateRoomChatViewState { @@ -65,6 +67,7 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat var bubbleMap = [String:TemplateRoomChatBubble]() messages.enumerated().forEach { i, message in + // New message content let messageItem = TemplateRoomChatBubbleItem( id: message.id, timestamp: message.timestamp, @@ -77,6 +80,8 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat let interveningTime = lastBubble.items.last?.timestamp.timeIntervalSince(message.timestamp), abs(interveningTime) < Constants.maxTimeBeforeNewBubble { + // if the last bubble's last message was within + // the last 5 minutes append let item = TemplateRoomChatBubbleItem( id: message.id, timestamp: message.timestamp, @@ -85,6 +90,7 @@ class TemplateRoomChatViewModel: TemplateRoomChatViewModelType, TemplateRoomChat lastBubble.items.append(item) bubbleMap[lastBubble.id] = lastBubble } else { + // else create a new bubble and add the message as the first item let bubble = TemplateRoomChatBubble( id: message.id, sender: message.sender, diff --git a/RiotSwiftUI/target.yml b/RiotSwiftUI/target.yml index a685db564..5200f73ca 100644 --- a/RiotSwiftUI/target.yml +++ b/RiotSwiftUI/target.yml @@ -36,6 +36,8 @@ targets: - path: ../Riot/Generated/Images.swift - path: ../Riot/Managers/Theme/ThemeIdentifier.swift - path: ../Riot/Managers/Locale/LocaleProviderType.swift + - path: ../Riot/Categories/String.swift + - path: ../Riot/Categories/Character.swift - path: ../Riot/Assets/en.lproj/Vector.strings buildPhase: resources - path: ../Riot/Assets/Images.xcassets