Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f71c2d0
Add threads tab to demo app
nuno-vieira Oct 4, 2024
4cd882a
Add `NoThreadsView` implementation
nuno-vieira Oct 4, 2024
6e9ca92
Add `ThreadList` design implementation + Add `MessagePreviewFormatter`
nuno-vieira Oct 7, 2024
2e59963
Fix Channel List not preselecting a channel for iPads
nuno-vieira Oct 8, 2024
6714a1e
Add background selection to Channel List items on iPad
nuno-vieira Oct 8, 2024
96af171
Revert "Fix Channel List not preselecting a channel for iPads"
nuno-vieira Oct 9, 2024
e10cd60
Revert "Add background selection to Channel List items on iPad"
nuno-vieira Oct 9, 2024
6e40837
Add `ChatThreadList` + `ChatThreadListNavigatableItem`
nuno-vieira Oct 9, 2024
d9c0676
Add `ChatThreadListLoadingView` implementation and handle empty and l…
nuno-vieira Oct 9, 2024
adc9c61
Add `ChatThreadListHeaderViewModifier` implementation
nuno-vieira Oct 9, 2024
d45d28c
Fix Channel List shimmering effect and improve shimmering animation
nuno-vieira Oct 10, 2024
d745725
Add the possibility to customise the background of Thread List
nuno-vieira Oct 10, 2024
6f7b535
Add `ChatThreadListErrorBannerView`
nuno-vieira Oct 10, 2024
79f2afe
Add `ChatThreadListFooterView` + Loading More Theads
nuno-vieira Oct 10, 2024
6364a23
Add mark thread read logic to `ChatChannelViewModel`
nuno-vieira Oct 11, 2024
44b9b7d
Add markThreadAsUnreadAction when message is the root of a thread and…
nuno-vieira Oct 11, 2024
aa57d96
Fix double mark unread action
nuno-vieira Oct 11, 2024
74da45e
Add `ChatThreadListHeaderView` to display new available threads
nuno-vieira Oct 11, 2024
e1be9e4
Add thread selection logic to iPad
nuno-vieira Oct 14, 2024
5ab707b
Add a modifier that wraps the thread list so that the list can be cus…
nuno-vieira Oct 14, 2024
adacc5d
Update CHANGELOG.md
nuno-vieira Oct 14, 2024
8275785
Add missing comments to Thread List View Model
nuno-vieira Oct 14, 2024
b856aa9
Add more doc comments to public views
nuno-vieira Oct 14, 2024
5f74c95
Add background color when a thread is selected on iPad
nuno-vieira Oct 14, 2024
7032e72
Add background color when a channel is selected on iPad
nuno-vieira Oct 14, 2024
e3f1fa8
Fix Channel List not preselecting channel in iPad
nuno-vieira Oct 14, 2024
accd20f
Update CHANGELOG.md
nuno-vieira Oct 14, 2024
47a8578
Add Thread List Item test coverage
nuno-vieira Oct 14, 2024
b7dea0a
Add test coverage to ChatThreadListView
nuno-vieira Oct 15, 2024
163349b
Add Thread List View Model test coverage
nuno-vieira Oct 15, 2024
fec1995
Fix snapshot tests
nuno-vieira Oct 15, 2024
bebb31e
Remove ChatThreadListScreen since it is not needed
nuno-vieira Oct 15, 2024
1fb573b
Fix changelog typoe
nuno-vieira Oct 15, 2024
5fa98d2
Do not pass colors to the view factory
nuno-vieira Oct 15, 2024
937da68
Use message preview formatter from utils
nuno-vieira Oct 15, 2024
e954494
Forgotten public inits
nuno-vieira Oct 15, 2024
13343a0
Remove unused properties in Thread List View
nuno-vieira Oct 15, 2024
4f0eaef
Remove unused colors and utils from ChatThreadListViewModel
nuno-vieira Oct 15, 2024
cbfbc3e
[CI] Snapshots
Oct 15, 2024
149d123
Merge pull request #622 from GetStream/add/threads-v2-snapshots
nuno-vieira Oct 15, 2024
f20898a
Missing public inits
nuno-vieira Oct 15, 2024
dc60bc8
Fix glitch in loading view
nuno-vieira Oct 16, 2024
96aef0f
Add missing comments to some public views
nuno-vieira Oct 16, 2024
48b4e6c
Fix layout shift when a thread has a new unread message
nuno-vieira Oct 16, 2024
f378189
Add `ChatThreadListItemViewModel` to make it easier for customers to …
nuno-vieira Oct 16, 2024
5563c83
Fix thread list item view test not compiling
nuno-vieira Oct 16, 2024
7a91804
[CI] Snapshots
Oct 16, 2024
754ce6c
Merge pull request #623 from GetStream/add/threads-v2-snapshots
nuno-vieira Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions Brewfile.lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"entries": {
"brew": {
"mint": {
"version": "0.17.5",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_sequoia": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:a754e28b7b9e4e13c31af783857de64d2550b8866c2c9eb3ac9216154ab0f25a",
"sha256": "a754e28b7b9e4e13c31af783857de64d2550b8866c2c9eb3ac9216154ab0f25a"
},
"arm64_sonoma": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:ada351985ef562807e7460f869c527bb314600311738a944219225226f43addf",
"sha256": "ada351985ef562807e7460f869c527bb314600311738a944219225226f43addf"
},
"arm64_ventura": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:250948fe6fc14179d7c381d084a90d6796861ba9a8456617cadda9ac62cbc2b8",
"sha256": "250948fe6fc14179d7c381d084a90d6796861ba9a8456617cadda9ac62cbc2b8"
},
"arm64_monterey": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:6546b80b980a45036415162189dd340b1f8d3f4e82a80d40a24e7b5dd672eb04",
"sha256": "6546b80b980a45036415162189dd340b1f8d3f4e82a80d40a24e7b5dd672eb04"
},
"arm64_big_sur": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:39f9d254b248a44bb44e399081b7e50a6c598834e2bf86bb7de3ebc349f11e0d",
"sha256": "39f9d254b248a44bb44e399081b7e50a6c598834e2bf86bb7de3ebc349f11e0d"
},
"sonoma": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:154b8b94602d6d38249cfa936f7d071d9113935b3756d5781021fe04c3971e29",
"sha256": "154b8b94602d6d38249cfa936f7d071d9113935b3756d5781021fe04c3971e29"
},
"ventura": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:068f9984e81b578f2ed6cef4cc9659835a689bdecf121651ea24ebcfefd49339",
"sha256": "068f9984e81b578f2ed6cef4cc9659835a689bdecf121651ea24ebcfefd49339"
},
"monterey": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:f8b09a640942548a151c7450c85f33d40162c7540049666131740d49c68e61e6",
"sha256": "f8b09a640942548a151c7450c85f33d40162c7540049666131740d49c68e61e6"
},
"big_sur": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:528ea907912e8002cd3a769e8ddda4556cf2482122c3f848a7d923146df37101",
"sha256": "528ea907912e8002cd3a769e8ddda4556cf2482122c3f848a7d923146df37101"
},
"x86_64_linux": {
"cellar": "/home/linuxbrew/.linuxbrew/Cellar",
"url": "https://ghcr.io/v2/homebrew/core/mint/blobs/sha256:7c8dd63f0310a46f67550f92ee48a370fadfc1a4d884b8a3904a36b7b610b3f2",
"sha256": "7c8dd63f0310a46f67550f92ee48a370fadfc1a4d884b8a3904a36b7b610b3f2"
}
}
}
},
"sonar-scanner": {
"version": "6.2.1.4610",
"bottle": {
"rebuild": 1,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"all": {
"cellar": ":any_skip_relocation",
"url": "https://ghcr.io/v2/homebrew/core/sonar-scanner/blobs/sha256:5e24f759a690b4abb55737325a6c9e94d42b2235abd0e93021306d6016d967e6",
"sha256": "5e24f759a690b4abb55737325a6c9e94d42b2235abd0e93021306d6016d967e6"
}
}
}
}
}
},
"system": {
"macos": {
"sonoma": {
"HOMEBREW_VERSION": "4.4.0",
"HOMEBREW_PREFIX": "/opt/homebrew",
"Homebrew/homebrew-core": "api",
"CLT": "16.0.0.0.1.1724870825",
"Xcode": "15.4",
"macOS": "14.7"
}
}
}
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Upcoming

