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

feat: Chat Improvements #3365

Open
wants to merge 168 commits into
base: dev
Choose a base branch
from
Open

feat: Chat Improvements #3365

wants to merge 168 commits into from

Conversation

fcolarich
Copy link
Contributor

@fcolarich fcolarich commented Feb 14, 2025

Congrats! You have been cursed selected to review this PR!

You will get 10 karma points added to your character*

*Terms and conditions may apply.

The first question that might pop into your mind must be:

What kind of demonic ritual is this?

Well, turns out it is a really cool and groovy demonic ritual, and now you are part of it too!*

*If you spot a new tentacle growing on your back, its normal, squish squish, welcome to the team.

The first thing you should know is that this implements all the logic requested here + some improvements done thinking in a future shape that will require to have independent chat channels for different conversations.

All these features were done in their own PRs and merged, here are the PRs in case you want some specific detail not mentioned here:
Mentions
Hyperlinks
Unread Messages
Members List
Copy Paste

So, we will go through all the relevant classes and assets, with some short explanations about the logic and mostly the why the hell did you do this's,:

Note these descriptions go in the order they appear when reviewing the PR for easier reference, have fun!

ChatEntry

image
We have split the default ChatEntry into a base one and 3 variants, so we dont have to replicate all the changes into the 3 independent ones. This is necessary to display Own, System and Other users' messages. This also included making prefabs of some of the elements of the asset to make it easier to edit without conflicts and to reuse them + splitting the responsibility of the ChatEntryView. These are the ChatEntryMessageBubbleElement, ChatEntryMessageContentElement && ChatEntryUsernameElement, now each tasked with specific responsibilities. The only logic that we have not extracted is the profile picture, because we plan to replace it with a generic asset after this shape, as this logic is re-copied in so many places.
image
We also added a new button that opens a small popup menu with options (for now only with the Copy option, but might have more in the future).
As you will notice these elements are pure views, without a controller, given that they only affect the visual state of the asset and do not change the data at all. This means they are mostly monobehaviours. This pattern will repeat all along the code with some sprinkles on top to keep it fun.

IViewWithGlobalDependencies

We will do a small stop here to talk about the IViewWithGlobalDependencies. This new type of view has access to a new shared class called ViewDependencies which includes many generic dependencies that are needed in views without requiring a controller just to get a reference. Ideally these dependencies should not allow to write data, but only to process it in some way or another to use of the view. Like getting a list of profiles or getting access to the ClipboardManager or to the also new MVCManagerMenusAccessFacade. In turn this class provides a restricted access to the MVC to display some specific popups that should be accessible from any view, for example, a generic context menu with profile data or the prompt to change realm.

ChatView

image
We split the historic chat asset and view into many sub-views and prefabs. We also moved a bunch of code from the controller into the view, as its code that strictly related to visual changes and not data changes. So the view now exposes its functionality to the controller through public methods and events instead of by giving it full access to everything.

ChatInputBox

image
This little thing represents the area of the chat dedicated to receive user inputs, including the emoji button and the character counter, as well as the suggestion box to display suggestions.
image
This asset was extracted from the big Chat asset, again to make it easier to edit and debug and is governed by the ChatInputBoxElement which makes use of the ViewDependencies mentioned earlier.
From the ChatInputBoxElement we have extracted a lot of functionality that managed emoji suggestions. Now those are in their own independent classes, starting with the InputSuggestionPanelElement which displays any kind of suggestion (which now are a generic type that gets assigned through the editor, so we can have suggestion panels with different kind of suggestions each).
Also a lot of changes were done to better handle different types of suggestions, with different regex's for them, the new copy/paste functionality and the new channels handling.

ChatMessageViewerElement

The other half of the Chat, where the messages are displayed, it was also extracted from the main Chat asset and class, with a lot of custom logic
image
This class handles how the messages are displayed, as well as how the new messages are notified and loaded, the new messages indicator, etc. This includes a lot of changes to the message loading logic, to properly follow the new rules for new messages arriving either while the player is looking or when they have the chat disabled.

ChatCommandBus & IChatCommandsBus

This little 🚌 was created to remove dependencies between chat commands and everything else. Now, the chat commands invoke methods in the bus and the receiver end subscribes to them. No weird reference sharing needed nor weird public access to classes created.
So far we only replaced a few of these dependencies (mainly the ones that gave us circular refs), but all chat commands should go through the bus as much as possible instead of referencing directly other classes (unless it makes sense of course :))
We also did some housekeeping and moved all chat commands to where they belong, the Chat/Commands/ folder where they will live happy ever after.

ChatChannel

This class represents a conversation thread, this is implemented but is not functional completely, as we only have one channel right now (Nearby). The channel stores all the messages corresponding to it and signals through events when the message list has changed somehow.
This also involves changes to the ChatHistory, that now adds messages to the corresponding channel. Of course, this code is still WIP in the sense that we only have one source of messages, but it will be further changed in future shapes when conversations are implemented.

