Compare commits

..

31 Commits

Author SHA1 Message Date
defnotken
eeafe1d3d7 dev bump
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 34s
2025-10-21 16:28:05 -05:00
defnotken
cdf34c82bd Merge branch '1.12.3' into dev
Some checks failed
Tag and Release Lightless / tag-and-release (push) Has been cancelled
2025-10-21 16:27:11 -05:00
b5bdededae Merge pull request 'Client API changes' (#72) from banner-api-changes into 1.12.3
Reviewed-on: #72
Reviewed-by: defnotken <defnotken@noreply.git.lightless-sync.org>
2025-10-21 22:51:29 +02:00
CakeAndBanana
487156e4f9 Submodule update 2025-10-21 22:48:26 +02:00
90d8f691d2 Merge branch '1.12.3' into banner-api-changes 2025-10-21 22:41:29 +02:00
CakeAndBanana
764bb8bae2 API changes 2025-10-21 22:38:12 +02:00
defnotken
6f2213171c dev bump
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 34s
2025-10-21 13:47:26 -05:00
defnotken
da47d3be01 Merge branch '1.12.3' into dev 2025-10-21 13:46:41 -05:00
5d2c58bf3e Merge pull request 'some caching stuff and bug fixes' (#71) from some-garbage-optimization into 1.12.3
Reviewed-on: #71
2025-10-21 20:46:15 +02:00
azyges
6bb00c50d8 improve logging fallback 2025-10-22 03:33:51 +09:00
azyges
1a89c2caee some caching stuff and bug fixes 2025-10-22 03:20:13 +09:00
defnotken
6e129f6a1d dev building
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 34s
2025-10-21 11:23:40 -05:00
defnotken
1e97f27cb8 Merge branch '1.12.3' of https://git.lightless-sync.org/Lightless-Sync/LightlessClient into 1.12.3 2025-10-21 11:22:31 -05:00
defnotken
7aadbcec10 Wording changes 2025-10-21 11:22:19 -05:00
choco
a32ac02c6d download notification stuck fix 2025-10-21 09:59:30 +02:00
f48698373b Merge pull request 'notification-cleanup' (#70) from notification-cleanup into 1.12.3
Reviewed-on: #70
Reviewed-by: defnotken <defnotken@noreply.git.lightless-sync.org>
2025-10-20 21:28:15 +02:00
ee20b6fa5f version 1.12.3 2025-10-20 21:25:28 +02:00
f11741225b Merge branch 'dev' into notification-cleanup 2025-10-20 21:16:47 +02:00
choco
147baa4c1b api cleanup, decline message on notification decline 2025-10-20 21:16:30 +02:00
choco
4f5ef8ff4b type cleanup 2025-10-20 14:51:10 +02:00
choco
fae6d31792 Merge remote-tracking branch 'origin/notification-cleanup' into notification-cleanup 2025-10-20 14:32:29 +02:00
choco
b4dd0ee0e1 type cleanup 2025-10-20 14:32:21 +02:00
f5458c7f97 Delete CONTRIBUTING.md 2025-10-20 14:05:51 +02:00
923f118a47 Delete DEVELOPMENT.md 2025-10-20 14:05:45 +02:00
choco
0cb71e5444 service cleanups, containing logic directly now 2025-10-20 14:00:54 +02:00
defnotken
268fd471fe welcome screen fix bump
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 34s
2025-10-19 15:50:02 -05:00
defnotken
d517a21f5d Merge branch '1.12.3' into dev 2025-10-19 15:49:16 -05:00
cac94374d9 Merge pull request 'patch-notes' (#69) from patch-notes into 1.12.3
Reviewed-on: #69
2025-10-19 22:48:44 +02:00
choco
b513e0555b temp menu removal 2025-10-19 22:46:36 +02:00
choco
de8c9cf035 Merge remote-tracking branch 'origin/patch-notes' into patch-notes 2025-10-19 22:43:58 +02:00
choco
7f8872cbe0 added open changelog button to settings title menu 2025-10-19 22:43:45 +02:00
25 changed files with 507 additions and 276 deletions

View File

@@ -25,6 +25,7 @@ changelog:
- "More customizable notification options." - "More customizable notification options."
- "Perfomance limiter shows as notifications." - "Perfomance limiter shows as notifications."
- "All notifications can be configured or disabled in Settings → Notifications." - "All notifications can be configured or disabled in Settings → Notifications."
- "Cleaning up notifications implementation"
- number: "Bugfixes" - number: "Bugfixes"
icon: "" icon: ""
items: items:

View File

@@ -51,12 +51,12 @@ credits:
role: "Height offset integration" role: "Height offset integration"
- name: "Honorific Team" - name: "Honorific Team"
role: "Title system integration" role: "Title system integration"
- name: "Moodles Team" - name: "Glyceri"
role: "Status effect integration" role: "Moodles - Status effect integration"
- name: "PetNicknames Team" - name: "Glyceri"
role: "Pet naming integration" role: "PetNicknames - Pet naming integration"
- name: "Brio Team" - name: "Minmoose"
role: "GPose enhancement integration" role: "Brio - GPose enhancement integration"
- category: "Special Thanks" - category: "Special Thanks"
items: items:

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>1.12.2.5</Version> <Version>1.12.2.9</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl> <PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>

View File

@@ -255,9 +255,9 @@ public sealed class Plugin : IDalamudPlugin
collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>())); collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<DalamudUtilService>())); collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<DalamudUtilService>()));
collection.AddScoped<IPopupHandler, BanUserPopupHandler>(); collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
collection.AddScoped<WindowMediatorSubscriberBase, LightlessNotificationUI>((s) => collection.AddScoped<WindowMediatorSubscriberBase, LightlessNotificationUi>((s) =>
new LightlessNotificationUI( new LightlessNotificationUi(
s.GetRequiredService<ILogger<LightlessNotificationUI>>(), s.GetRequiredService<ILogger<LightlessNotificationUi>>(),
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<LightlessMediator>(),
s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<PerformanceCollectorService>(),
s.GetRequiredService<LightlessConfigService>())); s.GetRequiredService<LightlessConfigService>()));
@@ -269,8 +269,7 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<WindowSystem>(), s.GetServices<WindowMediatorSubscriberBase>(), s.GetRequiredService<WindowSystem>(), s.GetServices<WindowMediatorSubscriberBase>(),
s.GetRequiredService<UiFactory>(), s.GetRequiredService<UiFactory>(),
s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<FileDialogManager>(),
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<LightlessMediator>()));
s.GetRequiredService<NotificationService>()));
collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(), collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(),
s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<ApiController>(),
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<LightlessConfigService>())); s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<LightlessConfigService>()));

View File

@@ -6,7 +6,11 @@ using LightlessSync.UI;
using LightlessSync.Utils; using LightlessSync.Utils;
using Lumina.Data.Files; using Lumina.Data.Files;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace LightlessSync.Services; namespace LightlessSync.Services;
public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
@@ -16,6 +20,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
private CancellationTokenSource? _analysisCts; private CancellationTokenSource? _analysisCts;
private CancellationTokenSource _baseAnalysisCts = new(); private CancellationTokenSource _baseAnalysisCts = new();
private string _lastDataHash = string.Empty; private string _lastDataHash = string.Empty;
private CharacterAnalysisSummary _latestSummary = CharacterAnalysisSummary.Empty;
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, LightlessMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer) public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, LightlessMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
: base(logger, mediator) : base(logger, mediator)
@@ -34,6 +39,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
public bool IsAnalysisRunning => _analysisCts != null; public bool IsAnalysisRunning => _analysisCts != null;
public int TotalFiles { get; internal set; } public int TotalFiles { get; internal set; }
internal Dictionary<ObjectKind, Dictionary<string, FileDataEntry>> LastAnalysis { get; } = []; internal Dictionary<ObjectKind, Dictionary<string, FileDataEntry>> LastAnalysis { get; } = [];
public CharacterAnalysisSummary LatestSummary => _latestSummary;
public void CancelAnalyze() public void CancelAnalyze()
{ {
@@ -80,6 +86,8 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
} }
} }
RecalculateSummary();
Mediator.Publish(new CharacterDataAnalyzedMessage()); Mediator.Publish(new CharacterDataAnalyzedMessage());
_analysisCts.CancelDispose(); _analysisCts.CancelDispose();
@@ -137,11 +145,39 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
LastAnalysis[obj.Key] = data; LastAnalysis[obj.Key] = data;
} }
RecalculateSummary();
Mediator.Publish(new CharacterDataAnalyzedMessage()); Mediator.Publish(new CharacterDataAnalyzedMessage());
_lastDataHash = charaData.DataHash.Value; _lastDataHash = charaData.DataHash.Value;
} }
private void RecalculateSummary()
{
var builder = ImmutableDictionary.CreateBuilder<ObjectKind, CharacterAnalysisObjectSummary>();
foreach (var (objectKind, entries) in LastAnalysis)
{
long totalTriangles = 0;
long texOriginalBytes = 0;
long texCompressedBytes = 0;
foreach (var entry in entries.Values)
{
totalTriangles += entry.Triangles;
if (string.Equals(entry.FileType, "tex", StringComparison.OrdinalIgnoreCase))
{
texOriginalBytes += entry.OriginalSize;
texCompressedBytes += entry.CompressedSize;
}
}
builder[objectKind] = new CharacterAnalysisObjectSummary(entries.Count, totalTriangles, texOriginalBytes, texCompressedBytes);
}
_latestSummary = new CharacterAnalysisSummary(builder.ToImmutable());
}
private void PrintAnalysis() private void PrintAnalysis()
{ {
if (LastAnalysis.Count == 0) return; if (LastAnalysis.Count == 0) return;
@@ -232,4 +268,24 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
} }
}); });
} }
}
public readonly record struct CharacterAnalysisObjectSummary(int EntryCount, long TotalTriangles, long TexOriginalBytes, long TexCompressedBytes)
{
public bool HasEntries => EntryCount > 0;
}
public sealed class CharacterAnalysisSummary
{
public static CharacterAnalysisSummary Empty { get; } =
new(ImmutableDictionary<ObjectKind, CharacterAnalysisObjectSummary>.Empty);
internal CharacterAnalysisSummary(IImmutableDictionary<ObjectKind, CharacterAnalysisObjectSummary> objects)
{
Objects = objects;
}
public IImmutableDictionary<ObjectKind, CharacterAnalysisObjectSummary> Objects { get; }
public bool HasData => Objects.Any(kvp => kvp.Value.HasEntries);
} }

