mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
08d02001bf
* Use our standard XYZMock(.init()) pattern everywhere instead of XYZMock.default. * Remove the configuration parameter label on mock convenience inits. * Update the enterprise submodule.
147 lines
4.9 KiB
Swift
147 lines
4.9 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2023-2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct ReactionsSummaryView: View {
|
|
let reactions: [AggregatedReaction]
|
|
let members: [String: RoomMemberState]
|
|
let mediaProvider: MediaProviderProtocol?
|
|
|
|
@State var selectedReactionKey: String
|
|
private var selectedReaction: AggregatedReaction? {
|
|
reactions.first { $0.key == selectedReactionKey }
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
reactionButtons
|
|
sendersList
|
|
}
|
|
.presentationDetents([.medium])
|
|
.presentationBackground(Color.compound.bgCanvasDefault)
|
|
.presentationDragIndicator(.visible)
|
|
}
|
|
|
|
private var reactionButtons: some View {
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
ScrollViewReader { scrollView in
|
|
HStack(spacing: 8) {
|
|
ForEach(reactions) { reaction in
|
|
ReactionSummaryButton(reaction: reaction, highlighted: selectedReactionKey == reaction.key) { key in
|
|
selectedReactionKey = key
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 20)
|
|
.onAppear {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
scrollView.scrollTo(selectedReactionKey)
|
|
}
|
|
}
|
|
.onChange(of: selectedReactionKey) {
|
|
scrollView.scrollTo(selectedReactionKey)
|
|
}
|
|
}
|
|
}
|
|
.padding(.top, 24)
|
|
.padding(.bottom, 12)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var sendersList: some View {
|
|
if let selectedReaction {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
ForEach(selectedReaction.senders) { sender in
|
|
ReactionSummarySenderView(sender: sender, member: members[sender.id], mediaProvider: mediaProvider)
|
|
.padding(.horizontal, 16)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct ReactionSummaryButton: View {
|
|
let reaction: AggregatedReaction
|
|
let highlighted: Bool
|
|
let action: (String) -> Void
|
|
|
|
var body: some View {
|
|
Button { action(reaction.key) } label: { label }
|
|
}
|
|
|
|
var label: some View {
|
|
HStack(spacing: 4) {
|
|
Text(reaction.displayKey)
|
|
.font(.compound.headingSM)
|
|
.foregroundColor(textColor)
|
|
if reaction.count > 1 {
|
|
Text(String(reaction.count))
|
|
.font(.compound.headingSM)
|
|
.foregroundColor(textColor)
|
|
}
|
|
}
|
|
.padding(.vertical, 8)
|
|
.padding(.horizontal, 12)
|
|
.background(highlighted ? Color.compound.bgActionPrimaryRest : .clear, in: Capsule())
|
|
.accessibilityElement(children: .combine)
|
|
}
|
|
|
|
var textColor: Color {
|
|
highlighted ? Color.compound.textOnSolidPrimary : Color.compound.textSecondary
|
|
}
|
|
}
|
|
|
|
private struct ReactionSummarySenderView: View {
|
|
var sender: ReactionSender
|
|
var member: RoomMemberState?
|
|
let mediaProvider: MediaProviderProtocol?
|
|
|
|
var displayName: String {
|
|
member?.displayName ?? sender.id
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
LoadableAvatarImage(url: member?.avatarURL,
|
|
name: displayName,
|
|
contentID: sender.id,
|
|
avatarSize: .user(on: .timeline),
|
|
mediaProvider: mediaProvider)
|
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
HStack(spacing: 8) {
|
|
Text(displayName)
|
|
.font(.compound.bodyMDSemibold)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
Text(sender.timestamp.formattedMinimal())
|
|
.font(.compound.bodyXS)
|
|
.foregroundColor(.compound.textSecondary)
|
|
}
|
|
Text(sender.id)
|
|
.font(.compound.bodySM)
|
|
.foregroundColor(.compound.textSecondary)
|
|
}
|
|
}
|
|
|
|
.padding(.vertical, 8)
|
|
}
|
|
}
|
|
|
|
struct ReactionsSummaryView_Previews: PreviewProvider, TestablePreview {
|
|
static var previews: some View {
|
|
ReactionsSummaryView(reactions: AggregatedReaction.mockReactions,
|
|
members: [:],
|
|
mediaProvider: MediaProviderMock(.init()),
|
|
selectedReactionKey: AggregatedReaction.mockReactions[0].key)
|
|
}
|
|
}
|