Ajout du système de notifications : service, configuration et persistance des notifications introduits. Mise à jour de l'UI pour afficher et gérer les notifications Syncshell.

This commit is contained in:
2025-11-02 13:48:11 +01:00
parent 7a391e6253
commit 699678f641
10 changed files with 240 additions and 31 deletions

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using MareSynchronos.MareConfiguration.Models;
namespace MareSynchronos.MareConfiguration.Configurations;
[Serializable]
public class NotificationsConfig : IMareConfiguration
{
public List<StoredNotification> Notifications { get; set; } = new();
public int Version { get; set; } = 1;
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
namespace MareSynchronos.MareConfiguration.Models;
[Serializable]
public class StoredNotification
{
public string Category { get; set; } = string.Empty; // name of enum NotificationCategory
public string Id { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,14 @@
using MareSynchronos.MareConfiguration.Configurations;
namespace MareSynchronos.MareConfiguration;
public class NotificationsConfigService : ConfigurationServiceBase<NotificationsConfig>
{
public const string ConfigName = "notifications.json";
public NotificationsConfigService(string configDir) : base(configDir)
{
}
public override string ConfigurationName => ConfigName;
}

View File

@@ -167,6 +167,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton((s) => new ServerBlockConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerBlockConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new CharaDataConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new CharaDataConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new RemoteConfigCacheService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new RemoteConfigCacheService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new NotificationsConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<MareConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<MareConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<NotesConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<NotesConfigService>());
@@ -178,6 +179,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerBlockConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerBlockConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<CharaDataConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<CharaDataConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<RemoteConfigCacheService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<RemoteConfigCacheService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<NotificationsConfigService>());
collection.AddSingleton<ConfigurationMigrator>(); collection.AddSingleton<ConfigurationMigrator>();
collection.AddSingleton<ConfigurationSaveService>(); collection.AddSingleton<ConfigurationSaveService>();

View File

@@ -2,29 +2,45 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.MareConfiguration;
using MareSynchronos.MareConfiguration.Configurations;
using MareSynchronos.MareConfiguration.Models;
namespace MareSynchronos.Services.Notifications; namespace MareSynchronos.Services.Notifications;
public enum NotificationCategory public enum NotificationCategory
{ {
AutoDetect, AutoDetect,
Syncshell,
} }
public sealed record NotificationEntry(NotificationCategory Category, string Id, string Title, string? Description, DateTime CreatedAt) public sealed record NotificationEntry(NotificationCategory Category, string Id, string Title, string? Description, DateTime CreatedAt)
{ {
public static NotificationEntry AutoDetect(string uid, string displayName) public static NotificationEntry AutoDetect(string uid, string displayName)
=> new(NotificationCategory.AutoDetect, uid, displayName, "Nouvelle demande d'appairage via AutoDetect.", DateTime.UtcNow); => new(NotificationCategory.AutoDetect, uid, displayName, "Nouvelle demande d'appairage via AutoDetect.", DateTime.UtcNow);
public static NotificationEntry SyncshellPublic(string gid, string aliasOrGid)
=> new(NotificationCategory.Syncshell, gid, $"Syncshell publique: {aliasOrGid}", "La Syncshell est désormais visible via AutoDetect.", DateTime.UtcNow);
public static NotificationEntry SyncshellNotPublic(string gid, string aliasOrGid)
=> new(NotificationCategory.Syncshell, gid, $"Syncshell non publique: {aliasOrGid}", "La Syncshell n'est plus visible via AutoDetect.", DateTime.UtcNow);
} }
public sealed class NotificationTracker public sealed class NotificationTracker
{ {
private const int MaxStored = 100;
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
private readonly NotificationsConfigService _configService;
private readonly Dictionary<(NotificationCategory Category, string Id), NotificationEntry> _entries = new(); private readonly Dictionary<(NotificationCategory Category, string Id), NotificationEntry> _entries = new();
private readonly object _lock = new(); private readonly object _lock = new();
public NotificationTracker(MareMediator mediator) public NotificationTracker(MareMediator mediator, NotificationsConfigService configService)
{ {
_mediator = mediator; _mediator = mediator;
_configService = configService;
LoadPersisted();
PublishState();
} }
public void Upsert(NotificationEntry entry) public void Upsert(NotificationEntry entry)
@@ -32,6 +48,8 @@ public sealed class NotificationTracker
lock (_lock) lock (_lock)
{ {
_entries[(entry.Category, entry.Id)] = entry; _entries[(entry.Category, entry.Id)] = entry;
TrimIfNecessary_NoLock();
Persist_NoLock();
} }
PublishState(); PublishState();
} }
@@ -41,6 +59,7 @@ public sealed class NotificationTracker
lock (_lock) lock (_lock)
{ {
_entries.Remove((category, id)); _entries.Remove((category, id));
Persist_NoLock();
} }
PublishState(); PublishState();
} }
@@ -70,4 +89,56 @@ public sealed class NotificationTracker
{ {
_mediator.Publish(new NotificationStateChanged(Count)); _mediator.Publish(new NotificationStateChanged(Count));
} }
private void LoadPersisted()
{
try
{
var list = _configService.Current.Notifications ?? new List<StoredNotification>();
foreach (var s in list)
{
if (!Enum.TryParse<NotificationCategory>(s.Category, out var cat)) continue;
var entry = new NotificationEntry(cat, s.Id, s.Title, s.Description, s.CreatedAtUtc);
_entries[(entry.Category, entry.Id)] = entry;
}
TrimIfNecessary_NoLock();
}
catch
{
// ignore load errors, start empty
}
}
private void Persist_NoLock()
{
try
{
var stored = _entries.Values
.OrderBy(e => e.CreatedAt)
.Select(e => new StoredNotification
{
Category = e.Category.ToString(),
Id = e.Id,
Title = e.Title,
Description = e.Description,
CreatedAtUtc = e.CreatedAt
})
.ToList();
_configService.Current.Notifications = stored;
_configService.Save();
}
catch
{
// ignore persistence errors
}
}
private void TrimIfNecessary_NoLock()
{
if (_entries.Count <= MaxStored) return;
foreach (var kv in _entries.Values.OrderByDescending(v => v.CreatedAt).Skip(MaxStored).ToList())
{
_entries.Remove((kv.Category, kv.Id));
}
}
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@@ -17,22 +18,29 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly MareConfigService _configurationService; private readonly MareConfigService _configurationService;
private readonly Services.Notifications.NotificationTracker _notificationTracker;
private readonly PlayerData.Pairs.PairManager _pairManager;
public NotificationService(ILogger<NotificationService> logger, MareMediator mediator, public NotificationService(ILogger<NotificationService> logger, MareMediator mediator,
DalamudUtilService dalamudUtilService, DalamudUtilService dalamudUtilService,
INotificationManager notificationManager, INotificationManager notificationManager,
IChatGui chatGui, MareConfigService configurationService) : base(logger, mediator) IChatGui chatGui, MareConfigService configurationService,
Services.Notifications.NotificationTracker notificationTracker,
PlayerData.Pairs.PairManager pairManager) : base(logger, mediator)
{ {
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_notificationManager = notificationManager; _notificationManager = notificationManager;
_chatGui = chatGui; _chatGui = chatGui;
_configurationService = configurationService; _configurationService = configurationService;
_notificationTracker = notificationTracker;
_pairManager = pairManager;
} }
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
Mediator.Subscribe<NotificationMessage>(this, ShowNotification); Mediator.Subscribe<NotificationMessage>(this, ShowNotification);
Mediator.Subscribe<DualNotificationMessage>(this, ShowDualNotification); Mediator.Subscribe<DualNotificationMessage>(this, ShowDualNotification);
Mediator.Subscribe<Services.Mediator.SyncshellAutoDetectStateChanged>(this, OnSyncshellAutoDetectStateChanged);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -113,6 +121,31 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
ShowChat(baseMsg); ShowChat(baseMsg);
} }
private void OnSyncshellAutoDetectStateChanged(SyncshellAutoDetectStateChanged msg)
{
try
{
if (msg.Visible) return; // only handle transition to not visible
var gid = msg.Gid;
// Try to resolve alias from PairManager snapshot; fallback to gid
var alias = _pairManager.Groups.Values.FirstOrDefault(g => string.Equals(g.GID, gid, StringComparison.OrdinalIgnoreCase))?.GroupAliasOrGID ?? gid;
var title = $"Syncshell non publique: {alias}";
var message = "La Syncshell n'est plus visible via AutoDetect.";
// Show toast + chat
ShowDualNotification(new DualNotificationMessage(title, message, NotificationType.Info, TimeSpan.FromSeconds(4)));
// Persist into notification center
_notificationTracker.Upsert(Services.Notifications.NotificationEntry.SyncshellNotPublic(gid, alias));
}
catch
{
// ignore failures
}
}
private static bool ShouldForceChat(NotificationMessage msg, out bool appendInstruction) private static bool ShouldForceChat(NotificationMessage msg, out bool appendInstruction)
{ {
appendInstruction = false; appendInstruction = false;

View File

@@ -3,6 +3,7 @@ using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.AutoDetect; using MareSynchronos.Services.AutoDetect;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Services.Notifications;
using MareSynchronos.UI; using MareSynchronos.UI;
using MareSynchronos.UI.Components.Popup; using MareSynchronos.UI.Components.Popup;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
@@ -21,10 +22,11 @@ public class UiFactory
private readonly MareProfileManager _mareProfileManager; private readonly MareProfileManager _mareProfileManager;
private readonly PerformanceCollectorService _performanceCollectorService; private readonly PerformanceCollectorService _performanceCollectorService;
private readonly SyncshellDiscoveryService _syncshellDiscoveryService; private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
private readonly NotificationTracker _notificationTracker;
public UiFactory(ILoggerFactory loggerFactory, MareMediator mareMediator, ApiController apiController, public UiFactory(ILoggerFactory loggerFactory, MareMediator mareMediator, ApiController apiController,
UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService, ServerConfigurationManager serverConfigManager, UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService, ServerConfigurationManager serverConfigManager,
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService) MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService, NotificationTracker notificationTracker)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_mareMediator = mareMediator; _mareMediator = mareMediator;
@@ -35,12 +37,13 @@ public class UiFactory
_serverConfigManager = serverConfigManager; _serverConfigManager = serverConfigManager;
_mareProfileManager = mareProfileManager; _mareProfileManager = mareProfileManager;
_performanceCollectorService = performanceCollectorService; _performanceCollectorService = performanceCollectorService;
_notificationTracker = notificationTracker;
} }
public SyncshellAdminUI CreateSyncshellAdminUi(GroupFullInfoDto dto) public SyncshellAdminUI CreateSyncshellAdminUi(GroupFullInfoDto dto)
{ {
return new SyncshellAdminUI(_loggerFactory.CreateLogger<SyncshellAdminUI>(), _mareMediator, return new SyncshellAdminUI(_loggerFactory.CreateLogger<SyncshellAdminUI>(), _mareMediator,
_apiController, _uiSharedService, _pairManager, _syncshellDiscoveryService, dto, _performanceCollectorService); _apiController, _uiSharedService, _pairManager, _syncshellDiscoveryService, dto, _performanceCollectorService, _notificationTracker);
} }
public StandaloneProfileUi CreateStandaloneProfileUi(Pair pair) public StandaloneProfileUi CreateStandaloneProfileUi(Pair pair)

