Skip to content

Commit

Permalink
Fix reactions users view not paginating results (#712)
Browse files Browse the repository at this point in the history
* Fix reactions users view not paginating results

* Update CHANGELOG.md

* Create seperate file for the view model
  • Loading branch information
nuno-vieira authored Jan 13, 2025
1 parent ff23f48 commit f566ec1
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Use bright color for typing indicator animation in dark mode [#702](https://github.com/GetStream/stream-chat-swiftui/pull/702)
- Refresh quoted message preview when the quoted message is deleted [#705](https://github.com/GetStream/stream-chat-swiftui/pull/705)
- Fix composer command view not Themable [#710](https://github.com/GetStream/stream-chat-swiftui/pull/710)
- Fix reactions users view not paginating results [#712](https://github.com/GetStream/stream-chat-swiftui/pull/712)

# [4.69.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.69.0)
_December 18, 2024_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,48 @@ import SwiftUI

/// View displaying users who have reacted to a message.
struct ReactionsUsersView: View {

@StateObject private var viewModel: ReactionsUsersViewModel

@Injected(\.fonts) private var fonts
@Injected(\.colors) private var colors

var message: ChatMessage
var maxHeight: CGFloat

private static let columnCount = 4
private static let itemSize: CGFloat = 64

private let columns = Array(
repeating:
GridItem(
.adaptive(minimum: itemSize),
alignment: .top
),
repeating: GridItem(.adaptive(minimum: itemSize), alignment: .top),
count: columnCount
)

init(message: ChatMessage, maxHeight: CGFloat) {
self.maxHeight = maxHeight
_viewModel = StateObject(wrappedValue: ReactionsUsersViewModel(message: message))
}

private var reactions: [ChatMessageReaction] {
Array(message.latestReactions)
init(viewModel: ReactionsUsersViewModel, maxHeight: CGFloat) {
self.maxHeight = maxHeight
_viewModel = StateObject(wrappedValue: viewModel)
}

var body: some View {
HStack {
if message.isRightAligned {
if viewModel.isRightAligned {
Spacer()
}

VStack(alignment: .center) {
Text(L10n.Reaction.Authors.numberOfReactions(reactions.count))
Text(L10n.Reaction.Authors.numberOfReactions(viewModel.totalReactionsCount))
.foregroundColor(Color(colors.text))
.font(fonts.title3)
.fontWeight(.bold)
.padding()

if reactions.count > Self.columnCount {
if viewModel.reactions.count > Self.columnCount {
ScrollView {
LazyVGrid(columns: columns, alignment: .center, spacing: 8) {
ForEach(reactions) { reaction in
ForEach(viewModel.reactions) { reaction in
ReactionUserView(
reaction: reaction,
imageSize: Self.itemSize
Expand All @@ -57,7 +59,7 @@ struct ReactionsUsersView: View {
.frame(maxHeight: maxHeight)
} else {
HStack(alignment: .top, spacing: 0) {
ForEach(reactions) { reaction in
ForEach(viewModel.reactions) { reaction in
ReactionUserView(
reaction: reaction,
imageSize: Self.itemSize
Expand All @@ -70,7 +72,7 @@ struct ReactionsUsersView: View {
.background(Color(colors.background))
.cornerRadius(16)

if !message.isRightAligned {
if !viewModel.isRightAligned {
Spacer()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright © 2025 Stream.io Inc. All rights reserved.
//

import StreamChat
import SwiftUI

class ReactionsUsersViewModel: ObservableObject, ChatMessageControllerDelegate {
@Published var reactions: [ChatMessageReaction] = []

var totalReactionsCount: Int {
messageController?.message?.totalReactionsCount ?? 0
}

var isRightAligned: Bool {
messageController?.message?.isRightAligned == true
}

private var isLoading = false
private let messageController: ChatMessageController?

init(message: ChatMessage) {
if let cid = message.cid {
messageController = InjectedValues[\.chatClient].messageController(
cid: cid,
messageId: message.id
)
} else {
messageController = nil
}
messageController?.delegate = self
loadMoreReactions()
}

func loadMoreReactions() {
guard let messageController = self.messageController else {
return
}
guard !isLoading && messageController.hasLoadedAllReactions == false else {
return
}

isLoading = true
messageController.loadNextReactions { [weak self] _ in
self?.isLoading = false
}
}

func messageController(_ controller: ChatMessageController, didChangeReactions reactions: [ChatMessageReaction]) {
self.reactions = reactions
}
}
4 changes: 4 additions & 0 deletions StreamChatSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@
AD3AB65C2CB730090014D4D7 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3AB65B2CB730090014D4D7 /* Shimmer.swift */; };
AD3AB65E2CB731360014D4D7 /* ChatThreadListLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3AB65D2CB731360014D4D7 /* ChatThreadListLoadingView.swift */; };
AD3AB6602CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3AB65F2CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift */; };
AD6B7E052D356E8800ADEF39 /* ReactionsUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B7E042D356E8800ADEF39 /* ReactionsUsersViewModel.swift */; };
ADE0F55E2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F55D2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift */; };
ADE0F5602CB846EC0053B8B9 /* FloatingBannerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F55F2CB846EC0053B8B9 /* FloatingBannerViewModifier.swift */; };
ADE0F5622CB8556F0053B8B9 /* ChatThreadListFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F5612CB8556F0053B8B9 /* ChatThreadListFooterView.swift */; };
Expand Down Expand Up @@ -1107,6 +1108,7 @@
AD3AB65B2CB730090014D4D7 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
AD3AB65D2CB731360014D4D7 /* ChatThreadListLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListLoadingView.swift; sourceTree = "<group>"; };
AD3AB65F2CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListHeaderViewModifier.swift; sourceTree = "<group>"; };
AD6B7E042D356E8800ADEF39 /* ReactionsUsersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsUsersViewModel.swift; sourceTree = "<group>"; };
ADE0F55D2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListErrorBannerView.swift; sourceTree = "<group>"; };
ADE0F55F2CB846EC0053B8B9 /* FloatingBannerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingBannerViewModifier.swift; sourceTree = "<group>"; };
ADE0F5612CB8556F0053B8B9 /* ChatThreadListFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListFooterView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1824,6 +1826,7 @@
8465FD212746A95600AF091E /* ReactionsOverlayViewModel.swift */,
8465FD252746A95600AF091E /* ReactionsHelperViews.swift */,
84E6EC26279B0C930017207B /* ReactionsUsersView.swift */,
AD6B7E042D356E8800ADEF39 /* ReactionsUsersViewModel.swift */,
846D6563279FF0800094B36E /* ReactionUserView.swift */,
);
path = Reactions;
Expand Down Expand Up @@ -2717,6 +2720,7 @@
842383E427678A4D00888CFC /* QuotedMessageView.swift in Sources */,
84289BE328071C7200282ABE /* ChatChannelInfoViewModel.swift in Sources */,
4F6D83512C1079A00098C298 /* AlertBannerViewModifier.swift in Sources */,
AD6B7E052D356E8800ADEF39 /* ReactionsUsersViewModel.swift in Sources */,
8465FD932746A95700AF091E /* PhotoAttachmentPickerView.swift in Sources */,
841B64C82774BA770016FF3B /* CommandsHandler.swift in Sources */,
8465FDC42746A95700AF091E /* ChatChannelListScreen.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ class ReactionsUsersView_Tests: StreamChatTestCase {
author: author,
extraData: [:]
)
let message = ChatMessage.mock(
id: .unique,
cid: .unique,
text: "test",
author: .mock(id: .unique),
latestReactions: [reaction]
let mockViewModel = MockReactionUsersViewModel(
reactions: [reaction],
totalReactionsCount: 1
)

// When
let view = ReactionsUsersView(message: message, maxHeight: 140)
let view = ReactionsUsersView(viewModel: mockViewModel, maxHeight: 140)
.frame(width: 250)

// Then
Expand All @@ -56,19 +53,39 @@ class ReactionsUsersView_Tests: StreamChatTestCase {
reactions.insert(reaction)
}

let message = ChatMessage.mock(
id: .unique,
cid: .unique,
text: "test",
author: .mock(id: .unique),
latestReactions: reactions
let mockViewModel = MockReactionUsersViewModel(
reactions: Array(reactions),
totalReactionsCount: 8
)

// When
let view = ReactionsUsersView(message: message, maxHeight: 280)
let view = ReactionsUsersView(viewModel: mockViewModel, maxHeight: 280)
.frame(width: defaultScreenSize.width, height: 320)

// Then
assertSnapshot(matching: view, as: .image(perceptualPrecision: precision))
}
}

class MockReactionUsersViewModel: ReactionsUsersViewModel {
init(
reactions: [ChatMessageReaction] = [],
totalReactionsCount: Int = 0,
isRightAligned: Bool = false
) {
super.init(message: .mock())
self.reactions = reactions
mockedIsRightAligned = isRightAligned
mockedTotalReactionsCount = totalReactionsCount
}

var mockedTotalReactionsCount: Int = 0
override var totalReactionsCount: Int {
mockedTotalReactionsCount
}

var mockedIsRightAligned: Bool = false
override var isRightAligned: Bool {
mockedIsRightAligned
}
}

0 comments on commit f566ec1

Please sign in to comment.