Skip to content

Commit 9adc3cf

Browse files
authored
Demo app changes for pinned and archived channels (#683)
* Use native pinning support for channels * Add archive and unarchive actions. Add predefined channel queries and action sheet for selecting different queries
1 parent 194eea5 commit 9adc3cf

11 files changed

+217
-60
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright © 2024 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
import StreamChat
7+
8+
enum ChannelListQueryIdentifier: String, CaseIterable, Identifiable {
9+
case initial
10+
case archived
11+
case pinned
12+
case unarchivedAndPinnedSorted
13+
14+
var id: String {
15+
rawValue
16+
}
17+
18+
var title: String {
19+
switch self {
20+
case .initial: "Initial Channels"
21+
case .archived: "Archived Channels"
22+
case .pinned: "Pinned Channels"
23+
case .unarchivedAndPinnedSorted: "Sort by Pinned and Ignore Archived Channels"
24+
}
25+
}
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// Copyright © 2024 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import StreamChat
6+
import StreamChatSwiftUI
7+
import SwiftUI
8+
9+
struct ChooseChannelQueryView: View {
10+
static let queryIdentifiers = ChannelListQueryIdentifier.allCases.sorted(using: KeyPathComparator(\.title))
11+
12+
var body: some View {
13+
ForEach(Self.queryIdentifiers) { queryIdentifier in
14+
Button {
15+
AppState.shared.setChannelQueryIdentifier(queryIdentifier)
16+
} label: {
17+
Text(queryIdentifier.title)
18+
}
19+
}
20+
}
21+
}

DemoAppSwiftUI/CustomChannelHeader.swift renamed to DemoAppSwiftUI/ChannelHeader/CustomChannelHeader.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ struct CustomChannelModifier: ChannelListHeaderViewModifier {
5353

5454
var title: String
5555

56+
@State var isChooseChannelQueryShown = false
5657
@State var isNewChatShown = false
5758
@State var logoutAlertShown = false
5859
@State var actionsPopupShown = false
@@ -99,18 +100,26 @@ struct CustomChannelModifier: ChannelListHeaderViewModifier {
99100
)
100101
}
101102
.confirmationDialog("", isPresented: $actionsPopupShown) {
102-
Button("Blocked users") {
103+
Button("Choose Channel Query") {
104+
isChooseChannelQueryShown = true
105+
}
106+
Button("Show Blocked Users") {
103107
blockedUsersShown = true
104108
}
105109

106-
Button("Logout") {
110+
Button("Logout", role: .destructive) {
107111
logoutAlertShown = true
108112
}
109113

110114
Button("Cancel", role: .cancel) {}
111115
} message: {
112116
Text("Select an action")
113117
}
118+
.confirmationDialog("", isPresented: $isChooseChannelQueryShown) {
119+
ChooseChannelQueryView()
120+
} message: {
121+
Text("Choose a channel query")
122+
}
114123
}
115124
}
116125
}

DemoAppSwiftUI/DemoAppSwiftUIApp.swift

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright © 2024 Stream.io Inc. All rights reserved.
33
//
44

5+
import Combine
56
import StreamChat
67
import StreamChatSwiftUI
78
import SwiftUI
@@ -39,25 +40,11 @@ struct DemoAppSwiftUIApp: App {
3940
.tabItem { Label("Threads", systemImage: "text.bubble") }
4041
.badge(appState.unreadCount.threads)
4142
}
43+
.id(appState.contentIdentifier)
4244
}
4345
}
4446
.onChange(of: appState.userState) { newValue in
4547
if newValue == .loggedIn {
46-
/*
47-
if let currentUserId = chatClient.currentUserId {
48-
let pinnedByKey = ChatChannel.isPinnedBy(keyForUserId: currentUserId)
49-
let channelListQuery = ChannelListQuery(
50-
filter: .containMembers(userIds: [currentUserId]),
51-
sort: [
52-
.init(key: .custom(keyPath: \.isPinned, key: pinnedByKey), isAscending: true),
53-
.init(key: .lastMessageAt),
54-
.init(key: .updatedAt)
55-
]
56-
)
57-
appState.channelListController = chatClient.channelListController(query: channelListQuery)
58-
}
59-
*/
60-
appState.currentUserController = chatClient.currentUserController()
6148
notificationsHandler.setupRemoteNotifications()
6249
}
6350
}
@@ -86,28 +73,55 @@ struct DemoAppSwiftUIApp: App {
8673
}
8774