View File

@@ -860,28 +860,34 @@ if (showNearby && pendingInvites > 0)
ImGuiHelpers.ScaledDummy(4f); ImGuiHelpers.ScaledDummy(4f);
var indent = 18f * ImGuiHelpers.GlobalScale; var indent = 18f * ImGuiHelpers.GlobalScale;
ImGui.Indent(indent); ImGui.Indent(indent);
foreach (var e in nearbyEntries)
// Use a table to guarantee right-aligned action within the card content area
var actionButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.UserPlus);
if (ImGui.BeginTable("nearby-table", 2, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg | ImGuiTableFlags.PadOuterX | ImGuiTableFlags.BordersInnerV))
{ {
if (!e.AcceptPairRequests || string.IsNullOrEmpty(e.Token)) ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 1f);
{ ImGui.TableSetupColumn("Action", ImGuiTableColumnFlags.WidthFixed, actionButtonSize.X);
continue;
}
var name = e.DisplayName ?? e.Name; foreach (var e in nearbyEntries)
ImGui.AlignTextToFramePadding(); {
ImGui.TextUnformatted(name); if (!e.AcceptPairRequests || string.IsNullOrEmpty(e.Token))
var right = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth(); {
ImGui.SameLine(); continue;
}
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
var name = e.DisplayName ?? e.Name;
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(name);
// Right column: action button, aligned to the right within the column
ImGui.TableSetColumnIndex(1);
var curX = ImGui.GetCursorPosX();
var availX = ImGui.GetContentRegionAvail().X; // width of the action column
ImGui.SetCursorPosX(curX + MathF.Max(0, availX - actionButtonSize.X));
var statusButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.UserPlus);
ImGui.SetCursorPosX(right - statusButtonSize.X);
if (!e.AcceptPairRequests)
{
_uiSharedService.IconText(FontAwesomeIcon.Ban, ImGuiColors.DalamudGrey3);
UiSharedService.AttachToolTip("Les demandes sont désactivées pour ce joueur");
}
else if (!string.IsNullOrEmpty(e.Token))
{
using (ImRaii.PushId(e.Token ?? e.Uid ?? e.Name ?? string.Empty)) using (ImRaii.PushId(e.Token ?? e.Uid ?? e.Name ?? string.Empty))
{ {
if (_uiSharedService.IconButton(FontAwesomeIcon.UserPlus)) if (_uiSharedService.IconButton(FontAwesomeIcon.UserPlus))
@@ -891,12 +897,9 @@ if (showNearby && pendingInvites > 0)
} }
UiSharedService.AttachToolTip("Envoyer une invitation d'apparaige"); UiSharedService.AttachToolTip("Envoyer une invitation d'apparaige");
} }
else ImGui.EndTable();
{
_uiSharedService.IconText(FontAwesomeIcon.QuestionCircle, ImGuiColors.DalamudGrey3);
UiSharedService.AttachToolTip("Impossible d'inviter ce joueur");
}
} }
ImGui.Unindent(indent); ImGui.Unindent(indent);
}, stretchWidth: true); }, stretchWidth: true);
} }
@@ -1173,6 +1176,9 @@ if (showNearby && pendingInvites > 0)
case NotificationCategory.AutoDetect: case NotificationCategory.AutoDetect:
DrawAutoDetectNotification(notification); DrawAutoDetectNotification(notification);
break; break;
case NotificationCategory.Syncshell:
DrawSyncshellNotification(notification);
break;
default: default:
UiSharedService.DrawCard($"notification-{notification.Category}-{notification.Id}", () => UiSharedService.DrawCard($"notification-{notification.Category}-{notification.Id}", () =>
{ {
@@ -1237,6 +1243,30 @@ if (showNearby && pendingInvites > 0)
}, stretchWidth: true); }, stretchWidth: true);
} }
private void DrawSyncshellNotification(NotificationEntry notification)
{
UiSharedService.DrawCard($"notification-syncshell-{notification.Id}", () =>
{
ImGui.TextUnformatted(notification.Title);
if (!string.IsNullOrEmpty(notification.Description))
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
ImGui.TextWrapped(notification.Description);
ImGui.PopStyleColor();
}
ImGuiHelpers.ScaledDummy(3f);
using (ImRaii.PushId($"syncshell-{notification.Id}"))
{
if (ImGui.Button("Effacer"))
{
_notificationTracker.Remove(NotificationCategory.Syncshell, notification.Id);
}
}
}, stretchWidth: true);
}
private void TriggerAcceptAutoDetectNotification(string uid) private void TriggerAcceptAutoDetectNotification(string uid)
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>