### ✅ Added
- New Thread List UI Component [#621](https://github.com/GetStream/stream-chat-swiftui/pull/621)
- Handles marking a thread read in `ChatChannelViewModel` [#621](https://github.com/GetStream/stream-chat-swiftui/pull/621)
- Adds `ViewFactory.makeChannelListItemBackground` [#621](https://github.com/GetStream/stream-chat-swiftui/pull/621)
### 🐞 Fixed
- Fix Channel List loading view shimmering effect not working [#621](https://github.com/GetStream/stream-chat-swiftui/pull/621)
- Fix Channel List not preselecting the Channel on iPad [#621](https://github.com/GetStream/stream-chat-swiftui/pull/621)
### 🔄 Changed
- Channel List Item has now a background color when it is selected on iPad [#621](https://github.com/GetStream/stream-chat-swiftui/pull/621)

# [4.64.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.64.0)
_October 03, 2024_
Expand Down
64 changes: 49 additions & 15 deletions DemoAppSwiftUI/DemoAppSwiftUIApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct DemoAppSwiftUIApp: App {

@ObservedObject var appState = AppState.shared
@ObservedObject var notificationsHandler = NotificationsHandler.shared

var channelListController: ChatChannelListController? {
appState.channelListController
}
Expand All @@ -27,18 +27,14 @@ struct DemoAppSwiftUIApp: App {
case .notLoggedIn:
LoginView()
case .loggedIn:
if notificationsHandler.notificationChannelId != nil {
ChatChannelListView(
viewFactory: DemoAppFactory.shared,
channelListController: channelListController,
selectedChannelId: notificationsHandler.notificationChannelId
)
} else {
ChatChannelListView(
viewFactory: DemoAppFactory.shared,
channelListController: channelListController
)
}
TabView {
channelListView()
.tabItem { Label("Chat", systemImage: "message") }
.badge(appState.unreadCount.channels)
threadListView()
.tabItem { Label("Threads", systemImage: "text.bubble") }
.badge(appState.unreadCount.threads)
}
}
}
.onChange(of: appState.userState) { newValue in
Expand All @@ -57,13 +53,33 @@ struct DemoAppSwiftUIApp: App {
appState.channelListController = chatClient.channelListController(query: channelListQuery)
}
*/
appState.currentUserController = chatClient.currentUserController()
notificationsHandler.setupRemoteNotifications()
}
}
}

func channelListView() -> ChatChannelListView<DemoAppFactory> {
if notificationsHandler.notificationChannelId != nil {
ChatChannelListView(
viewFactory: DemoAppFactory.shared,
channelListController: channelListController,
selectedChannelId: notificationsHandler.notificationChannelId
)
} else {
ChatChannelListView(
viewFactory: DemoAppFactory.shared,
channelListController: channelListController
)
}
}

func threadListView() -> ChatThreadListView<DemoAppFactory> {
ChatThreadListView(viewFactory: DemoAppFactory.shared)
}
}

class AppState: ObservableObject {
class AppState: ObservableObject, CurrentChatUserControllerDelegate {

@Published var userState: UserState = .launchAnimation {
willSet {
Expand All @@ -72,12 +88,30 @@ class AppState: ObservableObject {
}
}
}


@Published var unreadCount: UnreadCount = .noUnread

var channelListController: ChatChannelListController?
var currentUserController: CurrentChatUserController? {
didSet {
currentUserController?.delegate = self
currentUserController?.synchronize()
}
}

static let shared = AppState()

private init() {}

func currentUserController(_ controller: CurrentChatUserController, didChangeCurrentUserUnreadCount: UnreadCount) {
self.unreadCount = didChangeCurrentUserUnreadCount
let totalUnreadBadge = unreadCount.channels + unreadCount.threads
if #available(iOS 16.0, *) {
UNUserNotificationCenter.current().setBadgeCount(totalUnreadBadge)
} else {
UIApplication.shared.applicationIconBadgeNumber = totalUnreadBadge
}
}
}

enum UserState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ struct PinnedMessageView: View {
if message.poll != nil {
return "📊 \(L10n.Channel.Item.poll)"
}
return channel.attachmentPreviewText(for: message) ?? message.adjustedText
let messageFormatter = InjectedValues[\.utils].messagePreviewFormatter
return messageFormatter.formatAttachmentContent(for: message) ?? message.adjustedText
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
if canMarkRead {
sendReadEventIfNeeded(for: first)
}
if shouldMarkThreadRead {
sendThreadReadEvent()
}
}

public func scrollToLastMessage() {
Expand Down Expand Up @@ -346,6 +349,9 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
sendReadEventIfNeeded(for: message)
}
}
if index == 0 && shouldMarkThreadRead {
sendThreadReadEvent()
}
}

open func groupMessages() {
Expand Down Expand Up @@ -655,7 +661,24 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
}
}
}