8875
class AppState: ObservableObject, CurrentChatUserControllerDelegate {
76+
@Injected(\.chatClient) var chatClient: ChatClient
8977

90-
@Published var userState: UserState = .launchAnimation {
91-
willSet {
92-
if newValue == .notLoggedIn && userState == .loggedIn {
93-
channelListController = nil
94-
}
95-
}
96-
}
97-
78+
// Recreate the content view when channel query changes.
79+
@Published private(set) var contentIdentifier: String = ""
80+
81+
@Published var userState: UserState = .launchAnimation
9882
@Published var unreadCount: UnreadCount = .noUnread
9983

100-
var channelListController: ChatChannelListController?
101-
var currentUserController: CurrentChatUserController? {
102-
didSet {
103-
currentUserController?.delegate = self
104-
currentUserController?.synchronize()
105-
}
106-
}
84+
private(set) var channelListController: ChatChannelListController?
85+
private(set) var currentUserController: CurrentChatUserController?
86+
private var cancellables = Set<AnyCancellable>()
10787

10888
static let shared = AppState()
10989

110-
private init() {}
90+
private init() {
91+
$userState
92+
.removeDuplicates()
93+
.filter { $0 == .notLoggedIn }
94+
.sink { [weak self] _ in
95+
self?.didLogout()
96+
}
97+
.store(in: &cancellables)
98+
$userState
99+
.removeDuplicates()
100+
.filter { $0 == .loggedIn }
101+
.sink { [weak self] _ in
102+
self?.didLogin()
103+
}
104+
.store(in: &cancellables)
105+
}
106+
107+
private func didLogout() {
108+
channelListController = nil
109+
currentUserController = nil
110+
}
111+
112+
private func didLogin() {
113+
setChannelQueryIdentifier(.initial)
114+
115+
currentUserController = chatClient.currentUserController()
116+
currentUserController?.delegate = self
117+
currentUserController?.synchronize()
118+
}
119+
120+
func setChannelQueryIdentifier(_ identifier: ChannelListQueryIdentifier) {
121+
let query = AppState.channelListQuery(forIdentifier: identifier, chatClient: chatClient)
122+
channelListController = chatClient.channelListController(query: query)
123+
contentIdentifier = identifier.rawValue
124+
}
111125

112126
func currentUserController(_ controller: CurrentChatUserController, didChangeCurrentUserUnreadCount: UnreadCount) {
113127
unreadCount = didChangeCurrentUserUnreadCount
@@ -125,3 +139,43 @@ enum UserState {
125139
case notLoggedIn
126140
case loggedIn
127141
}
142+
143+
extension AppState {
144+
private static func channelListQuery(
145+
forIdentifier identifier: ChannelListQueryIdentifier,
146+
chatClient: ChatClient
147+
) -> ChannelListQuery {
148+
guard let currentUserId = chatClient.currentUserId else { fatalError("Not logged in") }
149+
switch identifier {
150+
case .initial:
151+
return ChannelListQuery(
152+
filter: .containMembers(userIds: [currentUserId])
153+
)
154+
case .unarchivedAndPinnedSorted:
155+
return ChannelListQuery(
156+
filter: .and([
157+
.containMembers(userIds: [currentUserId]),
158+
.equal(.archived, to: false)
159+
]),
160+
sort: [
161+
.init(key: .pinnedAt, isAscending: false),
162+
.init(key: .default)
163+
]
164+
)
165+
case .archived:
166+
return ChannelListQuery(
167+
filter: .and([
168+
.containMembers(userIds: [currentUserId]),
169+
.equal(.archived, to: true)
170+
])
171+
)
172+
case .pinned:
173+
return ChannelListQuery(
174+
filter: .and([
175+
.containMembers(userIds: [currentUserId]),
176+
.equal(.pinned, to: true)
177+
])
178+
)
179+
}
180+
}
181+
}

DemoAppSwiftUI/PinChannelHelpers.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,6 @@ import StreamChat
66
import StreamChatSwiftUI
77
import SwiftUI
88

9-
extension ChatChannel {
10-
static func isPinnedBy(keyForUserId userId: UserId) -> String {
11-
"is_pinned_by_\(userId)"
12-
}
13-
14-
var isPinned: Bool {
15-
guard let userId = membership?.id else { return false }
16-
let key = Self.isPinnedBy(keyForUserId: userId)
17-
return extraData[key]?.boolValue ?? false
18-
}
19-
}
20-
219
struct DemoAppChatChannelListItem: View {
2210

2311
@Injected(\.fonts) private var fonts

DemoAppSwiftUI/ViewFactoryExamples.swift

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ class DemoAppFactory: ViewFactory {
3131
onDismiss: onDismiss,
3232
onError: onError
3333
)
34+
let archiveChannel = archiveChannelAction(for: channel, onDismiss: onDismiss, onError: onError)
3435
let pinChannel = pinChannelAction(for: channel, onDismiss: onDismiss, onError: onError)
36+
actions.insert(archiveChannel, at: actions.count - 2)
3537
actions.insert(pinChannel, at: actions.count - 2)
3638
return actions
3739
}
@@ -76,6 +78,40 @@ class DemoAppFactory: ViewFactory {
7678
ShowProfileModifier(messageModifierInfo: messageModifierInfo, mentionsHandler: mentionsHandler)
7779
}
7880

81+
private func archiveChannelAction(
82+
for channel: ChatChannel,
83+
onDismiss: @escaping () -> Void,
84+
onError: @escaping (Error) -> Void
85+
) -> ChannelAction {
86+
ChannelAction(
87+
title: channel.isArchived ? "Unarchive Channel" : "Archive Channel",
88+
iconName: "archivebox",
89+
action: { [weak self] in
90+
guard let self else { return }
91+
let channelController = self.chatClient.channelController(for: channel.cid)
92+
if channel.isArchived {
93+
channelController.unarchive { error in
94+
if let error = error {
95+
onError(error)
96+
} else {
97+
onDismiss()
98+
}
99+
}
100+
} else {
101+
channelController.archive { error in
102+
if let error = error {
103+
onError(error)
104+
} else {
105+
onDismiss()
106+
}
107+
}
108+
}
109+
},
110+
confirmationPopup: nil,
111+
isDestructive: false
112+
)
113+
}
114+
79115
private func pinChannelAction(
80116
for channel: ChatChannel,
81117
onDismiss: @escaping () -> Void,
@@ -87,14 +123,21 @@ class DemoAppFactory: ViewFactory {
87123
action: { [weak self] in
88124
guard let self else { return }
89125
let channelController = self.chatClient.channelController(for: channel.cid)
90-
let userId = channelController.channel?.membership?.id ?? ""
91-
let pinnedKey = ChatChannel.isPinnedBy(keyForUserId: userId)
92-
let newState = !channel.isPinned
93-
channelController.partialChannelUpdate(extraData: [pinnedKey: .bool(newState)]) { error in
94-
if let error = error {
95-
onError(error)
96-
} else {
97-
onDismiss()
126+
if channel.isPinned {
127+
channelController.unpin { error in
128+
if let error = error {
129+
onError(error)
130+
} else {
131+
onDismiss()
132+
}
133+
}
134+
} else {
135+
channelController.pin { error in
136+
if let error = error {
137+
onError(error)
138+
} else {
139+
onDismiss()
140+
}
98141
}
99142
}
100143
},

0 commit comments

Comments
 (0)