View File

@@ -108,7 +108,9 @@ public record OpenCharaDataHubWithFilterMessage(UserData UserData) : MessageBase
public record EnableBroadcastMessage(string HashedCid, bool Enabled) : MessageBase; public record EnableBroadcastMessage(string HashedCid, bool Enabled) : MessageBase;
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 PairRequestReceivedMessage(string HashedCid, string Message) : MessageBase;
public record PairRequestsUpdatedMessage : MessageBase; public record PairRequestsUpdatedMessage : MessageBase;
public record PairDownloadStatusMessage(List<(string PlayerName, float Progress, string Status)> DownloadStatus, int QueueWaiting) : 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

View File

@@ -45,7 +45,9 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage); Mediator.Subscribe<NotificationMessage>(this, HandleNotificationMessage);
Mediator.Subscribe<PairRequestReceivedMessage>(this, HandlePairRequestReceived);
Mediator.Subscribe<PairRequestsUpdatedMessage>(this, HandlePairRequestsUpdated); Mediator.Subscribe<PairRequestsUpdatedMessage>(this, HandlePairRequestsUpdated);
Mediator.Subscribe<PairDownloadStatusMessage>(this, HandlePairDownloadStatus);
Mediator.Subscribe<PerformanceNotificationMessage>(this, HandlePerformanceNotification); Mediator.Subscribe<PerformanceNotificationMessage>(this, HandlePerformanceNotification);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -293,33 +295,8 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
return actions; 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 message = BuildPairDownloadMessage(userDownloads, queueWaiting);
var notification = new LightlessNotification private string BuildPairDownloadMessage(List<(string PlayerName, float Progress, string Status)> userDownloads,
{
Id = "pair_download_progress",
Title = "Downloading Pair Data",
Message = message,
Type = NotificationType.Download,
Duration = TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
ShowProgress = true,
Progress = totalProgress
};
Mediator.Publish(new LightlessNotificationMessage(notification));
if (AreAllDownloadsCompleted(userDownloads))
{
DismissPairDownloadNotification();
}
}
private string BuildPairDownloadMessage(List<(string playerName, float progress, string status)> userDownloads,
int queueWaiting) int queueWaiting)
{ {
var messageParts = new List<string>(); var messageParts = new List<string>();
@@ -331,7 +308,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
if (userDownloads.Count > 0) if (userDownloads.Count > 0)
{ {
var completedCount = userDownloads.Count(x => x.progress >= 1.0f); var completedCount = userDownloads.Count(x => x.Progress >= 1.0f);
messageParts.Add($"Progress: {completedCount}/{userDownloads.Count} completed"); messageParts.Add($"Progress: {completedCount}/{userDownloads.Count} completed");
} }
@@ -344,29 +321,29 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
return string.Join("\n", messageParts); return string.Join("\n", messageParts);
} }
private string BuildActiveDownloadLines(List<(string playerName, float progress, string status)> userDownloads) private string BuildActiveDownloadLines(List<(string PlayerName, float Progress, string Status)> userDownloads)
{ {
var activeDownloads = userDownloads var activeDownloads = userDownloads
.Where(x => x.progress < 1.0f) .Where(x => x.Progress < 1.0f)
.Take(_configService.Current.MaxConcurrentPairApplications); .Take(_configService.Current.MaxConcurrentPairApplications);
if (!activeDownloads.Any()) return string.Empty; if (!activeDownloads.Any()) return string.Empty;
return string.Join("\n", activeDownloads.Select(x => $"• {x.playerName}: {FormatDownloadStatus(x)}")); return string.Join("\n", activeDownloads.Select(x => $"• {x.PlayerName}: {FormatDownloadStatus(x)}"));
} }
private string FormatDownloadStatus((string playerName, float progress, string status) download) => private string FormatDownloadStatus((string PlayerName, float Progress, string Status) download) =>
download.status switch download.Status switch
{ {
"downloading" => $"{download.progress:P0}", "downloading" => $"{download.Progress:P0}",
"decompressing" => "decompressing", "decompressing" => "decompressing",
"queued" => "queued", "queued" => "queued",
"waiting" => "waiting for slot", "waiting" => "waiting for slot",
_ => download.status _ => download.Status
}; };
private bool AreAllDownloadsCompleted(List<(string playerName, float progress, string status)> userDownloads) => private bool AreAllDownloadsCompleted(List<(string PlayerName, float Progress, string Status)> userDownloads) =>
userDownloads.Any() && userDownloads.All(x => x.progress >= 1.0f); userDownloads.Any() && userDownloads.All(x => x.Progress >= 1.0f);
public void DismissPairDownloadNotification() => public void DismissPairDownloadNotification() =>
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress")); Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
@@ -581,12 +558,25 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
_chatGui.Print(se.BuiltString); _chatGui.Print(se.BuiltString);
} }
private void HandlePairRequestReceived(PairRequestReceivedMessage msg)
{
var request = _pairRequestService.RegisterIncomingRequest(msg.HashedCid, msg.Message);
var senderName = string.IsNullOrEmpty(request.DisplayName) ? "Unknown User" : request.DisplayName;
_shownPairRequestNotifications.Add(request.HashedCid);
ShowPairRequestNotification(
senderName,
request.HashedCid,
onAccept: () => _pairRequestService.AcceptPairRequest(request.HashedCid, senderName),
onDecline: () => _pairRequestService.DeclinePairRequest(request.HashedCid, senderName));
}
private void HandlePairRequestsUpdated(PairRequestsUpdatedMessage _) private void HandlePairRequestsUpdated(PairRequestsUpdatedMessage _)
{ {
var activeRequests = _pairRequestService.GetActiveRequests(); var activeRequests = _pairRequestService.GetActiveRequests();
var activeRequestIds = activeRequests.Select(r => r.HashedCid).ToHashSet(); var activeRequestIds = activeRequests.Select(r => r.HashedCid).ToHashSet();
// Dismiss notifications for requests that are no longer active // Dismiss notifications for requests that are no longer active (expired)
var notificationsToRemove = _shownPairRequestNotifications var notificationsToRemove = _shownPairRequestNotifications
.Where(hashedCid => !activeRequestIds.Contains(hashedCid)) .Where(hashedCid => !activeRequestIds.Contains(hashedCid))
.ToList(); .ToList();
@@ -597,17 +587,30 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
Mediator.Publish(new LightlessNotificationDismissMessage(notificationId)); Mediator.Publish(new LightlessNotificationDismissMessage(notificationId));
_shownPairRequestNotifications.Remove(hashedCid); _shownPairRequestNotifications.Remove(hashedCid);
} }
}
// Show/update notifications for all active requests private void HandlePairDownloadStatus(PairDownloadStatusMessage msg)
foreach (var request in activeRequests) {
var userDownloads = msg.DownloadStatus.Where(x => x.PlayerName != "Pair Queue").ToList();
var totalProgress = userDownloads.Count > 0 ? userDownloads.Average(x => x.Progress) : 0f;
var message = BuildPairDownloadMessage(userDownloads, msg.QueueWaiting);
var notification = new LightlessNotification
{ {
_shownPairRequestNotifications.Add(request.HashedCid); Id = "pair_download_progress",
ShowPairRequestNotification( Title = "Downloading Pair Data",
request.DisplayName, Message = message,
request.HashedCid, Type = NotificationType.Download,
() => _pairRequestService.AcceptPairRequest(request.HashedCid, request.DisplayName), Duration = TimeSpan.FromSeconds(_configService.Current.DownloadNotificationDurationSeconds),
() => _pairRequestService.DeclinePairRequest(request.HashedCid) ShowProgress = true,
); Progress = totalProgress
};
Mediator.Publish(new LightlessNotificationMessage(notification));
if (userDownloads.Count == 0 || AreAllDownloadsCompleted(userDownloads))
{
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
} }
} }