private var shouldMarkThreadRead: Bool {
guard UIApplication.shared.applicationState == .active else {
return false
}
guard messageController?.replies.isEmpty == false else {
return false
}

return channelDataSource.hasLoadedAllNextMessages
}

private func sendThreadReadEvent() {
throttler.throttle { [weak self] in
self?.messageController?.markThreadRead()
}
}

private func handleDateChange() {
guard showScrollToLatestButton == true, let currentDate = currentDate else {
currentDateString = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ extension MessageAction {
messageActions.append(copyAction)
}

if message.isRootOfThread {
let messageController = InjectedValues[\.utils]
.channelControllerFactory
.makeMessageController(for: message.id, channelId: channel.cid)
// At the moment, this is the only way to know if we are inside a thread.
// This should be optimised in the future and provide the view context.
let isInsideThreadView = messageController.replies.count > 0
if isInsideThreadView {
let markThreadUnreadAction = markThreadAsUnreadAction(
messageController: messageController,
message: message,
onFinish: onFinish,
onError: onError
)
messageActions.append(markThreadUnreadAction)
}
} else if !message.isSentByCurrentUser {
if !message.isPartOfThread || message.showReplyInChannel {
let markUnreadAction = markAsUnreadAction(
for: message,
channel: channel,
chatClient: chatClient,
onFinish: onFinish,
onError: onError
)

messageActions.append(markUnreadAction)
}
}

if message.isSentByCurrentUser {
if message.poll == nil {
let editAction = editMessageAction(
Expand All @@ -130,18 +160,6 @@ extension MessageAction {

messageActions.append(deleteAction)
} else {
if !message.isPartOfThread || message.showReplyInChannel {
let markUnreadAction = markAsUnreadAction(
for: message,
channel: channel,
chatClient: chatClient,
onFinish: onFinish,
onError: onError
)

messageActions.append(markUnreadAction)
}

if channel.canFlagMessage {
let flagAction = flagMessageAction(
for: message,
Expand Down Expand Up @@ -512,6 +530,38 @@ extension MessageAction {
return unreadAction
}

private static func markThreadAsUnreadAction(
messageController: ChatMessageController,
message: ChatMessage,
onFinish: @escaping (MessageActionInfo) -> Void,
onError: @escaping (Error) -> Void
) -> MessageAction {
let action = {
messageController.markThreadUnread() { error in
if let error {
onError(error)
} else {
onFinish(
MessageActionInfo(
message: message,
identifier: MessageActionId.markUnread
)
)
}
}
}
let unreadAction = MessageAction(
id: MessageActionId.markUnread,
title: L10n.Message.Actions.markUnread,
iconName: "message.badge",
action: action,
confirmationPopup: nil,
isDestructive: false
)

return unreadAction
}

private static func muteAction(
for message: ChatMessage,
channel: ChatChannel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public struct ChannelList<Factory: ViewFactory>: View {

/// LazyVStack displaying list of channels.
public struct ChannelsLazyVStack<Factory: ViewFactory>: View {
@Injected(\.colors) private var colors

private var factory: Factory
var channels: LazyCachedMapCollection<ChatChannel>
Expand Down Expand Up @@ -170,6 +171,10 @@ public struct ChannelsLazyVStack<Factory: ViewFactory>: View {
trailingSwipeLeftButtonTapped: trailingSwipeLeftButtonTapped,
leadingSwipeButtonTapped: leadingSwipeButtonTapped
)
.background(factory.makeChannelListItemBackground(
channel: channel,
isSelected: selectedChannel?.channel.id == channel.id
))
.onAppear {
if let index = channels.firstIndex(where: { chatChannel in
chatChannel.id == channel.id
Expand Down
Loading
Loading