8000 Add support for bounced message alert actions by nuno-vieira · Pull Request #764 · GetStream/stream-chat-swiftui · GitHub
[go: up one dir, main page]

Skip to content

Add support for bounced message alert actions #764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Upcoming

### ✅ Added
- Add `Utils.MessageListConfig.bouncedMessagesAlertActionsEnabled` to support bounced actions alert [#764](https://github.com/GetStream/stream-chat-swiftui/pull/764)
- Add `ViewFactory.makeBouncedMessageActionsModifier()` to customize the new bounced actions alert [#764](https://github.com/GetStream/stream-chat-swiftui/pull/764)
### 🐞 Fixed
- Fix visibility of tabbar when reactions are shown [#750](https://github.com/GetStream/stream-chat-swiftui/pull/750)
- Show all members in direct message channel info view [#760](https://github.com/GetStream/stream-chat-swiftui/pull/760)
### 🔄 Changed
- Only show "Pin/Unpin message" Action if user has permission [#749](https://github.com/GetStream/stream-chat-swiftui/pull/749)
- Filter deactivated users in channel info view [#758](https://github.com/GetStream/stream-chat-swiftui/pull/758)
- Bounced message actions will now be shown as an alert instead of a context menu by default [#764](https://github.com/GetStream/stream-chat-swiftui/pull/764)
### 🎭 New Localizations
Add localizable keys for supporting moderation alerts:
- `message.moderation.alert.title`
- `message.moderation.alert.message`
- `message.moderation.alert.resend`
- `message.moderation.alert.edit`
- `message.moderation.alert.delete`
- `message.moderation.alert.cancel`

# [4.72.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.72.0)
_February 04, 2025_
Expand Down
1 change: 1 addition & 0 deletions DemoAppSwiftUI/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
messageListConfig: MessageListConfig(
dateIndicatorPlacement: .messageList,
userBlockingEnabled: true,
bouncedMessagesAlertActionsEnabled: true,
skipEditedMessageLabel: { message in
message.extraData["ai_generated"]?.boolValue == true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Copyright © 2025 Stream.io Inc. All rights reserved.
//

import StreamChat
import SwiftUI

/// The modifier that shows the actions for a bounced message.
///
/// This modifier is only used if `Utils.messageListConfig.bouncedMessagesAlertActionsEnabled` is `true`.
public struct BouncedMessageActionsModifier: ViewModifier {
@ObservedObject private var viewModel: ChatChannelViewModel

public init(
viewModel: ChatChannelViewModel
) {
self.viewModel = viewModel
}

public func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content
.alert(
L10n.Message.Moderation.Alert.title,
isPresented: $viewModel.bouncedActionsViewShown
) {
Button(L10n.Message.Moderation.Alert.resend) {
resendBouncedMessage()
}
Button(L10n.Message.Moderation.Alert.edit) {
editBouncedMessage()
}
Button(L10n.Message.Moderation.Alert.delete, role: .destructive) {
deleteBouncedMessage()
}
Button(L10n.Message.Moderation.Alert.cancel, role: .cancel, action: {
viewModel.bouncedActionsViewShown = false
})
} message: {
Text(L10n.Message.Moderation.Alert.message)
}
} else {
content
.actionSheet(isPresented: $viewModel.bouncedActionsViewShown) {
ActionSheet(
title: Text(L10n.Message.Moderation.Alert.title),
message: Text(L10n.Message.Moderation.Alert.message),
buttons: [
.default(Text(L10n.Message.Moderation.Alert.resend)) {
resendBouncedMessage()
},
.default(Text(L10n.Message.Moderation.Alert.edit)) {
editBouncedMessage()
},
.destructive(Text(L10n.Message.Moderation.Alert.delete)) {
deleteBouncedMessage()
},
.cancel(Text(L10n.Message.Moderation.Alert.cancel))
]
)
}
}
}

private func editBouncedMessage() {
guard let bouncedMessage = viewModel.bouncedMessage else {
return
}
viewModel.editMessage(bouncedMessage)
}

private func resendBouncedMessage() {
guard let bouncedMessage = viewModel.bouncedMessage else {
return
}
viewModel.resendMessage(bouncedMessage)
}

private func deleteBouncedMessage() {
guard let bouncedMessage = viewModel.bouncedMessage else {
return
}
viewModel.deleteMessage(bouncedMessage)
}
}
12 changes: 9 additions & 3 deletions Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ public struct ChatChannelView<Factory: ViewFactory>: View, KeyboardReadable {
onMessageAppear: viewModel.handleMessageAppear(index:scrollDirection:),
onScrollToBottom: viewModel.scrollToLastMessage,
onLongPress: { displayInfo in
messageDisplayInfo = displayInfo
withAnimation {
viewModel.showReactionOverlay(for: AnyView(self))
let isBouncedAlertEnabled = utils.messageListConfig.bouncedMessagesAlertActionsEnabled
if isBouncedAlertEnabled && displayInfo.message.isBounced {
viewModel.showBouncedActionsView(for: displayInfo.message)
} else {
messageDisplayInfo = displayInfo
withAnimation {
viewModel.showReactionOverlay(for: AnyView(self))
}
}
},
onJumpToMessage: viewModel.jumpToMessage(messageId:)
Expand Down Expand Up @@ -196,6 +201,7 @@ public struct ChatChannelView<Factory: ViewFactory>: View, KeyboardReadable {
.alertBanner(isPresented: $viewModel.showAlertBanner)
.accessibilityElement(children: .contain)
.accessibilityIdentifier("ChatChannelView")
.modifier(factory.makeBouncedMessageActionsModifier(viewModel: viewModel))
}

private var generatingSnapshot: Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
}
}

@Published public var bouncedMessage: ChatMessage?
@Published public var bouncedActionsViewShown = false {
didSet {
if bouncedActionsViewShown == false {
bouncedMessage = nil
}
}
}

@Published public var quotedMessage: ChatMessage? {
didSet {
if oldValue != nil && quotedMessage == nil {
Expand Down Expand Up @@ -451,7 +460,28 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
public func showReactionOverlay(for view: AnyView) {
currentSnapshot = utils.snapshotCreator.makeSnapshot(for: view)
}


public func showBouncedActionsView(for message: ChatMessage) {
bouncedActionsViewShown = true
bouncedMessage = message
}

public func deleteMessage(_ message: ChatMessage) {
guard let cid = message.cid else { return }
let messageController = chatClient.messageController(cid: cid, messageId: message.id)
messageController.deleteMessage()
}

public func resendMessage(_ message: ChatMessage) {
guard let cid = message.cid else { return }
let messageController = chatClient.messageController(cid: cid, messageId: message.id)
messageController.resendMessage()
}

public func editMessage(_ message: ChatMessage) {
messageActionExecuted(.init(message: message, identifier: "edit"))
}

public func messageActionExecuted(_ messageActionInfo: MessageActionInfo) {
utils.messageActionsResolver.resolveMessageAction(
info: messageActionInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public struct MessageListConfig {
isMessageEditedLabelEnabled: Bool = true,
markdownSupportEnabled: Bool = true,
userBlockingEnabled: Bool = false,
bouncedMessagesAlertActionsEnabled: Bool = true,
skipEditedMessageLabel: @escaping (ChatMessage) -> Bool = { _ in false }
) {
self.messageListType = messageListType
Expand All @@ -57,6 +58,7 @@ public struct MessageListConfig {
self.isMessageEditedLabelEnabled = isMessageEditedLabelEnabled
self.markdownSupportEnabled = markdownSupportEnabled
self.userBlockingEnabled = userBlockingEnabled
self.bouncedMessagesAlertActionsEnabled = bouncedMessagesAlertActionsEnabled
self.skipEditedMessageLabel = skipEditedMessageLabel
}

Expand All @@ -83,6 +85,12 @@ public struct MessageListConfig {
public let isMessageEditedLabelEnabled: Bool
public let markdownSupportEnabled: Bool
public let userBlockingEnabled: Bool

/// A boolean to enable the alert actions for bounced messages.
///
/// By default it is true and the bounced actions are displayed as an alert instead of a context menu.
public let bouncedMessagesAlertActionsEnabled: Bool

public let skipEditedMessageLabel: (ChatMessage) -> Bool
}

Expand Down Expand Up @@ -111,7 +119,6 @@ public enum DateIndicatorPlacement {

/// Used to show and hide different helper views around the message.
public struct MessageDisplayOptions {

public let showAvatars: Bool
public let showAvatarsInGroups: Bool
public let showMessageDate: Bool
Expand Down
6 changes: 5 additions & 1 deletion Sources/StreamChatSwiftUI/DefaultViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,11 @@ extension ViewFactory {
forceLeftToRight: messageModifierInfo.forceLeftToRight
)
}


public func makeBouncedMessageActionsModifier(viewModel: ChatChannelViewModel) -> some ViewModifier {
BouncedMessageActionsModifier(viewModel: viewModel)
}

public func makeEmptyMessagesView(
for channel: ChatChannel,
colors: ColorPalette
Expand Down
16 changes: 16 additions & 0 deletions Sources/StreamChatSwiftUI/Generated/L10n.swift
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,22 @@ internal enum L10n {
/// GIPHY
internal static var title: String { L10n.tr("Localizable", "message.giphy-attachment.title") }
}
internal enum Moderation {
internal enum Alert {
/// Cancel
internal static var cancel: String { L10n.tr("Localizable", "message.moderation.alert.cancel") }
/// Delete Message
internal static var delete: String { L10n.tr("Localizable", "message.moderation.alert.delete") }
/// Edit Message
internal static var edit: String { L10n.tr("Localizable", "message.moderation.alert.edit") }
/// Consider how your comment might make others feel and be sure to follow our Community Guidelines.
internal static var message: String { L10n.tr("Localizable", "message.moderation.alert.message") }
/// Send Anyway
internal static var resend: String { L10n.tr("Localizable", "message.moderation.alert.resend") }
/// Are you sure?
internal static var title: String { L10n.tr("Localizable", "message.moderation.alert.title") }
}
}
internal enum Polls {
/// Anonymous
internal static var unknownVoteAuthor: String { L10n.tr("Localizable", "message.polls.unknown-vote-author") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@
"message.actions.user-block.confirmation-message" = "Are you sure you want to block this user?";
"message.actions.user-unblock.confirmation-message" = "Are you sure you want to unblock this user?";

"message.moderation.alert.title" = "Are you sure?";
"message.moderation.alert.message" = "Consider how your comment might make others feel and be sure to follow our Community Guidelines.";
"message.moderation.alert.resend" = "Send Anyway";
"message.moderation.alert.edit" = "Edit Message";
"message.moderation.alert.delete" = "Delete Message";
"message.moderation.alert.cancel" = "Cancel";

"messageList.typingIndicator.typing-unknown" = "Someone is typing";

"message.threads.reply" = "Thread Reply";
Expand Down
8 changes: 8 additions & 0 deletions Sources/StreamChatSwiftUI/ViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ public protocol ViewFactory: AnyObject {
/// - Parameter messageModifierInfo: the message modifier info, that will be applied to the message.
func makeMessageViewModifier(for messageModifierInfo: MessageModifierInfo) -> MessageViewModifier

associatedtype BouncedMessageActionsModifierType: ViewModifier
/// Returns a view modifier applied to the bounced message actions.
///
/// This modifier is only used if `Utils.messageListConfig.bouncedMessagesAlertActionsEnabled` is `true`.
/// By default the flag is true and the bounced actions are shown as an alert instead of a context menu.
< 10669 /td> /// - Parameter viewModel: the view model of the chat channel view.
func makeBouncedMessageActionsModifier(viewModel: ChatChannelViewModel) -> BouncedMessageActionsModifierType

associatedtype UserAvatar: View
/// Creates the message avatar view.
/// - Parameter userDisplayInfo: the author's display info.
Expand Down
4 changes: 4 additions & 0 deletions StreamChatSwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,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 */; };
AD5C0A5F2D6FDD9700E1E500 /* BouncedMessageActionsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD5C0A5E2D6FDD8600E1E500 /* BouncedMessageActionsModifier.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 */; };
Expand Down Expand Up @@ -1110,6 +1111,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>"; };
AD5C0A5E2D6FDD8600E1E500 /* BouncedMessageActionsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BouncedMessageActionsModifier.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>"; };
Expand Down Expand Up @@ -1740,6 +1742,7 @@
8465FD302746A95600AF091E /* ChatChannelViewModel.swift */,
84DEC8E72760EABC00172876 /* ChatChannelDataSource.swift */,
844D1D6528510304000CCCB9 /* ChannelControllerFactory.swift */,
AD5C0A5E2D6FDD8600E1E500 /* BouncedMessageActionsModifier.swift */,
8465FD2E2746A95600AF091E /* ChannelHeader */,
8465FCFD2746A95600AF091E /* MessageList */,
8465FD0F2746A95600AF091E /* Composer */,
Expand Down Expand Up @@ -2793,6 +2796,7 @@
82D64BF82AD7E5B700C5C79E /* ImageProcessors+Circle.swift in Sources */,
8465FDD32746A95800AF091E /* ColorPalette.swift in Sources */,
8465FD782746A95700AF091E /* FileAttachmentView.swift in Sources */,
AD5C0A5F2D6FDD9700E1E500 /* BouncedMessageActionsModifier.swift in Sources */,
82D64C152AD7E5B700C5C79E /* ImageContainer.swift in Sources */,
84EADEA22B2735D80046B50C /* VoiceRecordingContainerView.swift in Sources */,
82D64C0F2AD7E5B700C5C79E /* ImageDecoders+Video.swift in Sources */,
Expand Down
Loading
0