Fix UI + Amélioration AutoDetect & Self Analyse + Update Penumbra API

This commit is contained in:
2025-10-12 12:42:31 +02:00
parent d4a46910f9
commit d225a3844a
14 changed files with 763 additions and 95 deletions

View File

@@ -1,3 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Colors;
using Dalamud.Game.ClientState.Objects.SubKinds;
@@ -5,13 +9,15 @@ using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Services;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Logging;
using System.Numerics;
using System.Globalization;
using System.Text;
using MareSynchronos.Services;
using MareSynchronos.Services.AutoDetect;
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
namespace MareSynchronos.UI;
@@ -20,13 +26,15 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
private readonly MareConfigService _configService;
private readonly DalamudUtilService _dalamud;
private readonly IObjectTable _objectTable;
private readonly Services.AutoDetect.AutoDetectRequestService _requestService;
private readonly AutoDetectRequestService _requestService;
private readonly NearbyPendingService _pendingService;
private readonly PairManager _pairManager;
private List<Services.Mediator.NearbyEntry> _entries = new();
private readonly HashSet<string> _acceptInFlight = new(StringComparer.Ordinal);
public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
Services.AutoDetect.AutoDetectRequestService requestService, PairManager pairManager,
AutoDetectRequestService requestService, NearbyPendingService pendingService, PairManager pairManager,
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "AutoDetect", performanceCollectorService)
{
@@ -34,6 +42,7 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
_dalamud = dalamudUtilService;
_objectTable = objectTable;
_requestService = requestService;
_pendingService = pendingService;
_pairManager = pairManager;
Flags |= ImGuiWindowFlags.NoScrollbar;
@@ -53,9 +62,141 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
{
using var idScope = ImRaii.PushId("autodetect-ui");
var incomingInvites = _pendingService.Pending.ToList();
var outgoingInvites = _requestService.GetPendingRequestsSnapshot();
Vector4 accent = UiSharedService.AccentColor;
if (accent.W <= 0f) accent = ImGuiColors.ParsedPurple;
Vector4 inactiveTab = new(accent.X * 0.45f, accent.Y * 0.45f, accent.Z * 0.45f, Math.Clamp(accent.W + 0.15f, 0f, 1f));
Vector4 hoverTab = UiSharedService.AccentHoverColor;
using var tabs = ImRaii.TabBar("AutoDetectTabs");
if (!tabs.Success) return;
var incomingCount = incomingInvites.Count;
DrawStyledTab($"Invitations ({incomingCount})", accent, inactiveTab, hoverTab, () =>
{
DrawInvitationsTab(incomingInvites, outgoingInvites);
});
DrawStyledTab("Proximité", accent, inactiveTab, hoverTab, DrawNearbyTab);
using (ImRaii.Disabled(true))
{
DrawStyledTab("Syncshell", accent, inactiveTab, hoverTab, () =>
{
UiSharedService.ColorTextWrapped("Disponible prochainement.", ImGuiColors.DalamudGrey3);
}, true);
}
}
private static void DrawStyledTab(string label, Vector4 accent, Vector4 inactive, Vector4 hover, Action draw, bool disabled = false)
{
var tabColor = disabled ? ImGuiColors.DalamudGrey3 : inactive;
var tabHover = disabled ? ImGuiColors.DalamudGrey3 : hover;
var tabActive = disabled ? ImGuiColors.DalamudGrey2 : accent;
using var baseColor = ImRaii.PushColor(ImGuiCol.Tab, tabColor);
using var hoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, tabHover);
using var activeColor = ImRaii.PushColor(ImGuiCol.TabActive, tabActive);
using var activeText = ImRaii.PushColor(ImGuiCol.Text, disabled ? ImGuiColors.DalamudGrey2 : Vector4.One, false);
using var tab = ImRaii.TabItem(label);
if (tab.Success)
{
draw();
}
}
private void DrawInvitationsTab(List<KeyValuePair<string, string>> incomingInvites, IReadOnlyCollection<AutoDetectRequestService.PendingRequestInfo> outgoingInvites)
{
if (incomingInvites.Count == 0 && outgoingInvites.Count == 0)
{
UiSharedService.ColorTextWrapped("Aucune invitation en attente. Cette page regroupera les demandes reçues et celles que vous avez envoyées.", ImGuiColors.DalamudGrey3);
return;
}
if (incomingInvites.Count == 0)
{
UiSharedService.ColorTextWrapped("Vous n'avez aucune invitation de pair en attente pour le moment.", ImGuiColors.DalamudGrey3);
}
ImGuiHelpers.ScaledDummy(4);
float leftWidth = Math.Max(220f * ImGuiHelpers.GlobalScale, ImGui.CalcTextSize("Invitations reçues (00)").X + ImGui.GetStyle().FramePadding.X * 4f);
var avail = ImGui.GetContentRegionAvail();
ImGui.BeginChild("incoming-requests", new Vector2(leftWidth, avail.Y), true);
ImGui.TextColored(ImGuiColors.DalamudOrange, $"Invitations reçues ({incomingInvites.Count})");
ImGui.Separator();
if (incomingInvites.Count == 0)
{
ImGui.TextDisabled("Aucune invitation reçue.");
}
else
{
foreach (var (uid, name) in incomingInvites.OrderBy(k => k.Value, StringComparer.OrdinalIgnoreCase))
{
using var id = ImRaii.PushId(uid);
bool processing = _acceptInFlight.Contains(uid);
ImGui.TextUnformatted(name);
ImGui.TextDisabled(uid);
if (processing)
{
ImGui.TextDisabled("Traitement en cours...");
}
else
{
if (ImGui.Button("Accepter"))
{
TriggerAccept(uid);
}
ImGui.SameLine();
if (ImGui.Button("Refuser"))
{
_pendingService.Remove(uid);
}
}
ImGui.Separator();
}
}
ImGui.EndChild();
ImGui.SameLine();
ImGui.BeginChild("outgoing-requests", new Vector2(0, avail.Y), true);
ImGui.TextColored(ImGuiColors.DalamudOrange, $"Invitations envoyées ({outgoingInvites.Count})");
ImGui.Separator();
if (outgoingInvites.Count == 0)
{
ImGui.TextDisabled("Aucune invitation envoyée en attente.");
ImGui.EndChild();
return;
}
foreach (var info in outgoingInvites.OrderByDescending(i => i.SentAt))
{
using var id = ImRaii.PushId(info.Key);
ImGui.TextUnformatted(info.TargetDisplayName);
if (!string.IsNullOrEmpty(info.Uid))
{
ImGui.TextDisabled(info.Uid);
}
ImGui.TextDisabled($"Envoyée il y a {FormatDuration(DateTime.UtcNow - info.SentAt)}");
if (ImGui.Button("Retirer"))
{
_requestService.RemovePendingRequestByKey(info.Key);
}
UiSharedService.AttachToolTip("Retire uniquement cette entrée locale de suivi.");
ImGui.Separator();
}
ImGui.EndChild();
}
private void DrawNearbyTab()
{
if (!_configService.Current.EnableAutoDetectDiscovery)
{
UiSharedService.ColorTextWrapped("Nearby detection is disabled. Enable it in Settings to start detecting nearby Umbra users.", ImGuiColors.DalamudYellow);
UiSharedService.ColorTextWrapped("AutoDetect est désactivé. Activez-le dans les paramètres pour détecter les utilisateurs Umbra à proximité.", ImGuiColors.DalamudYellow);
ImGuiHelpers.ScaledDummy(6);
}
@@ -134,26 +275,6 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
_entries = msg.Entries;
}
private List<Services.Mediator.NearbyEntry> BuildLocalSnapshot(int maxDist)
{
var list = new List<Services.Mediator.NearbyEntry>();
var local = _dalamud.GetPlayerCharacter();
var localPos = local?.Position ?? Vector3.Zero;
for (int i = 0; i < 200; i += 2)
{
var obj = _objectTable[i];
if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (local != null && obj.Address == local.Address) continue;
float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position);
if (!float.IsNaN(dist) && dist > maxDist) continue;
string name = obj.Name.ToString();
ushort worldId = 0;
if (obj is IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId;
list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null, null, null));
}
return list;
}
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
{
try
@@ -194,4 +315,37 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
}
return sb.ToString();
}
private void TriggerAccept(string uid)
{
if (!_acceptInFlight.Add(uid)) return;
Task.Run(async () =>
{
try
{
bool ok = await _pendingService.AcceptAsync(uid).ConfigureAwait(false);
if (!ok)
{
Mediator.Publish(new NotificationMessage("AutoDetect", $"Impossible d'accepter l'invitation {uid}.", NotificationType.Warning, TimeSpan.FromSeconds(5)));
}
}
finally
{
_acceptInFlight.Remove(uid);
}
});
}
private static string FormatDuration(TimeSpan span)
{
if (span.TotalMinutes >= 1)
{
var minutes = Math.Max(1, (int)Math.Round(span.TotalMinutes));
return minutes == 1 ? "1 minute" : $"{minutes} minutes";
}
var seconds = Math.Max(1, (int)Math.Round(span.TotalSeconds));
return seconds == 1 ? "1 seconde" : $"{seconds} secondes";
}
}

