397 lines
19 KiB
C#
397 lines
19 KiB
C#
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.Dto.User;
|
|
using LightlessSync.Services;
|
|
using LightlessSync.Services.Mediator;
|
|
using LightlessSync.UI.Style;
|
|
using LightlessSync.Utils;
|
|
using LightlessSync.WebAPI;
|
|
using Microsoft.Extensions.Logging;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using System.Numerics;
|
|
|
|
namespace LightlessSync.UI;
|
|
|
|
public class EditProfileUi : WindowMediatorSubscriberBase
|
|
{
|
|
private readonly ApiController _apiController;
|
|
private readonly FileDialogManager _fileDialogManager;
|
|
private readonly LightlessProfileManager _lightlessProfileManager;
|
|
private readonly UiSharedService _uiSharedService;
|
|
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 bool _wasOpen;
|
|
|
|
private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f);
|
|
private bool vanityInitialized; // useless for now
|
|
private bool textEnabled;
|
|
private bool glowEnabled;
|
|
private Vector4 textColor;
|
|
private Vector4 glowColor;
|
|
|
|
private record VanityState(bool TextEnabled, bool GlowEnabled, Vector4 TextColor, Vector4 GlowColor);
|
|
private VanityState _savedVanity;
|
|
|
|
public EditProfileUi(ILogger<EditProfileUi> logger, LightlessMediator mediator,
|
|
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
|
|
LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService)
|
|
: base(logger, mediator, "Lightless Sync Edit Profile###LightlessSyncEditProfileUI", performanceCollectorService)
|
|
{
|
|
IsOpen = false;
|
|
this.SizeConstraints = new()
|
|
{
|
|
MinimumSize = new(850, 640),
|
|
MaximumSize = new(850, 700)
|
|
};
|
|
_apiController = apiController;
|
|
_uiSharedService = uiSharedService;
|
|
_fileDialogManager = fileDialogManager;
|
|
_lightlessProfileManager = lightlessProfileManager;
|
|
|
|
Mediator.Subscribe<GposeStartMessage>(this, (_) => { _wasOpen = IsOpen; IsOpen = false; });
|
|
Mediator.Subscribe<GposeEndMessage>(this, (_) => IsOpen = _wasOpen);
|
|
Mediator.Subscribe<DisconnectedMessage>(this, (_) => IsOpen = false);
|
|
Mediator.Subscribe<ClearProfileUserDataMessage>(this, (msg) =>
|
|
{
|
|
if (msg.UserData == null || string.Equals(msg.UserData.UID, _apiController.UID, StringComparison.Ordinal))
|
|
{
|
|
_pfpTextureWrap?.Dispose();
|
|
_pfpTextureWrap = null;
|
|
}
|
|
});
|
|
Mediator.Subscribe<ConnectedMessage>(this, msg =>
|
|
{
|
|
LoadVanity();
|
|
});
|
|
}
|
|
|
|
private void LoadVanity()
|
|
{
|
|
textEnabled = !string.IsNullOrEmpty(_apiController.TextColorHex);
|
|
glowEnabled = !string.IsNullOrEmpty(_apiController.TextGlowColorHex);
|
|
|
|
textColor = textEnabled ? UIColors.HexToRgba(_apiController.TextColorHex!) : Vector4.One;
|
|
glowColor = glowEnabled ? UIColors.HexToRgba(_apiController.TextGlowColorHex!) : Vector4.Zero;
|
|
|
|
_savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor);
|
|
vanityInitialized = true;
|
|
}
|
|
|
|
protected override void DrawInternal()
|
|
{
|
|
|
|
_uiSharedService.UnderlinedBigText("Notes and Rules for Profiles", UIColors.Get("LightlessYellow"));
|
|
ImGui.Dummy(new Vector2(5));
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, 1));
|
|
|
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile picture and description.");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules.");
|
|
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)");
|
|
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.");
|
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in profile settings.");
|
|
|
|
ImGui.PopStyleVar();
|
|
|
|
ImGui.Dummy(new Vector2(3));
|
|
|
|
var profile = _lightlessProfileManager.GetLightlessUserProfile(new UserData(_apiController.UID));
|
|
_logger.LogInformation("Profile fetched for drawing: {profile}", profile);
|
|
|
|
if (ImGui.BeginTabBar("##EditProfileTabs"))
|
|
{
|
|
if (ImGui.BeginTabItem("Current Profile"))
|
|
{
|
|
_uiSharedService.MediumText("Current Profile (as saved on server)", UIColors.Get("LightlessPurple"));
|
|
ImGui.Dummy(new Vector2(5));
|
|
|
|
if (profile.IsFlagged)
|
|
{
|
|
UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed);
|
|
return;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
var nsfw = profile.IsNSFW;
|
|
ImGui.BeginDisabled();
|
|
ImGui.Checkbox("Is NSFW", ref nsfw);
|
|
ImGui.EndDisabled();
|
|
|
|
_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<Rgba32>(fileContent);
|
|
|
|
if (image.Width > 256 || image.Height > 256 || (fileContent.Length > 250 * 1024))
|
|
{
|
|
_showFileDialogError = true;
|
|
return;
|
|
}
|
|
|
|
_showFileDialogError = false;
|
|
await _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, Convert.ToBase64String(fileContent), Description: null, Tags: null))
|
|
.ConfigureAwait(false);
|
|
});
|
|
});
|
|
}
|
|
UiSharedService.AttachToolTip("Select and upload a new profile picture");
|
|
ImGui.SameLine();
|
|
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));
|
|
}
|
|
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);
|
|
}
|
|
var isNsfw = profile.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));
|
|
}
|
|
_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.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, _descriptionText, Tags: null));
|
|
}
|
|
UiSharedService.AttachToolTip("Sets your profile description text");
|
|
ImGui.SameLine();
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description"))
|
|
{
|
|
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, "", Tags: null));
|
|
}
|
|
UiSharedService.AttachToolTip("Clears your profile description text");
|
|
|
|
ImGui.EndTabItem();
|
|
}
|
|
|
|
if (ImGui.BeginTabItem("Vanity Settings"))
|
|
{
|
|
_uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple"));
|
|
ImGui.Dummy(new Vector2(4));
|
|
_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;
|
|
|
|
if (!hasVanity)
|
|
{
|
|
UiSharedService.ColorTextWrapped("You do not currently have vanity access. Become a supporter to unlock these features.", UIColors.Get("DimRed"));
|
|
ImGui.Dummy(new Vector2(8));
|
|
ImGui.BeginDisabled();
|
|
}
|
|
|
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
|
_uiSharedService.MediumText("Colored UID", UIColors.Get("LightlessPurple"));
|
|
ImGui.Dummy(new Vector2(5));
|
|
|
|
var font = UiBuilder.MonoFont;
|
|
var playerUID = _apiController.UID;
|
|
var playerDisplay = _apiController.DisplayName;
|
|
|
|
var previewTextColor = textEnabled ? textColor : Vector4.One;
|
|
var previewGlowColor = glowEnabled ? glowColor : Vector4.Zero;
|
|
|
|
var seString = SeStringUtils.BuildFormattedPlayerName(playerDisplay, previewTextColor, previewGlowColor);
|
|
|
|
using (ImRaii.PushFont(font))
|
|
{
|
|
var drawList = ImGui.GetWindowDrawList();
|
|
var textSize = ImGui.CalcTextSize(seString.TextValue);
|
|
|
|
float minWidth = 150f * ImGuiHelpers.GlobalScale;
|
|
float bgWidth = Math.Max(textSize.X + 20f, minWidth);
|
|
|
|
float paddingY = 5f * ImGuiHelpers.GlobalScale;
|
|
|
|
var cursor = ImGui.GetCursorScreenPos();
|
|
|
|
var rectMin = cursor;
|
|
var rectMax = rectMin + new Vector2(bgWidth, textSize.Y + (paddingY * 2f));
|
|
|
|
float boost = Luminance.ComputeHighlight(previewTextColor, previewGlowColor);
|
|
|
|
var baseBg = new Vector4(0.15f + boost, 0.15f + boost, 0.15f + boost, 1f);
|
|
var bgColor = Luminance.BackgroundContrast(previewTextColor, previewGlowColor, baseBg, ref _currentBg);
|
|
|
|
var borderColor = UIColors.Get("LightlessPurple");
|
|
|
|
drawList.AddRectFilled(rectMin, rectMax, ImGui.GetColorU32(bgColor), 6.0f);
|
|
drawList.AddRect(rectMin, rectMax, ImGui.GetColorU32(borderColor), 6.0f, ImDrawFlags.None, 1.5f);
|
|
|
|
var textPos = new Vector2(
|
|
rectMin.X + (bgWidth - textSize.X) * 0.5f,
|
|
rectMin.Y + paddingY
|
|
);
|
|
|
|
SeStringUtils.RenderSeStringWithHitbox(seString, textPos, font);
|
|
|
|
ImGui.Dummy(new Vector2(5));
|
|
}
|
|
|
|
const float colorPickAlign = 90f;
|
|
|
|
_uiSharedService.DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Text Color");
|
|
ImGui.SameLine(colorPickAlign);
|
|
ImGui.Checkbox("##toggleTextColor", ref textEnabled);
|
|
ImGui.SameLine();
|
|
ImGui.BeginDisabled(!textEnabled);
|
|
ImGui.ColorEdit4($"##color_text", ref textColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
|
|
ImGui.EndDisabled();
|
|
|
|
_uiSharedService.DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Glow Color");
|
|
ImGui.SameLine(colorPickAlign);
|
|
ImGui.Checkbox("##toggleGlowColor", ref glowEnabled);
|
|
ImGui.SameLine();
|
|
ImGui.BeginDisabled(!glowEnabled);
|
|
ImGui.ColorEdit4($"##color_glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
|
|
ImGui.EndDisabled();
|
|
|
|
bool changed = !Equals(_savedVanity, new VanityState(textEnabled, glowEnabled, textColor, glowColor));
|
|
|
|
if (!changed)
|
|
ImGui.BeginDisabled();
|
|
|
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Changes"))
|
|
{
|
|
string? newText = textEnabled ? UIColors.RgbaToHex(textColor) : string.Empty;
|
|
string? newGlow = glowEnabled ? UIColors.RgbaToHex(glowColor) : string.Empty;
|
|
|
|
_ = _apiController.UserUpdateVanityColors(new UserVanityColorsDto(newText, newGlow));
|
|
|
|
_savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor);
|
|
}
|
|
|
|
if (!changed)
|
|
ImGui.EndDisabled();
|
|
|
|
ImGui.Dummy(new Vector2(5));
|
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
|
|
|
if (!hasVanity)
|
|
ImGui.EndDisabled();
|
|
|
|
ImGui.EndTabItem();
|
|
}
|
|
|
|
ImGui.EndTabBar();
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
_pfpTextureWrap?.Dispose();
|
|
}
|
|
} |