ChatMessage

Changes to this class focus mostly in adding new fields or changing some field names to better reflect their content, as well as being moved from its original location. The new fields added are:

  • IsMention -> if the message is a mention, i.e. if the receiving user is mentioned in the message
  • SenderValidatedName -> now uses the ValidatedName of the user, which is the name after being parsed and passing a validation for weird characters.
  • SenderWalletId -> if the user does not own a name, this field will have a string in format "# 1234" that is used in several places.

ClipboardManager

Encapsulates logic used to interact with the clipboard and sends events upon copy or paste. Also includes a CopyAndSanitize method to remove all rich text tags from a copied text, used when copying text from a chat message.
This included changes to the ISystemClipboard, to encapsulate the GUIUtility.systemCopyBuffer access, so now we can Set and Get, as well as check if it HasValue.

IProfileNameColorHelper

This class was introduced to remove dependencies with the Chat caused by the use of the ChatEntryConfigurationSO. This basically helps to select a color from a list using a string (usually the avatar name) as input. Now the list of colors is defined directly in the DynamicSettings and used by this class, so it no longer needs to be a SO at all.

DCLCursor

Added a new boolean IsStyleForced that helps when setting a cursor style (which changes the visual aspect of the cursor), making it ignore the value set in the ECS systen UpdateCursorInputSystem. This is needed when hovering over an hyperlink inside a text component. Given how our system works, there is no way to discriminate against a link inside a text vs the whole TMP_Text component. But we can do it from the text component directly, but the system would overwrite this change and is where forcing it comes into place.

NametagView

Even thou it looks like it was heavily modified, most of it were name changes for fields + the introduction of the mentionBackground which needs to be displayed instead of the normal background when the message contains a mention.

FloatingPanel prefab

Was removed because it is no longer used

GenericContextMenuPlugin

And associated classes, we needed to add 2 new context menu types (GenericContextMenuOpenUserProfileButtonView & GenericContextMenuMentionUserButtonView), even thou they are basically menus with a button, they have some integrated behavior related to Profiles that we needed them to properly do and with current implementation we cannot reuse easily a view with different types of content. It remains a point that can be improved in the future.
Also, we added the option to display the context menu using different anchor points, so it doesnt need to be always on the top left.

GenericPopupsPlugin

And related classes, this were created before the GenericContextMenu was available, it serves a similar purpose, to display small popups for COPY and PASTE. It is already planned to migrate them to use the new GenericContextMenu

DefaultProfileCache