View File

@@ -19,7 +19,12 @@ public sealed class PairRequestService : DisposableMediatorSubscriberBase
private static readonly TimeSpan Expiration = TimeSpan.FromMinutes(5); private static readonly TimeSpan Expiration = TimeSpan.FromMinutes(5);
public PairRequestService(ILogger<PairRequestService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtil, PairManager pairManager, Lazy<WebAPI.ApiController> apiController) public PairRequestService(
ILogger<PairRequestService> logger,
LightlessMediator mediator,
DalamudUtilService dalamudUtil,
PairManager pairManager,
Lazy<WebAPI.ApiController> apiController)
: base(logger, mediator) : base(logger, mediator)
{ {
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
@@ -215,9 +220,13 @@ public sealed class PairRequestService : DisposableMediatorSubscriberBase
}); });
} }
public void DeclinePairRequest(string hashedCid) public void DeclinePairRequest(string hashedCid, string displayName)
{ {
RemoveRequest(hashedCid); RemoveRequest(hashedCid);
Mediator.Publish(new NotificationMessage("Pair request declined",
"Declined " + displayName + "'s pending pair request.",
NotificationType.Info,
TimeSpan.FromSeconds(3)));
Logger.LogDebug("Declined pair request from {HashedCid}", hashedCid); Logger.LogDebug("Declined pair request from {HashedCid}", hashedCid);
} }

View File

@@ -23,8 +23,7 @@ public sealed class UiService : DisposableMediatorSubscriberBase
LightlessConfigService lightlessConfigService, WindowSystem windowSystem, LightlessConfigService lightlessConfigService, WindowSystem windowSystem,
IEnumerable<WindowMediatorSubscriberBase> windows, IEnumerable<WindowMediatorSubscriberBase> windows,
UiFactory uiFactory, FileDialogManager fileDialogManager, UiFactory uiFactory, FileDialogManager fileDialogManager,
LightlessMediator lightlessMediator, LightlessMediator lightlessMediator) : base(logger, lightlessMediator)
NotificationService notificationService) : base(logger, lightlessMediator)
{ {
_logger = logger; _logger = logger;
_logger.LogTrace("Creating {type}", GetType().Name); _logger.LogTrace("Creating {type}", GetType().Name);

View File

@@ -56,7 +56,6 @@ public class CompactUi : WindowMediatorSubscriberBase
private readonly BroadcastService _broadcastService; private readonly BroadcastService _broadcastService;
private List<IDrawFolder> _drawFolders; private List<IDrawFolder> _drawFolders;
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
private Pair? _lastAddedUser; private Pair? _lastAddedUser;
private string _lastAddedUserComment = string.Empty; private string _lastAddedUserComment = string.Empty;
private Vector2 _lastPosition = Vector2.One; private Vector2 _lastPosition = Vector2.One;
@@ -382,15 +381,26 @@ public class CompactUi : WindowMediatorSubscriberBase
_uiSharedService.IconText(FontAwesomeIcon.Upload); _uiSharedService.IconText(FontAwesomeIcon.Upload);
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
if (currentUploads.Any()) if (currentUploads.Count > 0)
{ {
var totalUploads = currentUploads.Count; int totalUploads = currentUploads.Count;
int doneUploads = 0;
long totalUploaded = 0;
long totalToUpload = 0;
var doneUploads = currentUploads.Count(c => c.IsTransferred); foreach (var upload in currentUploads)
var activeUploads = currentUploads.Count(c => !c.IsTransferred); {
if (upload.IsTransferred)
{
doneUploads++;
}
totalUploaded += upload.Transferred;
totalToUpload += upload.Total;
}
int activeUploads = totalUploads - doneUploads;
var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8); var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8);
var totalUploaded = currentUploads.Sum(c => c.Transferred);
var totalToUpload = currentUploads.Sum(c => c.Total);
ImGui.TextUnformatted($"{doneUploads}/{totalUploads} (slots {activeUploads}/{uploadSlotLimit})"); ImGui.TextUnformatted($"{doneUploads}/{totalUploads} (slots {activeUploads}/{uploadSlotLimit})");
var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})"; var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})";
@@ -405,17 +415,17 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted("No uploads in progress"); ImGui.TextUnformatted("No uploads in progress");
} }
var currentDownloads = BuildCurrentDownloadSnapshot(); var downloadSummary = GetDownloadSummary();
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
_uiSharedService.IconText(FontAwesomeIcon.Download); _uiSharedService.IconText(FontAwesomeIcon.Download);
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
if (currentDownloads.Any()) if (downloadSummary.HasDownloads)
{ {
var totalDownloads = currentDownloads.Sum(c => c.TotalFiles); var totalDownloads = downloadSummary.TotalFiles;
var doneDownloads = currentDownloads.Sum(c => c.TransferredFiles); var doneDownloads = downloadSummary.TransferredFiles;
var totalDownloaded = currentDownloads.Sum(c => c.TransferredBytes); var totalDownloaded = downloadSummary.TransferredBytes;
var totalToDownload = currentDownloads.Sum(c => c.TotalBytes); var totalToDownload = downloadSummary.TotalBytes;
ImGui.TextUnformatted($"{doneDownloads}/{totalDownloads}"); ImGui.TextUnformatted($"{doneDownloads}/{totalDownloads}");
var downloadText = var downloadText =
@@ -433,27 +443,35 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
private List<FileDownloadStatus> BuildCurrentDownloadSnapshot() private DownloadSummary GetDownloadSummary()
{ {
List<FileDownloadStatus> snapshot = new(); long totalBytes = 0;
long transferredBytes = 0;
int totalFiles = 0;
int transferredFiles = 0;
foreach (var kvp in _currentDownloads.ToArray()) foreach (var kvp in _currentDownloads.ToArray())
{ {
var value = kvp.Value; if (kvp.Value is not { Count: > 0 } statuses)
if (value == null || value.Count == 0) {
continue; continue;
try
{
snapshot.AddRange(value.Values.ToArray());
} }
catch (System.ArgumentException)
foreach (var status in statuses.Values)
{ {
// skibidi totalBytes += status.TotalBytes;
transferredBytes += status.TransferredBytes;
totalFiles += status.TotalFiles;
transferredFiles += status.TransferredFiles;
} }
} }
return snapshot; return new DownloadSummary(totalFiles, transferredFiles, transferredBytes, totalBytes);
}
private readonly record struct DownloadSummary(int TotalFiles, int TransferredFiles, long TransferredBytes, long TotalBytes)
{
public bool HasDownloads => TotalFiles > 0 || TotalBytes > 0;
} }
private void DrawUIDHeader() private void DrawUIDHeader()
@@ -480,7 +498,7 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
//Getting information of character and triangles threshold to show overlimit status in UID bar. //Getting information of character and triangles threshold to show overlimit status in UID bar.
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); var analysisSummary = _characterAnalyzer.LatestSummary;
Vector2 uidTextSize, iconSize; Vector2 uidTextSize, iconSize;
using (_uiSharedService.UidFont.Push()) using (_uiSharedService.UidFont.Push())
@@ -509,6 +527,7 @@ public class CompactUi : WindowMediatorSubscriberBase
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
ImGui.BeginTooltip(); ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 32f);
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("PairBlue")); ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("PairBlue"));
ImGui.Text("Lightfinder"); ImGui.Text("Lightfinder");
@@ -556,6 +575,7 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.PopStyleColor(); ImGui.PopStyleColor();
} }
ImGui.PopTextWrapPos();
ImGui.EndTooltip(); ImGui.EndTooltip();
} }
@@ -574,7 +594,7 @@ public class CompactUi : WindowMediatorSubscriberBase
var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor); var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor);
var cursorPos = ImGui.GetCursorScreenPos(); var cursorPos = ImGui.GetCursorScreenPos();
var fontPtr = ImGui.GetFont(); var fontPtr = ImGui.GetFont();
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr); SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-header");
} }
else else
{ {
@@ -591,56 +611,40 @@ public class CompactUi : WindowMediatorSubscriberBase
UiSharedService.AttachToolTip("Click to copy"); UiSharedService.AttachToolTip("Click to copy");
if (_cachedAnalysis != null && _apiController.ServerState is ServerState.Connected) if (_apiController.ServerState is ServerState.Connected && analysisSummary.HasData)
{ {
var firstEntry = _cachedAnalysis.FirstOrDefault(); var objectSummary = analysisSummary.Objects.Values.FirstOrDefault(summary => summary.HasEntries);
var valueDict = firstEntry.Value; if (objectSummary.HasEntries)
if (valueDict != null && valueDict.Count > 0)
{ {
var groupedfiles = valueDict var actualVramUsage = objectSummary.TexOriginalBytes;
.Select(v => v.Value) var actualTriCount = objectSummary.TotalTriangles;
.Where(v => v != null)
.GroupBy(f => f.FileType, StringComparer.Ordinal)
.OrderBy(k => k.Key, StringComparer.Ordinal)
.ToList();
var actualTriCount = valueDict var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage;
.Select(v => v.Value) var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000);
.Where(v => v != null)
.Sum(f => f.Triangles);
if (groupedfiles != null) if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds)
{ {
//Checking of VRAM threshhold ImGui.SameLine();
var texGroup = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)); ImGui.SetCursorPosY(cursorY + 15f);
var actualVramUsage = texGroup != null ? texGroup.Sum(f => f.OriginalSize) : 0L; _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage;
var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000);
if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds) string warningMessage = "";
if (isOverTriHold)
{ {
ImGui.SameLine(); warningMessage += $"You exceed your own triangles threshold by " +
ImGui.SetCursorPosY(cursorY + 15f); $"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles.";
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); warningMessage += Environment.NewLine;
string warningMessage = ""; }
if (isOverTriHold) if (isOverVRAMUsage)
{ {
warningMessage += $"You exceed your own triangles threshold by " + warningMessage += $"You exceed your own VRAM threshold by " +
$"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles."; $"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}.";
warningMessage += Environment.NewLine; }
UiSharedService.AttachToolTip(warningMessage);
} if (ImGui.IsItemClicked())
if (isOverVRAMUsage) {
{ _lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
warningMessage += $"You exceed your own VRAM threshold by " +
$"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}.";
}
UiSharedService.AttachToolTip(warningMessage);
if (ImGui.IsItemClicked())
{
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
}
} }
} }
} }
@@ -663,7 +667,7 @@ public class CompactUi : WindowMediatorSubscriberBase
var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor); var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor);
var cursorPos = ImGui.GetCursorScreenPos(); var cursorPos = ImGui.GetCursorScreenPos();
var fontPtr = ImGui.GetFont(); var fontPtr = ImGui.GetFont();
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr); SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-footer");
} }
else else
{ {
@@ -921,4 +925,4 @@ public class CompactUi : WindowMediatorSubscriberBase
_wasOpen = IsOpen; _wasOpen = IsOpen;
IsOpen = false; IsOpen = false;
} }
} }

