Ajout de la gestion avancée de l'indicateur de frappe et des paramètres associés
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: f75f16fb13...d105d20507
@@ -87,6 +87,8 @@ public class MareConfig : IMareConfiguration
|
||||
public bool ExtraChatTags { get; set; } = false;
|
||||
public bool TypingIndicatorShowOnNameplates { get; set; } = true;
|
||||
public bool TypingIndicatorShowOnPartyList { get; set; } = true;
|
||||
public bool TypingIndicatorEnabled { get; set; } = true;
|
||||
public bool TypingIndicatorShowSelf { get; set; } = true;
|
||||
public TypingIndicatorBubbleSize TypingIndicatorBubbleSize { get; set; } = TypingIndicatorBubbleSize.Large;
|
||||
|
||||
public bool MareAPI { get; set; } = true;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
@@ -38,6 +39,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
private CancellationTokenSource? _typingCts;
|
||||
private bool _isTypingAnnounced;
|
||||
private DateTime _lastTypingSent = DateTime.MinValue;
|
||||
private TypingScope _lastScope = TypingScope.Unknown;
|
||||
private static readonly TimeSpan TypingIdle = TimeSpan.FromSeconds(2);
|
||||
private static readonly TimeSpan TypingResendInterval = TimeSpan.FromMilliseconds(750);
|
||||
|
||||
@@ -79,7 +81,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
if (_gameChatHooks.IsValueCreated)
|
||||
_gameChatHooks.Value!.Dispose();
|
||||
}
|
||||
public void NotifyTypingKeystroke()
|
||||
public void NotifyTypingKeystroke(TypingScope scope)
|
||||
{
|
||||
lock (_typingLock)
|
||||
{
|
||||
@@ -88,11 +90,12 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try { await _apiController.UserSetTypingState(true).ConfigureAwait(false); }
|
||||
try { await _apiController.UserSetTypingState(true, scope).ConfigureAwait(false); }
|
||||
catch (Exception ex) { _logger.LogDebug(ex, "NotifyTypingKeystroke: failed to send typing=true"); }
|
||||
});
|
||||
_isTypingAnnounced = true;
|
||||
_lastTypingSent = now;
|
||||
_lastScope = scope;
|
||||
}
|
||||
|
||||
_typingCts?.Cancel();
|
||||
@@ -105,7 +108,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
try
|
||||
{
|
||||
await Task.Delay(TypingIdle, token).ConfigureAwait(false);
|
||||
await _apiController.UserSetTypingState(false).ConfigureAwait(false);
|
||||
await _apiController.UserSetTypingState(false, _lastScope).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
@@ -140,7 +143,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try { await _apiController.UserSetTypingState(false).ConfigureAwait(false); }
|
||||
try { await _apiController.UserSetTypingState(false, _lastScope).ConfigureAwait(false); }
|
||||
catch (Exception ex) { _logger.LogDebug(ex, "ClearTypingState: failed to send typing=false"); }
|
||||
});
|
||||
_isTypingAnnounced = false;
|
||||
|
||||
@@ -10,6 +10,8 @@ using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.WebAPI;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
@@ -24,16 +26,18 @@ public sealed class ChatTypingDetectionService : IDisposable
|
||||
private readonly ApiController _apiController;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly IPartyList _partyList;
|
||||
private readonly MareConfigService _configService;
|
||||
|
||||
private string _lastChatText = string.Empty;
|
||||
private bool _isTyping;
|
||||
private bool _notifyingRemote;
|
||||
private bool _serverSupportWarnLogged;
|
||||
private bool _remoteNotificationsEnabled;
|
||||
private bool _subscribed;
|
||||
|
||||
public ChatTypingDetectionService(ILogger<ChatTypingDetectionService> logger, IFramework framework,
|
||||
IClientState clientState, IGameGui gameGui, ChatService chatService, PairManager pairManager, IPartyList partyList,
|
||||
TypingIndicatorStateService typingStateService, ApiController apiController)
|
||||
TypingIndicatorStateService typingStateService, ApiController apiController, MareConfigService configService)
|
||||
{
|
||||
_logger = logger;
|
||||
_framework = framework;
|
||||
@@ -44,17 +48,50 @@ public sealed class ChatTypingDetectionService : IDisposable
|
||||
_partyList = partyList;
|
||||
_typingStateService = typingStateService;
|
||||
_apiController = apiController;
|
||||
_configService = configService;
|
||||
|
||||
_framework.Update += OnFrameworkUpdate;
|
||||
Subscribe();
|
||||
_logger.LogInformation("ChatTypingDetectionService initialized");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_framework.Update -= OnFrameworkUpdate;
|
||||
Unsubscribe();
|
||||
ResetTypingState();
|
||||
}
|
||||
|
||||
public void SoftRestart()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("TypingDetection: soft restarting");
|
||||
Unsubscribe();
|
||||
ResetTypingState();
|
||||
_chatService.ClearTypingState();
|
||||
_typingStateService.ClearAll();
|
||||
Subscribe();
|
||||
_logger.LogInformation("TypingDetection: soft restart completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "TypingDetection: soft restart failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void Subscribe()
|
||||
{
|
||||
if (_subscribed) return;
|
||||
_framework.Update += OnFrameworkUpdate;
|
||||
_subscribed = true;
|
||||
}
|
||||
|
||||
private void Unsubscribe()
|
||||
{
|
||||
if (!_subscribed) return;
|
||||
_framework.Update -= OnFrameworkUpdate;
|
||||
_subscribed = false;
|
||||
}
|
||||
|
||||
private void OnFrameworkUpdate(IFramework framework)
|
||||
{
|
||||
try
|
||||
@@ -65,6 +102,13 @@ public sealed class ChatTypingDetectionService : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_configService.Current.TypingIndicatorEnabled)
|
||||
{
|
||||
ResetTypingState();
|
||||
_chatService.ClearTypingState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetChatInput(out var chatText) || string.IsNullOrEmpty(chatText))
|
||||
{
|
||||
ResetTypingState();
|
||||
@@ -89,7 +133,8 @@ public sealed class ChatTypingDetectionService : IDisposable
|
||||
{
|
||||
if (notifyRemote)
|
||||
{
|
||||
_chatService.NotifyTypingKeystroke();
|
||||
var scope = GetCurrentTypingScope();
|
||||
_chatService.NotifyTypingKeystroke(scope);
|
||||
_notifyingRemote = true;
|
||||
}
|
||||
|
||||
@@ -120,6 +165,35 @@ public sealed class ChatTypingDetectionService : IDisposable
|
||||
_typingStateService.SetSelfTypingLocal(false);
|
||||
}
|
||||
|
||||
private unsafe TypingScope GetCurrentTypingScope()
|
||||
{
|
||||
try
|
||||
{
|
||||
var shellModule = RaptureShellModule.Instance();
|
||||
if (shellModule == null)
|
||||
return TypingScope.Unknown;
|
||||
|
||||
var chatType = (XivChatType)shellModule->ChatType;
|
||||
switch (chatType)
|
||||
{
|
||||
case XivChatType.Say:
|
||||
case XivChatType.Shout:
|
||||
case XivChatType.Yell:
|
||||
return TypingScope.Proximity;
|
||||
case XivChatType.Party:
|
||||
return TypingScope.Party;
|
||||
case XivChatType.CrossParty:
|
||||
return TypingScope.CrossParty;
|
||||
default:
|
||||
return TypingScope.Unknown;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return TypingScope.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsIgnoredCommand(string chatText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(chatText))
|
||||
@@ -146,6 +220,11 @@ public sealed class ChatTypingDetectionService : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_configService.Current.TypingIndicatorEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var supportsTypingState = _apiController.SystemInfoDto.SupportsTypingState;
|
||||
var connected = _apiController.IsConnected;
|
||||
if (!connected || !supportsTypingState)
|
||||
|
||||
@@ -41,8 +41,8 @@ public class PartyListTypingService : DisposableMediatorSubscriberBase
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (!_configService.Current.TypingIndicatorEnabled) return;
|
||||
if (!_configService.Current.TypingIndicatorShowOnPartyList) return;
|
||||
// Build map of visible users by AliasOrUID -> UID (case-insensitive)
|
||||
var visibleByAlias = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
try
|
||||
{
|
||||
|
||||
@@ -5,24 +5,27 @@ using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposable
|
||||
{
|
||||
private sealed record TypingEntry(UserData User, DateTime FirstSeen, DateTime LastUpdate);
|
||||
private sealed record TypingEntry(UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope);
|
||||
|
||||
private readonly ConcurrentDictionary<string, TypingEntry> _typingUsers = new(StringComparer.Ordinal);
|
||||
private readonly ApiController _apiController;
|
||||
private readonly ILogger<TypingIndicatorStateService> _logger;
|
||||
private readonly MareConfigService _configService;
|
||||
private DateTime _selfTypingLast = DateTime.MinValue;
|
||||
private DateTime _selfTypingStart = DateTime.MinValue;
|
||||
private bool _selfTypingActive;
|
||||
|
||||
public TypingIndicatorStateService(ILogger<TypingIndicatorStateService> logger, MareMediator mediator, ApiController apiController)
|
||||
public TypingIndicatorStateService(ILogger<TypingIndicatorStateService> logger, MareMediator mediator, ApiController apiController, MareConfigService configService)
|
||||
{
|
||||
_logger = logger;
|
||||
_apiController = apiController;
|
||||
_configService = configService;
|
||||
Mediator = mediator;
|
||||
|
||||
mediator.Subscribe<UserTypingStateMessage>(this, OnTypingState);
|
||||
@@ -51,8 +54,19 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
|
||||
_selfTypingActive = isTyping;
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
_typingUsers.Clear();
|
||||
_selfTypingActive = false;
|
||||
_selfTypingStart = DateTime.MinValue;
|
||||
_selfTypingLast = DateTime.MinValue;
|
||||
_logger.LogDebug("TypingIndicatorStateService: cleared all typing state");
|
||||
}
|
||||
|
||||
private void OnTypingState(UserTypingStateMessage msg)
|
||||
{
|
||||
if (!_configService.Current.TypingIndicatorEnabled)
|
||||
return;
|
||||
var uid = msg.Typing.User.UID;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
@@ -74,8 +88,8 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
|
||||
else if (msg.Typing.IsTyping)
|
||||
{
|
||||
_typingUsers.AddOrUpdate(uid,
|
||||
_ => new TypingEntry(msg.Typing.User, now, now),
|
||||
(_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now));
|
||||
_ => new TypingEntry(msg.Typing.User, now, now, msg.Typing.Scope),
|
||||
(_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now, msg.Typing.Scope));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -101,7 +115,7 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
|
||||
return true;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate)> GetActiveTypers(TimeSpan maxAge)
|
||||
public IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope)> GetActiveTypers(TimeSpan maxAge)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var kvp in _typingUsers.ToArray())
|
||||
@@ -112,6 +126,6 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
|
||||
}
|
||||
}
|
||||
|
||||
return _typingUsers.ToDictionary(k => k.Key, v => (v.Value.User, v.Value.FirstSeen, v.Value.LastUpdate), StringComparer.Ordinal);
|
||||
return _typingUsers.ToDictionary(k => k.Key, v => (v.Value.User, v.Value.FirstSeen, v.Value.LastUpdate, v.Value.Scope), StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private readonly AccountRegistrationService _registerService;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly UiSharedService _uiShared;
|
||||
private readonly TypingIndicatorStateService _typingStateService;
|
||||
private readonly ChatTypingDetectionService _chatTypingDetectionService;
|
||||
private static readonly string DtrDefaultPreviewText = DtrEntry.DefaultGlyph + " 123";
|
||||
private bool _deleteAccountPopupModalShown = false;
|
||||
private string _lastTab = string.Empty;
|
||||
@@ -80,7 +82,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
FileCompactor fileCompactor, ApiController apiController,
|
||||
IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor,
|
||||
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService,
|
||||
AutoDetectSuppressionService autoDetectSuppressionService) : base(logger, mediator, "Umbra Settings", performanceCollector)
|
||||
AutoDetectSuppressionService autoDetectSuppressionService,
|
||||
TypingIndicatorStateService typingIndicatorStateService,
|
||||
ChatTypingDetectionService chatTypingDetectionService) : base(logger, mediator, "Umbra Settings", performanceCollector)
|
||||
{
|
||||
_configService = configService;
|
||||
_pairManager = pairManager;
|
||||
@@ -102,6 +106,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
_autoDetectSuppressionService = autoDetectSuppressionService;
|
||||
_fileCompactor = fileCompactor;
|
||||
_uiShared = uiShared;
|
||||
_typingStateService = typingIndicatorStateService;
|
||||
_chatTypingDetectionService = chatTypingDetectionService;
|
||||
AllowClickthrough = false;
|
||||
AllowPinning = false;
|
||||
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
|
||||
@@ -1123,8 +1129,10 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
var useNameColors = _configService.Current.UseNameColors;
|
||||
var nameColors = _configService.Current.NameColors;
|
||||
var autoPausedNameColors = _configService.Current.BlockedNameColors;
|
||||
var typingEnabled = _configService.Current.TypingIndicatorEnabled;
|
||||
var typingIndicatorNameplates = _configService.Current.TypingIndicatorShowOnNameplates;
|
||||
var typingIndicatorPartyList = _configService.Current.TypingIndicatorShowOnPartyList;
|
||||
var typingShowSelf = _configService.Current.TypingIndicatorShowSelf;
|
||||
if (ImGui.Checkbox("Coloriser les plaques de nom des paires", ref useNameColors))
|
||||
{
|
||||
_configService.Current.UseNameColors = useNameColors;
|
||||
@@ -1152,42 +1160,67 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Checkbox("Afficher la bulle de frappe sur les plaques", ref typingIndicatorNameplates))
|
||||
if (ImGui.Checkbox("Activer le système d'indicateur de frappe", ref typingEnabled))
|
||||
{
|
||||
_configService.Current.TypingIndicatorShowOnNameplates = typingIndicatorNameplates;
|
||||
_configService.Current.TypingIndicatorEnabled = typingEnabled;
|
||||
_configService.Save();
|
||||
_chatTypingDetectionService.SoftRestart();
|
||||
}
|
||||
_uiShared.DrawHelpText("Ajoute une bulle '...' sur la plaque des paires en train d'écrire.");
|
||||
_uiShared.DrawHelpText("Active ou désactive complètement l'envoi/la réception et l'affichage des bulles de frappe.");
|
||||
|
||||
using (ImRaii.Disabled(!typingIndicatorNameplates))
|
||||
using (ImRaii.Disabled(!typingEnabled))
|
||||
{
|
||||
using var indentTyping = ImRaii.PushIndent();
|
||||
var bubbleSize = _configService.Current.TypingIndicatorBubbleSize;
|
||||
TypingIndicatorBubbleSize? selectedBubbleSize = _uiShared.DrawCombo("Taille de la bulle de frappe##typingBubbleSize",
|
||||
Enum.GetValues<TypingIndicatorBubbleSize>(),
|
||||
size => size switch
|
||||
{
|
||||
TypingIndicatorBubbleSize.Small => "Petite",
|
||||
TypingIndicatorBubbleSize.Medium => "Moyenne",
|
||||
TypingIndicatorBubbleSize.Large => "Grande",
|
||||
_ => size.ToString()
|
||||
},
|
||||
null,
|
||||
bubbleSize);
|
||||
|
||||
if (selectedBubbleSize.HasValue && selectedBubbleSize.Value != bubbleSize)
|
||||
if (ImGui.Checkbox("Afficher la bulle de frappe sur les plaques", ref typingIndicatorNameplates))
|
||||
{
|
||||
_configService.Current.TypingIndicatorBubbleSize = selectedBubbleSize.Value;
|
||||
_configService.Current.TypingIndicatorShowOnNameplates = typingIndicatorNameplates;
|
||||
_configService.Save();
|
||||
}
|
||||
}
|
||||
_uiShared.DrawHelpText("Ajoute une bulle '...' sur la plaque des paires en train d'écrire.");
|
||||
|
||||
if (ImGui.Checkbox("Tracer la frappe dans la liste de groupe", ref typingIndicatorPartyList))
|
||||
{
|
||||
_configService.Current.TypingIndicatorShowOnPartyList = typingIndicatorPartyList;
|
||||
_configService.Save();
|
||||
using (ImRaii.Disabled(!typingIndicatorNameplates))
|
||||
{
|
||||
using var indentTyping = ImRaii.PushIndent();
|
||||
var bubbleSize = _configService.Current.TypingIndicatorBubbleSize;
|
||||
TypingIndicatorBubbleSize? selectedBubbleSize = _uiShared.DrawCombo("Taille de la bulle de frappe##typingBubbleSize",
|
||||
Enum.GetValues<TypingIndicatorBubbleSize>(),
|
||||
size => size switch
|
||||
{
|
||||
TypingIndicatorBubbleSize.Small => "Petite",
|
||||
TypingIndicatorBubbleSize.Medium => "Moyenne",
|
||||
TypingIndicatorBubbleSize.Large => "Grande",
|
||||
_ => size.ToString()
|
||||
},
|
||||
null,
|
||||
bubbleSize);
|
||||
|
||||
if (selectedBubbleSize.HasValue && selectedBubbleSize.Value != bubbleSize)
|
||||
{
|
||||
_configService.Current.TypingIndicatorBubbleSize = selectedBubbleSize.Value;
|
||||
_configService.Save();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Checkbox("Tracer la frappe dans la liste de groupe", ref typingIndicatorPartyList))
|
||||
{
|
||||
_configService.Current.TypingIndicatorShowOnPartyList = typingIndicatorPartyList;
|
||||
_configService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("Consigne dans les journaux quand une paire du groupe est en train d'écrire (bulle visuelle ultérieure).");
|
||||
|
||||
if (ImGui.Checkbox("Afficher ma propre bulle", ref typingShowSelf))
|
||||
{
|
||||
_configService.Current.TypingIndicatorShowSelf = typingShowSelf;
|
||||
_configService.Save();
|
||||
}
|
||||
_uiShared.DrawHelpText("Affiche votre propre bulle lorsque vous tapez (utile pour test/retour visuel).");
|
||||
|
||||
if (ImGui.Button("Redémarrer le système de frappe"))
|
||||
{
|
||||
_chatTypingDetectionService.SoftRestart();
|
||||
_guiHookService.RequestRedraw();
|
||||
}
|
||||
_uiShared.DrawHelpText("Vide l'état local et réinitialise les timers sans redémarrer le plugin.");
|
||||
}
|
||||
_uiShared.DrawHelpText("Consigne dans les journaux quand une paire du groupe est en train d'écrire (bulle visuelle ultérieure).");
|
||||
|
||||
if (ImGui.Checkbox("Show separate Visible group", ref showVisibleSeparate))
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Data.Extensions;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
@@ -75,6 +76,10 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
if (!_clientState.IsLoggedIn)
|
||||
return;
|
||||
|
||||
var typingEnabled = _configService.Current.TypingIndicatorEnabled;
|
||||
if (!typingEnabled)
|
||||
return;
|
||||
|
||||
var showParty = _configService.Current.TypingIndicatorShowOnPartyList;
|
||||
var showNameplates = _configService.Current.TypingIndicatorShowOnNameplates;
|
||||
|
||||
@@ -97,14 +102,16 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void DrawPartyIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate)> activeTypers,
|
||||
private unsafe void DrawPartyIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope)> activeTypers,
|
||||
bool selfActive, DateTime now, DateTime selfStart, DateTime selfLast)
|
||||
{
|
||||
var partyAddon = (AtkUnitBase*)_gameGui.GetAddonByName("_PartyList", 1).Address;
|
||||
if (partyAddon == null || !partyAddon->IsVisible)
|
||||
return;
|
||||
|
||||
var showSelf = _configService.Current.TypingIndicatorShowSelf;
|
||||
if (selfActive
|
||||
&& showSelf
|
||||
&& (now - selfStart) >= TypingDisplayDelay
|
||||
&& (now - selfLast) <= TypingDisplayFade)
|
||||
{
|
||||
@@ -180,14 +187,16 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f)));
|
||||
}
|
||||
|
||||
private unsafe void DrawNameplateIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate)> activeTypers,
|
||||
private unsafe void DrawNameplateIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope)> activeTypers,
|
||||
bool selfActive, DateTime now, DateTime selfStart, DateTime selfLast)
|
||||
{
|
||||
var iconWrap = _textureProvider.GetFromGameIcon(NameplateIconId).GetWrapOrEmpty();
|
||||
if (iconWrap == null || iconWrap.Handle == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var showSelf = _configService.Current.TypingIndicatorShowSelf;
|
||||
if (selfActive
|
||||
&& showSelf
|
||||
&& _clientState.LocalPlayer != null
|
||||
&& (now - selfStart) >= TypingDisplayDelay
|
||||
&& (now - selfLast) <= TypingDisplayFade)
|
||||
@@ -212,11 +221,22 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
var pairName = pair?.PlayerName ?? entry.User.AliasOrUID ?? string.Empty;
|
||||
var pairIdent = pair?.Ident ?? string.Empty;
|
||||
var isPartyMember = IsPartyMember(objectId, pairName);
|
||||
|
||||
// Enforce party-only visibility when the scope is Party/CrossParty
|
||||
if (entry.Scope is TypingScope.Party or TypingScope.CrossParty)
|
||||
{
|
||||
if (!isPartyMember)
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: suppressed non-party bubble for {uid} due to scope={scope}", uid, entry.Scope);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var isRelevantMember = IsPlayerRelevant(pair, isPartyMember);
|
||||
|
||||
if (objectId != uint.MaxValue && objectId != 0 && TryDrawNameplateBubble(drawList, iconWrap, objectId))
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: drew nameplate bubble for {uid} (objectId={objectId})", uid, objectId);
|
||||
_typedLogger.LogTrace("TypingIndicator: drew nameplate bubble for {uid} (objectId={objectId}, scope={scope})", uid, objectId, entry.Scope);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,13 +246,20 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
if (!isRelevantMember && !isNearby)
|
||||
continue;
|
||||
|
||||
// For Party/CrossParty scope, do not draw fallback world icon for non-party even if nearby
|
||||
if (entry.Scope is TypingScope.Party or TypingScope.CrossParty)
|
||||
{
|
||||
if (!isPartyMember)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pair == null)
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: no pair found for {uid}, attempting fallback", uid);
|
||||
}
|
||||
|
||||
_typedLogger.LogTrace("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident})",
|
||||
uid, objectId, pairName, pairIdent);
|
||||
_typedLogger.LogTrace("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident}, scope={scope})",
|
||||
uid, objectId, pairName, pairIdent, entry.Scope);
|
||||
|
||||
if (hasWorldPosition)
|
||||
{
|
||||
|
||||
@@ -103,6 +103,21 @@ public partial class ApiController
|
||||
await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task UserSetTypingState(bool isTyping, MareSynchronos.API.Data.Enum.TypingScope scope)
|
||||
{
|
||||
CheckConnection();
|
||||
try
|
||||
{
|
||||
await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping, scope).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// fallback for older servers without scope support
|
||||
Logger.LogDebug(ex, "UserSetTypingState(scope) not supported on server, falling back to legacy call");
|
||||
await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PushCharacterDataInternal(CharacterData character, List<UserData> visibleCharacters)
|
||||
{
|
||||
Logger.LogInformation("Pushing character data for {hash} to {charas}", character.DataHash.Value, string.Join(", ", visibleCharacters.Select(c => c.AliasOrUID)));
|
||||
|
||||
Reference in New Issue
Block a user