View File

@@ -169,6 +169,16 @@ public sealed class ChangelogUi : WindowMediatorSubscriberBase
{
return new List<ChangelogEntry>
{
new(new Version(0, 1, 9, 4), "0.1.9.4", new List<ChangelogLine>
{
new("Réécriture complète de la bulle de frappe avec la possibilité de choisir la taille de la bulle."),
new("Désactivation de l'AutoDetect en zone instanciée."),
new("Réécriture interface AutoDetect pour acceuillir les invitations en attente et préparer les synchsells publiques."),
new("Amélioration de la compréhension des activations / désactivations des préférences de synchronisation par défaut."),
new("Mise en avant du Self Analyse avec une alerte lorsqu'un seuil de donnée a été atteint."),
new("Ajout de l'alerte de la non-compatibilité du plugin Chat2."),
new("Divers fix de l'interface."),
}),
new(new Version(0, 1, 9, 3), "0.1.9.3", new List<ChangelogLine>
{
new("Correctif de l'affichage de la bulle de frappe quand l'interface est à + de 100%."),

View File

@@ -62,7 +62,6 @@ public class CompactUi : WindowMediatorSubscriberBase
private bool _showSyncShells;
private bool _wasOpen;
private bool _nearbyOpen = true;
private bool _pendingOpen = true;
private List<Services.Mediator.NearbyEntry> _nearbyEntries = new();
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
@@ -297,6 +296,10 @@ public class CompactUi : WindowMediatorSubscriberBase
bool animsDisabled = _configService.Current.DefaultDisableAnimations;
bool vfxDisabled = _configService.Current.DefaultDisableVfx;
bool showNearby = _configService.Current.EnableAutoDetectDiscovery;
int pendingInvites = _nearbyPending.Pending.Count;
const string nearbyLabel = "AutoDetect";
var soundIcon = soundsDisabled ? FontAwesomeIcon.VolumeMute : FontAwesomeIcon.VolumeUp;
var animIcon = animsDisabled ? FontAwesomeIcon.WindowClose : FontAwesomeIcon.Running;
@@ -306,7 +309,7 @@ public class CompactUi : WindowMediatorSubscriberBase
float audioWidth = _uiSharedService.GetIconTextButtonSize(soundIcon, soundLabel);
float animWidth = _uiSharedService.GetIconTextButtonSize(animIcon, animLabel);
float vfxWidth = _uiSharedService.GetIconTextButtonSize(vfxIcon, vfxLabel);
float nearbyWidth = showNearby ? _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, "Nearby") : 0f;
float nearbyWidth = showNearby ? _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, pendingInvites > 0 ? $"{nearbyLabel} ({pendingInvites})" : nearbyLabel) : 0f;
int buttonCount = 3 + (showNearby ? 1 : 0);
float totalWidth = audioWidth + animWidth + vfxWidth + nearbyWidth + spacing * (buttonCount - 1);
float available = ImGui.GetContentRegionAvail().X;
@@ -346,11 +349,15 @@ public class CompactUi : WindowMediatorSubscriberBase
if (showNearby)
{
ImGui.SameLine(0, spacing);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", nearbyWidth))
var autodetectLabel = pendingInvites > 0 ? $"{nearbyLabel} ({pendingInvites})" : nearbyLabel;
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, autodetectLabel, nearbyWidth))
{
Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
}
UiSharedService.AttachToolTip("Ouvrir la détection de proximité");
string tooltip = pendingInvites > 0
? string.Format("Vous avez {0} invitation{1} reçue. Ouvrez l\'interface AutoDetect pour y répondre.", pendingInvites, pendingInvites > 1 ? "s" : string.Empty)
: "Ouvrir les outils AutoDetect (invitations et proximité).\n\nLes demandes reçues sont listées dans l\'onglet 'Invitations'.";
UiSharedService.AttachToolTip(tooltip);
}
}
ImGui.Separator();
@@ -524,48 +531,11 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false);
try
{
// intentionally left blank; pending requests handled in collapsible section below
}
catch { }
var pendingCount = _nearbyPending?.Pending.Count ?? 0;
if (pendingCount > 0)
{
using (ImRaii.PushId("group-Pending"))
{
var icon = _pendingOpen ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
_uiSharedService.IconText(icon);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _pendingOpen = !_pendingOpen;
ImGui.SameLine();
ImGui.TextUnformatted($"En attente ({pendingCount})");
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _pendingOpen = !_pendingOpen;
if (_pendingOpen)
{
ImGui.Indent();
foreach (var kv in _nearbyPending!.Pending)
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted($"{kv.Value} [{kv.Key}]");
ImGui.SameLine();
if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
{
_ = _nearbyPending.AcceptAsync(kv.Key);
}
UiSharedService.AttachToolTip("Accept and add as pair");
ImGui.SameLine();
if (_uiSharedService.IconButton(FontAwesomeIcon.Times))
{
_nearbyPending.Remove(kv.Key);
}
UiSharedService.AttachToolTip("Dismiss request");
}
ImGui.Unindent();
ImGui.Separator();
}
}
UiSharedService.ColorTextWrapped("Invitation AutoDetect en attente. Ouvrez l\'interface AutoDetect pour gérer vos demandes.", ImGuiColors.DalamudYellow);
ImGuiHelpers.ScaledDummy(4);
}
var onlineUsers = users.Where(u => u.UserPair!.OtherPermissions.IsPaired() && (u.IsOnline || u.UserPair!.OwnPermissions.IsPaused())).Select(c => new DrawUserPair("Online" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager)).ToList();