View File

@@ -2,6 +2,7 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User; using LightlessSync.API.Dto.User;
@@ -13,6 +14,9 @@ using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Handlers; using LightlessSync.UI.Handlers;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
namespace LightlessSync.UI.Components; namespace LightlessSync.UI.Components;
@@ -32,6 +36,8 @@ public class DrawUserPair
private readonly CharaDataManager _charaDataManager; private readonly CharaDataManager _charaDataManager;
private float _menuWidth = -1; private float _menuWidth = -1;
private bool _wasHovered = false; private bool _wasHovered = false;
private TooltipSnapshot _tooltipSnapshot = TooltipSnapshot.Empty;
private string _cachedTooltip = string.Empty;
public DrawUserPair(string id, Pair entry, List<GroupFullInfoDto> syncedGroups, public DrawUserPair(string id, Pair entry, List<GroupFullInfoDto> syncedGroups,
GroupFullInfoDto? currentGroup, GroupFullInfoDto? currentGroup,
@@ -190,15 +196,12 @@ public class DrawUserPair
private void DrawLeftSide() private void DrawLeftSide()
{ {
string userPairText = string.Empty;
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
if (_pair.IsPaused) if (_pair.IsPaused)
{ {
using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")); using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow"));
_uiSharedService.IconText(FontAwesomeIcon.PauseCircle); _uiSharedService.IconText(FontAwesomeIcon.PauseCircle);
userPairText = _pair.UserData.AliasOrUID + " is paused";
} }
else if (!_pair.IsOnline) else if (!_pair.IsOnline)
{ {
@@ -207,12 +210,10 @@ public class DrawUserPair
? FontAwesomeIcon.ArrowsLeftRight ? FontAwesomeIcon.ArrowsLeftRight
: (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional : (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional
? FontAwesomeIcon.User : FontAwesomeIcon.Users)); ? FontAwesomeIcon.User : FontAwesomeIcon.Users));
userPairText = _pair.UserData.AliasOrUID + " is offline";
} }
else if (_pair.IsVisible) else if (_pair.IsVisible)
{ {
_uiSharedService.IconText(FontAwesomeIcon.Eye, UIColors.Get("LightlessBlue")); _uiSharedService.IconText(FontAwesomeIcon.Eye, UIColors.Get("LightlessBlue"));
userPairText = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName + Environment.NewLine + "Click to target this player";
if (ImGui.IsItemClicked()) if (ImGui.IsItemClicked())
{ {
_mediator.Publish(new TargetPairMessage(_pair)); _mediator.Publish(new TargetPairMessage(_pair));
@@ -223,46 +224,9 @@ public class DrawUserPair
using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("PairBlue")); using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("PairBlue"));
_uiSharedService.IconText(_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional _uiSharedService.IconText(_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional
? FontAwesomeIcon.User : FontAwesomeIcon.Users); ? FontAwesomeIcon.User : FontAwesomeIcon.Users);
userPairText = _pair.UserData.AliasOrUID + " is online";
} }
if (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.OneSided) UiSharedService.AttachToolTip(GetUserTooltip());
{
userPairText += UiSharedService.TooltipSeparator + "User has not added you back";
}
else if (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional)
{
userPairText += UiSharedService.TooltipSeparator + "You are directly Paired";
}
if (_pair.LastAppliedDataBytes >= 0)
{
userPairText += UiSharedService.TooltipSeparator;
userPairText += ((!_pair.IsPaired) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
userPairText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true);
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
{
userPairText += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true);
}
if (_pair.LastAppliedDataTris >= 0)
{
userPairText += Environment.NewLine + "Approx. Triangle Count (excl. Vanilla): "
+ (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris);
}
}
if (_syncedGroups.Any())
{
userPairText += UiSharedService.TooltipSeparator + string.Join(Environment.NewLine,
_syncedGroups.Select(g =>
{
var groupNote = _serverConfigurationManager.GetNoteForGid(g.GID);
var groupString = string.IsNullOrEmpty(groupNote) ? g.GroupAliasOrGID : $"{groupNote} ({g.GroupAliasOrGID})";
return "Paired through " + groupString;
}));
}
UiSharedService.AttachToolTip(userPairText);
if (_performanceConfigService.Current.ShowPerformanceIndicator if (_performanceConfigService.Current.ShowPerformanceIndicator
&& !_performanceConfigService.Current.UIDsToIgnore && !_performanceConfigService.Current.UIDsToIgnore
@@ -327,6 +291,143 @@ public class DrawUserPair
_displayHandler.DrawPairText(_id, _pair, leftSide, () => rightSide - leftSide); _displayHandler.DrawPairText(_id, _pair, leftSide, () => rightSide - leftSide);
} }
private string GetUserTooltip()
{
List<string>? groupDisplays = null;
if (_syncedGroups.Count > 0)
{
groupDisplays = new List<string>(_syncedGroups.Count);
foreach (var group in _syncedGroups)
{
var groupNote = _serverConfigurationManager.GetNoteForGid(group.GID);
groupDisplays.Add(string.IsNullOrEmpty(groupNote) ? group.GroupAliasOrGID : $"{groupNote} ({group.GroupAliasOrGID})");
}
}
var snapshot = new TooltipSnapshot(
_pair.IsPaused,
_pair.IsOnline,
_pair.IsVisible,
_pair.IndividualPairStatus,
_pair.UserData.AliasOrUID,
_pair.PlayerName ?? string.Empty,
_pair.LastAppliedDataBytes,
_pair.LastAppliedApproximateVRAMBytes,
_pair.LastAppliedDataTris,
_pair.IsPaired,
groupDisplays is null ? ImmutableArray<string>.Empty : ImmutableArray.CreateRange(groupDisplays));
if (!_tooltipSnapshot.Equals(snapshot))
{
_cachedTooltip = BuildTooltip(snapshot);
_tooltipSnapshot = snapshot;
}
return _cachedTooltip;
}
private static string BuildTooltip(in TooltipSnapshot snapshot)
{
var builder = new StringBuilder(256);
if (snapshot.IsPaused)
{
builder.Append(snapshot.AliasOrUid);
builder.Append(" is paused");
}
else if (!snapshot.IsOnline)
{
builder.Append(snapshot.AliasOrUid);
builder.Append(" is offline");
}
else if (snapshot.IsVisible)
{
builder.Append(snapshot.AliasOrUid);
builder.Append(" is visible: ");
builder.Append(snapshot.PlayerName);
builder.Append(Environment.NewLine);
builder.Append("Click to target this player");
}
else
{
builder.Append(snapshot.AliasOrUid);
builder.Append(" is online");
}
if (snapshot.PairStatus == IndividualPairStatus.OneSided)
{
builder.Append(UiSharedService.TooltipSeparator);
builder.Append("User has not added you back");
}
else if (snapshot.PairStatus == IndividualPairStatus.Bidirectional)
{
builder.Append(UiSharedService.TooltipSeparator);
builder.Append("You are directly Paired");
}
if (snapshot.LastAppliedDataBytes >= 0)
{
builder.Append(UiSharedService.TooltipSeparator);
if (!snapshot.IsPaired)
{
builder.Append("(Last) ");
}
builder.Append("Mods Info");
builder.Append(Environment.NewLine);
builder.Append("Files Size: ");
builder.Append(UiSharedService.ByteToString(snapshot.LastAppliedDataBytes, true));
if (snapshot.LastAppliedApproximateVRAMBytes >= 0)
{
builder.Append(Environment.NewLine);
builder.Append("Approx. VRAM Usage: ");
builder.Append(UiSharedService.ByteToString(snapshot.LastAppliedApproximateVRAMBytes, true));
}
if (snapshot.LastAppliedDataTris >= 0)
{
builder.Append(Environment.NewLine);
builder.Append("Approx. Triangle Count (excl. Vanilla): ");
builder.Append(snapshot.LastAppliedDataTris > 1000
? (snapshot.LastAppliedDataTris / 1000d).ToString("0.0'k'")
: snapshot.LastAppliedDataTris);
}
}
if (!snapshot.GroupDisplays.IsEmpty)
{
builder.Append(UiSharedService.TooltipSeparator);
for (int i = 0; i < snapshot.GroupDisplays.Length; i++)
{
if (i > 0)
{
builder.Append(Environment.NewLine);
}
builder.Append("Paired through ");
builder.Append(snapshot.GroupDisplays[i]);
}
}
return builder.ToString();
}
private readonly record struct TooltipSnapshot(
bool IsPaused,
bool IsOnline,
bool IsVisible,
IndividualPairStatus PairStatus,
string AliasOrUid,
string PlayerName,
long LastAppliedDataBytes,
long LastAppliedApproximateVRAMBytes,
long LastAppliedDataTris,
bool IsPaired,
ImmutableArray<string> GroupDisplays)
{
public static TooltipSnapshot Empty { get; } =
new(false, false, false, IndividualPairStatus.None, string.Empty, string.Empty, -1, -1, -1, false, ImmutableArray<string>.Empty);
}
private void DrawPairedClientMenu() private void DrawPairedClientMenu()
{ {
DrawIndividualMenu(); DrawIndividualMenu();

View File

@@ -22,13 +22,12 @@ public class DownloadUi : WindowMediatorSubscriberBase
private readonly UiSharedService _uiShared; private readonly UiSharedService _uiShared;
private readonly PairProcessingLimiter _pairProcessingLimiter; private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new(); private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
private readonly NotificationService _notificationService;
private bool _notificationDismissed = true; private bool _notificationDismissed = true;
private int _lastDownloadStateHash = 0; 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,
PerformanceCollectorService performanceCollectorService, NotificationService notificationService) PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService) : base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService)
{ {
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
@@ -36,7 +35,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
_pairProcessingLimiter = pairProcessingLimiter; _pairProcessingLimiter = pairProcessingLimiter;
_fileTransferManager = fileTransferManager; _fileTransferManager = fileTransferManager;
_uiShared = uiShared; _uiShared = uiShared;
_notificationService = notificationService;
SizeConstraints = new WindowSizeConstraints() SizeConstraints = new WindowSizeConstraints()
{ {
@@ -359,7 +357,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
_lastDownloadStateHash = currentHash; _lastDownloadStateHash = currentHash;
if (downloadStatus.Count > 0 || queueWaiting > 0) if (downloadStatus.Count > 0 || queueWaiting > 0)
{ {
_notificationService.ShowPairDownloadNotification(downloadStatus, queueWaiting); Mediator.Publish(new PairDownloadStatusMessage(downloadStatus, queueWaiting));
} }
} }
} }

