Some checks failed
Tag and Release Lightless / tag-and-release (push) Failing after 32s
 Lightless 1.12.0 is HERE! In this major update, we are introducing something we've been working on and testing for the last couple of weeks. In this update we are introducing a new (**OPTIONAL**) feature called **LightFinder**! We took inspiration from FFXIV's very own Party Finder and decided to implement something that allows users to not only look for fellow Lightless users, but also put up their Syncshell for others looking to join a sync community! When you enable LightFinder, you will be visible to other LightFinder users for **3 hours** or when you want to disabled it. When the 3 hours are up, you can either leave it disabled or enable it again for another 3 hours. The tag shown above will show above your nameplate, and you will be able to receive pair requests in your UI from other users with LightFinder enabled without having to input their uid!  Are you at a Venue? In Limsa? Partying in the streets of Uldah? If you're looking for fellow Lightless users you can now enable LightFinder and you will be shown to others who also have LightFinder enabled! Looking for a Syncshell to join? Enable LightFinder to see what SyncShells are available to join! Want to advertise your Syncshell? Choose the syncshell you want to put up in LightFinder and enable LightFinder. **IMPORTANT: We want to stress the fact that, if you just want to sync with just your friends and no one else, this does not take that away. No one will know you use Lightless unless you choose to tell them, or use this **OPTIONAL** feature. Your privacy is still maintained if you don't want to use the feature.** # Major Changes ## LightFinder - **OPTIONAL FEATURE** - **DOES NOT AFFECT YOU IF YOU DON'T WANT TO USE IT**  * New **OPTIONAL** syncing feature where one can enable something like a Party Finder so that other Lightless users in the area can see you are looking for people to sync with. Enable it by clicking the compass button and then the `Enable LightFinder` button.  * [L] Send Pair Request has been added to player context menus. You should still be able to send a request without Lightfinder on BUT you will need to know the other player is using Lightless and have them send a pair request back.  * When in LightFinder mode, for X mins you will be visible to all Lightless Users WHO ALSO HAVE LIGHTFINDER ON and will receive notifications of people wanting to pair with you. If you are the person using LightFinder, you understand the risks of pairing with someone you don't know. If you are the person sending a request to someone with LightFinder on, you also understand the risks of pairing with someone you don't know. **AGAIN, THIS IS OPTIONAL.** * When in LightFinder mode, you can also put up a Syncshell you own on the Syncshell Finder so that others can easily find it and join. This has to be done prior to enabling LightFinder.  * Syncshell Finder allows you to join Syncshells that are indexed by LightFinder  # Minor Changes * Vanity addition to our supporters: On top of vanity ids you can find a fun addition under Your User Settings -> Edit Lightless Profile -> Vanity Settings to change the colour of your name in the lightless ui. This will be shown to all users.  * Pairing nameplate colour override can also now override FC tag (new option_ * Bunch of UI fixes, updates, and changes * kdb is now a whitelisted filetype that can upload Co-authored-by: CakeAndBanana <admin@cakeandbanana.nl> Co-authored-by: azyges <aaaaaa@aaa.aaa> Co-authored-by: choco <thijmenhogenkamp@gmail.com> Co-authored-by: choco <choco@noreply.git.lightless-sync.org> Co-authored-by: defnotken <itsdefnotken@gmail.com> Reviewed-on: #39
395 lines
19 KiB
C#
395 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<ClearProfileDataMessage>(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.GetLightlessProfile(new UserData(_apiController.UID));
|
|
|
|
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))
|
|
.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));
|
|
}
|
|
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));
|
|
}
|
|
_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));
|
|
}
|
|
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, ""));
|
|
}
|
|
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.");
|
|
|
|
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();
|
|
}
|
|
} |