Added the option to get a Profile by full Username (including the # 1234 suffix), as in some places we only have that information. This meant creating a small dictionary that maps usernames to walletIds.

Profile

Added several improvements to avoid re-calculating certain data, particularly strings and colors.
Now the profile stores the assigned color for that user, so we only calculate it once when the profile is created.
Also now we store the WalletId (so the # 1234) string directly, so no more string operations to get it from the username.
Also we store the MentionName which includes the @. This is only cached on first use, so its not created unless we use it.

ChatSoundsSettings

Along with all the related classes needed to modify the settings menu, we replaced the toggle for chat sounds with a dropdown, that allows us to enable all sounds, disable them all or enable only mention sounds (so only when someone mentions you, you hear a sound).

CustomInputField

This is a class that inherits from TMP_InputField, because we need to override what goes on with certain input actions (like ctrl+v and when using the arrow keys UP and DOWN) during the OnUpdateSelected flow. Also, we added additional functionality that handles inserting and replacing text in an efficient way, avoiding unnecessary OnInputChanged events. Check the TryHandleSpecialKeys method for some more details.
It also allows us to send events upon receiving a right click, which we need to display a paste popup (but could be used for other functionality in other situations as well). Normally the TMP_InputField would just ignore right clicks altogether.

TextHyperlinkHandlerElement

As it names says, it allows for handling hyperlinks in objects with a TMP_Text. It reacts to mouse movements to highlight hyperlinks and reacts on click doing different actions depending on the type of link. Uses heavily the new ViewDependencies for this.

BaseSuggestionElement & IInputSuggestionElementData

These and their inheritors are the replacement for the old emoji suggestions. These work in a generic way to define any kind of suggestion, for now only Emojis and Profiles, but can be expanded rather easily. These elements are then loaded into a SO that is referenced by the InputSuggestionPanelElement, so each panel can display different types of suggestions depending on how it is configured.

ITextFormatter & HyperlinkTextFormatter

This new class helps format text, by changing the input text into something else using the defined rules inside the formatter.
In the case of the HyperlinkTextFormatter we use it to add rich text Links and to remove invalid rich text tags (all except bold and cursive). Also, this formatter does some validation for some of the links, so it knows if to make it a link or not and which format to give them. This happens for parcel coordinates, where we check if they are inside the bounds and also for user names, where we check if its our own username (so we color it) but not make it a link or we check if we got their profile in our cache, in which case its made a profile link.

GenesisCityData

We had to add a new Rect[] called EXTENDED_WORLD_BOUNDS because when doing the check of IsInsideBounds, the rect.Contains excludes limit values, so we had to extend the bounds so now it includes them.

Summon Instructions

Please follow the instructions in these PRs for testing the different features:
Mentions
Hyperlinks -> with the caveat that only http/https web addresses are recognized now.
Unread Messages
Member List

QThund and others added 30 commits January 10, 2025 19:11
…bottom of its content.

Fixed: There was an event handler that was never unsubscribed.
The scroll view uses now clamped movement and the LoopListView is not using item snapping.
* Added new button to chat bubble + some layout fixes + show on hover

* Added paste logic and hasValue check for clipboard

* Added logic to show paste popup anywhere from anywhere + paste logic

* Fixed some logic + prefabs

* Fixed functionality + paste prefab tweaks

* missing changes

* Post dev merge fix

* Changes to Chat Entries to be properly reused prefabs

* Fixed chat scale

* Fixed paste popup canvas ref

* Fixed some rect issues with own chats

* Fix after merge

* Extracted design consts into SO for easier access

* Update ChatEntryConfigurationSO.cs

* fixed chat scale

* Re-added missing paste popup position marker

* Added character limit check when pasting text

* Added context menu with copy  option

* fixed closing task

* Update ChatController.cs

* Fixed non masked frame on chats

* Fixes from CR

* Added clipboard manager service class to handle clipboard operations and callbacks

* Update ChatController.cs

* tweaks to clipboard manager
…for the controller

Many fields and methods moved from ChatController to ChatView and adapted so the code flow is the same but separating concerns. The controller does not know anything about how the view is implemented. ChatView now exposes some public methods for the controller to call them, as well as some events to which the controller has to subscribe.
Some dependencies injected to ChatController (emoji menu prefabs) are now directly referenced by ChatView in the Chat prefab.
Some setting constants exposed in the inspector of ChatView.
Now the chat bubbles toggle uses the isOn property and its events instead of showing / hiding images of the toggle manually.
Functionality has not changed.
The rest of scripts only contain renamed events.
The flag hasToAnimate was added to the ChatMessage structure as a way to know which messages were already animated in the chat window (a fade-in FX is used when a new message arrives) when using LoopListView2, which reuses ChatEntryViews through internal pools. It made no sense to store data strictly related to a view in the model. This workaround is not necessary anymore, ChatView is now in charge of knowing how many messages to animate.
… can access some external systems

Added a new asmdef MVC.ViewDependencies.
ChatView and EmojiSuggestionPanel inherit now from IViewWithSystemDependencies.
…of prompts and menus

IViewWithSystemDependencies renamed to IViewWithGlobalDependencies.
In order to implement GlobalUIViews class, I had to create asmdefs: Chat.Commands, Chat.MessageBus, UI.ChatEntryMenuPopup and UI.PastePopupToast. Scripts related to each asmdef were moved too into their own folder.
Now the ViewDependencies instance is created in the Dynamic world container and sent to the ChatPlugin.
Some original code of EmojiSuggestionPanel reverted.
Now it is possible to have multiple conversations that reuse the scroll view of the Chat view, and send messages to any of them.
Created the ChatChannelId struct which may be replaced by anything else in the future to identify a channel by its type and name.
Currently it's not possible to send messages to specific channels of other users, that has to be implemented in the future, although the base has been put in, the protobuf Chat message may store the Id of the channel at the start of the actual message and can be parsed by both endpoints (see MultiplayerChatMessageBus). Currently everything is sent to the special channel "nearby".
The ClearChatCommand had to be adapted since it has to be sent to the current chat channel, but only ChatView knows what's the current channel. So now the command does not change the Chat history, it calls the ChatController (injected in a later step during the initialization of the program).
ChatController and ChatView refactoring: Now the view is a black box for the controller
ChatMessages do not store view data anymore, hasToAnimate removed
fcolarich and others added 23 commits February 21, 2025 18:56
…esent

A VerticalLayout component in Content was causing that LoopListView was going crazy. Now the layout has been removed and the width of the items adjusted.
Added margins to Viewport, removed from Content (in Chat.prefab too).
Changed the fading effect in the viewport of the Member list, as top padding was removed (with the VerticalLayout), top is now zero.
The ChatMemberListItem prefab is now independent, it is not a variant of the Friends item prefab anymore. Unused components have been removed.
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.

Creation of centralized system to manage visibility of panels that share the same space
7 participants