View File

@@ -206,7 +206,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
} }
_showFileDialogError = false; _showFileDialogError = false;
await _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, Convert.ToBase64String(fileContent), Description: null, Tags: null)) await _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, Convert.ToBase64String(fileContent), BannerPictureBase64: null, Description: null, Tags: null))
.ConfigureAwait(false); .ConfigureAwait(false);
}); });
}); });
@@ -215,7 +215,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
ImGui.SameLine(); ImGui.SameLine();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture"))
{ {
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, "", Description: null, Tags: null)); _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, "", Description: null, BannerPictureBase64: null, Tags: null));
} }
UiSharedService.AttachToolTip("Clear your currently uploaded profile picture"); UiSharedService.AttachToolTip("Clear your currently uploaded profile picture");
if (_showFileDialogError) if (_showFileDialogError)
@@ -225,7 +225,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
var isNsfw = profile.IsNSFW; var isNsfw = profile.IsNSFW;
if (ImGui.Checkbox("Profile is NSFW", ref isNsfw)) if (ImGui.Checkbox("Profile is NSFW", ref isNsfw))
{ {
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, isNsfw, ProfilePictureBase64: null, Description: null, Tags: null)); _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, isNsfw, ProfilePictureBase64: null, Description: null, BannerPictureBase64: null, Tags: null));
} }
_uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON"); _uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON");
var widthTextBox = 400; var widthTextBox = 400;
@@ -264,13 +264,13 @@ public class EditProfileUi : WindowMediatorSubscriberBase
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description"))
{ {
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, _descriptionText, Tags: null)); _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, _descriptionText, Tags: null));
} }
UiSharedService.AttachToolTip("Sets your profile description text"); UiSharedService.AttachToolTip("Sets your profile description text");
ImGui.SameLine(); ImGui.SameLine();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description"))
{ {
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, "", Tags: null)); _ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, BannerPictureBase64: null, "", Tags: null));
} }
UiSharedService.AttachToolTip("Clears your profile description text"); UiSharedService.AttachToolTip("Clears your profile description text");
@@ -281,7 +281,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
{ {
_uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple")); _uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple"));
ImGui.Dummy(new Vector2(4)); ImGui.Dummy(new Vector2(4));
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings."); _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings. If you have the vanity role, you must interact with the Discord bot first.");
var hasVanity = _apiController.HasVanity; var hasVanity = _apiController.HasVanity;

View File

@@ -157,7 +157,7 @@ public class IdDisplayHandler
Vector2 textSize; Vector2 textSize;
using (ImRaii.PushFont(font, textIsUid)) using (ImRaii.PushFont(font, textIsUid))
{ {
SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font); SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID);
itemMin = ImGui.GetItemRectMin(); itemMin = ImGui.GetItemRectMin();
itemMax = ImGui.GetItemRectMax(); itemMax = ImGui.GetItemRectMax();
//textSize = itemMax - itemMin; //textSize = itemMax - itemMin;

View File

