mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Improve scrolling behaviour and add name colors
This commit is contained in:
parent
2e952a6298
commit
50684fda0b
7 changed files with 72 additions and 15 deletions
|
@ -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]
|
||||
}
|
||||
}
|
|
@ -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([])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,6 +32,7 @@ struct TemplateRoomChatBubbleMessage: View {
|
|||
var body: some View {
|
||||
Text(messageContent.body)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.font(theme.fonts.body)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue