diff --git a/DemoAppSwiftUI/AppDelegate.swift b/DemoAppSwiftUI/AppDelegate.swift index 43778074..8737a3f6 100644 --- a/DemoAppSwiftUI/AppDelegate.swift +++ b/DemoAppSwiftUI/AppDelegate.swift @@ -63,7 +63,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { #endif let utils = Utils( - messageListConfig: MessageListConfig(dateIndicatorPlacement: .messageList), + messageListConfig: MessageListConfig(dateIndicatorPlacement: .messageList, userBlockingEnabled: true), composerConfig: ComposerConfig(isVoiceRecordingEnabled: true) ) streamChat = StreamChat(chatClient: chatClient, utils: utils) diff --git a/DemoAppSwiftUI/BlockedUsersView.swift b/DemoAppSwiftUI/BlockedUsersView.swift new file mode 100644 index 00000000..d0176d69 --- /dev/null +++ b/DemoAppSwiftUI/BlockedUsersView.swift @@ -0,0 +1,50 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import StreamChatSwiftUI +import SwiftUI + +struct BlockedUsersView: View { + + @StateObject var viewModel = BlockedUsersViewModel() + + var body: some View { + ZStack { + if !viewModel.blockedUsers.isEmpty { + List { + ForEach(viewModel.blockedUsers) { blockedUser in + HStack { + MessageAvatarView(avatarURL: blockedUser.imageURL, size: .init(width: 48, height: 48)) + Text(blockedUser.name ?? blockedUser.id) + .font(.headline) + Spacer() + } + .listRowSeparator(.hidden) + } + .onDelete(perform: delete) + } + .toolbar { + EditButton() + } + .listStyle(.plain) + } else { + VStack { + Text("There are currently no blocked users.") + .padding() + Spacer() + } + } + } + .onAppear { + viewModel.loadBlockedUsers() + } + .navigationTitle("Blocked Users") + } + + func delete(at offsets: IndexSet) { + if let first = offsets.first, first < viewModel.blockedUsers.count { + viewModel.unblock(user: viewModel.blockedUsers[first]) + } + } +} diff --git a/DemoAppSwiftUI/BlockedUsersViewModel.swift b/DemoAppSwiftUI/BlockedUsersViewModel.swift new file mode 100644 index 00000000..2647962d --- /dev/null +++ b/DemoAppSwiftUI/BlockedUsersViewModel.swift @@ -0,0 +1,48 @@ +// +// Copyright © 2024 Stream.io Inc. All rights reserved. +// + +import StreamChat +import StreamChatSwiftUI +import SwiftUI + +class BlockedUsersViewModel: ObservableObject { + + @Injected(\.chatClient) var chatClient + + @Published var blockedUsers = [ChatUser]() + + private let currentUserController: CurrentChatUserController + + init() { + currentUserController = InjectedValues[\.chatClient].currentUserController() + currentUserController.synchronize() + } + + func loadBlockedUsers() { + let blockedUserIds = currentUserController.currentUser?.blockedUserIds ?? [] + for blockedUserId in blockedUserIds { + if let user = currentUserController.dataStore.user(id: blockedUserId) { + blockedUsers.append(user) + } else { + let controller = chatClient.userController(userId: blockedUserId) + controller.synchronize { [weak self] _ in + if let user = controller.user { + self?.blockedUsers.append(user) + } + } + } + } + } + + func unblock(user: ChatUser) { + let unblockController = chatClient.userController(userId: user.id) + unblockController.unblock { [weak self] error in + if error == nil { + self?.blockedUsers.removeAll { blocked in + blocked.id == user.id + } + } + } + } +} diff --git a/DemoAppSwiftUI/CustomChannelHeader.swift b/DemoAppSwiftUI/CustomChannelHeader.swift index 5788038a..e18f21a0 100644 --- a/DemoAppSwiftUI/CustomChannelHeader.swift +++ b/DemoAppSwiftUI/CustomChannelHeader.swift @@ -15,7 +15,7 @@ public struct CustomChannelHeader: ToolbarContent { var title: String var currentUserController: CurrentChatUserController @Binding var isNewChatShown: Bool - @Binding var logoutAlertShown: Bool + @Binding var actionsPopupShown: Bool @MainActor public var body: some ToolbarContent { @@ -39,7 +39,7 @@ public struct CustomChannelHeader: ToolbarContent { } ToolbarItem(placement: .navigationBarLeading) { Button { - logoutAlertShown = true + actionsPopupShown = true } label: { StreamLazyImage(url: currentUserController.currentUser?.imageURL) } @@ -55,6 +55,8 @@ struct CustomChannelModifier: ChannelListHeaderViewModifier { @State var isNewChatShown = false @State var logoutAlertShown = false + @State var actionsPopupShown = false + @State var blockedUsersShown = false func body(content: Content) -> some View { ZStack { @@ -63,9 +65,15 @@ struct CustomChannelModifier: ChannelListHeaderViewModifier { title: title, currentUserController: chatClient.currentUserController(), isNewChatShown: $isNewChatShown, - logoutAlertShown: $logoutAlertShown + actionsPopupShown: $actionsPopupShown ) } + + NavigationLink(isActive: $blockedUsersShown) { + BlockedUsersView() + } label: { + EmptyView() + } NavigationLink(isActive: $isNewChatShown) { NewChatView(isNewChatShown: $isNewChatShown) @@ -90,6 +98,19 @@ struct CustomChannelModifier: ChannelListHeaderViewModifier { secondaryButton: .cancel() ) } + .confirmationDialog("", isPresented: $actionsPopupShown) { + Button("Blocked users") { + blockedUsersShown = true + } + + Button("Logout") { + logoutAlertShown = true + } + + Button("Cancel", role: .cancel) {} + } message: { + Text("Select an action") + } } } } diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj index 6eddd91d..9145da55 100644 --- a/StreamChatSwiftUI.xcodeproj/project.pbxproj +++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj @@ -322,6 +322,8 @@ 846AD4D2284F89B10074A0DD /* StreamChatTestTools in Frameworks */ = {isa = PBXBuildFile; productRef = 846AD4D1284F89B10074A0DD /* StreamChatTestTools */; }; 846AD4D4284F95710074A0DD /* CustomChannelHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846AD4D3284F95710074A0DD /* CustomChannelHeader.swift */; }; 846B15F42817E7630017F7A1 /* ChatChannelInfoViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846B15F32817E7630017F7A1 /* ChatChannelInfoViewModel_Tests.swift */; }; + 846B8E2C2C5B8117006A6249 /* BlockedUsersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846B8E2B2C5B8117006A6249 /* BlockedUsersView.swift */; }; + 846B8E2E2C5B8130006A6249 /* BlockedUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846B8E2D2C5B8130006A6249 /* BlockedUsersViewModel.swift */; }; 846D6564279FF0800094B36E /* ReactionUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846D6563279FF0800094B36E /* ReactionUserView.swift */; }; 847110B628611033004A46D6 /* MessageActions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840008BA27E8D64A00282D88 /* MessageActions_Tests.swift */; }; 847305BB28241D8D004AC770 /* ChatChannelHeader_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847305BA28241D8D004AC770 /* ChatChannelHeader_Tests.swift */; }; @@ -885,6 +887,8 @@ 8469592E29BB235400134EA0 /* LazyImageExtensions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyImageExtensions_Tests.swift; sourceTree = ""; }; 846AD4D3284F95710074A0DD /* CustomChannelHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomChannelHeader.swift; sourceTree = ""; }; 846B15F32817E7630017F7A1 /* ChatChannelInfoViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelInfoViewModel_Tests.swift; sourceTree = ""; }; + 846B8E2B2C5B8117006A6249 /* BlockedUsersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersView.swift; sourceTree = ""; }; + 846B8E2D2C5B8130006A6249 /* BlockedUsersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersViewModel.swift; sourceTree = ""; }; 846D6563279FF0800094B36E /* ReactionUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionUserView.swift; sourceTree = ""; }; 847305BA28241D8D004AC770 /* ChatChannelHeader_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelHeader_Tests.swift; sourceTree = ""; }; 847305BC28243D25004AC770 /* WebView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView_Tests.swift; sourceTree = ""; }; @@ -1571,6 +1575,8 @@ 8451617F2AE7C4E2000A9230 /* WhatsAppChannelHeader.swift */, 8417AE912ADEDB6400445021 /* UserRepository.swift */, 8413C4542B4409B600190AF4 /* PinChannelHelpers.swift */, + 846B8E2B2C5B8117006A6249 /* BlockedUsersView.swift */, + 846B8E2D2C5B8130006A6249 /* BlockedUsersViewModel.swift */, 84EDBC36274FE5CD0057218D /* Localizable.strings */, 8465FCCA27468B7500AF091E /* Info.plist */, 8465FCC227468B6A00AF091E /* Assets.xcassets */, @@ -2920,6 +2926,7 @@ files = ( 8465FDDD2747A14700AF091E /* CustomComposerAttachmentView.swift in Sources */, 84335016274BABF3007A1B81 /* NewChatView.swift in Sources */, + 846B8E2C2C5B8117006A6249 /* BlockedUsersView.swift in Sources */, 84335018274BAD4B007A1B81 /* NewChatViewModel.swift in Sources */, 84B288CD274C544B00DD090B /* CreateGroupView.swift in Sources */, 845161802AE7C4E2000A9230 /* WhatsAppChannelHeader.swift in Sources */, @@ -2930,6 +2937,7 @@ 8465FCDE274694D200AF091E /* CustomChannelHeader.swift in Sources */, 8451617E2AE7B093000A9230 /* AppleMessageComposerView.swift in Sources */, 8413C4552B4409B600190AF4 /* PinChannelHelpers.swift in Sources */, + 846B8E2E2C5B8130006A6249 /* BlockedUsersViewModel.swift in Sources */, 84B288D3274D23AF00DD090B /* LoginView.swift in Sources */, 84B288D5274D286500DD090B /* LoginViewModel.swift in Sources */, 8465FCBF27468B6900AF091E /* DemoAppSwiftUIApp.swift in Sources */, @@ -3129,7 +3137,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3564,7 +3572,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3599,7 +3607,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks",