Skip to content
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

Message Reactions 🥳 #140

Merged
merged 53 commits into from
Feb 21, 2025
Merged

Message Reactions 🥳 #140

merged 53 commits into from
Feb 21, 2025

Conversation

btoms20
Copy link
Contributor

@btoms20 btoms20 commented Feb 18, 2025

What:

  • Adds support for message reactions

Changes:

Models:

  • Introduced a Reaction model
    • A Reaction has the same id, user, createdAt and status params that Message has
public struct Reaction: Codable, Identifiable, Hashable {
    public let id: String
    public let user: User
    public let createdAt: Date
    public let type: ReactionType
    public var status: Status
}
  • A Reaction contains a ReactionType
    • At the moment the only ReactionType is an emoji, but other types can be added later.
public enum ReactionType: Codable, Equatable, Hashable {
    case emoji(String)
    //case sticker(Image / Giphy / Memoji)
    //case other...
}
  • Added a ReactionDelegate
    • Used for responding to Message Reactions and optionally configuring the various reaction views within the Menu
public protocol ReactionDelegate {
   // Required
   func didReact(to message: Message, reaction: DraftReaction)

   // Optional configuration methods
   func shouldShowOverview(for message: Message) -> Bool
   func canReact(to message: Message) -> Bool
   func reactions(for message: Message) -> [ReactionType]?
   func allowEmojiSearch(for message: Message) -> Bool
}
  • Updated the Message model to accept an array of Reactions.
  • The ChatView initializers have been updated to accept a ReactionDelegate, but default to nil to maintain backward compatibility.

Note

If no ReactionDelegate is provided to the ChatView then all reaction related views are disabled within the MessageMenu

Views

  • MessageReaction View
    MessageReactionView

    • Supports multiple reactions ordered with most recent on top.
    • When the number of reactions exceed a configurable limit, they're combined in an overflow bubble.
    • Supports in-flight .sending reactions by animating the bubbles stroke
    • Supports failed reactions by coloring the background red (re-delivery / error handling should be left up to the developer)
  • ReactionSelection View
    ReactionSelectionView

    • Supports configurable emojis on a per message basis via the ReactionDelegate
    • Supports custom emoji search (launches a keyboard in emoji mode)
    • Shows & Highlights existing Reactions (so developers can support removing reactions if they chose to)
  • ReactionOverview View
    ReactionOverview

    • Links Users avatars by reactions for an easy overview
    • Shows all reactions, useful when an overflow bubble is rendered on the message bubble
    • Can be enabled / disable via the ReactionDelegate
  • New MessageMenu view that doesn't rely on swift-introspection

    • The Menu Buttons automatically get rendered in a ScrollView when there are more buttons then the configured max height allows
    • When the entire layout exceeds the available screen real estate, the entire MessageMenu is rendered in a ScrollView
    • Configurable animation duration (a single ChatView modifier that allows the developer to control how snappy the Menu feels)
      • func messageMenuAnimationDuration(_ duration:Double) -> ChatView
Message Menu - Light MessageMenu - Dark
MessageMenu-Light MessageMenu-Dark
Reaction Selection Emoji Search
MessageMenu-Selection MessageMenu-EmojiSearch

Configuring:

The following configuration using the ChatView modifier....

ChatView(...)
.onMessageReaction(
    didReactTo: { message, draftReaction in
        viewModel.add(draftReaction: draftReaction, to: draftReaction.messageID)
    },
    // Prevents the user from being able to react to their own messages
    canReactTo: { !$0.user.isCurrentUser },
    // Limit the available reactions to just thumbs up / down
    availableReactionsFor: { _ in [.emoji("👍"), .emoji("👎")] },
    // Prevent the user from searching for / using custom emojis
    allowEmojiSearchFor: { _ in false }
)

Results in the following message menu layouts...

Custom Emoji Set No Reaction Selection Available
CustomEmojiSet NoReactionsAvailable

The developer can also set a ReactionDelegate on the ChatView using either the initializers or the .messageReactionDelegate(...) method.

Supporting Reactions

  • This PR doesn't provide any sort of validation on user input. It doesn't enforce that the String passed to .emoji is in fact an emoji.
  • The developer should perform adequate user input validation.
  • The developer should also handle aspects such as
    • Duplicate reactions
    • Removing reactions
    • Resending reactions that failed to deliver

Custom Message Builder:

  • This PR doesn't offer a complete solution to those using the custom message builder.
  • The message menu reaction views will still appear above your message cell, but the reaction won't animate to the corner of the message like it does for the default message view.
  • Developers using custom messages will have to roll their own reaction views that work for their messages. But copy & pasting the default views should provide a nice launching off point to get started.
  • Also, I'm open to discuss ideas on how to make this more compatible with custom message builders.

Fixes / Addresses:

#120
#129

Examples:

  • I've updated the ChatExampleView, MockChatInteractor and MockChatData to generate random messages with Reactions and to support reacting to existing messages.

Testing:

  • I've tested the layout mechanics extensively on various iPhone and iPad simulators across the full range of Dynamic Text Sizes (including accessibility sizes). The views should adapt appropriately, preserve their appearance and most importantly maintain functionality across the full spectrum of text sizes.
  • Having said all that, I'm sure there will be bugs that I've overlooked!

Dependencies:

  • The new MessageMenu doesn't rely on either swift-introspection or FloatingButton packages, so these dependencies can be removed from Package.swift.

Disclosure:

  • Feel free to modify this PR or delete it if deemed out of scope.

…nd message.reactions from this check and use the triggerRedraw functionality correctly)
…eyboards frame as well as whether it's shown or not.
…height to 1 px (when `viewSize` was used with large leadingPadding or trailingPadding values the height would effect the views layout, this was noticeable in the rendering of leading vs trailing aligned Message Menu Buttons).
…xtFeild but when focused launches the keybaord in Emoji mode.
…iguration. The ReactionDelegate provides an interface for both responding to message reactions and configuring the message menu for reactions on a per message basis.
…existing frame and size getter, but captures the maximum height of a view instead of just it's current height (useful for laying out the message menu with dynamic system font sizes).
…ont.Title3 size across the full range of dynamic text sizes (including accessiblity fonts). We use these values when constructing the ReactionSelection view.
…essage when invoking the message menu. This value gets populated by a `frameGetter` call in `MessgeView` when `isDisplayingMessageMenu` is true.
…g the default delegate on per closure basis. Also added a modifier for adjusting the MessageMenu's animation duration.
…source and an add(draftReaction:, to:) method for simulating reacting to messages.
…ctorProtocol. Also added a method for updating a reactions status.
…andom reaction and a function for appending a reaction to an existing MockMessage.
…l for reaction support in our ChatExampleView. Also added a remove function so we can actually remove messages when a user uses the MessageMenu to delete a message.
#Conflicts:
#	Package.swift
#	Sources/ExyteChat/ChatView/ChatView.swift
… and for a width to be specified by the parent view, instead of using UIScreen bounds.
…e constraint (the previous layout resulted in very jittery scrolling).
…with the background. A transparent friendMessageColor presents an issue when overlaying reactions on message bubbles.
@btoms20
Copy link
Contributor Author

btoms20 commented Feb 18, 2025

While working on this, I noticed that the

UIList(...)
    .transparentNonAnimatingFullScreenCover(...)

modifier doesn't seem to be preventing animations upon launching anymore. This results in a moving background while launching the menu while the keyboard is deployed.

I tried a couple of things to keep the ChatView static during the keyboard dismissal but I didn't have any luck.

I believe this behavior is consistent with the current main branch. I'm not sure when transparentNonAnimatingFullScreenCover stopped working correctly.

@f3dm76 f3dm76 merged commit 603580a into exyte:main Feb 21, 2025
@f3dm76
Copy link
Collaborator

f3dm76 commented Feb 21, 2025

@btoms20, wow, cool! thank you so much for making the lib better, have a great day!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants