| | 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 | |
|
| | 10 | | namespace DCL.Chat.Notifications |
| | 11 | | { |
| | 12 | | public class ChatNotificationController : IHUD |
| | 13 | | { |
| | 14 | | private const int FADEOUT_DELAY = 8000; |
| | 15 | |
|
| | 16 | | private readonly DataStore dataStore; |
| | 17 | | private readonly IChatController chatController; |
| | 18 | | private readonly IMainChatNotificationsComponentView mainChatNotificationView; |
| | 19 | | private readonly ITopNotificationsComponentView topNotificationView; |
| | 20 | | private readonly IUserProfileBridge userProfileBridge; |
| | 21 | | private readonly IProfanityFilter profanityFilter; |
| 8 | 22 | | private readonly TimeSpan maxNotificationInterval = new TimeSpan(0, 1, 0); |
| 8 | 23 | | private readonly HashSet<string> notificationEntries = new HashSet<string>(); |
| 24 | 24 | | private BaseVariable<bool> shouldShowNotificationPanel => dataStore.HUDs.shouldShowNotificationPanel; |
| 8 | 25 | | private BaseVariable<Transform> notificationPanelTransform => dataStore.HUDs.notificationPanelTransform; |
| 31 | 26 | | private BaseVariable<Transform> topNotificationPanelTransform => dataStore.HUDs.topNotificationPanelTransform; |
| 16 | 27 | | private BaseVariable<HashSet<string>> visibleTaskbarPanels => dataStore.HUDs.visibleTaskbarPanels; |
| 7 | 28 | | private BaseVariable<string> openedChat => dataStore.HUDs.openedChat; |
| 8 | 29 | | private CancellationTokenSource fadeOutCT = new CancellationTokenSource(); |
| | 30 | | private UserProfile ownUserProfile; |
| | 31 | |
|
| 8 | 32 | | public ChatNotificationController(DataStore dataStore, |
| | 33 | | IMainChatNotificationsComponentView mainChatNotificationView, |
| | 34 | | ITopNotificationsComponentView topNotificationView, |
| | 35 | | IChatController chatController, |
| | 36 | | IUserProfileBridge userProfileBridge, |
| | 37 | | IProfanityFilter profanityFilter) |
| | 38 | | { |
| 8 | 39 | | this.dataStore = dataStore; |
| 8 | 40 | | this.chatController = chatController; |
| 8 | 41 | | this.userProfileBridge = userProfileBridge; |
| 8 | 42 | | this.profanityFilter = profanityFilter; |
| 8 | 43 | | this.mainChatNotificationView = mainChatNotificationView; |
| 8 | 44 | | this.topNotificationView = topNotificationView; |
| 8 | 45 | | mainChatNotificationView.OnResetFade += ResetFadeOut; |
| 8 | 46 | | topNotificationView.OnResetFade += ResetFadeOut; |
| 8 | 47 | | mainChatNotificationView.OnPanelFocus += TogglePanelBackground; |
| 8 | 48 | | chatController.OnAddMessage += HandleMessageAdded; |
| 8 | 49 | | notificationPanelTransform.Set(mainChatNotificationView.GetPanelTransform()); |
| 8 | 50 | | topNotificationPanelTransform.Set(topNotificationView.GetPanelTransform()); |
| 8 | 51 | | visibleTaskbarPanels.OnChange += VisiblePanelsChanged; |
| 8 | 52 | | shouldShowNotificationPanel.OnChange += ResetVisibility; |
| 8 | 53 | | ResetVisibility(shouldShowNotificationPanel.Get(), false); |
| 8 | 54 | | } |
| | 55 | |
|
| | 56 | | public void SetVisibility(bool visible) |
| | 57 | | { |
| 8 | 58 | | ResetFadeOut(visible); |
| | 59 | |
|
| 8 | 60 | | if (visible) |
| | 61 | | { |
| 8 | 62 | | if (shouldShowNotificationPanel.Get()) |
| 8 | 63 | | mainChatNotificationView.Show(); |
| | 64 | |
|
| 8 | 65 | | topNotificationView.Hide(); |
| 8 | 66 | | mainChatNotificationView.ShowNotifications(); |
| 8 | 67 | | } |
| | 68 | | else |
| | 69 | | { |
| 0 | 70 | | mainChatNotificationView.Hide(); |
| 0 | 71 | | if (!visibleTaskbarPanels.Get().Contains("WorldChatPanel")) |
| 0 | 72 | | topNotificationView.Show(); |
| | 73 | | } |
| 0 | 74 | | } |
| | 75 | |
|
| | 76 | | public void Dispose() |
| | 77 | | { |
| 8 | 78 | | chatController.OnAddMessage -= HandleMessageAdded; |
| 8 | 79 | | visibleTaskbarPanels.OnChange -= VisiblePanelsChanged; |
| 8 | 80 | | mainChatNotificationView.OnResetFade -= ResetFadeOut; |
| 8 | 81 | | topNotificationView.OnResetFade -= ResetFadeOut; |
| 8 | 82 | | } |
| | 83 | |
|
| | 84 | | private void VisiblePanelsChanged(HashSet<string> newList, HashSet<string> oldList) |
| | 85 | | { |
| 0 | 86 | | SetVisibility(newList.Count == 0); |
| 0 | 87 | | } |
| | 88 | |
|
| | 89 | | private void HandleMessageAdded(ChatMessage message) |
| | 90 | | { |
| 8 | 91 | | if (message.messageType != ChatMessage.Type.PRIVATE && |
| 0 | 92 | | message.messageType != ChatMessage.Type.PUBLIC) return; |
| 8 | 93 | | ownUserProfile ??= userProfileBridge.GetOwn(); |
| 8 | 94 | | if (message.sender == ownUserProfile.userId) return; |
| | 95 | |
|
| 8 | 96 | | var span = Utils.UnixToDateTimeWithTime((ulong) DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()) - |
| | 97 | | Utils.UnixToDateTimeWithTime(message.timestamp); |
| | 98 | |
|
| 9 | 99 | | if (span >= maxNotificationInterval) return; |
| | 100 | |
|
| 7 | 101 | | var channel = chatController.GetAllocatedChannel( |
| | 102 | | string.IsNullOrEmpty(message.recipient) && message.messageType == ChatMessage.Type.PUBLIC |
| | 103 | | ? "nearby" |
| | 104 | | : message.recipient); |
| 7 | 105 | | if (channel?.Muted ?? false) return; |
| | 106 | |
|
| | 107 | | // TODO: entries may have an inconsistent state. We should update the entry with new data |
| 7 | 108 | | if (notificationEntries.Contains(message.messageId)) return; |
| 7 | 109 | | notificationEntries.Add(message.messageId); |
| | 110 | |
|
| 7 | 111 | | AddNotification(message, channel).Forget(); |
| 7 | 112 | | } |
| | 113 | |
|
| | 114 | | private async UniTaskVoid AddNotification(ChatMessage message, Channel channel = null) |
| | 115 | | { |
| 7 | 116 | | var peerId = ExtractPeerId(message); |
| 7 | 117 | | var peerProfile = userProfileBridge.Get(peerId); |
| 7 | 118 | | var peerName = peerProfile?.userName ?? peerId; |
| 7 | 119 | | var peerProfilePicture = peerProfile?.face256SnapshotURL; |
| 7 | 120 | | var body = message.body; |
| | 121 | |
|
| 7 | 122 | | switch (message.messageType) |
| | 123 | | { |
| | 124 | | case ChatMessage.Type.PRIVATE: |
| 2 | 125 | | var privateModel = new PrivateChatMessageNotificationModel(message.messageId, |
| | 126 | | message.sender, body, message.timestamp, peerName, peerProfilePicture); |
| | 127 | |
|
| 2 | 128 | | if (message.sender != openedChat.Get()) |
| | 129 | | { |
| 2 | 130 | | mainChatNotificationView.AddNewChatNotification(privateModel); |
| 2 | 131 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 2 | 132 | | topNotificationView.AddNewChatNotification(privateModel); |
| | 133 | | } |
| | 134 | |
|
| 2 | 135 | | break; |
| | 136 | | case ChatMessage.Type.PUBLIC: |
| 5 | 137 | | if (IsProfanityFilteringEnabled()) |
| | 138 | | { |
| 2 | 139 | | peerName = await profanityFilter.Filter(peerProfile?.userName ?? peerId); |
| 2 | 140 | | body = await profanityFilter.Filter(message.body); |
| | 141 | | } |
| 5 | 142 | | var publicModel = new PublicChannelMessageNotificationModel(message.messageId, |
| | 143 | | body, channel?.Name ?? message.recipient, channel?.ChannelId, message.timestamp, |
| | 144 | | peerName); |
| | 145 | |
|
| 5 | 146 | | if (channel?.ChannelId != openedChat.Get()) |
| | 147 | | { |
| 5 | 148 | | mainChatNotificationView.AddNewChatNotification(publicModel); |
| 5 | 149 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 5 | 150 | | topNotificationView.AddNewChatNotification(publicModel); |
| | 151 | | } |
| | 152 | |
|
| 7 | 153 | | break; |
| | 154 | | } |
| 7 | 155 | | } |
| | 156 | |
|
| | 157 | | private void ResetFadeOut(bool fadeOutAfterDelay = false) |
| | 158 | | { |
| 8 | 159 | | mainChatNotificationView.ShowNotifications(); |
| 8 | 160 | | if (topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 8 | 161 | | topNotificationView.ShowNotification(); |
| | 162 | |
|
| 8 | 163 | | fadeOutCT.Cancel(); |
| 8 | 164 | | fadeOutCT = new CancellationTokenSource(); |
| | 165 | |
|
| 8 | 166 | | if (fadeOutAfterDelay) |
| 8 | 167 | | WaitThenFadeOutNotifications(fadeOutCT.Token).Forget(); |
| 8 | 168 | | } |
| | 169 | |
|
| | 170 | | private void TogglePanelBackground(bool isInFocus) |
| | 171 | | { |
| 0 | 172 | | if (isInFocus) |
| 0 | 173 | | mainChatNotificationView.ShowPanel(); |
| | 174 | | else |
| 0 | 175 | | mainChatNotificationView.HidePanel(); |
| 0 | 176 | | } |
| | 177 | |
|
| | 178 | | private async UniTaskVoid WaitThenFadeOutNotifications(CancellationToken cancellationToken) |
| | 179 | | { |
| 24 | 180 | | await UniTask.Delay(FADEOUT_DELAY, cancellationToken: cancellationToken); |
| 8 | 181 | | await UniTask.SwitchToMainThread(cancellationToken); |
| 8 | 182 | | if (cancellationToken.IsCancellationRequested) |
| 0 | 183 | | return; |
| | 184 | |
|
| 8 | 185 | | mainChatNotificationView.HideNotifications(); |
| | 186 | |
|
| 8 | 187 | | if (topNotificationPanelTransform.Get() != null && |
| | 188 | | topNotificationPanelTransform.Get().gameObject.activeInHierarchy) |
| 0 | 189 | | topNotificationView.HideNotification(); |
| 8 | 190 | | } |
| | 191 | |
|
| | 192 | | private string ExtractPeerId(ChatMessage message) => |
| 7 | 193 | | message.sender != ownUserProfile.userId ? message.sender : message.recipient; |
| | 194 | |
|
| 8 | 195 | | private void ResetVisibility(bool current, bool previous) => SetVisibility(current); |
| | 196 | |
|
| | 197 | | private bool IsProfanityFilteringEnabled() => |
| 5 | 198 | | dataStore.settings.profanityChatFilteringEnabled.Get(); |
| | 199 | | } |
| | 200 | | } |