@@ -15,17 +15,17 @@ using Dalamud.Bindings.ImGui;
namespace LightlessSync.UI; namespace LightlessSync.UI;
public class LightlessNotificationUI : WindowMediatorSubscriberBase public class LightlessNotificationUi : WindowMediatorSubscriberBase
{ {
private const float NotificationMinHeight = 60f; private const float _notificationMinHeight = 60f;
private const float NotificationMaxHeight = 250f; private const float _notificationMaxHeight = 250f;
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 _contentPaddingX = 10f;
private const float ContentPaddingY = 6f; private const float _contentPaddingY = 6f;
private const float TitleMessageSpacing = 4f; private const float _titleMessageSpacing = 4f;
private const float ActionButtonSpacing = 8f; 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();
@@ -33,7 +33,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private readonly Dictionary<string, float> _notificationYOffsets = new(); private readonly Dictionary<string, float> _notificationYOffsets = new();
private readonly Dictionary<string, float> _notificationTargetYOffsets = new(); private readonly Dictionary<string, float> _notificationTargetYOffsets = new();
public LightlessNotificationUI(ILogger<LightlessNotificationUI> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService) public LightlessNotificationUi(ILogger<LightlessNotificationUi> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService)
: base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector) : base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector)
{ {
_configService = configService; _configService = configService;
@@ -155,8 +155,8 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
var width = _configService.Current.NotificationWidth; var width = _configService.Current.NotificationWidth;
float posX = corner == NotificationCorner.Left float posX = corner == NotificationCorner.Left
? viewport.WorkPos.X + offsetX - WindowPaddingOffset ? viewport.WorkPos.X + offsetX - _windowPaddingOffset
: viewport.WorkPos.X + viewport.WorkSize.X - width - offsetX - WindowPaddingOffset; : viewport.WorkPos.X + viewport.WorkSize.X - width - offsetX - _windowPaddingOffset;
return new Vector2(posX, viewport.WorkPos.Y); return new Vector2(posX, viewport.WorkPos.Y);
} }
@@ -274,7 +274,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
else if (notification.IsAnimatingOut && notification.AnimationProgress > 0f) else if (notification.IsAnimatingOut && notification.AnimationProgress > 0f)
{ {
notification.AnimationProgress = Math.Max(0f, notification.AnimationProgress = Math.Max(0f,
notification.AnimationProgress - deltaTime * _configService.Current.NotificationAnimationSpeed * OutAnimationSpeedMultiplier); notification.AnimationProgress - deltaTime * _configService.Current.NotificationAnimationSpeed * _outAnimationSpeedMultiplier);
} }
else if (!notification.IsAnimatingOut && !notification.IsDismissed) else if (!notification.IsAnimatingOut && !notification.IsDismissed)
{ {
@@ -289,7 +289,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private Vector2 CalculateSlideOffset(float alpha) private Vector2 CalculateSlideOffset(float alpha)
{ {
var distance = (1f - alpha) * SlideAnimationDistance; var distance = (1f - alpha) * _slideAnimationDistance;
var corner = _configService.Current.NotificationCorner; var corner = _configService.Current.NotificationCorner;
return corner == NotificationCorner.Left ? new Vector2(-distance, 0) : new Vector2(distance, 0); return corner == NotificationCorner.Left ? new Vector2(-distance, 0) : new Vector2(distance, 0);
} }
@@ -466,7 +466,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private void DrawNotificationText(LightlessNotification notification, float alpha) private void DrawNotificationText(LightlessNotification notification, float alpha)
{ {
var contentPos = new Vector2(ContentPaddingX, ContentPaddingY); var contentPos = new Vector2(_contentPaddingX, _contentPaddingY);
var windowSize = ImGui.GetWindowSize(); var windowSize = ImGui.GetWindowSize();
var contentWidth = CalculateContentWidth(windowSize.X); var contentWidth = CalculateContentWidth(windowSize.X);
@@ -483,7 +483,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
} }
private float CalculateContentWidth(float windowWidth) => private float CalculateContentWidth(float windowWidth) =>
windowWidth - (ContentPaddingX * 2); windowWidth - (_contentPaddingX * 2);
private bool HasActions(LightlessNotification notification) => private bool HasActions(LightlessNotification notification) =>
notification.Actions.Count > 0; notification.Actions.Count > 0;
@@ -491,9 +491,9 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private void PositionActionsAtBottom(float windowHeight) private void PositionActionsAtBottom(float windowHeight)
{ {
var actionHeight = ImGui.GetFrameHeight(); var actionHeight = ImGui.GetFrameHeight();
var bottomY = windowHeight - ContentPaddingY - actionHeight; var bottomY = windowHeight - _contentPaddingY - actionHeight;
ImGui.SetCursorPosY(bottomY); ImGui.SetCursorPosY(bottomY);
ImGui.SetCursorPosX(ContentPaddingX); ImGui.SetCursorPosX(_contentPaddingX);
} }
private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha) private float DrawTitle(LightlessNotification notification, float contentWidth, float alpha)
@@ -530,7 +530,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
{ {
if (string.IsNullOrEmpty(notification.Message)) return; if (string.IsNullOrEmpty(notification.Message)) return;
var messagePos = contentPos + new Vector2(0f, titleHeight + TitleMessageSpacing); var messagePos = contentPos + new Vector2(0f, titleHeight + _titleMessageSpacing);
var messageColor = new Vector4(0.9f, 0.9f, 0.9f, alpha); var messageColor = new Vector4(0.9f, 0.9f, 0.9f, alpha);
ImGui.SetCursorPos(messagePos); ImGui.SetCursorPos(messagePos);
@@ -563,13 +563,13 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private float CalculateActionButtonWidth(int actionCount, float availableWidth) private float CalculateActionButtonWidth(int actionCount, float availableWidth)
{ {
var totalSpacing = (actionCount - 1) * ActionButtonSpacing; var totalSpacing = (actionCount - 1) * _actionButtonSpacing;
return (availableWidth - totalSpacing) / actionCount; return (availableWidth - totalSpacing) / actionCount;
} }
private void PositionActionButton(int index, float startX, float buttonWidth) private void PositionActionButton(int index, float startX, float buttonWidth)
{ {
var xPosition = startX + index * (buttonWidth + ActionButtonSpacing); var xPosition = startX + index * (buttonWidth + _actionButtonSpacing);
ImGui.SetCursorPosX(xPosition); ImGui.SetCursorPosX(xPosition);
} }
@@ -687,7 +687,7 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
height += 12f; height += 12f;
} }
return Math.Clamp(height, NotificationMinHeight, NotificationMaxHeight); return Math.Clamp(height, _notificationMinHeight, _notificationMaxHeight);
} }
private float CalculateTitleHeight(LightlessNotification notification, float contentWidth) private float CalculateTitleHeight(LightlessNotification notification, float contentWidth)

View File

