Files
LightlessClient/LightlessSync/UI/SyncshellFinderUI.cs
2025-11-27 00:29:56 +09:00

338 lines
13 KiB
C#

using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using LightlessSync.UI.Services;
using Microsoft.Extensions.Logging;
using System.Numerics;
namespace LightlessSync.UI;
public class SyncshellFinderUI : WindowMediatorSubscriberBase
{
private readonly ApiController _apiController;
private readonly BroadcastService _broadcastService;
private readonly UiSharedService _uiSharedService;
private readonly BroadcastScannerService _broadcastScannerService;
private readonly PairUiService _pairUiService;
private readonly DalamudUtilService _dalamudUtilService;
private readonly List<GroupJoinDto> _nearbySyncshells = [];
private List<GroupFullInfoDto> _currentSyncshells = [];
private int _selectedNearbyIndex = -1;
private readonly HashSet<string> _recentlyJoined = new(StringComparer.Ordinal);
private GroupJoinDto? _joinDto;
private GroupJoinInfoDto? _joinInfo;
private DefaultPermissionsDto _ownPermissions = null!;
public SyncshellFinderUI(
ILogger<SyncshellFinderUI> logger,
LightlessMediator mediator,
PerformanceCollectorService performanceCollectorService,
BroadcastService broadcastService,
UiSharedService uiShared,
ApiController apiController,
BroadcastScannerService broadcastScannerService,
PairUiService pairUiService,
DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
{
_broadcastService = broadcastService;
_uiSharedService = uiShared;
_apiController = apiController;
_broadcastScannerService = broadcastScannerService;
_pairUiService = pairUiService;
_dalamudUtilService = dalamudUtilService;
IsOpen = false;
SizeConstraints = new()
{
MinimumSize = new(600, 400),
MaximumSize = new(600, 550)
};
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
}
public override async void OnOpen()
{
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
await RefreshSyncshellsAsync().ConfigureAwait(false);
}
protected override void DrawInternal()
{
_uiSharedService.MediumText("Nearby Syncshells", UIColors.Get("PairBlue"));
_uiSharedService.ColoredSeparator(UIColors.Get("PairBlue"));
if (_nearbySyncshells.Count == 0)
{
ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby syncshells are being broadcasted.");
if (!_broadcastService.IsBroadcasting)
{
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"));
ImGui.TextColored(UIColors.Get("LightlessYellow"), "Lightfinder is currently disabled, to locate nearby syncshells, Lightfinder must be active.");
ImGuiHelpers.ScaledDummy(0.5f);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 10.0f);
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("PairBlue"));
if (ImGui.Button("Open Lightfinder", new Vector2(200 * ImGuiHelpers.GlobalScale, 0)))
{
Mediator.Publish(new UiToggleMessage(typeof(BroadcastUI)));
}
ImGui.PopStyleColor();
ImGui.PopStyleVar();
return;
}
return;
}
DrawSyncshellTable();
if (_joinDto != null && _joinInfo != null && _joinInfo.Success)
DrawConfirmation();
}
private void DrawSyncshellTable()
{
if (ImGui.BeginTable("##NearbySyncshellsTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
{
ImGui.TableSetupColumn("Syncshell", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Broadcaster", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale);
ImGui.TableHeadersRow();
foreach (var shell in _nearbySyncshells)
{
// Check if there is an active broadcast for this syncshell, if not, skipping this syncshell
var broadcast = _broadcastScannerService.GetActiveSyncshellBroadcasts()
.FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal));
if (broadcast == null)
continue; // no active broadcasts
var (Name, Address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID);
if (string.IsNullOrEmpty(Name))
continue; // broadcaster not found in area, skipping
ImGui.TableNextRow();
ImGui.TableNextColumn();
var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID;
ImGui.TextUnformatted(displayName);
ImGui.TableNextColumn();
var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(Address);
var broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{Name} ({worldName})" : Name;
ImGui.TextUnformatted(broadcasterName);
ImGui.TableNextColumn();
var label = $"Join##{shell.Group.GID}";
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen"));
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));
var isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal));
var isRecentlyJoined = _recentlyJoined.Contains(shell.GID);
if (!isAlreadyMember && !isRecentlyJoined)
{
if (ImGui.Button(label))
{
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
_ = Task.Run(async () =>
{
try
{
var info = await _apiController.GroupJoinHashed(new GroupJoinHashedDto(
shell.Group,
shell.Password,
shell.GroupUserPreferredPermissions
)).ConfigureAwait(false);
if (info != null && info.Success)
{
_joinDto = new GroupJoinDto(shell.Group, shell.Password, shell.GroupUserPreferredPermissions);
_joinInfo = info;
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
_logger.LogInformation($"Fetched join info for {shell.Group.GID}");
}
else
{
_logger.LogWarning($"Failed to join {shell.Group.GID}: info was null or unsuccessful");
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Join failed for {shell.Group.GID}");
}
});
}
}
else
{
using (ImRaii.Disabled())
{
ImGui.Button(label);
}
UiSharedService.AttachToolTip("Already a member or owner of this Syncshell.");
}
ImGui.PopStyleColor(3);
}
ImGui.EndTable();
}
}
private void DrawConfirmation()
{
if (_joinDto != null && _joinInfo != null)
{
ImGui.Separator();
ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}");
ImGuiHelpers.ScaledDummy(2f);
ImGui.TextUnformatted("Suggested Syncshell Permissions:");
DrawPermissionRow("Sounds", _joinInfo.GroupPermissions.IsPreferDisableSounds(), _ownPermissions.DisableGroupSounds, v => _ownPermissions.DisableGroupSounds = v);
DrawPermissionRow("Animations", _joinInfo.GroupPermissions.IsPreferDisableAnimations(), _ownPermissions.DisableGroupAnimations, v => _ownPermissions.DisableGroupAnimations = v);
DrawPermissionRow("VFX", _joinInfo.GroupPermissions.IsPreferDisableVFX(), _ownPermissions.DisableGroupVFX, v => _ownPermissions.DisableGroupVFX = v);
ImGui.NewLine();
ImGui.NewLine();
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Plus, $"Finalize and join {_joinDto.Group.AliasOrGID}"))
{
var finalPermissions = GroupUserPreferredPermissions.NoneSet;
finalPermissions.SetDisableSounds(_ownPermissions.DisableGroupSounds);
finalPermissions.SetDisableAnimations(_ownPermissions.DisableGroupAnimations);
finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX);
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
_recentlyJoined.Add(_joinDto.Group.GID);
_joinDto = null;
_joinInfo = null;
}
}
}
private void DrawPermissionRow(string label, bool suggested, bool current, Action<bool> apply)
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted($"- {label}");
ImGui.SameLine(150 * ImGuiHelpers.GlobalScale);
ImGui.TextUnformatted("Current:");
ImGui.SameLine();
_uiSharedService.BooleanToColoredIcon(!current);
ImGui.SameLine(300 * ImGuiHelpers.GlobalScale);
ImGui.TextUnformatted("Suggested:");
ImGui.SameLine();
_uiSharedService.BooleanToColoredIcon(!suggested);
ImGui.SameLine(450 * ImGuiHelpers.GlobalScale);
using var id = ImRaii.PushId(label);
if (current != suggested)
{
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowRight, "Apply"))
apply(suggested);
}
ImGui.NewLine();
}
private async Task RefreshSyncshellsAsync()
{
var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
var snapshot = _pairUiService.GetSnapshot();
_currentSyncshells = snapshot.GroupPairs.Keys.ToList();
_recentlyJoined.RemoveWhere(gid => _currentSyncshells.Any(s => string.Equals(s.GID, gid, StringComparison.Ordinal)));
if (syncshellBroadcasts.Count == 0)
{
ClearSyncshells();
return;
}
List<GroupJoinDto>? updatedList = [];
try
{
var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false);
updatedList = groups?.ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to refresh broadcasted syncshells.");
return;
}
if (updatedList != null)
{
var previousGid = GetSelectedGid();
_nearbySyncshells.Clear();
_nearbySyncshells.AddRange(updatedList);
if (previousGid != null)
{
var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal));
if (newIndex >= 0)
{
_selectedNearbyIndex = newIndex;
return;
}
}
}
ClearSelection();
}
private void ClearSyncshells()
{
if (_nearbySyncshells.Count == 0)
return;
_nearbySyncshells.Clear();
ClearSelection();
}
private void ClearSelection()
{
_selectedNearbyIndex = -1;
_joinDto = null;
_joinInfo = null;
}
private string? GetSelectedGid()
{
if (_selectedNearbyIndex < 0 || _selectedNearbyIndex >= _nearbySyncshells.Count)
return null;
return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
}
}