diff --git a/DemoAppSwiftUI/CreateGroupView.swift b/DemoAppSwiftUI/CreateGroupView.swift index 13fb7977..8bd0b4d3 100644 --- a/DemoAppSwiftUI/CreateGroupView.swift +++ b/DemoAppSwiftUI/CreateGroupView.swift @@ -48,7 +48,7 @@ struct CreateGroupView: View, KeyboardReadable { } } .listStyle(.plain) - } + }.padding(.top) .toolbarThemed(content: { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoHelperViews.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoHelperViews.swift index d5f7a9f3..8676db76 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoHelperViews.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoHelperViews.swift @@ -76,13 +76,15 @@ public struct ChatInfoOptionsView: View { Divider() - ChannelInfoItemView( - icon: images.muted, - title: viewModel.mutedText, - verticalPadding: 12 - ) { - Toggle(isOn: $viewModel.muted) { - EmptyView() + if viewModel.shouldShowMuteChannelButton { + ChannelInfoItemView( + icon: images.muted, + title: viewModel.mutedText, + verticalPadding: 12 + ) { + Toggle(isOn: $viewModel.muted) { + EmptyView() + } } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoView.swift index a6a8837d..91b4955b 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoView.swift @@ -53,6 +53,7 @@ public struct ChatChannelInfoView: View, KeyboardReadable ChatInfoParticipantsView( factory: factory, participants: viewModel.displayedParticipants, + channel: viewModel.channel, onItemAppear: viewModel.onParticipantAppear(_:), selectedParticipant: $viewModel.selectedParticipant ) @@ -101,7 +102,7 @@ public struct ChatChannelInfoView: View, KeyboardReadable ) } } - } + }.padding(.top) } .overlay( popupShown ? @@ -158,7 +159,8 @@ struct ChatChannelInfoViewHeaderViewModifier: ViewModifier { @Injected(\.colors) private var colors @Injected(\.fonts) private var fonts - let viewModel: ChatChannelInfoViewModel + @ObservedObject var viewModel: ChatChannelInfoViewModel + func body(content: Content) -> some View { if #available(iOS 26.0, *) { diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoViewModel.swift index 4c9051f5..b1d15b0f 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatChannelInfoViewModel.swift @@ -43,6 +43,10 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele channel.ownCapabilities.contains(.leaveChannel) } } + + open var shouldShowMuteChannelButton: Bool { + channel.ownCapabilities.contains(.muteChannel) + } open var canRenameChannel: Bool { channel.ownCapabilities.contains(.updateChannel) @@ -87,7 +91,7 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele } } - public var leaveButtonTitle: String { + open var leaveButtonTitle: String { if channel.isDirectMessageChannel { L10n.Alert.Actions.deleteChannelTitle } else { @@ -95,7 +99,7 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele } } - public var leaveConversationDescription: String { + open var leaveConversationDescription: String { if channel.isDirectMessageChannel { L10n.Alert.Actions.deleteChannelMessage } else { @@ -180,7 +184,7 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele loadAdditionalUsers() } - public func leaveConversationTapped(completion: @escaping () -> Void) { + open func leaveConversationTapped(completion: @escaping () -> Void) { if !channel.isDirectMessageChannel { removeUserFromConversation(completion: completion) } else { @@ -225,9 +229,7 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele addUsersShown = false } - // MARK: - private - - private func removeUserFromConversation(completion: @escaping () -> Void) { + public func removeUserFromConversation(completion: @escaping () -> Void) { guard let userId = chatClient.currentUserId else { return } channelController.removeMembers(userIds: [userId]) { [weak self] error in if error != nil { @@ -237,7 +239,9 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele } } } - + + // MARK: - private + private func deleteChannel(completion: @escaping () -> Void) { channelController.deleteChannel { [weak self] error in if error != nil { @@ -278,11 +282,11 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele } open func participantActions(for participant: ParticipantInfo) -> [ParticipantAction] { - var actions = [ParticipantAction]() + var actions = [ParticipantAction?]() var directMessageAction = ParticipantAction( title: L10n.Channel.Item.sendDirectMessage, - iconName: "message.circle.fill", + iconName: "message", action: {}, confirmationPopup: nil, isDestructive: false @@ -339,7 +343,7 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele actions.append(cancel) - return actions + return actions.compactMap({$0}) } public func muteAction( @@ -403,11 +407,11 @@ open class ChatChannelInfoViewModel: ObservableObject, ChatChannelControllerDele return unmuteUser } - public func removeUserAction( + open func removeUserAction( participant: ParticipantInfo, onDismiss: @escaping () -> Void, onError: @escaping (Error) -> Void - ) -> ParticipantAction { + ) -> ParticipantAction? { let action = { [weak self] in guard let self else { onError(ClientError.Unexpected("Self is nil")) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatInfoParticipantsView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatInfoParticipantsView.swift index 2bf11a91..aed501ce 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatInfoParticipantsView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/ChatInfoParticipantsView.swift @@ -16,16 +16,19 @@ public struct ChatInfoParticipantsView: View { let factory: Factory var participants: [ParticipantInfo] var onItemAppear: (ParticipantInfo) -> Void + private let channel: ChatChannel public init( factory: Factory = DefaultViewFactory.shared, participants: [ParticipantInfo], + channel: ChatChannel, onItemAppear: @escaping (ParticipantInfo) -> Void, selectedParticipant: Binding = .constant(nil) ) { self.factory = factory self.participants = participants self.onItemAppear = onItemAppear + self.channel = channel _selectedParticipant = selectedParticipant } @@ -41,14 +44,24 @@ public struct ChatInfoParticipantsView: View { ) factory.makeMessageAvatarView(for: displayInfo) - VStack(alignment: .leading, spacing: 4) { - Text(participant.displayName) - .lineLimit(1) - .font(fonts.bodyBold) - Text(participant.onlineInfoText) - .font(fonts.footnote) - .foregroundColor(Color(colors.textLowEmphasis)) + HStack(alignment: .center) { + VStack(alignment: .leading, spacing: 4) { + Text(participant.displayName) + .lineLimit(1) + .font(fonts.bodyBold) + Text(participant.onlineInfoText) + .font(fonts.footnote) + .foregroundColor(Color(colors.textLowEmphasis)) + } + Spacer() + + if channel.createdBy?.id == participant.chatUser.id { + Text(L10n.chatGroupInfoOwner) + .font(fonts.footnote) + .foregroundColor(Color(colors.textLowEmphasis)) + } } + Spacer() } .padding(.all, 8) diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelSwipeableListItem.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelSwipeableListItem.swift index 511d043d..594005f8 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelSwipeableListItem.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelSwipeableListItem.swift @@ -132,7 +132,6 @@ public struct ChatChannelSwipeableListItem Void var rightButtonTapped: (ChatChannel) -> Void + + var hasActions: Bool { + channel.ownCapabilities.contains(.moreOptionsChannel) || + channel.ownCapabilities.contains(.deleteChannel) + } public var body: some View { HStack { Spacer() ZStack { HStack(spacing: 0) { - ActionItemButton(imageName: "ellipsis", action: { - withAnimation { - leftButtonTapped(channel) - } - }) - .frame(width: buttonWidth) - .foregroundColor(Color(colors.text)) - .background(Color(colors.background1)) + if channel.ownCapabilities.contains(.moreOptionsChannel) { + ActionItemButton(imageName: "ellipsis", action: { + withAnimation { + leftButtonTapped(channel) + } + }) + .frame(width: buttonWidth) + .foregroundColor(Color(colors.text)) + .background(Color(colors.background1)) + } if channel.ownCapabilities.contains(.deleteChannel) { ActionItemButton(imageName: "trash", action: { @@ -331,3 +334,11 @@ public struct ActionItemButton: View { } } } + +/* + If you wanna add this button to 'ChannelCapability' of ChatChannel manual + */ +extension ChannelCapability { + /// Ability to add more Options the channel. + public static let moreOptionsChannel: Self = "moreOptions-channel" +} diff --git a/Sources/StreamChatSwiftUI/Generated/L10n.swift b/Sources/StreamChatSwiftUI/Generated/L10n.swift index 9c4dcf11..8f8754e7 100644 --- a/Sources/StreamChatSwiftUI/Generated/L10n.swift +++ b/Sources/StreamChatSwiftUI/Generated/L10n.swift @@ -6,6 +6,8 @@ import Foundation // MARK: - Strings internal enum L10n { + /// Owner + internal static var chatGroupInfoOwner: String { L10n.tr("Localizable", "chat_group_info_owner") } /// %d of %d internal static func currentSelection(_ p1: Int, _ p2: Int) -> String { return L10n.tr("Localizable", "current-selection", p1, p2) diff --git a/Sources/StreamChatSwiftUI/Resources/en.lproj/Localizable.strings b/Sources/StreamChatSwiftUI/Resources/en.lproj/Localizable.strings index 619e24b3..dbfb37f4 100644 --- a/Sources/StreamChatSwiftUI/Resources/en.lproj/Localizable.strings +++ b/Sources/StreamChatSwiftUI/Resources/en.lproj/Localizable.strings @@ -236,3 +236,5 @@ "thread.error.message" = "Error loading threads"; "thread.no-content.message" = "No threads here yet..."; "thread.item.replied-to" = "replied to: %@"; + +"chat_group_info_owner" = "Owner";