| | 1 | | using System.Collections.Generic; |
| | 2 | | using System.Threading; |
| | 3 | | using Cysharp.Threading.Tasks; |
| | 4 | | using DCL.Interface; |
| | 5 | | using DCL.Helpers; |
| | 6 | | using UnityEngine; |
| | 7 | | using System; |
| | 8 | | using Channel = DCL.Chat.Channels.Channel; |
| | 9 | | using DCl.Social.Friends; |
| | 10 | | using DCL.Social.Friends; |
| | 11 | |
|
| | 12 | | namespace DCL.Chat.Notifications |
| | 13 | | { |
| | 14 | | public class ChatNotificationController : IHUD |
| | 15 | | { |
| | 16 | | private const int FADEOUT_DELAY = 8000; |
| | 17 | | private const string NEW_FRIEND_REQUESTS_FLAG = "new_friend_requests"; |
| | 18 | |
|
| | 19 | | private readonly DataStore dataStore; |
| | 20 | | private readonly IChatController chatController; |
| | 21 | | private readonly IFriendsController friendsController; |
| | 22 | | private readonly IMainChatNotificationsComponentView mainChatNotificationView; |
| | 23 | | private readonly ITopNotificationsComponentView topNotificationView; |
| | 24 | | private readonly IUserProfileBridge userProfileBridge; |
| | 25 | | private readonly IProfanityFilter profanityFilter; |
| 9 | 26 | | private readonly TimeSpan maxNotificationInterval = new TimeSpan(0, 1, 0); |
| 9 | 27 | | private readonly HashSet<string> notificationEntries = new HashSet<string>(); |
| 27 | 28 | | private BaseVariable<bool> shouldShowNotificationPanel => dataStore.HUDs.shouldShowNotificationPanel; |
| 9 | 29 | | private BaseVariable<Transform> notificationPanelTransform => dataStore.HUDs.notificationPanelTransform; |
| 35 | 30 | | private BaseVariable<Transform> topNotificationPanelTransform => dataStore.HUDs.topNotificationPanelTransform; |
| 18 | 31 | | private BaseVariable<HashSet<string>> visibleTaskbarPanels => dataStore.HUDs.visibleTaskbarPanels; |
| 7 | 32 | | private BaseVariable<string> openedChat => dataStore.HUDs.openedChat; |
| 9 | 33 | | private CancellationTokenSource fadeOutCT = new CancellationTokenSource(); |
| | 34 | | private UserProfile ownUserProfile; |
| 1 | 35 | | private bool isNewFriendRequestsEnabled => dataStore.featureFlags.flags.Get().IsFeatureEnabled(NEW_FRIEND_REQUES |
| | 36 | |
|
| 9 | 37 | | public ChatNotificationController(DataStore dataStore, |
| | 38 | | IMainChatNotificationsComponentView mainChatNotificationView, |
| | 39 | | ITopNotificationsComponentView topNotificationView, |
| | 40 | | IChatController chatController, |
| | 41 | | IFriendsController friendsController, |
| | 42 | | IUserProfileBridge userProfileBridge, |
| | 43 | | IProfanityFilter profanityFilter) |
| | 44 | | { |
| 9 | 45 | | this.dataStore = dataStore; |
| 9 | 46 | | this.chatController = chatController; |
| 9 | 47 | | this.friendsController = friendsController; |
| 9 | 48 | | this.userProfileBridge = userProfileBridge; |
| 9 | 49 | | this.profanityFilter = profanityFilter; |
| 9 | 50 | | this.mainChatNotificationView = mainChatNotificationView; |
| 9 | 51 | | this.topNotificationView = topNotificationView; |
| 9 | 52 | | mainChatNotificationView.OnResetFade += ResetFadeOut; |
| 9 | 53 | | topNotificationView.OnResetFade += ResetFadeOut; |
| 9 | 54 | | mainChatNotificationView.OnPanelFocus += TogglePanelBackground; |
| 9 | 55 | | chatController.OnAddMessage += HandleMessageAdded; |
| 9 | 56 | | friendsController.OnAddFriendRequest += HandleFriendRequestAdded; |
| 9 | 57 | | notificationPanelTransform.Set(mainChatNotificationView.GetPanelTransform()); |
| 9 | 58 | | topNotificationPanelTransform.Set(topNotificationView.GetPanelTransform()); |
| 9 | 59 | | visibleTaskbarPanels.OnChange += VisiblePanelsChanged; |
| 9 | 60 | | shouldShowNotificationPanel.OnChange += ResetVisibility; |
| 9 | 61 | | ResetVisibility(shouldShowNotificationPanel.Get(), false); |
| 9 | 62 | | } |
| | 63 | |
|
| | 64 | | public void SetVisibility(bool visible) |
| | 65 | | { |
| 9 | 66 | | ResetFadeOut(visible); |
| | 67 | |
|
| 9 | 68 | | if (visible) |
| | 69 | | { |
| 9 | 70 | | if (shouldShowNotificationPanel.Get()) |
| 9 | 71 | | mainChatNotificationView.Show(); |
| | 72 | |
|
| 9 | 73 | | topNotificationView.Hide(); |
| 9 | 74 | | mainChatNotificationView.ShowNotifications(); |
| | 75 | | } |
| | 76 | | else |
| | 77 | | { |
| 0 | 78 | | mainChatNotificationView.Hide(); |
| 0 | 79 | | if (!visibleTaskbarPanels.Get().Contains("WorldChatPanel")) |
| 0 | 80 | | topNotificationView.Show(); |
| | 81 | | } |
| 0 | 82 | | } |
| | 83 | |
|
| | 84 | | public void Dispose() |
| | 85 | | { |
| 9 | 86 | | chatController.OnAddMessage -= HandleMessageAdded; |
| 9 | 87 | | friendsController.OnAddFriendRequest -= HandleFriendRequestAdded; |
| 9 | 88 | | visibleTaskbarPanels.OnChange -= VisiblePanelsChanged; |
| 9 | 89 | | mainChatNotificationView.OnResetFade -= ResetFadeOut; |
| 9 | 90 | | topNotificationView.OnResetFade -= ResetFadeOut; |
| 9 | 91 | | } |
| | 92 | |
|
| | 93 | | private void VisiblePanelsChanged(HashSet<string> newList, HashSet<string> oldList) |
| | 94 | | { |
| 0 | 95 | | SetVisibility(newList.Count == 0); |
| 0 | 96 | | } |
| | 97 | |
|
| | 98 | | private void HandleMessageAdded(ChatMessage[] messages) |
| | 99 | | { |
| 31 | 100 | | foreach (var message in messages) |
| | 101 | | { |
| 8 | 102 | | if (message.messageType != ChatMessage.Type.PRIVATE && |
| 0 | 103 | | message.messageType != ChatMessage.Type.PUBLIC) return; |
| 8 | 104 | | ownUserProfile ??= userProfileBridge.GetOwn(); |
| 8 | 105 | | if (message.sender == ownUserProfile.userId) return; |
| | 106 | |
|
| 8 | 107 | | var span = Utils.UnixToDateTimeWithTime((ulong) DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()) - |
| | 108 | | Utils.UnixToDateTimeWithTime(message.timestamp); |
| | 109 | |
|
| 9 | 110 | | if (span >= maxNotificationInterval) return; |
| | 111 | |
|
| 7 | 112 | | var channel = chatController.GetAllocatedChannel( |
| | 113 | | string.IsNullOrEmpty(message.recipient) && message.messageType == ChatMessage.Type.PUBLIC |
| | 114 | | ? "nearby" |
| | 115 | | : message.recipient); |
| 7 | 116 | | if (channel?.Muted ?? false) return; |
| | 117 | |
|
| | 118 | | // TODO: entries may have an inconsistent state. We should update the entry with new data |
| 7 | 119 | | if (notificationEntries.Contains(message.messageId)) return; |
| 7 | 120 | | notificationEntries.Add(message.messageId); |
| | 121 | |
|
| 7 | 122 | | AddNotification(message, channel).Forget(); |
| | 123 | | } |
| 7 | 124 | | } |
| | 125 | |
|
| | 126 | | private async UniTaskVoid AddNotification(ChatMessage message, Channel channel = null) |
| | 127 | | { |
| 7 | 128 | | var peerId = ExtractPeerId(message); |
| 7 | 129 | | var peerProfile = userProfileBridge.Get(peerId); |
| 7 | 130 | | var peerName = peerProfile?.userName ?? peerId; |
| 7 | 131 | | var peerProfilePicture = peerProfile?.face256SnapshotURL; |
| 7 | 132 | | var body = message.body; |
| | 133 | |
|
| 7 | 134 | | switch (message.messageType) |
| | 135 | | { |
| | 136 | | case ChatMessage.Type.PRIVATE: |
| 2 | 137 | | var privateModel = new PrivateChatMessageNotificationModel(message.messageId, |
| | 138 | | message.sender, body, message.timestamp, peerName, peerProfilePicture); |
| | 139 | |
|
| 2 | 140 | | if (message.sender != openedChat.Get()) |
| | 141 | | { |
| 2 | 142 | | mainChatNotificationView.AddNewChatNotification(privateModel); |
| 2 | 143 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 2 | 144 | | topNotificationView.AddNewChatNotification(privateModel); |
| | 145 | | } |
| | 146 | |
|
| 2 | 147 | | break; |
| | 148 | | case ChatMessage.Type.PUBLIC: |
| 5 | 149 | | if (IsProfanityFilteringEnabled()) |
| | 150 | | { |
| 2 | 151 | | peerName = await profanityFilter.Filter(peerProfile?.userName ?? peerId); |
| 2 | 152 | | body = await profanityFilter.Filter(message.body); |
| | 153 | | } |
| 5 | 154 | | var publicModel = new PublicChannelMessageNotificationModel(message.messageId, |
| | 155 | | body, channel?.Name ?? message.recipient, channel?.ChannelId, message.timestamp, |
| | 156 | | peerName); |
| | 157 | |
|
| 5 | 158 | | if (channel?.ChannelId != openedChat.Get()) |
| | 159 | | { |
| 5 | 160 | | mainChatNotificationView.AddNewChatNotification(publicModel); |
| 5 | 161 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 5 | 162 | | topNotificationView.AddNewChatNotification(publicModel); |
| | 163 | | } |
| | 164 | |
|
| | 165 | | break; |
| | 166 | | } |
| 7 | 167 | | } |
| | 168 | |
|
| | 169 | | private void HandleFriendRequestAdded(FriendRequest friendRequest) |
| | 170 | | { |
| 1 | 171 | | if (!isNewFriendRequestsEnabled) |
| 0 | 172 | | return; |
| | 173 | |
|
| 1 | 174 | | var ownUserProfile = userProfileBridge.GetOwn(); |
| | 175 | |
|
| 1 | 176 | | if (friendRequest.From == ownUserProfile.userId || |
| | 177 | | friendRequest.To != ownUserProfile.userId) |
| 0 | 178 | | return; |
| | 179 | |
|
| 1 | 180 | | var friendRequestProfile = userProfileBridge.Get(friendRequest.From); |
| 1 | 181 | | var friendRequestName = friendRequestProfile?.userName ?? friendRequest.From; |
| 1 | 182 | | var friendRequestProfilePicture = friendRequestProfile?.face256SnapshotURL; |
| | 183 | |
|
| 1 | 184 | | FriendRequestNotificationModel friendRequestNotificationModel = new FriendRequestNotificationModel( |
| | 185 | | friendRequest.From, |
| | 186 | | friendRequestName, |
| | 187 | | "Friend Request", |
| | 188 | | $"wants to be your friend.", |
| | 189 | | (ulong)friendRequest.Timestamp, |
| | 190 | | friendRequestProfilePicture, |
| | 191 | | false); |
| | 192 | |
|
| 1 | 193 | | mainChatNotificationView.AddNewFriendRequestNotification(friendRequestNotificationModel); |
| 1 | 194 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 1 | 195 | | topNotificationView.AddNewFriendRequestNotification(friendRequestNotificationModel); |
| 1 | 196 | | } |
| | 197 | |
|
| | 198 | | private void ResetFadeOut(bool fadeOutAfterDelay = false) |
| | 199 | | { |
| 9 | 200 | | mainChatNotificationView.ShowNotifications(); |
| 9 | 201 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 9 | 202 | | topNotificationView.ShowNotification(); |
| | 203 | |
|
| 9 | 204 | | fadeOutCT.Cancel(); |
| 9 | 205 | | fadeOutCT = new CancellationTokenSource(); |
| | 206 | |
|
| 9 | 207 | | if (fadeOutAfterDelay) |
| 9 | 208 | | WaitThenFadeOutNotifications(fadeOutCT.Token).Forget(); |
| 9 | 209 | | } |
| | 210 | |
|
| | 211 | | private void TogglePanelBackground(bool isInFocus) |
| | 212 | | { |
| 0 | 213 | | if (isInFocus) |
| 0 | 214 | | mainChatNotificationView.ShowPanel(); |
| | 215 | | else |
| 0 | 216 | | mainChatNotificationView.HidePanel(); |
| 0 | 217 | | } |
| | 218 | |
|
| | 219 | | private async UniTaskVoid WaitThenFadeOutNotifications(CancellationToken cancellationToken) |
| | 220 | | { |
| 27 | 221 | | await UniTask.Delay(FADEOUT_DELAY, cancellationToken: cancellationToken); |
| 9 | 222 | | await UniTask.SwitchToMainThread(cancellationToken); |
| 9 | 223 | | if (cancellationToken.IsCancellationRequested) |
| 0 | 224 | | return; |
| | 225 | |
|
| 9 | 226 | | mainChatNotificationView.HideNotifications(); |
| | 227 | |
|
| 9 | 228 | | if (topNotificationPanelTransform.Get() != null && |
| | 229 | | topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 0 | 230 | | topNotificationView.HideNotification(); |
| 9 | 231 | | } |
| | 232 | |
|
| | 233 | | private string ExtractPeerId(ChatMessage message) => |
| 7 | 234 | | message.sender != ownUserProfile.userId ? message.sender : message.recipient; |
| | 235 | |
|
| 9 | 236 | | private void ResetVisibility(bool current, bool previous) => SetVisibility(current); |
| | 237 | |
|
| | 238 | | private bool IsProfanityFilteringEnabled() => |
| 5 | 239 | | dataStore.settings.profanityChatFilteringEnabled.Get(); |
| | 240 | | } |
| | 241 | | } |