@@ -63,7 +63,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress; private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
private readonly NameplateService _nameplateService; private readonly NameplateService _nameplateService;
private readonly NameplateHandler _nameplateHandler; private readonly NameplateHandler _nameplateHandler;
private readonly NotificationService _lightlessNotificationService;
private (int, int, FileCacheEntity) _currentProgress; private (int, int, FileCacheEntity) _currentProgress;
private bool _deleteAccountPopupModalShown = false; private bool _deleteAccountPopupModalShown = false;
private bool _deleteFilesPopupModalShown = false; private bool _deleteFilesPopupModalShown = false;
@@ -107,8 +106,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
IpcManager ipcManager, CacheMonitor cacheMonitor, IpcManager ipcManager, CacheMonitor cacheMonitor,
DalamudUtilService dalamudUtilService, HttpClient httpClient, DalamudUtilService dalamudUtilService, HttpClient httpClient,
NameplateService nameplateService, NameplateService nameplateService,
NameplateHandler nameplateHandler, NameplateHandler nameplateHandler) : base(logger, mediator, "Lightless Sync Settings",
NotificationService lightlessNotificationService) : base(logger, mediator, "Lightless Sync Settings",
performanceCollector) performanceCollector)
{ {
_configService = configService; _configService = configService;
@@ -130,7 +128,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared = uiShared; _uiShared = uiShared;
_nameplateService = nameplateService; _nameplateService = nameplateService;
_nameplateHandler = nameplateHandler; _nameplateHandler = nameplateHandler;
_lightlessNotificationService = lightlessNotificationService;
AllowClickthrough = false; AllowClickthrough = false;
AllowPinning = true; AllowPinning = true;
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v); _validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
@@ -140,6 +137,25 @@ public class SettingsUi : WindowMediatorSubscriberBase
MinimumSize = new Vector2(800, 400), MaximumSize = new Vector2(800, 2000), MinimumSize = new Vector2(800, 400), MaximumSize = new Vector2(800, 2000),
}; };
TitleBarButtons = new()
{
new TitleBarButton()
{
Icon = FontAwesomeIcon.FileAlt,
Click = (msg) =>
{
Mediator.Publish(new UiToggleMessage(typeof(UpdateNotesUi)));
},
IconOffset = new(2, 1),
ShowTooltip = () =>
{
ImGui.BeginTooltip();
ImGui.Text("View Update Notes");
ImGui.EndTooltip();
}
}
};
Mediator.Subscribe<OpenSettingsUiMessage>(this, (_) => Toggle()); Mediator.Subscribe<OpenSettingsUiMessage>(this, (_) => Toggle());
Mediator.Subscribe<OpenLightfinderSettingsMessage>(this, (_) => Mediator.Subscribe<OpenLightfinderSettingsMessage>(this, (_) =>
{ {
@@ -2339,9 +2355,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.Separator(); ImGui.Separator();
} }
private void DrawPerformance() private void DrawPerformance()
@@ -3599,20 +3613,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
{ {
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_pair", new Vector2(availableWidth, 0))) if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_pair", new Vector2(availableWidth, 0)))
{ {
_lightlessNotificationService.ShowPairRequestNotification( Mediator.Publish(new PairRequestReceivedMessage("test-uid-123", "Test User wants to pair with you."));
"Test User",
"test-uid-123",
() =>
{
Mediator.Publish(new NotificationMessage("Accepted", "You accepted the test pair request.",
NotificationType.Info));
},
() =>
{
Mediator.Publish(new NotificationMessage("Declined", "You declined the test pair request.",
NotificationType.Info));
}
);
} }
} }
UiSharedService.AttachToolTip("Test pair request notification"); UiSharedService.AttachToolTip("Test pair request notification");
@@ -3635,15 +3636,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
{ {
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_download", new Vector2(availableWidth, 0))) if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_download", new Vector2(availableWidth, 0)))
{ {
_lightlessNotificationService.ShowPairDownloadNotification( Mediator.Publish(new PairDownloadStatusMessage(
new List<(string playerName, float progress, string status)> [
{
("Player One", 0.35f, "downloading"), ("Player One", 0.35f, "downloading"),
("Player Two", 0.75f, "downloading"), ("Player Two", 0.75f, "downloading"),
("Player Three", 1.0f, "downloading") ("Player Three", 1.0f, "downloading")
}, ],
queueWaiting: 2 2
); ));
} }
} }
UiSharedService.AttachToolTip("Test download progress notification"); UiSharedService.AttachToolTip("Test download progress notification");

View File

@@ -303,7 +303,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
} }
_showFileDialogError = false; _showFileDialogError = false;
await _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, Convert.ToBase64String(fileContent), IsNsfw: null, IsDisabled: null)) await _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, Convert.ToBase64String(fileContent), BannerBase64: null, IsNsfw: null, IsDisabled: null))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
}); });
@@ -313,7 +313,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
ImGui.SameLine(); ImGui.SameLine();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture"))
{ {
_ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, IsNsfw: null, IsDisabled: null)); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null));
} }
UiSharedService.AttachToolTip("Clear your currently uploaded profile picture"); UiSharedService.AttachToolTip("Clear your currently uploaded profile picture");
if (_showFileDialogError) if (_showFileDialogError)
@@ -368,13 +368,13 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description"))
{ {
_ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: _descriptionText, Tags: null, PictureBase64: null, IsNsfw: null, IsDisabled: null)); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: _descriptionText, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null));
} }
UiSharedService.AttachToolTip("Sets your profile description text"); UiSharedService.AttachToolTip("Sets your profile description text");
ImGui.SameLine(); ImGui.SameLine();
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description"))
{ {
_ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, IsNsfw: null, IsDisabled: null)); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null));
} }
UiSharedService.AttachToolTip("Clears your profile description text"); UiSharedService.AttachToolTip("Clears your profile description text");
ImGui.Separator(); ImGui.Separator();
@@ -382,7 +382,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
var isNsfw = _profileData.IsNsfw; var isNsfw = _profileData.IsNsfw;
if (ImGui.Checkbox("Profile is NSFW", ref isNsfw)) if (ImGui.Checkbox("Profile is NSFW", ref isNsfw))
{ {
_ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, IsNsfw: isNsfw, IsDisabled: null)); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: null, PictureBase64: null, BannerBase64: null, IsNsfw: isNsfw, IsDisabled: null));
} }
_uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON"); _uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON");
ImGui.TreePop(); ImGui.TreePop();
@@ -744,12 +744,12 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
if (HasTag) if (HasTag)
{ {
_selectedTags.Add(tag); _selectedTags.Add(tag);
_ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: _selectedTags.ToArray(), PictureBase64: null, IsNsfw: null, IsDisabled: null)); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: _selectedTags.ToArray(), PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null));
} }
else else
{ {
_selectedTags.Remove(tag); _selectedTags.Remove(tag);
_ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: _selectedTags.ToArray(), PictureBase64: null, IsNsfw: null, IsDisabled: null)); _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.AliasOrGID), Description: null, Tags: _selectedTags.ToArray(), PictureBase64: null, BannerBase64: null, IsNsfw: null, IsDisabled: null));
} }
} }
} }

View File

@@ -52,12 +52,12 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
private float _particleSpawnTimer; private float _particleSpawnTimer;
private readonly Random _random = new(); private readonly Random _random = new();
private const float HeaderHeight = 150f; private const float _headerHeight = 150f;
private const float ParticleSpawnInterval = 0.2f; private const float _particleSpawnInterval = 0.2f;
private const int MaxParticles = 50; private const int _maxParticles = 50;
private const int MaxTrailLength = 50; private const int _maxTrailLength = 50;
private const float EdgeFadeDistance = 30f; private const float _edgeFadeDistance = 30f;
private const float ExtendedParticleHeight = 40f; private const float _extendedParticleHeight = 40f;
public UpdateNotesUi(ILogger<UpdateNotesUi> logger, public UpdateNotesUi(ILogger<UpdateNotesUi> logger,
LightlessMediator mediator, LightlessMediator mediator,
@@ -111,16 +111,16 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
var headerWidth = (800f * ImGuiHelpers.GlobalScale) - (windowPadding.X * 2); var headerWidth = (800f * ImGuiHelpers.GlobalScale) - (windowPadding.X * 2);
var headerStart = windowPos + new Vector2(windowPadding.X, windowPadding.Y); var headerStart = windowPos + new Vector2(windowPadding.X, windowPadding.Y);
var headerEnd = headerStart + new Vector2(headerWidth, HeaderHeight); var headerEnd = headerStart + new Vector2(headerWidth, _headerHeight);
var extendedParticleSize = new Vector2(headerWidth, HeaderHeight + ExtendedParticleHeight); var extendedParticleSize = new Vector2(headerWidth, _headerHeight + _extendedParticleHeight);
DrawGradientBackground(headerStart, headerEnd); DrawGradientBackground(headerStart, headerEnd);
DrawHeaderText(headerStart); DrawHeaderText(headerStart);
DrawHeaderButtons(headerStart, headerWidth); DrawHeaderButtons(headerStart, headerWidth);
DrawBottomGradient(headerStart, headerEnd, headerWidth); DrawBottomGradient(headerStart, headerEnd, headerWidth);
ImGui.SetCursorPosY(windowPadding.Y + HeaderHeight + 5); ImGui.SetCursorPosY(windowPadding.Y + _headerHeight + 5);
ImGui.SetCursorPosX(20); ImGui.SetCursorPosX(20);
using (ImRaii.PushFont(UiBuilder.IconFont)) using (ImRaii.PushFont(UiBuilder.IconFont))
{ {
@@ -260,7 +260,7 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
var deltaTime = ImGui.GetIO().DeltaTime; var deltaTime = ImGui.GetIO().DeltaTime;
_particleSpawnTimer += deltaTime; _particleSpawnTimer += deltaTime;
if (_particleSpawnTimer > ParticleSpawnInterval && _particles.Count < MaxParticles) if (_particleSpawnTimer > _particleSpawnInterval && _particles.Count < _maxParticles)
{ {
SpawnParticle(bannerSize); SpawnParticle(bannerSize);
_particleSpawnTimer = 0f; _particleSpawnTimer = 0f;
@@ -282,7 +282,7 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
if (particle.Type == ParticleType.ShootingStar && particle.Trail != null) if (particle.Type == ParticleType.ShootingStar && particle.Trail != null)
{ {
particle.Trail.Insert(0, particle.Position); particle.Trail.Insert(0, particle.Position);
if (particle.Trail.Count > MaxTrailLength) if (particle.Trail.Count > _maxTrailLength)
particle.Trail.RemoveAt(particle.Trail.Count - 1); particle.Trail.RemoveAt(particle.Trail.Count - 1);
} }
@@ -316,12 +316,12 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
var lifeFade = Math.Min(fadeIn, fadeOut); var lifeFade = Math.Min(fadeIn, fadeOut);
var edgeFadeX = Math.Min( var edgeFadeX = Math.Min(
Math.Min(1f, (particle.Position.X + EdgeFadeDistance) / EdgeFadeDistance), Math.Min(1f, (particle.Position.X + _edgeFadeDistance) / _edgeFadeDistance),
Math.Min(1f, (bannerSize.X - particle.Position.X + EdgeFadeDistance) / EdgeFadeDistance) Math.Min(1f, (bannerSize.X - particle.Position.X + _edgeFadeDistance) / _edgeFadeDistance)
); );
var edgeFadeY = Math.Min( var edgeFadeY = Math.Min(
Math.Min(1f, (particle.Position.Y + EdgeFadeDistance) / EdgeFadeDistance), Math.Min(1f, (particle.Position.Y + _edgeFadeDistance) / _edgeFadeDistance),
Math.Min(1f, (bannerSize.Y - particle.Position.Y + EdgeFadeDistance) / EdgeFadeDistance) Math.Min(1f, (bannerSize.Y - particle.Position.Y + _edgeFadeDistance) / _edgeFadeDistance)
); );
var edgeFade = Math.Min(edgeFadeX, edgeFadeY); var edgeFade = Math.Min(edgeFadeX, edgeFadeY);
@@ -589,6 +589,11 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
IsOpen = false; IsOpen = false;
} }
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("You can view this window again in the settings (title menu)");
}
} }
} }