View File

@@ -11,11 +11,13 @@ using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.AutoDetect; using MareSynchronos.Services.AutoDetect;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.Notifications;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Globalization; using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MareSynchronos.MareConfiguration.Models;
namespace MareSynchronos.UI.Components.Popup; namespace MareSynchronos.UI.Components.Popup;
@@ -28,6 +30,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly SyncshellDiscoveryService _syncshellDiscoveryService; private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
private readonly NotificationTracker _notificationTracker;
private List<BannedGroupUserDto> _bannedUsers = []; private List<BannedGroupUserDto> _bannedUsers = [];
private int _multiInvites; private int _multiInvites;
private string _newPassword; private string _newPassword;
@@ -54,7 +57,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
public SyncshellAdminUI(ILogger<SyncshellAdminUI> logger, MareMediator mediator, ApiController apiController, public SyncshellAdminUI(ILogger<SyncshellAdminUI> logger, MareMediator mediator, ApiController apiController,
UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService, UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService,
GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService) GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService, NotificationTracker notificationTracker)
: base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService) : base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService)
{ {
GroupFullInfo = groupFullInfo; GroupFullInfo = groupFullInfo;
@@ -62,6 +65,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_pairManager = pairManager; _pairManager = pairManager;
_syncshellDiscoveryService = syncshellDiscoveryService; _syncshellDiscoveryService = syncshellDiscoveryService;
_notificationTracker = notificationTracker;
_isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal); _isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal);
_isModerator = GroupFullInfo.GroupUserInfo.IsModerator(); _isModerator = GroupFullInfo.GroupUserInfo.IsModerator();
_newPassword = string.Empty; _newPassword = string.Empty;
@@ -650,6 +654,11 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_autoDetectMessage = desiredVisibility _autoDetectMessage = desiredVisibility
? "La Syncshell est désormais visible dans AutoDetect." ? "La Syncshell est désormais visible dans AutoDetect."
: "La Syncshell n'est plus visible dans AutoDetect."; : "La Syncshell n'est plus visible dans AutoDetect.";
if (desiredVisibility)
{
PublishSyncshellPublicNotification();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -711,6 +720,11 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_autoDetectMessage = _autoDetectDesiredVisibility _autoDetectMessage = _autoDetectDesiredVisibility
? "Paramètres AutoDetect envoyés. La Syncshell sera visible selon le planning défini." ? "Paramètres AutoDetect envoyés. La Syncshell sera visible selon le planning défini."
: "La Syncshell n'est plus visible dans AutoDetect."; : "La Syncshell n'est plus visible dans AutoDetect.";
if (_autoDetectDesiredVisibility)
{
PublishSyncshellPublicNotification();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -744,4 +758,19 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
{ {
Mediator.Publish(new RemoveWindowMessage(this)); Mediator.Publish(new RemoveWindowMessage(this));
} }
private void PublishSyncshellPublicNotification()
{
try
{
var title = $"Syncshell publique: {GroupFullInfo.GroupAliasOrGID}";
var message = "La Syncshell est désormais visible via AutoDetect.";
Mediator.Publish(new DualNotificationMessage(title, message, NotificationType.Info, TimeSpan.FromSeconds(4)));
_notificationTracker.Upsert(NotificationEntry.SyncshellPublic(GroupFullInfo.GID, GroupFullInfo.GroupAliasOrGID));
}
catch
{
// swallow any notification errors to not break UI flow
}
}
} }

View File

@@ -174,9 +174,10 @@ public class HubFactory : MediatorSubscriberBase
}) })
.AddMessagePackProtocol(opt => .AddMessagePackProtocol(opt =>
{ {
var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance, var resolver = CompositeResolver.Create(
BuiltinResolver.Instance,
AttributeFormatterResolver.Instance, AttributeFormatterResolver.Instance,
StandardResolverAllowPrivate.Instance,
BuiltinResolver.Instance,
// replace enum resolver // replace enum resolver
DynamicEnumAsStringResolver.Instance, DynamicEnumAsStringResolver.Instance,
DynamicGenericResolver.Instance, DynamicGenericResolver.Instance,