added notification system for file downloads and pair requests

This commit is contained in:
choco
2025-10-06 16:14:34 +02:00
parent ca70c622bc
commit 090b81c989
13 changed files with 960 additions and 46 deletions

View File

@@ -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;

View File

@@ -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<GameObjectHandler, bool> _uploadingPlayers = new();
private readonly LightlessNotificationService _notificationService;
private bool _notificationDismissed = true;
public DownloadUi(ILogger<DownloadUi> 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<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) =>
{
_currentDownloads.TryRemove(msg.DownloadId, out _);
if (!_currentDownloads.Any())
{
_notificationService.DismissPairDownloadNotification();
}
});
Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen = false);
Mediator.Subscribe<GposeEndMessage>(this, (_) => IsOpen = true);
Mediator.Subscribe<PlayerUploadingMessage>(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);
}
}
}

View File

@@ -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<LightlessNotification> _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<LightlessNotificationUI> 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<LightlessNotificationMessage>(this, HandleNotificationMessage);
Mediator.Subscribe<LightlessNotificationDismissMessage>(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")
};
}
}

View File

@@ -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<LightlessNotificationAction> 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<LightlessNotification> OnClick { get; set; } = _ => { };
public bool IsPrimary { get; set; } = false;
public bool IsDestructive { get; set; } = false;
}

View File

@@ -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))
{

View File

@@ -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();
}
}
}
}