View File

@@ -7,6 +7,7 @@ using Dalamud.Interface.Utility;
using Lumina.Text; using Lumina.Text;
using System; using System;
using System.Numerics; using System.Numerics;
using System.Threading;
using DalamudSeString = Dalamud.Game.Text.SeStringHandling.SeString; using DalamudSeString = Dalamud.Game.Text.SeStringHandling.SeString;
using DalamudSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; using DalamudSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
@@ -15,6 +16,9 @@ namespace LightlessSync.Utils;
public static class SeStringUtils public static class SeStringUtils
{ {
private static int _seStringHitboxCounter;
private static int _iconHitboxCounter;
public static DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor) public static DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor)
{ {
var b = new DalamudSeStringBuilder(); var b = new DalamudSeStringBuilder();
@@ -119,7 +123,7 @@ public static class SeStringUtils
ImGui.Dummy(new Vector2(0f, textSize.Y)); ImGui.Dummy(new Vector2(0f, textSize.Y));
} }
public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null) public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null)
{ {
var drawList = ImGui.GetWindowDrawList(); var drawList = ImGui.GetWindowDrawList();
@@ -137,12 +141,28 @@ public static class SeStringUtils
var textSize = ImGui.CalcTextSize(seString.TextValue); var textSize = ImGui.CalcTextSize(seString.TextValue);
ImGui.SetCursorScreenPos(position); ImGui.SetCursorScreenPos(position);
ImGui.InvisibleButton($"##hitbox_{Guid.NewGuid()}", textSize); if (id is not null)
{
ImGui.PushID(id);
}
else
{
ImGui.PushID(Interlocked.Increment(ref _seStringHitboxCounter));
}
try
{
ImGui.InvisibleButton("##hitbox", textSize);
}
finally
{
ImGui.PopID();
}
return textSize; return textSize;
} }
public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null) public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null)
{ {
var drawList = ImGui.GetWindowDrawList(); var drawList = ImGui.GetWindowDrawList();
@@ -158,7 +178,23 @@ public static class SeStringUtils
var drawResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams); var drawResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams);
ImGui.SetCursorScreenPos(position); ImGui.SetCursorScreenPos(position);
ImGui.InvisibleButton($"##iconHitbox_{Guid.NewGuid()}", drawResult.Size); if (id is not null)
{
ImGui.PushID(id);
}
else
{
ImGui.PushID(Interlocked.Increment(ref _iconHitboxCounter));
}
try
{
ImGui.InvisibleButton("##iconHitbox", drawResult.Size);
}
finally
{
ImGui.PopID();
}
return drawResult.Size; return drawResult.Size;
} }

View File

@@ -215,6 +215,26 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
await Task.Delay(retryDelay, ct).ConfigureAwait(false); await Task.Delay(retryDelay, ct).ConfigureAwait(false);
} }
catch (TaskCanceledException ex) when (!ct.IsCancellationRequested)
{
response?.Dispose();
retryCount++;
Logger.LogWarning(ex, "Cancellation/timeout during download of {requestUrl}. Attempt {attempt} of {maxRetries}", requestUrl, retryCount, maxRetries);
if (retryCount >= maxRetries)
{
Logger.LogError("Max retries reached for {requestUrl} after TaskCanceledException", requestUrl);
throw;
}
await Task.Delay(retryDelay, ct).ConfigureAwait(false);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
response?.Dispose();
throw;
}
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
response?.Dispose(); response?.Dispose();

View File

@@ -84,7 +84,7 @@ public partial class ApiController
public async Task<UserProfileDto> UserGetProfile(UserDto dto) public async Task<UserProfileDto> UserGetProfile(UserDto dto)
{ {
if (!IsConnected) return new UserProfileDto(dto.User, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, Description: null, Tags: null); if (!IsConnected) return new UserProfileDto(dto.User, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, Description: null, BannerPictureBase64: null, Tags: null);
return await _lightlessHub!.InvokeAsync<UserProfileDto>(nameof(UserGetProfile), dto).ConfigureAwait(false); return await _lightlessHub!.InvokeAsync<UserProfileDto>(nameof(UserGetProfile), dto).ConfigureAwait(false);
} }

View File

@@ -107,17 +107,17 @@ public partial class ApiController
} }
public Task Client_ReceiveBroadcastPairRequest(UserPairNotificationDto dto) public Task Client_ReceiveBroadcastPairRequest(UserPairNotificationDto dto)
{ {
if (dto == null) Logger.LogDebug("Client_ReceiveBroadcastPairRequest: {dto}", dto);
if (dto is null)
{
return Task.CompletedTask; return Task.CompletedTask;
}
var request = _pairRequestService.RegisterIncomingRequest(dto.myHashedCid, dto.message ?? string.Empty); ExecuteSafely(() =>
var senderName = string.IsNullOrEmpty(request.DisplayName) ? "Unknown User" : request.DisplayName; {
Mediator.Publish(new PairRequestReceivedMessage(dto.myHashedCid, dto.message ?? string.Empty));
_lightlessNotificationService.ShowPairRequestNotification( });
senderName,
request.HashedCid,
onAccept: () => _pairRequestService.AcceptPairRequest(request.HashedCid, senderName),
onDecline: () => _pairRequestService.DeclinePairRequest(request.HashedCid));
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -118,7 +118,7 @@ public partial class ApiController
public async Task<GroupProfileDto> GroupGetProfile(GroupDto dto) public async Task<GroupProfileDto> GroupGetProfile(GroupDto dto)
{ {
CheckConnection(); CheckConnection();
if (!IsConnected) return new GroupProfileDto(Group: dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false); if (!IsConnected) return new GroupProfileDto(Group: dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, BannerBase64: null, IsDisabled: false);
return await _lightlessHub!.InvokeAsync<GroupProfileDto>(nameof(GroupGetProfile), dto).ConfigureAwait(false); return await _lightlessHub!.InvokeAsync<GroupProfileDto>(nameof(GroupGetProfile), dto).ConfigureAwait(false);
} }

View File

@@ -32,7 +32,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private readonly TokenProvider _tokenProvider; private readonly TokenProvider _tokenProvider;
private readonly LightlessConfigService _lightlessConfigService; private readonly LightlessConfigService _lightlessConfigService;
private readonly NotificationService _lightlessNotificationService;
private CancellationTokenSource _connectionCancellationTokenSource; private CancellationTokenSource _connectionCancellationTokenSource;
private ConnectionDto? _connectionDto; private ConnectionDto? _connectionDto;
private bool _doNotNotifyOnNextInfo = false; private bool _doNotNotifyOnNextInfo = false;
@@ -54,7 +53,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
_serverManager = serverManager; _serverManager = serverManager;
_tokenProvider = tokenProvider; _tokenProvider = tokenProvider;
_lightlessConfigService = lightlessConfigService; _lightlessConfigService = lightlessConfigService;
_lightlessNotificationService = lightlessNotificationService;
_connectionCancellationTokenSource = new CancellationTokenSource(); _connectionCancellationTokenSource = new CancellationTokenSource();
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn()); Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());