notifcation refactor for better readability

This commit is contained in:
choco
2025-10-11 21:24:39 +02:00
parent c545ccea52
commit a441bbfcc8
4 changed files with 474 additions and 389 deletions

View File

@@ -39,14 +39,29 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public void ShowNotification(string title, string message, NotificationType type = NotificationType.Info,
TimeSpan? duration = null, List<LightlessNotificationAction>? actions = null, uint? soundEffectId = null)
{
var notification = new LightlessNotification
var notification = CreateNotification(title, message, type, duration, actions, soundEffectId);
if (_configService.Current.AutoDismissOnAction && notification.Actions.Any())
{
WrapActionsWithAutoDismiss(notification);
}
if (notification.SoundEffectId.HasValue)
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
}
private LightlessNotification CreateNotification(string title, string message, NotificationType type,
TimeSpan? duration, List<LightlessNotificationAction>? actions, uint? soundEffectId)
{
return new LightlessNotification
{
Title = title,
Message = message,
@@ -57,30 +72,28 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
ShowProgress = _configService.Current.ShowNotificationProgress,
CreatedAt = DateTime.UtcNow
};
if (_configService.Current.AutoDismissOnAction && notification.Actions.Any())
}
private void WrapActionsWithAutoDismiss(LightlessNotification notification)
{
foreach (var action in notification.Actions)
{
foreach (var action in notification.Actions)
var originalOnClick = action.OnClick;
action.OnClick = (n) =>
{
var originalOnClick = action.OnClick;
action.OnClick = (n) =>
originalOnClick(n);
if (_configService.Current.AutoDismissOnAction)
{
originalOnClick(n);
if (_configService.Current.AutoDismissOnAction)
{
n.IsDismissed = true;
n.IsAnimatingOut = true;
}
};
}
DismissNotification(n);
}
};
}
if (notification.SoundEffectId.HasValue)
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
}
private void DismissNotification(LightlessNotification notification)
{
notification.IsDismissed = true;
notification.IsAnimatingOut = true;
}
public void ShowPairRequestNotification(string senderName, string senderId, Action onAccept, Action onDecline)
{
@@ -90,40 +103,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Message = $"{senderName} wants to directly pair with you.",
Type = NotificationType.PairRequest,
Duration = TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
SoundEffectId = !_configService.Current.DisablePairRequestSound ? _configService.Current.PairRequestSoundId : null,
Actions = new List<LightlessNotificationAction>
{
new()
{
Id = "accept",
Label = "Accept",
Icon = FontAwesomeIcon.Check,
Color = UIColors.Get("LightlessGreen"),
IsPrimary = true,
OnClick = (n) =>
{
_logger.LogInformation("Pair request accepted");
onAccept();
n.IsDismissed = true;
n.IsAnimatingOut = true;
}
},
new()
{
Id = "decline",
Label = "Decline",
Icon = FontAwesomeIcon.Times,
Color = UIColors.Get("DimRed"),
IsDestructive = true,
OnClick = (n) =>
{
_logger.LogInformation("Pair request declined");
onDecline();
n.IsDismissed = true;
n.IsAnimatingOut = true;
}
}
}
SoundEffectId = GetPairRequestSoundId(),
Actions = CreatePairRequestActions(onAccept, onDecline)
};
if (notification.SoundEffectId.HasValue)
@@ -133,7 +114,70 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Publish(new LightlessNotificationMessage(notification));
}
private uint? GetPairRequestSoundId() =>
!_configService.Current.DisablePairRequestSound ? _configService.Current.PairRequestSoundId : null;
private List<LightlessNotificationAction> CreatePairRequestActions(Action onAccept, Action onDecline)
{
return new List<LightlessNotificationAction>
{
new()
{
Id = "accept",
Label = "Accept",
Icon = FontAwesomeIcon.Check,
Color = UIColors.Get("LightlessGreen"),
IsPrimary = true,
OnClick = (n) =>
{
_logger.LogInformation("Pair request accepted");
onAccept();
DismissNotification(n);
}
},
new()
{
Id = "decline",
Label = "Decline",
Icon = FontAwesomeIcon.Times,
Color = UIColors.Get("DimRed"),
IsDestructive = true,
OnClick = (n) =>
{
_logger.LogInformation("Pair request declined");
onDecline();
DismissNotification(n);
}
}
};
}
public void ShowDownloadCompleteNotification(string fileName, int fileCount, Action? onOpenFolder = null)
{
var notification = new LightlessNotification
{
Title = "Download Complete",
Message = FormatDownloadCompleteMessage(fileName, fileCount),
Type = NotificationType.Info,
Duration = TimeSpan.FromSeconds(8),
Actions = CreateDownloadCompleteActions(onOpenFolder),
SoundEffectId = NotificationSounds.DownloadComplete
};
if (notification.SoundEffectId.HasValue)
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
}
private string FormatDownloadCompleteMessage(string fileName, int fileCount) =>
fileCount > 1
? $"Downloaded {fileCount} files successfully."
: $"Downloaded {fileName} successfully.";
private List<LightlessNotificationAction> CreateDownloadCompleteActions(Action? onOpenFolder)
{
var actions = new List<LightlessNotificationAction>();
@@ -148,21 +192,23 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
OnClick = (n) =>
{
onOpenFolder();
n.IsDismissed = true;
n.IsAnimatingOut = true;
DismissNotification(n);
}
});
}
return actions;
}
public void ShowErrorNotification(string title, string message, Exception? exception = null, Action? onRetry = null, Action? onViewLog = null)
{
var notification = new LightlessNotification
{
Title = "Download Complete",
Message = fileCount > 1 ?
$"Downloaded {fileCount} files successfully." :
$"Downloaded {fileName} successfully.",
Type = NotificationType.Info,
Duration = TimeSpan.FromSeconds(8),
Actions = actions,
SoundEffectId = NotificationSounds.DownloadComplete
Title = title,
Message = FormatErrorMessage(message, exception),
Type = NotificationType.Error,
Duration = TimeSpan.FromSeconds(15),
Actions = CreateErrorActions(onRetry, onViewLog),
SoundEffectId = NotificationSounds.Error
};
if (notification.SoundEffectId.HasValue)
@@ -172,9 +218,14 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Publish(new LightlessNotificationMessage(notification));
}
public void ShowErrorNotification(string title, string message, Exception? exception = null, Action? onRetry = null, Action? onViewLog = null)
private string FormatErrorMessage(string message, Exception? exception) =>
exception != null ? $"{message}\n\nError: {exception.Message}" : message;
private List<LightlessNotificationAction> CreateErrorActions(Action? onRetry, Action? onViewLog)
{
var actions = new List<LightlessNotificationAction>();
if (onRetry != null)
{
actions.Add(new LightlessNotificationAction
@@ -186,11 +237,11 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
OnClick = (n) =>
{
onRetry();
n.IsDismissed = true;
n.IsAnimatingOut = true;
DismissNotification(n);
}
});
}
if (onViewLog != null)
{
actions.Add(new LightlessNotificationAction
@@ -202,65 +253,14 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
OnClick = (n) => onViewLog()
});
}
var notification = new LightlessNotification
{
Title = title,
Message = exception != null ? $"{message}\n\nError: {exception.Message}" : message,
Type = NotificationType.Error,
Duration = TimeSpan.FromSeconds(15),
Actions = actions,
SoundEffectId = NotificationSounds.Error
};
if (notification.SoundEffectId.HasValue)
{
PlayNotificationSound(notification.SoundEffectId.Value);
}
Mediator.Publish(new LightlessNotificationMessage(notification));
return actions;
}
public void ShowPairDownloadNotification(List<(string playerName, float progress, string status)> downloadStatus, int queueWaiting = 0)
{
var userDownloads = downloadStatus.Where(x => x.playerName != "Pair Queue").ToList();
var totalProgress = userDownloads.Count > 0 ? userDownloads.Average(x => x.progress) : 0f;
var completedCount = userDownloads.Count(x => x.progress >= 1.0f);
var totalCount = userDownloads.Count;
var message = "";
if (queueWaiting > 0)
{
message = $"Queue: {queueWaiting} waiting";
}
if (totalCount > 0)
{
var progressMessage = $"Progress: {completedCount}/{totalCount} completed";
message = string.IsNullOrEmpty(message) ? progressMessage : $"{message}\n{progressMessage}";
}
if (userDownloads.Any(x => x.progress < 1.0f))
{
var maxNamesToShow = _configService.Current.MaxConcurrentPairApplications;
var activeDownloads = userDownloads.Where(x => x.progress < 1.0f).Take(maxNamesToShow);
var downloadLines = string.Join("\n", activeDownloads.Select(x =>
{
var statusText = x.status switch
{
"downloading" => $"{x.progress:P0}",
"decompressing" => "decompressing",
"queued" => "queued",
"waiting" => "waiting for slot",
_ => x.status
};
return $"• {x.playerName}: {statusText}";
}));
message += string.IsNullOrEmpty(message) ? downloadLines : $"\n{downloadLines}";
}
var allDownloadsCompleted = userDownloads.All(x => x.progress >= 1.0f) && userDownloads.Any();
var message = BuildPairDownloadMessage(userDownloads, queueWaiting);
var notification = new LightlessNotification
{
@@ -272,59 +272,99 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
ShowProgress = true,
Progress = totalProgress
};
Mediator.Publish(new LightlessNotificationMessage(notification));
if (allDownloadsCompleted)
if (AreAllDownloadsCompleted(userDownloads))
{
DismissPairDownloadNotification();
}
}
public void DismissPairDownloadNotification()
private string BuildPairDownloadMessage(List<(string playerName, float progress, string status)> userDownloads, int queueWaiting)
{
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
}
private TimeSpan GetDefaultDurationForType(NotificationType type)
{
return type switch
var messageParts = new List<string>();
if (queueWaiting > 0)
{
NotificationType.Info => TimeSpan.FromSeconds(_configService.Current.InfoNotificationDurationSeconds),
NotificationType.Warning => TimeSpan.FromSeconds(_configService.Current.WarningNotificationDurationSeconds),
NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds),
NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
_ => TimeSpan.FromSeconds(10) // Fallback for any unknown types
};
messageParts.Add($"Queue: {queueWaiting} waiting");
}
if (userDownloads.Count > 0)
{
var completedCount = userDownloads.Count(x => x.progress >= 1.0f);
messageParts.Add($"Progress: {completedCount}/{userDownloads.Count} completed");
}
var activeDownloadLines = BuildActiveDownloadLines(userDownloads);
if (!string.IsNullOrEmpty(activeDownloadLines))
{
messageParts.Add(activeDownloadLines);
}
return string.Join("\n", messageParts);
}
private string BuildActiveDownloadLines(List<(string playerName, float progress, string status)> userDownloads)
{
var activeDownloads = userDownloads
.Where(x => x.progress < 1.0f)
.Take(_configService.Current.MaxConcurrentPairApplications);
if (!activeDownloads.Any()) return string.Empty;
return string.Join("\n", activeDownloads.Select(x => $"• {x.playerName}: {FormatDownloadStatus(x)}"));
}
private string FormatDownloadStatus((string playerName, float progress, string status) download) => download.status switch
{
"downloading" => $"{download.progress:P0}",
"decompressing" => "decompressing",
"queued" => "queued",
"waiting" => "waiting for slot",
_ => download.status
};
private bool AreAllDownloadsCompleted(List<(string playerName, float progress, string status)> userDownloads) =>
userDownloads.Any() && userDownloads.All(x => x.progress >= 1.0f);
public void DismissPairDownloadNotification() =>
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
private TimeSpan GetDefaultDurationForType(NotificationType type) => type switch
{
NotificationType.Info => TimeSpan.FromSeconds(_configService.Current.InfoNotificationDurationSeconds),
NotificationType.Warning => TimeSpan.FromSeconds(_configService.Current.WarningNotificationDurationSeconds),
NotificationType.Error => TimeSpan.FromSeconds(_configService.Current.ErrorNotificationDurationSeconds),
NotificationType.PairRequest => TimeSpan.FromSeconds(_configService.Current.PairRequestDurationSeconds),
NotificationType.Download => TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
_ => TimeSpan.FromSeconds(10)
};
private uint? GetSoundEffectId(NotificationType type, uint? overrideSoundId)
{
if (overrideSoundId.HasValue)
return overrideSoundId;
// Check if this specific notification type is disabled
bool isDisabled = type switch
{
NotificationType.Info => _configService.Current.DisableInfoSound,
NotificationType.Warning => _configService.Current.DisableWarningSound,
NotificationType.Error => _configService.Current.DisableErrorSound,
NotificationType.Download => _configService.Current.DisableDownloadSound,
_ => false
};
if (isDisabled)
return null;
// Return the configured sound for this type
return type switch
{
NotificationType.Info => _configService.Current.CustomInfoSoundId,
NotificationType.Warning => _configService.Current.CustomWarningSoundId,
NotificationType.Error => _configService.Current.CustomErrorSoundId,
NotificationType.Download => _configService.Current.DownloadSoundId,
_ => NotificationSounds.GetDefaultSound(type)
};
if (overrideSoundId.HasValue) return overrideSoundId;
if (IsSoundDisabledForType(type)) return null;
return GetConfiguredSoundForType(type);
}
private bool IsSoundDisabledForType(NotificationType type) => type switch
{
NotificationType.Info => _configService.Current.DisableInfoSound,
NotificationType.Warning => _configService.Current.DisableWarningSound,
NotificationType.Error => _configService.Current.DisableErrorSound,
NotificationType.Download => _configService.Current.DisableDownloadSound,
_ => false
};
private uint GetConfiguredSoundForType(NotificationType type) => type switch
{
NotificationType.Info => _configService.Current.CustomInfoSoundId,
NotificationType.Warning => _configService.Current.CustomWarningSoundId,
NotificationType.Error => _configService.Current.CustomErrorSoundId,
NotificationType.Download => _configService.Current.DownloadSoundId,
_ => NotificationSounds.GetDefaultSound(type)
};
private void PlayNotificationSound(uint soundEffectId)
{
@@ -342,32 +382,36 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
private void HandleNotificationMessage(NotificationMessage msg)
{
_logger.LogInformation("{msg}", msg.ToString());
if (!_dalamudUtilService.IsLoggedIn) return;
// Get notification location based on type and system preference
var location = _configService.Current.UseLightlessNotifications
? msg.Type switch
{
NotificationType.Info => _configService.Current.LightlessInfoNotification,
NotificationType.Warning => _configService.Current.LightlessWarningNotification,
NotificationType.Error => _configService.Current.LightlessErrorNotification,
NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification,
NotificationType.Download => _configService.Current.LightlessDownloadNotification,
_ => NotificationLocation.LightlessUi
}
: msg.Type switch
{
NotificationType.Info => _configService.Current.InfoNotification,
NotificationType.Warning => _configService.Current.WarningNotification,
NotificationType.Error => _configService.Current.ErrorNotification,
NotificationType.PairRequest => NotificationLocation.Toast,
NotificationType.Download => NotificationLocation.Toast,
_ => NotificationLocation.Nowhere
};
var location = GetNotificationLocation(msg.Type);
ShowNotificationLocationBased(msg, location);
}
private NotificationLocation GetNotificationLocation(NotificationType type) =>
_configService.Current.UseLightlessNotifications
? GetLightlessNotificationLocation(type)
: GetClassicNotificationLocation(type);
private NotificationLocation GetLightlessNotificationLocation(NotificationType type) => type switch
{
NotificationType.Info => _configService.Current.LightlessInfoNotification,
NotificationType.Warning => _configService.Current.LightlessWarningNotification,
NotificationType.Error => _configService.Current.LightlessErrorNotification,
NotificationType.PairRequest => _configService.Current.LightlessPairRequestNotification,
NotificationType.Download => _configService.Current.LightlessDownloadNotification,
_ => NotificationLocation.LightlessUi
};
private NotificationLocation GetClassicNotificationLocation(NotificationType type) => type switch
{
NotificationType.Info => _configService.Current.InfoNotification,
NotificationType.Warning => _configService.Current.WarningNotification,
NotificationType.Error => _configService.Current.ErrorNotification,
NotificationType.PairRequest => NotificationLocation.Toast,
NotificationType.Download => NotificationLocation.Toast,
_ => NotificationLocation.Nowhere
};
private void ShowNotificationLocationBased(NotificationMessage msg, NotificationLocation location)
{
@@ -403,18 +447,12 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
private void ShowLightlessNotification(NotificationMessage msg)
{
var duration = msg.TimeShownOnScreen ?? GetDefaultDurationForType(msg.Type);
// GetSoundEffectId will handle checking if the sound is disabled
ShowNotification(msg.Title ?? "Lightless Sync", msg.Message ?? string.Empty, msg.Type, duration, null, null);
}
private void ShowToast(NotificationMessage msg)
{
Dalamud.Interface.ImGuiNotification.NotificationType dalamudType = msg.Type switch
{
NotificationType.Error => Dalamud.Interface.ImGuiNotification.NotificationType.Error,
NotificationType.Warning => Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
_ => Dalamud.Interface.ImGuiNotification.NotificationType.Info
};
var dalamudType = ConvertToDalamudNotificationType(msg.Type);
_notificationManager.AddNotification(new Notification()
{
@@ -425,6 +463,13 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
InitialDuration = msg.TimeShownOnScreen ?? TimeSpan.FromSeconds(3)
});
}
private Dalamud.Interface.ImGuiNotification.NotificationType ConvertToDalamudNotificationType(NotificationType type) => type switch
{
NotificationType.Error => Dalamud.Interface.ImGuiNotification.NotificationType.Error,
NotificationType.Warning => Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
_ => Dalamud.Interface.ImGuiNotification.NotificationType.Info
};
private void ShowChat(NotificationMessage msg)
{