From 090b81c989151357c9edc431839dcd41b701bf5f Mon Sep 17 00:00:00 2001 From: choco Date: Mon, 6 Oct 2025 16:14:34 +0200 Subject: [PATCH] added notification system for file downloads and pair requests --- .../Configurations/LightlessConfig.cs | 1 + LightlessSync/Plugin.cs | 17 +- .../Services/LightlessNotificationService.cs | 212 ++++++++ LightlessSync/Services/Mediator/Messages.cs | 4 +- LightlessSync/Services/UiService.cs | 9 +- LightlessSync/UI/CompactUI.cs | 4 +- LightlessSync/UI/DownloadUi.cs | 108 +++- LightlessSync/UI/LightlessNotificationUI.cs | 469 ++++++++++++++++++ .../UI/Models/LightlessNotification.cs | 34 ++ LightlessSync/UI/SettingsUi.cs | 10 +- LightlessSync/UI/TopTabMenu.cs | 84 +++- .../ApiController.Functions.Callbacks.cs | 50 +- LightlessSync/WebAPI/SignalR/ApiController.cs | 4 +- 13 files changed, 960 insertions(+), 46 deletions(-) create mode 100644 LightlessSync/Services/LightlessNotificationService.cs create mode 100644 LightlessSync/UI/LightlessNotificationUI.cs create mode 100644 LightlessSync/UI/Models/LightlessNotification.cs diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index f90472a..c7359e9 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -56,6 +56,7 @@ public class LightlessConfig : ILightlessConfiguration public bool ShowOnlineNotificationsOnlyForNamedPairs { get; set; } = false; public bool ShowTransferBars { get; set; } = true; public bool ShowTransferWindow { get; set; } = false; + public bool UseNotificationsForDownloads { get; set; } = true; public bool ShowUploading { get; set; } = true; public bool ShowUploadingBigText { get; set; } = true; public bool ShowVisibleUsersSeparately { get; set; } = true; diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index c1648ca..745afd3 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -1,4 +1,4 @@ -using Dalamud.Game; +using Dalamud.Game; using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Windowing; @@ -175,6 +175,11 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton((s) => new NotificationService(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), notificationManager, chatGui, s.GetRequiredService())); + collection.AddSingleton((s) => new LightlessNotificationService( + s.GetRequiredService>(), + s.GetRequiredService(), + s.GetRequiredService(), + s.GetRequiredService())); collection.AddSingleton((s) => { var httpClient = new HttpClient(); @@ -235,6 +240,11 @@ public sealed class Plugin : IDalamudPlugin collection.AddScoped((s) => new BroadcastUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped((s) => new SyncshellFinderUI(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddScoped(); + collection.AddScoped((s) => + new LightlessNotificationUI( + s.GetRequiredService>(), + s.GetRequiredService(), + s.GetRequiredService())); collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); @@ -242,7 +252,9 @@ public sealed class Plugin : IDalamudPlugin collection.AddScoped((s) => new UiService(s.GetRequiredService>(), pluginInterface.UiBuilder, s.GetRequiredService(), s.GetRequiredService(), s.GetServices(), s.GetRequiredService(), - s.GetRequiredService(), s.GetRequiredService())); + s.GetRequiredService(), + s.GetRequiredService(), + s.GetRequiredService())); collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); @@ -261,6 +273,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); + collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); diff --git a/LightlessSync/Services/LightlessNotificationService.cs b/LightlessSync/Services/LightlessNotificationService.cs new file mode 100644 index 0000000..7dabe71 --- /dev/null +++ b/LightlessSync/Services/LightlessNotificationService.cs @@ -0,0 +1,212 @@ +using Dalamud.Interface; +using LightlessSync.LightlessConfiguration; +using LightlessSync.LightlessConfiguration.Models; +using LightlessSync.Services.Mediator; +using LightlessSync.UI; +using LightlessSync.UI.Models; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Numerics; +namespace LightlessSync.Services; +public class LightlessNotificationService : DisposableMediatorSubscriberBase, IHostedService +{ + private readonly ILogger _logger; + private readonly LightlessConfigService _configService; + private readonly DalamudUtilService _dalamudUtilService; + private LightlessNotificationUI? _notificationUI; + public LightlessNotificationService( + ILogger logger, + LightlessConfigService configService, + DalamudUtilService dalamudUtilService, + LightlessMediator mediator) : base(logger, mediator) + { + _logger = logger; + _configService = configService; + _dalamudUtilService = dalamudUtilService; + } + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + public void SetNotificationUI(LightlessNotificationUI notificationUI) + { + _notificationUI = notificationUI; + } + public void ShowNotification(string title, string message, NotificationType type = NotificationType.Info, + TimeSpan? duration = null, List? actions = null) + { + var notification = new LightlessNotification + { + Title = title, + Message = message, + Type = type, + Duration = duration ?? TimeSpan.FromSeconds(10), + Actions = actions ?? new List() + }; + Mediator.Publish(new LightlessNotificationMessage(notification)); + } + public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline) + { + var notification = new LightlessNotification + { + Title = "Pair Request Received", + Message = $"{senderName} wants to pair with you.", + Type = NotificationType.Info, + Duration = TimeSpan.FromSeconds(60), + Actions = new List + { + new() + { + Id = "accept", + Label = "Accept", + Icon = FontAwesomeIcon.Check, + Color = UIColors.Get("LightlessGreen"), + IsPrimary = true, + OnClick = (n) => + { + _logger.LogInformation("Pair request accepted"); + onAccept(); + n.IsDismissed = true; + n.IsAnimatingOut = true; + } + }, + new() + { + Id = "decline", + Label = "Decline", + Icon = FontAwesomeIcon.Times, + Color = UIColors.Get("DimRed"), + IsDestructive = true, + OnClick = (n) => + { + _logger.LogInformation("Pair request declined"); + onDecline(); + n.IsDismissed = true; + n.IsAnimatingOut = true; + } + } + } + }; + Mediator.Publish(new LightlessNotificationMessage(notification)); + } + public void ShowDownloadCompleteNotification(string fileName, int fileCount, Action? onOpenFolder = null) + { + var actions = new List(); + + if (onOpenFolder != null) + { + actions.Add(new LightlessNotificationAction + { + Id = "open_folder", + Label = "Open Folder", + Icon = FontAwesomeIcon.FolderOpen, + Color = UIColors.Get("LightlessBlue"), + OnClick = (n) => + { + onOpenFolder(); + n.IsDismissed = true; + n.IsAnimatingOut = true; + } + }); + } + var notification = new LightlessNotification + { + Title = "Download Complete", + Message = fileCount > 1 ? + $"Downloaded {fileCount} files successfully." : + $"Downloaded {fileName} successfully.", + Type = NotificationType.Info, + Duration = TimeSpan.FromSeconds(8), + Actions = actions + }; + Mediator.Publish(new LightlessNotificationMessage(notification)); + } + public void ShowErrorNotification(string title, string message, Exception? exception = null, Action? onRetry = null, Action? onViewLog = null) + { + var actions = new List(); + if (onRetry != null) + { + actions.Add(new LightlessNotificationAction + { + Id = "retry", + Label = "Retry", + Icon = FontAwesomeIcon.Redo, + Color = UIColors.Get("LightlessBlue"), + OnClick = (n) => + { + onRetry(); + n.IsDismissed = true; + n.IsAnimatingOut = true; + } + }); + } + if (onViewLog != null) + { + actions.Add(new LightlessNotificationAction + { + Id = "view_log", + Label = "View Log", + Icon = FontAwesomeIcon.FileAlt, + Color = UIColors.Get("LightlessYellow"), + OnClick = (n) => onViewLog() + }); + } + var notification = new LightlessNotification + { + Title = title, + Message = exception != null ? $"{message}\n\nError: {exception.Message}" : message, + Type = NotificationType.Error, + Duration = TimeSpan.FromSeconds(15), + Actions = actions + }; + Mediator.Publish(new LightlessNotificationMessage(notification)); + } + public void ShowPairDownloadNotification(List<(string playerName, float progress, string status)> downloadStatus) + { + var totalProgress = downloadStatus.Count > 0 ? downloadStatus.Average(x => x.progress) : 0f; + var completedCount = downloadStatus.Count(x => x.progress >= 1.0f); + var totalCount = downloadStatus.Count; + + var message = $"Progress: {completedCount}/{totalCount} completed"; + if (downloadStatus.Any(x => x.progress < 1.0f)) + { + var activeDownloads = downloadStatus.Where(x => x.progress < 1.0f).Take(3); + message += "\n" + string.Join("\n", activeDownloads.Select(x => + { + var statusText = x.status switch + { + "downloading" => $"{x.progress:P0}", + "decompressing" => "decompressing", + "queued" => "queued", + "waiting" => "waiting for slot", + _ => x.status + }; + return $"• {x.playerName}: {statusText}"; + })); + + if (downloadStatus.Count(x => x.progress < 1.0f) > 3) + { + message += $"\n• ... and {downloadStatus.Count(x => x.progress < 1.0f) - 3} more"; + } + } + var notification = new LightlessNotification + { + Id = "pair_download_progress", + Title = "Downloading Pair Data", + Message = message, + Type = NotificationType.Info, + Duration = TimeSpan.FromMinutes(5), + ShowProgress = true, + Progress = totalProgress + }; + Mediator.Publish(new LightlessNotificationMessage(notification)); + } + public void DismissPairDownloadNotification() + { + Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress")); + } +} \ No newline at end of file diff --git a/LightlessSync/Services/Mediator/Messages.cs b/LightlessSync/Services/Mediator/Messages.cs index 963bcbf..a7ee439 100644 --- a/LightlessSync/Services/Mediator/Messages.cs +++ b/LightlessSync/Services/Mediator/Messages.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Types; using LightlessSync.API.Data; using LightlessSync.API.Dto; using LightlessSync.API.Dto.CharaData; @@ -53,6 +53,8 @@ public record NotificationMessage public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage; +public record LightlessNotificationMessage(LightlessSync.UI.Models.LightlessNotification Notification) : MessageBase; +public record LightlessNotificationDismissMessage(string NotificationId) : MessageBase; public record CharacterDataAnalyzedMessage : MessageBase; public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase; public record PenumbraEndRedrawMessage(IntPtr Address) : MessageBase; diff --git a/LightlessSync/Services/UiService.cs b/LightlessSync/Services/UiService.cs index 4071b61..73fc289 100644 --- a/LightlessSync/Services/UiService.cs +++ b/LightlessSync/Services/UiService.cs @@ -22,7 +22,8 @@ public sealed class UiService : DisposableMediatorSubscriberBase LightlessConfigService lightlessConfigService, WindowSystem windowSystem, IEnumerable windows, UiFactory uiFactory, FileDialogManager fileDialogManager, - LightlessMediator lightlessMediator) : base(logger, lightlessMediator) + LightlessMediator lightlessMediator, + LightlessNotificationService lightlessNotificationService) : base(logger, lightlessMediator) { _logger = logger; _logger.LogTrace("Creating {type}", GetType().Name); @@ -40,6 +41,12 @@ public sealed class UiService : DisposableMediatorSubscriberBase foreach (var window in windows) { _windowSystem.AddWindow(window); + + // Connect the notification service to the notification UI + if (window is LightlessNotificationUI notificationUI) + { + lightlessNotificationService.SetNotificationUI(notificationUI); + } } Mediator.Subscribe(this, (msg) => diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index b02594f..c724124 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -87,7 +87,7 @@ public class CompactUi : WindowMediatorSubscriberBase IpcManager ipcManager, BroadcastService broadcastService, CharacterAnalyzer characterAnalyzer, - PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService) + PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService, LightlessNotificationService lightlessNotificationService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService) { _uiSharedService = uiShared; _configService = configService; @@ -105,7 +105,7 @@ public class CompactUi : WindowMediatorSubscriberBase _renamePairTagUi = renameTagUi; _ipcManager = ipcManager; _broadcastService = broadcastService; - _tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService, pairRequestService, dalamudUtilService); + _tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService, pairRequestService, dalamudUtilService, lightlessNotificationService); AllowPinning = true; AllowClickthrough = false; diff --git a/LightlessSync/UI/DownloadUi.cs b/LightlessSync/UI/DownloadUi.cs index 2e9f366..eb38bc1 100644 --- a/LightlessSync/UI/DownloadUi.cs +++ b/LightlessSync/UI/DownloadUi.cs @@ -1,4 +1,4 @@ -using System; +using System; using Dalamud.Bindings.ImGui; using Dalamud.Interface.Colors; using LightlessSync.LightlessConfiguration; @@ -22,9 +22,12 @@ public class DownloadUi : WindowMediatorSubscriberBase private readonly UiSharedService _uiShared; private readonly PairProcessingLimiter _pairProcessingLimiter; private readonly ConcurrentDictionary _uploadingPlayers = new(); + private readonly LightlessNotificationService _notificationService; + private bool _notificationDismissed = true; public DownloadUi(ILogger logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService, - PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared, PerformanceCollectorService performanceCollectorService) + PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared, + PerformanceCollectorService performanceCollectorService, LightlessNotificationService notificationService) : base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService) { _dalamudUtilService = dalamudUtilService; @@ -32,6 +35,7 @@ public class DownloadUi : WindowMediatorSubscriberBase _pairProcessingLimiter = pairProcessingLimiter; _fileTransferManager = fileTransferManager; _uiShared = uiShared; + _notificationService = notificationService; SizeConstraints = new WindowSizeConstraints() { @@ -56,7 +60,14 @@ public class DownloadUi : WindowMediatorSubscriberBase IsOpen = true; Mediator.Subscribe(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus); - Mediator.Subscribe(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _)); + Mediator.Subscribe(this, (msg) => + { + _currentDownloads.TryRemove(msg.DownloadId, out _); + if (!_currentDownloads.Any()) + { + _notificationService.DismissPairDownloadNotification(); + } + }); Mediator.Subscribe(this, (_) => IsOpen = false); Mediator.Subscribe(this, (_) => IsOpen = true); Mediator.Subscribe(this, (msg) => @@ -122,28 +133,46 @@ public class DownloadUi : WindowMediatorSubscriberBase try { - foreach (var item in _currentDownloads.ToList()) + if (_configService.Current.UseNotificationsForDownloads) { - var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot); - var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue); - var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading); - var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing); - var totalFiles = item.Value.Sum(c => c.Value.TotalFiles); - var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles); - var totalBytes = item.Value.Sum(c => c.Value.TotalBytes); - var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes); + // Use new notification stuff + if (_currentDownloads.Any()) + { + UpdateDownloadNotification(); + _notificationDismissed = false; + } + else if (!_notificationDismissed) + { + _notificationService.DismissPairDownloadNotification(); + _notificationDismissed = true; + } + } + else + { + // text overlay + foreach (var item in _currentDownloads.ToList()) + { + var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot); + var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue); + var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading); + var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing); + var totalFiles = item.Value.Sum(c => c.Value.TotalFiles); + var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles); + var totalBytes = item.Value.Sum(c => c.Value.TotalBytes); + var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes); - UiSharedService.DrawOutlinedFont($"▼", ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1); - ImGui.SameLine(); - var xDistance = ImGui.GetCursorPosX(); - UiSharedService.DrawOutlinedFont( - $"{item.Key.Name} [W:{dlSlot}/Q:{dlQueue}/P:{dlProg}/D:{dlDecomp}]", - ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1); - ImGui.NewLine(); - ImGui.SameLine(xDistance); - UiSharedService.DrawOutlinedFont( - $"{transferredFiles}/{totalFiles} ({UiSharedService.ByteToString(transferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(totalBytes)})", - ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1); + UiSharedService.DrawOutlinedFont($"▼", ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1); + ImGui.SameLine(); + var xDistance = ImGui.GetCursorPosX(); + UiSharedService.DrawOutlinedFont( + $"{item.Key.Name} [W:{dlSlot}/Q:{dlQueue}/P:{dlProg}/D:{dlDecomp}]", + ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1); + ImGui.NewLine(); + ImGui.SameLine(xDistance); + UiSharedService.DrawOutlinedFont( + $"{transferredFiles}/{totalFiles} ({UiSharedService.ByteToString(transferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(totalBytes)})", + ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1); + } } } catch @@ -262,4 +291,37 @@ public class DownloadUi : WindowMediatorSubscriberBase MaximumSize = new Vector2(300, maxHeight), }; } + + private void UpdateDownloadNotification() + { + var downloadStatus = new List<(string playerName, float progress, string status)>(); + + foreach (var item in _currentDownloads.ToList()) + { + var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot); + var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue); + var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading); + var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing); + var totalFiles = item.Value.Sum(c => c.Value.TotalFiles); + var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles); + var totalBytes = item.Value.Sum(c => c.Value.TotalBytes); + var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes); + + var progress = totalBytes > 0 ? (float)transferredBytes / totalBytes : 0f; + + string status; + if (dlDecomp > 0) status = "decompressing"; + else if (dlProg > 0) status = "downloading"; + else if (dlQueue > 0) status = "queued"; + else if (dlSlot > 0) status = "waiting"; + else status = "completed"; + + downloadStatus.Add((item.Key.Name, progress, status)); + } + + if (downloadStatus.Any()) + { + _notificationService.ShowPairDownloadNotification(downloadStatus); + } + } } \ No newline at end of file diff --git a/LightlessSync/UI/LightlessNotificationUI.cs b/LightlessSync/UI/LightlessNotificationUI.cs new file mode 100644 index 0000000..f1c8455 --- /dev/null +++ b/LightlessSync/UI/LightlessNotificationUI.cs @@ -0,0 +1,469 @@ +using Dalamud.Interface; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using LightlessSync.LightlessConfiguration.Models; +using LightlessSync.Services; +using LightlessSync.Services.Mediator; +using LightlessSync.UI.Models; +using Microsoft.Extensions.Logging; +using System.Numerics; +using Dalamud.Bindings.ImGui; + +namespace LightlessSync.UI; + +public class LightlessNotificationUI : WindowMediatorSubscriberBase +{ + private readonly List _notifications = new(); + private readonly object _notificationLock = new(); + + private const float NotificationWidth = 350f; + private const float NotificationMinHeight = 60f; + private const float NotificationSpacing = 8f; + private const float AnimationSpeed = 10f; + private const float EdgeXMargin = 0; + private const float EdgeYMargin = 30f; + private const float SlideDistance = 100f; + + public LightlessNotificationUI(ILogger logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector) + : base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector) + { + Flags = ImGuiWindowFlags.NoDecoration | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoResize | + ImGuiWindowFlags.NoSavedSettings | + ImGuiWindowFlags.NoFocusOnAppearing | + ImGuiWindowFlags.NoNav | + ImGuiWindowFlags.NoBackground | + ImGuiWindowFlags.NoCollapse | + ImGuiWindowFlags.AlwaysAutoResize; + + var viewport = ImGui.GetMainViewport(); + if (viewport.WorkSize.X > 0) + { + Position = new Vector2(viewport.WorkPos.X + viewport.WorkSize.X - NotificationWidth - EdgeXMargin, + viewport.WorkPos.Y + EdgeYMargin); + PositionCondition = ImGuiCond.Always; + } + + Size = new Vector2(NotificationWidth, 100); + SizeCondition = ImGuiCond.FirstUseEver; + IsOpen = false; + RespectCloseHotkey = false; + DisableWindowSounds = true; + + Mediator.Subscribe(this, HandleNotificationMessage); + Mediator.Subscribe(this, HandleNotificationDismissMessage); + } + private void HandleNotificationMessage(LightlessNotificationMessage message) + { + AddNotification(message.Notification); + } + + private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) + { + RemoveNotification(message.NotificationId); + } + + public void AddNotification(LightlessNotification notification) + { + lock (_notificationLock) + { + var existingNotification = _notifications.FirstOrDefault(n => n.Id == notification.Id); + if (existingNotification != null) + { + // Update existing notification without restarting animation + existingNotification.Message = notification.Message; + existingNotification.Progress = notification.Progress; + existingNotification.ShowProgress = notification.ShowProgress; + existingNotification.Title = notification.Title; + _logger.LogDebug("Updated existing notification: {Title}", notification.Title); + } + else + { + _notifications.Add(notification); + _logger.LogDebug("Added new notification: {Title}", notification.Title); + } + + if (!IsOpen) + { + IsOpen = true; + } + } + } + + public void RemoveNotification(string id) + { + lock (_notificationLock) + { + var notification = _notifications.FirstOrDefault(n => n.Id == id); + if (notification != null) + { + notification.IsAnimatingOut = true; + notification.IsAnimatingIn = false; + } + } + } + + protected override void DrawInternal() + { + lock (_notificationLock) + { + UpdateNotifications(); + + if (_notifications.Count == 0) + { + IsOpen = false; + return; + } + + var viewport = ImGui.GetMainViewport(); + var windowPos = new Vector2( + viewport.WorkPos.X + viewport.WorkSize.X - NotificationWidth - EdgeXMargin, + viewport.WorkPos.Y + EdgeYMargin + ); + ImGui.SetWindowPos(windowPos); + + for (int i = 0; i < _notifications.Count; i++) + { + DrawNotification(_notifications[i], i); + + if (i < _notifications.Count - 1) + { + ImGui.Dummy(new Vector2(0, NotificationSpacing)); + } + } + } + } + + private void UpdateNotifications() + { + var deltaTime = ImGui.GetIO().DeltaTime; + + for (int i = _notifications.Count - 1; i >= 0; i--) + { + var notification = _notifications[i]; + + if (notification.IsAnimatingIn && notification.AnimationProgress < 1f) + { + notification.AnimationProgress = Math.Min(1f, notification.AnimationProgress + deltaTime * AnimationSpeed); + } + else if (notification.IsAnimatingOut && notification.AnimationProgress > 0f) + { + notification.AnimationProgress = Math.Max(0f, notification.AnimationProgress - deltaTime * (AnimationSpeed * 0.7f)); + } + else if (!notification.IsAnimatingOut && !notification.IsDismissed) + { + notification.IsAnimatingIn = false; + + if (notification.IsExpired && !notification.IsAnimatingOut) + { + notification.IsAnimatingOut = true; + notification.IsAnimatingIn = false; + } + } + + if (notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f) + { + _notifications.RemoveAt(i); + } + } + } + + private void DrawNotification(LightlessNotification notification, int index) + { + var alpha = notification.AnimationProgress; + if (alpha <= 0f) return; + + var slideOffset = (1f - alpha) * SlideDistance; + var originalCursorPos = ImGui.GetCursorPos(); + ImGui.SetCursorPosX(originalCursorPos.X + slideOffset); + + var notificationHeight = CalculateNotificationHeight(notification); + + using var child = ImRaii.Child($"notification_{notification.Id}", + new Vector2(NotificationWidth - slideOffset, notificationHeight), + false, ImGuiWindowFlags.NoScrollbar); + + if (child.Success) + { + DrawNotificationContent(notification, alpha); + } + } + + private void DrawNotificationContent(LightlessNotification notification, float alpha) + { + var drawList = ImGui.GetWindowDrawList(); + var windowPos = ImGui.GetWindowPos(); + var windowSize = ImGui.GetWindowSize(); + + var bgColor = new Vector4(30f/255f, 30f/255f, 30f/255f, 0.95f * alpha); + var accentColor = GetNotificationAccentColor(notification.Type); + var progressBarColor = UIColors.Get("LightlessBlue"); + accentColor.W *= alpha; + + var shadowOffset = new Vector2(1f, 1f); + var shadowColor = new Vector4(0f, 0f, 0f, 0.4f * alpha); + drawList.AddRectFilled( + windowPos + shadowOffset, + windowPos + windowSize + shadowOffset, + ImGui.ColorConvertFloat4ToU32(shadowColor), + 3f + ); + + var isHovered = ImGui.IsWindowHovered(); + if (isHovered) + { + bgColor = bgColor * 1.1f; + bgColor.W = Math.Min(bgColor.W, 0.98f); + } + + drawList.AddRectFilled( + windowPos, + windowPos + windowSize, + ImGui.ColorConvertFloat4ToU32(bgColor), + 3f + ); + + var accentWidth = 3f; + drawList.AddRectFilled( + windowPos, + windowPos + new Vector2(accentWidth, windowSize.Y), + ImGui.ColorConvertFloat4ToU32(accentColor), + 3f + ); + + DrawDurationProgressBar(notification, alpha, windowPos, windowSize, progressBarColor, drawList); + + DrawNotificationText(notification, alpha); + } + + private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, Vector4 progressBarColor, ImDrawListPtr drawList) + { + var elapsed = DateTime.UtcNow - notification.CreatedAt; + var progress = Math.Min(1.0f, (float)(elapsed.TotalSeconds / notification.Duration.TotalSeconds)); + + var progressHeight = 2f; + var progressY = windowPos.Y + windowSize.Y - progressHeight; + var progressWidth = windowSize.X * progress; + + var bgProgressColor = new Vector4(progressBarColor.X * 0.3f, progressBarColor.Y * 0.3f, progressBarColor.Z * 0.3f, 0.5f * alpha); + drawList.AddRectFilled( + new Vector2(windowPos.X, progressY), + new Vector2(windowPos.X + windowSize.X, progressY + progressHeight), + ImGui.ColorConvertFloat4ToU32(bgProgressColor), + 0f + ); + + if (progress > 0) + { + var progressColor = progressBarColor; + progressColor.W *= alpha; + drawList.AddRectFilled( + new Vector2(windowPos.X, progressY), + new Vector2(windowPos.X + progressWidth, progressY + progressHeight), + ImGui.ColorConvertFloat4ToU32(progressColor), + 0f + ); + } + } + + private void DrawNotificationText(LightlessNotification notification, float alpha) + { + var padding = new Vector2(10f, 6f); + var contentPos = new Vector2(6f, 0f) + padding; + var windowSize = ImGui.GetWindowSize(); + var contentSize = windowSize - padding * 2 - new Vector2(6f, 0f); + + ImGui.SetCursorPos(contentPos); + + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha))) + { + ImGui.Text(notification.Title); + } + + if (!string.IsNullOrEmpty(notification.Message)) + { + ImGui.SetCursorPos(contentPos + new Vector2(0f, 18f)); + ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentSize.X); + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.9f, 0.9f, 0.9f, alpha))) + { + ImGui.TextWrapped(notification.Message); + } + ImGui.PopTextWrapPos(); + } + + if (notification.ShowProgress) + { + ImGui.Spacing(); + var progressColor = GetNotificationAccentColor(notification.Type); + progressColor.W *= alpha; + using (ImRaii.PushColor(ImGuiCol.PlotHistogram, progressColor)) + { + ImGui.ProgressBar(notification.Progress, new Vector2(contentSize.X, 2f), ""); + } + } + + if (notification.Actions.Count > 0) + { + ImGui.Spacing(); + ImGui.SetCursorPosX(contentPos.X); + DrawNotificationActions(notification, contentSize.X, alpha); + } + } + + private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha) + { + var buttonSpacing = 8f; + var rightPadding = 10f; + var usableWidth = availableWidth - rightPadding; + var totalSpacing = (notification.Actions.Count - 1) * buttonSpacing; + var buttonWidth = (usableWidth - totalSpacing) / notification.Actions.Count; + + _logger.LogDebug("Drawing {ActionCount} notification actions, buttonWidth: {ButtonWidth}, availableWidth: {AvailableWidth}", + notification.Actions.Count, buttonWidth, availableWidth); + + var startCursorPos = ImGui.GetCursorPos(); + + for (int i = 0; i < notification.Actions.Count; i++) + { + var action = notification.Actions[i]; + + if (i > 0) + { + ImGui.SameLine(); + var currentX = startCursorPos.X + i * (buttonWidth + buttonSpacing); + ImGui.SetCursorPosX(currentX); + } + DrawActionButton(action, notification, alpha, buttonWidth); + } + } + + private void DrawActionButton(LightlessNotificationAction action, LightlessNotification notification, float alpha, float buttonWidth) + { + _logger.LogDebug("Drawing action button: {ActionId} - {ActionLabel}, width: {ButtonWidth}", action.Id, action.Label, buttonWidth); + + var buttonColor = action.Color; + buttonColor.W *= alpha; + + var hoveredColor = buttonColor * 1.1f; + hoveredColor.W = buttonColor.W; + + var activeColor = buttonColor * 0.9f; + activeColor.W = buttonColor.W; + + using (ImRaii.PushColor(ImGuiCol.Button, buttonColor)) + using (ImRaii.PushColor(ImGuiCol.ButtonHovered, hoveredColor)) + using (ImRaii.PushColor(ImGuiCol.ButtonActive, activeColor)) + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha))) + { + var buttonPressed = false; + + if (action.Icon != FontAwesomeIcon.None) + { + buttonPressed = DrawIconTextButton(action.Icon, action.Label, buttonWidth, alpha); + } + else + { + buttonPressed = ImGui.Button(action.Label, new Vector2(buttonWidth, 0)); + } + + _logger.LogDebug("Button {ActionId} pressed: {ButtonPressed}", action.Id, buttonPressed); + + if (buttonPressed) + { + try + { + _logger.LogDebug("Executing action: {ActionId}", action.Id); + action.OnClick(notification); + _logger.LogDebug("Action executed successfully: {ActionId}", action.Id); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error executing notification action: {ActionId}", action.Id); + } + } + } + } + + private bool DrawIconTextButton(FontAwesomeIcon icon, string text, float width, float alpha) + { + var drawList = ImGui.GetWindowDrawList(); + var cursorPos = ImGui.GetCursorScreenPos(); + var frameHeight = ImGui.GetFrameHeight(); + + Vector2 iconSize; + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + iconSize = ImGui.CalcTextSize(icon.ToIconString()); + } + + var textSize = ImGui.CalcTextSize(text); + var spacing = 3f * ImGuiHelpers.GlobalScale; + var totalTextWidth = iconSize.X + spacing + textSize.X; + + var buttonPressed = ImGui.InvisibleButton($"btn_{icon}_{text}", new Vector2(width, frameHeight)); + + var buttonMin = ImGui.GetItemRectMin(); + var buttonMax = ImGui.GetItemRectMax(); + var buttonSize = buttonMax - buttonMin; + + var buttonColor = ImGui.GetColorU32(ImGuiCol.Button); + if (ImGui.IsItemHovered()) + buttonColor = ImGui.GetColorU32(ImGuiCol.ButtonHovered); + if (ImGui.IsItemActive()) + buttonColor = ImGui.GetColorU32(ImGuiCol.ButtonActive); + + drawList.AddRectFilled(buttonMin, buttonMax, buttonColor, 3f); + + var iconPos = buttonMin + new Vector2((buttonSize.X - totalTextWidth) / 2f, (buttonSize.Y - iconSize.Y) / 2f); + var textPos = iconPos + new Vector2(iconSize.X + spacing, (iconSize.Y - textSize.Y) / 2f); + + var textColor = ImGui.GetColorU32(ImGuiCol.Text); + + // Draw icon + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + drawList.AddText(iconPos, textColor, icon.ToIconString()); + } + + // Draw text + drawList.AddText(textPos, textColor, text); + + return buttonPressed; + } + + private float CalculateNotificationHeight(LightlessNotification notification) + { + var height = 40f; + if (!string.IsNullOrEmpty(notification.Message)) + { + var textSize = ImGui.CalcTextSize(notification.Message, true, NotificationWidth - 35f); + height += textSize.Y + 4f; + } + + if (notification.ShowProgress) + { + height += 12f; + } + + if (notification.Actions.Count > 0) + { + height += 28f; + } + + return Math.Max(height, NotificationMinHeight); + } + + private Vector4 GetNotificationAccentColor(NotificationType type) + { + return type switch + { + NotificationType.Info => UIColors.Get("LightlessPurple"), + NotificationType.Warning => UIColors.Get("LightlessYellow"), + NotificationType.Error => UIColors.Get("DimRed"), + _ => UIColors.Get("LightlessPurple") + }; + } +} \ No newline at end of file diff --git a/LightlessSync/UI/Models/LightlessNotification.cs b/LightlessSync/UI/Models/LightlessNotification.cs new file mode 100644 index 0000000..c9000da --- /dev/null +++ b/LightlessSync/UI/Models/LightlessNotification.cs @@ -0,0 +1,34 @@ +using Dalamud.Interface; +using LightlessSync.LightlessConfiguration.Models; +using System.Numerics; +namespace LightlessSync.UI.Models; +public class LightlessNotification +{ + public string Id { get; set; } = Guid.NewGuid().ToString(); + public string Title { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + public NotificationType Type { get; set; } = NotificationType.Info; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(5); + public bool IsExpired => DateTime.UtcNow - CreatedAt > Duration; + public bool IsDismissed { get; set; } = false; + public List Actions { get; set; } = new(); + public bool ShowProgress { get; set; } = false; + public float Progress { get; set; } = 0f; + public bool IsMinimized { get; set; } = false; + + // Animation properties + public float AnimationProgress { get; set; } = 0f; + public bool IsAnimatingIn { get; set; } = true; + public bool IsAnimatingOut { get; set; } = false; +} +public class LightlessNotificationAction +{ + public string Id { get; set; } = Guid.NewGuid().ToString(); + public string Label { get; set; } = string.Empty; + public FontAwesomeIcon Icon { get; set; } = FontAwesomeIcon.None; + public Vector4 Color { get; set; } = Vector4.One; + public Action OnClick { get; set; } = _ => { }; + public bool IsPrimary { get; set; } = false; + public bool IsDestructive { get; set; } = false; +} \ No newline at end of file diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 0a592f0..c54e912 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.Colors; @@ -351,6 +351,14 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.UnderlinedBigText("Transfer UI", UIColors.Get("LightlessBlue")); ImGuiHelpers.ScaledDummy(5); + bool useNotificationsForDownloads = _configService.Current.UseNotificationsForDownloads; + if (ImGui.Checkbox("Use notifications for download progress", ref useNotificationsForDownloads)) + { + _configService.Current.UseNotificationsForDownloads = useNotificationsForDownloads; + _configService.Save(); + } + _uiShared.DrawHelpText("Show download progress as clean notifications instead of overlay text. Notifications update in real-time."); + bool showTransferWindow = _configService.Current.ShowTransferWindow; if (ImGui.Checkbox("Show separate transfer window", ref showTransferWindow)) { diff --git a/LightlessSync/UI/TopTabMenu.cs b/LightlessSync/UI/TopTabMenu.cs index 8a9e6c9..52b15a6 100644 --- a/LightlessSync/UI/TopTabMenu.cs +++ b/LightlessSync/UI/TopTabMenu.cs @@ -33,13 +33,14 @@ public class TopTabMenu private bool _pairRequestsExpanded; // useless for now private int _lastRequestCount; private readonly UiSharedService _uiSharedService; + private readonly LightlessNotificationService _lightlessNotificationService; private string _filter = string.Empty; private int _globalControlCountdown = 0; private float _pairRequestsHeight = 150f; private string _pairToAdd = string.Empty; private SelectedTab _selectedTab = SelectedTab.None; - public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService) + public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService, LightlessNotificationService lightlessNotificationService) { _lightlessMediator = lightlessMediator; _apiController = apiController; @@ -47,6 +48,7 @@ public class TopTabMenu _pairRequestService = pairRequestService; _dalamudUtilService = dalamudUtilService; _uiSharedService = uiSharedService; + _lightlessNotificationService = lightlessNotificationService; } private enum SelectedTab @@ -199,15 +201,79 @@ public class TopTabMenu if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f); #if DEBUG - if (ImGui.Button("Add Test Pair Request")) + if (ImGui.Button("Test Pair Request")) { - var fakeCid = Guid.NewGuid().ToString("N"); - var display = _pairRequestService.RegisterIncomingRequest(fakeCid, "Debug pair request"); - _lightlessMediator.Publish(new NotificationMessage( - "Pair request received (debug)", - display.Message, + _lightlessNotificationService.ShowPairRequestNotification( + "Debug User", + "debug-user-id", + onAccept: () => + { + _lightlessNotificationService.ShowNotification( + "Pair Accepted", + "Debug pair request was accepted!", + NotificationType.Info, + TimeSpan.FromSeconds(3)); + }, + onDecline: () => + { + _lightlessNotificationService.ShowNotification( + "Pair Declined", + "Debug pair request was declined.", + NotificationType.Warning, + TimeSpan.FromSeconds(3)); + } + ); + } + + ImGui.SameLine(); + if (ImGui.Button("Test Info")) + { + _lightlessNotificationService.ShowNotification( + "Information", + "This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps. This is a test ifno notification with some longer text to see how it wraps.", NotificationType.Info, - TimeSpan.FromSeconds(5))); + TimeSpan.FromSeconds(5)); + } + + ImGui.SameLine(); + if (ImGui.Button("Test Warning")) + { + _lightlessNotificationService.ShowNotification( + "Warning", + "This is a test warning notification.", + NotificationType.Warning, + TimeSpan.FromSeconds(7)); + } + + ImGui.SameLine(); + if (ImGui.Button("Test Error")) + { + _lightlessNotificationService.ShowNotification( + "Error", + "This is a test error notification erp police", + NotificationType.Error, + TimeSpan.FromSeconds(10)); + } + + if (ImGui.Button("Test Download Progress")) + { + var downloadStatus = new List<(string playerName, float progress, string status)> + { + ("Mauwmauw Nekochan", 0.85f, "downloading"), + ("Raelynn Kitsune", 0.34f, "downloading"), + ("Jaina Elraeth", 0.67f, "downloading"), + ("Vaelstra Bloodthorn", 0.19f, "downloading"), + ("Lydia Hera Moondrop", 0.86f, "downloading"), + ("C'liina Star", 1.0f, "completed") + }; + + _lightlessNotificationService.ShowPairDownloadNotification(downloadStatus); + } + + ImGui.SameLine(); + if (ImGui.Button("Dismiss Download")) + { + _lightlessNotificationService.DismissPairDownloadNotification(); } #endif @@ -850,4 +916,4 @@ public class TopTabMenu ImGui.EndPopup(); } } -} +} \ No newline at end of file diff --git a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs index 263c87a..7a27fe0 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs @@ -6,6 +6,7 @@ using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.User; using LightlessSync.LightlessConfiguration.Models; using LightlessSync.Services.Mediator; +using LightlessSync.Utils; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Logging; @@ -104,7 +105,6 @@ public partial class ApiController return Task.CompletedTask; } - public Task Client_ReceiveBroadcastPairRequest(UserPairNotificationDto dto) { if (dto == null) @@ -112,17 +112,55 @@ public partial class ApiController var request = _pairRequestService.RegisterIncomingRequest(dto.myHashedCid, dto.message ?? string.Empty); - Mediator.Publish(new NotificationMessage( - "Pair request received", - request.Message, - NotificationType.Info, - TimeSpan.FromSeconds(5))); + // Use the new interactive notification system for pair requests + var senderName = string.IsNullOrEmpty(request.DisplayName) ? "Unknown User" : request.DisplayName; + _lightlessNotificationService.ShowPairRequestNotification( + senderName, + request.HashedCid, + onAccept: () => + { + // Fire and forget async operation + _ = Task.Run(async () => + { + try + { + var myCidHash = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256(); + await TryPairWithContentId(request.HashedCid, myCidHash).ConfigureAwait(false); + _pairRequestService.RemoveRequest(request.HashedCid); + + _lightlessNotificationService.ShowNotification( + "Pair Request Accepted", + $"Sent a pair request back to {senderName}.", + NotificationType.Info, + TimeSpan.FromSeconds(3)); + } + catch (Exception ex) + { + _lightlessNotificationService.ShowNotification( + "Failed to Accept Pair Request", + ex.Message, + NotificationType.Error, + TimeSpan.FromSeconds(5)); + } + }); + }, + onDecline: () => + { + _pairRequestService.RemoveRequest(request.HashedCid); + _lightlessNotificationService.ShowNotification( + "Pair Request Declined", + $"Declined {senderName}'s pair request.", + NotificationType.Info, + TimeSpan.FromSeconds(3)); + }); return Task.CompletedTask; } + public Task Client_UpdateSystemInfo(SystemInfoDto systemInfo) { SystemInfoDto = systemInfo; + //Mediator.Publish(new UpdateSystemInfoMessage(systemInfo)); return Task.CompletedTask; } diff --git a/LightlessSync/WebAPI/SignalR/ApiController.cs b/LightlessSync/WebAPI/SignalR/ApiController.cs index 90be67f..fb21d2a 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.cs @@ -32,6 +32,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL private readonly ServerConfigurationManager _serverManager; private readonly TokenProvider _tokenProvider; private readonly LightlessConfigService _lightlessConfigService; + private readonly LightlessNotificationService _lightlessNotificationService; private CancellationTokenSource _connectionCancellationTokenSource; private ConnectionDto? _connectionDto; private bool _doNotNotifyOnNextInfo = false; @@ -44,7 +45,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL public ApiController(ILogger logger, HubFactory hubFactory, DalamudUtilService dalamudUtil, PairManager pairManager, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator, - TokenProvider tokenProvider, LightlessConfigService lightlessConfigService) : base(logger, mediator) + TokenProvider tokenProvider, LightlessConfigService lightlessConfigService, LightlessNotificationService lightlessNotificationService) : base(logger, mediator) { _hubFactory = hubFactory; _dalamudUtil = dalamudUtil; @@ -53,6 +54,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL _serverManager = serverManager; _tokenProvider = tokenProvider; _lightlessConfigService = lightlessConfigService; + _lightlessNotificationService = lightlessNotificationService; _connectionCancellationTokenSource = new CancellationTokenSource(); Mediator.Subscribe(this, (_) => DalamudUtilOnLogIn());