| | 1 | | using Cysharp.Threading.Tasks; |
| | 2 | | using DCL.Helpers; |
| | 3 | | using DCL.Interface; |
| | 4 | | using DCL.ProfanityFiltering; |
| | 5 | | using DCL.Social.Friends; |
| | 6 | | using System; |
| | 7 | | using System.Collections.Generic; |
| | 8 | | using System.Threading; |
| | 9 | | using UnityEngine; |
| | 10 | | using Channel = DCL.Chat.Channels.Channel; |
| | 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; |
| 13 | 26 | | private readonly TimeSpan maxNotificationInterval = new (0, 1, 0); |
| 13 | 27 | | private readonly HashSet<string> notificationEntries = new (); |
| 39 | 28 | | private BaseVariable<bool> shouldShowNotificationPanel => dataStore.HUDs.shouldShowNotificationPanel; |
| 13 | 29 | | private BaseVariable<Transform> notificationPanelTransform => dataStore.HUDs.notificationPanelTransform; |
| 49 | 30 | | private BaseVariable<Transform> topNotificationPanelTransform => dataStore.HUDs.topNotificationPanelTransform; |
| 26 | 31 | | private BaseVariable<HashSet<string>> visibleTaskbarPanels => dataStore.HUDs.visibleTaskbarPanels; |
| 7 | 32 | | private BaseVariable<string> openedChat => dataStore.HUDs.openedChat; |
| 13 | 33 | | private CancellationTokenSource fadeOutCT = new (); |
| | 34 | | private UserProfile internalOwnUserProfile; |
| | 35 | | private UserProfile ownUserProfile |
| | 36 | | { |
| | 37 | | get |
| | 38 | | { |
| 19 | 39 | | internalOwnUserProfile ??= userProfileBridge.GetOwn(); |
| 19 | 40 | | return internalOwnUserProfile; |
| | 41 | | } |
| | 42 | | } |
| 3 | 43 | | private bool isNewFriendRequestsEnabled => dataStore.featureFlags.flags.Get().IsFeatureEnabled(NEW_FRIEND_REQUES |
| | 44 | |
|
| 13 | 45 | | public ChatNotificationController(DataStore dataStore, |
| | 46 | | IMainChatNotificationsComponentView mainChatNotificationView, |
| | 47 | | ITopNotificationsComponentView topNotificationView, |
| | 48 | | IChatController chatController, |
| | 49 | | IFriendsController friendsController, |
| | 50 | | IUserProfileBridge userProfileBridge, |
| | 51 | | IProfanityFilter profanityFilter) |
| | 52 | | { |
| 13 | 53 | | this.dataStore = dataStore; |
| 13 | 54 | | this.chatController = chatController; |
| 13 | 55 | | this.friendsController = friendsController; |
| 13 | 56 | | this.userProfileBridge = userProfileBridge; |
| 13 | 57 | | this.profanityFilter = profanityFilter; |
| 13 | 58 | | this.mainChatNotificationView = mainChatNotificationView; |
| 13 | 59 | | this.topNotificationView = topNotificationView; |
| 13 | 60 | | mainChatNotificationView.OnResetFade += ResetFadeOut; |
| 13 | 61 | | topNotificationView.OnResetFade += ResetFadeOut; |
| 13 | 62 | | mainChatNotificationView.OnPanelFocus += TogglePanelBackground; |
| 13 | 63 | | mainChatNotificationView.OnClickedFriendRequest += HandleClickedFriendRequest; |
| 13 | 64 | | topNotificationView.OnClickedFriendRequest += HandleClickedFriendRequest; |
| 13 | 65 | | mainChatNotificationView.OnClickedChatMessage += OpenChat; |
| 13 | 66 | | topNotificationView.OnClickedChatMessage += OpenChat; |
| 13 | 67 | | chatController.OnAddMessage += HandleMessageAdded; |
| 13 | 68 | | friendsController.OnFriendRequestReceived += HandleFriendRequestReceived; |
| 13 | 69 | | friendsController.OnSentFriendRequestApproved += HandleSentFriendRequestApproved; |
| 13 | 70 | | notificationPanelTransform.Set(mainChatNotificationView.GetPanelTransform()); |
| 13 | 71 | | topNotificationPanelTransform.Set(topNotificationView.GetPanelTransform()); |
| 13 | 72 | | visibleTaskbarPanels.OnChange += VisiblePanelsChanged; |
| 13 | 73 | | shouldShowNotificationPanel.OnChange += ResetVisibility; |
| 13 | 74 | | ResetVisibility(shouldShowNotificationPanel.Get(), false); |
| 13 | 75 | | } |
| | 76 | |
|
| | 77 | | public void SetVisibility(bool visible) |
| | 78 | | { |
| 13 | 79 | | ResetFadeOut(visible); |
| | 80 | |
|
| 13 | 81 | | if (visible) |
| | 82 | | { |
| 13 | 83 | | if (shouldShowNotificationPanel.Get()) |
| 13 | 84 | | mainChatNotificationView.Show(); |
| | 85 | |
|
| 13 | 86 | | topNotificationView.Hide(); |
| 13 | 87 | | mainChatNotificationView.ShowNotifications(); |
| | 88 | | } |
| | 89 | | else |
| | 90 | | { |
| 0 | 91 | | mainChatNotificationView.Hide(); |
| | 92 | |
|
| 0 | 93 | | if (!visibleTaskbarPanels.Get().Contains("WorldChatPanel")) |
| 0 | 94 | | topNotificationView.Show(); |
| | 95 | | } |
| 0 | 96 | | } |
| | 97 | |
|
| | 98 | | public void Dispose() |
| | 99 | | { |
| 13 | 100 | | chatController.OnAddMessage -= HandleMessageAdded; |
| 13 | 101 | | friendsController.OnFriendRequestReceived -= HandleFriendRequestReceived; |
| 13 | 102 | | friendsController.OnSentFriendRequestApproved -= HandleSentFriendRequestApproved; |
| 13 | 103 | | visibleTaskbarPanels.OnChange -= VisiblePanelsChanged; |
| 13 | 104 | | mainChatNotificationView.OnResetFade -= ResetFadeOut; |
| 13 | 105 | | topNotificationView.OnResetFade -= ResetFadeOut; |
| 13 | 106 | | mainChatNotificationView.OnClickedChatMessage -= OpenChat; |
| 13 | 107 | | topNotificationView.OnClickedChatMessage -= OpenChat; |
| 13 | 108 | | } |
| | 109 | |
|
| | 110 | | private void VisiblePanelsChanged(HashSet<string> newList, HashSet<string> oldList) |
| | 111 | | { |
| 0 | 112 | | SetVisibility(newList.Count == 0); |
| 0 | 113 | | } |
| | 114 | |
|
| | 115 | | private void HandleMessageAdded(ChatMessage[] messages) |
| | 116 | | { |
| 31 | 117 | | foreach (var message in messages) |
| | 118 | | { |
| 8 | 119 | | if (message.messageType != ChatMessage.Type.PRIVATE && |
| 0 | 120 | | message.messageType != ChatMessage.Type.PUBLIC) return; |
| | 121 | |
|
| 8 | 122 | | if (message.sender == ownUserProfile.userId) return; |
| | 123 | |
|
| 8 | 124 | | var span = Utils.UnixToDateTimeWithTime((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()) - |
| | 125 | | Utils.UnixToDateTimeWithTime(message.timestamp); |
| | 126 | |
|
| 9 | 127 | | if (span >= maxNotificationInterval) return; |
| | 128 | |
|
| 7 | 129 | | var channel = chatController.GetAllocatedChannel( |
| | 130 | | string.IsNullOrEmpty(message.recipient) && message.messageType == ChatMessage.Type.PUBLIC |
| | 131 | | ? "nearby" |
| | 132 | | : message.recipient); |
| | 133 | |
|
| 7 | 134 | | if (channel?.Muted ?? false) return; |
| | 135 | |
|
| | 136 | | // TODO: entries may have an inconsistent state. We should update the entry with new data |
| 7 | 137 | | if (notificationEntries.Contains(message.messageId)) return; |
| 7 | 138 | | notificationEntries.Add(message.messageId); |
| | 139 | |
|
| 7 | 140 | | AddNotification(message, channel).Forget(); |
| | 141 | | } |
| 7 | 142 | | } |
| | 143 | |
|
| | 144 | | private async UniTaskVoid AddNotification(ChatMessage message, Channel channel = null) |
| | 145 | | { |
| 7 | 146 | | var peerId = ExtractPeerId(message); |
| 7 | 147 | | var peerProfile = userProfileBridge.Get(peerId); |
| 7 | 148 | | var peerName = peerProfile?.userName ?? peerId; |
| 7 | 149 | | var peerProfilePicture = peerProfile?.face256SnapshotURL; |
| 7 | 150 | | var body = message.body; |
| | 151 | |
|
| 7 | 152 | | switch (message.messageType) |
| | 153 | | { |
| | 154 | | case ChatMessage.Type.PRIVATE: |
| 2 | 155 | | var privateModel = new PrivateChatMessageNotificationModel(message.messageId, |
| | 156 | | message.sender, body, message.timestamp, peerName, peerProfilePicture); |
| | 157 | |
|
| 2 | 158 | | if (message.sender != openedChat.Get()) |
| | 159 | | { |
| 2 | 160 | | mainChatNotificationView.AddNewChatNotification(privateModel); |
| | 161 | |
|
| 2 | 162 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 2 | 163 | | topNotificationView.AddNewChatNotification(privateModel); |
| | 164 | | } |
| | 165 | |
|
| 2 | 166 | | break; |
| | 167 | | case ChatMessage.Type.PUBLIC: |
| 5 | 168 | | if (IsProfanityFilteringEnabled()) |
| | 169 | | { |
| 2 | 170 | | peerName = await profanityFilter.Filter(peerProfile?.userName ?? peerId); |
| 2 | 171 | | body = await profanityFilter.Filter(message.body); |
| | 172 | | } |
| | 173 | |
|
| 5 | 174 | | var publicModel = new PublicChannelMessageNotificationModel(message.messageId, |
| | 175 | | body, channel?.Name ?? message.recipient, channel?.ChannelId, message.timestamp, |
| | 176 | | peerName); |
| | 177 | |
|
| 5 | 178 | | if (channel?.ChannelId != openedChat.Get()) |
| | 179 | | { |
| 5 | 180 | | mainChatNotificationView.AddNewChatNotification(publicModel); |
| | 181 | |
|
| 5 | 182 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 5 | 183 | | topNotificationView.AddNewChatNotification(publicModel); |
| | 184 | | } |
| | 185 | |
|
| | 186 | | break; |
| | 187 | | } |
| 7 | 188 | | } |
| | 189 | |
|
| | 190 | | private void HandleFriendRequestReceived(FriendRequest friendRequest) |
| | 191 | | { |
| 2 | 192 | | if (!isNewFriendRequestsEnabled) return; |
| | 193 | |
|
| 2 | 194 | | if (friendRequest.From == ownUserProfile.userId || |
| | 195 | | friendRequest.To != ownUserProfile.userId) |
| 0 | 196 | | return; |
| | 197 | |
|
| 2 | 198 | | var friendRequestProfile = userProfileBridge.Get(friendRequest.From); |
| 2 | 199 | | var friendRequestName = friendRequestProfile?.userName ?? friendRequest.From; |
| | 200 | |
|
| 2 | 201 | | FriendRequestNotificationModel friendRequestNotificationModel = new FriendRequestNotificationModel( |
| | 202 | | friendRequest.FriendRequestId, |
| | 203 | | friendRequest.From, |
| | 204 | | friendRequestName, |
| | 205 | | "Friend Request received", |
| | 206 | | "wants to be your friend.", |
| | 207 | | (ulong)friendRequest.Timestamp, |
| | 208 | | false); |
| | 209 | |
|
| 2 | 210 | | mainChatNotificationView.AddNewFriendRequestNotification(friendRequestNotificationModel); |
| | 211 | |
|
| 2 | 212 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 2 | 213 | | topNotificationView.AddNewFriendRequestNotification(friendRequestNotificationModel); |
| 2 | 214 | | } |
| | 215 | |
|
| | 216 | | private void HandleSentFriendRequestApproved(FriendRequest friendRequest) |
| | 217 | | { |
| 1 | 218 | | if (!isNewFriendRequestsEnabled) return; |
| | 219 | |
|
| 1 | 220 | | string recipientUserId = friendRequest.To; |
| 1 | 221 | | var friendRequestProfile = userProfileBridge.Get(recipientUserId); |
| | 222 | |
|
| 1 | 223 | | FriendRequestNotificationModel friendRequestNotificationModel = new FriendRequestNotificationModel( |
| | 224 | | friendRequest.FriendRequestId, |
| | 225 | | recipientUserId, |
| | 226 | | friendRequestProfile.userName, |
| | 227 | | "Friend Request accepted", |
| | 228 | | "and you are friends now!", |
| | 229 | | (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), |
| | 230 | | true); |
| | 231 | |
|
| 1 | 232 | | mainChatNotificationView.AddNewFriendRequestNotification(friendRequestNotificationModel); |
| | 233 | |
|
| 1 | 234 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 1 | 235 | | topNotificationView.AddNewFriendRequestNotification(friendRequestNotificationModel); |
| 1 | 236 | | } |
| | 237 | |
|
| | 238 | | private void ResetFadeOut(bool fadeOutAfterDelay = false) |
| | 239 | | { |
| 13 | 240 | | mainChatNotificationView.ShowNotifications(); |
| | 241 | |
|
| 13 | 242 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 13 | 243 | | topNotificationView.ShowNotification(); |
| | 244 | |
|
| 13 | 245 | | fadeOutCT.Cancel(); |
| 13 | 246 | | fadeOutCT = new CancellationTokenSource(); |
| | 247 | |
|
| 13 | 248 | | if (fadeOutAfterDelay) |
| 13 | 249 | | WaitThenFadeOutNotifications(fadeOutCT.Token).Forget(); |
| 13 | 250 | | } |
| | 251 | |
|
| | 252 | | private void TogglePanelBackground(bool isInFocus) |
| | 253 | | { |
| 0 | 254 | | if (isInFocus) |
| 0 | 255 | | mainChatNotificationView.ShowPanel(); |
| | 256 | | else |
| 0 | 257 | | mainChatNotificationView.HidePanel(); |
| 0 | 258 | | } |
| | 259 | |
|
| | 260 | | private async UniTaskVoid WaitThenFadeOutNotifications(CancellationToken cancellationToken) |
| | 261 | | { |
| 39 | 262 | | await UniTask.Delay(FADEOUT_DELAY, cancellationToken: cancellationToken); |
| 13 | 263 | | await UniTask.SwitchToMainThread(cancellationToken); |
| | 264 | |
|
| 13 | 265 | | if (cancellationToken.IsCancellationRequested) |
| 0 | 266 | | return; |
| | 267 | |
|
| 13 | 268 | | mainChatNotificationView.HideNotifications(); |
| | 269 | |
|
| 13 | 270 | | if (topNotificationPanelTransform.Get() != null && |
| | 271 | | topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 0 | 272 | | topNotificationView.HideNotification(); |
| 13 | 273 | | } |
| | 274 | |
|
| | 275 | | private string ExtractPeerId(ChatMessage message) => |
| 7 | 276 | | message.sender != ownUserProfile.userId ? message.sender : message.recipient; |
| | 277 | |
|
| | 278 | | private void ResetVisibility(bool current, bool previous) => |
| 13 | 279 | | SetVisibility(current); |
| | 280 | |
|
| | 281 | | private bool IsProfanityFilteringEnabled() => |
| 5 | 282 | | dataStore.settings.profanityChatFilteringEnabled.Get(); |
| | 283 | |
|
| | 284 | | private void HandleClickedFriendRequest(string friendRequestId, string userId, bool isAcceptedFromPeer) |
| | 285 | | { |
| 2 | 286 | | if (string.IsNullOrEmpty(friendRequestId)) return; |
| | 287 | |
|
| 2 | 288 | | FriendRequest request = friendsController.GetAllocatedFriendRequest(friendRequestId); |
| 2 | 289 | | bool isFriend = friendsController.IsFriend(userId); |
| | 290 | |
|
| 2 | 291 | | if (request != null && !isFriend && !isAcceptedFromPeer) |
| 1 | 292 | | dataStore.HUDs.openReceivedFriendRequestDetail.Set(friendRequestId, true); |
| 1 | 293 | | else if (isFriend) |
| 1 | 294 | | OpenChat(userId); |
| 1 | 295 | | } |
| | 296 | |
|
| | 297 | | private void OpenChat(string chatId) => |
| 1 | 298 | | dataStore.HUDs.openChat.Set(chatId, true); |
| | 299 | | } |
| | 300 | | } |