View File

@@ -14,6 +14,7 @@ using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services;
using MareSynchronos.Services.AutoDetect;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.WebAPI;
@@ -44,6 +45,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly PairManager _pairManager;
private readonly ChatService _chatService;
private readonly GuiHookService _guiHookService;
private readonly AutoDetectSuppressionService _autoDetectSuppressionService;
private readonly PerformanceCollectorService _performanceCollector;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly PlayerPerformanceService _playerPerformanceService;
@@ -77,7 +79,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
FileCacheManager fileCacheManager,
FileCompactor fileCompactor, ApiController apiController,
IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor,
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService) : base(logger, mediator, "Umbra Settings", performanceCollector)
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService,
AutoDetectSuppressionService autoDetectSuppressionService) : base(logger, mediator, "Umbra Settings", performanceCollector)
{
_configService = configService;
_pairManager = pairManager;
@@ -96,6 +99,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
_cacheMonitor = cacheMonitor;
_dalamudUtilService = dalamudUtilService;
_registerService = registerService;
_autoDetectSuppressionService = autoDetectSuppressionService;
_fileCompactor = fileCompactor;
_uiShared = uiShared;
AllowClickthrough = false;
@@ -214,26 +218,34 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Separator();
_uiShared.BigText("AutoDetect");
bool isAutoDetectSuppressed = _autoDetectSuppressionService?.IsSuppressed ?? false;
bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery;
if (ImGui.Checkbox("Enable Nearby detection (beta)", ref enableDiscovery))
using (ImRaii.Disabled(isAutoDetectSuppressed))
{
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
_configService.Save();
// notify services of toggle
Mediator.Publish(new NearbyDetectionToggled(enableDiscovery));
// if Nearby is turned OFF, force Allow Pair Requests OFF as well
if (!enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
if (ImGui.Checkbox("Enable AutoDetect", ref enableDiscovery))
{
_configService.Current.AllowAutoDetectPairRequests = false;
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
_configService.Save();
Mediator.Publish(new AllowPairRequestsToggled(false));
// notify services of toggle
Mediator.Publish(new NearbyDetectionToggled(enableDiscovery));
// if Nearby is turned OFF, force Allow Pair Requests OFF as well
if (!enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
{
_configService.Current.AllowAutoDetectPairRequests = false;
_configService.Save();
Mediator.Publish(new AllowPairRequestsToggled(false));
}
}
if (isAutoDetectSuppressed && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
UiSharedService.AttachToolTip("AutoDetect est temporairement désactivé dans cette zone instanciée.");
}
}
// Allow Pair Requests is disabled when Nearby is OFF
using (ImRaii.Disabled(!enableDiscovery))
using (ImRaii.Disabled(isAutoDetectSuppressed || !enableDiscovery))
{
bool allowRequests = _configService.Current.AllowAutoDetectPairRequests;
if (ImGui.Checkbox("Allow pair requests", ref allowRequests))
@@ -246,15 +258,19 @@ public class SettingsUi : WindowMediatorSubscriberBase
// user-facing info toast
Mediator.Publish(new NotificationMessage(
"Nearby Detection",
allowRequests ? "Pair requests enabled: others can invite you." : "Pair requests disabled: others cannot invite you.",
"AutoDetect",
allowRequests ? "Invitations entrantes autorisées : les autres peuvent vous inviter." : "Invitations entrantes désactivées : les autres ne peuvent pas vous inviter.",
NotificationType.Info,
default));
}
if (isAutoDetectSuppressed && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
UiSharedService.AttachToolTip("AutoDetect est temporairement désactivé dans cette zone instanciée.");
}
}
// Radius only available when both Nearby and Allow Pair Requests are ON
if (enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
if (!isAutoDetectSuppressed && enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
{
ImGui.Indent();
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
@@ -266,6 +282,10 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
ImGui.Unindent();
}
else if (isAutoDetectSuppressed)
{
UiSharedService.ColorTextWrapped("AutoDetect est verrouillé tant que vous restez dans une zone instanciée.", ImGuiColors.DalamudYellow);
}
ImGui.Separator();
_uiShared.BigText("Transfer UI");