diff --git a/LightlessSync/Services/UiFactory.cs b/LightlessSync/Services/UiFactory.cs index b8ce902..248eae0 100644 --- a/LightlessSync/Services/UiFactory.cs +++ b/LightlessSync/Services/UiFactory.cs @@ -1,4 +1,5 @@ -using LightlessSync.API.Dto.Group; +using Dalamud.Interface.ImGuiFileDialog; +using LightlessSync.API.Dto.Group; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; @@ -18,10 +19,11 @@ public class UiFactory private readonly ServerConfigurationManager _serverConfigManager; private readonly LightlessProfileManager _lightlessProfileManager; private readonly PerformanceCollectorService _performanceCollectorService; + private readonly FileDialogManager _fileDialogManager; public UiFactory(ILoggerFactory loggerFactory, LightlessMediator lightlessMediator, ApiController apiController, UiSharedService uiSharedService, PairManager pairManager, ServerConfigurationManager serverConfigManager, - LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService) + LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService, FileDialogManager fileDialogManager) { _loggerFactory = loggerFactory; _lightlessMediator = lightlessMediator; @@ -31,12 +33,13 @@ public class UiFactory _serverConfigManager = serverConfigManager; _lightlessProfileManager = lightlessProfileManager; _performanceCollectorService = performanceCollectorService; + _fileDialogManager = fileDialogManager; } public SyncshellAdminUI CreateSyncshellAdminUi(GroupFullInfoDto dto) { return new SyncshellAdminUI(_loggerFactory.CreateLogger(), _lightlessMediator, - _apiController, _uiSharedService, _pairManager, dto, _performanceCollectorService, _lightlessProfileManager); + _apiController, _uiSharedService, _pairManager, dto, _performanceCollectorService, _lightlessProfileManager, _fileDialogManager); } public StandaloneProfileUi CreateStandaloneProfileUi(Pair pair) diff --git a/LightlessSync/UI/EditProfileUi.cs b/LightlessSync/UI/EditProfileUi.cs index d9db33b..8c84571 100644 --- a/LightlessSync/UI/EditProfileUi.cs +++ b/LightlessSync/UI/EditProfileUi.cs @@ -25,8 +25,8 @@ public class EditProfileUi : WindowMediatorSubscriberBase private readonly FileDialogManager _fileDialogManager; private readonly LightlessProfileManager _lightlessProfileManager; private readonly UiSharedService _uiSharedService; - private bool _adjustedForScollBarsLocalProfile = false; - private bool _adjustedForScollBarsOnlineProfile = false; + private bool _adjustedForScollBarsLocalProfile = false; + private bool _adjustedForScollBarsOnlineProfile = false; private string _descriptionText = string.Empty; private IDalamudTextureWrap? _pfpTextureWrap; private string _profileDescription = string.Empty; diff --git a/LightlessSync/UI/SyncshellAdminUI.cs b/LightlessSync/UI/SyncshellAdminUI.cs index fb30067..2904d3d 100644 --- a/LightlessSync/UI/SyncshellAdminUI.cs +++ b/LightlessSync/UI/SyncshellAdminUI.cs @@ -1,17 +1,24 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; +using Dalamud.Interface.ImGuiFileDialog; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using LightlessSync.API.Data; using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; +using LightlessSync.API.Dto.User; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; using LightlessSync.Services.Mediator; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; using System.Globalization; +using System.Numerics; namespace LightlessSync.UI; @@ -22,8 +29,17 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase private readonly bool _isOwner = false; private readonly List _oneTimeInvites = []; private readonly PairManager _pairManager; + private readonly LightlessProfileManager _lightlessProfileManager; + private readonly FileDialogManager _fileDialogManager; private readonly UiSharedService _uiSharedService; private List _bannedUsers = []; + private bool _adjustedForScollBarsLocalProfile = false; + private bool _adjustedForScollBarsOnlineProfile = false; + private string _descriptionText = string.Empty; + private IDalamudTextureWrap? _pfpTextureWrap; + private string _profileDescription = string.Empty; + private byte[] _profileImage = []; + private bool _showFileDialogError = false; private int _multiInvites; private string _newPassword; private bool _pwChangeSuccess; @@ -32,13 +48,16 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase private int _pruneDays = 14; public SyncshellAdminUI(ILogger logger, LightlessMediator mediator, ApiController apiController, - UiSharedService uiSharedService, PairManager pairManager, GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService) + UiSharedService uiSharedService, PairManager pairManager, GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService, LightlessProfileManager lightlessProfileManager, FileDialogManager fileDialogManager) : base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService) { GroupFullInfo = groupFullInfo; _apiController = apiController; _uiSharedService = uiSharedService; _pairManager = pairManager; + _lightlessProfileManager = lightlessProfileManager; + _fileDialogManager = fileDialogManager; + _isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal); _isModerator = GroupFullInfo.GroupUserInfo.IsModerator(); _newPassword = string.Empty; @@ -76,7 +95,9 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase DrawManagement(); - DrawPermission(perm); + DrawPermission(perm); + + DrawProfile(); } } @@ -176,6 +197,157 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase ownerTab.Dispose(); } } + private void DrawProfile() + { + var profile = _lightlessProfileManager.GetLightlessGroupProfile(new GroupData(GroupFullInfo.Group.GID)); + var profileTab = ImRaii.TabItem("Profile"); + + if (profileTab) + { + _uiSharedService.MediumText("Current Profile (as saved on server)", UIColors.Get("LightlessPurple")); + ImGui.Dummy(new Vector2(5)); + + if (!_profileImage.SequenceEqual(profile.ImageData.Value)) + { + _profileImage = profile.ImageData.Value; + _pfpTextureWrap?.Dispose(); + _pfpTextureWrap = _uiSharedService.LoadImage(_profileImage); + } + + if (!string.Equals(_profileDescription, profile.Description, StringComparison.OrdinalIgnoreCase)) + { + _profileDescription = profile.Description; + _descriptionText = _profileDescription; + } + + if (_pfpTextureWrap != null) + { + ImGui.Image(_pfpTextureWrap.Handle, ImGuiHelpers.ScaledVector2(_pfpTextureWrap.Width, _pfpTextureWrap.Height)); + } + + var spacing = ImGui.GetStyle().ItemSpacing.X; + ImGuiHelpers.ScaledRelativeSameLine(256, spacing); + using (_uiSharedService.GameFont.Push()) + { + var descriptionTextSize = ImGui.CalcTextSize(profile.Description, wrapWidth: 256f); + var childFrame = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 256); + if (descriptionTextSize.Y > childFrame.Y) + { + _adjustedForScollBarsOnlineProfile = true; + } + else + { + _adjustedForScollBarsOnlineProfile = false; + } + childFrame = childFrame with + { + X = childFrame.X + (_adjustedForScollBarsOnlineProfile ? ImGui.GetStyle().ScrollbarSize : 0), + }; + if (ImGui.BeginChildFrame(101, childFrame)) + { + UiSharedService.TextWrapped(profile.Description); + } + ImGui.EndChildFrame(); + } + _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Profile Settings")) + { + _uiSharedService.MediumText("Profile Settings", UIColors.Get("LightlessPurple")); + ImGui.Dummy(new Vector2(5)); + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture")) + { + _fileDialogManager.OpenFileDialog("Select new Profile picture", ".png", (success, file) => + { + if (!success) return; + _ = Task.Run(async () => + { + var fileContent = File.ReadAllBytes(file); + using MemoryStream ms = new(fileContent); + var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false); + if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) + { + _showFileDialogError = true; + return; + } + using var image = Image.Load(fileContent); + + if (image.Width > 256 || image.Height > 256 || (fileContent.Length > 250 * 1024)) + { + _showFileDialogError = true; + return; + } + + _showFileDialogError = false; + await _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.GID), Description: null, Tags: null, Convert.ToBase64String(fileContent))) + .ConfigureAwait(false); + }); + }); + } + UiSharedService.AttachToolTip("Select and upload a new profile picture"); + ImGui.SameLine(); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture")) + { + _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.GID), Description: null, Tags: null, PictureBase64: null)); + } + UiSharedService.AttachToolTip("Clear your currently uploaded profile picture"); + if (_showFileDialogError) + { + UiSharedService.ColorTextWrapped("The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size", ImGuiColors.DalamudRed); + } + + _uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON"); + var widthTextBox = 400; + var posX = ImGui.GetCursorPosX(); + ImGui.TextUnformatted($"Description {_descriptionText.Length}/1500"); + ImGui.SetCursorPosX(posX); + ImGuiHelpers.ScaledRelativeSameLine(widthTextBox, ImGui.GetStyle().ItemSpacing.X); + ImGui.TextUnformatted("Preview (approximate)"); + using (_uiSharedService.GameFont.Push()) + ImGui.InputTextMultiline("##description", ref _descriptionText, 1500, ImGuiHelpers.ScaledVector2(widthTextBox, 200)); + + ImGui.SameLine(); + + using (_uiSharedService.GameFont.Push()) + { + var descriptionTextSizeLocal = ImGui.CalcTextSize(_descriptionText, wrapWidth: 256f); + var childFrameLocal = ImGuiHelpers.ScaledVector2(256 + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().WindowBorderSize, 200); + if (descriptionTextSizeLocal.Y > childFrameLocal.Y) + { + _adjustedForScollBarsLocalProfile = true; + } + else + { + _adjustedForScollBarsLocalProfile = false; + } + childFrameLocal = childFrameLocal with + { + X = childFrameLocal.X + (_adjustedForScollBarsLocalProfile ? ImGui.GetStyle().ScrollbarSize : 0), + }; + if (ImGui.BeginChildFrame(102, childFrameLocal)) + { + UiSharedService.TextWrapped(_descriptionText); + } + ImGui.EndChildFrame(); + } + + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description")) + { + _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.GID), Description: _descriptionText, Tags: null, PictureBase64: null)); + } + UiSharedService.AttachToolTip("Sets your profile description text"); + ImGui.SameLine(); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description")) + { + _ = _apiController.GroupSetProfile(new GroupProfileDto(new GroupData(GroupFullInfo.Group.GID), Description: null, Tags: null, PictureBase64: null)); + } + UiSharedService.AttachToolTip("Clears your profile description text"); + } + profileTab.Dispose(); + } private void DrawManagement() { @@ -474,7 +646,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase ImGui.Separator(); } mgmtTab.Dispose(); - } private void DrawInvites(GroupPermissions perm)