Compare commits
12 Commits
debug-clie
...
notificati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9030549a7 | ||
|
|
10336a829a | ||
| 77ff8ae372 | |||
|
|
cf27a67296 | ||
|
|
d6a4595bb8 | ||
|
|
f202818b55 | ||
|
|
3f2e4d6640 | ||
|
|
90b483e4ea | ||
|
|
bcb524df52 | ||
| b64bb66119 | |||
|
|
118edb9dea | ||
|
|
6c0d00dc39 |
@@ -86,6 +86,7 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public NotificationLocation LightlessErrorNotification { get; set; } = NotificationLocation.ChatAndLightlessUi;
|
public NotificationLocation LightlessErrorNotification { get; set; } = NotificationLocation.ChatAndLightlessUi;
|
||||||
public NotificationLocation LightlessPairRequestNotification { get; set; } = NotificationLocation.LightlessUi;
|
public NotificationLocation LightlessPairRequestNotification { get; set; } = NotificationLocation.LightlessUi;
|
||||||
public NotificationLocation LightlessDownloadNotification { get; set; } = NotificationLocation.TextOverlay;
|
public NotificationLocation LightlessDownloadNotification { get; set; } = NotificationLocation.TextOverlay;
|
||||||
|
public NotificationLocation LightlessPerformanceNotification { get; set; } = NotificationLocation.LightlessUi;
|
||||||
|
|
||||||
// Basic Settings
|
// Basic Settings
|
||||||
public float NotificationOpacity { get; set; } = 0.95f;
|
public float NotificationOpacity { get; set; } = 0.95f;
|
||||||
@@ -112,16 +113,19 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public int ErrorNotificationDurationSeconds { get; set; } = 20;
|
public int ErrorNotificationDurationSeconds { get; set; } = 20;
|
||||||
public int PairRequestDurationSeconds { get; set; } = 180;
|
public int PairRequestDurationSeconds { get; set; } = 180;
|
||||||
public int DownloadNotificationDurationSeconds { get; set; } = 300;
|
public int DownloadNotificationDurationSeconds { get; set; } = 300;
|
||||||
|
public int PerformanceNotificationDurationSeconds { get; set; } = 20;
|
||||||
public uint CustomInfoSoundId { get; set; } = 2; // Se2
|
public uint CustomInfoSoundId { get; set; } = 2; // Se2
|
||||||
public uint CustomWarningSoundId { get; set; } = 16; // Se15
|
public uint CustomWarningSoundId { get; set; } = 16; // Se15
|
||||||
public uint CustomErrorSoundId { get; set; } = 16; // Se15
|
public uint CustomErrorSoundId { get; set; } = 16; // Se15
|
||||||
public uint PairRequestSoundId { get; set; } = 5; // Se5
|
public uint PairRequestSoundId { get; set; } = 5; // Se5
|
||||||
public uint DownloadSoundId { get; set; } = 15; // Se14
|
public uint PerformanceSoundId { get; set; } = 16; // Se15
|
||||||
public bool DisableInfoSound { get; set; } = true;
|
public bool DisableInfoSound { get; set; } = true;
|
||||||
public bool DisableWarningSound { get; set; } = true;
|
public bool DisableWarningSound { get; set; } = true;
|
||||||
public bool DisableErrorSound { get; set; } = true;
|
public bool DisableErrorSound { get; set; } = true;
|
||||||
public bool DisablePairRequestSound { get; set; } = true;
|
public bool DisablePairRequestSound { get; set; } = true;
|
||||||
public bool DisableDownloadSound { get; set; } = true;
|
public bool DisablePerformanceSound { get; set; } = true;
|
||||||
|
public bool ShowPerformanceNotificationActions { get; set; } = true;
|
||||||
|
public bool ShowPairRequestNotificationActions { get; set; } = true;
|
||||||
public bool UseFocusTarget { get; set; } = false;
|
public bool UseFocusTarget { get; set; } = false;
|
||||||
public bool overrideFriendColor { get; set; } = false;
|
public bool overrideFriendColor { get; set; } = false;
|
||||||
public bool overridePartyColor { get; set; } = false;
|
public bool overridePartyColor { get; set; } = false;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ public enum NotificationType
|
|||||||
Warning,
|
Warning,
|
||||||
Error,
|
Error,
|
||||||
PairRequest,
|
PairRequest,
|
||||||
Download
|
Download,
|
||||||
|
Performance
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NotificationCorner
|
public enum NotificationCorner
|
||||||
|
|||||||
@@ -190,7 +190,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
notificationManager,
|
notificationManager,
|
||||||
chatGui,
|
chatGui,
|
||||||
s.GetRequiredService<LightlessMediator>(),
|
s.GetRequiredService<LightlessMediator>(),
|
||||||
s.GetRequiredService<PairRequestService>()));
|
s.GetRequiredService<PairRequestService>(),
|
||||||
|
s.GetRequiredService<BroadcastService>()));
|
||||||
collection.AddSingleton((s) =>
|
collection.AddSingleton((s) =>
|
||||||
{
|
{
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using LightlessSync.API.Dto.Group;
|
using LightlessSync.API.Dto.Group;
|
||||||
using LightlessSync.API.Dto.User;
|
using LightlessSync.API.Dto.User;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
@@ -394,6 +394,10 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
if (!IsLightFinderAvailable)
|
if (!IsLightFinderAvailable)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("ToggleBroadcast - Lightfinder is not available.");
|
_logger.LogWarning("ToggleBroadcast - Lightfinder is not available.");
|
||||||
|
_mediator.Publish(new NotificationMessage(
|
||||||
|
"Broadcast Unavailable",
|
||||||
|
"Lightfinder is not available on this server.",
|
||||||
|
LightlessConfiguration.Models.NotificationType.Error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,6 +407,10 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
if (!_config.Current.BroadcastEnabled && cooldown is { } cd && cd > TimeSpan.Zero)
|
if (!_config.Current.BroadcastEnabled && cooldown is { } cd && cd > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Cooldown active. Must wait {Remaining}s before re-enabling.", cd.TotalSeconds);
|
_logger.LogWarning("Cooldown active. Must wait {Remaining}s before re-enabling.", cd.TotalSeconds);
|
||||||
|
_mediator.Publish(new NotificationMessage(
|
||||||
|
"Broadcast Cooldown",
|
||||||
|
$"Please wait {cd.TotalSeconds:F0} seconds before re-enabling broadcast.",
|
||||||
|
LightlessConfiguration.Models.NotificationType.Warning));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,10 +435,19 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_logger.LogDebug("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);
|
_logger.LogDebug("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);
|
||||||
|
|
||||||
_mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
|
_mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
|
||||||
|
|
||||||
|
_mediator.Publish(new NotificationMessage(
|
||||||
|
newStatus ? "Broadcast Enabled" : "Broadcast Disabled",
|
||||||
|
newStatus ? "Your Lightfinder broadcast has been enabled." : "Your Lightfinder broadcast has been disabled.",
|
||||||
|
LightlessConfiguration.Models.NotificationType.Info));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to determine current broadcast status for toggle");
|
_logger.LogError(ex, "Failed to determine current broadcast status for toggle");
|
||||||
|
_mediator.Publish(new NotificationMessage(
|
||||||
|
"Broadcast Failed",
|
||||||
|
$"Failed to toggle broadcast: {ex.Message}",
|
||||||
|
LightlessConfiguration.Models.NotificationType.Error));
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -493,6 +510,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally.");
|
_logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally.");
|
||||||
ApplyBroadcastDisabled(forcePublish: true);
|
ApplyBroadcastDisabled(forcePublish: true);
|
||||||
|
_mediator.Publish(new BroadcastExpiredMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ public record TransientResourceChangedMessage(IntPtr Address) : MessageBase;
|
|||||||
public record HaltScanMessage(string Source) : MessageBase;
|
public record HaltScanMessage(string Source) : MessageBase;
|
||||||
public record NotificationMessage
|
public record NotificationMessage
|
||||||
(string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase;
|
(string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase;
|
||||||
|
public record PerformanceNotificationMessage
|
||||||
|
(string Title, string Message, UserData UserData, bool IsPaused, string PlayerName) : MessageBase;
|
||||||
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
|
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
|
||||||
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
|
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
|
||||||
public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage;
|
public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage;
|
||||||
@@ -106,6 +108,8 @@ public record EnableBroadcastMessage(string HashedCid, bool Enabled) : MessageBa
|
|||||||
public record BroadcastStatusChangedMessage(bool Enabled, TimeSpan? Ttl) : MessageBase;
|
public record BroadcastStatusChangedMessage(bool Enabled, TimeSpan? Ttl) : MessageBase;
|
||||||
public record SyncshellBroadcastsUpdatedMessage : MessageBase;
|
public record SyncshellBroadcastsUpdatedMessage : MessageBase;
|
||||||
public record PairRequestsUpdatedMessage : MessageBase;
|
public record PairRequestsUpdatedMessage : MessageBase;
|
||||||
|
public record PairRequestReceivedMessage(string SenderName, string SenderId) : MessageBase;
|
||||||
|
public record BroadcastExpiredMessage : MessageBase;
|
||||||
public record VisibilityChange : MessageBase;
|
public record VisibilityChange : MessageBase;
|
||||||
#pragma warning restore S2094
|
#pragma warning restore S2094
|
||||||
#pragma warning restore MA0048 // File name must match type name
|
#pragma warning restore MA0048 // File name must match type name
|
||||||
@@ -10,9 +10,11 @@ using LightlessSync.UI.Models;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using LightlessSync.API.Data;
|
||||||
using NotificationType = LightlessSync.LightlessConfiguration.Models.NotificationType;
|
using NotificationType = LightlessSync.LightlessConfiguration.Models.NotificationType;
|
||||||
|
|
||||||
namespace LightlessSync.Services;
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
public class NotificationService : DisposableMediatorSubscriberBase, IHostedService
|
public class NotificationService : DisposableMediatorSubscriberBase, IHostedService
|
||||||
{
|
{
|
||||||
private readonly ILogger<NotificationService> _logger;
|
private readonly ILogger<NotificationService> _logger;
|
||||||
@@ -21,6 +23,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
private readonly INotificationManager _notificationManager;
|
private readonly INotificationManager _notificationManager;
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
private readonly PairRequestService _pairRequestService;
|
private readonly PairRequestService _pairRequestService;
|
||||||
|
private readonly BroadcastService _broadcastService;
|
||||||
private readonly HashSet<string> _shownPairRequestNotifications = new();
|
private readonly HashSet<string> _shownPairRequestNotifications = new();
|
||||||
|
|
||||||
public NotificationService(
|
public NotificationService(
|
||||||
@@ -30,7 +33,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
INotificationManager notificationManager,
|
INotificationManager notificationManager,
|
||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
LightlessMediator mediator,
|
LightlessMediator mediator,
|
||||||
PairRequestService pairRequestService) : base(logger, mediator)
|
PairRequestService pairRequestService,
|
||||||
|
BroadcastService broadcastService) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
@@ -38,12 +42,16 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
_notificationManager = notificationManager;
|
_notificationManager = notificationManager;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
_pairRequestService = pairRequestService;
|
_pairRequestService = pairRequestService;
|
||||||
|
_broadcastService = broadcastService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage);
|
Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage);
|
||||||
Mediator.Subscribe<PairRequestsUpdatedMessage>(this, HandlePairRequestsUpdated);
|
Mediator.Subscribe<PairRequestsUpdatedMessage>(this, HandlePairRequestsUpdated);
|
||||||
|
Mediator.Subscribe<PairRequestReceivedMessage>(this, HandlePairRequestReceived);
|
||||||
|
Mediator.Subscribe<PerformanceNotificationMessage>(this, HandlePerformanceNotification);
|
||||||
|
Mediator.Subscribe<BroadcastExpiredMessage>(this, HandleBroadcastExpired);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,23 +115,42 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
|
|
||||||
public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline)
|
public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline)
|
||||||
{
|
{
|
||||||
var notification = new LightlessNotification
|
var location = GetNotificationLocation(NotificationType.PairRequest);
|
||||||
|
|
||||||
|
// Show in chat if configured
|
||||||
|
if (location == NotificationLocation.Chat || location == NotificationLocation.ChatAndLightlessUi)
|
||||||
{
|
{
|
||||||
Id = $"pair_request_{senderId}",
|
ShowChat(new NotificationMessage("Pair Request Received", $"{senderName} wants to directly pair with you.", NotificationType.PairRequest));
|
||||||
Title = "Pair Request Received",
|
|
||||||
Message = $"{senderName} wants to directly pair with you.",
|
|
||||||
Type = NotificationType.PairRequest,
|
|
||||||
Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
|
|
||||||
SoundEffectId = GetPairRequestSoundId(),
|
|
||||||
Actions = CreatePairRequestActions(onAccept, onDecline)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (notification.SoundEffectId.HasValue)
|
|
||||||
{
|
|
||||||
PlayNotificationSound(notification.SoundEffectId.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show Lightless notification if configured and action buttons are enabled
|
||||||
|
if ((location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi)
|
||||||
|
&& _configService.Current.UseLightlessNotifications
|
||||||
|
&& _configService.Current.ShowPairRequestNotificationActions)
|
||||||
|
{
|
||||||
|
var notification = new LightlessNotification
|
||||||
|
{
|
||||||
|
Id = $"pair_request_{senderId}",
|
||||||
|
Title = "Pair Request Received",
|
||||||
|
Message = $"{senderName} wants to directly pair with you.",
|
||||||
|
Type = NotificationType.PairRequest,
|
||||||
|
Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
|
||||||
|
SoundEffectId = GetPairRequestSoundId(),
|
||||||
|
Actions = CreatePairRequestActions(onAccept, onDecline)
|
||||||
|
};
|
||||||
|
|
||||||
Mediator.Publish(new LightlessNotificationMessage(notification));
|
if (notification.SoundEffectId.HasValue)
|
||||||
|
{
|
||||||
|
PlayNotificationSound(notification.SoundEffectId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mediator.Publish(new LightlessNotificationMessage(notification));
|
||||||
|
}
|
||||||
|
else if (location != NotificationLocation.Nowhere && location != NotificationLocation.Chat)
|
||||||
|
{
|
||||||
|
// Fall back to regular notification without action buttons
|
||||||
|
HandleNotificationMessage(new NotificationMessage("Pair Request Received", $"{senderName} wants to directly pair with you.", NotificationType.PairRequest));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint? GetPairRequestSoundId() =>
|
private uint? GetPairRequestSoundId() =>
|
||||||
@@ -356,6 +383,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds),
|
NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds),
|
||||||
NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
|
NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
|
||||||
NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
|
NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
|
||||||
|
NotificationType.Performance => TimeSpan.FromSeconds(_configService.Current.PerformanceNotificationDurationSeconds),
|
||||||
_ => TimeSpan.FromSeconds(10)
|
_ => TimeSpan.FromSeconds(10)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -371,7 +399,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
NotificationType.Info => _configService.Current.DisableInfoSound,
|
NotificationType.Info => _configService.Current.DisableInfoSound,
|
||||||
NotificationType.Warning => _configService.Current.DisableWarningSound,
|
NotificationType.Warning => _configService.Current.DisableWarningSound,
|
||||||
NotificationType.Error => _configService.Current.DisableErrorSound,
|
NotificationType.Error => _configService.Current.DisableErrorSound,
|
||||||
NotificationType.Download => _configService.Current.DisableDownloadSound,
|
NotificationType.Performance => _configService.Current.DisablePerformanceSound,
|
||||||
|
NotificationType.Download => true, // Download sounds always disabled
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -380,7 +409,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
NotificationType.Info => _configService.Current.CustomInfoSoundId,
|
NotificationType.Info => _configService.Current.CustomInfoSoundId,
|
||||||
NotificationType.Warning => _configService.Current.CustomWarningSoundId,
|
NotificationType.Warning => _configService.Current.CustomWarningSoundId,
|
||||||
NotificationType.Error => _configService.Current.CustomErrorSoundId,
|
NotificationType.Error => _configService.Current.CustomErrorSoundId,
|
||||||
NotificationType.Download => _configService.Current.DownloadSoundId,
|
NotificationType.Performance => _configService.Current.PerformanceSoundId,
|
||||||
_ => NotificationSounds.GetDefaultSound(type)
|
_ => NotificationSounds.GetDefaultSound(type)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -418,6 +447,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
NotificationType.Error => _configService.Current.LightlessErrorNotification,
|
NotificationType.Error => _configService.Current.LightlessErrorNotification,
|
||||||
NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification,
|
NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification,
|
||||||
NotificationType.Download => _configService.Current.LightlessDownloadNotification,
|
NotificationType.Download => _configService.Current.LightlessDownloadNotification,
|
||||||
|
NotificationType.Performance => _configService.Current.LightlessPerformanceNotification,
|
||||||
_ => NotificationLocation.LightlessUi
|
_ => NotificationLocation.LightlessUi
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -505,6 +535,18 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
case NotificationType.Error:
|
case NotificationType.Error:
|
||||||
PrintErrorChat(msg.Message);
|
PrintErrorChat(msg.Message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case NotificationType.PairRequest:
|
||||||
|
PrintPairRequestChat(msg.Title, msg.Message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotificationType.Performance:
|
||||||
|
PrintPerformanceChat(msg.Title, msg.Message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Download notifications don't support chat output, will be a giga spam otherwise
|
||||||
|
case NotificationType.Download:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +570,32 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
_chatGui.Print(se.BuiltString);
|
_chatGui.Print(se.BuiltString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PrintPairRequestChat(string? title, string? message)
|
||||||
|
{
|
||||||
|
SeStringBuilder se = new SeStringBuilder().AddText("[Lightless Sync] ")
|
||||||
|
.AddUiForeground("Pair Request: ", 541).AddUiForegroundOff()
|
||||||
|
.AddText(title ?? message ?? string.Empty);
|
||||||
|
_chatGui.Print(se.BuiltString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintPerformanceChat(string? title, string? message)
|
||||||
|
{
|
||||||
|
SeStringBuilder se = new SeStringBuilder().AddText("[Lightless Sync] ")
|
||||||
|
.AddUiForeground("Performance: ", 508).AddUiForegroundOff()
|
||||||
|
.AddText(title ?? message ?? string.Empty);
|
||||||
|
_chatGui.Print(se.BuiltString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePairRequestReceived(PairRequestReceivedMessage msg)
|
||||||
|
{
|
||||||
|
ShowPairRequestNotification(
|
||||||
|
msg.SenderName,
|
||||||
|
msg.SenderId,
|
||||||
|
() => _pairRequestService.AcceptPairRequest(msg.SenderId, msg.SenderName),
|
||||||
|
() => _pairRequestService.DeclinePairRequest(msg.SenderId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void HandlePairRequestsUpdated(PairRequestsUpdatedMessage _)
|
private void HandlePairRequestsUpdated(PairRequestsUpdatedMessage _)
|
||||||
{
|
{
|
||||||
var activeRequests = _pairRequestService.GetActiveRequests();
|
var activeRequests = _pairRequestService.GetActiveRequests();
|
||||||
@@ -545,17 +613,219 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
_shownPairRequestNotifications.Remove(hashedCid);
|
_shownPairRequestNotifications.Remove(hashedCid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/update notifications for all active requests
|
// Track active requests
|
||||||
foreach (var request in activeRequests)
|
foreach (var request in activeRequests)
|
||||||
{
|
{
|
||||||
_shownPairRequestNotifications.Add(request.HashedCid);
|
_shownPairRequestNotifications.Add(request.HashedCid);
|
||||||
ShowPairRequestNotification(
|
|
||||||
request.DisplayName,
|
|
||||||
request.HashedCid,
|
|
||||||
() => _pairRequestService.AcceptPairRequest(request.HashedCid, request.DisplayName),
|
|
||||||
() => _pairRequestService.DeclinePairRequest(request.HashedCid)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void HandlePerformanceNotification(PerformanceNotificationMessage msg)
|
||||||
|
{
|
||||||
|
var location = GetNotificationLocation(NotificationType.Performance);
|
||||||
|
|
||||||
|
// Show in chat if configured
|
||||||
|
if (location == NotificationLocation.Chat || location == NotificationLocation.ChatAndLightlessUi)
|
||||||
|
{
|
||||||
|
ShowChat(new NotificationMessage(msg.Title, msg.Message, NotificationType.Performance));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Lightless notification if configured and action buttons are enabled
|
||||||
|
if ((location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi)
|
||||||
|
&& _configService.Current.UseLightlessNotifications
|
||||||
|
&& _configService.Current.ShowPerformanceNotificationActions)
|
||||||
|
{
|
||||||
|
var actions = CreatePerformanceActions(msg.UserData, msg.IsPaused, msg.PlayerName);
|
||||||
|
var notification = new LightlessNotification
|
||||||
|
{
|
||||||
|
Title = msg.Title,
|
||||||
|
Message = msg.Message,
|
||||||
|
Type = NotificationType.Performance,
|
||||||
|
Duration = TimeSpan.FromSeconds(_configService.Current.PerformanceNotificationDurationSeconds),
|
||||||
|
Actions = actions,
|
||||||
|
SoundEffectId = GetSoundEffectId(NotificationType.Performance, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.SoundEffectId.HasValue)
|
||||||
|
{
|
||||||
|
PlayNotificationSound(notification.SoundEffectId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mediator.Publish(new LightlessNotificationMessage(notification));
|
||||||
|
}
|
||||||
|
else if (location != NotificationLocation.Nowhere && location != NotificationLocation.Chat)
|
||||||
|
{
|
||||||
|
// Fall back to regular notification without action buttons
|
||||||
|
HandleNotificationMessage(new NotificationMessage(msg.Title, msg.Message, NotificationType.Performance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LightlessNotificationAction> CreatePerformanceActions(UserData userData, bool isPaused, string playerName)
|
||||||
|
{
|
||||||
|
var actions = new List<LightlessNotificationAction>();
|
||||||
|
|
||||||
|
if (isPaused)
|
||||||
|
{
|
||||||
|
actions.Add(new LightlessNotificationAction
|
||||||
|
{
|
||||||
|
Label = "Unpause",
|
||||||
|
Icon = FontAwesomeIcon.Play,
|
||||||
|
Color = UIColors.Get("LightlessGreen"),
|
||||||
|
IsPrimary = true,
|
||||||
|
OnClick = (notification) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Mediator.Publish(new CyclePauseMessage(userData));
|
||||||
|
DismissNotification(notification);
|
||||||
|
|
||||||
|
var displayName = GetUserDisplayName(userData, playerName);
|
||||||
|
ShowNotification(
|
||||||
|
"Player Unpaused",
|
||||||
|
$"Successfully unpaused {displayName}",
|
||||||
|
NotificationType.Info,
|
||||||
|
TimeSpan.FromSeconds(3));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to unpause player {uid}", userData.UID);
|
||||||
|
var displayName = GetUserDisplayName(userData, playerName);
|
||||||
|
ShowNotification(
|
||||||
|
"Unpause Failed",
|
||||||
|
$"Failed to unpause {displayName}",
|
||||||
|
NotificationType.Error,
|
||||||
|
TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions.Add(new LightlessNotificationAction
|
||||||
|
{
|
||||||
|
Label = "Pause",
|
||||||
|
Icon = FontAwesomeIcon.Pause,
|
||||||
|
Color = UIColors.Get("LightlessOrange"),
|
||||||
|
IsPrimary = true,
|
||||||
|
OnClick = (notification) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Mediator.Publish(new PauseMessage(userData));
|
||||||
|
DismissNotification(notification);
|
||||||
|
|
||||||
|
var displayName = GetUserDisplayName(userData, playerName);
|
||||||
|
ShowNotification(
|
||||||
|
"Player Paused",
|
||||||
|
$"Successfully paused {displayName}",
|
||||||
|
NotificationType.Info,
|
||||||
|
TimeSpan.FromSeconds(3));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to pause player {uid}", userData.UID);
|
||||||
|
var displayName = GetUserDisplayName(userData, playerName);
|
||||||
|
ShowNotification(
|
||||||
|
"Pause Failed",
|
||||||
|
$"Failed to pause {displayName}",
|
||||||
|
NotificationType.Error,
|
||||||
|
TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add dismiss button
|
||||||
|
actions.Add(new LightlessNotificationAction
|
||||||
|
{
|
||||||
|
Label = "Dismiss",
|
||||||
|
Icon = FontAwesomeIcon.Times,
|
||||||
|
Color = UIColors.Get("DimRed"),
|
||||||
|
IsPrimary = false,
|
||||||
|
OnClick = (notification) =>
|
||||||
|
{
|
||||||
|
DismissNotification(notification);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUserDisplayName(UserData userData, string playerName)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(userData.Alias) && !string.Equals(userData.Alias, userData.UID, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return $"{playerName} ({userData.Alias})";
|
||||||
|
}
|
||||||
|
return $"{playerName} ({userData.UID})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleBroadcastExpired(BroadcastExpiredMessage _)
|
||||||
|
{
|
||||||
|
var location = GetNotificationLocation(NotificationType.Warning);
|
||||||
|
|
||||||
|
if (location == NotificationLocation.Chat || location == NotificationLocation.ChatAndLightlessUi)
|
||||||
|
{
|
||||||
|
ShowChat(new NotificationMessage("Broadcast Expired", "Your Lightfinder broadcast has expired after 3 hours.", NotificationType.Warning));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((location == NotificationLocation.LightlessUi || location == NotificationLocation.ChatAndLightlessUi)
|
||||||
|
&& _configService.Current.UseLightlessNotifications)
|
||||||
|
{
|
||||||
|
var notification = new LightlessNotification
|
||||||
|
{
|
||||||
|
Id = "broadcast_expired",
|
||||||
|
Title = "Broadcast Expired",
|
||||||
|
Message = "Your Lightfinder broadcast has expired after 3 hours. Would you like to re-enable it?",
|
||||||
|
Type = NotificationType.Warning,
|
||||||
|
Duration = TimeSpan.FromSeconds(_configService.Current.WarningNotificationDurationSeconds),
|
||||||
|
SoundEffectId = GetSoundEffectId(NotificationType.Warning, null),
|
||||||
|
Actions = CreateBroadcastExpiredActions()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.SoundEffectId.HasValue)
|
||||||
|
{
|
||||||
|
PlayNotificationSound(notification.SoundEffectId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mediator.Publish(new LightlessNotificationMessage(notification));
|
||||||
|
}
|
||||||
|
else if (location != NotificationLocation.Nowhere && location != NotificationLocation.Chat)
|
||||||
|
{
|
||||||
|
HandleNotificationMessage(new NotificationMessage("Broadcast Expired", "Your Lightfinder broadcast has expired after 3 hours.", NotificationType.Warning));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LightlessNotificationAction> CreateBroadcastExpiredActions()
|
||||||
|
{
|
||||||
|
return new List<LightlessNotificationAction>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = "re_enable",
|
||||||
|
Label = "Re-enable Broadcast",
|
||||||
|
Icon = FontAwesomeIcon.Plus,
|
||||||
|
Color = UIColors.Get("LightlessGreen"),
|
||||||
|
IsPrimary = true,
|
||||||
|
OnClick = (n) =>
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Re-enabling broadcast from notification");
|
||||||
|
_broadcastService.ToggleBroadcast();
|
||||||
|
DismissNotification(n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = "close",
|
||||||
|
Label = "Close",
|
||||||
|
Icon = FontAwesomeIcon.Times,
|
||||||
|
Color = UIColors.Get("DimRed"),
|
||||||
|
OnClick = (n) =>
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Broadcast expiration notification dismissed");
|
||||||
|
DismissNotification(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,6 +70,10 @@ public sealed class PairRequestService : DisposableMediatorSubscriberBase
|
|||||||
: _dalamudUtil.RunOnFrameworkThread(() => ToDisplay(entry)).GetAwaiter().GetResult();
|
: _dalamudUtil.RunOnFrameworkThread(() => ToDisplay(entry)).GetAwaiter().GetResult();
|
||||||
|
|
||||||
Mediator.Publish(new PairRequestsUpdatedMessage());
|
Mediator.Publish(new PairRequestsUpdatedMessage());
|
||||||
|
|
||||||
|
var senderName = string.IsNullOrEmpty(display.DisplayName) ? "Unknown User" : display.DisplayName;
|
||||||
|
Mediator.Publish(new PairRequestReceivedMessage(senderName, display.HashedCid));
|
||||||
|
|
||||||
return display;
|
return display;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,23 +78,26 @@ public class PlayerPerformanceService
|
|||||||
string warningText = string.Empty;
|
string warningText = string.Empty;
|
||||||
if (exceedsTris && !exceedsVram)
|
if (exceedsTris && !exceedsVram)
|
||||||
{
|
{
|
||||||
warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured triangle warning threshold (" +
|
warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured triangle warning threshold\n" +
|
||||||
$"{triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles).";
|
$"{triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles";
|
||||||
}
|
}
|
||||||
else if (!exceedsTris)
|
else if (!exceedsTris)
|
||||||
{
|
{
|
||||||
warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured VRAM warning threshold (" +
|
warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds your configured VRAM warning threshold\n" +
|
||||||
$"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB).";
|
$"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds both VRAM warning threshold (" +
|
warningText = $"Player {pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds both VRAM warning threshold and triangle warning threshold\n" +
|
||||||
$"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB) and " +
|
$"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB and {triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles";
|
||||||
$"triangle warning threshold ({triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles).";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mediator.Publish(new NotificationMessage($"{pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds performance threshold(s)",
|
_mediator.Publish(new PerformanceNotificationMessage(
|
||||||
warningText, LightlessConfiguration.Models.NotificationType.Warning));
|
$"{pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds performance threshold(s)",
|
||||||
|
warningText,
|
||||||
|
pairHandler.Pair.UserData,
|
||||||
|
pairHandler.Pair.IsPaused,
|
||||||
|
pairHandler.Pair.PlayerName));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -138,11 +141,15 @@ public class PlayerPerformanceService
|
|||||||
if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.TrisAutoPauseThresholdThousands * 1000,
|
if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.TrisAutoPauseThresholdThousands * 1000,
|
||||||
triUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm))
|
triUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm))
|
||||||
{
|
{
|
||||||
_mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused",
|
var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold and has been automatically paused\n" +
|
||||||
$"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold (" +
|
$"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles";
|
||||||
$"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)" +
|
|
||||||
$" and has been automatically paused.",
|
_mediator.Publish(new PerformanceNotificationMessage(
|
||||||
LightlessConfiguration.Models.NotificationType.Warning));
|
$"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused",
|
||||||
|
message,
|
||||||
|
pair.UserData,
|
||||||
|
true,
|
||||||
|
pair.PlayerName));
|
||||||
|
|
||||||
_mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning,
|
_mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning,
|
||||||
$"Exceeds triangle threshold: automatically paused ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)")));
|
$"Exceeds triangle threshold: automatically paused ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)")));
|
||||||
@@ -214,11 +221,15 @@ public class PlayerPerformanceService
|
|||||||
if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.VRAMSizeAutoPauseThresholdMiB * 1024 * 1024,
|
if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.VRAMSizeAutoPauseThresholdMiB * 1024 * 1024,
|
||||||
vramUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm))
|
vramUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm))
|
||||||
{
|
{
|
||||||
_mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused",
|
var message = $"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold and has been automatically paused\n" +
|
||||||
$"Player {pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold (" +
|
$"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB";
|
||||||
$"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB)" +
|
|
||||||
$" and has been automatically paused.",
|
_mediator.Publish(new PerformanceNotificationMessage(
|
||||||
LightlessConfiguration.Models.NotificationType.Warning));
|
$"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused",
|
||||||
|
message,
|
||||||
|
pair.UserData,
|
||||||
|
true,
|
||||||
|
pair.PlayerName));
|
||||||
|
|
||||||
_mediator.Publish(new PauseMessage(pair.UserData));
|
_mediator.Publish(new PauseMessage(pair.UserData));
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
|
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
|
||||||
private readonly NotificationService _notificationService;
|
private readonly NotificationService _notificationService;
|
||||||
private bool _notificationDismissed = true;
|
private bool _notificationDismissed = true;
|
||||||
|
private int _lastDownloadStateHash = 0;
|
||||||
|
|
||||||
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService,
|
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService,
|
||||||
PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared,
|
PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared,
|
||||||
@@ -65,7 +66,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
_currentDownloads.TryRemove(msg.DownloadId, out _);
|
_currentDownloads.TryRemove(msg.DownloadId, out _);
|
||||||
if (!_currentDownloads.Any())
|
if (!_currentDownloads.Any())
|
||||||
{
|
{
|
||||||
_notificationService.DismissPairDownloadNotification();
|
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen = false);
|
Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen = false);
|
||||||
@@ -116,7 +117,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignore errors thrown from UI
|
_logger.LogDebug("Error drawing upload progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -131,17 +132,19 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
// Use notification system
|
// Use notification system
|
||||||
if (_currentDownloads.Any())
|
if (_currentDownloads.Any())
|
||||||
{
|
{
|
||||||
UpdateDownloadNotification(limiterSnapshot);
|
UpdateDownloadNotificationIfChanged(limiterSnapshot);
|
||||||
_notificationDismissed = false;
|
_notificationDismissed = false;
|
||||||
}
|
}
|
||||||
else if (!_notificationDismissed)
|
else if (!_notificationDismissed)
|
||||||
{
|
{
|
||||||
_notificationService.DismissPairDownloadNotification();
|
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
|
||||||
_notificationDismissed = true;
|
_notificationDismissed = true;
|
||||||
|
_lastDownloadStateHash = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Use text overlay
|
||||||
if (limiterSnapshot.IsEnabled)
|
if (limiterSnapshot.IsEnabled)
|
||||||
{
|
{
|
||||||
var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey;
|
var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey;
|
||||||
@@ -183,7 +186,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignore errors thrown from UI
|
_logger.LogDebug("Error drawing download progress");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +258,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignore errors thrown on UI
|
_logger.LogDebug("Error drawing upload progress");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,20 +301,34 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateDownloadNotification(PairProcessingLimiterSnapshot limiterSnapshot)
|
private void UpdateDownloadNotificationIfChanged(PairProcessingLimiterSnapshot limiterSnapshot)
|
||||||
{
|
{
|
||||||
var downloadStatus = new List<(string playerName, float progress, string status)>();
|
var downloadStatus = new List<(string playerName, float progress, string status)>(_currentDownloads.Count);
|
||||||
|
var hashCode = new HashCode();
|
||||||
|
|
||||||
foreach (var item in _currentDownloads.ToList())
|
foreach (var item in _currentDownloads)
|
||||||
{
|
{
|
||||||
var dlSlot = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForSlot);
|
var dlSlot = 0;
|
||||||
var dlQueue = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.WaitingForQueue);
|
var dlQueue = 0;
|
||||||
var dlProg = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Downloading);
|
var dlProg = 0;
|
||||||
var dlDecomp = item.Value.Count(c => c.Value.DownloadStatus == DownloadStatus.Decompressing);
|
var dlDecomp = 0;
|
||||||
var totalFiles = item.Value.Sum(c => c.Value.TotalFiles);
|
long totalBytes = 0;
|
||||||
var transferredFiles = item.Value.Sum(c => c.Value.TransferredFiles);
|
long transferredBytes = 0;
|
||||||
var totalBytes = item.Value.Sum(c => c.Value.TotalBytes);
|
|
||||||
var transferredBytes = item.Value.Sum(c => c.Value.TransferredBytes);
|
// Single pass through the dictionary to count everything - avoid multiple LINQ iterations
|
||||||
|
foreach (var entry in item.Value)
|
||||||
|
{
|
||||||
|
var fileStatus = entry.Value;
|
||||||
|
switch (fileStatus.DownloadStatus)
|
||||||
|
{
|
||||||
|
case DownloadStatus.WaitingForSlot: dlSlot++; break;
|
||||||
|
case DownloadStatus.WaitingForQueue: dlQueue++; break;
|
||||||
|
case DownloadStatus.Downloading: dlProg++; break;
|
||||||
|
case DownloadStatus.Decompressing: dlDecomp++; break;
|
||||||
|
}
|
||||||
|
totalBytes += fileStatus.TotalBytes;
|
||||||
|
transferredBytes += fileStatus.TransferredBytes;
|
||||||
|
}
|
||||||
|
|
||||||
var progress = totalBytes > 0 ? (float)transferredBytes / totalBytes : 0f;
|
var progress = totalBytes > 0 ? (float)transferredBytes / totalBytes : 0f;
|
||||||
|
|
||||||
@@ -323,13 +340,27 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
else status = "completed";
|
else status = "completed";
|
||||||
|
|
||||||
downloadStatus.Add((item.Key.Name, progress, status));
|
downloadStatus.Add((item.Key.Name, progress, status));
|
||||||
|
|
||||||
|
// Build hash from meaningful state
|
||||||
|
hashCode.Add(item.Key.Name);
|
||||||
|
hashCode.Add(transferredBytes);
|
||||||
|
hashCode.Add(totalBytes);
|
||||||
|
hashCode.Add(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass queue waiting count separately, show notification if there are downloads or queue items
|
|
||||||
var queueWaiting = limiterSnapshot.IsEnabled ? limiterSnapshot.Waiting : 0;
|
var queueWaiting = limiterSnapshot.IsEnabled ? limiterSnapshot.Waiting : 0;
|
||||||
if (downloadStatus.Any() || queueWaiting > 0)
|
hashCode.Add(queueWaiting);
|
||||||
|
|
||||||
|
var currentHash = hashCode.ToHashCode();
|
||||||
|
|
||||||
|
// Only update notification if state has actually changed
|
||||||
|
if (currentHash != _lastDownloadStateHash)
|
||||||
{
|
{
|
||||||
_notificationService.ShowPairDownloadNotification(downloadStatus, queueWaiting);
|
_lastDownloadStateHash = currentHash;
|
||||||
|
if (downloadStatus.Count > 0 || queueWaiting > 0)
|
||||||
|
{
|
||||||
|
_notificationService.ShowPairDownloadNotification(downloadStatus, queueWaiting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
|||||||
private const float WindowPaddingOffset = 6f;
|
private const float WindowPaddingOffset = 6f;
|
||||||
private const float SlideAnimationDistance = 100f;
|
private const float SlideAnimationDistance = 100f;
|
||||||
private const float OutAnimationSpeedMultiplier = 0.7f;
|
private const float OutAnimationSpeedMultiplier = 0.7f;
|
||||||
|
private const float ContentPaddingX = 10f;
|
||||||
|
private const float ContentPaddingY = 6f;
|
||||||
|
private const float TitleMessageSpacing = 4f;
|
||||||
|
private const float ActionButtonSpacing = 8f;
|
||||||
|
|
||||||
private readonly List<LightlessNotification> _notifications = new();
|
private readonly List<LightlessNotification> _notifications = new();
|
||||||
private readonly object _notificationLock = new();
|
private readonly object _notificationLock = new();
|
||||||
@@ -134,11 +138,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
var viewport = ImGui.GetMainViewport();
|
var viewport = ImGui.GetMainViewport();
|
||||||
|
|
||||||
// Set window to full viewport height
|
// Window auto-resizes based on content (AlwaysAutoResize flag)
|
||||||
var width = _configService.Current.NotificationWidth;
|
|
||||||
Size = new Vector2(width, viewport.WorkSize.Y);
|
|
||||||
SizeCondition = ImGuiCond.Always;
|
|
||||||
|
|
||||||
Position = CalculateWindowPosition(viewport);
|
Position = CalculateWindowPosition(viewport);
|
||||||
PositionCondition = ImGuiCond.Always;
|
PositionCondition = ImGuiCond.Always;
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
|||||||
ImGui.SetCursorPos(originalCursorPos + slideOffset);
|
ImGui.SetCursorPos(originalCursorPos + slideOffset);
|
||||||
|
|
||||||
var notificationHeight = CalculateNotificationHeight(notification);
|
var notificationHeight = CalculateNotificationHeight(notification);
|
||||||
var notificationWidth = _configService.Current.NotificationWidth - Math.Abs(slideOffset.X);
|
var notificationWidth = _configService.Current.NotificationWidth;
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||||
|
|
||||||
@@ -466,81 +466,112 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
private void DrawNotificationText(LightlessNotification notification, float alpha)
|
private void DrawNotificationText(LightlessNotification notification, float alpha)
|
||||||
{
|
{
|
||||||
var padding = new Vector2(10f, 6f);
|
var contentPos = new Vector2(ContentPaddingX, ContentPaddingY);
|
||||||
var contentPos = new Vector2(padding.X, padding.Y);
|
|
||||||
var windowSize = ImGui.GetWindowSize();
|
var windowSize = ImGui.GetWindowSize();
|
||||||
var contentSize = new Vector2(windowSize.X - padding.X, windowSize.Y - padding.Y * 2);
|
var contentWidth = CalculateContentWidth(windowSize.X);
|
||||||
|
|
||||||
ImGui.SetCursorPos(contentPos);
|
ImGui.SetCursorPos(contentPos);
|
||||||
|
|
||||||
var titleHeight = DrawTitle(notification, contentSize.X, alpha);
|
var titleHeight = DrawTitle(notification, contentWidth, alpha);
|
||||||
DrawMessage(notification, contentPos, contentSize.X, titleHeight, alpha);
|
DrawMessage(notification, contentPos, contentWidth, titleHeight, alpha);
|
||||||
|
|
||||||
if (notification.Actions.Count > 0)
|
if (HasActions(notification))
|
||||||
{
|
{
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetStyle().ItemSpacing.Y);
|
PositionActionsAtBottom(windowSize.Y);
|
||||||
ImGui.SetCursorPosX(contentPos.X);
|
DrawNotificationActions(notification, contentWidth, alpha);
|
||||||
DrawNotificationActions(notification, contentSize.X, alpha);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float CalculateContentWidth(float windowWidth) =>
|
||||||
|
windowWidth - (ContentPaddingX * 2);
|
||||||
|
|
||||||
|
private bool HasActions(LightlessNotification notification) =>
|
||||||
|
notification.Actions.Count > 0;
|
||||||
|
|
||||||
|
private void PositionActionsAtBottom(float windowHeight)
|
||||||
|
{
|
||||||
|
var actionHeight = ImGui.GetFrameHeight();
|
||||||
|
var bottomY = windowHeight - ContentPaddingY - actionHeight;
|
||||||
|
ImGui.SetCursorPosY(bottomY);
|
||||||
|
ImGui.SetCursorPosX(ContentPaddingX);
|
||||||
|
}
|
||||||
|
|
||||||
private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha)
|
private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha)
|
||||||
{
|
{
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 1f, 1f, alpha)))
|
var titleColor = new Vector4(1f, 1f, 1f, alpha);
|
||||||
|
var titleText = FormatTitleText(notification);
|
||||||
|
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Text, titleColor))
|
||||||
{
|
{
|
||||||
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth);
|
return DrawWrappedText(titleText, contentWidth);
|
||||||
var titleStartY = ImGui.GetCursorPosY();
|
|
||||||
|
|
||||||
var titleText = _configService.Current.ShowNotificationTimestamp
|
|
||||||
? $"[{notification.CreatedAt.ToLocalTime():HH:mm:ss}] {notification.Title}"
|
|
||||||
: notification.Title;
|
|
||||||
|
|
||||||
ImGui.TextWrapped(titleText);
|
|
||||||
var titleHeight = ImGui.GetCursorPosY() - titleStartY;
|
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
return titleHeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatTitleText(LightlessNotification notification)
|
||||||
|
{
|
||||||
|
if (!_configService.Current.ShowNotificationTimestamp)
|
||||||
|
return notification.Title;
|
||||||
|
|
||||||
|
var timestamp = notification.CreatedAt.ToLocalTime().ToString("HH:mm:ss");
|
||||||
|
return $"[{timestamp}] {notification.Title}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private float DrawWrappedText(string text, float wrapWidth)
|
||||||
|
{
|
||||||
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + wrapWidth);
|
||||||
|
var startY = ImGui.GetCursorPosY();
|
||||||
|
ImGui.TextWrapped(text);
|
||||||
|
var height = ImGui.GetCursorPosY() - startY;
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawMessage(LightlessNotification notification, Vector2 contentPos, float contentWidth, float titleHeight, float alpha)
|
private void DrawMessage(LightlessNotification notification, Vector2 contentPos, float contentWidth, float titleHeight, float alpha)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(notification.Message)) return;
|
if (string.IsNullOrEmpty(notification.Message)) return;
|
||||||
|
|
||||||
ImGui.SetCursorPos(contentPos + new Vector2(0f, titleHeight + 4f));
|
var messagePos = contentPos + new Vector2(0f, titleHeight + TitleMessageSpacing);
|
||||||
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + contentWidth);
|
var messageColor = new Vector4(0.9f, 0.9f, 0.9f, alpha);
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(0.9f, 0.9f, 0.9f, alpha)))
|
|
||||||
|
ImGui.SetCursorPos(messagePos);
|
||||||
|
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Text, messageColor))
|
||||||
{
|
{
|
||||||
ImGui.TextWrapped(notification.Message);
|
DrawWrappedText(notification.Message, contentWidth);
|
||||||
}
|
}
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha)
|
private void DrawNotificationActions(LightlessNotification notification, float availableWidth, float alpha)
|
||||||
{
|
{
|
||||||
var buttonSpacing = 8f;
|
var buttonWidth = CalculateActionButtonWidth(notification.Actions.Count, availableWidth);
|
||||||
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}",
|
_logger.LogDebug("Drawing {ActionCount} notification actions, buttonWidth: {ButtonWidth}, availableWidth: {AvailableWidth}",
|
||||||
notification.Actions.Count, buttonWidth, availableWidth);
|
notification.Actions.Count, buttonWidth, availableWidth);
|
||||||
|
|
||||||
var startCursorPos = ImGui.GetCursorPos();
|
var startX = ImGui.GetCursorPosX();
|
||||||
|
|
||||||
for (int i = 0; i < notification.Actions.Count; i++)
|
for (int i = 0; i < notification.Actions.Count; i++)
|
||||||
{
|
{
|
||||||
var action = notification.Actions[i];
|
|
||||||
|
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var currentX = startCursorPos.X + i * (buttonWidth + buttonSpacing);
|
PositionActionButton(i, startX, buttonWidth);
|
||||||
ImGui.SetCursorPosX(currentX);
|
|
||||||
}
|
}
|
||||||
DrawActionButton(action, notification, alpha, buttonWidth);
|
DrawActionButton(notification.Actions[i], notification, alpha, buttonWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float CalculateActionButtonWidth(int actionCount, float availableWidth)
|
||||||
|
{
|
||||||
|
var totalSpacing = (actionCount - 1) * ActionButtonSpacing;
|
||||||
|
return (availableWidth - totalSpacing) / actionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PositionActionButton(int index, float startX, float buttonWidth)
|
||||||
|
{
|
||||||
|
var xPosition = startX + index * (buttonWidth + ActionButtonSpacing);
|
||||||
|
ImGui.SetCursorPosX(xPosition);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawActionButton(LightlessNotificationAction action, LightlessNotification notification, float alpha, float buttonWidth)
|
private void DrawActionButton(LightlessNotificationAction action, LightlessNotification notification, float alpha, float buttonWidth)
|
||||||
{
|
{
|
||||||
@@ -638,7 +669,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
private float CalculateNotificationHeight(LightlessNotification notification)
|
private float CalculateNotificationHeight(LightlessNotification notification)
|
||||||
{
|
{
|
||||||
var contentWidth = _configService.Current.NotificationWidth - 35f;
|
var contentWidth = CalculateContentWidth(_configService.Current.NotificationWidth);
|
||||||
var height = 12f;
|
var height = 12f;
|
||||||
|
|
||||||
height += CalculateTitleHeight(notification, contentWidth);
|
height += CalculateTitleHeight(notification, contentWidth);
|
||||||
@@ -685,6 +716,8 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
|
|||||||
NotificationType.Error => UIColors.Get("DimRed"),
|
NotificationType.Error => UIColors.Get("DimRed"),
|
||||||
NotificationType.PairRequest => UIColors.Get("LightlessBlue"),
|
NotificationType.PairRequest => UIColors.Get("LightlessBlue"),
|
||||||
NotificationType.Download => UIColors.Get("LightlessGreen"),
|
NotificationType.Download => UIColors.Get("LightlessGreen"),
|
||||||
|
NotificationType.Performance => UIColors.Get("LightlessOrange"),
|
||||||
|
|
||||||
_ => UIColors.Get("LightlessPurple")
|
_ => UIColors.Get("LightlessPurple")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1990,7 +1990,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"),
|
("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"),
|
||||||
("LightlessGreen", "Success Green", "Join buttons and success messages"),
|
("LightlessGreen", "Success Green", "Join buttons and success messages"),
|
||||||
("LightlessYellow", "Warning Yellow", "Warning colors"),
|
("LightlessYellow", "Warning Yellow", "Warning colors"),
|
||||||
("LightlessYellow2", "Warning Yellow (Alt)", "Warning colors"),
|
("LightlessOrange", "Performance Orange", "Performance notifications and warnings"),
|
||||||
("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"),
|
("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"),
|
||||||
("DimRed", "Error Red", "Error and offline colors")
|
("DimRed", "Error Red", "Error and offline colors")
|
||||||
};
|
};
|
||||||
@@ -3640,6 +3640,36 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Test download progress notification");
|
UiSharedService.AttachToolTip("Test download progress notification");
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableSetColumnIndex(0);
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted("Performance Notifications");
|
||||||
|
ImGui.TableSetColumnIndex(1);
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
_uiShared.DrawCombo("###enhanced_performance", lightlessLocations, GetNotificationLocationLabel,
|
||||||
|
(location) =>
|
||||||
|
{
|
||||||
|
_configService.Current.LightlessPerformanceNotification = location;
|
||||||
|
_configService.Save();
|
||||||
|
}, _configService.Current.LightlessPerformanceNotification);
|
||||||
|
ImGui.TableSetColumnIndex(2);
|
||||||
|
availableWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_performance", new Vector2(availableWidth, 0)))
|
||||||
|
{
|
||||||
|
var testUserData = new UserData("TEST123", "TestUser", false, false, false, null, null);
|
||||||
|
Mediator.Publish(new PerformanceNotificationMessage(
|
||||||
|
"Test Player (TestUser) exceeds performance threshold(s)",
|
||||||
|
"Player Test Player (TestUser) exceeds your configured VRAM warning threshold\n500 MB/300 MB",
|
||||||
|
testUserData,
|
||||||
|
false,
|
||||||
|
"Test Player"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("Test performance notification");
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3974,6 +4004,20 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
ImGui.SetTooltip("Right click to reset to default (300).");
|
ImGui.SetTooltip("Right click to reset to default (300).");
|
||||||
|
|
||||||
|
int performanceDuration = _configService.Current.PerformanceNotificationDurationSeconds;
|
||||||
|
if (ImGui.SliderInt("Performance Duration (seconds)", ref performanceDuration, 5, 60))
|
||||||
|
{
|
||||||
|
_configService.Current.PerformanceNotificationDurationSeconds = Math.Clamp(performanceDuration, 5, 60);
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
|
{
|
||||||
|
_configService.Current.PerformanceNotificationDurationSeconds = 20;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Right click to reset to default (20).");
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
@@ -4041,6 +4085,38 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_uiShared.MediumTreeNode("Pair Request Notifications", UIColors.Get("PairBlue")))
|
||||||
|
{
|
||||||
|
var showPairRequestActions = _configService.Current.ShowPairRequestNotificationActions;
|
||||||
|
if (ImGui.Checkbox("Show action buttons on pair requests", ref showPairRequestActions))
|
||||||
|
{
|
||||||
|
_configService.Current.ShowPairRequestNotificationActions = showPairRequestActions;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiShared.DrawHelpText(
|
||||||
|
"When you receive a pair request, show Accept/Decline buttons in the notification.");
|
||||||
|
|
||||||
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uiShared.MediumTreeNode("Performance Notifications", UIColors.Get("LightlessOrange")))
|
||||||
|
{
|
||||||
|
var showPerformanceActions = _configService.Current.ShowPerformanceNotificationActions;
|
||||||
|
if (ImGui.Checkbox("Show action buttons on performance warnings", ref showPerformanceActions))
|
||||||
|
{
|
||||||
|
_configService.Current.ShowPerformanceNotificationActions = showPerformanceActions;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiShared.DrawHelpText(
|
||||||
|
"When a player exceeds performance thresholds or is auto-paused, show Pause/Unpause buttons in the notification.");
|
||||||
|
|
||||||
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessOrange"), 1.5f);
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
if (_uiShared.MediumTreeNode("System Notifications", UIColors.Get("LightlessYellow")))
|
if (_uiShared.MediumTreeNode("System Notifications", UIColors.Get("LightlessYellow")))
|
||||||
{
|
{
|
||||||
var disableOptionalPluginWarnings = _configService.Current.DisableOptionalPluginWarnings;
|
var disableOptionalPluginWarnings = _configService.Current.DisableOptionalPluginWarnings;
|
||||||
@@ -4074,8 +4150,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
NotificationLocation.LightlessUi, NotificationLocation.ChatAndLightlessUi,
|
NotificationLocation.LightlessUi, NotificationLocation.TextOverlay, NotificationLocation.Nowhere
|
||||||
NotificationLocation.TextOverlay, NotificationLocation.Nowhere
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4138,7 +4213,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
("Warning", 1, _configService.Current.CustomWarningSoundId, _configService.Current.DisableWarningSound, 16u),
|
("Warning", 1, _configService.Current.CustomWarningSoundId, _configService.Current.DisableWarningSound, 16u),
|
||||||
("Error", 2, _configService.Current.CustomErrorSoundId, _configService.Current.DisableErrorSound, 16u),
|
("Error", 2, _configService.Current.CustomErrorSoundId, _configService.Current.DisableErrorSound, 16u),
|
||||||
("Pair Request", 3, _configService.Current.PairRequestSoundId, _configService.Current.DisablePairRequestSound, 5u),
|
("Pair Request", 3, _configService.Current.PairRequestSoundId, _configService.Current.DisablePairRequestSound, 5u),
|
||||||
("Download", 4, _configService.Current.DownloadSoundId, _configService.Current.DisableDownloadSound, 15u)
|
("Performance", 4, _configService.Current.PerformanceSoundId, _configService.Current.DisablePerformanceSound, 16u)
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (typeName, typeIndex, currentSoundId, isDisabled, defaultSoundId) in soundTypes)
|
foreach (var (typeName, typeIndex, currentSoundId, isDisabled, defaultSoundId) in soundTypes)
|
||||||
@@ -4168,7 +4243,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
case 1: _configService.Current.CustomWarningSoundId = newSoundId; break;
|
case 1: _configService.Current.CustomWarningSoundId = newSoundId; break;
|
||||||
case 2: _configService.Current.CustomErrorSoundId = newSoundId; break;
|
case 2: _configService.Current.CustomErrorSoundId = newSoundId; break;
|
||||||
case 3: _configService.Current.PairRequestSoundId = newSoundId; break;
|
case 3: _configService.Current.PairRequestSoundId = newSoundId; break;
|
||||||
case 4: _configService.Current.DownloadSoundId = newSoundId; break;
|
case 4: _configService.Current.PerformanceSoundId = newSoundId; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
@@ -4222,7 +4297,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
case 1: _configService.Current.DisableWarningSound = newDisabled; break;
|
case 1: _configService.Current.DisableWarningSound = newDisabled; break;
|
||||||
case 2: _configService.Current.DisableErrorSound = newDisabled; break;
|
case 2: _configService.Current.DisableErrorSound = newDisabled; break;
|
||||||
case 3: _configService.Current.DisablePairRequestSound = newDisabled; break;
|
case 3: _configService.Current.DisablePairRequestSound = newDisabled; break;
|
||||||
case 4: _configService.Current.DisableDownloadSound = newDisabled; break;
|
case 4: _configService.Current.DisablePerformanceSound = newDisabled; break;
|
||||||
}
|
}
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
@@ -4248,7 +4323,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
case 1: _configService.Current.CustomWarningSoundId = defaultSoundId; break;
|
case 1: _configService.Current.CustomWarningSoundId = defaultSoundId; break;
|
||||||
case 2: _configService.Current.CustomErrorSoundId = defaultSoundId; break;
|
case 2: _configService.Current.CustomErrorSoundId = defaultSoundId; break;
|
||||||
case 3: _configService.Current.PairRequestSoundId = defaultSoundId; break;
|
case 3: _configService.Current.PairRequestSoundId = defaultSoundId; break;
|
||||||
case 4: _configService.Current.DownloadSoundId = defaultSoundId; break;
|
case 4: _configService.Current.PerformanceSoundId = defaultSoundId; break;
|
||||||
}
|
}
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
ImGuiHelpers.ScaledDummy(0.5f);
|
ImGuiHelpers.ScaledDummy(0.5f);
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 10.0f);
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 10.0f);
|
||||||
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessYellow2"));
|
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("PairBlue"));
|
||||||
|
|
||||||
if (ImGui.Button("Open Lightfinder", new Vector2(200 * ImGuiHelpers.GlobalScale, 0)))
|
if (ImGui.Button("Open Lightfinder", new Vector2(200 * ImGuiHelpers.GlobalScale, 0)))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -196,82 +196,6 @@ public class TopTabMenu
|
|||||||
|
|
||||||
if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f);
|
if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f);
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
if (ImGui.Button("Test Pair Request"))
|
|
||||||
{
|
|
||||||
_lightlessNotificationService.ShowPairRequestNotification(
|
|
||||||
"Debug User",
|
|
||||||
"debug-user-id",
|
|
||||||
onAccept: () =>
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage(
|
|
||||||
"Pair Accepted",
|
|
||||||
"Debug pair request was accepted!",
|
|
||||||
NotificationType.Info,
|
|
||||||
TimeSpan.FromSeconds(3)));
|
|
||||||
},
|
|
||||||
onDecline: () =>
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage(
|
|
||||||
"Pair Declined",
|
|
||||||
"Debug pair request was declined.",
|
|
||||||
NotificationType.Warning,
|
|
||||||
TimeSpan.FromSeconds(3)));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Test Info"))
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage(
|
|
||||||
"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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Test Warning"))
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage(
|
|
||||||
"Warning",
|
|
||||||
"This is a test warning notification.",
|
|
||||||
NotificationType.Warning,
|
|
||||||
TimeSpan.FromSeconds(7)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Test Error"))
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage(
|
|
||||||
"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
|
|
||||||
|
|
||||||
DrawIncomingPairRequests(availableWidth);
|
DrawIncomingPairRequests(availableWidth);
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|||||||
@@ -11,21 +11,17 @@ namespace LightlessSync.UI
|
|||||||
{ "LightlessPurple", "#ad8af5" },
|
{ "LightlessPurple", "#ad8af5" },
|
||||||
{ "LightlessPurpleActive", "#be9eff" },
|
{ "LightlessPurpleActive", "#be9eff" },
|
||||||
{ "LightlessPurpleDefault", "#9375d1" },
|
{ "LightlessPurpleDefault", "#9375d1" },
|
||||||
|
|
||||||
{ "ButtonDefault", "#323232" },
|
{ "ButtonDefault", "#323232" },
|
||||||
{ "FullBlack", "#000000" },
|
{ "FullBlack", "#000000" },
|
||||||
|
|
||||||
{ "LightlessBlue", "#a6c2ff" },
|
{ "LightlessBlue", "#a6c2ff" },
|
||||||
{ "LightlessYellow", "#ffe97a" },
|
{ "LightlessYellow", "#ffe97a" },
|
||||||
{ "LightlessYellow2", "#cfbd63" },
|
|
||||||
{ "LightlessGreen", "#7cd68a" },
|
{ "LightlessGreen", "#7cd68a" },
|
||||||
|
{ "LightlessOrange", "#ffb366" },
|
||||||
{ "PairBlue", "#88a2db" },
|
{ "PairBlue", "#88a2db" },
|
||||||
{ "DimRed", "#d44444" },
|
{ "DimRed", "#d44444" },
|
||||||
|
|
||||||
{ "LightlessAdminText", "#ffd663" },
|
{ "LightlessAdminText", "#ffd663" },
|
||||||
{ "LightlessAdminGlow", "#b09343" },
|
{ "LightlessAdminGlow", "#b09343" },
|
||||||
{ "LightlessModeratorText", "#94ffda" },
|
{ "LightlessModeratorText", "#94ffda" },
|
||||||
{ "LightlessModeratorGlow", "#599c84" },
|
|
||||||
|
|
||||||
{ "Lightfinder", "#ad8af5" },
|
{ "Lightfinder", "#ad8af5" },
|
||||||
{ "LightfinderEdge", "#000000" },
|
{ "LightfinderEdge", "#000000" },
|
||||||
|
|||||||
@@ -110,14 +110,7 @@ public partial class ApiController
|
|||||||
if (dto == null)
|
if (dto == null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var request = _pairRequestService.RegisterIncomingRequest(dto.myHashedCid, dto.message ?? string.Empty);
|
_pairRequestService.RegisterIncomingRequest(dto.myHashedCid, dto.message ?? string.Empty);
|
||||||
var senderName = string.IsNullOrEmpty(request.DisplayName) ? "Unknown User" : request.DisplayName;
|
|
||||||
|
|
||||||
_lightlessNotificationService.ShowPairRequestNotification(
|
|
||||||
senderName,
|
|
||||||
request.HashedCid,
|
|
||||||
onAccept: () => _pairRequestService.AcceptPairRequest(request.HashedCid, senderName),
|
|
||||||
onDecline: () => _pairRequestService.DeclinePairRequest(request.HashedCid));
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user